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