]> git.lyx.org Git - lyx.git/blob - src/client/client.cpp
add busy.gif to resources (in line with cmake)
[lyx.git] / src / client / client.cpp
1 /**
2  * \file client.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author João Luis M. Assirati
7  * \author Lars Gullik Bjønnes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12
13 #include <config.h>
14
15 #include "support/debug.h"
16 #include "support/FileName.h"
17 #include "support/FileNameList.h"
18 #include "support/lstrings.h"
19 #include "support/unicode.h"
20
21 #include <boost/scoped_ptr.hpp>
22
23 // getpid(), getppid()
24 #ifdef HAVE_SYS_TYPES_H
25 # include <sys/types.h>
26 #endif
27 #ifdef HAVE_UNISTD_H
28 # include <unistd.h>
29 #endif
30
31 // struct timeval
32 #ifdef HAVE_SYS_TIME_H
33 # include <sys/time.h>
34 #endif
35
36 // select()
37 #ifdef HAVE_SYS_SELECT_H
38 # include <sys/select.h>
39 #endif
40
41 // socket(), connect()
42 #ifdef HAVE_SYS_SOCKET_H
43 # include <sys/socket.h>
44 #endif
45 #include <sys/un.h>
46
47 // fcntl()
48 #include <fcntl.h>
49
50 // strerror()
51 #include <string.h>
52
53 #include <cerrno>
54 #include <cstdio>
55 #include <cstdlib>
56 #include <string>
57 #include <vector>
58 #include <map>
59 #include <iostream>
60
61 using namespace std;
62 using namespace lyx::support;
63
64 using ::boost::scoped_ptr;
65
66 namespace lyx {
67
68 // Keep the linker happy on Windows
69 void lyx_exit(int)
70 {}
71
72 namespace support {
73
74 string itoa(unsigned int i)
75 {
76         char buf[20];
77         sprintf(buf, "%d", i);
78         return buf;
79 }
80
81
82 /// Returns the absolute pathnames of all lyx local sockets in
83 /// file system encoding.
84 /// Parts stolen from lyx::support::DirList().
85 FileNameList lyxSockets(string const & dir, string const & pid)
86 {
87         FileNameList dirlist;
88
89         FileName dirpath(dir + "/");
90
91         if (!dirpath.exists() || !dirpath.isDirectory()) {
92                 lyxerr << dir << " does not exist or is not a directory."
93                        << endl;
94                 return dirlist;
95         }
96
97         FileNameList dirs = dirpath.dirList("");
98         FileNameList::const_iterator it = dirs.begin();
99         FileNameList::const_iterator end = dirs.end();
100
101         for (; it != end; ++it) {
102                 if (!it->isDirectory())
103                         continue;
104                 string const tmpdir = it->absFileName();
105                 if (!contains(tmpdir, "lyx_tmpdir" + pid))
106                         continue;
107
108                 FileName lyxsocket(tmpdir + "/lyxsocket");
109                 if (lyxsocket.exists())
110                         dirlist.push_back(lyxsocket);
111         }
112
113         return dirlist;
114 }
115
116
117 namespace socktools {
118
119
120 /// Connect to the socket \p name.
121 int connect(FileName const & name)
122 {
123         int fd; // File descriptor for the socket
124         sockaddr_un addr; // Structure that hold the socket address
125
126         string const encoded = name.toFilesystemEncoding();
127         // char sun_path[108]
128         string::size_type len = encoded.size();
129         if (len > 107) {
130                 cerr << "lyxclient: Socket address '" << name
131                      << "' too long." << endl;
132                 return -1;
133         }
134         // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
135         addr.sun_family = AF_UNIX;
136         encoded.copy(addr.sun_path, 107);
137         addr.sun_path[len] = '\0';
138
139         if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
140                 cerr << "lyxclient: Could not create socket descriptor: "
141                      << strerror(errno) << endl;
142                 return -1;
143         }
144         if (::connect(fd,
145                       reinterpret_cast<struct sockaddr *>(&addr),
146                       sizeof(addr)) == -1) {
147                 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
148                      << ": " << strerror(errno) << endl;
149                 ::close(fd);
150                 return -1;
151         }
152         if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
153                 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
154                      << strerror(errno) << endl;
155                 ::close(fd);
156                 return -1;
157         }
158         return fd;
159 }
160
161
162 } // namespace socktools
163 } // namespace support
164
165
166
167 /////////////////////////////////////////////////////////////////////
168 //
169 // IOWatch
170 //
171 /////////////////////////////////////////////////////////////////////
172
173 class IOWatch {
174 public:
175         IOWatch();
176         void clear();
177         void addfd(int);
178         bool wait(double);
179         bool wait();
180         bool isset(int fd);
181 private:
182         fd_set des;
183         fd_set act;
184 };
185
186
187 IOWatch::IOWatch()
188 {
189         clear();
190 }
191
192
193 void IOWatch::clear()
194 {
195         FD_ZERO(&des);
196 }
197
198
199 void IOWatch::addfd(int fd)
200 {
201         FD_SET(fd, &des);
202 }
203
204
205 bool IOWatch::wait(double timeout)
206 {
207         timeval to;
208         to.tv_sec = static_cast<long int>(timeout);
209         to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
210         act = des;
211         return select(FD_SETSIZE, &act,
212                       (fd_set *)0, (fd_set *)0, &to);
213 }
214
215
216 bool IOWatch::wait()
217 {
218         act = des;
219         return select(FD_SETSIZE, &act,
220                       (fd_set *)0, (fd_set *)0, (timeval *)0);
221 }
222
223
224 bool IOWatch::isset(int fd)
225 {
226         return FD_ISSET(fd, &act);
227 }
228
229
230
231 /////////////////////////////////////////////////////////////////////
232 //
233 // LyXDataSocket
234 //
235 /////////////////////////////////////////////////////////////////////
236
237 // Modified LyXDataSocket class for use with the client
238 class LyXDataSocket {
239 public:
240         LyXDataSocket(FileName const &);
241         ~LyXDataSocket();
242         // File descriptor of the connection
243         int fd() const;
244         // Connection status
245         bool connected() const;
246         // Line buffered input from the socket
247         bool readln(string &);
248         // Write the string + '\n' to the socket
249         void writeln(string const &);
250 private:
251         // File descriptor for the data socket
252         int fd_;
253         // True if the connection is up
254         bool connected_;
255         // buffer for input data
256         string buffer;
257 };
258
259
260 LyXDataSocket::LyXDataSocket(FileName const & address)
261 {
262         if ((fd_ = socktools::connect(address)) == -1) {
263                 connected_ = false;
264         } else {
265                 connected_ = true;
266         }
267 }
268
269
270 LyXDataSocket::~LyXDataSocket()
271 {
272         ::close(fd_);
273 }
274
275
276 int LyXDataSocket::fd() const
277 {
278         return fd_;
279 }
280
281
282 bool LyXDataSocket::connected() const
283 {
284         return connected_;
285 }
286
287
288 // Returns true if there was a complete line to input
289 // A line is of the form <key>:<value>
290 //   A line not of this form will not be passed
291 // The line read is splitted and stored in 'key' and 'value'
292 bool LyXDataSocket::readln(string & line)
293 {
294         int const charbuf_size = 100;
295         char charbuf[charbuf_size]; // buffer for the ::read() system call
296         int count;
297         string::size_type pos;
298
299         // read and store characters in buffer
300         while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
301                 charbuf[count] = '\0'; // turn it into a c string
302                 buffer += charbuf;
303         }
304
305         // Error conditions. The buffer must still be
306         // processed for lines read
307         if (count == 0) { // EOF -- connection closed
308                 connected_ = false;
309         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
310                 cerr << "lyxclient: IO error." << endl;
311                 connected_ = false;
312         }
313
314         // Cut a line from buffer
315         if ((pos = buffer.find('\n')) == string::npos)
316                 return false; // No complete line stored
317         line = buffer.substr(0, pos);
318         buffer = buffer.substr(pos + 1);
319         return true;
320 }
321
322
323 // Write a line of the form <key>:<value> to the socket
324 void LyXDataSocket::writeln(string const & line)
325 {
326         string linen(line + '\n');
327         int size = linen.size();
328         int written = ::write(fd_, linen.c_str(), size);
329         if (written < size) { // Allways mean end of connection.
330                 if ((written == -1) && (errno == EPIPE)) {
331                         // The program will also receive a SIGPIPE
332                         // that must be catched
333                         cerr << "lyxclient: connection closed while writing."
334                              << endl;
335                 } else {
336                         // Anything else, including errno == EAGAIN, must be
337                         // considered IO error. EAGAIN should never happen
338                         // when line is small
339                         cerr << "lyxclient: IO error: " << strerror(errno);
340                 }
341                 connected_ = false;
342         }
343 }
344
345
346 /////////////////////////////////////////////////////////////////////
347 //
348 // CmdLineParser
349 //
350 /////////////////////////////////////////////////////////////////////
351
352 class CmdLineParser {
353 public:
354         typedef int (*optfunc)(vector<docstring> const & args);
355         map<string, optfunc> helper;
356         map<string, bool> isset;
357         bool parse(int, char * []);
358         vector<char *> nonopt;
359 };
360
361
362 bool CmdLineParser::parse(int argc, char * argv[])
363 {
364         int opt = 1;
365         while (opt < argc) {
366                 vector<docstring> args;
367                 if (helper[argv[opt]]) {
368                         isset[argv[opt]] = true;
369                         int arg = opt + 1;
370                         while ((arg < argc) && (!helper[argv[arg]])) {
371                                 args.push_back(from_local8bit(argv[arg]));
372                                 ++arg;
373                         }
374                         int taken = helper[argv[opt]](args);
375                         if (taken == -1)
376                                 return false;
377                         opt += 1 + taken;
378                 } else {
379                         if (argv[opt][0] == '-') {
380                                 if ((argv[opt][1] == '-')
381                                    && (argv[opt][2]== '\0')) {
382                                         ++opt;
383                                         while (opt < argc) {
384                                                 nonopt.push_back(argv[opt]);
385                                                 ++opt;
386                                         }
387                                         return true;
388                                 } else {
389                                         cerr << "lyxclient: unknown option "
390                                              << argv[opt] << endl;
391                                         return false;
392                                 }
393                         }
394                         nonopt.push_back(argv[opt]);
395                         ++opt;
396                 }
397         }
398         return true;
399 }
400 // ~Class CmdLineParser -------------------------------------------------------
401
402
403
404 namespace cmdline {
405
406 void usage()
407 {
408         cerr <<
409                 "Usage: lyxclient [options]\n"
410           "Options are:\n"
411           "  -a address    set address of the lyx socket\n"
412           "  -t directory  set system temporary directory (for detecting sockets)\n"
413           "  -p pid        select a running lyx by pidi\n"
414           "  -c command    send a single command and quit (LYXCMD prefix needed)\n"
415           "  -g file row   send a command to go to file and row\n"
416           "  -n name       set client name\n"
417           "  -h name       display this help end exit\n"
418           "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
419           "a running lyx. If -t is not set, 'directory' defaults to /tmp. If -p is set,\n"
420           "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
421           "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
422           "will read commands from standard input and disconnect when command read is BYE:"
423            << endl;
424 }
425
426
427 int h(vector<docstring> const &)
428 {
429         usage();
430         exit(0);
431 }
432
433
434 docstring clientName =
435         from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
436
437 int n(vector<docstring> const & arg)
438 {
439         if (arg.size() < 1) {
440                 cerr << "lyxclient: The option -n requires 1 argument."
441                      << endl;
442                 return -1;
443         }
444         clientName = arg[0];
445         return 1;
446 }
447
448
449 docstring singleCommand;
450
451
452 int c(vector<docstring> const & arg)
453 {
454         if (arg.size() < 1) {
455                 cerr << "lyxclient: The option -c requires 1 argument."
456                      << endl;
457                 return -1;
458         }
459         singleCommand = arg[0];
460         return 1;
461 }
462
463
464 int g(vector<docstring> const & arg)
465 {
466         if (arg.size() < 2) {
467                 cerr << "lyxclient: The option -g requires 2 arguments."
468                      << endl;
469                 return -1;
470         }
471         singleCommand = "LYXCMD:server-goto-file-row "
472                 + arg[0] + ' '
473                 + arg[1];
474         return 2;
475 }
476
477
478 // empty if LYXSOCKET is not set in the environment
479 docstring serverAddress;
480
481
482 int a(vector<docstring> const & arg)
483 {
484         if (arg.size() < 1) {
485                 cerr << "lyxclient: The option -a requires 1 argument."
486                      << endl;
487                 return -1;
488         }
489         // -a supercedes LYXSOCKET environment variable
490         serverAddress = arg[0];
491         return 1;
492 }
493
494
495 docstring mainTmp(from_ascii("/tmp"));
496
497
498 int t(vector<docstring> const & arg)
499 {
500         if (arg.size() < 1) {
501                 cerr << "lyxclient: The option -t requires 1 argument."
502                      << endl;
503                 return -1;
504         }
505         mainTmp = arg[0];
506         return 1;
507 }
508
509
510 string serverPid; // Init to empty string
511
512
513 int p(vector<docstring> const & arg)
514 {
515         if (arg.size() < 1) {
516                 cerr << "lyxclient: The option -p requires 1 argument."
517                      << endl;
518                 return -1;
519         }
520         serverPid = to_ascii(arg[0]);
521         return 1;
522 }
523
524
525 } // namespace cmdline
526 } // namespace lyx
527
528
529 int main(int argc, char * argv[])
530 {
531         using namespace lyx;
532         lyxerr.setStream(cerr);
533
534         char const * const lyxsocket = getenv("LYXSOCKET");
535         if (lyxsocket)
536                 cmdline::serverAddress = from_local8bit(lyxsocket);
537
538         CmdLineParser args;
539         args.helper["-h"] = cmdline::h;
540         args.helper["-c"] = cmdline::c;
541         args.helper["-g"] = cmdline::g;
542         args.helper["-n"] = cmdline::n;
543         args.helper["-a"] = cmdline::a;
544         args.helper["-t"] = cmdline::t;
545         args.helper["-p"] = cmdline::p;
546
547         // Command line failure conditions:
548         if ((!args.parse(argc, argv))
549            || (args.isset["-c"] && args.isset["-g"])
550            || (args.isset["-a"] && args.isset["-p"])) {
551                 cmdline::usage();
552                 return 1;
553         }
554
555         scoped_ptr<LyXDataSocket> server;
556
557         if (!cmdline::serverAddress.empty()) {
558                 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
559                 if (!server->connected()) {
560                         cerr << "lyxclient: " << "Could not connect to "
561                              << to_utf8(cmdline::serverAddress) << endl;
562                         return EXIT_FAILURE;
563                 }
564         } else {
565                 // We have to look for an address.
566                 // serverPid can be empty.
567                 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
568                 FileNameList::const_iterator addr = addrs.begin();
569                 FileNameList::const_iterator end = addrs.end();
570                 for (; addr != end; ++addr) {
571                         // Caution: addr->string() is in filesystem encoding
572                         server.reset(new LyXDataSocket(*addr));
573                         if (server->connected())
574                                 break;
575                         lyxerr << "lyxclient: " << "Could not connect to "
576                              << addr->absFileName() << endl;
577                 }
578                 if (addr == end) {
579                         lyxerr << "lyxclient: No suitable server found."
580                                << endl;
581                         return EXIT_FAILURE;
582                 }
583                 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
584         }
585
586         int const serverfd = server->fd();
587
588         IOWatch iowatch;
589         iowatch.addfd(serverfd);
590
591         // Used to read from server
592         string answer;
593
594         // Send greeting
595         server->writeln("HELLO:" + to_utf8(cmdline::clientName));
596         // wait at most 2 seconds until server responds
597         iowatch.wait(2.0);
598         if (iowatch.isset(serverfd) && server->readln(answer)) {
599                 if (prefixIs(answer, "BYE:")) {
600                         cerr << "lyxclient: Server disconnected." << endl;
601                         cout << answer << endl;
602                         return EXIT_FAILURE;
603                 }
604         } else {
605                 cerr << "lyxclient: No answer from server." << endl;
606                 return EXIT_FAILURE;
607         }
608
609         if (args.isset["-g"] || args.isset["-c"]) {
610                 server->writeln(to_utf8(cmdline::singleCommand));
611                 iowatch.wait(2.0);
612                 if (iowatch.isset(serverfd) && server->readln(answer)) {
613                         cout << answer;
614                         if (prefixIs(answer, "ERROR:"))
615                                 return EXIT_FAILURE;
616                         return EXIT_SUCCESS;
617                 } else {
618                         cerr << "lyxclient: No answer from server." << endl;
619                         return EXIT_FAILURE;
620                 }
621         }
622
623         // Take commands from stdin
624         iowatch.addfd(0); // stdin
625         bool saidbye = false;
626         while ((!saidbye) && server->connected()) {
627                 iowatch.wait();
628                 if (iowatch.isset(0)) {
629                         string command;
630                         getline(cin, command);
631                         if (command.empty())
632                                 continue;
633                         if (command == "BYE:") {
634                                 server->writeln("BYE:");
635                                 saidbye = true;
636                         } else {
637                                 server->writeln("LYXCMD:" + command);
638                         }
639                 }
640                 if (iowatch.isset(serverfd)) {
641                         while(server->readln(answer))
642                                 cout << answer << endl;
643                 }
644         }
645
646         return EXIT_SUCCESS;
647 }
648
649
650 namespace boost {
651
652 void assertion_failed(char const* a, char const* b, char const* c, long d)
653 {
654         lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '
655                 << d << '\n';
656 }
657
658 } // namespace boost