]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
typos
[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
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         if (lyxerr.debugging(Debug::LYXSERVER)) {
304                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
305         }
306
307         if (pipename.empty()) return;
308
309         if (!ready) {
310                 lyxerr << "LyXComm: Pipes are closed. Could not send "
311                        << msg << endl;
312         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
313                 lyxerr << "LyXComm: Error sending message: " << msg
314                        << '\n' << strerror(errno)
315                        << "\nLyXComm: Resetting connection" << endl;
316                 closeConnection();
317                 openConnection();
318         }
319 }
320
321 #endif // defined (HAVE_MKFIFO)
322
323
324 string const LyXComm::inPipeName() const
325 {
326         return pipename + string(".in");
327 }
328
329
330 string const LyXComm::outPipeName() const
331 {
332         return pipename + string(".out");
333 }
334
335
336 // LyXServer class
337
338 LyXServer::~LyXServer()
339 {
340         // say goodbye to clients so they stop sending messages
341         // modified june 1999 by stefano@zool.su.se to send as many bye
342         // messages as there are clients, each with client's name.
343         string message;
344         for (int i= 0; i<numclients; ++i) {
345                 message = "LYXSRV:" + clients[i] + ":bye\n";
346                 pipes.send(message);
347         }
348 }
349
350
351 /* ---F+------------------------------------------------------------------ *\
352    Function  : ServerCallback
353     Called by : LyXComm
354     Purpose   : handle data gotten from communication
355 \* ---F------------------------------------------------------------------- */
356
357 void LyXServer::callback(LyXServer * serv, string const & msg)
358 {
359         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
360                                  << msg << '\'' << endl;
361
362         char const * p = msg.c_str();
363
364         // --- parse the string --------------------------------------------
365         //
366         //  Format: LYXCMD:<client>:<func>:<argstring>\n
367         //
368         bool server_only = false;
369         while (*p) {
370                 // --- 1. check 'header' ---
371
372                 if (compare(p, "LYXSRV:", 7) == 0) {
373                         server_only = true;
374                 } else if (0 != compare(p, "LYXCMD:", 7)) {
375                         lyxerr << "LyXServer: Unknown request \""
376                                << p << '"' << endl;
377                         return;
378                 }
379                 p += 7;
380
381                 // --- 2. for the moment ignore the client name ---
382                 string client;
383                 while (*p && *p != ':')
384                         client += char(*p++);
385                 if (*p == ':') ++p;
386                 if (!*p) return;
387
388                 // --- 3. get function name ---
389                 string cmd;
390                 while (*p && *p != ':')
391                         cmd += char(*p++);
392
393                 // --- 4. parse the argument ---
394                 string arg;
395                 if (!server_only && *p == ':' && *(++p)) {
396                         while (*p && *p != '\n')
397                                 arg += char(*p++);
398                         if (*p) ++p;
399                 }
400
401                 lyxerr[Debug::LYXSERVER]
402                         << "LyXServer: Client: '" << client
403                         << "' Command: '" << cmd
404                         << "' Argument: '" << arg << '\'' << endl;
405
406                 // --- lookup and exec the command ------------------
407
408                 if (server_only) {
409                         string buf;
410                         // return the greeting to inform the client that
411                         // we are listening.
412                         if (cmd == "hello") {
413                                 // One more client
414                                 if (serv->numclients == MAX_CLIENTS) { //paranoid check
415                                         lyxerr[Debug::LYXSERVER]
416                                                 << "LyXServer: too many clients..."
417                                                 << endl;
418                                         return;
419                                 }
420                                 int i= 0; //find place in clients[]
421                                 while (!serv->clients[i].empty()
422                                        && i<serv->numclients)
423                                         ++i;
424                                 serv->clients[i] = client;
425                                 serv->numclients++;
426                                 buf = "LYXSRV:" + client + ":hello\n";
427                                 lyxerr[Debug::LYXSERVER]
428                                         << "LyXServer: Greeting "
429                                         << client << endl;
430                                 serv->pipes.send(buf);
431                         } else if (cmd == "bye") {
432                                 // If clients == 0 maybe we should reset the pipes
433                                 // to prevent fake callbacks
434                                 int i = 0; //look if client is registered
435                                 for (; i < serv->numclients; ++i) {
436                                         if (serv->clients[i] == client) break;
437                                 }
438                                 if (i < serv->numclients) {
439                                         serv->numclients--;
440                                         serv->clients[i].erase();
441                                         lyxerr[Debug::LYXSERVER]
442                                                 << "LyXServer: Client "
443                                                 << client << " said goodbye"
444                                                 << endl;
445                                 } else {
446                                         lyxerr[Debug::LYXSERVER]
447                                                 << "LyXServer: ignoring bye messge from unregistered client"
448                                                 << client << endl;
449                                 }
450                         } else {
451                                 lyxerr <<"LyXServer: Undefined server command "
452                                        << cmd << '.' << endl;
453                         }
454                         return;
455                 }
456
457                 if (!cmd.empty()) {
458                         // which lyxfunc should we let it connect to?
459                         // The correct solution would be to have a
460                         // specialized (non-gui) BufferView. But how do
461                         // we do it now? Probably we should just let it
462                         // connect to the lyxfunc in the single LyXView we
463                         // support currently. (Lgb)
464
465
466                         serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
467                         string const rval = to_utf8(serv->func->getMessage());
468
469                         //modified june 1999 stefano@zool.su.se:
470                         //all commands produce an INFO or ERROR message
471                         //in the output pipe, even if they do not return
472                         //anything. See chapter 4 of Customization doc.
473                         string buf;
474                         if (serv->func->errorStat())
475                                 buf = "ERROR:";
476                         else
477                                 buf = "INFO:";
478                         buf += client + ':' + cmd + ':' +  rval + '\n';
479                         serv->pipes.send(buf);
480
481                         // !!! we don't do any error checking -
482                         //  if the client won't listen, the
483                         //  message is lost and others too
484                         //  maybe; so the client should empty
485                         //  the outpipe before issuing a request.
486
487                         // not found
488                 }
489         }  /* while *p */
490 }
491
492
493 /* ---F+------------------------------------------------------------------ *\
494    Function  : LyXNotifyClient
495    Called by : WorkAreaKeyPress
496    Purpose   : send a notify messge to a client
497    Parameters: s - string to send
498    Returns   : nothing
499    \* ---F------------------------------------------------------------------- */
500
501 void LyXServer::notifyClient(string const & s)
502 {
503         string buf = string("NOTIFY:") + s + "\n";
504         pipes.send(buf);
505 }
506
507
508 } // namespace lyx