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