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 "support/debug.h"
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>
65 using support::compare;
66 using support::FileName;
71 /////////////////////////////////////////////////////////////////////
75 /////////////////////////////////////////////////////////////////////
77 #if !defined (HAVE_MKFIFO)
78 // We provide a stub class that disables the lyxserver.
80 LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
83 void LyXComm::openConnection()
87 void LyXComm::closeConnection()
91 int LyXComm::startPipe(string const & filename, bool write)
97 void LyXComm::endPipe(int & fd, string const & filename, bool write)
101 void LyXComm::emergencyCleanup()
104 void LyXComm::read_ready()
108 void LyXComm::send(string const & msg)
112 #else // defined (HAVE_MKFIFO)
115 LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
116 : pipename_(pip), client_(cli), clientcb_(ccb)
123 void LyXComm::openConnection()
125 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
127 // If we are up, that's an error
129 lyxerr << "LyXComm: Already connected" << endl;
132 // We assume that we don't make it
135 if (pipename_.empty()) {
136 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
140 infd_ = startPipe(inPipeName(), false);
144 outfd_ = startPipe(outPipeName(), true);
146 endPipe(infd_, inPipeName(), false);
150 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
151 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
152 << '\n' << strerror(errno) << endl;
158 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
163 void LyXComm::closeConnection()
165 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
167 if (pipename_.empty()) {
168 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
173 LYXERR0("LyXComm: Already disconnected");
177 endPipe(infd_, inPipeName(), false);
178 endPipe(outfd_, outPipeName(), true);
184 int LyXComm::startPipe(string const & file, bool write)
186 FileName const filename(file);
187 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
188 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
189 << "If no other LyX program is active, please delete"
190 " the pipe by hand and try again." << endl;
195 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
196 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
197 << strerror(errno) << endl;
200 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
201 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
204 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
205 << strerror(errno) << endl;
206 filename.removeFile();
211 theApp()->registerSocketCallback(fd,
212 boost::bind(&LyXComm::read_ready, this));
219 void LyXComm::endPipe(int & fd, string const & filename, bool write)
225 theApp()->unregisterSocketCallback(fd);
227 if (::close(fd) < 0) {
228 lyxerr << "LyXComm: Could not close pipe " << filename
229 << '\n' << strerror(errno) << endl;
232 if (FileName(filename).removeFile() < 0) {
233 lyxerr << "LyXComm: Could not remove pipe " << filename
234 << '\n' << strerror(errno) << endl;
241 void LyXComm::emergencyCleanup()
243 if (!pipename_.empty()) {
244 endPipe(infd_, inPipeName(), false);
245 endPipe(outfd_, outPipeName(), true);
250 // Receives messages and sends then to client
251 void LyXComm::read_ready()
253 // FIXME: make read_buffer_ a class-member for multiple sessions
254 static string read_buffer_;
255 read_buffer_.erase();
257 int const charbuf_size = 100;
258 char charbuf[charbuf_size];
262 // the single = is intended here.
263 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
266 charbuf[status] = '\0'; // turn it into a c string
267 read_buffer_ += rtrim(charbuf, "\r");
268 // commit any commands read
269 while (read_buffer_.find('\n') != string::npos) {
270 // split() grabs the entire string if
271 // the delim /wasn't/ found. ?:-P
273 read_buffer_= split(read_buffer_, cmd,'\n');
274 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
275 << ", read_buffer_:" << read_buffer_
278 clientcb_(client_, cmd);
282 if (errno == EAGAIN) {
287 LYXERR0("LyXComm: " << strerror(errno));
288 if (!read_buffer_.empty()) {
289 LYXERR0("LyXComm: truncated command: " << read_buffer_);
290 read_buffer_.erase();
292 break; // reset connection
296 // The connection gets reset in errno != EAGAIN
297 // Why does it need to be reset if errno == 0?
304 void LyXComm::send(string const & msg)
307 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
311 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
313 if (pipename_.empty()) return;
316 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
317 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
318 lyxerr << "LyXComm: Error sending message: " << msg
319 << '\n' << strerror(errno)
320 << "\nLyXComm: Resetting connection" << endl;
326 #endif // defined (HAVE_MKFIFO)
329 string const LyXComm::inPipeName() const
331 return pipename_ + ".in";
335 string const LyXComm::outPipeName() const
337 return pipename_ + ".out";
341 /////////////////////////////////////////////////////////////////////
345 /////////////////////////////////////////////////////////////////////
347 void ServerCallback(Server * server, string const & msg)
349 server->callback(msg);
352 Server::Server(LyXFunc * f, std::string const & pipes)
353 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
359 // say goodbye to clients so they stop sending messages
360 // send as many bye messages as there are clients,
361 // each with client's name.
363 for (int i = 0; i != numclients_; ++i) {
364 message = "LYXSRV:" + clients_[i] + ":bye\n";
365 pipes_.send(message);
370 int compare(char const * a, char const * b, unsigned int len)
373 return strncmp(a, b, len);
377 // Handle data gotten from communication, called by LyXComm
378 void Server::callback(string const & msg)
380 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
382 char const * p = msg.c_str();
384 // --- parse the string --------------------------------------------
386 // Format: LYXCMD:<client>:<func>:<argstring>\n
388 bool server_only = false;
390 // --- 1. check 'header' ---
392 if (compare(p, "LYXSRV:", 7) == 0) {
394 } else if (0 != compare(p, "LYXCMD:", 7)) {
395 lyxerr << "Server: Unknown request \""
401 // --- 2. for the moment ignore the client name ---
403 while (*p && *p != ':')
404 client += char(*p++);
410 // --- 3. get function name ---
412 while (*p && *p != ':')
415 // --- 4. parse the argument ---
417 if (!server_only && *p == ':' && *(++p)) {
418 while (*p && *p != '\n')
423 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
424 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
426 // --- lookup and exec the command ------------------
430 // return the greeting to inform the client that
432 if (cmd == "hello") {
434 if (numclients_ == MAX_CLIENTS) { //paranoid check
435 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
439 while (!clients_[i].empty() && i < numclients_)
441 clients_[i] = client;
443 buf = "LYXSRV:" + client + ":hello\n";
444 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
446 } else if (cmd == "bye") {
447 // If clients_ == 0 maybe we should reset the pipes
448 // to prevent fake callbacks
449 int i = 0; //look if client is registered
450 for (; i < numclients_; ++i) {
451 if (clients_[i] == client)
454 if (i < numclients_) {
457 LYXERR(Debug::LYXSERVER, "Server: Client "
458 << client << " said goodbye");
460 LYXERR(Debug::LYXSERVER,
461 "Server: ignoring bye messge from unregistered client" << client);
464 LYXERR0("Server: Undefined server command " << cmd << '.');
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");