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/lyx_gui.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 lyx_gui::register_socket_callback(fd, boost::bind(&LyXComm::read_ready, this));
199 void LyXComm::endPipe(int & fd, string const & filename, bool write)
205 lyx_gui::unregister_socket_callback(fd);
208 if (::close(fd) < 0) {
209 lyxerr << "LyXComm: Could not close pipe " << filename
210 << '\n' << strerror(errno) << endl;
213 if (unlink(filename) < 0) {
214 lyxerr << "LyXComm: Could not remove pipe " << filename
215 << '\n' << strerror(errno) << endl;
222 void LyXComm::emergencyCleanup()
224 if (!pipename.empty()) {
225 endPipe(infd, inPipeName(), false);
226 endPipe(outfd, outPipeName(), true);
231 // Receives messages and sends then to client
232 void LyXComm::read_ready()
234 // nb! make read_buffer_ a class-member for multiple sessions
235 static string read_buffer_;
236 read_buffer_.erase();
238 int const charbuf_size = 100;
239 char charbuf[charbuf_size];
243 // the single = is intended here.
244 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
247 charbuf[status] = '\0'; // turn it into a c string
248 read_buffer_ += rtrim(charbuf, "\r");
249 // commit any commands read
250 while (read_buffer_.find('\n') != string::npos) {
251 // split() grabs the entire string if
252 // the delim /wasn't/ found. ?:-P
254 read_buffer_= split(read_buffer_, cmd,'\n');
255 lyxerr[Debug::LYXSERVER]
256 << "LyXComm: status:" << status
257 << ", read_buffer_:" << read_buffer_
258 << ", cmd:" << cmd << endl;
260 clientcb(client, cmd);
264 if (errno == EAGAIN) {
269 lyxerr << "LyXComm: " << strerror(errno) << endl;
270 if (!read_buffer_.empty()) {
271 lyxerr << "LyXComm: truncated command: "
272 << read_buffer_ << endl;
273 read_buffer_.erase();
275 break; // reset connection
279 // The connection gets reset in errno != EAGAIN
280 // Why does it need to be reset if errno == 0?
287 void LyXComm::send(string const & msg)
290 lyxerr << "LyXComm: Request to send empty string. Ignoring."
295 if (lyxerr.debugging(Debug::LYXSERVER)) {
296 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
299 if (pipename.empty()) return;
302 lyxerr << "LyXComm: Pipes are closed. Could not send "
304 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
305 lyxerr << "LyXComm: Error sending message: " << msg
306 << '\n' << strerror(errno)
307 << "\nLyXComm: Resetting connection" << endl;
313 #endif // defined (HAVE_MKFIFO)
316 string const LyXComm::inPipeName() const
318 return pipename + string(".in");
322 string const LyXComm::outPipeName() const
324 return pipename + string(".out");
330 LyXServer::~LyXServer()
332 // say goodbye to clients so they stop sending messages
333 // modified june 1999 by stefano@zool.su.se to send as many bye
334 // messages as there are clients, each with client's name.
336 for (int i= 0; i<numclients; ++i) {
337 message = "LYXSRV:" + clients[i] + ":bye\n";
343 /* ---F+------------------------------------------------------------------ *\
344 Function : ServerCallback
346 Purpose : handle data gotten from communication
347 \* ---F------------------------------------------------------------------- */
349 void LyXServer::callback(LyXServer * serv, string const & msg)
351 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
352 << msg << '\'' << endl;
354 char const * p = msg.c_str();
356 // --- parse the string --------------------------------------------
358 // Format: LYXCMD:<client>:<func>:<argstring>\n
360 bool server_only = false;
362 // --- 1. check 'header' ---
364 if (compare(p, "LYXSRV:", 7) == 0) {
366 } else if (0 != compare(p, "LYXCMD:", 7)) {
367 lyxerr << "LyXServer: Unknown request \""
373 // --- 2. for the moment ignore the client name ---
375 while (*p && *p != ':')
376 client += char(*p++);
380 // --- 3. get function name ---
382 while (*p && *p != ':')
385 // --- 4. parse the argument ---
387 if (!server_only && *p == ':' && *(++p)) {
388 while (*p && *p != '\n')
393 lyxerr[Debug::LYXSERVER]
394 << "LyXServer: Client: '" << client
395 << "' Command: '" << cmd
396 << "' Argument: '" << arg << '\'' << endl;
398 // --- lookup and exec the command ------------------
402 // return the greeting to inform the client that
404 if (cmd == "hello") {
406 if (serv->numclients == MAX_CLIENTS) { //paranoid check
407 lyxerr[Debug::LYXSERVER]
408 << "LyXServer: too many clients..."
412 int i= 0; //find place in clients[]
413 while (!serv->clients[i].empty()
414 && i<serv->numclients)
416 serv->clients[i] = client;
418 buf = "LYXSRV:" + client + ":hello\n";
419 lyxerr[Debug::LYXSERVER]
420 << "LyXServer: Greeting "
422 serv->pipes.send(buf);
423 } else if (cmd == "bye") {
424 // If clients == 0 maybe we should reset the pipes
425 // to prevent fake callbacks
426 int i = 0; //look if client is registered
427 for (; i < serv->numclients; ++i) {
428 if (serv->clients[i] == client) break;
430 if (i < serv->numclients) {
432 serv->clients[i].erase();
433 lyxerr[Debug::LYXSERVER]
434 << "LyXServer: Client "
435 << client << " said goodbye"
438 lyxerr[Debug::LYXSERVER]
439 << "LyXServer: ignoring bye messge from unregistered client"
443 lyxerr <<"LyXServer: Undefined server command "
444 << cmd << '.' << endl;
450 // which lyxfunc should we let it connect to?
451 // The correct solution would be to have a
452 // specialized (non-gui) BufferView. But how do
453 // we do it now? Probably we should just let it
454 // connect to the lyxfunc in the single LyXView we
455 // support currently. (Lgb)
458 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
459 string const rval = lyx::to_utf8(serv->func->getMessage());
461 //modified june 1999 stefano@zool.su.se:
462 //all commands produce an INFO or ERROR message
463 //in the output pipe, even if they do not return
464 //anything. See chapter 4 of Customization doc.
466 if (serv->func->errorStat())
470 buf += client + ':' + cmd + ':' + rval + '\n';
471 serv->pipes.send(buf);
473 // !!! we don't do any error checking -
474 // if the client won't listen, the
475 // message is lost and others too
476 // maybe; so the client should empty
477 // the outpipe before issuing a request.
485 /* ---F+------------------------------------------------------------------ *\
486 Function : LyXNotifyClient
487 Called by : WorkAreaKeyPress
488 Purpose : send a notify messge to a client
489 Parameters: s - string to send
491 \* ---F------------------------------------------------------------------- */
493 void LyXServer::notifyClient(string const & s)
495 string buf = string("NOTIFY:") + s + "\n";