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.
15 #include "support/ConsoleApplication.h"
16 #include "support/debug.h"
17 #include "support/FileName.h"
18 #include "support/FileNameList.h"
19 #include "support/lstrings.h"
20 #include "support/Messages.h"
21 #include "support/unicode.h"
23 #include <boost/scoped_ptr.hpp>
25 // getpid(), getppid()
26 #ifdef HAVE_SYS_TYPES_H
27 # include <sys/types.h>
34 #ifdef HAVE_SYS_TIME_H
35 # include <sys/time.h>
39 #ifdef HAVE_SYS_SELECT_H
40 # include <sys/select.h>
43 // socket(), connect()
44 #ifdef HAVE_SYS_SOCKET_H
45 # include <sys/socket.h>
65 using namespace lyx::support;
67 using ::boost::scoped_ptr;
71 // Dummy LyXRC support
76 // Keep the linker happy on Windows
80 // Dummy language support
81 Messages const & getGuiMessages()
83 static Messages lyx_messages;
89 Messages const & getMessages(string const &)
91 return getGuiMessages();
97 string itoa(unsigned int i)
100 sprintf(buf, "%d", i);
105 /// Returns the absolute pathnames of all lyx local sockets in
106 /// file system encoding.
107 /// Parts stolen from lyx::support::DirList().
108 FileNameList lyxSockets(string const & dir, string const & pid)
110 FileNameList dirlist;
112 FileName dirpath(dir + "/");
114 if (!dirpath.exists() || !dirpath.isDirectory()) {
115 lyxerr << dir << " does not exist or is not a directory."
120 FileNameList dirs = dirpath.dirList("");
121 FileNameList::const_iterator it = dirs.begin();
122 FileNameList::const_iterator end = dirs.end();
124 for (; it != end; ++it) {
125 if (!it->isDirectory())
127 string const tmpdir = it->absFileName();
128 if (!contains(tmpdir, "lyx_tmpdir" + pid))
131 FileName lyxsocket(tmpdir + "/lyxsocket");
132 if (lyxsocket.exists())
133 dirlist.push_back(lyxsocket);
140 namespace socktools {
143 /// Connect to the socket \p name.
144 int connect(FileName const & name)
146 int fd; // File descriptor for the socket
147 sockaddr_un addr; // Structure that hold the socket address
149 string const encoded = name.toFilesystemEncoding();
150 // char sun_path[108]
151 string::size_type len = encoded.size();
153 cerr << "lyxclient: Socket address '" << name
154 << "' too long." << endl;
157 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
158 addr.sun_family = AF_UNIX;
159 encoded.copy(addr.sun_path, 107);
160 addr.sun_path[len] = '\0';
162 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
163 cerr << "lyxclient: Could not create socket descriptor: "
164 << strerror(errno) << endl;
168 reinterpret_cast<struct sockaddr *>(&addr),
169 sizeof(addr)) == -1) {
170 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
171 << ": " << strerror(errno) << endl;
175 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
176 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
177 << strerror(errno) << endl;
185 } // namespace socktools
186 } // namespace support
190 /////////////////////////////////////////////////////////////////////
194 /////////////////////////////////////////////////////////////////////
216 void IOWatch::clear()
222 void IOWatch::addfd(int fd)
228 bool IOWatch::wait(double timeout)
231 to.tv_sec = static_cast<long int>(timeout);
232 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
234 return select(FD_SETSIZE, &act,
235 (fd_set *)0, (fd_set *)0, &to);
242 return select(FD_SETSIZE, &act,
243 (fd_set *)0, (fd_set *)0, (timeval *)0);
247 bool IOWatch::isset(int fd)
249 return FD_ISSET(fd, &act);
254 /////////////////////////////////////////////////////////////////////
258 /////////////////////////////////////////////////////////////////////
260 // Modified LyXDataSocket class for use with the client
261 class LyXDataSocket {
263 LyXDataSocket(FileName const &);
265 // File descriptor of the connection
268 bool connected() const;
269 // Line buffered input from the socket
270 bool readln(string &);
271 // Write the string + '\n' to the socket
272 void writeln(string const &);
274 // File descriptor for the data socket
276 // True if the connection is up
278 // buffer for input data
283 LyXDataSocket::LyXDataSocket(FileName const & address)
285 if ((fd_ = socktools::connect(address)) == -1) {
293 LyXDataSocket::~LyXDataSocket()
299 int LyXDataSocket::fd() const
305 bool LyXDataSocket::connected() const
311 // Returns true if there was a complete line to input
312 // A line is of the form <key>:<value>
313 // A line not of this form will not be passed
314 // The line read is split and stored in 'key' and 'value'
315 bool LyXDataSocket::readln(string & line)
317 int const charbuf_size = 100;
318 char charbuf[charbuf_size]; // buffer for the ::read() system call
320 string::size_type pos;
322 // read and store characters in buffer
323 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
324 charbuf[count] = '\0'; // turn it into a c string
328 // Error conditions. The buffer must still be
329 // processed for lines read
330 if (count == 0) { // EOF -- connection closed
332 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
333 cerr << "lyxclient: IO error." << endl;
337 // Cut a line from buffer
338 if ((pos = buffer.find('\n')) == string::npos)
339 return false; // No complete line stored
340 line = buffer.substr(0, pos);
341 buffer = buffer.substr(pos + 1);
346 // Write a line of the form <key>:<value> to the socket
347 void LyXDataSocket::writeln(string const & line)
349 string linen(line + '\n');
350 int size = linen.size();
351 int written = ::write(fd_, linen.c_str(), size);
352 if (written < size) { // Allways mean end of connection.
353 if ((written == -1) && (errno == EPIPE)) {
354 // The program will also receive a SIGPIPE
355 // that must be catched
356 cerr << "lyxclient: connection closed while writing."
359 // Anything else, including errno == EAGAIN, must be
360 // considered IO error. EAGAIN should never happen
361 // when line is small
362 cerr << "lyxclient: IO error: " << strerror(errno);
369 /////////////////////////////////////////////////////////////////////
373 /////////////////////////////////////////////////////////////////////
375 class CmdLineParser {
377 typedef int (*optfunc)(vector<docstring> const & args);
378 map<string, optfunc> helper;
379 map<string, bool> isset;
380 bool parse(int, char * []);
381 vector<char *> nonopt;
385 bool CmdLineParser::parse(int argc, char * argv[])
389 vector<docstring> args;
390 if (helper[argv[opt]]) {
391 isset[argv[opt]] = true;
393 while ((arg < argc) && (!helper[argv[arg]])) {
394 args.push_back(from_local8bit(argv[arg]));
397 int taken = helper[argv[opt]](args);
402 if (argv[opt][0] == '-') {
403 if ((argv[opt][1] == '-')
404 && (argv[opt][2]== '\0')) {
407 nonopt.push_back(argv[opt]);
412 cerr << "lyxclient: unknown option "
413 << argv[opt] << endl;
417 nonopt.push_back(argv[opt]);
423 // ~Class CmdLineParser -------------------------------------------------------
429 docstring mainTmp(from_ascii("/tmp"));
432 class StopException : public exception
435 StopException(int status) : status_(status) {}
436 int status() const { return status_; }
445 "Usage: lyxclient [options]\n"
447 " -a address set address of the lyx socket\n"
448 " -t directory set system temporary directory (for detecting sockets)\n"
449 " -p pid select a running lyx by pidi\n"
450 " -c command send a single command and quit (LYXCMD prefix needed)\n"
451 " -g file row send a command to go to file and row\n"
452 " -n name set client name\n"
453 " -h name display this help end exit\n"
454 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
455 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
456 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
457 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
458 "will read commands from standard input and disconnect when command read is BYE:\n"
460 "System directory is: " << to_utf8(cmdline::mainTmp)
465 int h(vector<docstring> const &)
468 throw StopException(EXIT_SUCCESS);
472 docstring clientName =
473 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
475 int n(vector<docstring> const & arg)
478 cerr << "lyxclient: The option -n requires 1 argument."
487 docstring singleCommand;
490 int c(vector<docstring> const & arg)
493 cerr << "lyxclient: The option -c requires 1 argument."
497 singleCommand = arg[0];
502 int g(vector<docstring> const & arg)
504 if (arg.size() < 2) {
505 cerr << "lyxclient: The option -g requires 2 arguments."
509 singleCommand = "LYXCMD:server-goto-file-row "
516 // empty if LYXSOCKET is not set in the environment
517 docstring serverAddress;
520 int a(vector<docstring> const & arg)
523 cerr << "lyxclient: The option -a requires 1 argument."
527 // -a supercedes LYXSOCKET environment variable
528 serverAddress = arg[0];
535 int t(vector<docstring> const & arg)
538 cerr << "lyxclient: The option -t requires 1 argument."
547 string serverPid; // Init to empty string
550 int p(vector<docstring> const & arg)
553 cerr << "lyxclient: The option -p requires 1 argument."
557 serverPid = to_ascii(arg[0]);
562 } // namespace cmdline
564 /// The main application class
565 class LyXClientApp : public ConsoleApplication
568 LyXClientApp(int & argc, char * argv[])
569 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
570 argc_(argc), argv_(argv)
576 int const exit_status = run();
579 catch (cmdline::StopException & e) {
590 int LyXClientApp::run()
592 // qt changes this, and our numeric conversions require the C locale
593 setlocale(LC_NUMERIC, "C");
596 char const * const lyxsocket = getenv("LYXSOCKET");
598 cmdline::serverAddress = from_local8bit(lyxsocket);
601 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
603 // Command line builder
605 args.helper["-h"] = cmdline::h;
606 args.helper["-c"] = cmdline::c;
607 args.helper["-g"] = cmdline::g;
608 args.helper["-n"] = cmdline::n;
609 args.helper["-a"] = cmdline::a;
610 args.helper["-t"] = cmdline::t;
611 args.helper["-p"] = cmdline::p;
613 // Command line failure conditions:
614 if ((!args.parse(argc_, argv_))
615 || (args.isset["-c"] && args.isset["-g"])
616 || (args.isset["-a"] && args.isset["-p"])) {
621 scoped_ptr<LyXDataSocket> server;
623 if (!cmdline::serverAddress.empty()) {
624 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
625 if (!server->connected()) {
626 cerr << "lyxclient: " << "Could not connect to "
627 << to_utf8(cmdline::serverAddress) << endl;
631 // We have to look for an address.
632 // serverPid can be empty.
633 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
634 FileNameList::const_iterator addr = addrs.begin();
635 FileNameList::const_iterator end = addrs.end();
636 for (; addr != end; ++addr) {
637 // Caution: addr->string() is in filesystem encoding
638 server.reset(new LyXDataSocket(*addr));
639 if (server->connected())
641 lyxerr << "lyxclient: " << "Could not connect to "
642 << addr->absFileName() << endl;
645 lyxerr << "lyxclient: No suitable server found."
649 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
652 int const serverfd = server->fd();
655 iowatch.addfd(serverfd);
657 // Used to read from server
661 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
662 // wait at most 2 seconds until server responds
664 if (iowatch.isset(serverfd) && server->readln(answer)) {
665 if (prefixIs(answer, "BYE:")) {
666 cerr << "lyxclient: Server disconnected." << endl;
667 cout << answer << endl;
671 cerr << "lyxclient: No answer from server." << endl;
675 if (args.isset["-g"] || args.isset["-c"]) {
676 server->writeln(to_utf8(cmdline::singleCommand));
678 if (iowatch.isset(serverfd) && server->readln(answer)) {
680 if (prefixIs(answer, "ERROR:"))
684 cerr << "lyxclient: No answer from server." << endl;
689 // Take commands from stdin
690 iowatch.addfd(0); // stdin
691 bool saidbye = false;
692 while ((!saidbye) && server->connected()) {
694 if (iowatch.isset(0)) {
696 getline(cin, command);
699 if (command == "BYE:") {
700 server->writeln("BYE:");
703 server->writeln("LYXCMD:" + command);
706 if (iowatch.isset(serverfd)) {
707 while(server->readln(answer))
708 cout << answer << endl;
718 int main(int argc, char * argv[])
720 lyx::lyxerr.setStream(cerr);
722 lyx::LyXClientApp app(argc, argv);
729 void assertion_failed(char const* a, char const* b, char const* c, long d)
731 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '