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 string itoa(unsigned int i)
99 sprintf(buf, "%d", i);
104 /// Returns the absolute pathnames of all lyx local sockets in
105 /// file system encoding.
106 /// Parts stolen from lyx::support::DirList().
107 FileNameList lyxSockets(string const & dir, string const & pid)
109 FileNameList dirlist;
111 FileName dirpath(dir + "/");
113 if (!dirpath.exists() || !dirpath.isDirectory()) {
114 lyxerr << dir << " does not exist or is not a directory."
119 FileNameList dirs = dirpath.dirList("");
120 FileNameList::const_iterator it = dirs.begin();
121 FileNameList::const_iterator end = dirs.end();
123 for (; it != end; ++it) {
124 if (!it->isDirectory())
126 string const tmpdir = it->absFileName();
127 if (!contains(tmpdir, "lyx_tmpdir" + pid))
130 FileName lyxsocket(tmpdir + "/lyxsocket");
131 if (lyxsocket.exists())
132 dirlist.push_back(lyxsocket);
139 namespace socktools {
142 /// Connect to the socket \p name.
143 int connect(FileName const & name)
145 int fd; // File descriptor for the socket
146 sockaddr_un addr; // Structure that hold the socket address
148 string const encoded = name.toFilesystemEncoding();
149 // char sun_path[108]
150 string::size_type len = encoded.size();
152 cerr << "lyxclient: Socket address '" << name
153 << "' too long." << endl;
156 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
157 addr.sun_family = AF_UNIX;
158 encoded.copy(addr.sun_path, 107);
159 addr.sun_path[len] = '\0';
161 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
162 cerr << "lyxclient: Could not create socket descriptor: "
163 << strerror(errno) << endl;
167 reinterpret_cast<struct sockaddr *>(&addr),
168 sizeof(addr)) == -1) {
169 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
170 << ": " << strerror(errno) << endl;
174 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
175 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
176 << strerror(errno) << endl;
184 } // namespace socktools
185 } // namespace support
189 /////////////////////////////////////////////////////////////////////
193 /////////////////////////////////////////////////////////////////////
215 void IOWatch::clear()
221 void IOWatch::addfd(int fd)
227 bool IOWatch::wait(double timeout)
230 to.tv_sec = static_cast<long int>(timeout);
231 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
233 return select(FD_SETSIZE, &act,
234 (fd_set *)0, (fd_set *)0, &to);
241 return select(FD_SETSIZE, &act,
242 (fd_set *)0, (fd_set *)0, (timeval *)0);
246 bool IOWatch::isset(int fd)
248 return FD_ISSET(fd, &act);
253 /////////////////////////////////////////////////////////////////////
257 /////////////////////////////////////////////////////////////////////
259 // Modified LyXDataSocket class for use with the client
260 class LyXDataSocket {
262 LyXDataSocket(FileName const &);
264 // File descriptor of the connection
267 bool connected() const;
268 // Line buffered input from the socket
269 bool readln(string &);
270 // Write the string + '\n' to the socket
271 void writeln(string const &);
273 // File descriptor for the data socket
275 // True if the connection is up
277 // buffer for input data
282 LyXDataSocket::LyXDataSocket(FileName const & address)
284 if ((fd_ = socktools::connect(address)) == -1) {
292 LyXDataSocket::~LyXDataSocket()
298 int LyXDataSocket::fd() const
304 bool LyXDataSocket::connected() const
310 // Returns true if there was a complete line to input
311 // A line is of the form <key>:<value>
312 // A line not of this form will not be passed
313 // The line read is split and stored in 'key' and 'value'
314 bool LyXDataSocket::readln(string & line)
316 int const charbuf_size = 100;
317 char charbuf[charbuf_size]; // buffer for the ::read() system call
319 string::size_type pos;
321 // read and store characters in buffer
322 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
323 charbuf[count] = '\0'; // turn it into a c string
327 // Error conditions. The buffer must still be
328 // processed for lines read
329 if (count == 0) { // EOF -- connection closed
331 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
332 cerr << "lyxclient: IO error." << endl;
336 // Cut a line from buffer
337 if ((pos = buffer.find('\n')) == string::npos)
338 return false; // No complete line stored
339 line = buffer.substr(0, pos);
340 buffer = buffer.substr(pos + 1);
345 // Write a line of the form <key>:<value> to the socket
346 void LyXDataSocket::writeln(string const & line)
348 string linen(line + '\n');
349 int size = linen.size();
350 int written = ::write(fd_, linen.c_str(), size);
351 if (written < size) { // Allways mean end of connection.
352 if ((written == -1) && (errno == EPIPE)) {
353 // The program will also receive a SIGPIPE
354 // that must be catched
355 cerr << "lyxclient: connection closed while writing."
358 // Anything else, including errno == EAGAIN, must be
359 // considered IO error. EAGAIN should never happen
360 // when line is small
361 cerr << "lyxclient: IO error: " << strerror(errno);
368 /////////////////////////////////////////////////////////////////////
372 /////////////////////////////////////////////////////////////////////
374 class CmdLineParser {
376 typedef int (*optfunc)(vector<docstring> const & args);
377 map<string, optfunc> helper;
378 map<string, bool> isset;
379 bool parse(int, char * []);
380 vector<char *> nonopt;
384 bool CmdLineParser::parse(int argc, char * argv[])
388 vector<docstring> args;
389 if (helper[argv[opt]]) {
390 isset[argv[opt]] = true;
392 while ((arg < argc) && (!helper[argv[arg]])) {
393 args.push_back(from_local8bit(argv[arg]));
396 int taken = helper[argv[opt]](args);
401 if (argv[opt][0] == '-') {
402 if ((argv[opt][1] == '-')
403 && (argv[opt][2]== '\0')) {
406 nonopt.push_back(argv[opt]);
411 cerr << "lyxclient: unknown option "
412 << argv[opt] << endl;
416 nonopt.push_back(argv[opt]);
422 // ~Class CmdLineParser -------------------------------------------------------
428 docstring mainTmp(from_ascii("/tmp"));
431 class StopException : public exception
434 StopException(int status) : status_(status) {}
435 int status() const { return status_; }
444 "Usage: lyxclient [options]\n"
446 " -a address set address of the lyx socket\n"
447 " -t directory set system temporary directory (for detecting sockets)\n"
448 " -p pid select a running lyx by pidi\n"
449 " -c command send a single command and quit (LYXCMD prefix needed)\n"
450 " -g file row send a command to go to file and row\n"
451 " -n name set client name\n"
452 " -h name display this help end exit\n"
453 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
454 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
455 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
456 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
457 "will read commands from standard input and disconnect when command read is BYE:\n"
459 "System directory is: " << to_utf8(cmdline::mainTmp)
464 int h(vector<docstring> const &)
467 throw StopException(EXIT_SUCCESS);
471 docstring clientName =
472 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
474 int n(vector<docstring> const & arg)
477 cerr << "lyxclient: The option -n requires 1 argument."
486 docstring singleCommand;
489 int c(vector<docstring> const & arg)
492 cerr << "lyxclient: The option -c requires 1 argument."
496 singleCommand = arg[0];
501 int g(vector<docstring> const & arg)
503 if (arg.size() < 2) {
504 cerr << "lyxclient: The option -g requires 2 arguments."
508 singleCommand = "LYXCMD:command-sequence "
509 "server-goto-file-row "
517 // empty if LYXSOCKET is not set in the environment
518 docstring serverAddress;
521 int a(vector<docstring> const & arg)
524 cerr << "lyxclient: The option -a requires 1 argument."
528 // -a supercedes LYXSOCKET environment variable
529 serverAddress = arg[0];
536 int t(vector<docstring> const & arg)
539 cerr << "lyxclient: The option -t requires 1 argument."
548 string serverPid; // Init to empty string
551 int p(vector<docstring> const & arg)
554 cerr << "lyxclient: The option -p requires 1 argument."
558 serverPid = to_ascii(arg[0]);
563 } // namespace cmdline
565 /// The main application class
566 class LyXClientApp : public ConsoleApplication
569 LyXClientApp(int & argc, char * argv[])
570 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
571 argc_(argc), argv_(argv)
577 int const exit_status = run();
580 catch (cmdline::StopException & e) {
591 int LyXClientApp::run()
593 // qt changes this, and our numeric conversions require the C locale
594 setlocale(LC_NUMERIC, "C");
597 char const * const lyxsocket = getenv("LYXSOCKET");
599 cmdline::serverAddress = from_local8bit(lyxsocket);
602 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
604 // Command line builder
606 args.helper["-h"] = cmdline::h;
607 args.helper["-c"] = cmdline::c;
608 args.helper["-g"] = cmdline::g;
609 args.helper["-n"] = cmdline::n;
610 args.helper["-a"] = cmdline::a;
611 args.helper["-t"] = cmdline::t;
612 args.helper["-p"] = cmdline::p;
614 // Command line failure conditions:
615 if ((!args.parse(argc_, argv_))
616 || (args.isset["-c"] && args.isset["-g"])
617 || (args.isset["-a"] && args.isset["-p"])) {
622 unique_ptr<LyXDataSocket> server;
624 if (!cmdline::serverAddress.empty()) {
625 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
626 if (!server->connected()) {
627 cerr << "lyxclient: " << "Could not connect to "
628 << to_utf8(cmdline::serverAddress) << endl;
632 // We have to look for an address.
633 // serverPid can be empty.
634 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
635 FileNameList::const_iterator addr = addrs.begin();
636 FileNameList::const_iterator end = addrs.end();
637 for (; addr != end; ++addr) {
638 // Caution: addr->string() is in filesystem encoding
639 server.reset(new LyXDataSocket(*addr));
640 if (server->connected())
642 lyxerr << "lyxclient: " << "Could not connect to "
643 << addr->absFileName() << endl;
646 lyxerr << "lyxclient: No suitable server found."
650 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
653 int const serverfd = server->fd();
656 iowatch.addfd(serverfd);
658 // Used to read from server
662 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
663 // wait at most 2 seconds until server responds
665 if (iowatch.isset(serverfd) && server->readln(answer)) {
666 if (prefixIs(answer, "BYE:")) {
667 cerr << "lyxclient: Server disconnected." << endl;
668 cout << answer << endl;
672 cerr << "lyxclient: No answer from server." << endl;
676 if (args.isset["-g"] || args.isset["-c"]) {
677 server->writeln(to_utf8(cmdline::singleCommand));
679 if (iowatch.isset(serverfd) && server->readln(answer)) {
681 if (prefixIs(answer, "ERROR:"))
685 cerr << "lyxclient: No answer from server." << endl;
690 // Take commands from stdin
691 iowatch.addfd(0); // stdin
692 bool saidbye = false;
693 while ((!saidbye) && server->connected()) {
695 if (iowatch.isset(0)) {
697 getline(cin, command);
700 if (command == "BYE:") {
701 server->writeln("BYE:");
704 server->writeln("LYXCMD:" + command);
707 if (iowatch.isset(serverfd)) {
708 while(server->readln(answer))
709 cout << answer << endl;
719 int main(int argc, char * argv[])
721 lyx::lyxerr.setStream(cerr);
723 lyx::LyXClientApp app(argc, argv);