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"
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;
73 /////////////////////////////////////////////////////////////////////
77 /////////////////////////////////////////////////////////////////////
79 #if !defined (HAVE_MKFIFO)
80 // We provide a stub class that disables the lyxserver.
82 LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
85 void LyXComm::openConnection()
89 void LyXComm::closeConnection()
93 int LyXComm::startPipe(string const & filename, bool write)
99 void LyXComm::endPipe(int & fd, string const & filename, bool write)
103 void LyXComm::emergencyCleanup()
106 void LyXComm::read_ready()
110 void LyXComm::send(string const & msg)
114 #else // defined (HAVE_MKFIFO)
117 LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
118 : pipename_(pip), client_(cli), clientcb_(ccb)
125 void LyXComm::openConnection()
127 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
129 // If we are up, that's an error
131 lyxerr << "LyXComm: Already connected" << endl;
134 // We assume that we don't make it
137 if (pipename_.empty()) {
138 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
142 infd_ = startPipe(inPipeName(), false);
146 outfd_ = startPipe(outPipeName(), true);
148 endPipe(infd_, inPipeName(), false);
152 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
153 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
154 << '\n' << strerror(errno) << endl;
160 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
165 void LyXComm::closeConnection()
167 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
169 if (pipename_.empty()) {
170 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
175 lyxerr << "LyXComm: Already disconnected" << endl;
179 endPipe(infd_, inPipeName(), false);
180 endPipe(outfd_, outPipeName(), true);
186 int LyXComm::startPipe(string const & file, bool write)
188 FileName const filename(file);
189 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
190 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
191 << "If no other LyX program is active, please delete"
192 " the pipe by hand and try again." << endl;
197 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
198 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
199 << strerror(errno) << endl;
202 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
203 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
206 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
207 << strerror(errno) << endl;
208 filename.removeFile();
213 theApp()->registerSocketCallback(fd,
214 boost::bind(&LyXComm::read_ready, this));
221 void LyXComm::endPipe(int & fd, string const & filename, bool write)
227 theApp()->unregisterSocketCallback(fd);
229 if (::close(fd) < 0) {
230 lyxerr << "LyXComm: Could not close pipe " << filename
231 << '\n' << strerror(errno) << endl;
234 if (FileName(filename).removeFile() < 0) {
235 lyxerr << "LyXComm: Could not remove pipe " << filename
236 << '\n' << strerror(errno) << endl;
243 void LyXComm::emergencyCleanup()
245 if (!pipename_.empty()) {
246 endPipe(infd_, inPipeName(), false);
247 endPipe(outfd_, outPipeName(), true);
252 // Receives messages and sends then to client
253 void LyXComm::read_ready()
255 // FIXME: make read_buffer_ a class-member for multiple sessions
256 static string read_buffer_;
257 read_buffer_.erase();
259 int const charbuf_size = 100;
260 char charbuf[charbuf_size];
264 // the single = is intended here.
265 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
268 charbuf[status] = '\0'; // turn it into a c string
269 read_buffer_ += rtrim(charbuf, "\r");
270 // commit any commands read
271 while (read_buffer_.find('\n') != string::npos) {
272 // split() grabs the entire string if
273 // the delim /wasn't/ found. ?:-P
275 read_buffer_= split(read_buffer_, cmd,'\n');
276 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
277 << ", read_buffer_:" << read_buffer_
280 clientcb_(client_, cmd);
284 if (errno == EAGAIN) {
289 lyxerr << "LyXComm: " << strerror(errno) << endl;
290 if (!read_buffer_.empty()) {
291 lyxerr << "LyXComm: truncated command: "
292 << read_buffer_ << endl;
293 read_buffer_.erase();
295 break; // reset connection
299 // The connection gets reset in errno != EAGAIN
300 // Why does it need to be reset if errno == 0?
307 void LyXComm::send(string const & msg)
310 lyxerr << "LyXComm: Request to send empty string. Ignoring."
315 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
317 if (pipename_.empty()) return;
320 lyxerr << "LyXComm: Pipes are closed. Could not send "
322 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
323 lyxerr << "LyXComm: Error sending message: " << msg
324 << '\n' << strerror(errno)
325 << "\nLyXComm: Resetting connection" << endl;
331 #endif // defined (HAVE_MKFIFO)
334 string const LyXComm::inPipeName() const
336 return pipename_ + ".in";
340 string const LyXComm::outPipeName() const
342 return pipename_ + ".out";
346 /////////////////////////////////////////////////////////////////////
350 /////////////////////////////////////////////////////////////////////
352 void ServerCallback(Server * server, string const & msg)
354 server->callback(msg);
357 Server::Server(LyXFunc * f, std::string const & pipes)
358 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
364 // say goodbye to clients so they stop sending messages
365 // send as many bye messages as there are clients,
366 // each with client's name.
368 for (int i = 0; i != numclients_; ++i) {
369 message = "LYXSRV:" + clients_[i] + ":bye\n";
370 pipes_.send(message);
375 // Handle data gotten from communication, called by LyXComm
376 void Server::callback(string const & msg)
378 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
380 char const * p = msg.c_str();
382 // --- parse the string --------------------------------------------
384 // Format: LYXCMD:<client>:<func>:<argstring>\n
386 bool server_only = false;
388 // --- 1. check 'header' ---
390 if (compare(p, "LYXSRV:", 7) == 0) {
392 } else if (0 != compare(p, "LYXCMD:", 7)) {
393 lyxerr << "Server: Unknown request \""
399 // --- 2. for the moment ignore the client name ---
401 while (*p && *p != ':')
402 client += char(*p++);
408 // --- 3. get function name ---
410 while (*p && *p != ':')
413 // --- 4. parse the argument ---
415 if (!server_only && *p == ':' && *(++p)) {
416 while (*p && *p != '\n')
421 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
422 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
424 // --- lookup and exec the command ------------------
428 // return the greeting to inform the client that
430 if (cmd == "hello") {
432 if (numclients_ == MAX_CLIENTS) { //paranoid check
433 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
437 while (!clients_[i].empty() && i < numclients_)
439 clients_[i] = client;
441 buf = "LYXSRV:" + client + ":hello\n";
442 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
444 } else if (cmd == "bye") {
445 // If clients_ == 0 maybe we should reset the pipes
446 // to prevent fake callbacks
447 int i = 0; //look if client is registered
448 for (; i < numclients_; ++i) {
449 if (clients_[i] == client)
452 if (i < numclients_) {
455 LYXERR(Debug::LYXSERVER, "Server: Client "
456 << client << " said goodbye");
458 LYXERR(Debug::LYXSERVER,
459 "Server: ignoring bye messge from unregistered client" << client);
462 lyxerr <<"Server: Undefined server command "
463 << cmd << '.' << endl;
469 // which lyxfunc should we let it connect to?
470 // The correct solution would be to have a
471 // specialized (non-gui) BufferView. But how do
472 // we do it now? Probably we should just let it
473 // connect to the lyxfunc in the single LyXView we
474 // support currently. (Lgb)
476 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
477 string const rval = to_utf8(func_->getMessage());
479 // all commands produce an INFO or ERROR message
480 // in the output pipe, even if they do not return
481 // anything. See chapter 4 of Customization doc.
483 if (func_->errorStat())
487 buf += client + ':' + cmd + ':' + rval + '\n';
490 // !!! we don't do any error checking -
491 // if the client won't listen, the
492 // message is lost and others too
493 // maybe; so the client should empty
494 // the outpipe before issuing a request.
502 // Send a notify messge to a client, called by WorkAreaKeyPress
503 void Server::notifyClient(string const & s)
505 pipes_.send("NOTIFY:" + s + "\n");