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"
58 #define OS2EMX_PLAIN_CHAR
59 #define INCL_DOSNMPIPES
60 #define INCL_DOSERRORS
62 #include "support/os2_errortable.h"
65 using lyx::support::compare;
66 using lyx::support::rtrim;
67 using lyx::support::split;
68 using lyx::support::unlink;
74 // provide an empty mkfifo() if we do not have one. This disables the
77 int mkfifo(char const * __path, mode_t __mode) {
83 void LyXComm::openConnection()
85 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
87 // If we are up, that's an error
89 lyxerr << "LyXComm: Already connected" << endl;
92 // We assume that we don't make it
95 if (pipename.empty()) {
96 lyxerr[Debug::LYXSERVER]
97 << "LyXComm: server is disabled, nothing to do"
102 if ((infd = startPipe(inPipeName(), false)) == -1)
105 if ((outfd = startPipe(outPipeName(), true)) == -1) {
106 endPipe(infd, inPipeName(), false);
110 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
111 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
112 << '\n' << strerror(errno) << endl;
118 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
123 void LyXComm::closeConnection()
125 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
127 if (pipename.empty()) {
128 lyxerr[Debug::LYXSERVER]
129 << "LyXComm: server is disabled, nothing to do"
135 lyxerr << "LyXComm: Already disconnected" << endl;
139 endPipe(infd, inPipeName(), false);
140 endPipe(outfd, outPipeName(), true);
146 int LyXComm::startPipe(string const & filename, bool write)
154 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
155 // The current emx implementation of access() won't work with pipes.
156 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
157 NP_NOWAIT|0x01, 0600, 0600, 0);
158 if (rc == ERROR_PIPE_BUSY) {
159 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
160 << "If no other LyX program is active, please delete"
161 " the pipe by hand and try again." << endl;
166 if (rc != NO_ERROR) {
167 errnum = TranslateOS2Error(rc);
168 lyxerr <<"LyXComm: Could not create pipe " << filename
169 << strerror(errnum) << endl;
173 rc = DosConnectNPipe(os2fd);
174 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
175 errnum = TranslateOS2Error(rc);
176 lyxerr <<"LyXComm: Could not create pipe " << filename
177 << strerror(errnum) << endl;
180 // Imported handles can be used both with OS/2 APIs and emx
181 // library functions.
182 fd = _imphandle(os2fd);
184 if (::access(filename.c_str(), F_OK) == 0) {
185 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
186 << "If no other LyX program is active, please delete"
187 " the pipe by hand and try again." << endl;
192 if (::mkfifo(filename.c_str(), 0600) < 0) {
193 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
194 << strerror(errno) << endl;
197 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
201 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
202 << strerror(errno) << endl;
208 lyx_gui::set_read_callback(fd, this);
215 void LyXComm::endPipe(int & fd, string const & filename, bool write)
221 lyx_gui::remove_read_callback(fd);
228 rc = DosDisConnectNPipe(fd);
229 if (rc != NO_ERROR) {
230 errnum = TranslateOS2Error(rc);
231 lyxerr << "LyXComm: Could not disconnect pipe " << filename
232 << '\n' << strerror(errnum) << endl;
237 if (::close(fd) < 0) {
238 lyxerr << "LyXComm: Could not close pipe " << filename
239 << '\n' << strerror(errno) << endl;
242 // OS/2 pipes are deleted automatically
244 if (unlink(filename) < 0) {
245 lyxerr << "LyXComm: Could not remove pipe " << filename
246 << '\n' << strerror(errno) << endl;
254 void LyXComm::emergencyCleanup()
256 if (!pipename.empty()) {
257 endPipe(infd, inPipeName(), false);
258 endPipe(outfd, outPipeName(), true);
263 // Receives messages and sends then to client
264 void LyXComm::read_ready()
266 // nb! make read_buffer_ a class-member for multiple sessions
267 static string read_buffer_;
268 read_buffer_.erase();
270 int const charbuf_size = 100;
271 char charbuf[charbuf_size];
275 // the single = is intended here.
276 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
279 charbuf[status] = '\0'; // turn it into a c string
280 read_buffer_ += rtrim(charbuf, "\r");
281 // commit any commands read
282 while (read_buffer_.find('\n') != string::npos) {
283 // split() grabs the entire string if
284 // the delim /wasn't/ found. ?:-P
286 read_buffer_= split(read_buffer_, cmd,'\n');
287 lyxerr[Debug::LYXSERVER]
288 << "LyXComm: status:" << status
289 << ", read_buffer_:" << read_buffer_
290 << ", cmd:" << cmd << endl;
292 clientcb(client, cmd);
296 if (errno == EAGAIN) {
301 lyxerr << "LyXComm: " << strerror(errno) << endl;
302 if (!read_buffer_.empty()) {
303 lyxerr << "LyXComm: truncated command: "
304 << read_buffer_ << endl;
305 read_buffer_.erase();
307 break; // reset connection
311 // The connection gets reset in errno != EAGAIN
312 // Why does it need to be reset if errno == 0?
319 void LyXComm::send(string const & msg)
322 lyxerr << "LyXComm: Request to send empty string. Ignoring."
327 if (lyxerr.debugging(Debug::LYXSERVER)) {
328 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
331 if (pipename.empty()) return;
334 lyxerr << "LyXComm: Pipes are closed. Could not send "
336 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
337 lyxerr << "LyXComm: Error sending message: " << msg
338 << '\n' << strerror(errno)
339 << "\nLyXComm: Resetting connection" << endl;
346 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
347 if (rc != NO_ERROR) {
348 errnum = TranslateOS2Error(rc);
349 lyxerr << "LyXComm: Message could not be flushed: " << msg
350 << '\n' << strerror(errnum) << endl;
358 LyXServer::~LyXServer()
360 // say goodbye to clients so they stop sending messages
361 // modified june 1999 by stefano@zool.su.se to send as many bye
362 // messages as there are clients, each with client's name.
364 for (int i= 0; i<numclients; ++i) {
365 message = "LYXSRV:" + clients[i] + ":bye\n";
371 /* ---F+------------------------------------------------------------------ *\
372 Function : ServerCallback
374 Purpose : handle data gotten from communication
375 \* ---F------------------------------------------------------------------- */
377 void LyXServer::callback(LyXServer * serv, string const & msg)
379 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
380 << msg << '\'' << endl;
382 char const * p = msg.c_str();
384 // --- parse the string --------------------------------------------
386 // Format: LYXCMD:<client>:<func>:<argstring>\n
388 bool server_only = false;
390 // --- 1. check 'header' ---
392 if (compare(p, "LYXSRV:", 7) == 0) {
394 } else if (0 != compare(p, "LYXCMD:", 7)) {
395 lyxerr << "LyXServer: Unknown request \""
401 // --- 2. for the moment ignore the client name ---
403 while (*p && *p != ':')
404 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]
422 << "LyXServer: Client: '" << client
423 << "' Command: '" << cmd
424 << "' Argument: '" << arg << '\'' << endl;
426 // --- lookup and exec the command ------------------
430 // return the greeting to inform the client that
432 if (cmd == "hello") {
434 if (serv->numclients == MAX_CLIENTS) { //paranoid check
435 lyxerr[Debug::LYXSERVER]
436 << "LyXServer: too many clients..."
440 int i= 0; //find place in clients[]
441 while (!serv->clients[i].empty()
442 && i<serv->numclients)
444 serv->clients[i] = client;
446 buf = "LYXSRV:" + client + ":hello\n";
447 lyxerr[Debug::LYXSERVER]
448 << "LyXServer: Greeting "
450 serv->pipes.send(buf);
451 } else if (cmd == "bye") {
452 // If clients == 0 maybe we should reset the pipes
453 // to prevent fake callbacks
454 int i = 0; //look if client is registered
455 for (; i < serv->numclients; ++i) {
456 if (serv->clients[i] == client) break;
458 if (i < serv->numclients) {
460 serv->clients[i].erase();
461 lyxerr[Debug::LYXSERVER]
462 << "LyXServer: Client "
463 << client << " said goodbye"
466 lyxerr[Debug::LYXSERVER]
467 << "LyXServer: ignoring bye messge from unregistered client"
471 lyxerr <<"LyXServer: Undefined server command "
472 << cmd << '.' << endl;
478 // which lyxfunc should we let it connect to?
479 // The correct solution would be to have a
480 // specialized (non-gui) BufferView. But how do
481 // we do it now? Probably we should just let it
482 // connect to the lyxfunc in the single LyXView we
483 // support currently. (Lgb)
486 serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
487 string const rval = serv->func->getMessage();
489 //modified june 1999 stefano@zool.su.se:
490 //all commands produce an INFO or ERROR message
491 //in the output pipe, even if they do not return
492 //anything. See chapter 4 of Customization doc.
494 if (serv->func->errorStat())
498 buf += client + ':' + cmd + ':' + rval + '\n';
499 serv->pipes.send(buf);
501 // !!! we don't do any error checking -
502 // if the client won't listen, the
503 // message is lost and others too
504 // maybe; so the client should empty
505 // the outpipe before issuing a request.
513 /* ---F+------------------------------------------------------------------ *\
514 Function : LyXNotifyClient
515 Called by : WorkAreaKeyPress
516 Purpose : send a notify messge to a client
517 Parameters: s - string to send
519 \* ---F------------------------------------------------------------------- */
521 void LyXServer::notifyClient(string const & s)
523 string buf = string("NOTIFY:") + s + "\n";