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 "support/lstrings.h"
48 #include "support/lyxlib.h"
50 #include <boost/bind.hpp>
53 #ifdef HAVE_SYS_STAT_H
54 # include <sys/stat.h>
58 using lyx::support::compare;
59 using lyx::support::rtrim;
60 using lyx::support::split;
61 using lyx::support::unlink;
67 #if !defined (HAVE_MKFIFO)
68 // We provide a stub class that disables the lyxserver.
70 void LyXComm::openConnection()
74 void LyXComm::closeConnection()
78 int LyXComm::startPipe(string const & filename, bool write)
84 void LyXComm::endPipe(int & fd, string const & filename, bool write)
88 void LyXComm::emergencyCleanup()
91 void LyXComm::read_ready()
95 void LyXComm::send(string const & msg)
99 #else // defined (HAVE_MKFIFO)
102 void LyXComm::openConnection()
104 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
106 // If we are up, that's an error
108 lyxerr << "LyXComm: Already connected" << endl;
111 // We assume that we don't make it
114 if (pipename.empty()) {
115 lyxerr[Debug::LYXSERVER]
116 << "LyXComm: server is disabled, nothing to do"
121 if ((infd = startPipe(inPipeName(), false)) == -1)
124 if ((outfd = startPipe(outPipeName(), true)) == -1) {
125 endPipe(infd, inPipeName(), false);
129 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
130 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
131 << '\n' << strerror(errno) << endl;
137 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
142 void LyXComm::closeConnection()
144 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
146 if (pipename.empty()) {
147 lyxerr[Debug::LYXSERVER]
148 << "LyXComm: server is disabled, nothing to do"
154 lyxerr << "LyXComm: Already disconnected" << endl;
158 endPipe(infd, inPipeName(), false);
159 endPipe(outfd, outPipeName(), true);
165 int LyXComm::startPipe(string const & filename, bool write)
167 if (::access(filename.c_str(), F_OK) == 0) {
168 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
169 << "If no other LyX program is active, please delete"
170 " the pipe by hand and try again." << endl;
175 if (::mkfifo(filename.c_str(), 0600) < 0) {
176 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
177 << strerror(errno) << endl;
180 int const fd = ::open(filename.c_str(),
181 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
184 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
185 << strerror(errno) << endl;
191 lyx_gui::register_socket_callback(fd, boost::bind(&LyXComm::read_ready, this));
198 void LyXComm::endPipe(int & fd, string const & filename, bool write)
204 lyx_gui::unregister_socket_callback(fd);
207 if (::close(fd) < 0) {
208 lyxerr << "LyXComm: Could not close pipe " << filename
209 << '\n' << strerror(errno) << endl;
212 if (unlink(filename) < 0) {
213 lyxerr << "LyXComm: Could not remove pipe " << filename
214 << '\n' << strerror(errno) << endl;
221 void LyXComm::emergencyCleanup()
223 if (!pipename.empty()) {
224 endPipe(infd, inPipeName(), false);
225 endPipe(outfd, outPipeName(), true);
230 // Receives messages and sends then to client
231 void LyXComm::read_ready()
233 // nb! make read_buffer_ a class-member for multiple sessions
234 static string read_buffer_;
235 read_buffer_.erase();
237 int const charbuf_size = 100;
238 char charbuf[charbuf_size];
242 // the single = is intended here.
243 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
246 charbuf[status] = '\0'; // turn it into a c string
247 read_buffer_ += rtrim(charbuf, "\r");
248 // commit any commands read
249 while (read_buffer_.find('\n') != string::npos) {
250 // split() grabs the entire string if
251 // the delim /wasn't/ found. ?:-P
253 read_buffer_= split(read_buffer_, cmd,'\n');
254 lyxerr[Debug::LYXSERVER]
255 << "LyXComm: status:" << status
256 << ", read_buffer_:" << read_buffer_
257 << ", cmd:" << cmd << endl;
259 clientcb(client, cmd);
263 if (errno == EAGAIN) {
268 lyxerr << "LyXComm: " << strerror(errno) << endl;
269 if (!read_buffer_.empty()) {
270 lyxerr << "LyXComm: truncated command: "
271 << read_buffer_ << endl;
272 read_buffer_.erase();
274 break; // reset connection
278 // The connection gets reset in errno != EAGAIN
279 // Why does it need to be reset if errno == 0?
286 void LyXComm::send(string const & msg)
289 lyxerr << "LyXComm: Request to send empty string. Ignoring."
294 if (lyxerr.debugging(Debug::LYXSERVER)) {
295 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
298 if (pipename.empty()) return;
301 lyxerr << "LyXComm: Pipes are closed. Could not send "
303 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
304 lyxerr << "LyXComm: Error sending message: " << msg
305 << '\n' << strerror(errno)
306 << "\nLyXComm: Resetting connection" << endl;
312 #endif // defined (HAVE_MKFIFO)
315 string const LyXComm::inPipeName() const
317 return pipename + string(".in");
321 string const LyXComm::outPipeName() const
323 return pipename + string(".out");
329 LyXServer::~LyXServer()
331 // say goodbye to clients so they stop sending messages
332 // modified june 1999 by stefano@zool.su.se to send as many bye
333 // messages as there are clients, each with client's name.
335 for (int i= 0; i<numclients; ++i) {
336 message = "LYXSRV:" + clients[i] + ":bye\n";
342 /* ---F+------------------------------------------------------------------ *\
343 Function : ServerCallback
345 Purpose : handle data gotten from communication
346 \* ---F------------------------------------------------------------------- */
348 void LyXServer::callback(LyXServer * serv, string const & msg)
350 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
351 << msg << '\'' << endl;
353 char const * p = msg.c_str();
355 // --- parse the string --------------------------------------------
357 // Format: LYXCMD:<client>:<func>:<argstring>\n
359 bool server_only = false;
361 // --- 1. check 'header' ---
363 if (compare(p, "LYXSRV:", 7) == 0) {
365 } else if (0 != compare(p, "LYXCMD:", 7)) {
366 lyxerr << "LyXServer: Unknown request \""
372 // --- 2. for the moment ignore the client name ---
374 while (*p && *p != ':')
375 client += char(*p++);
379 // --- 3. get function name ---
381 while (*p && *p != ':')
384 // --- 4. parse the argument ---
386 if (!server_only && *p == ':' && *(++p)) {
387 while (*p && *p != '\n')
392 lyxerr[Debug::LYXSERVER]
393 << "LyXServer: Client: '" << client
394 << "' Command: '" << cmd
395 << "' Argument: '" << arg << '\'' << endl;
397 // --- lookup and exec the command ------------------
401 // return the greeting to inform the client that
403 if (cmd == "hello") {
405 if (serv->numclients == MAX_CLIENTS) { //paranoid check
406 lyxerr[Debug::LYXSERVER]
407 << "LyXServer: too many clients..."
411 int i= 0; //find place in clients[]
412 while (!serv->clients[i].empty()
413 && i<serv->numclients)
415 serv->clients[i] = client;
417 buf = "LYXSRV:" + client + ":hello\n";
418 lyxerr[Debug::LYXSERVER]
419 << "LyXServer: Greeting "
421 serv->pipes.send(buf);
422 } else if (cmd == "bye") {
423 // If clients == 0 maybe we should reset the pipes
424 // to prevent fake callbacks
425 int i = 0; //look if client is registered
426 for (; i < serv->numclients; ++i) {
427 if (serv->clients[i] == client) break;
429 if (i < serv->numclients) {
431 serv->clients[i].erase();
432 lyxerr[Debug::LYXSERVER]
433 << "LyXServer: Client "
434 << client << " said goodbye"
437 lyxerr[Debug::LYXSERVER]
438 << "LyXServer: ignoring bye messge from unregistered client"
442 lyxerr <<"LyXServer: Undefined server command "
443 << cmd << '.' << endl;
449 // which lyxfunc should we let it connect to?
450 // The correct solution would be to have a
451 // specialized (non-gui) BufferView. But how do
452 // we do it now? Probably we should just let it
453 // connect to the lyxfunc in the single LyXView we
454 // support currently. (Lgb)
457 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
458 string const rval = lyx::to_utf8(serv->func->getMessage());
460 //modified june 1999 stefano@zool.su.se:
461 //all commands produce an INFO or ERROR message
462 //in the output pipe, even if they do not return
463 //anything. See chapter 4 of Customization doc.
465 if (serv->func->errorStat())
469 buf += client + ':' + cmd + ':' + rval + '\n';
470 serv->pipes.send(buf);
472 // !!! we don't do any error checking -
473 // if the client won't listen, the
474 // message is lost and others too
475 // maybe; so the client should empty
476 // the outpipe before issuing a request.
484 /* ---F+------------------------------------------------------------------ *\
485 Function : LyXNotifyClient
486 Called by : WorkAreaKeyPress
487 Purpose : send a notify messge to a client
488 Parameters: s - string to send
490 \* ---F------------------------------------------------------------------- */
492 void LyXServer::notifyClient(string const & s)
494 string buf = string("NOTIFY:") + s + "\n";