3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jean-Marc Lasgouttes
8 * \author Angus Leeming
11 * Full author contact details are available in file CREDITS.
15 Docu : To use the lyxserver define the name of the pipe in your
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.
36 See development/server_monitor.c for an example client.
37 Purpose: implement a client/server lib for LyX
43 #include "FuncRequest.h"
44 #include "LyXAction.h"
47 #include "frontends/Application.h"
49 #include "support/debug.h"
50 #include "support/FileName.h"
51 #include "support/lstrings.h"
53 #include <boost/bind.hpp>
56 #ifdef HAVE_SYS_STAT_H
57 # include <sys/stat.h>
62 using namespace lyx::support;
66 /////////////////////////////////////////////////////////////////////
70 /////////////////////////////////////////////////////////////////////
72 #if !defined (HAVE_MKFIFO)
73 // We provide a stub class that disables the lyxserver.
75 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
78 void LyXComm::openConnection()
82 void LyXComm::closeConnection()
86 int LyXComm::startPipe(string const & filename, bool write)
92 void LyXComm::endPipe(int & fd, string const & filename, bool write)
96 void LyXComm::emergencyCleanup()
99 void LyXComm::read_ready()
103 void LyXComm::send(string const & msg)
107 #else // defined (HAVE_MKFIFO)
110 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
111 : pipename_(pip), client_(cli), clientcb_(ccb)
118 void LyXComm::openConnection()
120 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
122 // If we are up, that's an error
124 lyxerr << "LyXComm: Already connected" << endl;
127 // We assume that we don't make it
130 if (pipename_.empty()) {
131 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
135 infd_ = startPipe(inPipeName(), false);
139 outfd_ = startPipe(outPipeName(), true);
141 endPipe(infd_, inPipeName(), false);
145 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
146 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
147 << '\n' << strerror(errno) << endl;
153 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
158 void LyXComm::closeConnection()
160 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
162 if (pipename_.empty()) {
163 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
168 LYXERR0("LyXComm: Already disconnected");
172 endPipe(infd_, inPipeName(), false);
173 endPipe(outfd_, outPipeName(), true);
179 int LyXComm::startPipe(string const & file, bool write)
181 static bool stalepipe = false;
182 FileName const filename(file);
183 if (filename.exists()) {
185 // Let's see whether we have a stale pipe.
187 int fd = ::open(filename.toFilesystemEncoding().c_str(),
188 O_WRONLY | O_NONBLOCK);
190 // Another LyX instance is using it.
192 } else if (errno == ENXIO) {
193 // No process is reading from the other end.
195 LYXERR(Debug::LYXSERVER,
196 "LyXComm: trying to remove "
198 filename.removeFile();
200 } else if (stalepipe) {
201 LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
203 filename.removeFile();
206 if (filename.exists()) {
207 lyxerr << "LyXComm: Pipe " << filename
208 << " already exists.\nIf no other LyX program"
209 " is active, please delete the pipe by hand"
217 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
218 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
219 << strerror(errno) << endl;
222 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
223 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
226 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
227 << strerror(errno) << endl;
228 filename.removeFile();
233 theApp()->registerSocketCallback(fd,
234 boost::bind(&LyXComm::read_ready, this));
241 void LyXComm::endPipe(int & fd, string const & filename, bool write)
247 theApp()->unregisterSocketCallback(fd);
249 if (::close(fd) < 0) {
250 lyxerr << "LyXComm: Could not close pipe " << filename
251 << '\n' << strerror(errno) << endl;
254 if (FileName(filename).removeFile() < 0) {
255 lyxerr << "LyXComm: Could not remove pipe " << filename
256 << '\n' << strerror(errno) << endl;
263 void LyXComm::emergencyCleanup()
265 if (!pipename_.empty()) {
266 endPipe(infd_, inPipeName(), false);
267 endPipe(outfd_, outPipeName(), true);
272 // Receives messages and sends then to client
273 void LyXComm::read_ready()
275 // FIXME: make read_buffer_ a class-member for multiple sessions
276 static string read_buffer_;
277 read_buffer_.erase();
279 int const charbuf_size = 100;
280 char charbuf[charbuf_size];
282 // As O_NONBLOCK is set, until no data is available for reading,
283 // read() doesn't block but returns -1 and set errno to EAGAIN.
284 // After a client that opened the pipe for writing, closes it
285 // (and no other client is using the pipe), read() would always
286 // return 0 and thus the connection has to be reset.
290 // the single = is intended here.
291 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
294 charbuf[status] = '\0'; // turn it into a c string
295 read_buffer_ += rtrim(charbuf, "\r");
296 // commit any commands read
297 while (read_buffer_.find('\n') != string::npos) {
298 // split() grabs the entire string if
299 // the delim /wasn't/ found. ?:-P
301 read_buffer_= split(read_buffer_, cmd,'\n');
302 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
303 << ", read_buffer_:" << read_buffer_
306 clientcb_(client_, cmd);
310 if (errno == EAGAIN) {
311 // Nothing to read, continue
315 // An error occurred, better bailing out
316 LYXERR0("LyXComm: " << strerror(errno));
317 if (!read_buffer_.empty()) {
318 LYXERR0("LyXComm: truncated command: " << read_buffer_);
319 read_buffer_.erase();
321 break; // reset connection
325 // The connection gets reset when read() returns 0 (meaning that the
326 // last client closed the pipe) or an error occurred, in which case
327 // read() returns -1 and errno != EAGAIN.
334 void LyXComm::send(string const & msg)
337 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
341 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
343 if (pipename_.empty()) return;
346 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
347 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
348 lyxerr << "LyXComm: Error sending message: " << msg
349 << '\n' << strerror(errno)
350 << "\nLyXComm: Resetting connection" << endl;
356 #endif // defined (HAVE_MKFIFO)
359 string const LyXComm::inPipeName() const
361 return pipename_ + ".in";
365 string const LyXComm::outPipeName() const
367 return pipename_ + ".out";
371 /////////////////////////////////////////////////////////////////////
375 /////////////////////////////////////////////////////////////////////
377 void ServerCallback(Server * server, string const & msg)
379 server->callback(msg);
382 Server::Server(LyXFunc * f, string const & pipes)
383 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
389 // say goodbye to clients so they stop sending messages
390 // send as many bye messages as there are clients,
391 // each with client's name.
393 for (int i = 0; i != numclients_; ++i) {
394 message = "LYXSRV:" + clients_[i] + ":bye\n";
395 pipes_.send(message);
400 int compare(char const * a, char const * b, unsigned int len)
403 return strncmp(a, b, len);
407 // Handle data gotten from communication, called by LyXComm
408 void Server::callback(string const & msg)
410 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
412 char const * p = msg.c_str();
414 // --- parse the string --------------------------------------------
416 // Format: LYXCMD:<client>:<func>:<argstring>\n
418 bool server_only = false;
420 // --- 1. check 'header' ---
422 if (compare(p, "LYXSRV:", 7) == 0) {
424 } else if (0 != compare(p, "LYXCMD:", 7)) {
425 lyxerr << "Server: Unknown request \""
431 // --- 2. for the moment ignore the client name ---
433 while (*p && *p != ':')
434 client += char(*p++);
440 // --- 3. get function name ---
442 while (*p && *p != ':')
445 // --- 4. parse the argument ---
447 if (!server_only && *p == ':' && *(++p)) {
448 while (*p && *p != '\n')
453 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
454 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
456 // --- lookup and exec the command ------------------
460 // return the greeting to inform the client that
462 if (cmd == "hello") {
464 if (numclients_ == MAX_CLIENTS) { //paranoid check
465 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
469 while (!clients_[i].empty() && i < numclients_)
471 clients_[i] = client;
473 buf = "LYXSRV:" + client + ":hello\n";
474 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
476 } else if (cmd == "bye") {
477 // If clients_ == 0 maybe we should reset the pipes
478 // to prevent fake callbacks
479 int i = 0; //look if client is registered
480 for (; i < numclients_; ++i) {
481 if (clients_[i] == client)
484 if (i < numclients_) {
487 LYXERR(Debug::LYXSERVER, "Server: Client "
488 << client << " said goodbye");
490 LYXERR(Debug::LYXSERVER,
491 "Server: ignoring bye messge from unregistered client" << client);
494 LYXERR0("Server: Undefined server command " << cmd << '.');
500 // which lyxfunc should we let it connect to?
501 // The correct solution would be to have a
502 // specialized (non-gui) BufferView. But how do
503 // we do it now? Probably we should just let it
504 // connect to the lyxfunc in the single LyXView we
505 // support currently. (Lgb)
507 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
508 string const rval = to_utf8(func_->getMessage());
510 // all commands produce an INFO or ERROR message
511 // in the output pipe, even if they do not return
512 // anything. See chapter 4 of Customization doc.
514 if (func_->errorStat())
518 buf += client + ':' + cmd + ':' + rval + '\n';
521 // !!! we don't do any error checking -
522 // if the client won't listen, the
523 // message is lost and others too
524 // maybe; so the client should empty
525 // the outpipe before issuing a request.
533 // Send a notify message to a client, called by WorkAreaKeyPress
534 void Server::notifyClient(string const & s)
536 pipes_.send("NOTIFY:" + s + "\n");