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