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 "lyxserver.h"
47 #include "support/lstrings.h"
48 #include "support/lyxlib.h"
49 #include "frontends/lyx_gui.h"
52 #define OS2EMX_PLAIN_CHAR
53 #define INCL_DOSNMPIPES
54 #define INCL_DOSERRORS
58 using namespace lyx::support;
62 // provide an empty mkfifo() if we do not have one. This disables the
65 int mkfifo(char const * __path, mode_t __mode) {
71 void LyXComm::openConnection()
73 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
75 // If we are up, that's an error
77 lyxerr << "LyXComm: Already connected" << endl;
80 // We assume that we don't make it
83 if (pipename.empty()) {
84 lyxerr[Debug::LYXSERVER]
85 << "LyXComm: server is disabled, nothing to do"
90 if ((infd = startPipe(inPipeName(), false)) == -1)
93 if ((outfd = startPipe(outPipeName(), true)) == -1) {
94 endPipe(infd, inPipeName(), false);
98 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
99 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
100 << '\n' << strerror(errno) << endl;
106 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
111 void LyXComm::closeConnection()
113 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
115 if (pipename.empty()) {
116 lyxerr[Debug::LYXSERVER]
117 << "LyXComm: server is disabled, nothing to do"
123 lyxerr << "LyXComm: Already disconnected" << endl;
127 endPipe(infd, inPipeName(), false);
128 endPipe(outfd, outPipeName(), true);
133 int LyXComm::startPipe(string const & filename, bool write)
141 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
142 // The current emx implementation of access() won't work with pipes.
143 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
144 NP_NOWAIT|0x01, 0600, 0600, 0);
145 if (rc == ERROR_PIPE_BUSY) {
146 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
147 << "If no other LyX program is active, please delete"
148 " the pipe by hand and try again." << endl;
153 if (rc != NO_ERROR) {
154 errnum = TranslateOS2Error(rc);
155 lyxerr <<"LyXComm: Could not create pipe " << filename
156 << strerror(errnum) << endl;
160 rc = DosConnectNPipe(os2fd);
161 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
162 errnum = TranslateOS2Error(rc);
163 lyxerr <<"LyXComm: Could not create pipe " << filename
164 << strerror(errnum) << endl;
167 // Imported handles can be used both with OS/2 APIs and emx
168 // library functions.
169 fd = _imphandle(os2fd);
171 if (::access(filename.c_str(), F_OK) == 0) {
172 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
173 << "If no other LyX program is active, please delete"
174 " the pipe by hand and try again." << endl;
179 if (::mkfifo(filename.c_str(), 0600) < 0) {
180 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
181 << strerror(errno) << endl;
184 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
188 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
189 << strerror(errno) << endl;
195 lyx_gui::set_read_callback(fd, this);
202 void LyXComm::endPipe(int & fd, string const & filename, bool write)
208 lyx_gui::remove_read_callback(fd);
215 rc = DosDisConnectNPipe(fd);
216 if (rc != NO_ERROR) {
217 errnum = TranslateOS2Error(rc);
218 lyxerr << "LyXComm: Could not disconnect pipe " << filename
219 << '\n' << strerror(errnum) << endl;
224 if (::close(fd) < 0) {
225 lyxerr << "LyXComm: Could not close pipe " << filename
226 << '\n' << strerror(errno) << endl;
229 // OS/2 pipes are deleted automatically
231 if (unlink(filename) < 0) {
232 lyxerr << "LyXComm: Could not remove pipe " << filename
233 << '\n' << strerror(errno) << endl;
241 void LyXComm::emergencyCleanup()
243 if (!pipename.empty()) {
244 endPipe(infd, inPipeName(), false);
245 endPipe(outfd, outPipeName(), true);
250 // Receives messages and sends then to client
251 void LyXComm::read_ready()
253 // nb! make read_buffer_ a class-member for multiple sessions
254 static string read_buffer_;
255 read_buffer_.erase();
257 int const charbuf_size = 100;
258 char charbuf[charbuf_size];
262 // the single = is intended here.
263 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
266 charbuf[status] = '\0'; // turn it into a c string
267 read_buffer_ += rtrim(charbuf, "\r");
268 // commit any commands read
269 while (read_buffer_.find('\n') != string::npos) {
270 // split() grabs the entire string if
271 // the delim /wasn't/ found. ?:-P
273 read_buffer_= split(read_buffer_, cmd,'\n');
274 lyxerr[Debug::LYXSERVER]
275 << "LyXComm: status:" << status
276 << ", read_buffer_:" << read_buffer_
277 << ", cmd:" << cmd << endl;
279 clientcb(client, cmd);
283 if (errno == EAGAIN) {
288 lyxerr << "LyXComm: " << strerror(errno) << endl;
289 if (!read_buffer_.empty()) {
290 lyxerr << "LyXComm: truncated command: "
291 << read_buffer_ << endl;
292 read_buffer_.erase();
294 break; // reset connection
298 // The connection gets reset in errno != EAGAIN
299 // Why does it need to be reset if errno == 0?
306 void LyXComm::send(string const & msg)
309 lyxerr << "LyXComm: Request to send empty string. Ignoring."
314 if (lyxerr.debugging(Debug::LYXSERVER)) {
315 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
318 if (pipename.empty()) return;
321 lyxerr << "LyXComm: Pipes are closed. Could not send "
323 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
324 lyxerr << "LyXComm: Error sending message: " << msg
325 << '\n' << strerror(errno)
326 << "\nLyXComm: Resetting connection" << endl;
333 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
334 if (rc != NO_ERROR) {
335 errnum = TranslateOS2Error(rc);
336 lyxerr << "LyXComm: Message could not be flushed: " << msg
337 << '\n' << strerror(errnum) << endl;
345 LyXServer::~LyXServer()
347 // say goodbye to clients so they stop sending messages
348 // modified june 1999 by stefano@zool.su.se to send as many bye
349 // messages as there are clients, each with client's name.
351 for (int i= 0; i<numclients; ++i) {
352 message = "LYXSRV:" + clients[i] + ":bye\n";
358 /* ---F+------------------------------------------------------------------ *\
359 Function : ServerCallback
361 Purpose : handle data gotten from communication
362 \* ---F------------------------------------------------------------------- */
364 void LyXServer::callback(LyXServer * serv, string const & msg)
366 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
367 << msg << '\'' << endl;
369 char const * p = msg.c_str();
371 // --- parse the string --------------------------------------------
373 // Format: LYXCMD:<client>:<func>:<argstring>\n
375 bool server_only = false;
377 // --- 1. check 'header' ---
379 if (compare(p, "LYXSRV:", 7) == 0) {
381 } else if (0 != compare(p, "LYXCMD:", 7)) {
382 lyxerr << "LyXServer: Unknown request \""
388 // --- 2. for the moment ignore the client name ---
390 while (*p && *p != ':')
391 client += char(*p++);
395 // --- 3. get function name ---
397 while (*p && *p != ':')
400 // --- 4. parse the argument ---
402 if (!server_only && *p == ':' && *(++p)) {
403 while (*p && *p != '\n')
408 lyxerr[Debug::LYXSERVER]
409 << "LyXServer: Client: '" << client
410 << "' Command: '" << cmd
411 << "' Argument: '" << arg << '\'' << endl;
413 // --- lookup and exec the command ------------------
417 // return the greeting to inform the client that
419 if (cmd == "hello") {
421 if (serv->numclients == MAX_CLIENTS) { //paranoid check
422 lyxerr[Debug::LYXSERVER]
423 << "LyXServer: too many clients..."
427 int i= 0; //find place in clients[]
428 while (!serv->clients[i].empty()
429 && i<serv->numclients)
431 serv->clients[i] = client;
433 buf = "LYXSRV:" + client + ":hello\n";
434 lyxerr[Debug::LYXSERVER]
435 << "LyXServer: Greeting "
437 serv->pipes.send(buf);
438 } else if (cmd == "bye") {
439 // If clients == 0 maybe we should reset the pipes
440 // to prevent fake callbacks
441 int i = 0; //look if client is registered
442 for (; i < serv->numclients; ++i) {
443 if (serv->clients[i] == client) break;
445 if (i < serv->numclients) {
447 serv->clients[i].erase();
448 lyxerr[Debug::LYXSERVER]
449 << "LyXServer: Client "
450 << client << " said goodbye"
453 lyxerr[Debug::LYXSERVER]
454 << "LyXServer: ignoring bye messge from unregistered client"
458 lyxerr <<"LyXServer: Undefined server command "
459 << cmd << '.' << endl;
465 // which lyxfunc should we let it connect to?
466 // The correct solution would be to have a
467 // specialized (non-gui) BufferView. But how do
468 // we do it now? Probably we should just let it
469 // connect to the lyxfunc in the single LyXView we
470 // support currently. (Lgb)
473 serv->func->dispatch(cmd + ' ' + arg);
474 string const rval = serv->func->getMessage();
476 //modified june 1999 stefano@zool.su.se:
477 //all commands produce an INFO or ERROR message
478 //in the output pipe, even if they do not return
479 //anything. See chapter 4 of Customization doc.
481 if (serv->func->errorStat())
485 buf += client + ':' + cmd + ':' + rval + '\n';
486 serv->pipes.send(buf);
488 // !!! we don't do any error checking -
489 // if the client won't listen, the
490 // message is lost and others too
491 // maybe; so the client should empty
492 // the outpipe before issuing a request.
500 /* ---F+------------------------------------------------------------------ *\
501 Function : LyXNotifyClient
502 Called by : WorkAreaKeyPress
503 Purpose : send a notify messge to a client
504 Parameters: s - string to send
506 \* ---F------------------------------------------------------------------- */
508 void LyXServer::notifyClient(string const & s)
510 string buf = string("NOTIFY:") + s + "\n";