]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
insetquote fix; getStatus tweaks
[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 <cstring>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <fcntl.h>
44 #include <cerrno>
45 #include FORMS_H_LOCATION
46
47 #ifdef __GNUG__
48 #pragma implementation
49 #endif
50
51 #include "lyxserver.h"
52 #include "lyx_main.h"
53 #include "debug.h"
54 #include "LyXAction.h"
55 #include "lyxfunc.h"
56 #include "support/lstrings.h"
57 #include "support/lyxlib.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 const * __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.erase();
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.erase();
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 (lyx::unlink(tmp) < 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 (lyx::unlink(tmp) < 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.erase();
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
434                 if (compare(p, "LYXSRV:", 7) == 0) {
435                         server_only = true; 
436                 } else if (0 != compare(p, "LYXCMD:", 7)) {
437                         lyxerr << "LyXServer: Unknown request" << endl;
438                         return;
439                 }
440                 p += 7;
441                 
442                 // --- 2. for the moment ignore the client name ---
443                 string client;
444                 while(*p && *p != ':')
445                         client += char(*p++);
446                 if (*p == ':') ++p;
447                 if (!*p) return;
448                 
449                 // --- 3. get function name ---
450                 string cmd;
451                 while(*p && *p != ':')
452                         cmd += char(*p++);
453                 
454                 // --- 4. parse the argument ---
455                 string arg;
456                 if (!server_only && *p == ':' && *(++p)) {
457                         while(*p && *p != '\n')
458                                 arg += char(*p++);
459                         if (*p) ++p;
460                 }
461  
462                 lyxerr[Debug::LYXSERVER]
463                         << "LyXServer: Client: '" << client
464                         << "' Command: '" << cmd
465                         << "' Argument: '" << arg << '\'' << endl;
466                 
467                 // --- lookup and exec the command ------------------
468  
469                 if (server_only) {
470                         string buf;
471                         // return the greeting to inform the client that 
472                         // we are listening.
473                         if (cmd == "hello") {
474                                 // One more client
475                                 if (serv->numclients == MAX_CLIENTS){ //paranoid check
476                                         lyxerr[Debug::LYXSERVER]
477                                                 << "LyXServer: too many clients..."
478                                                 << endl;
479                                         return;
480                                 }
481                                 int i= 0; //find place in clients[]
482                                 while (!serv->clients[i].empty() 
483                                        && i<serv->numclients) 
484                                         ++i;
485                                 serv->clients[i] = client;
486                                 serv->numclients++;
487                                 buf = "LYXSRV:" + client + ":hello\n";
488                                 lyxerr[Debug::LYXSERVER]
489                                         << "LyXServer: Greeting "
490                                         << client << endl;
491                                 serv->pipes.send(buf);
492                         } else if (cmd == "bye") {
493                                 // If clients == 0 maybe we should reset the pipes
494                                 // to prevent fake callbacks
495                                 int i = 0; //look if client is registered
496                                 for (; i < serv->numclients; ++i) {
497                                         if (serv->clients[i] == client) break;
498                                 }
499                                 if (i < serv->numclients) {
500                                         serv->numclients--;
501                                         serv->clients[i].erase();
502                                         lyxerr[Debug::LYXSERVER]
503                                                 << "LyXServer: Client "
504                                                 << client << " said goodbye"
505                                                 << endl;
506                                 } else {
507                                         lyxerr[Debug::LYXSERVER]
508                                                 << "LyXServer: ignoring bye messge from unregistered client"
509                                                 << client << endl;
510                                 }
511                         } else {
512                                 lyxerr <<"LyXServer: Undefined server command "
513                                        << cmd << "." << endl;
514                         }
515                         return;
516                 }
517  
518                 if (!cmd.empty()) {
519                         // which lyxfunc should we let it connect to?
520                         // The correct solution would be to have a
521                         // specialized (non-gui) BufferView. But how do
522                         // we do it now? Probably we should just let it
523                         // connect to the lyxfunc in the single LyXView we
524                         // support currently. (Lgb)
525
526                         int action = lyxaction.LookupFunc(cmd);
527                         //int action = -1;
528                         string rval, buf;
529                     
530                         if (action>= 0) {
531                                 rval = serv->func->dispatch(action, arg);
532                         } else {
533                                 rval = "Unknown command";
534                         }
535
536       //modified june 1999 stefano@zool.su.se:
537                         //all commands produce an INFO or ERROR message
538                         //in the output pipe, even if they do not return
539                         //anything. See chapter 4 of Customization doc.
540                         if (action<0 || serv->func->errorStat())
541                                 buf = "ERROR:";
542                         else
543                                 buf = "INFO:";
544                         buf += string(client) + ":" + cmd       + ":" + rval + "\n";
545                         serv->pipes.send(buf);
546
547                         // !!! we don't do any error checking -
548                         //  if the client won't listen, the
549                         //  message is lost and others too
550                         //  maybe; so the client should empty
551                         //  the outpipe before issuing a request.
552                 
553                         // not found
554                 }
555         }  /* while *p */
556 }
557
558
559 /* ---F+------------------------------------------------------------------ *\
560    Function  : LyxNotifyClient
561    Called by : WorkAreaKeyPress
562    Purpose   : send a notify messge to a client
563    Parameters: s - string to send
564    Returns   : nothing
565    \* ---F------------------------------------------------------------------- */
566
567 void LyXServer::notifyClient(string const & s)
568 {
569         string buf = string("NOTIFY:") + s + "\n";
570         pipes.send(buf);
571 }
572