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>
62 using support::compare;
65 using support::unlink;
71 #if !defined (HAVE_MKFIFO)
72 // We provide a stub class that disables the lyxserver.
74 void LyXComm::openConnection()
78 void LyXComm::closeConnection()
82 int LyXComm::startPipe(string const & filename, bool write)
88 void LyXComm::endPipe(int & fd, string const & filename, bool write)
92 void LyXComm::emergencyCleanup()
95 void LyXComm::read_ready()
99 void LyXComm::send(string const & msg)
103 #else // defined (HAVE_MKFIFO)
106 void LyXComm::openConnection()
108 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
110 // If we are up, that's an error
112 lyxerr << "LyXComm: Already connected" << endl;
115 // We assume that we don't make it
118 if (pipename.empty()) {
119 lyxerr[Debug::LYXSERVER]
120 << "LyXComm: server is disabled, nothing to do"
125 if ((infd = startPipe(inPipeName(), false)) == -1)
128 if ((outfd = startPipe(outPipeName(), true)) == -1) {
129 endPipe(infd, inPipeName(), false);
133 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
134 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
135 << '\n' << strerror(errno) << endl;
141 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
146 void LyXComm::closeConnection()
148 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
150 if (pipename.empty()) {
151 lyxerr[Debug::LYXSERVER]
152 << "LyXComm: server is disabled, nothing to do"
158 lyxerr << "LyXComm: Already disconnected" << endl;
162 endPipe(infd, inPipeName(), false);
163 endPipe(outfd, outPipeName(), true);
169 int LyXComm::startPipe(string const & filename, bool write)
171 if (::access(filename.c_str(), F_OK) == 0) {
172 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
173 << "If no other LyX program is active, please delete"
174 " the pipe by hand and try again." << endl;
179 if (::mkfifo(filename.c_str(), 0600) < 0) {
180 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
181 << strerror(errno) << endl;
184 int const fd = ::open(filename.c_str(),
185 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
188 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
189 << strerror(errno) << endl;
195 theApp->registerSocketCallback(fd,
196 boost::bind(&LyXComm::read_ready, this));
203 void LyXComm::endPipe(int & fd, string const & filename, bool write)
209 theApp->unregisterSocketCallback(fd);
212 if (::close(fd) < 0) {
213 lyxerr << "LyXComm: Could not close pipe " << filename
214 << '\n' << strerror(errno) << endl;
217 if (unlink(filename) < 0) {
218 lyxerr << "LyXComm: Could not remove pipe " << filename
219 << '\n' << strerror(errno) << endl;
226 void LyXComm::emergencyCleanup()
228 if (!pipename.empty()) {
229 endPipe(infd, inPipeName(), false);
230 endPipe(outfd, outPipeName(), true);
235 // Receives messages and sends then to client
236 void LyXComm::read_ready()
238 // nb! make read_buffer_ a class-member for multiple sessions
239 static string read_buffer_;
240 read_buffer_.erase();
242 int const charbuf_size = 100;
243 char charbuf[charbuf_size];
247 // the single = is intended here.
248 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
251 charbuf[status] = '\0'; // turn it into a c string
252 read_buffer_ += rtrim(charbuf, "\r");
253 // commit any commands read
254 while (read_buffer_.find('\n') != string::npos) {
255 // split() grabs the entire string if
256 // the delim /wasn't/ found. ?:-P
258 read_buffer_= split(read_buffer_, cmd,'\n');
259 lyxerr[Debug::LYXSERVER]
260 << "LyXComm: status:" << status
261 << ", read_buffer_:" << read_buffer_
262 << ", cmd:" << cmd << endl;
264 clientcb(client, cmd);
268 if (errno == EAGAIN) {
273 lyxerr << "LyXComm: " << strerror(errno) << endl;
274 if (!read_buffer_.empty()) {
275 lyxerr << "LyXComm: truncated command: "
276 << read_buffer_ << endl;
277 read_buffer_.erase();
279 break; // reset connection
283 // The connection gets reset in errno != EAGAIN
284 // Why does it need to be reset if errno == 0?
291 void LyXComm::send(string const & msg)
294 lyxerr << "LyXComm: Request to send empty string. Ignoring."
299 if (lyxerr.debugging(Debug::LYXSERVER)) {
300 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
303 if (pipename.empty()) return;
306 lyxerr << "LyXComm: Pipes are closed. Could not send "
308 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
309 lyxerr << "LyXComm: Error sending message: " << msg
310 << '\n' << strerror(errno)
311 << "\nLyXComm: Resetting connection" << endl;
317 #endif // defined (HAVE_MKFIFO)
320 string const LyXComm::inPipeName() const
322 return pipename + string(".in");
326 string const LyXComm::outPipeName() const
328 return pipename + string(".out");
334 LyXServer::~LyXServer()
336 // say goodbye to clients so they stop sending messages
337 // modified june 1999 by stefano@zool.su.se to send as many bye
338 // messages as there are clients, each with client's name.
340 for (int i= 0; i<numclients; ++i) {
341 message = "LYXSRV:" + clients[i] + ":bye\n";
347 /* ---F+------------------------------------------------------------------ *\
348 Function : ServerCallback
350 Purpose : handle data gotten from communication
351 \* ---F------------------------------------------------------------------- */
353 void LyXServer::callback(LyXServer * serv, string const & msg)
355 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
356 << msg << '\'' << endl;
358 char const * p = msg.c_str();
360 // --- parse the string --------------------------------------------
362 // Format: LYXCMD:<client>:<func>:<argstring>\n
364 bool server_only = false;
366 // --- 1. check 'header' ---
368 if (compare(p, "LYXSRV:", 7) == 0) {
370 } else if (0 != compare(p, "LYXCMD:", 7)) {
371 lyxerr << "LyXServer: Unknown request \""
377 // --- 2. for the moment ignore the client name ---
379 while (*p && *p != ':')
380 client += char(*p++);
384 // --- 3. get function name ---
386 while (*p && *p != ':')
389 // --- 4. parse the argument ---
391 if (!server_only && *p == ':' && *(++p)) {
392 while (*p && *p != '\n')
397 lyxerr[Debug::LYXSERVER]
398 << "LyXServer: Client: '" << client
399 << "' Command: '" << cmd
400 << "' Argument: '" << arg << '\'' << endl;
402 // --- lookup and exec the command ------------------
406 // return the greeting to inform the client that
408 if (cmd == "hello") {
410 if (serv->numclients == MAX_CLIENTS) { //paranoid check
411 lyxerr[Debug::LYXSERVER]
412 << "LyXServer: too many clients..."
416 int i= 0; //find place in clients[]
417 while (!serv->clients[i].empty()
418 && i<serv->numclients)
420 serv->clients[i] = client;
422 buf = "LYXSRV:" + client + ":hello\n";
423 lyxerr[Debug::LYXSERVER]
424 << "LyXServer: Greeting "
426 serv->pipes.send(buf);
427 } else if (cmd == "bye") {
428 // If clients == 0 maybe we should reset the pipes
429 // to prevent fake callbacks
430 int i = 0; //look if client is registered
431 for (; i < serv->numclients; ++i) {
432 if (serv->clients[i] == client) break;
434 if (i < serv->numclients) {
436 serv->clients[i].erase();
437 lyxerr[Debug::LYXSERVER]
438 << "LyXServer: Client "
439 << client << " said goodbye"
442 lyxerr[Debug::LYXSERVER]
443 << "LyXServer: ignoring bye messge from unregistered client"
447 lyxerr <<"LyXServer: Undefined server command "
448 << cmd << '.' << endl;
454 // which lyxfunc should we let it connect to?
455 // The correct solution would be to have a
456 // specialized (non-gui) BufferView. But how do
457 // we do it now? Probably we should just let it
458 // connect to the lyxfunc in the single LyXView we
459 // support currently. (Lgb)
462 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
463 string const rval = to_utf8(serv->func->getMessage());
465 //modified june 1999 stefano@zool.su.se:
466 //all commands produce an INFO or ERROR message
467 //in the output pipe, even if they do not return
468 //anything. See chapter 4 of Customization doc.
470 if (serv->func->errorStat())
474 buf += client + ':' + cmd + ':' + rval + '\n';
475 serv->pipes.send(buf);
477 // !!! we don't do any error checking -
478 // if the client won't listen, the
479 // message is lost and others too
480 // maybe; so the client should empty
481 // the outpipe before issuing a request.
489 /* ---F+------------------------------------------------------------------ *\
490 Function : LyXNotifyClient
491 Called by : WorkAreaKeyPress
492 Purpose : send a notify messge to a client
493 Parameters: s - string to send
495 \* ---F------------------------------------------------------------------- */
497 void LyXServer::notifyClient(string const & s)
499 string buf = string("NOTIFY:") + s + "\n";