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