]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
3e16f061389964d1d8574028a98af5e0d31460f3
[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(inPipeName(), false)) == -1)
114                 return;
115
116         if ((outfd = startPipe(outPipeName(), true)) == -1) {
117                 endPipe(infd, inPipeName());
118                 return;
119         }
120  
121         if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
122                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
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, inPipeName());
148         endPipe(outfd, outPipeName());
149  
150         ready = false;
151 }
152
153 int LyXComm::startPipe(string const & filename, bool write)
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(), write ? (O_RDWR) : (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  
214         if (!write)
215                 fl_add_io_callback(fd, FL_READ, C_LyXComm_callback, this);
216  
217         return fd;
218 }
219
220
221 void LyXComm::endPipe(int & fd, string const & filename)
222 {
223         if (fd < 0)
224                 return;
225
226 #ifdef __EMX__
227         APIRET rc;
228         int errnum;
229  
230         rc = DosDisConnectNPipe(fd);
231         if (rc != NO_ERROR) {
232                 errnum = TranslateOS2Error(rc);
233                 lyxerr << "LyXComm: Could not disconnect pipe " << filename
234                        << '\n' << strerror(errnum) << endl;
235                 return;
236         }
237 #endif
238  
239         if (::close(fd) < 0) {
240                 lyxerr << "LyXComm: Could not close pipe " << filename
241                        << '\n' << strerror(errno) << endl;
242         }
243  
244 // OS/2 pipes are deleted automatically
245 #ifndef __EMX__
246         if (lyx::unlink(filename) < 0){
247                 lyxerr << "LyXComm: Could not remove pipe " << filename
248                        << '\n' << strerror(errno) << endl;
249         };
250 #endif
251
252         fd = -1;
253 }
254
255
256 void LyXComm::emergencyCleanup()
257 {
258         endPipe(infd, inPipeName());
259         endPipe(outfd, outPipeName());
260 }
261
262
263 // Receives messages and sends then to client
264 void LyXComm::callback(int fd, void *v)
265 {
266         LyXComm * c = static_cast<LyXComm*>(v);
267  
268         if (lyxerr.debugging(Debug::LYXSERVER)) {
269                 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
270         }
271  
272         const int CMDBUFLEN = 100;
273         char charbuf[CMDBUFLEN];
274         string cmd;
275 // nb! make lsbuf a class-member for multiple sessions
276         static string lsbuf;
277
278         errno = 0;
279         int status;
280         // the single = is intended here.
281         while((status = read(fd, charbuf, CMDBUFLEN-1)))
282         {// break and return in loop
283                 if (status > 0) // got something
284                 {
285                         charbuf[status]= '\0'; // turn it into a c string
286                         lsbuf += strip(charbuf, '\r');
287                         // commit any commands read
288                         while(lsbuf.find('\n') != string::npos) // while still
289                                                         // commands
290                                                         // left 
291                         {
292                                 // split() grabs the entire string if
293                                 // the delim /wasn't/ found. ?:-P 
294                                 lsbuf= split(lsbuf, cmd,'\n');
295                                 lyxerr[Debug::LYXSERVER]
296                                         << "LyXComm: status:" << status
297                                         << ", lsbuf:" << lsbuf 
298                                         << ", cmd:" << cmd << endl;
299                                 if (!cmd.empty())
300                                         c->clientcb(c->client, cmd); 
301                                         //\n or not \n?
302                         }
303                 }
304                 if (errno == EAGAIN)
305                 {  // EAGAIN is not really an error , it means we're
306                    // only reading too fast for the writing process on
307                    // the other end of the pipe. 
308                         errno = 0;
309                         return; // up to libforms select-loop (*crunch*)
310                 }
311                 if (errno != 0 )
312                 {
313                         lyxerr << "LyXComm: " << strerror(errno) << endl;
314                         if (!lsbuf.empty())
315                         {
316                                 lyxerr << "LyxComm: truncated command: " 
317                                        << lsbuf << endl;
318                                 lsbuf.erase();
319                         }
320                         break; // reset connection
321                 }
322         }
323         c->closeConnection();
324         c->openConnection();
325         errno= 0;
326 }
327
328
329 void LyXComm::send(string const & msg)
330 {
331         if (msg.empty()) {
332                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
333                        << endl;
334                 return;
335         }
336
337         if (lyxerr.debugging(Debug::LYXSERVER)) {
338                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
339         }
340
341         if (pipename.empty()) return;
342
343         if (!ready) {
344                 lyxerr << "LyXComm: Pipes are closed. Could not send "
345                        << msg << endl;
346         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
347                 lyxerr << "LyXComm: Error sending message: " << msg
348                        << '\n' << strerror(errno)
349                        << "\nLyXComm: Resetting connection" << endl;
350                 closeConnection();
351                 openConnection();
352         }
353 #ifdef __EMX__
354         APIRET rc;
355         int errnum;
356         rc = DosResetBuffer(outfd);     // To avoid synchronization problems.
357         if (rc != NO_ERROR) {
358                 errnum = TranslateOS2Error(rc);
359                 lyxerr << "LyXComm: Message could not be flushed: " << msg
360                        << '\n' << strerror(errnum) << endl;
361         }
362 #endif
363 }
364
365
366 // LyXServer class
367
368 LyXServer::~LyXServer()
369 {
370         // say goodbye to clients so they stop sending messages
371         // modified june 1999 by stefano@zool.su.se to send as many bye
372         // messages as there are clients, each with client's name.
373         string message;
374         for (int i= 0; i<numclients; ++i) {
375                 message = "LYXSRV:" + clients[i] + ":bye\n";
376                 pipes.send(message);
377         }
378 }
379
380
381 /* ---F+------------------------------------------------------------------ *\
382    Function  : ServerCallback
383     Called by : LyXComm
384     Purpose   : handle data gotten from communication
385 \* ---F------------------------------------------------------------------- */
386
387 void LyXServer::callback(LyXServer * serv, string const & msg)
388 {
389         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
390                                  << msg << '\'' << endl;
391  
392         char const * p = msg.c_str();
393  
394         // --- parse the string --------------------------------------------
395         //
396         //  Format: LYXCMD:<client>:<func>:<argstring>\n
397         //
398         bool server_only = false;
399         while(*p) {
400                 // --- 1. check 'header' ---
401
402                 if (compare(p, "LYXSRV:", 7) == 0) {
403                         server_only = true; 
404                 } else if (0 != compare(p, "LYXCMD:", 7)) {
405                         lyxerr << "LyXServer: Unknown request \"" << p << "\"" << endl;
406                         return;
407                 }
408                 p += 7;
409                 
410                 // --- 2. for the moment ignore the client name ---
411                 string client;
412                 while(*p && *p != ':')
413                         client += char(*p++);
414                 if (*p == ':') ++p;
415                 if (!*p) return;
416                 
417                 // --- 3. get function name ---
418                 string cmd;
419                 while(*p && *p != ':')
420                         cmd += char(*p++);
421                 
422                 // --- 4. parse the argument ---
423                 string arg;
424                 if (!server_only && *p == ':' && *(++p)) {
425                         while(*p && *p != '\n')
426                                 arg += char(*p++);
427                         if (*p) ++p;
428                 }
429  
430                 lyxerr[Debug::LYXSERVER]
431                         << "LyXServer: Client: '" << client
432                         << "' Command: '" << cmd
433                         << "' Argument: '" << arg << '\'' << endl;
434                 
435                 // --- lookup and exec the command ------------------
436  
437                 if (server_only) {
438                         string buf;
439                         // return the greeting to inform the client that 
440                         // we are listening.
441                         if (cmd == "hello") {
442                                 // One more client
443                                 if (serv->numclients == MAX_CLIENTS){ //paranoid check
444                                         lyxerr[Debug::LYXSERVER]
445                                                 << "LyXServer: too many clients..."
446                                                 << endl;
447                                         return;
448                                 }
449                                 int i= 0; //find place in clients[]
450                                 while (!serv->clients[i].empty() 
451                                        && i<serv->numclients) 
452                                         ++i;
453                                 serv->clients[i] = client;
454                                 serv->numclients++;
455                                 buf = "LYXSRV:" + client + ":hello\n";
456                                 lyxerr[Debug::LYXSERVER]
457                                         << "LyXServer: Greeting "
458                                         << client << endl;
459                                 serv->pipes.send(buf);
460                         } else if (cmd == "bye") {
461                                 // If clients == 0 maybe we should reset the pipes
462                                 // to prevent fake callbacks
463                                 int i = 0; //look if client is registered
464                                 for (; i < serv->numclients; ++i) {
465                                         if (serv->clients[i] == client) break;
466                                 }
467                                 if (i < serv->numclients) {
468                                         serv->numclients--;
469                                         serv->clients[i].erase();
470                                         lyxerr[Debug::LYXSERVER]
471                                                 << "LyXServer: Client "
472                                                 << client << " said goodbye"
473                                                 << endl;
474                                 } else {
475                                         lyxerr[Debug::LYXSERVER]
476                                                 << "LyXServer: ignoring bye messge from unregistered client"
477                                                 << client << endl;
478                                 }
479                         } else {
480                                 lyxerr <<"LyXServer: Undefined server command "
481                                        << cmd << "." << endl;
482                         }
483                         return;
484                 }
485  
486                 if (!cmd.empty()) {
487                         // which lyxfunc should we let it connect to?
488                         // The correct solution would be to have a
489                         // specialized (non-gui) BufferView. But how do
490                         // we do it now? Probably we should just let it
491                         // connect to the lyxfunc in the single LyXView we
492                         // support currently. (Lgb)
493
494                         int action = lyxaction.LookupFunc(cmd);
495                         //int action = -1;
496                         string rval, buf;
497                     
498                         if (action>= 0) {
499                                 rval = serv->func->dispatch(action, arg);
500                         } else {
501                                 rval = "Unknown command";
502                         }
503
504       //modified june 1999 stefano@zool.su.se:
505                         //all commands produce an INFO or ERROR message
506                         //in the output pipe, even if they do not return
507                         //anything. See chapter 4 of Customization doc.
508                         if (action<0 || serv->func->errorStat())
509                                 buf = "ERROR:";
510                         else
511                                 buf = "INFO:";
512                         buf += string(client) + ":" + cmd       + ":" + rval + "\n";
513                         serv->pipes.send(buf);
514
515                         // !!! we don't do any error checking -
516                         //  if the client won't listen, the
517                         //  message is lost and others too
518                         //  maybe; so the client should empty
519                         //  the outpipe before issuing a request.
520                 
521                         // not found
522                 }
523         }  /* while *p */
524 }
525
526
527 /* ---F+------------------------------------------------------------------ *\
528    Function  : LyxNotifyClient
529    Called by : WorkAreaKeyPress
530    Purpose   : send a notify messge to a client
531    Parameters: s - string to send
532    Returns   : nothing
533    \* ---F------------------------------------------------------------------- */
534
535 void LyXServer::notifyClient(string const & s)
536 {
537         string buf = string("NOTIFY:") + s + "\n";
538         pipes.send(buf);
539 }
540