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