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 "support/lstrings.h"
48 #include "support/lyxlib.h"
49 #include "frontends/lyx_gui.h"
51 #include <boost/bind.hpp>
54 #ifdef HAVE_SYS_STAT_H
55 # include <sys/stat.h>
62 # define OS2EMX_PLAIN_CHAR
63 # define INCL_DOSNMPIPES
64 # define INCL_DOSERRORS
66 # include "support/os2_errortable.h"
69 using lyx::support::compare;
70 using lyx::support::rtrim;
71 using lyx::support::split;
72 using lyx::support::unlink;
78 // provide an empty mkfifo() if we do not have one. This disables the
81 int mkfifo(char const * __path, mode_t __mode) {
87 void LyXComm::openConnection()
89 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
91 // If we are up, that's an error
93 lyxerr << "LyXComm: Already connected" << endl;
96 // We assume that we don't make it
99 if (pipename.empty()) {
100 lyxerr[Debug::LYXSERVER]
101 << "LyXComm: server is disabled, nothing to do"
106 if ((infd = startPipe(inPipeName(), false)) == -1)
109 if ((outfd = startPipe(outPipeName(), true)) == -1) {
110 endPipe(infd, inPipeName(), false);
114 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
115 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
116 << '\n' << strerror(errno) << endl;
122 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
127 void LyXComm::closeConnection()
129 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
131 if (pipename.empty()) {
132 lyxerr[Debug::LYXSERVER]
133 << "LyXComm: server is disabled, nothing to do"
139 lyxerr << "LyXComm: Already disconnected" << endl;
143 endPipe(infd, inPipeName(), false);
144 endPipe(outfd, outPipeName(), true);
150 int LyXComm::startPipe(string const & filename, bool write)
156 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
157 // The current emx implementation of access() won't work with pipes.
158 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
159 NP_NOWAIT|0x01, 0600, 0600, 0);
160 if (rc == ERROR_PIPE_BUSY) {
161 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
162 << "If no other LyX program is active, please delete"
163 " the pipe by hand and try again." << endl;
168 if (rc != NO_ERROR) {
169 errnum = TranslateOS2Error(rc);
170 lyxerr <<"LyXComm: Could not create pipe " << filename
171 << strerror(errnum) << endl;
175 rc = DosConnectNPipe(os2fd);
176 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
177 errnum = TranslateOS2Error(rc);
178 lyxerr <<"LyXComm: Could not create pipe " << filename
179 << strerror(errnum) << endl;
182 // Imported handles can be used both with OS/2 APIs and emx
183 // library functions.
184 int const fd = _imphandle(os2fd);
186 if (::access(filename.c_str(), F_OK) == 0) {
187 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
188 << "If no other LyX program is active, please delete"
189 " the pipe by hand and try again." << endl;
194 if (::mkfifo(filename.c_str(), 0600) < 0) {
195 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
196 << strerror(errno) << endl;
199 int const fd = ::open(filename.c_str(),
200 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
204 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
205 << strerror(errno) << endl;
211 lyx_gui::register_socket_callback(fd, boost::bind(&LyXComm::read_ready, this));
218 void LyXComm::endPipe(int & fd, string const & filename, bool write)
224 lyx_gui::unregister_socket_callback(fd);
231 rc = DosDisConnectNPipe(fd);
232 if (rc != NO_ERROR) {
233 errnum = TranslateOS2Error(rc);
234 lyxerr << "LyXComm: Could not disconnect pipe " << filename
235 << '\n' << strerror(errnum) << endl;
240 if (::close(fd) < 0) {
241 lyxerr << "LyXComm: Could not close pipe " << filename
242 << '\n' << strerror(errno) << endl;
245 // OS/2 pipes are deleted automatically
247 if (unlink(filename) < 0) {
248 lyxerr << "LyXComm: Could not remove pipe " << filename
249 << '\n' << strerror(errno) << endl;
257 void LyXComm::emergencyCleanup()
259 if (!pipename.empty()) {
260 endPipe(infd, inPipeName(), false);
261 endPipe(outfd, outPipeName(), true);
266 // Receives messages and sends then to client
267 void LyXComm::read_ready()
269 // nb! make read_buffer_ a class-member for multiple sessions
270 static string read_buffer_;
271 read_buffer_.erase();
273 int const charbuf_size = 100;
274 char charbuf[charbuf_size];
278 // the single = is intended here.
279 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
282 charbuf[status] = '\0'; // turn it into a c string
283 read_buffer_ += rtrim(charbuf, "\r");
284 // commit any commands read
285 while (read_buffer_.find('\n') != string::npos) {
286 // split() grabs the entire string if
287 // the delim /wasn't/ found. ?:-P
289 read_buffer_= split(read_buffer_, cmd,'\n');
290 lyxerr[Debug::LYXSERVER]
291 << "LyXComm: status:" << status
292 << ", read_buffer_:" << read_buffer_
293 << ", cmd:" << cmd << endl;
295 clientcb(client, cmd);
299 if (errno == EAGAIN) {
304 lyxerr << "LyXComm: " << strerror(errno) << endl;
305 if (!read_buffer_.empty()) {
306 lyxerr << "LyXComm: truncated command: "
307 << read_buffer_ << endl;
308 read_buffer_.erase();
310 break; // reset connection
314 // The connection gets reset in errno != EAGAIN
315 // Why does it need to be reset if errno == 0?
322 void LyXComm::send(string const & msg)
325 lyxerr << "LyXComm: Request to send empty string. Ignoring."
330 if (lyxerr.debugging(Debug::LYXSERVER)) {
331 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
334 if (pipename.empty()) return;
337 lyxerr << "LyXComm: Pipes are closed. Could not send "
339 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
340 lyxerr << "LyXComm: Error sending message: " << msg
341 << '\n' << strerror(errno)
342 << "\nLyXComm: Resetting connection" << endl;
349 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
350 if (rc != NO_ERROR) {
351 errnum = TranslateOS2Error(rc);
352 lyxerr << "LyXComm: Message could not be flushed: " << msg
353 << '\n' << strerror(errnum) << endl;
359 string const LyXComm::inPipeName() const
361 return pipename + string(".in");
365 string const LyXComm::outPipeName() const
367 return pipename + string(".out");
373 LyXServer::~LyXServer()
375 // say goodbye to clients so they stop sending messages
376 // modified june 1999 by stefano@zool.su.se to send as many bye
377 // messages as there are clients, each with client's name.
379 for (int i= 0; i<numclients; ++i) {
380 message = "LYXSRV:" + clients[i] + ":bye\n";
386 /* ---F+------------------------------------------------------------------ *\
387 Function : ServerCallback
389 Purpose : handle data gotten from communication
390 \* ---F------------------------------------------------------------------- */
392 void LyXServer::callback(LyXServer * serv, string const & msg)
394 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
395 << msg << '\'' << endl;
397 char const * p = msg.c_str();
399 // --- parse the string --------------------------------------------
401 // Format: LYXCMD:<client>:<func>:<argstring>\n
403 bool server_only = false;
405 // --- 1. check 'header' ---
407 if (compare(p, "LYXSRV:", 7) == 0) {
409 } else if (0 != compare(p, "LYXCMD:", 7)) {
410 lyxerr << "LyXServer: Unknown request \""
416 // --- 2. for the moment ignore the client name ---
418 while (*p && *p != ':')
419 client += char(*p++);
423 // --- 3. get function name ---
425 while (*p && *p != ':')
428 // --- 4. parse the argument ---
430 if (!server_only && *p == ':' && *(++p)) {
431 while (*p && *p != '\n')
436 lyxerr[Debug::LYXSERVER]
437 << "LyXServer: Client: '" << client
438 << "' Command: '" << cmd
439 << "' Argument: '" << arg << '\'' << endl;
441 // --- lookup and exec the command ------------------
445 // return the greeting to inform the client that
447 if (cmd == "hello") {
449 if (serv->numclients == MAX_CLIENTS) { //paranoid check
450 lyxerr[Debug::LYXSERVER]
451 << "LyXServer: too many clients..."
455 int i= 0; //find place in clients[]
456 while (!serv->clients[i].empty()
457 && i<serv->numclients)
459 serv->clients[i] = client;
461 buf = "LYXSRV:" + client + ":hello\n";
462 lyxerr[Debug::LYXSERVER]
463 << "LyXServer: Greeting "
465 serv->pipes.send(buf);
466 } else if (cmd == "bye") {
467 // If clients == 0 maybe we should reset the pipes
468 // to prevent fake callbacks
469 int i = 0; //look if client is registered
470 for (; i < serv->numclients; ++i) {
471 if (serv->clients[i] == client) break;
473 if (i < serv->numclients) {
475 serv->clients[i].erase();
476 lyxerr[Debug::LYXSERVER]
477 << "LyXServer: Client "
478 << client << " said goodbye"
481 lyxerr[Debug::LYXSERVER]
482 << "LyXServer: ignoring bye messge from unregistered client"
486 lyxerr <<"LyXServer: Undefined server command "
487 << cmd << '.' << endl;
493 // which lyxfunc should we let it connect to?
494 // The correct solution would be to have a
495 // specialized (non-gui) BufferView. But how do
496 // we do it now? Probably we should just let it
497 // connect to the lyxfunc in the single LyXView we
498 // support currently. (Lgb)
501 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
502 string const rval = serv->func->getMessage();
504 //modified june 1999 stefano@zool.su.se:
505 //all commands produce an INFO or ERROR message
506 //in the output pipe, even if they do not return
507 //anything. See chapter 4 of Customization doc.
509 if (serv->func->errorStat())
513 buf += client + ':' + cmd + ':' + rval + '\n';
514 serv->pipes.send(buf);
516 // !!! we don't do any error checking -
517 // if the client won't listen, the
518 // message is lost and others too
519 // maybe; so the client should empty
520 // the outpipe before issuing a request.
528 /* ---F+------------------------------------------------------------------ *\
529 Function : LyXNotifyClient
530 Called by : WorkAreaKeyPress
531 Purpose : send a notify messge to a client
532 Parameters: s - string to send
534 \* ---F------------------------------------------------------------------- */
536 void LyXServer::notifyClient(string const & s)
538 string buf = string("NOTIFY:") + s + "\n";