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