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