3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author João Luis M. Assirati
7 * \author Lars Gullik Bjønnes
9 * Full author contact details are available in file CREDITS.
17 #include "support/ConsoleApplication.h"
18 #include "support/debug.h"
19 #include "support/FileName.h"
20 #include "support/FileNameList.h"
21 #include "support/lstrings.h"
22 #include "support/Messages.h"
23 #include "support/unicode.h"
24 #include "support/unique_ptr.h"
26 // getpid(), getppid()
27 #ifdef HAVE_SYS_TYPES_H
28 # include <sys/types.h>
35 #ifdef HAVE_SYS_TIME_H
36 # include <sys/time.h>
40 #ifdef HAVE_SYS_SELECT_H
41 # include <sys/select.h>
44 // socket(), connect()
45 #ifdef HAVE_SYS_SOCKET_H
46 # include <sys/socket.h>
67 using namespace lyx::support;
71 // Required global variables
75 // Keep the linker happy on Windows
79 // Dummy language support
80 Messages const & getGuiMessages()
82 static Messages lyx_messages;
88 Messages const & getMessages(string const &)
90 return getGuiMessages();
96 /// Returns the absolute pathnames of all lyx local sockets in
97 /// file system encoding.
98 /// Parts stolen from lyx::support::DirList().
99 FileNameList lyxSockets(string const & dir, string const & pid)
101 FileNameList dirlist;
103 FileName dirpath(dir + "/");
105 if (!dirpath.exists() || !dirpath.isDirectory()) {
106 lyxerr << dir << " does not exist or is not a directory."
111 FileNameList dirs = dirpath.dirList("");
112 FileNameList::const_iterator it = dirs.begin();
113 FileNameList::const_iterator end = dirs.end();
115 for (; it != end; ++it) {
116 if (!it->isDirectory())
118 string const tmpdir = it->absFileName();
119 if (!contains(tmpdir, "lyx_tmpdir" + pid))
122 FileName lyxsocket(tmpdir + "/lyxsocket");
123 if (lyxsocket.exists())
124 dirlist.push_back(lyxsocket);
131 namespace socktools {
134 /// Connect to the socket \p name.
135 int connect(FileName const & name)
137 int fd; // File descriptor for the socket
138 sockaddr_un addr; // Structure that hold the socket address
140 string const encoded = name.toFilesystemEncoding();
141 // char sun_path[108]
142 string::size_type len = encoded.size();
144 cerr << "lyxclient: Socket address '" << name
145 << "' too long." << endl;
148 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
149 addr.sun_family = AF_UNIX;
150 encoded.copy(addr.sun_path, 107);
151 addr.sun_path[len] = '\0';
153 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
154 cerr << "lyxclient: Could not create socket descriptor: "
155 << strerror(errno) << endl;
159 reinterpret_cast<struct sockaddr *>(&addr),
160 sizeof(addr)) == -1) {
161 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
162 << ": " << strerror(errno) << endl;
166 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
167 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
168 << strerror(errno) << endl;
176 } // namespace socktools
177 } // namespace support
181 /////////////////////////////////////////////////////////////////////
185 /////////////////////////////////////////////////////////////////////
207 void IOWatch::clear()
213 void IOWatch::addfd(int fd)
219 bool IOWatch::wait(double timeout)
222 to.tv_sec = static_cast<long int>(timeout);
223 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
225 return select(FD_SETSIZE, &act,
226 (fd_set *)0, (fd_set *)0, &to);
233 return select(FD_SETSIZE, &act,
234 (fd_set *)0, (fd_set *)0, (timeval *)0);
238 bool IOWatch::isset(int fd)
240 return FD_ISSET(fd, &act);
245 /////////////////////////////////////////////////////////////////////
249 /////////////////////////////////////////////////////////////////////
251 // Modified LyXDataSocket class for use with the client
252 class LyXDataSocket {
254 LyXDataSocket(FileName const &);
256 // File descriptor of the connection
259 bool connected() const;
260 // Line buffered input from the socket
261 bool readln(string &);
262 // Write the string + '\n' to the socket
263 void writeln(string const &);
265 // File descriptor for the data socket
267 // True if the connection is up
269 // buffer for input data
274 LyXDataSocket::LyXDataSocket(FileName const & address)
276 if ((fd_ = socktools::connect(address)) == -1) {
284 LyXDataSocket::~LyXDataSocket()
290 int LyXDataSocket::fd() const
296 bool LyXDataSocket::connected() const
302 // Returns true if there was a complete line to input
303 // A line is of the form <key>:<value>
304 // A line not of this form will not be passed
305 // The line read is split and stored in 'key' and 'value'
306 bool LyXDataSocket::readln(string & line)
308 int const charbuf_size = 100;
309 char charbuf[charbuf_size]; // buffer for the ::read() system call
311 string::size_type pos;
313 // read and store characters in buffer
314 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
315 charbuf[count] = '\0'; // turn it into a c string
319 // Error conditions. The buffer must still be
320 // processed for lines read
321 if (count == 0) { // EOF -- connection closed
323 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
324 cerr << "lyxclient: IO error." << endl;
328 // Cut a line from buffer
329 if ((pos = buffer.find('\n')) == string::npos)
330 return false; // No complete line stored
331 line = buffer.substr(0, pos);
332 buffer = buffer.substr(pos + 1);
337 // Write a line of the form <key>:<value> to the socket
338 void LyXDataSocket::writeln(string const & line)
340 string linen(line + '\n');
341 int size = linen.size();
342 int written = ::write(fd_, linen.c_str(), size);
343 if (written < size) { // Always mean end of connection.
344 if ((written == -1) && (errno == EPIPE)) {
345 // The program will also receive a SIGPIPE
346 // that must be caught
347 cerr << "lyxclient: connection closed while writing."
350 // Anything else, including errno == EAGAIN, must be
351 // considered IO error. EAGAIN should never happen
352 // when line is small
353 cerr << "lyxclient: IO error: " << strerror(errno);
360 /////////////////////////////////////////////////////////////////////
364 /////////////////////////////////////////////////////////////////////
366 class CmdLineParser {
368 typedef int (*optfunc)(vector<docstring> const & args);
369 map<string, optfunc> helper;
370 map<string, bool> isset;
371 bool parse(int, char * []);
372 vector<char *> nonopt;
376 bool CmdLineParser::parse(int argc, char * argv[])
380 vector<docstring> args;
381 if (helper[argv[opt]]) {
382 isset[argv[opt]] = true;
384 while ((arg < argc) && (!helper[argv[arg]])) {
385 args.push_back(from_local8bit(argv[arg]));
388 int taken = helper[argv[opt]](args);
393 if (argv[opt][0] == '-') {
394 if ((argv[opt][1] == '-')
395 && (argv[opt][2]== '\0')) {
398 nonopt.push_back(argv[opt]);
403 cerr << "lyxclient: unknown option "
404 << argv[opt] << endl;
408 nonopt.push_back(argv[opt]);
414 // ~Class CmdLineParser -------------------------------------------------------
420 docstring mainTmp(from_ascii("/tmp"));
423 class StopException : public exception
426 StopException(int status) : status_(status) {}
427 int status() const { return status_; }
436 "Usage: lyxclient [options]\n"
438 " -a address set address of the lyx socket\n"
439 " -t directory set system temporary directory (for detecting sockets)\n"
440 " -p pid select a running lyx by pidi\n"
441 " -c command send a single command and quit (LYXCMD prefix needed)\n"
442 " -g file row send a command to go to file and row\n"
443 " -n name set client name\n"
444 " -h name display this help end exit\n"
445 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
446 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
447 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
448 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
449 "will read commands from standard input and disconnect when command read is BYE:\n"
451 "System directory is: " << to_utf8(cmdline::mainTmp)
456 int h(vector<docstring> const &)
459 throw StopException(EXIT_SUCCESS);
463 docstring clientName =
464 from_ascii(to_string(::getppid()) + ">" + to_string(::getpid()));
466 int n(vector<docstring> const & arg)
469 cerr << "lyxclient: The option -n requires 1 argument."
478 docstring singleCommand;
481 int c(vector<docstring> const & arg)
484 cerr << "lyxclient: The option -c requires 1 argument."
488 singleCommand = arg[0];
493 int g(vector<docstring> const & arg)
495 if (arg.size() < 2) {
496 cerr << "lyxclient: The option -g requires 2 arguments."
500 singleCommand = "LYXCMD:command-sequence "
501 "server-goto-file-row "
509 // empty if LYXSOCKET is not set in the environment
510 docstring serverAddress;
513 int a(vector<docstring> const & arg)
516 cerr << "lyxclient: The option -a requires 1 argument."
520 // -a supersedes LYXSOCKET environment variable
521 serverAddress = arg[0];
528 int t(vector<docstring> const & arg)
531 cerr << "lyxclient: The option -t requires 1 argument."
540 string serverPid; // Init to empty string
543 int p(vector<docstring> const & arg)
546 cerr << "lyxclient: The option -p requires 1 argument."
550 serverPid = to_ascii(arg[0]);
555 } // namespace cmdline
557 /// The main application class
558 class LyXClientApp : public ConsoleApplication
561 LyXClientApp(int & argc, char * argv[])
562 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
563 argc_(argc), argv_(argv)
569 int const exit_status = run();
572 catch (cmdline::StopException & e) {
583 int LyXClientApp::run()
585 // qt changes this, and our numeric conversions require the C locale
586 setlocale(LC_NUMERIC, "C");
589 char const * const lyxsocket = getenv("LYXSOCKET");
591 cmdline::serverAddress = from_local8bit(lyxsocket);
594 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
596 // Command line builder
598 args.helper["-h"] = cmdline::h;
599 args.helper["-c"] = cmdline::c;
600 args.helper["-g"] = cmdline::g;
601 args.helper["-n"] = cmdline::n;
602 args.helper["-a"] = cmdline::a;
603 args.helper["-t"] = cmdline::t;
604 args.helper["-p"] = cmdline::p;
606 // Command line failure conditions:
607 if ((!args.parse(argc_, argv_))
608 || (args.isset["-c"] && args.isset["-g"])
609 || (args.isset["-a"] && args.isset["-p"])) {
614 unique_ptr<LyXDataSocket> server;
616 if (!cmdline::serverAddress.empty()) {
617 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
618 if (!server->connected()) {
619 cerr << "lyxclient: " << "Could not connect to "
620 << to_utf8(cmdline::serverAddress) << endl;
624 // We have to look for an address.
625 // serverPid can be empty.
626 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
627 FileNameList::const_iterator addr = addrs.begin();
628 FileNameList::const_iterator end = addrs.end();
629 for (; addr != end; ++addr) {
630 // Caution: addr->string() is in filesystem encoding
631 server.reset(new LyXDataSocket(*addr));
632 if (server->connected())
634 lyxerr << "lyxclient: " << "Could not connect to "
635 << addr->absFileName() << endl;
638 lyxerr << "lyxclient: No suitable server found."
642 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
645 int const serverfd = server->fd();
648 iowatch.addfd(serverfd);
650 // Used to read from server
654 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
655 // wait at most 2 seconds until server responds
657 if (iowatch.isset(serverfd) && server->readln(answer)) {
658 if (prefixIs(answer, "BYE:")) {
659 cerr << "lyxclient: Server disconnected." << endl;
660 cout << answer << endl;
664 cerr << "lyxclient: No answer from server." << endl;
668 if (args.isset["-g"] || args.isset["-c"]) {
669 server->writeln(to_utf8(cmdline::singleCommand));
671 if (iowatch.isset(serverfd) && server->readln(answer)) {
673 if (prefixIs(answer, "ERROR:"))
677 cerr << "lyxclient: No answer from server." << endl;
682 // Take commands from stdin
683 iowatch.addfd(0); // stdin
684 bool saidbye = false;
685 while ((!saidbye) && server->connected()) {
687 if (iowatch.isset(0)) {
689 getline(cin, command);
692 if (command == "BYE:") {
693 server->writeln("BYE:");
696 server->writeln("LYXCMD:" + command);
699 if (iowatch.isset(serverfd)) {
700 while(server->readln(answer))
701 cout << answer << endl;
711 int main(int argc, char * argv[])
713 lyx::lyxerr.setStream(cerr);
715 lyx::LyXClientApp app(argc, argv);