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 <sys/types.h>
48 #include "lyxserver.h"
52 #include "support/lstrings.h"
53 #include "support/lyxlib.h"
54 #include "frontends/lyx_gui.h"
59 #define OS2EMX_PLAIN_CHAR
60 #define INCL_DOSNMPIPES
61 #define INCL_DOSERRORS
63 #include "support/os2_errortable.h"
67 using namespace lyx::support;
71 // provide an empty mkfifo() if we do not have one. This disables the
74 int mkfifo(char const * __path, mode_t __mode) {
80 void LyXComm::openConnection()
82 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
84 // If we are up, that's an error
86 lyxerr << "LyXComm: Already connected" << endl;
89 // We assume that we don't make it
92 if (pipename.empty()) {
93 lyxerr[Debug::LYXSERVER]
94 << "LyXComm: server is disabled, nothing to do"
99 if ((infd = startPipe(inPipeName(), false)) == -1)
102 if ((outfd = startPipe(outPipeName(), true)) == -1) {
103 endPipe(infd, inPipeName(), false);
107 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
108 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
109 << '\n' << strerror(errno) << endl;
115 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
120 void LyXComm::closeConnection()
122 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
124 if (pipename.empty()) {
125 lyxerr[Debug::LYXSERVER]
126 << "LyXComm: server is disabled, nothing to do"
132 lyxerr << "LyXComm: Already disconnected" << endl;
136 endPipe(infd, inPipeName(), false);
137 endPipe(outfd, outPipeName(), true);
142 int LyXComm::startPipe(string const & filename, bool write)
150 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
151 // The current emx implementation of access() won't work with pipes.
152 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
153 NP_NOWAIT|0x01, 0600, 0600, 0);
154 if (rc == ERROR_PIPE_BUSY) {
155 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
156 << "If no other LyX program is active, please delete"
157 " the pipe by hand and try again." << endl;
162 if (rc != NO_ERROR) {
163 errnum = TranslateOS2Error(rc);
164 lyxerr <<"LyXComm: Could not create pipe " << filename
165 << strerror(errnum) << endl;
169 rc = DosConnectNPipe(os2fd);
170 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
171 errnum = TranslateOS2Error(rc);
172 lyxerr <<"LyXComm: Could not create pipe " << filename
173 << strerror(errnum) << endl;
176 // Imported handles can be used both with OS/2 APIs and emx
177 // library functions.
178 fd = _imphandle(os2fd);
180 if (::access(filename.c_str(), F_OK) == 0) {
181 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
182 << "If no other LyX program is active, please delete"
183 " the pipe by hand and try again." << endl;
188 if (::mkfifo(filename.c_str(), 0600) < 0) {
189 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
190 << strerror(errno) << endl;
193 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
197 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
198 << strerror(errno) << endl;
204 lyx_gui::set_read_callback(fd, this);
211 void LyXComm::endPipe(int & fd, string const & filename, bool write)
217 lyx_gui::remove_read_callback(fd);
224 rc = DosDisConnectNPipe(fd);
225 if (rc != NO_ERROR) {
226 errnum = TranslateOS2Error(rc);
227 lyxerr << "LyXComm: Could not disconnect pipe " << filename
228 << '\n' << strerror(errnum) << endl;
233 if (::close(fd) < 0) {
234 lyxerr << "LyXComm: Could not close pipe " << filename
235 << '\n' << strerror(errno) << endl;
238 // OS/2 pipes are deleted automatically
240 if (unlink(filename) < 0) {
241 lyxerr << "LyXComm: Could not remove pipe " << filename
242 << '\n' << strerror(errno) << endl;
250 void LyXComm::emergencyCleanup()
252 if (!pipename.empty()) {
253 endPipe(infd, inPipeName(), false);
254 endPipe(outfd, outPipeName(), true);
259 // Receives messages and sends then to client
260 void LyXComm::read_ready()
262 // nb! make read_buffer_ a class-member for multiple sessions
263 static string read_buffer_;
264 read_buffer_.erase();
266 int const charbuf_size = 100;
267 char charbuf[charbuf_size];
271 // the single = is intended here.
272 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
275 charbuf[status] = '\0'; // turn it into a c string
276 read_buffer_ += rtrim(charbuf, "\r");
277 // commit any commands read
278 while (read_buffer_.find('\n') != string::npos) {
279 // split() grabs the entire string if
280 // the delim /wasn't/ found. ?:-P
282 read_buffer_= split(read_buffer_, cmd,'\n');
283 lyxerr[Debug::LYXSERVER]
284 << "LyXComm: status:" << status
285 << ", read_buffer_:" << read_buffer_
286 << ", cmd:" << cmd << endl;
288 clientcb(client, cmd);
292 if (errno == EAGAIN) {
297 lyxerr << "LyXComm: " << strerror(errno) << endl;
298 if (!read_buffer_.empty()) {
299 lyxerr << "LyXComm: truncated command: "
300 << read_buffer_ << endl;
301 read_buffer_.erase();
303 break; // reset connection
307 // The connection gets reset in errno != EAGAIN
308 // Why does it need to be reset if errno == 0?
315 void LyXComm::send(string const & msg)
318 lyxerr << "LyXComm: Request to send empty string. Ignoring."
323 if (lyxerr.debugging(Debug::LYXSERVER)) {
324 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
327 if (pipename.empty()) return;
330 lyxerr << "LyXComm: Pipes are closed. Could not send "
332 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
333 lyxerr << "LyXComm: Error sending message: " << msg
334 << '\n' << strerror(errno)
335 << "\nLyXComm: Resetting connection" << endl;
342 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
343 if (rc != NO_ERROR) {
344 errnum = TranslateOS2Error(rc);
345 lyxerr << "LyXComm: Message could not be flushed: " << msg
346 << '\n' << strerror(errnum) << endl;
354 LyXServer::~LyXServer()
356 // say goodbye to clients so they stop sending messages
357 // modified june 1999 by stefano@zool.su.se to send as many bye
358 // messages as there are clients, each with client's name.
360 for (int i= 0; i<numclients; ++i) {
361 message = "LYXSRV:" + clients[i] + ":bye\n";
367 /* ---F+------------------------------------------------------------------ *\
368 Function : ServerCallback
370 Purpose : handle data gotten from communication
371 \* ---F------------------------------------------------------------------- */
373 void LyXServer::callback(LyXServer * serv, string const & msg)
375 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
376 << msg << '\'' << endl;
378 char const * p = msg.c_str();
380 // --- parse the string --------------------------------------------
382 // Format: LYXCMD:<client>:<func>:<argstring>\n
384 bool server_only = false;
386 // --- 1. check 'header' ---
388 if (compare(p, "LYXSRV:", 7) == 0) {
390 } else if (0 != compare(p, "LYXCMD:", 7)) {
391 lyxerr << "LyXServer: Unknown request \""
397 // --- 2. for the moment ignore the client name ---
399 while (*p && *p != ':')
400 client += char(*p++);
404 // --- 3. get function name ---
406 while (*p && *p != ':')
409 // --- 4. parse the argument ---
411 if (!server_only && *p == ':' && *(++p)) {
412 while (*p && *p != '\n')
417 lyxerr[Debug::LYXSERVER]
418 << "LyXServer: Client: '" << client
419 << "' Command: '" << cmd
420 << "' Argument: '" << arg << '\'' << endl;
422 // --- lookup and exec the command ------------------
426 // return the greeting to inform the client that
428 if (cmd == "hello") {
430 if (serv->numclients == MAX_CLIENTS) { //paranoid check
431 lyxerr[Debug::LYXSERVER]
432 << "LyXServer: too many clients..."
436 int i= 0; //find place in clients[]
437 while (!serv->clients[i].empty()
438 && i<serv->numclients)
440 serv->clients[i] = client;
442 buf = "LYXSRV:" + client + ":hello\n";
443 lyxerr[Debug::LYXSERVER]
444 << "LyXServer: Greeting "
446 serv->pipes.send(buf);
447 } else if (cmd == "bye") {
448 // If clients == 0 maybe we should reset the pipes
449 // to prevent fake callbacks
450 int i = 0; //look if client is registered
451 for (; i < serv->numclients; ++i) {
452 if (serv->clients[i] == client) break;
454 if (i < serv->numclients) {
456 serv->clients[i].erase();
457 lyxerr[Debug::LYXSERVER]
458 << "LyXServer: Client "
459 << client << " said goodbye"
462 lyxerr[Debug::LYXSERVER]
463 << "LyXServer: ignoring bye messge from unregistered client"
467 lyxerr <<"LyXServer: Undefined server command "
468 << cmd << '.' << endl;
474 // which lyxfunc should we let it connect to?
475 // The correct solution would be to have a
476 // specialized (non-gui) BufferView. But how do
477 // we do it now? Probably we should just let it
478 // connect to the lyxfunc in the single LyXView we
479 // support currently. (Lgb)
482 serv->func->dispatch(cmd + ' ' + arg);
483 string const rval = serv->func->getMessage();
485 //modified june 1999 stefano@zool.su.se:
486 //all commands produce an INFO or ERROR message
487 //in the output pipe, even if they do not return
488 //anything. See chapter 4 of Customization doc.
490 if (serv->func->errorStat())
494 buf += client + ':' + cmd + ':' + rval + '\n';
495 serv->pipes.send(buf);
497 // !!! we don't do any error checking -
498 // if the client won't listen, the
499 // message is lost and others too
500 // maybe; so the client should empty
501 // the outpipe before issuing a request.
509 /* ---F+------------------------------------------------------------------ *\
510 Function : LyXNotifyClient
511 Called by : WorkAreaKeyPress
512 Purpose : send a notify messge to a client
513 Parameters: s - string to send
515 \* ---F------------------------------------------------------------------- */
517 void LyXServer::notifyClient(string const & s)
519 string buf = string("NOTIFY:") + s + "\n";