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