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