]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
qt longtabular ui fixes
[lyx.git] / src / lyxserver.C
1 /**
2  * \file lyxserver.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author Angus Leeming
9  * \author John Levon
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 /**
15   Docu   : To use the lyxserver define the name of the pipe in your
16            lyxrc:
17            \serverpipe "/home/myhome/.lyxpipe"
18            Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
19            Each message consists of a single line in ASCII. Input lines
20            (client -> LyX) have the following format:
21             "LYXCMD:<clientname>:<functionname>:<argument>"
22            Answers from LyX look like this:
23            "INFO:<clientname>:<functionname>:<data>"
24  [asierra970531] Or like this in case of error:
25            "ERROR:<clientname>:<functionname>:<error message>"
26            where <clientname> and <functionname> are just echoed.
27            If LyX notifies about a user defined extension key-sequence,
28            the line looks like this:
29            "NOTIFY:<key-sequence>"
30  [asierra970531] New server-only messages to implement a simple protocol
31            "LYXSRV:<clientname>:<protocol message>"
32            where <protocol message> can be "hello" or "bye". If hello is
33            received LyX will inform the client that it's listening its
34            messages, and 'bye' will inform that lyx is closing.
35
36            See development/server_monitor.c for an example client.
37   Purpose: implement a client/server lib for LyX
38 */
39
40 #include <config.h>
41
42 #include "lyxserver.h"
43 #include "debug.h"
44 #include "funcrequest.h"
45 #include "LyXAction.h"
46 #include "lyxfunc.h"
47 #include "support/lstrings.h"
48 #include "support/lyxlib.h"
49 #include "frontends/lyx_gui.h"
50
51 #include <boost/bind.hpp>
52
53 #include <cerrno>
54 #ifdef HAVE_SYS_STAT_H
55 # include <sys/stat.h>
56 #endif
57 #include <fcntl.h>
58
59 #ifdef __EMX__
60 # include <cstdlib>
61 # include <io.h>
62 # define OS2EMX_PLAIN_CHAR
63 # define INCL_DOSNMPIPES
64 # define INCL_DOSERRORS
65 # include <os2.h>
66 # include "support/os2_errortable.h"
67 #endif
68
69 using lyx::support::compare;
70 using lyx::support::rtrim;
71 using lyx::support::split;
72 using lyx::support::unlink;
73
74 using std::endl;
75 using std::string;
76
77
78 // provide an empty mkfifo() if we do not have one. This disables the
79 // lyxserver.
80 #ifndef HAVE_MKFIFO
81 int mkfifo(char const * __path, mode_t __mode) {
82         return 0;
83 }
84 #endif
85
86
87 void LyXComm::openConnection()
88 {
89         lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
90
91         // If we are up, that's an error
92         if (ready) {
93                 lyxerr << "LyXComm: Already connected" << endl;
94                 return;
95         }
96         // We assume that we don't make it
97         ready = false;
98
99         if (pipename.empty()) {
100                 lyxerr[Debug::LYXSERVER]
101                         << "LyXComm: server is disabled, nothing to do"
102                         << endl;
103                 return;
104         }
105
106         if ((infd = startPipe(inPipeName(), false)) == -1)
107                 return;
108
109         if ((outfd = startPipe(outPipeName(), true)) == -1) {
110                 endPipe(infd, inPipeName(), false);
111                 return;
112         }
113
114         if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
115                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
116                        << '\n' << strerror(errno) << endl;
117                 return;
118         }
119
120         // We made it!
121         ready = true;
122         lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
123 }
124
125
126 /// Close pipes
127 void LyXComm::closeConnection()
128 {
129         lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
130
131         if (pipename.empty()) {
132                 lyxerr[Debug::LYXSERVER]
133                         << "LyXComm: server is disabled, nothing to do"
134                         << endl;
135                 return;
136         }
137
138         if (!ready) {
139                 lyxerr << "LyXComm: Already disconnected" << endl;
140                 return;
141         }
142
143         endPipe(infd, inPipeName(), false);
144         endPipe(outfd, outPipeName(), true);
145
146         ready = false;
147 }
148
149
150 int LyXComm::startPipe(string const & filename, bool write)
151 {
152 #ifdef __EMX__
153         HPIPE os2fd;
154         APIRET rc;
155         int errnum;
156         // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
157         // The current emx implementation of access() won't work with pipes.
158         rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
159                 NP_NOWAIT|0x01, 0600, 0600, 0);
160         if (rc == ERROR_PIPE_BUSY) {
161                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
162                        << "If no other LyX program is active, please delete"
163                         " the pipe by hand and try again." << endl;
164                 pipename.erase();
165                 return -1;
166         }
167
168         if (rc != NO_ERROR) {
169                 errnum = TranslateOS2Error(rc);
170                 lyxerr <<"LyXComm: Could not create pipe " << filename
171                        << strerror(errnum) << endl;
172                 return -1;
173         };
174         // Listen to it.
175         rc = DosConnectNPipe(os2fd);
176         if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
177                 errnum = TranslateOS2Error(rc);
178                 lyxerr <<"LyXComm: Could not create pipe " << filename
179                        << strerror(errnum) << endl;
180                 return -1;
181         };
182         // Imported handles can be used both with OS/2 APIs and emx
183         // library functions.
184         int const fd = _imphandle(os2fd);
185 #else
186         if (::access(filename.c_str(), F_OK) == 0) {
187                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
188                        << "If no other LyX program is active, please delete"
189                         " the pipe by hand and try again." << endl;
190                 pipename.erase();
191                 return -1;
192         }
193
194         if (::mkfifo(filename.c_str(), 0600) < 0) {
195                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
196                        << strerror(errno) << endl;
197                 return -1;
198         };
199         int const fd = ::open(filename.c_str(),
200                               write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
201 #endif
202
203         if (fd < 0) {
204                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
205                        << strerror(errno) << endl;
206                 unlink(filename);
207                 return -1;
208         }
209
210         if (!write) {
211                 lyx_gui::register_socket_callback(fd, boost::bind(&LyXComm::read_ready, this));
212         }
213
214         return fd;
215 }
216
217
218 void LyXComm::endPipe(int & fd, string const & filename, bool write)
219 {
220         if (fd < 0)
221                 return;
222
223         if (!write) {
224                 lyx_gui::unregister_socket_callback(fd);
225         }
226
227 #ifdef __EMX__
228         APIRET rc;
229         int errnum;
230
231         rc = DosDisConnectNPipe(fd);
232         if (rc != NO_ERROR) {
233                 errnum = TranslateOS2Error(rc);
234                 lyxerr << "LyXComm: Could not disconnect pipe " << filename
235                        << '\n' << strerror(errnum) << endl;
236                 return;
237         }
238 #endif
239
240         if (::close(fd) < 0) {
241                 lyxerr << "LyXComm: Could not close pipe " << filename
242                        << '\n' << strerror(errno) << endl;
243         }
244
245 // OS/2 pipes are deleted automatically
246 #ifndef __EMX__
247         if (unlink(filename) < 0) {
248                 lyxerr << "LyXComm: Could not remove pipe " << filename
249                        << '\n' << strerror(errno) << endl;
250         };
251 #endif
252
253         fd = -1;
254 }
255
256
257 void LyXComm::emergencyCleanup()
258 {
259         if (!pipename.empty()) {
260                 endPipe(infd, inPipeName(), false);
261                 endPipe(outfd, outPipeName(), true);
262         }
263 }
264
265
266 // Receives messages and sends then to client
267 void LyXComm::read_ready()
268 {
269         // nb! make read_buffer_ a class-member for multiple sessions
270         static string read_buffer_;
271         read_buffer_.erase();
272
273         int const charbuf_size = 100;
274         char charbuf[charbuf_size];
275
276         errno = 0;
277         int status;
278         // the single = is intended here.
279         while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
280
281                 if (status > 0) {
282                         charbuf[status] = '\0'; // turn it into a c string
283                         read_buffer_ += rtrim(charbuf, "\r");
284                         // commit any commands read
285                         while (read_buffer_.find('\n') != string::npos) {
286                                 // split() grabs the entire string if
287                                 // the delim /wasn't/ found. ?:-P
288                                 string cmd;
289                                 read_buffer_= split(read_buffer_, cmd,'\n');
290                                 lyxerr[Debug::LYXSERVER]
291                                         << "LyXComm: status:" << status
292                                         << ", read_buffer_:" << read_buffer_
293                                         << ", cmd:" << cmd << endl;
294                                 if (!cmd.empty())
295                                         clientcb(client, cmd);
296                                         //\n or not \n?
297                         }
298                 }
299                 if (errno == EAGAIN) {
300                         errno = 0;
301                         return;
302                 }
303                 if (errno != 0) {
304                         lyxerr << "LyXComm: " << strerror(errno) << endl;
305                         if (!read_buffer_.empty()) {
306                                 lyxerr << "LyXComm: truncated command: "
307                                        << read_buffer_ << endl;
308                                 read_buffer_.erase();
309                         }
310                         break; // reset connection
311                 }
312         }
313
314         // The connection gets reset in errno != EAGAIN
315         // Why does it need to be reset if errno == 0?
316         closeConnection();
317         openConnection();
318         errno = 0;
319 }
320
321
322 void LyXComm::send(string const & msg)
323 {
324         if (msg.empty()) {
325                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
326                        << endl;
327                 return;
328         }
329
330         if (lyxerr.debugging(Debug::LYXSERVER)) {
331                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
332         }
333
334         if (pipename.empty()) return;
335
336         if (!ready) {
337                 lyxerr << "LyXComm: Pipes are closed. Could not send "
338                        << msg << endl;
339         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
340                 lyxerr << "LyXComm: Error sending message: " << msg
341                        << '\n' << strerror(errno)
342                        << "\nLyXComm: Resetting connection" << endl;
343                 closeConnection();
344                 openConnection();
345         }
346 #ifdef __EMX__
347         APIRET rc;
348         int errnum;
349         rc = DosResetBuffer(outfd);     // To avoid synchronization problems.
350         if (rc != NO_ERROR) {
351                 errnum = TranslateOS2Error(rc);
352                 lyxerr << "LyXComm: Message could not be flushed: " << msg
353                        << '\n' << strerror(errnum) << endl;
354         }
355 #endif
356 }
357
358
359 string const LyXComm::inPipeName() const
360 {
361         return pipename + string(".in");
362 }
363
364
365 string const LyXComm::outPipeName() const
366 {
367         return pipename + string(".out");
368 }
369
370
371 // LyXServer class
372
373 LyXServer::~LyXServer()
374 {
375         // say goodbye to clients so they stop sending messages
376         // modified june 1999 by stefano@zool.su.se to send as many bye
377         // messages as there are clients, each with client's name.
378         string message;
379         for (int i= 0; i<numclients; ++i) {
380                 message = "LYXSRV:" + clients[i] + ":bye\n";
381                 pipes.send(message);
382         }
383 }
384
385
386 /* ---F+------------------------------------------------------------------ *\
387    Function  : ServerCallback
388     Called by : LyXComm
389     Purpose   : handle data gotten from communication
390 \* ---F------------------------------------------------------------------- */
391
392 void LyXServer::callback(LyXServer * serv, string const & msg)
393 {
394         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
395                                  << msg << '\'' << endl;
396
397         char const * p = msg.c_str();
398
399         // --- parse the string --------------------------------------------
400         //
401         //  Format: LYXCMD:<client>:<func>:<argstring>\n
402         //
403         bool server_only = false;
404         while (*p) {
405                 // --- 1. check 'header' ---
406
407                 if (compare(p, "LYXSRV:", 7) == 0) {
408                         server_only = true;
409                 } else if (0 != compare(p, "LYXCMD:", 7)) {
410                         lyxerr << "LyXServer: Unknown request \""
411                                << p << '"' << endl;
412                         return;
413                 }
414                 p += 7;
415
416                 // --- 2. for the moment ignore the client name ---
417                 string client;
418                 while (*p && *p != ':')
419                         client += char(*p++);
420                 if (*p == ':') ++p;
421                 if (!*p) return;
422
423                 // --- 3. get function name ---
424                 string cmd;
425                 while (*p && *p != ':')
426                         cmd += char(*p++);
427
428                 // --- 4. parse the argument ---
429                 string arg;
430                 if (!server_only && *p == ':' && *(++p)) {
431                         while (*p && *p != '\n')
432                                 arg += char(*p++);
433                         if (*p) ++p;
434                 }
435
436                 lyxerr[Debug::LYXSERVER]
437                         << "LyXServer: Client: '" << client
438                         << "' Command: '" << cmd
439                         << "' Argument: '" << arg << '\'' << endl;
440
441                 // --- lookup and exec the command ------------------
442
443                 if (server_only) {
444                         string buf;
445                         // return the greeting to inform the client that
446                         // we are listening.
447                         if (cmd == "hello") {
448                                 // One more client
449                                 if (serv->numclients == MAX_CLIENTS) { //paranoid check
450                                         lyxerr[Debug::LYXSERVER]
451                                                 << "LyXServer: too many clients..."
452                                                 << endl;
453                                         return;
454                                 }
455                                 int i= 0; //find place in clients[]
456                                 while (!serv->clients[i].empty()
457                                        && i<serv->numclients)
458                                         ++i;
459                                 serv->clients[i] = client;
460                                 serv->numclients++;
461                                 buf = "LYXSRV:" + client + ":hello\n";
462                                 lyxerr[Debug::LYXSERVER]
463                                         << "LyXServer: Greeting "
464                                         << client << endl;
465                                 serv->pipes.send(buf);
466                         } else if (cmd == "bye") {
467                                 // If clients == 0 maybe we should reset the pipes
468                                 // to prevent fake callbacks
469                                 int i = 0; //look if client is registered
470                                 for (; i < serv->numclients; ++i) {
471                                         if (serv->clients[i] == client) break;
472                                 }
473                                 if (i < serv->numclients) {
474                                         serv->numclients--;
475                                         serv->clients[i].erase();
476                                         lyxerr[Debug::LYXSERVER]
477                                                 << "LyXServer: Client "
478                                                 << client << " said goodbye"
479                                                 << endl;
480                                 } else {
481                                         lyxerr[Debug::LYXSERVER]
482                                                 << "LyXServer: ignoring bye messge from unregistered client"
483                                                 << client << endl;
484                                 }
485                         } else {
486                                 lyxerr <<"LyXServer: Undefined server command "
487                                        << cmd << '.' << endl;
488                         }
489                         return;
490                 }
491
492                 if (!cmd.empty()) {
493                         // which lyxfunc should we let it connect to?
494                         // The correct solution would be to have a
495                         // specialized (non-gui) BufferView. But how do
496                         // we do it now? Probably we should just let it
497                         // connect to the lyxfunc in the single LyXView we
498                         // support currently. (Lgb)
499
500
501                         serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
502                         string const rval = serv->func->getMessage();
503
504                         //modified june 1999 stefano@zool.su.se:
505                         //all commands produce an INFO or ERROR message
506                         //in the output pipe, even if they do not return
507                         //anything. See chapter 4 of Customization doc.
508                         string buf;
509                         if (serv->func->errorStat())
510                                 buf = "ERROR:";
511                         else
512                                 buf = "INFO:";
513                         buf += client + ':' + cmd + ':' +  rval + '\n';
514                         serv->pipes.send(buf);
515
516                         // !!! we don't do any error checking -
517                         //  if the client won't listen, the
518                         //  message is lost and others too
519                         //  maybe; so the client should empty
520                         //  the outpipe before issuing a request.
521
522                         // not found
523                 }
524         }  /* while *p */
525 }
526
527
528 /* ---F+------------------------------------------------------------------ *\
529    Function  : LyXNotifyClient
530    Called by : WorkAreaKeyPress
531    Purpose   : send a notify messge to a client
532    Parameters: s - string to send
533    Returns   : nothing
534    \* ---F------------------------------------------------------------------- */
535
536 void LyXServer::notifyClient(string const & s)
537 {
538         string buf = string("NOTIFY:") + s + "\n";
539         pipes.send(buf);
540 }