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