]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
- Link against qt-mt333.lib which is what the current qt3 cvs produces
[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         int fd;
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         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         fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
200 #endif
201
202         if (fd < 0) {
203                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
204                        << strerror(errno) << endl;
205                 unlink(filename);
206                 return -1;
207         }
208
209         if (!write) {
210                 lyx_gui::register_socket_callback(fd, boost::bind(&LyXComm::read_ready, this));
211         }
212
213         return fd;
214 }
215
216
217 void LyXComm::endPipe(int & fd, string const & filename, bool write)
218 {
219         if (fd < 0)
220                 return;
221
222         if (!write) {
223                 lyx_gui::unregister_socket_callback(fd);
224         }
225
226 #ifdef __EMX__
227         APIRET rc;
228         int errnum;
229
230         rc = DosDisConnectNPipe(fd);
231         if (rc != NO_ERROR) {
232                 errnum = TranslateOS2Error(rc);
233                 lyxerr << "LyXComm: Could not disconnect pipe " << filename
234                        << '\n' << strerror(errnum) << endl;
235                 return;
236         }
237 #endif
238
239         if (::close(fd) < 0) {
240                 lyxerr << "LyXComm: Could not close pipe " << filename
241                        << '\n' << strerror(errno) << endl;
242         }
243
244 // OS/2 pipes are deleted automatically
245 #ifndef __EMX__
246         if (unlink(filename) < 0) {
247                 lyxerr << "LyXComm: Could not remove pipe " << filename
248                        << '\n' << strerror(errno) << endl;
249         };
250 #endif
251
252         fd = -1;
253 }
254
255
256 void LyXComm::emergencyCleanup()
257 {
258         if (!pipename.empty()) {
259                 endPipe(infd, inPipeName(), false);
260                 endPipe(outfd, outPipeName(), true);
261         }
262 }
263
264
265 // Receives messages and sends then to client
266 void LyXComm::read_ready()
267 {
268         // nb! make read_buffer_ a class-member for multiple sessions
269         static string read_buffer_;
270         read_buffer_.erase();
271
272         int const charbuf_size = 100;
273         char charbuf[charbuf_size];
274
275         errno = 0;
276         int status;
277         // the single = is intended here.
278         while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
279
280                 if (status > 0) {
281                         charbuf[status] = '\0'; // turn it into a c string
282                         read_buffer_ += rtrim(charbuf, "\r");
283                         // commit any commands read
284                         while (read_buffer_.find('\n') != string::npos) {
285                                 // split() grabs the entire string if
286                                 // the delim /wasn't/ found. ?:-P
287                                 string cmd;
288                                 read_buffer_= split(read_buffer_, cmd,'\n');
289                                 lyxerr[Debug::LYXSERVER]
290                                         << "LyXComm: status:" << status
291                                         << ", read_buffer_:" << read_buffer_
292                                         << ", cmd:" << cmd << endl;
293                                 if (!cmd.empty())
294                                         clientcb(client, cmd);
295                                         //\n or not \n?
296                         }
297                 }
298                 if (errno == EAGAIN) {
299                         errno = 0;
300                         return;
301                 }
302                 if (errno != 0) {
303                         lyxerr << "LyXComm: " << strerror(errno) << endl;
304                         if (!read_buffer_.empty()) {
305                                 lyxerr << "LyXComm: truncated command: "
306                                        << read_buffer_ << endl;
307                                 read_buffer_.erase();
308                         }
309                         break; // reset connection
310                 }
311         }
312
313         // The connection gets reset in errno != EAGAIN
314         // Why does it need to be reset if errno == 0?
315         closeConnection();
316         openConnection();
317         errno = 0;
318 }
319
320
321 void LyXComm::send(string const & msg)
322 {
323         if (msg.empty()) {
324                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
325                        << endl;
326                 return;
327         }
328
329         if (lyxerr.debugging(Debug::LYXSERVER)) {
330                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
331         }
332
333         if (pipename.empty()) return;
334
335         if (!ready) {
336                 lyxerr << "LyXComm: Pipes are closed. Could not send "
337                        << msg << endl;
338         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
339                 lyxerr << "LyXComm: Error sending message: " << msg
340                        << '\n' << strerror(errno)
341                        << "\nLyXComm: Resetting connection" << endl;
342                 closeConnection();
343                 openConnection();
344         }
345 #ifdef __EMX__
346         APIRET rc;
347         int errnum;
348         rc = DosResetBuffer(outfd);     // To avoid synchronization problems.
349         if (rc != NO_ERROR) {
350                 errnum = TranslateOS2Error(rc);
351                 lyxerr << "LyXComm: Message could not be flushed: " << msg
352                        << '\n' << strerror(errnum) << endl;
353         }
354 #endif
355 }
356
357
358 string const LyXComm::inPipeName() const
359 {
360         return pipename + string(".in");
361 }
362
363
364 string const LyXComm::outPipeName() const
365 {
366         return pipename + string(".out");
367 }
368
369
370 // LyXServer class
371
372 LyXServer::~LyXServer()
373 {
374         // say goodbye to clients so they stop sending messages
375         // modified june 1999 by stefano@zool.su.se to send as many bye
376         // messages as there are clients, each with client's name.
377         string message;
378         for (int i= 0; i<numclients; ++i) {
379                 message = "LYXSRV:" + clients[i] + ":bye\n";
380                 pipes.send(message);
381         }
382 }
383
384
385 /* ---F+------------------------------------------------------------------ *\
386    Function  : ServerCallback
387     Called by : LyXComm
388     Purpose   : handle data gotten from communication
389 \* ---F------------------------------------------------------------------- */
390
391 void LyXServer::callback(LyXServer * serv, string const & msg)
392 {
393         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
394                                  << msg << '\'' << endl;
395
396         char const * p = msg.c_str();
397
398         // --- parse the string --------------------------------------------
399         //
400         //  Format: LYXCMD:<client>:<func>:<argstring>\n
401         //
402         bool server_only = false;
403         while (*p) {
404                 // --- 1. check 'header' ---
405
406                 if (compare(p, "LYXSRV:", 7) == 0) {
407                         server_only = true;
408                 } else if (0 != compare(p, "LYXCMD:", 7)) {
409                         lyxerr << "LyXServer: Unknown request \""
410                                << p << '"' << endl;
411                         return;
412                 }
413                 p += 7;
414
415                 // --- 2. for the moment ignore the client name ---
416                 string client;
417                 while (*p && *p != ':')
418                         client += char(*p++);
419                 if (*p == ':') ++p;
420                 if (!*p) return;
421
422                 // --- 3. get function name ---
423                 string cmd;
424                 while (*p && *p != ':')
425                         cmd += char(*p++);
426
427                 // --- 4. parse the argument ---
428                 string arg;
429                 if (!server_only && *p == ':' && *(++p)) {
430                         while (*p && *p != '\n')
431                                 arg += char(*p++);
432                         if (*p) ++p;
433                 }
434
435                 lyxerr[Debug::LYXSERVER]
436                         << "LyXServer: Client: '" << client
437                         << "' Command: '" << cmd
438                         << "' Argument: '" << arg << '\'' << endl;
439
440                 // --- lookup and exec the command ------------------
441
442                 if (server_only) {
443                         string buf;
444                         // return the greeting to inform the client that
445                         // we are listening.
446                         if (cmd == "hello") {
447                                 // One more client
448                                 if (serv->numclients == MAX_CLIENTS) { //paranoid check
449                                         lyxerr[Debug::LYXSERVER]
450                                                 << "LyXServer: too many clients..."
451                                                 << endl;
452                                         return;
453                                 }
454                                 int i= 0; //find place in clients[]
455                                 while (!serv->clients[i].empty()
456                                        && i<serv->numclients)
457                                         ++i;
458                                 serv->clients[i] = client;
459                                 serv->numclients++;
460                                 buf = "LYXSRV:" + client + ":hello\n";
461                                 lyxerr[Debug::LYXSERVER]
462                                         << "LyXServer: Greeting "
463                                         << client << endl;
464                                 serv->pipes.send(buf);
465                         } else if (cmd == "bye") {
466                                 // If clients == 0 maybe we should reset the pipes
467                                 // to prevent fake callbacks
468                                 int i = 0; //look if client is registered
469                                 for (; i < serv->numclients; ++i) {
470                                         if (serv->clients[i] == client) break;
471                                 }
472                                 if (i < serv->numclients) {
473                                         serv->numclients--;
474                                         serv->clients[i].erase();
475                                         lyxerr[Debug::LYXSERVER]
476                                                 << "LyXServer: Client "
477                                                 << client << " said goodbye"
478                                                 << endl;
479                                 } else {
480                                         lyxerr[Debug::LYXSERVER]
481                                                 << "LyXServer: ignoring bye messge from unregistered client"
482                                                 << client << endl;
483                                 }
484                         } else {
485                                 lyxerr <<"LyXServer: Undefined server command "
486                                        << cmd << '.' << endl;
487                         }
488                         return;
489                 }
490
491                 if (!cmd.empty()) {
492                         // which lyxfunc should we let it connect to?
493                         // The correct solution would be to have a
494                         // specialized (non-gui) BufferView. But how do
495                         // we do it now? Probably we should just let it
496                         // connect to the lyxfunc in the single LyXView we
497                         // support currently. (Lgb)
498
499
500                         serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
501                         string const rval = serv->func->getMessage();
502
503                         //modified june 1999 stefano@zool.su.se:
504                         //all commands produce an INFO or ERROR message
505                         //in the output pipe, even if they do not return
506                         //anything. See chapter 4 of Customization doc.
507                         string buf;
508                         if (serv->func->errorStat())
509                                 buf = "ERROR:";
510                         else
511                                 buf = "INFO:";
512                         buf += client + ':' + cmd + ':' +  rval + '\n';
513                         serv->pipes.send(buf);
514
515                         // !!! we don't do any error checking -
516                         //  if the client won't listen, the
517                         //  message is lost and others too
518                         //  maybe; so the client should empty
519                         //  the outpipe before issuing a request.
520
521                         // not found
522                 }
523         }  /* while *p */
524 }
525
526
527 /* ---F+------------------------------------------------------------------ *\
528    Function  : LyXNotifyClient
529    Called by : WorkAreaKeyPress
530    Purpose   : send a notify messge to a client
531    Parameters: s - string to send
532    Returns   : nothing
533    \* ---F------------------------------------------------------------------- */
534
535 void LyXServer::notifyClient(string const & s)
536 {
537         string buf = string("NOTIFY:") + s + "\n";
538         pipes.send(buf);
539 }