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
43 #include "support/debug.h"
44 #include "FuncRequest.h"
45 #include "LyXAction.h"
47 #include "frontends/Application.h"
49 #include "support/FileName.h"
50 #include "support/lstrings.h"
52 #include <boost/bind.hpp>
55 #ifdef HAVE_SYS_STAT_H
56 # include <sys/stat.h>
61 using namespace lyx::support;
65 /////////////////////////////////////////////////////////////////////
69 /////////////////////////////////////////////////////////////////////
71 #if !defined (HAVE_MKFIFO)
72 // We provide a stub class that disables the lyxserver.
74 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
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 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
110 : pipename_(pip), client_(cli), clientcb_(ccb)
117 void LyXComm::openConnection()
119 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
121 // If we are up, that's an error
123 lyxerr << "LyXComm: Already connected" << endl;
126 // We assume that we don't make it
129 if (pipename_.empty()) {
130 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
134 infd_ = startPipe(inPipeName(), false);
138 outfd_ = startPipe(outPipeName(), true);
140 endPipe(infd_, inPipeName(), false);
144 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
145 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
146 << '\n' << strerror(errno) << endl;
152 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
157 void LyXComm::closeConnection()
159 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
161 if (pipename_.empty()) {
162 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
167 LYXERR0("LyXComm: Already disconnected");
171 endPipe(infd_, inPipeName(), false);
172 endPipe(outfd_, outPipeName(), true);
178 int LyXComm::startPipe(string const & file, bool write)
180 FileName const filename(file);
181 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
182 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
183 << "If no other LyX program is active, please delete"
184 " the pipe by hand and try again." << endl;
189 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
190 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
191 << strerror(errno) << endl;
194 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
195 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
198 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
199 << strerror(errno) << endl;
200 filename.removeFile();
205 theApp()->registerSocketCallback(fd,
206 boost::bind(&LyXComm::read_ready, this));
213 void LyXComm::endPipe(int & fd, string const & filename, bool write)
219 theApp()->unregisterSocketCallback(fd);
221 if (::close(fd) < 0) {
222 lyxerr << "LyXComm: Could not close pipe " << filename
223 << '\n' << strerror(errno) << endl;
226 if (FileName(filename).removeFile() < 0) {
227 lyxerr << "LyXComm: Could not remove pipe " << filename
228 << '\n' << strerror(errno) << endl;
235 void LyXComm::emergencyCleanup()
237 if (!pipename_.empty()) {
238 endPipe(infd_, inPipeName(), false);
239 endPipe(outfd_, outPipeName(), true);
244 // Receives messages and sends then to client
245 void LyXComm::read_ready()
247 // FIXME: make read_buffer_ a class-member for multiple sessions
248 static string read_buffer_;
249 read_buffer_.erase();
251 int const charbuf_size = 100;
252 char charbuf[charbuf_size];
256 // the single = is intended here.
257 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
260 charbuf[status] = '\0'; // turn it into a c string
261 read_buffer_ += rtrim(charbuf, "\r");
262 // commit any commands read
263 while (read_buffer_.find('\n') != string::npos) {
264 // split() grabs the entire string if
265 // the delim /wasn't/ found. ?:-P
267 read_buffer_= split(read_buffer_, cmd,'\n');
268 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
269 << ", read_buffer_:" << read_buffer_
272 clientcb_(client_, cmd);
276 if (errno == EAGAIN) {
281 LYXERR0("LyXComm: " << strerror(errno));
282 if (!read_buffer_.empty()) {
283 LYXERR0("LyXComm: truncated command: " << read_buffer_);
284 read_buffer_.erase();
286 break; // reset connection
290 // The connection gets reset in errno != EAGAIN
291 // Why does it need to be reset if errno == 0?
298 void LyXComm::send(string const & msg)
301 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
305 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
307 if (pipename_.empty()) return;
310 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
311 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
312 lyxerr << "LyXComm: Error sending message: " << msg
313 << '\n' << strerror(errno)
314 << "\nLyXComm: Resetting connection" << endl;
320 #endif // defined (HAVE_MKFIFO)
323 string const LyXComm::inPipeName() const
325 return pipename_ + ".in";
329 string const LyXComm::outPipeName() const
331 return pipename_ + ".out";
335 /////////////////////////////////////////////////////////////////////
339 /////////////////////////////////////////////////////////////////////
341 void ServerCallback(Server * server, string const & msg)
343 server->callback(msg);
346 Server::Server(LyXFunc * f, string const & pipes)
347 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
353 // say goodbye to clients so they stop sending messages
354 // send as many bye messages as there are clients,
355 // each with client's name.
357 for (int i = 0; i != numclients_; ++i) {
358 message = "LYXSRV:" + clients_[i] + ":bye\n";
359 pipes_.send(message);
364 int compare(char const * a, char const * b, unsigned int len)
367 return strncmp(a, b, len);
371 // Handle data gotten from communication, called by LyXComm
372 void Server::callback(string const & msg)
374 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
376 char const * p = msg.c_str();
378 // --- parse the string --------------------------------------------
380 // Format: LYXCMD:<client>:<func>:<argstring>\n
382 bool server_only = false;
384 // --- 1. check 'header' ---
386 if (compare(p, "LYXSRV:", 7) == 0) {
388 } else if (0 != compare(p, "LYXCMD:", 7)) {
389 lyxerr << "Server: Unknown request \""
395 // --- 2. for the moment ignore the client name ---
397 while (*p && *p != ':')
398 client += char(*p++);
404 // --- 3. get function name ---
406 while (*p && *p != ':')
409 // --- 4. parse the argument ---
411 if (!server_only && *p == ':' && *(++p)) {
412 while (*p && *p != '\n')
417 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
418 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
420 // --- lookup and exec the command ------------------
424 // return the greeting to inform the client that
426 if (cmd == "hello") {
428 if (numclients_ == MAX_CLIENTS) { //paranoid check
429 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
433 while (!clients_[i].empty() && i < numclients_)
435 clients_[i] = client;
437 buf = "LYXSRV:" + client + ":hello\n";
438 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
440 } else if (cmd == "bye") {
441 // If clients_ == 0 maybe we should reset the pipes
442 // to prevent fake callbacks
443 int i = 0; //look if client is registered
444 for (; i < numclients_; ++i) {
445 if (clients_[i] == client)
448 if (i < numclients_) {
451 LYXERR(Debug::LYXSERVER, "Server: Client "
452 << client << " said goodbye");
454 LYXERR(Debug::LYXSERVER,
455 "Server: ignoring bye messge from unregistered client" << client);
458 LYXERR0("Server: Undefined server command " << cmd << '.');
464 // which lyxfunc should we let it connect to?
465 // The correct solution would be to have a
466 // specialized (non-gui) BufferView. But how do
467 // we do it now? Probably we should just let it
468 // connect to the lyxfunc in the single LyXView we
469 // support currently. (Lgb)
471 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
472 string const rval = to_utf8(func_->getMessage());
474 // all commands produce an INFO or ERROR message
475 // in the output pipe, even if they do not return
476 // anything. See chapter 4 of Customization doc.
478 if (func_->errorStat())
482 buf += client + ':' + cmd + ':' + rval + '\n';
485 // !!! we don't do any error checking -
486 // if the client won't listen, the
487 // message is lost and others too
488 // maybe; so the client should empty
489 // the outpipe before issuing a request.
497 // Send a notify messge to a client, called by WorkAreaKeyPress
498 void Server::notifyClient(string const & s)
500 pipes_.send("NOTIFY:" + s + "\n");