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>
64 using support::compare;
65 using support::FileName;
73 /////////////////////////////////////////////////////////////////////
77 /////////////////////////////////////////////////////////////////////
79 #if !defined (HAVE_MKFIFO)
80 // We provide a stub class that disables the lyxserver.
82 LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
85 void LyXComm::openConnection()
89 void LyXComm::closeConnection()
93 int LyXComm::startPipe(string const & filename, bool write)
99 void LyXComm::endPipe(int & fd, string const & filename, bool write)
103 void LyXComm::emergencyCleanup()
106 void LyXComm::read_ready()
110 void LyXComm::send(string const & msg)
114 #else // defined (HAVE_MKFIFO)
117 LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
118 : pipename_(pip), client_(cli), clientcb_(ccb)
125 void LyXComm::openConnection()
127 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
129 // If we are up, that's an error
131 lyxerr << "LyXComm: Already connected" << endl;
134 // We assume that we don't make it
137 if (pipename_.empty()) {
138 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
142 infd_ = startPipe(inPipeName(), false);
146 outfd_ = startPipe(outPipeName(), true);
148 endPipe(infd_, inPipeName(), false);
152 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
153 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
154 << '\n' << strerror(errno) << endl;
160 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
165 void LyXComm::closeConnection()
167 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
169 if (pipename_.empty()) {
170 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
175 LYXERR0("LyXComm: Already disconnected");
179 endPipe(infd_, inPipeName(), false);
180 endPipe(outfd_, outPipeName(), true);
186 int LyXComm::startPipe(string const & file, bool write)
188 FileName const filename(file);
189 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
190 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
191 << "If no other LyX program is active, please delete"
192 " the pipe by hand and try again." << endl;
197 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
198 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
199 << strerror(errno) << endl;
202 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
203 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
206 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
207 << strerror(errno) << endl;
208 filename.removeFile();
213 theApp()->registerSocketCallback(fd,
214 boost::bind(&LyXComm::read_ready, this));
221 void LyXComm::endPipe(int & fd, string const & filename, bool write)
227 theApp()->unregisterSocketCallback(fd);
229 if (::close(fd) < 0) {
230 lyxerr << "LyXComm: Could not close pipe " << filename
231 << '\n' << strerror(errno) << endl;
234 if (FileName(filename).removeFile() < 0) {
235 lyxerr << "LyXComm: Could not remove pipe " << filename
236 << '\n' << strerror(errno) << endl;
243 void LyXComm::emergencyCleanup()
245 if (!pipename_.empty()) {
246 endPipe(infd_, inPipeName(), false);
247 endPipe(outfd_, outPipeName(), true);
252 // Receives messages and sends then to client
253 void LyXComm::read_ready()
255 // FIXME: make read_buffer_ a class-member for multiple sessions
256 static string read_buffer_;
257 read_buffer_.erase();
259 int const charbuf_size = 100;
260 char charbuf[charbuf_size];
264 // the single = is intended here.
265 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
268 charbuf[status] = '\0'; // turn it into a c string
269 read_buffer_ += rtrim(charbuf, "\r");
270 // commit any commands read
271 while (read_buffer_.find('\n') != string::npos) {
272 // split() grabs the entire string if
273 // the delim /wasn't/ found. ?:-P
275 read_buffer_= split(read_buffer_, cmd,'\n');
276 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
277 << ", read_buffer_:" << read_buffer_
280 clientcb_(client_, cmd);
284 if (errno == EAGAIN) {
289 LYXERR0("LyXComm: " << strerror(errno));
290 if (!read_buffer_.empty()) {
291 LYXERR0("LyXComm: truncated command: " << read_buffer_);
292 read_buffer_.erase();
294 break; // reset connection
298 // The connection gets reset in errno != EAGAIN
299 // Why does it need to be reset if errno == 0?
306 void LyXComm::send(string const & msg)
309 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
313 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
315 if (pipename_.empty()) return;
318 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
319 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
320 lyxerr << "LyXComm: Error sending message: " << msg
321 << '\n' << strerror(errno)
322 << "\nLyXComm: Resetting connection" << endl;
328 #endif // defined (HAVE_MKFIFO)
331 string const LyXComm::inPipeName() const
333 return pipename_ + ".in";
337 string const LyXComm::outPipeName() const
339 return pipename_ + ".out";
343 /////////////////////////////////////////////////////////////////////
347 /////////////////////////////////////////////////////////////////////
349 void ServerCallback(Server * server, string const & msg)
351 server->callback(msg);
354 Server::Server(LyXFunc * f, std::string const & pipes)
355 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
361 // say goodbye to clients so they stop sending messages
362 // send as many bye messages as there are clients,
363 // each with client's name.
365 for (int i = 0; i != numclients_; ++i) {
366 message = "LYXSRV:" + clients_[i] + ":bye\n";
367 pipes_.send(message);
372 int compare(char const * a, char const * b, unsigned int len)
375 return strncmp(a, b, len);
379 // Handle data gotten from communication, called by LyXComm
380 void Server::callback(string const & msg)
382 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
384 char const * p = msg.c_str();
386 // --- parse the string --------------------------------------------
388 // Format: LYXCMD:<client>:<func>:<argstring>\n
390 bool server_only = false;
392 // --- 1. check 'header' ---
394 if (compare(p, "LYXSRV:", 7) == 0) {
396 } else if (0 != compare(p, "LYXCMD:", 7)) {
397 lyxerr << "Server: Unknown request \""
403 // --- 2. for the moment ignore the client name ---
405 while (*p && *p != ':')
406 client += char(*p++);
412 // --- 3. get function name ---
414 while (*p && *p != ':')
417 // --- 4. parse the argument ---
419 if (!server_only && *p == ':' && *(++p)) {
420 while (*p && *p != '\n')
425 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
426 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
428 // --- lookup and exec the command ------------------
432 // return the greeting to inform the client that
434 if (cmd == "hello") {
436 if (numclients_ == MAX_CLIENTS) { //paranoid check
437 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
441 while (!clients_[i].empty() && i < numclients_)
443 clients_[i] = client;
445 buf = "LYXSRV:" + client + ":hello\n";
446 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
448 } else if (cmd == "bye") {
449 // If clients_ == 0 maybe we should reset the pipes
450 // to prevent fake callbacks
451 int i = 0; //look if client is registered
452 for (; i < numclients_; ++i) {
453 if (clients_[i] == client)
456 if (i < numclients_) {
459 LYXERR(Debug::LYXSERVER, "Server: Client "
460 << client << " said goodbye");
462 LYXERR(Debug::LYXSERVER,
463 "Server: ignoring bye messge from unregistered client" << client);
466 LYXERR0("Server: Undefined server command " << cmd << '.');
472 // which lyxfunc should we let it connect to?
473 // The correct solution would be to have a
474 // specialized (non-gui) BufferView. But how do
475 // we do it now? Probably we should just let it
476 // connect to the lyxfunc in the single LyXView we
477 // support currently. (Lgb)
479 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
480 string const rval = to_utf8(func_->getMessage());
482 // all commands produce an INFO or ERROR message
483 // in the output pipe, even if they do not return
484 // anything. See chapter 4 of Customization doc.
486 if (func_->errorStat())
490 buf += client + ':' + cmd + ':' + rval + '\n';
493 // !!! we don't do any error checking -
494 // if the client won't listen, the
495 // message is lost and others too
496 // maybe; so the client should empty
497 // the outpipe before issuing a request.
505 // Send a notify messge to a client, called by WorkAreaKeyPress
506 void Server::notifyClient(string const & s)
508 pipes_.send("NOTIFY:" + s + "\n");