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
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 /////////////////////////////////////////////////////////////////////
78 /////////////////////////////////////////////////////////////////////
80 #if !defined (HAVE_MKFIFO)
81 // We provide a stub class that disables the lyxserver.
83 LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
86 void LyXComm::openConnection()
90 void LyXComm::closeConnection()
94 int LyXComm::startPipe(string const & filename, bool write)
100 void LyXComm::endPipe(int & fd, string const & filename, bool write)
104 void LyXComm::emergencyCleanup()
107 void LyXComm::read_ready()
111 void LyXComm::send(string const & msg)
115 #else // defined (HAVE_MKFIFO)
118 LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
119 : pipename_(pip), client_(cli), clientcb_(ccb)
126 void LyXComm::openConnection()
128 LYXERR(Debug::LYXSERVER) << "LyXComm: Opening connection" << endl;
130 // If we are up, that's an error
132 lyxerr << "LyXComm: Already connected" << endl;
135 // We assume that we don't make it
138 if (pipename_.empty()) {
139 LYXERR(Debug::LYXSERVER)
140 << "LyXComm: server is disabled, nothing to do"
145 infd_ = startPipe(inPipeName(), false);
149 outfd_ = startPipe(outPipeName(), true);
151 endPipe(infd_, inPipeName(), false);
155 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
156 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
157 << '\n' << strerror(errno) << endl;
163 LYXERR(Debug::LYXSERVER) << "LyXComm: Connection established" << endl;
168 void LyXComm::closeConnection()
170 LYXERR(Debug::LYXSERVER) << "LyXComm: Closing connection" << endl;
172 if (pipename_.empty()) {
173 LYXERR(Debug::LYXSERVER)
174 << "LyXComm: server is disabled, nothing to do"
180 lyxerr << "LyXComm: Already disconnected" << endl;
184 endPipe(infd_, inPipeName(), false);
185 endPipe(outfd_, outPipeName(), true);
191 int LyXComm::startPipe(string const & file, bool write)
193 FileName const filename(file);
194 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
195 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
196 << "If no other LyX program is active, please delete"
197 " the pipe by hand and try again." << endl;
202 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
203 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
204 << strerror(errno) << endl;
207 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
208 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
211 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
212 << strerror(errno) << endl;
218 theApp()->registerSocketCallback(fd,
219 boost::bind(&LyXComm::read_ready, this));
226 void LyXComm::endPipe(int & fd, string const & filename, bool write)
232 theApp()->unregisterSocketCallback(fd);
234 if (::close(fd) < 0) {
235 lyxerr << "LyXComm: Could not close pipe " << filename
236 << '\n' << strerror(errno) << endl;
239 if (unlink(FileName(filename)) < 0) {
240 lyxerr << "LyXComm: Could not remove pipe " << filename
241 << '\n' << strerror(errno) << endl;
248 void LyXComm::emergencyCleanup()
250 if (!pipename_.empty()) {
251 endPipe(infd_, inPipeName(), false);
252 endPipe(outfd_, outPipeName(), true);
257 // Receives messages and sends then to client
258 void LyXComm::read_ready()
260 // FIXME: make read_buffer_ a class-member for multiple sessions
261 static string read_buffer_;
262 read_buffer_.erase();
264 int const charbuf_size = 100;
265 char charbuf[charbuf_size];
269 // the single = is intended here.
270 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
273 charbuf[status] = '\0'; // turn it into a c string
274 read_buffer_ += rtrim(charbuf, "\r");
275 // commit any commands read
276 while (read_buffer_.find('\n') != string::npos) {
277 // split() grabs the entire string if
278 // the delim /wasn't/ found. ?:-P
280 read_buffer_= split(read_buffer_, cmd,'\n');
281 LYXERR(Debug::LYXSERVER)
282 << "LyXComm: status:" << status
283 << ", read_buffer_:" << read_buffer_
284 << ", cmd:" << cmd << endl;
286 clientcb_(client_, cmd);
290 if (errno == EAGAIN) {
295 lyxerr << "LyXComm: " << strerror(errno) << endl;
296 if (!read_buffer_.empty()) {
297 lyxerr << "LyXComm: truncated command: "
298 << read_buffer_ << endl;
299 read_buffer_.erase();
301 break; // reset connection
305 // The connection gets reset in errno != EAGAIN
306 // Why does it need to be reset if errno == 0?
313 void LyXComm::send(string const & msg)
316 lyxerr << "LyXComm: Request to send empty string. Ignoring."
321 LYXERR(Debug::LYXSERVER) << "LyXComm: Sending '" << msg << '\'' << endl;
323 if (pipename_.empty()) return;
326 lyxerr << "LyXComm: Pipes are closed. Could not send "
328 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
329 lyxerr << "LyXComm: Error sending message: " << msg
330 << '\n' << strerror(errno)
331 << "\nLyXComm: Resetting connection" << endl;
337 #endif // defined (HAVE_MKFIFO)
340 string const LyXComm::inPipeName() const
342 return pipename_ + ".in";
346 string const LyXComm::outPipeName() const
348 return pipename_ + ".out";
352 /////////////////////////////////////////////////////////////////////
356 /////////////////////////////////////////////////////////////////////
358 void ServerCallback(Server * server, string const & msg)
360 server->callback(msg);
363 Server::Server(LyXFunc * f, std::string const & pipes)
364 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
370 // say goodbye to clients so they stop sending messages
371 // send as many bye messages as there are clients,
372 // each with client's name.
374 for (int i = 0; i != numclients_; ++i) {
375 message = "LYXSRV:" + clients_[i] + ":bye\n";
376 pipes_.send(message);
381 // Handle data gotten from communication, called by LyXComm
382 void Server::callback(string const & msg)
384 LYXERR(Debug::LYXSERVER) << "Server: Received: '"
385 << msg << '\'' << endl;
387 char const * p = msg.c_str();
389 // --- parse the string --------------------------------------------
391 // Format: LYXCMD:<client>:<func>:<argstring>\n
393 bool server_only = false;
395 // --- 1. check 'header' ---
397 if (compare(p, "LYXSRV:", 7) == 0) {
399 } else if (0 != compare(p, "LYXCMD:", 7)) {
400 lyxerr << "Server: Unknown request \""
406 // --- 2. for the moment ignore the client name ---
408 while (*p && *p != ':')
409 client += char(*p++);
415 // --- 3. get function name ---
417 while (*p && *p != ':')
420 // --- 4. parse the argument ---
422 if (!server_only && *p == ':' && *(++p)) {
423 while (*p && *p != '\n')
428 LYXERR(Debug::LYXSERVER)
429 << "Server: Client: '" << client
430 << "' Command: '" << cmd
431 << "' Argument: '" << arg << '\'' << endl;
433 // --- lookup and exec the command ------------------
437 // return the greeting to inform the client that
439 if (cmd == "hello") {
441 if (numclients_ == MAX_CLIENTS) { //paranoid check
442 LYXERR(Debug::LYXSERVER)
443 << "Server: too many clients..."
448 while (!clients_[i].empty() && i < numclients_)
450 clients_[i] = client;
452 buf = "LYXSRV:" + client + ":hello\n";
453 LYXERR(Debug::LYXSERVER)
454 << "Server: Greeting "
457 } else if (cmd == "bye") {
458 // If clients_ == 0 maybe we should reset the pipes
459 // to prevent fake callbacks
460 int i = 0; //look if client is registered
461 for (; i < numclients_; ++i) {
462 if (clients_[i] == client)
465 if (i < numclients_) {
468 LYXERR(Debug::LYXSERVER)
470 << client << " said goodbye"
473 LYXERR(Debug::LYXSERVER)
474 << "Server: ignoring bye messge from unregistered client"
478 lyxerr <<"Server: Undefined server command "
479 << cmd << '.' << endl;
485 // which lyxfunc should we let it connect to?
486 // The correct solution would be to have a
487 // specialized (non-gui) BufferView. But how do
488 // we do it now? Probably we should just let it
489 // connect to the lyxfunc in the single LyXView we
490 // support currently. (Lgb)
492 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
493 string const rval = to_utf8(func_->getMessage());
495 // all commands produce an INFO or ERROR message
496 // in the output pipe, even if they do not return
497 // anything. See chapter 4 of Customization doc.
499 if (func_->errorStat())
503 buf += client + ':' + cmd + ':' + rval + '\n';
506 // !!! we don't do any error checking -
507 // if the client won't listen, the
508 // message is lost and others too
509 // maybe; so the client should empty
510 // the outpipe before issuing a request.
518 // Send a notify messge to a client, called by WorkAreaKeyPress
519 void Server::notifyClient(string const & s)
521 pipes_.send("NOTIFY:" + s + "\n");