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