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"
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 #if !defined (HAVE_MKFIFO)
75 // We provide a stub class that disables the lyxserver.
77 void LyXComm::openConnection()
81 void LyXComm::closeConnection()
85 int LyXComm::startPipe(string const & filename, bool write)
91 void LyXComm::endPipe(int & fd, string const & filename, bool write)
95 void LyXComm::emergencyCleanup()
98 void LyXComm::read_ready()
102 void LyXComm::send(string const & msg)
106 #else // defined (HAVE_MKFIFO)
109 void LyXComm::openConnection()
111 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
113 // If we are up, that's an error
115 lyxerr << "LyXComm: Already connected" << endl;
118 // We assume that we don't make it
121 if (pipename.empty()) {
122 lyxerr[Debug::LYXSERVER]
123 << "LyXComm: server is disabled, nothing to do"
128 if ((infd = startPipe(inPipeName(), false)) == -1)
131 if ((outfd = startPipe(outPipeName(), true)) == -1) {
132 endPipe(infd, inPipeName(), false);
136 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
137 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
138 << '\n' << strerror(errno) << endl;
144 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
149 void LyXComm::closeConnection()
151 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
153 if (pipename.empty()) {
154 lyxerr[Debug::LYXSERVER]
155 << "LyXComm: server is disabled, nothing to do"
161 lyxerr << "LyXComm: Already disconnected" << endl;
165 endPipe(infd, inPipeName(), false);
166 endPipe(outfd, outPipeName(), true);
172 int LyXComm::startPipe(string const & file, bool write)
174 FileName const filename(file);
175 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
176 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
177 << "If no other LyX program is active, please delete"
178 " the pipe by hand and try again." << endl;
183 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
184 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
185 << strerror(errno) << endl;
188 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
189 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
192 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
193 << strerror(errno) << endl;
199 theApp()->registerSocketCallback(fd,
200 boost::bind(&LyXComm::read_ready, this));
207 void LyXComm::endPipe(int & fd, string const & filename, bool write)
213 theApp()->unregisterSocketCallback(fd);
216 if (::close(fd) < 0) {
217 lyxerr << "LyXComm: Could not close pipe " << filename
218 << '\n' << strerror(errno) << endl;
221 if (unlink(FileName(filename)) < 0) {
222 lyxerr << "LyXComm: Could not remove pipe " << filename
223 << '\n' << strerror(errno) << endl;
230 void LyXComm::emergencyCleanup()
232 if (!pipename.empty()) {
233 endPipe(infd, inPipeName(), false);
234 endPipe(outfd, outPipeName(), true);
239 // Receives messages and sends then to client
240 void LyXComm::read_ready()
242 // nb! make read_buffer_ a class-member for multiple sessions
243 static string read_buffer_;
244 read_buffer_.erase();
246 int const charbuf_size = 100;
247 char charbuf[charbuf_size];
251 // the single = is intended here.
252 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
255 charbuf[status] = '\0'; // turn it into a c string
256 read_buffer_ += rtrim(charbuf, "\r");
257 // commit any commands read
258 while (read_buffer_.find('\n') != string::npos) {
259 // split() grabs the entire string if
260 // the delim /wasn't/ found. ?:-P
262 read_buffer_= split(read_buffer_, cmd,'\n');
263 lyxerr[Debug::LYXSERVER]
264 << "LyXComm: status:" << status
265 << ", read_buffer_:" << read_buffer_
266 << ", cmd:" << cmd << endl;
268 clientcb(client, cmd);
272 if (errno == EAGAIN) {
277 lyxerr << "LyXComm: " << strerror(errno) << endl;
278 if (!read_buffer_.empty()) {
279 lyxerr << "LyXComm: truncated command: "
280 << read_buffer_ << endl;
281 read_buffer_.erase();
283 break; // reset connection
287 // The connection gets reset in errno != EAGAIN
288 // Why does it need to be reset if errno == 0?
295 void LyXComm::send(string const & msg)
298 lyxerr << "LyXComm: Request to send empty string. Ignoring."
303 if (lyxerr.debugging(Debug::LYXSERVER)) {
304 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
307 if (pipename.empty()) return;
310 lyxerr << "LyXComm: Pipes are closed. Could not send "
312 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
313 lyxerr << "LyXComm: Error sending message: " << msg
314 << '\n' << strerror(errno)
315 << "\nLyXComm: Resetting connection" << endl;
321 #endif // defined (HAVE_MKFIFO)
324 string const LyXComm::inPipeName() const
326 return pipename + string(".in");
330 string const LyXComm::outPipeName() const
332 return pipename + string(".out");
338 LyXServer::~LyXServer()
340 // say goodbye to clients so they stop sending messages
341 // modified june 1999 by stefano@zool.su.se to send as many bye
342 // messages as there are clients, each with client's name.
344 for (int i= 0; i<numclients; ++i) {
345 message = "LYXSRV:" + clients[i] + ":bye\n";
351 /* ---F+------------------------------------------------------------------ *\
352 Function : ServerCallback
354 Purpose : handle data gotten from communication
355 \* ---F------------------------------------------------------------------- */
357 void LyXServer::callback(LyXServer * serv, string const & msg)
359 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
360 << msg << '\'' << endl;
362 char const * p = msg.c_str();
364 // --- parse the string --------------------------------------------
366 // Format: LYXCMD:<client>:<func>:<argstring>\n
368 bool server_only = false;
370 // --- 1. check 'header' ---
372 if (compare(p, "LYXSRV:", 7) == 0) {
374 } else if (0 != compare(p, "LYXCMD:", 7)) {
375 lyxerr << "LyXServer: Unknown request \""
381 // --- 2. for the moment ignore the client name ---
383 while (*p && *p != ':')
384 client += char(*p++);
388 // --- 3. get function name ---
390 while (*p && *p != ':')
393 // --- 4. parse the argument ---
395 if (!server_only && *p == ':' && *(++p)) {
396 while (*p && *p != '\n')
401 lyxerr[Debug::LYXSERVER]
402 << "LyXServer: Client: '" << client
403 << "' Command: '" << cmd
404 << "' Argument: '" << arg << '\'' << endl;
406 // --- lookup and exec the command ------------------
410 // return the greeting to inform the client that
412 if (cmd == "hello") {
414 if (serv->numclients == MAX_CLIENTS) { //paranoid check
415 lyxerr[Debug::LYXSERVER]
416 << "LyXServer: too many clients..."
420 int i= 0; //find place in clients[]
421 while (!serv->clients[i].empty()
422 && i<serv->numclients)
424 serv->clients[i] = client;
426 buf = "LYXSRV:" + client + ":hello\n";
427 lyxerr[Debug::LYXSERVER]
428 << "LyXServer: Greeting "
430 serv->pipes.send(buf);
431 } else if (cmd == "bye") {
432 // If clients == 0 maybe we should reset the pipes
433 // to prevent fake callbacks
434 int i = 0; //look if client is registered
435 for (; i < serv->numclients; ++i) {
436 if (serv->clients[i] == client) break;
438 if (i < serv->numclients) {
440 serv->clients[i].erase();
441 lyxerr[Debug::LYXSERVER]
442 << "LyXServer: Client "
443 << client << " said goodbye"
446 lyxerr[Debug::LYXSERVER]
447 << "LyXServer: ignoring bye messge from unregistered client"
451 lyxerr <<"LyXServer: Undefined server command "
452 << cmd << '.' << endl;
458 // which lyxfunc should we let it connect to?
459 // The correct solution would be to have a
460 // specialized (non-gui) BufferView. But how do
461 // we do it now? Probably we should just let it
462 // connect to the lyxfunc in the single LyXView we
463 // support currently. (Lgb)
466 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
467 string const rval = to_utf8(serv->func->getMessage());
469 //modified june 1999 stefano@zool.su.se:
470 //all commands produce an INFO or ERROR message
471 //in the output pipe, even if they do not return
472 //anything. See chapter 4 of Customization doc.
474 if (serv->func->errorStat())
478 buf += client + ':' + cmd + ':' + rval + '\n';
479 serv->pipes.send(buf);
481 // !!! we don't do any error checking -
482 // if the client won't listen, the
483 // message is lost and others too
484 // maybe; so the client should empty
485 // the outpipe before issuing a request.
493 /* ---F+------------------------------------------------------------------ *\
494 Function : LyXNotifyClient
495 Called by : WorkAreaKeyPress
496 Purpose : send a notify messge to a client
497 Parameters: s - string to send
499 \* ---F------------------------------------------------------------------- */
501 void LyXServer::notifyClient(string const & s)
503 string buf = string("NOTIFY:") + s + "\n";