]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
redraw fix 1.
[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 "lyxfunc.h"
54 #include "support/lstrings.h"
55 #include "support/lyxlib.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 std::endl;
68
69 // provide an empty mkfifo() if we do not have one. This disables the
70 // lyxserver.
71 #ifndef HAVE_MKFIFO
72 int mkfifo(char const * __path, mode_t __mode) {
73         return 0;
74 }
75 #endif
76
77
78 /* === variables ========================================================= */
79
80 extern "C" {
81
82         // C wrapper
83         static
84         void C_LyXComm_callback(int fd, void *v)
85         {
86                 LyXComm::callback(fd, v);
87         }
88
89 }
90
91
92 // LyXComm class
93
94 // Open pipes
95 void LyXComm::openConnection()
96 {
97         lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
98
99         // If we are up, that's an error
100         if (ready) {
101                 lyxerr << "LyXComm: Already connected" << endl;
102                 return;
103         }
104         // We assume that we don't make it
105         ready = false;
106
107         if (pipename.empty()) {
108                 lyxerr[Debug::LYXSERVER]
109                         << "LyXComm: server is disabled, nothing to do"
110                         << endl;
111                 return;
112         }
113
114         if ((infd = startPipe(inPipeName(), false)) == -1)
115                 return;
116
117         if ((outfd = startPipe(outPipeName(), true)) == -1) {
118                 endPipe(infd, inPipeName());
119                 return;
120         }
121
122         if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
123                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
124                        << '\n' << strerror(errno) << endl;
125                 return;
126         }
127
128         // We made it!
129         ready = true;
130         lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
131 }
132
133
134 /// Close pipes
135 void LyXComm::closeConnection()
136 {
137         lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
138
139         if (pipename.empty()) {
140                 lyxerr[Debug::LYXSERVER]
141                         << "LyXComm: server is disabled, nothing to do"
142                         << endl;
143                 return;
144         }
145
146         if (!ready) {
147                 lyxerr << "LyXComm: Already disconnected" << endl;
148                 return;
149         }
150
151         endPipe(infd, inPipeName());
152         endPipe(outfd, outPipeName());
153
154         ready = false;
155 }
156
157 int LyXComm::startPipe(string const & filename, bool write)
158 {
159         int fd;
160
161 #ifdef __EMX__
162         HPIPE os2fd;
163         APIRET rc;
164         int errnum;
165         // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
166         // The current emx implementation of access() won't work with pipes.
167         rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
168                 NP_NOWAIT|0x01, 0600, 0600, 0);
169         if (rc == ERROR_PIPE_BUSY) {
170                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
171                        << "If no other LyX program is active, please delete"
172                         " the pipe by hand and try again." << endl;
173                 pipename.erase();
174                 return -1;
175         }
176
177         if (rc != NO_ERROR) {
178                 errnum = TranslateOS2Error(rc);
179                 lyxerr <<"LyXComm: Could not create pipe " << filename
180                        << strerror(errnum) << endl;
181                 return -1;
182         };
183         // Listen to it.
184         rc = DosConnectNPipe(os2fd);
185         if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
186                 errnum = TranslateOS2Error(rc);
187                 lyxerr <<"LyXComm: Could not create pipe " << filename
188                        << strerror(errnum) << endl;
189                 return -1;
190         };
191         // Imported handles can be used both with OS/2 APIs and emx
192         // library functions.
193         fd = _imphandle(os2fd);
194 #else
195         if (::access(filename.c_str(), F_OK) == 0) {
196                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
197                        << "If no other LyX program is active, please delete"
198                         " the pipe by hand and try again." << endl;
199                 pipename.erase();
200                 return -1;
201         }
202
203         if (::mkfifo(filename.c_str(), 0600) < 0) {
204                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
205                        << strerror(errno) << endl;
206                 return -1;
207         };
208         fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
209 #endif
210
211         if (fd < 0) {
212                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
213                        << strerror(errno) << endl;
214                 lyx::unlink(filename);
215                 return -1;
216         }
217
218         if (!write)
219                 fl_add_io_callback(fd, FL_READ, C_LyXComm_callback, this);
220
221         return fd;
222 }
223
224
225 void LyXComm::endPipe(int & fd, string const & filename)
226 {
227         if (fd < 0)
228                 return;
229
230 #ifdef __EMX__
231         APIRET rc;
232         int errnum;
233
234         rc = DosDisConnectNPipe(fd);
235         if (rc != NO_ERROR) {
236                 errnum = TranslateOS2Error(rc);
237                 lyxerr << "LyXComm: Could not disconnect pipe " << filename
238                        << '\n' << strerror(errnum) << endl;
239                 return;
240         }
241 #endif
242
243         if (::close(fd) < 0) {
244                 lyxerr << "LyXComm: Could not close pipe " << filename
245                        << '\n' << strerror(errno) << endl;
246         }
247
248 // OS/2 pipes are deleted automatically
249 #ifndef __EMX__
250         if (lyx::unlink(filename) < 0) {
251                 lyxerr << "LyXComm: Could not remove pipe " << filename
252                        << '\n' << strerror(errno) << endl;
253         };
254 #endif
255
256         fd = -1;
257 }
258
259
260 void LyXComm::emergencyCleanup()
261 {
262         if (!pipename.empty()) {
263                 endPipe(infd, inPipeName());
264                 endPipe(outfd, outPipeName());
265         }
266 }
267
268
269 // Receives messages and sends then to client
270 void LyXComm::callback(int fd, void *v)
271 {
272         LyXComm * c = static_cast<LyXComm*>(v);
273
274         if (lyxerr.debugging(Debug::LYXSERVER)) {
275                 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
276         }
277
278         const int CMDBUFLEN = 100;
279         char charbuf[CMDBUFLEN];
280         string cmd;
281 // nb! make lsbuf a class-member for multiple sessions
282         static string lsbuf;
283
284         errno = 0;
285         int status;
286         // the single = is intended here.
287         while ((status = read(fd, charbuf, CMDBUFLEN-1)))
288         {// break and return in loop
289                 if (status > 0) // got something
290                 {
291                         charbuf[status]= '\0'; // turn it into a c string
292                         lsbuf += strip(charbuf, '\r');
293                         // commit any commands read
294                         while (lsbuf.find('\n') != string::npos) // while still
295                                                         // commands
296                                                         // left
297                         {
298                                 // split() grabs the entire string if
299                                 // the delim /wasn't/ found. ?:-P
300                                 lsbuf= split(lsbuf, cmd,'\n');
301                                 lyxerr[Debug::LYXSERVER]
302                                         << "LyXComm: status:" << status
303                                         << ", lsbuf:" << lsbuf
304                                         << ", cmd:" << cmd << endl;
305                                 if (!cmd.empty())
306                                         c->clientcb(c->client, cmd);
307                                         //\n or not \n?
308                         }
309                 }
310                 if (errno == EAGAIN)
311                 {  // EAGAIN is not really an error , it means we're
312                    // only reading too fast for the writing process on
313                    // the other end of the pipe.
314                         errno = 0;
315                         return; // up to libforms select-loop (*crunch*)
316                 }
317                 if (errno != 0)
318                 {
319                         lyxerr << "LyXComm: " << strerror(errno) << endl;
320                         if (!lsbuf.empty())
321                         {
322                                 lyxerr << "LyxComm: truncated command: "
323                                        << lsbuf << endl;
324                                 lsbuf.erase();
325                         }
326                         break; // reset connection
327                 }
328         }
329         c->closeConnection();
330         c->openConnection();
331         errno= 0;
332 }
333
334
335 void LyXComm::send(string const & msg)
336 {
337         if (msg.empty()) {
338                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
339                        << endl;
340                 return;
341         }
342
343         if (lyxerr.debugging(Debug::LYXSERVER)) {
344                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
345         }
346
347         if (pipename.empty()) return;
348
349         if (!ready) {
350                 lyxerr << "LyXComm: Pipes are closed. Could not send "
351                        << msg << endl;
352         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
353                 lyxerr << "LyXComm: Error sending message: " << msg
354                        << '\n' << strerror(errno)
355                        << "\nLyXComm: Resetting connection" << endl;
356                 closeConnection();
357                 openConnection();
358         }
359 #ifdef __EMX__
360         APIRET rc;
361         int errnum;
362         rc = DosResetBuffer(outfd);     // To avoid synchronization problems.
363         if (rc != NO_ERROR) {
364                 errnum = TranslateOS2Error(rc);
365                 lyxerr << "LyXComm: Message could not be flushed: " << msg
366                        << '\n' << strerror(errnum) << endl;
367         }
368 #endif
369 }
370
371
372 // LyXServer class
373
374 LyXServer::~LyXServer()
375 {
376         // say goodbye to clients so they stop sending messages
377         // modified june 1999 by stefano@zool.su.se to send as many bye
378         // messages as there are clients, each with client's name.
379         string message;
380         for (int i= 0; i<numclients; ++i) {
381                 message = "LYXSRV:" + clients[i] + ":bye\n";
382                 pipes.send(message);
383         }
384 }
385
386
387 /* ---F+------------------------------------------------------------------ *\
388    Function  : ServerCallback
389     Called by : LyXComm
390     Purpose   : handle data gotten from communication
391 \* ---F------------------------------------------------------------------- */
392
393 void LyXServer::callback(LyXServer * serv, string const & msg)
394 {
395         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
396                                  << msg << '\'' << endl;
397
398         char const * p = msg.c_str();
399
400         // --- parse the string --------------------------------------------
401         //
402         //  Format: LYXCMD:<client>:<func>:<argstring>\n
403         //
404         bool server_only = false;
405         while (*p) {
406                 // --- 1. check 'header' ---
407
408                 if (compare(p, "LYXSRV:", 7) == 0) {
409                         server_only = true;
410                 } else if (0 != compare(p, "LYXCMD:", 7)) {
411                         lyxerr << "LyXServer: Unknown request \"" << 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->verboseDispatch(cmd + ' ' + arg, false);
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 }