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