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