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