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 FileName const filename(file);
182 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
183 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
184 << "If no other LyX program is active, please delete"
185 " the pipe by hand and try again." << endl;
190 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
191 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
192 << strerror(errno) << endl;
195 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
196 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
199 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
200 << strerror(errno) << endl;
201 filename.removeFile();
206 theApp()->registerSocketCallback(fd,
207 boost::bind(&LyXComm::read_ready, this));
214 void LyXComm::endPipe(int & fd, string const & filename, bool write)
220 theApp()->unregisterSocketCallback(fd);
222 if (::close(fd) < 0) {
223 lyxerr << "LyXComm: Could not close pipe " << filename
224 << '\n' << strerror(errno) << endl;
227 if (FileName(filename).removeFile() < 0) {
228 lyxerr << "LyXComm: Could not remove pipe " << filename
229 << '\n' << strerror(errno) << endl;
236 void LyXComm::emergencyCleanup()
238 if (!pipename_.empty()) {
239 endPipe(infd_, inPipeName(), false);
240 endPipe(outfd_, outPipeName(), true);
245 // Receives messages and sends then to client
246 void LyXComm::read_ready()
248 // FIXME: make read_buffer_ a class-member for multiple sessions
249 static string read_buffer_;
250 read_buffer_.erase();
252 int const charbuf_size = 100;
253 char charbuf[charbuf_size];
257 // the single = is intended here.
258 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
261 charbuf[status] = '\0'; // turn it into a c string
262 read_buffer_ += rtrim(charbuf, "\r");
263 // commit any commands read
264 while (read_buffer_.find('\n') != string::npos) {
265 // split() grabs the entire string if
266 // the delim /wasn't/ found. ?:-P
268 read_buffer_= split(read_buffer_, cmd,'\n');
269 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
270 << ", read_buffer_:" << read_buffer_
273 clientcb_(client_, cmd);
277 if (errno == EAGAIN) {
282 LYXERR0("LyXComm: " << strerror(errno));
283 if (!read_buffer_.empty()) {
284 LYXERR0("LyXComm: truncated command: " << read_buffer_);
285 read_buffer_.erase();
287 break; // reset connection
291 // The connection gets reset in errno != EAGAIN
292 // Why does it need to be reset if errno == 0?
299 void LyXComm::send(string const & msg)
302 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
306 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
308 if (pipename_.empty()) return;
311 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
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;
321 #endif // defined (HAVE_MKFIFO)
324 string const LyXComm::inPipeName() const
326 return pipename_ + ".in";
330 string const LyXComm::outPipeName() const
332 return pipename_ + ".out";
336 /////////////////////////////////////////////////////////////////////
340 /////////////////////////////////////////////////////////////////////
342 void ServerCallback(Server * server, string const & msg)
344 server->callback(msg);
347 Server::Server(LyXFunc * f, string const & pipes)
348 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
354 // say goodbye to clients so they stop sending messages
355 // send as many bye messages as there are clients,
356 // each with client's name.
358 for (int i = 0; i != numclients_; ++i) {
359 message = "LYXSRV:" + clients_[i] + ":bye\n";
360 pipes_.send(message);
365 int compare(char const * a, char const * b, unsigned int len)
368 return strncmp(a, b, len);
372 // Handle data gotten from communication, called by LyXComm
373 void Server::callback(string const & msg)
375 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
377 char const * p = msg.c_str();
379 // --- parse the string --------------------------------------------
381 // Format: LYXCMD:<client>:<func>:<argstring>\n
383 bool server_only = false;
385 // --- 1. check 'header' ---
387 if (compare(p, "LYXSRV:", 7) == 0) {
389 } else if (0 != compare(p, "LYXCMD:", 7)) {
390 lyxerr << "Server: Unknown request \""
396 // --- 2. for the moment ignore the client name ---
398 while (*p && *p != ':')
399 client += char(*p++);
405 // --- 3. get function name ---
407 while (*p && *p != ':')
410 // --- 4. parse the argument ---
412 if (!server_only && *p == ':' && *(++p)) {
413 while (*p && *p != '\n')
418 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
419 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
421 // --- lookup and exec the command ------------------
425 // return the greeting to inform the client that
427 if (cmd == "hello") {
429 if (numclients_ == MAX_CLIENTS) { //paranoid check
430 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
434 while (!clients_[i].empty() && i < numclients_)
436 clients_[i] = client;
438 buf = "LYXSRV:" + client + ":hello\n";
439 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
441 } else if (cmd == "bye") {
442 // If clients_ == 0 maybe we should reset the pipes
443 // to prevent fake callbacks
444 int i = 0; //look if client is registered
445 for (; i < numclients_; ++i) {
446 if (clients_[i] == client)
449 if (i < numclients_) {
452 LYXERR(Debug::LYXSERVER, "Server: Client "
453 << client << " said goodbye");
455 LYXERR(Debug::LYXSERVER,
456 "Server: ignoring bye messge from unregistered client" << client);
459 LYXERR0("Server: Undefined server command " << cmd << '.');
465 // which lyxfunc should we let it connect to?
466 // The correct solution would be to have a
467 // specialized (non-gui) BufferView. But how do
468 // we do it now? Probably we should just let it
469 // connect to the lyxfunc in the single LyXView we
470 // support currently. (Lgb)
472 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
473 string const rval = to_utf8(func_->getMessage());
475 // all commands produce an INFO or ERROR message
476 // in the output pipe, even if they do not return
477 // anything. See chapter 4 of Customization doc.
479 if (func_->errorStat())
483 buf += client + ':' + cmd + ':' + rval + '\n';
486 // !!! we don't do any error checking -
487 // if the client won't listen, the
488 // message is lost and others too
489 // maybe; so the client should empty
490 // the outpipe before issuing a request.
498 // Send a notify message to a client, called by WorkAreaKeyPress
499 void Server::notifyClient(string const & s)
501 pipes_.send("NOTIFY:" + s + "\n");