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