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
44 #include "FuncRequest.h"
45 #include "LyXAction.h"
47 #include "frontends/Application.h"
49 #include "support/FileName.h"
50 #include "support/lstrings.h"
51 #include "support/lyxlib.h"
53 #include <boost/bind.hpp>
56 #ifdef HAVE_SYS_STAT_H
57 # include <sys/stat.h>
64 using support::compare;
65 using support::FileName;
68 using support::unlink;
74 /////////////////////////////////////////////////////////////////////
78 /////////////////////////////////////////////////////////////////////
80 #if !defined (HAVE_MKFIFO)
81 // We provide a stub class that disables the lyxserver.
83 LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
86 void LyXComm::openConnection()
90 void LyXComm::closeConnection()
94 int LyXComm::startPipe(string const & filename, bool write)
100 void LyXComm::endPipe(int & fd, string const & filename, bool write)
104 void LyXComm::emergencyCleanup()
107 void LyXComm::read_ready()
111 void LyXComm::send(string const & msg)
115 #else // defined (HAVE_MKFIFO)
118 LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
119 : pipename_(pip), client_(cli), clientcb_(ccb)
126 void LyXComm::openConnection()
128 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
130 // If we are up, that's an error
132 lyxerr << "LyXComm: Already connected" << endl;
135 // We assume that we don't make it
138 if (pipename_.empty()) {
139 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
143 infd_ = startPipe(inPipeName(), false);
147 outfd_ = startPipe(outPipeName(), true);
149 endPipe(infd_, inPipeName(), false);
153 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
154 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
155 << '\n' << strerror(errno) << endl;
161 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
166 void LyXComm::closeConnection()
168 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
170 if (pipename_.empty()) {
171 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
176 lyxerr << "LyXComm: Already disconnected" << endl;
180 endPipe(infd_, inPipeName(), false);
181 endPipe(outfd_, outPipeName(), true);
187 int LyXComm::startPipe(string const & file, bool write)
189 FileName const filename(file);
190 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
191 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
192 << "If no other LyX program is active, please delete"
193 " the pipe by hand and try again." << endl;
198 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
199 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
200 << strerror(errno) << endl;
203 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
204 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
207 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
208 << strerror(errno) << endl;
214 theApp()->registerSocketCallback(fd,
215 boost::bind(&LyXComm::read_ready, this));
222 void LyXComm::endPipe(int & fd, string const & filename, bool write)
228 theApp()->unregisterSocketCallback(fd);
230 if (::close(fd) < 0) {
231 lyxerr << "LyXComm: Could not close pipe " << filename
232 << '\n' << strerror(errno) << endl;
235 if (unlink(FileName(filename)) < 0) {
236 lyxerr << "LyXComm: Could not remove pipe " << filename
237 << '\n' << strerror(errno) << endl;
244 void LyXComm::emergencyCleanup()
246 if (!pipename_.empty()) {
247 endPipe(infd_, inPipeName(), false);
248 endPipe(outfd_, outPipeName(), true);
253 // Receives messages and sends then to client
254 void LyXComm::read_ready()
256 // FIXME: make read_buffer_ a class-member for multiple sessions
257 static string read_buffer_;
258 read_buffer_.erase();
260 int const charbuf_size = 100;
261 char charbuf[charbuf_size];
265 // the single = is intended here.
266 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
269 charbuf[status] = '\0'; // turn it into a c string
270 read_buffer_ += rtrim(charbuf, "\r");
271 // commit any commands read
272 while (read_buffer_.find('\n') != string::npos) {
273 // split() grabs the entire string if
274 // the delim /wasn't/ found. ?:-P
276 read_buffer_= split(read_buffer_, cmd,'\n');
277 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
278 << ", read_buffer_:" << read_buffer_
281 clientcb_(client_, cmd);
285 if (errno == EAGAIN) {
290 lyxerr << "LyXComm: " << strerror(errno) << endl;
291 if (!read_buffer_.empty()) {
292 lyxerr << "LyXComm: truncated command: "
293 << read_buffer_ << endl;
294 read_buffer_.erase();
296 break; // reset connection
300 // The connection gets reset in errno != EAGAIN
301 // Why does it need to be reset if errno == 0?
308 void LyXComm::send(string const & msg)
311 lyxerr << "LyXComm: Request to send empty string. Ignoring."
316 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
318 if (pipename_.empty()) return;
321 lyxerr << "LyXComm: Pipes are closed. Could not send "
323 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
324 lyxerr << "LyXComm: Error sending message: " << msg
325 << '\n' << strerror(errno)
326 << "\nLyXComm: Resetting connection" << endl;
332 #endif // defined (HAVE_MKFIFO)
335 string const LyXComm::inPipeName() const
337 return pipename_ + ".in";
341 string const LyXComm::outPipeName() const
343 return pipename_ + ".out";
347 /////////////////////////////////////////////////////////////////////
351 /////////////////////////////////////////////////////////////////////
353 void ServerCallback(Server * server, string const & msg)
355 server->callback(msg);
358 Server::Server(LyXFunc * f, std::string const & pipes)
359 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
365 // say goodbye to clients so they stop sending messages
366 // send as many bye messages as there are clients,
367 // each with client's name.
369 for (int i = 0; i != numclients_; ++i) {
370 message = "LYXSRV:" + clients_[i] + ":bye\n";
371 pipes_.send(message);
376 // Handle data gotten from communication, called by LyXComm
377 void Server::callback(string const & msg)
379 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
381 char const * p = msg.c_str();
383 // --- parse the string --------------------------------------------
385 // Format: LYXCMD:<client>:<func>:<argstring>\n
387 bool server_only = false;
389 // --- 1. check 'header' ---
391 if (compare(p, "LYXSRV:", 7) == 0) {
393 } else if (0 != compare(p, "LYXCMD:", 7)) {
394 lyxerr << "Server: Unknown request \""
400 // --- 2. for the moment ignore the client name ---
402 while (*p && *p != ':')
403 client += char(*p++);
409 // --- 3. get function name ---
411 while (*p && *p != ':')
414 // --- 4. parse the argument ---
416 if (!server_only && *p == ':' && *(++p)) {
417 while (*p && *p != '\n')
422 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
423 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
425 // --- lookup and exec the command ------------------
429 // return the greeting to inform the client that
431 if (cmd == "hello") {
433 if (numclients_ == MAX_CLIENTS) { //paranoid check
434 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
438 while (!clients_[i].empty() && i < numclients_)
440 clients_[i] = client;
442 buf = "LYXSRV:" + client + ":hello\n";
443 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
445 } else if (cmd == "bye") {
446 // If clients_ == 0 maybe we should reset the pipes
447 // to prevent fake callbacks
448 int i = 0; //look if client is registered
449 for (; i < numclients_; ++i) {
450 if (clients_[i] == client)
453 if (i < numclients_) {
456 LYXERR(Debug::LYXSERVER, "Server: Client "
457 << client << " said goodbye");
459 LYXERR(Debug::LYXSERVER,
460 "Server: ignoring bye messge from unregistered client" << client);
463 lyxerr <<"Server: Undefined server command "
464 << cmd << '.' << endl;
470 // which lyxfunc should we let it connect to?
471 // The correct solution would be to have a
472 // specialized (non-gui) BufferView. But how do
473 // we do it now? Probably we should just let it
474 // connect to the lyxfunc in the single LyXView we
475 // support currently. (Lgb)
477 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
478 string const rval = to_utf8(func_->getMessage());
480 // all commands produce an INFO or ERROR message
481 // in the output pipe, even if they do not return
482 // anything. See chapter 4 of Customization doc.
484 if (func_->errorStat())
488 buf += client + ':' + cmd + ':' + rval + '\n';
491 // !!! we don't do any error checking -
492 // if the client won't listen, the
493 // message is lost and others too
494 // maybe; so the client should empty
495 // the outpipe before issuing a request.
503 // Send a notify messge to a client, called by WorkAreaKeyPress
504 void Server::notifyClient(string const & s)
506 pipes_.send("NOTIFY:" + s + "\n");