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.
186 int fd = ::open(filename.toFilesystemEncoding().c_str(),
187 O_WRONLY | O_NONBLOCK);
189 // Another LyX instance is using it.
191 } else if (errno == ENXIO) {
192 // No process is reading from the other end.
194 LYXERR(Debug::LYXSERVER,
195 "LyXComm: trying to remove "
197 filename.removeFile();
199 } else if (stalepipe) {
200 LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
202 filename.removeFile();
205 if (filename.exists()) {
206 lyxerr << "LyXComm: Pipe " << filename
207 << " already exists.\nIf no other LyX program"
208 " is active, please delete the pipe by hand"
216 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
217 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
218 << strerror(errno) << endl;
221 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
222 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
225 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
226 << strerror(errno) << endl;
227 filename.removeFile();
232 theApp()->registerSocketCallback(fd,
233 boost::bind(&LyXComm::read_ready, this));
240 void LyXComm::endPipe(int & fd, string const & filename, bool write)
246 theApp()->unregisterSocketCallback(fd);
248 if (::close(fd) < 0) {
249 lyxerr << "LyXComm: Could not close pipe " << filename
250 << '\n' << strerror(errno) << endl;
253 if (FileName(filename).removeFile() < 0) {
254 lyxerr << "LyXComm: Could not remove pipe " << filename
255 << '\n' << strerror(errno) << endl;
262 void LyXComm::emergencyCleanup()
264 if (!pipename_.empty()) {
265 endPipe(infd_, inPipeName(), false);
266 endPipe(outfd_, outPipeName(), true);
271 // Receives messages and sends then to client
272 void LyXComm::read_ready()
274 // FIXME: make read_buffer_ a class-member for multiple sessions
275 static string read_buffer_;
276 read_buffer_.erase();
278 int const charbuf_size = 100;
279 char charbuf[charbuf_size];
281 // As O_NONBLOCK is set, until no data is available for reading,
282 // read() doesn't block but returns -1 and set errno to EAGAIN.
283 // After a client that opened the pipe for writing, closes it
284 // (and no other client is using the pipe), read() would always
285 // return 0 and thus the connection has to be reset.
289 // the single = is intended here.
290 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
293 charbuf[status] = '\0'; // turn it into a c string
294 read_buffer_ += rtrim(charbuf, "\r");
295 // commit any commands read
296 while (read_buffer_.find('\n') != string::npos) {
297 // split() grabs the entire string if
298 // the delim /wasn't/ found. ?:-P
300 read_buffer_= split(read_buffer_, cmd,'\n');
301 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
302 << ", read_buffer_:" << read_buffer_
305 clientcb_(client_, cmd);
309 if (errno == EAGAIN) {
310 // Nothing to read, continue
314 // An error occurred, better bailing out
315 LYXERR0("LyXComm: " << strerror(errno));
316 if (!read_buffer_.empty()) {
317 LYXERR0("LyXComm: truncated command: " << read_buffer_);
318 read_buffer_.erase();
320 break; // reset connection
324 // The connection gets reset when read() returns 0 (meaning that the
325 // last client closed the pipe) or an error occurred, in which case
326 // read() returns -1 and errno != EAGAIN.
333 void LyXComm::send(string const & msg)
336 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
340 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
342 if (pipename_.empty()) return;
345 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
346 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
347 lyxerr << "LyXComm: Error sending message: " << msg
348 << '\n' << strerror(errno)
349 << "\nLyXComm: Resetting connection" << endl;
355 #endif // defined (HAVE_MKFIFO)
358 string const LyXComm::inPipeName() const
360 return pipename_ + ".in";
364 string const LyXComm::outPipeName() const
366 return pipename_ + ".out";
370 /////////////////////////////////////////////////////////////////////
374 /////////////////////////////////////////////////////////////////////
376 void ServerCallback(Server * server, string const & msg)
378 server->callback(msg);
381 Server::Server(LyXFunc * f, string const & pipes)
382 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
388 // say goodbye to clients so they stop sending messages
389 // send as many bye messages as there are clients,
390 // each with client's name.
392 for (int i = 0; i != numclients_; ++i) {
393 message = "LYXSRV:" + clients_[i] + ":bye\n";
394 pipes_.send(message);
399 int compare(char const * a, char const * b, unsigned int len)
402 return strncmp(a, b, len);
406 // Handle data gotten from communication, called by LyXComm
407 void Server::callback(string const & msg)
409 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
411 char const * p = msg.c_str();
413 // --- parse the string --------------------------------------------
415 // Format: LYXCMD:<client>:<func>:<argstring>\n
417 bool server_only = false;
419 // --- 1. check 'header' ---
421 if (compare(p, "LYXSRV:", 7) == 0) {
423 } else if (0 != compare(p, "LYXCMD:", 7)) {
424 lyxerr << "Server: Unknown request \""
430 // --- 2. for the moment ignore the client name ---
432 while (*p && *p != ':')
433 client += char(*p++);
439 // --- 3. get function name ---
441 while (*p && *p != ':')
444 // --- 4. parse the argument ---
446 if (!server_only && *p == ':' && *(++p)) {
447 while (*p && *p != '\n')
452 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
453 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
455 // --- lookup and exec the command ------------------
459 // return the greeting to inform the client that
461 if (cmd == "hello") {
463 if (numclients_ == MAX_CLIENTS) { //paranoid check
464 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
468 while (!clients_[i].empty() && i < numclients_)
470 clients_[i] = client;
472 buf = "LYXSRV:" + client + ":hello\n";
473 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
475 } else if (cmd == "bye") {
476 // If clients_ == 0 maybe we should reset the pipes
477 // to prevent fake callbacks
478 int i = 0; //look if client is registered
479 for (; i < numclients_; ++i) {
480 if (clients_[i] == client)
483 if (i < numclients_) {
486 LYXERR(Debug::LYXSERVER, "Server: Client "
487 << client << " said goodbye");
489 LYXERR(Debug::LYXSERVER,
490 "Server: ignoring bye messge from unregistered client" << client);
493 LYXERR0("Server: Undefined server command " << cmd << '.');
499 // which lyxfunc should we let it connect to?
500 // The correct solution would be to have a
501 // specialized (non-gui) BufferView. But how do
502 // we do it now? Probably we should just let it
503 // connect to the lyxfunc in the single LyXView we
504 // support currently. (Lgb)
506 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
507 string const rval = to_utf8(func_->getMessage());
509 // all commands produce an INFO or ERROR message
510 // in the output pipe, even if they do not return
511 // anything. See chapter 4 of Customization doc.
513 if (func_->errorStat())
517 buf += client + ':' + cmd + ':' + rval + '\n';
520 // !!! we don't do any error checking -
521 // if the client won't listen, the
522 // message is lost and others too
523 // maybe; so the client should empty
524 // the outpipe before issuing a request.
532 // Send a notify message to a client, called by WorkAreaKeyPress
533 void Server::notifyClient(string const & s)
535 pipes_.send("NOTIFY:" + s + "\n");