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