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