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