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