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