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
42 #include "lyxserver.h"
44 #include "funcrequest.h"
45 #include "LyXAction.h"
47 #include "frontends/Application.h"
48 #include "support/lstrings.h"
49 #include "support/lyxlib.h"
51 #include <boost/bind.hpp>
54 #ifdef HAVE_SYS_STAT_H
55 # include <sys/stat.h>
59 using lyx::support::compare;
60 using lyx::support::rtrim;
61 using lyx::support::split;
62 using lyx::support::unlink;
68 #if !defined (HAVE_MKFIFO)
69 // We provide a stub class that disables the lyxserver.
71 void LyXComm::openConnection()
75 void LyXComm::closeConnection()
79 int LyXComm::startPipe(string const & filename, bool write)
85 void LyXComm::endPipe(int & fd, string const & filename, bool write)
89 void LyXComm::emergencyCleanup()
92 void LyXComm::read_ready()
96 void LyXComm::send(string const & msg)
100 #else // defined (HAVE_MKFIFO)
103 void LyXComm::openConnection()
105 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
107 // If we are up, that's an error
109 lyxerr << "LyXComm: Already connected" << endl;
112 // We assume that we don't make it
115 if (pipename.empty()) {
116 lyxerr[Debug::LYXSERVER]
117 << "LyXComm: server is disabled, nothing to do"
122 if ((infd = startPipe(inPipeName(), false)) == -1)
125 if ((outfd = startPipe(outPipeName(), true)) == -1) {
126 endPipe(infd, inPipeName(), false);
130 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
131 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
132 << '\n' << strerror(errno) << endl;
138 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
143 void LyXComm::closeConnection()
145 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
147 if (pipename.empty()) {
148 lyxerr[Debug::LYXSERVER]
149 << "LyXComm: server is disabled, nothing to do"
155 lyxerr << "LyXComm: Already disconnected" << endl;
159 endPipe(infd, inPipeName(), false);
160 endPipe(outfd, outPipeName(), true);
166 int LyXComm::startPipe(string const & filename, bool write)
168 if (::access(filename.c_str(), F_OK) == 0) {
169 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
170 << "If no other LyX program is active, please delete"
171 " the pipe by hand and try again." << endl;
176 if (::mkfifo(filename.c_str(), 0600) < 0) {
177 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
178 << strerror(errno) << endl;
181 int const fd = ::open(filename.c_str(),
182 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
185 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
186 << strerror(errno) << endl;
192 theApp->registerSocketCallback(fd,
193 boost::bind(&LyXComm::read_ready, this));
200 void LyXComm::endPipe(int & fd, string const & filename, bool write)
206 theApp->unregisterSocketCallback(fd);
209 if (::close(fd) < 0) {
210 lyxerr << "LyXComm: Could not close pipe " << filename
211 << '\n' << strerror(errno) << endl;
214 if (unlink(filename) < 0) {
215 lyxerr << "LyXComm: Could not remove pipe " << filename
216 << '\n' << strerror(errno) << endl;
223 void LyXComm::emergencyCleanup()
225 if (!pipename.empty()) {
226 endPipe(infd, inPipeName(), false);
227 endPipe(outfd, outPipeName(), true);
232 // Receives messages and sends then to client
233 void LyXComm::read_ready()
235 // nb! make read_buffer_ a class-member for multiple sessions
236 static string read_buffer_;
237 read_buffer_.erase();
239 int const charbuf_size = 100;
240 char charbuf[charbuf_size];
244 // the single = is intended here.
245 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
248 charbuf[status] = '\0'; // turn it into a c string
249 read_buffer_ += rtrim(charbuf, "\r");
250 // commit any commands read
251 while (read_buffer_.find('\n') != string::npos) {
252 // split() grabs the entire string if
253 // the delim /wasn't/ found. ?:-P
255 read_buffer_= split(read_buffer_, cmd,'\n');
256 lyxerr[Debug::LYXSERVER]
257 << "LyXComm: status:" << status
258 << ", read_buffer_:" << read_buffer_
259 << ", cmd:" << cmd << endl;
261 clientcb(client, cmd);
265 if (errno == EAGAIN) {
270 lyxerr << "LyXComm: " << strerror(errno) << endl;
271 if (!read_buffer_.empty()) {
272 lyxerr << "LyXComm: truncated command: "
273 << read_buffer_ << endl;
274 read_buffer_.erase();
276 break; // reset connection
280 // The connection gets reset in errno != EAGAIN
281 // Why does it need to be reset if errno == 0?
288 void LyXComm::send(string const & msg)
291 lyxerr << "LyXComm: Request to send empty string. Ignoring."
296 if (lyxerr.debugging(Debug::LYXSERVER)) {
297 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
300 if (pipename.empty()) return;
303 lyxerr << "LyXComm: Pipes are closed. Could not send "
305 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
306 lyxerr << "LyXComm: Error sending message: " << msg
307 << '\n' << strerror(errno)
308 << "\nLyXComm: Resetting connection" << endl;
314 #endif // defined (HAVE_MKFIFO)
317 string const LyXComm::inPipeName() const
319 return pipename + string(".in");
323 string const LyXComm::outPipeName() const
325 return pipename + string(".out");
331 LyXServer::~LyXServer()
333 // say goodbye to clients so they stop sending messages
334 // modified june 1999 by stefano@zool.su.se to send as many bye
335 // messages as there are clients, each with client's name.
337 for (int i= 0; i<numclients; ++i) {
338 message = "LYXSRV:" + clients[i] + ":bye\n";
344 /* ---F+------------------------------------------------------------------ *\
345 Function : ServerCallback
347 Purpose : handle data gotten from communication
348 \* ---F------------------------------------------------------------------- */
350 void LyXServer::callback(LyXServer * serv, string const & msg)
352 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
353 << msg << '\'' << endl;
355 char const * p = msg.c_str();
357 // --- parse the string --------------------------------------------
359 // Format: LYXCMD:<client>:<func>:<argstring>\n
361 bool server_only = false;
363 // --- 1. check 'header' ---
365 if (compare(p, "LYXSRV:", 7) == 0) {
367 } else if (0 != compare(p, "LYXCMD:", 7)) {
368 lyxerr << "LyXServer: Unknown request \""
374 // --- 2. for the moment ignore the client name ---
376 while (*p && *p != ':')
377 client += char(*p++);
381 // --- 3. get function name ---
383 while (*p && *p != ':')
386 // --- 4. parse the argument ---
388 if (!server_only && *p == ':' && *(++p)) {
389 while (*p && *p != '\n')
394 lyxerr[Debug::LYXSERVER]
395 << "LyXServer: Client: '" << client
396 << "' Command: '" << cmd
397 << "' Argument: '" << arg << '\'' << endl;
399 // --- lookup and exec the command ------------------
403 // return the greeting to inform the client that
405 if (cmd == "hello") {
407 if (serv->numclients == MAX_CLIENTS) { //paranoid check
408 lyxerr[Debug::LYXSERVER]
409 << "LyXServer: too many clients..."
413 int i= 0; //find place in clients[]
414 while (!serv->clients[i].empty()
415 && i<serv->numclients)
417 serv->clients[i] = client;
419 buf = "LYXSRV:" + client + ":hello\n";
420 lyxerr[Debug::LYXSERVER]
421 << "LyXServer: Greeting "
423 serv->pipes.send(buf);
424 } else if (cmd == "bye") {
425 // If clients == 0 maybe we should reset the pipes
426 // to prevent fake callbacks
427 int i = 0; //look if client is registered
428 for (; i < serv->numclients; ++i) {
429 if (serv->clients[i] == client) break;
431 if (i < serv->numclients) {
433 serv->clients[i].erase();
434 lyxerr[Debug::LYXSERVER]
435 << "LyXServer: Client "
436 << client << " said goodbye"
439 lyxerr[Debug::LYXSERVER]
440 << "LyXServer: ignoring bye messge from unregistered client"
444 lyxerr <<"LyXServer: Undefined server command "
445 << cmd << '.' << endl;
451 // which lyxfunc should we let it connect to?
452 // The correct solution would be to have a
453 // specialized (non-gui) BufferView. But how do
454 // we do it now? Probably we should just let it
455 // connect to the lyxfunc in the single LyXView we
456 // support currently. (Lgb)
459 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
460 string const rval = lyx::to_utf8(serv->func->getMessage());
462 //modified june 1999 stefano@zool.su.se:
463 //all commands produce an INFO or ERROR message
464 //in the output pipe, even if they do not return
465 //anything. See chapter 4 of Customization doc.
467 if (serv->func->errorStat())
471 buf += client + ':' + cmd + ':' + rval + '\n';
472 serv->pipes.send(buf);
474 // !!! we don't do any error checking -
475 // if the client won't listen, the
476 // message is lost and others too
477 // maybe; so the client should empty
478 // the outpipe before issuing a request.
486 /* ---F+------------------------------------------------------------------ *\
487 Function : LyXNotifyClient
488 Called by : WorkAreaKeyPress
489 Purpose : send a notify messge to a client
490 Parameters: s - string to send
492 \* ---F------------------------------------------------------------------- */
494 void LyXServer::notifyClient(string const & s)
496 string buf = string("NOTIFY:") + s + "\n";