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