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