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"
45 #include "support/lstrings.h"
46 #include "support/lyxlib.h"
47 #include "frontends/lyx_gui.h"
56 #define OS2EMX_PLAIN_CHAR
57 #define INCL_DOSNMPIPES
58 #define INCL_DOSERRORS
60 #include "support/os2_errortable.h"
63 using lyx::support::compare;
64 using lyx::support::rtrim;
65 using lyx::support::split;
66 using lyx::support::unlink;
72 // provide an empty mkfifo() if we do not have one. This disables the
75 int mkfifo(char const * __path, mode_t __mode) {
81 void LyXComm::openConnection()
83 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
85 // If we are up, that's an error
87 lyxerr << "LyXComm: Already connected" << endl;
90 // We assume that we don't make it
93 if (pipename.empty()) {
94 lyxerr[Debug::LYXSERVER]
95 << "LyXComm: server is disabled, nothing to do"
100 if ((infd = startPipe(inPipeName(), false)) == -1)
103 if ((outfd = startPipe(outPipeName(), true)) == -1) {
104 endPipe(infd, inPipeName(), false);
108 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
109 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
110 << '\n' << strerror(errno) << endl;
116 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
121 void LyXComm::closeConnection()
123 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
125 if (pipename.empty()) {
126 lyxerr[Debug::LYXSERVER]
127 << "LyXComm: server is disabled, nothing to do"
133 lyxerr << "LyXComm: Already disconnected" << endl;
137 endPipe(infd, inPipeName(), false);
138 endPipe(outfd, outPipeName(), true);
144 int LyXComm::startPipe(string const & filename, bool write)
152 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
153 // The current emx implementation of access() won't work with pipes.
154 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
155 NP_NOWAIT|0x01, 0600, 0600, 0);
156 if (rc == ERROR_PIPE_BUSY) {
157 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
158 << "If no other LyX program is active, please delete"
159 " the pipe by hand and try again." << endl;
164 if (rc != NO_ERROR) {
165 errnum = TranslateOS2Error(rc);
166 lyxerr <<"LyXComm: Could not create pipe " << filename
167 << strerror(errnum) << endl;
171 rc = DosConnectNPipe(os2fd);
172 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
173 errnum = TranslateOS2Error(rc);
174 lyxerr <<"LyXComm: Could not create pipe " << filename
175 << strerror(errnum) << endl;
178 // Imported handles can be used both with OS/2 APIs and emx
179 // library functions.
180 fd = _imphandle(os2fd);
182 if (::access(filename.c_str(), F_OK) == 0) {
183 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
184 << "If no other LyX program is active, please delete"
185 " the pipe by hand and try again." << endl;
190 if (::mkfifo(filename.c_str(), 0600) < 0) {
191 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
192 << strerror(errno) << endl;
195 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
199 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
200 << strerror(errno) << endl;
206 lyx_gui::set_read_callback(fd, this);
213 void LyXComm::endPipe(int & fd, string const & filename, bool write)
219 lyx_gui::remove_read_callback(fd);
226 rc = DosDisConnectNPipe(fd);
227 if (rc != NO_ERROR) {
228 errnum = TranslateOS2Error(rc);
229 lyxerr << "LyXComm: Could not disconnect pipe " << filename
230 << '\n' << strerror(errnum) << endl;
235 if (::close(fd) < 0) {
236 lyxerr << "LyXComm: Could not close pipe " << filename
237 << '\n' << strerror(errno) << endl;
240 // OS/2 pipes are deleted automatically
242 if (unlink(filename) < 0) {
243 lyxerr << "LyXComm: Could not remove pipe " << filename
244 << '\n' << strerror(errno) << endl;
252 void LyXComm::emergencyCleanup()
254 if (!pipename.empty()) {
255 endPipe(infd, inPipeName(), false);
256 endPipe(outfd, outPipeName(), true);
261 // Receives messages and sends then to client
262 void LyXComm::read_ready()
264 // nb! make read_buffer_ a class-member for multiple sessions
265 static string read_buffer_;
266 read_buffer_.erase();
268 int const charbuf_size = 100;
269 char charbuf[charbuf_size];
273 // the single = is intended here.
274 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
277 charbuf[status] = '\0'; // turn it into a c string
278 read_buffer_ += rtrim(charbuf, "\r");
279 // commit any commands read
280 while (read_buffer_.find('\n') != string::npos) {
281 // split() grabs the entire string if
282 // the delim /wasn't/ found. ?:-P
284 read_buffer_= split(read_buffer_, cmd,'\n');
285 lyxerr[Debug::LYXSERVER]
286 << "LyXComm: status:" << status
287 << ", read_buffer_:" << read_buffer_
288 << ", cmd:" << cmd << endl;
290 clientcb(client, cmd);
294 if (errno == EAGAIN) {
299 lyxerr << "LyXComm: " << strerror(errno) << endl;
300 if (!read_buffer_.empty()) {
301 lyxerr << "LyXComm: truncated command: "
302 << read_buffer_ << endl;
303 read_buffer_.erase();
305 break; // reset connection
309 // The connection gets reset in errno != EAGAIN
310 // Why does it need to be reset if errno == 0?
317 void LyXComm::send(string const & msg)
320 lyxerr << "LyXComm: Request to send empty string. Ignoring."
325 if (lyxerr.debugging(Debug::LYXSERVER)) {
326 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
329 if (pipename.empty()) return;
332 lyxerr << "LyXComm: Pipes are closed. Could not send "
334 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
335 lyxerr << "LyXComm: Error sending message: " << msg
336 << '\n' << strerror(errno)
337 << "\nLyXComm: Resetting connection" << endl;
344 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
345 if (rc != NO_ERROR) {
346 errnum = TranslateOS2Error(rc);
347 lyxerr << "LyXComm: Message could not be flushed: " << msg
348 << '\n' << strerror(errnum) << endl;
356 LyXServer::~LyXServer()
358 // say goodbye to clients so they stop sending messages
359 // modified june 1999 by stefano@zool.su.se to send as many bye
360 // messages as there are clients, each with client's name.
362 for (int i= 0; i<numclients; ++i) {
363 message = "LYXSRV:" + clients[i] + ":bye\n";
369 /* ---F+------------------------------------------------------------------ *\
370 Function : ServerCallback
372 Purpose : handle data gotten from communication
373 \* ---F------------------------------------------------------------------- */
375 void LyXServer::callback(LyXServer * serv, string const & msg)
377 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
378 << msg << '\'' << endl;
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 << "LyXServer: Unknown request \""
399 // --- 2. for the moment ignore the client name ---
401 while (*p && *p != ':')
402 client += char(*p++);
406 // --- 3. get function name ---
408 while (*p && *p != ':')
411 // --- 4. parse the argument ---
413 if (!server_only && *p == ':' && *(++p)) {
414 while (*p && *p != '\n')
419 lyxerr[Debug::LYXSERVER]
420 << "LyXServer: Client: '" << client
421 << "' Command: '" << cmd
422 << "' Argument: '" << arg << '\'' << endl;
424 // --- lookup and exec the command ------------------
428 // return the greeting to inform the client that
430 if (cmd == "hello") {
432 if (serv->numclients == MAX_CLIENTS) { //paranoid check
433 lyxerr[Debug::LYXSERVER]
434 << "LyXServer: too many clients..."
438 int i= 0; //find place in clients[]
439 while (!serv->clients[i].empty()
440 && i<serv->numclients)
442 serv->clients[i] = client;
444 buf = "LYXSRV:" + client + ":hello\n";
445 lyxerr[Debug::LYXSERVER]
446 << "LyXServer: Greeting "
448 serv->pipes.send(buf);
449 } else if (cmd == "bye") {
450 // If clients == 0 maybe we should reset the pipes
451 // to prevent fake callbacks
452 int i = 0; //look if client is registered
453 for (; i < serv->numclients; ++i) {
454 if (serv->clients[i] == client) break;
456 if (i < serv->numclients) {
458 serv->clients[i].erase();
459 lyxerr[Debug::LYXSERVER]
460 << "LyXServer: Client "
461 << client << " said goodbye"
464 lyxerr[Debug::LYXSERVER]
465 << "LyXServer: ignoring bye messge from unregistered client"
469 lyxerr <<"LyXServer: Undefined server command "
470 << cmd << '.' << endl;
476 // which lyxfunc should we let it connect to?
477 // The correct solution would be to have a
478 // specialized (non-gui) BufferView. But how do
479 // we do it now? Probably we should just let it
480 // connect to the lyxfunc in the single LyXView we
481 // support currently. (Lgb)
484 serv->func->dispatch(cmd + ' ' + arg);
485 string const rval = serv->func->getMessage();
487 //modified june 1999 stefano@zool.su.se:
488 //all commands produce an INFO or ERROR message
489 //in the output pipe, even if they do not return
490 //anything. See chapter 4 of Customization doc.
492 if (serv->func->errorStat())
496 buf += client + ':' + cmd + ':' + rval + '\n';
497 serv->pipes.send(buf);
499 // !!! we don't do any error checking -
500 // if the client won't listen, the
501 // message is lost and others too
502 // maybe; so the client should empty
503 // the outpipe before issuing a request.
511 /* ---F+------------------------------------------------------------------ *\
512 Function : LyXNotifyClient
513 Called by : WorkAreaKeyPress
514 Purpose : send a notify messge to a client
515 Parameters: s - string to send
517 \* ---F------------------------------------------------------------------- */
519 void LyXServer::notifyClient(string const & s)
521 string buf = string("NOTIFY:") + s + "\n";