]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
This commit is purely mechanical and get rid of lyx_gui.[Ch].
[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 "frontends/Application.h"
48 #include "support/lstrings.h"
49 #include "support/lyxlib.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                 theApp->registerSocketCallback(fd,
193                         boost::bind(&LyXComm::read_ready, this));
194         }
195
196         return fd;
197 }
198
199
200 void LyXComm::endPipe(int & fd, string const & filename, bool write)
201 {
202         if (fd < 0)
203                 return;
204
205         if (!write) {
206                 theApp->unregisterSocketCallback(fd);
207         }
208
209         if (::close(fd) < 0) {
210                 lyxerr << "LyXComm: Could not close pipe " << filename
211                        << '\n' << strerror(errno) << endl;
212         }
213
214         if (unlink(filename) < 0) {
215                 lyxerr << "LyXComm: Could not remove pipe " << filename
216                        << '\n' << strerror(errno) << endl;
217         };
218
219         fd = -1;
220 }
221
222
223 void LyXComm::emergencyCleanup()
224 {
225         if (!pipename.empty()) {
226                 endPipe(infd, inPipeName(), false);
227                 endPipe(outfd, outPipeName(), true);
228         }
229 }
230
231
232 // Receives messages and sends then to client
233 void LyXComm::read_ready()
234 {
235         // nb! make read_buffer_ a class-member for multiple sessions
236         static string read_buffer_;
237         read_buffer_.erase();
238
239         int const charbuf_size = 100;
240         char charbuf[charbuf_size];
241
242         errno = 0;
243         int status;
244         // the single = is intended here.
245         while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
246
247                 if (status > 0) {
248                         charbuf[status] = '\0'; // turn it into a c string
249                         read_buffer_ += rtrim(charbuf, "\r");
250                         // commit any commands read
251                         while (read_buffer_.find('\n') != string::npos) {
252                                 // split() grabs the entire string if
253                                 // the delim /wasn't/ found. ?:-P
254                                 string cmd;
255                                 read_buffer_= split(read_buffer_, cmd,'\n');
256                                 lyxerr[Debug::LYXSERVER]
257                                         << "LyXComm: status:" << status
258                                         << ", read_buffer_:" << read_buffer_
259                                         << ", cmd:" << cmd << endl;
260                                 if (!cmd.empty())
261                                         clientcb(client, cmd);
262                                         //\n or not \n?
263                         }
264                 }
265                 if (errno == EAGAIN) {
266                         errno = 0;
267                         return;
268                 }
269                 if (errno != 0) {
270                         lyxerr << "LyXComm: " << strerror(errno) << endl;
271                         if (!read_buffer_.empty()) {
272                                 lyxerr << "LyXComm: truncated command: "
273                                        << read_buffer_ << endl;
274                                 read_buffer_.erase();
275                         }
276                         break; // reset connection
277                 }
278         }
279
280         // The connection gets reset in errno != EAGAIN
281         // Why does it need to be reset if errno == 0?
282         closeConnection();
283         openConnection();
284         errno = 0;
285 }
286
287
288 void LyXComm::send(string const & msg)
289 {
290         if (msg.empty()) {
291                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
292                        << endl;
293                 return;
294         }
295
296         if (lyxerr.debugging(Debug::LYXSERVER)) {
297                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
298         }
299
300         if (pipename.empty()) return;
301
302         if (!ready) {
303                 lyxerr << "LyXComm: Pipes are closed. Could not send "
304                        << msg << endl;
305         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
306                 lyxerr << "LyXComm: Error sending message: " << msg
307                        << '\n' << strerror(errno)
308                        << "\nLyXComm: Resetting connection" << endl;
309                 closeConnection();
310                 openConnection();
311         }
312 }
313
314 #endif // defined (HAVE_MKFIFO)
315
316
317 string const LyXComm::inPipeName() const
318 {
319         return pipename + string(".in");
320 }
321
322
323 string const LyXComm::outPipeName() const
324 {
325         return pipename + string(".out");
326 }
327
328
329 // LyXServer class
330
331 LyXServer::~LyXServer()
332 {
333         // say goodbye to clients so they stop sending messages
334         // modified june 1999 by stefano@zool.su.se to send as many bye
335         // messages as there are clients, each with client's name.
336         string message;
337         for (int i= 0; i<numclients; ++i) {
338                 message = "LYXSRV:" + clients[i] + ":bye\n";
339                 pipes.send(message);
340         }
341 }
342
343
344 /* ---F+------------------------------------------------------------------ *\
345    Function  : ServerCallback
346     Called by : LyXComm
347     Purpose   : handle data gotten from communication
348 \* ---F------------------------------------------------------------------- */
349
350 void LyXServer::callback(LyXServer * serv, string const & msg)
351 {
352         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
353                                  << msg << '\'' << endl;
354
355         char const * p = msg.c_str();
356
357         // --- parse the string --------------------------------------------
358         //
359         //  Format: LYXCMD:<client>:<func>:<argstring>\n
360         //
361         bool server_only = false;
362         while (*p) {
363                 // --- 1. check 'header' ---
364
365                 if (compare(p, "LYXSRV:", 7) == 0) {
366                         server_only = true;
367                 } else if (0 != compare(p, "LYXCMD:", 7)) {
368                         lyxerr << "LyXServer: Unknown request \""
369                                << p << '"' << endl;
370                         return;
371                 }
372                 p += 7;
373
374                 // --- 2. for the moment ignore the client name ---
375                 string client;
376                 while (*p && *p != ':')
377                         client += char(*p++);
378                 if (*p == ':') ++p;
379                 if (!*p) return;
380
381                 // --- 3. get function name ---
382                 string cmd;
383                 while (*p && *p != ':')
384                         cmd += char(*p++);
385
386                 // --- 4. parse the argument ---
387                 string arg;
388                 if (!server_only && *p == ':' && *(++p)) {
389                         while (*p && *p != '\n')
390                                 arg += char(*p++);
391                         if (*p) ++p;
392                 }
393
394                 lyxerr[Debug::LYXSERVER]
395                         << "LyXServer: Client: '" << client
396                         << "' Command: '" << cmd
397                         << "' Argument: '" << arg << '\'' << endl;
398
399                 // --- lookup and exec the command ------------------
400
401                 if (server_only) {
402                         string buf;
403                         // return the greeting to inform the client that
404                         // we are listening.
405                         if (cmd == "hello") {
406                                 // One more client
407                                 if (serv->numclients == MAX_CLIENTS) { //paranoid check
408                                         lyxerr[Debug::LYXSERVER]
409                                                 << "LyXServer: too many clients..."
410                                                 << endl;
411                                         return;
412                                 }
413                                 int i= 0; //find place in clients[]
414                                 while (!serv->clients[i].empty()
415                                        && i<serv->numclients)
416                                         ++i;
417                                 serv->clients[i] = client;
418                                 serv->numclients++;
419                                 buf = "LYXSRV:" + client + ":hello\n";
420                                 lyxerr[Debug::LYXSERVER]
421                                         << "LyXServer: Greeting "
422                                         << client << endl;
423                                 serv->pipes.send(buf);
424                         } else if (cmd == "bye") {
425                                 // If clients == 0 maybe we should reset the pipes
426                                 // to prevent fake callbacks
427                                 int i = 0; //look if client is registered
428                                 for (; i < serv->numclients; ++i) {
429                                         if (serv->clients[i] == client) break;
430                                 }
431                                 if (i < serv->numclients) {
432                                         serv->numclients--;
433                                         serv->clients[i].erase();
434                                         lyxerr[Debug::LYXSERVER]
435                                                 << "LyXServer: Client "
436                                                 << client << " said goodbye"
437                                                 << endl;
438                                 } else {
439                                         lyxerr[Debug::LYXSERVER]
440                                                 << "LyXServer: ignoring bye messge from unregistered client"
441                                                 << client << endl;
442                                 }
443                         } else {
444                                 lyxerr <<"LyXServer: Undefined server command "
445                                        << cmd << '.' << endl;
446                         }
447                         return;
448                 }
449
450                 if (!cmd.empty()) {
451                         // which lyxfunc should we let it connect to?
452                         // The correct solution would be to have a
453                         // specialized (non-gui) BufferView. But how do
454                         // we do it now? Probably we should just let it
455                         // connect to the lyxfunc in the single LyXView we
456                         // support currently. (Lgb)
457
458
459                         serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
460                         string const rval = lyx::to_utf8(serv->func->getMessage());
461
462                         //modified june 1999 stefano@zool.su.se:
463                         //all commands produce an INFO or ERROR message
464                         //in the output pipe, even if they do not return
465                         //anything. See chapter 4 of Customization doc.
466                         string buf;
467                         if (serv->func->errorStat())
468                                 buf = "ERROR:";
469                         else
470                                 buf = "INFO:";
471                         buf += client + ':' + cmd + ':' +  rval + '\n';
472                         serv->pipes.send(buf);
473
474                         // !!! we don't do any error checking -
475                         //  if the client won't listen, the
476                         //  message is lost and others too
477                         //  maybe; so the client should empty
478                         //  the outpipe before issuing a request.
479
480                         // not found
481                 }
482         }  /* while *p */
483 }
484
485
486 /* ---F+------------------------------------------------------------------ *\
487    Function  : LyXNotifyClient
488    Called by : WorkAreaKeyPress
489    Purpose   : send a notify messge to a client
490    Parameters: s - string to send
491    Returns   : nothing
492    \* ---F------------------------------------------------------------------- */
493
494 void LyXServer::notifyClient(string const & s)
495 {
496         string buf = string("NOTIFY:") + s + "\n";
497         pipes.send(buf);
498 }