1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
12 Docu : To use the lyxserver define the name of the pipe in your
14 \serverpipe "/home/myhome/.lyxpipe"
15 Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
16 Each message consists of a single line in ASCII. Input lines
17 (client -> LyX) have the following format:
18 "LYXCMD:<clientname>:<functionname>:<argument>"
19 Answers from LyX look like this:
20 "INFO:<clientname>:<functionname>:<data>"
21 [asierra970531] Or like this in case of error:
22 "ERROR:<clientname>:<functionname>:<error message>"
23 where <clientname> and <functionname> are just echoed.
24 If LyX notifies about a user defined extension key-sequence,
25 the line looks like this:
26 "NOTIFY:<key-sequence>"
27 [asierra970531] New server-only messages to implement a simple protocol
28 "LYXSRV:<clientname>:<protocol message>"
29 where <protocol message> can be "hello" or "bye". If hello is
30 received LyX will inform the client that it's listening its
31 messages, and 'bye' will inform that lyx is closing.
33 See development/server_monitor.c for an example client.
34 Purpose: implement a client/server lib for LyX
39 #include <sys/types.h>
46 #pragma implementation
49 #include "lyxserver.h"
53 #include "support/lstrings.h"
54 #include "support/lyxlib.h"
55 #include "frontends/lyx_gui.h"
60 #define OS2EMX_PLAIN_CHAR
61 #define INCL_DOSNMPIPES
62 #define INCL_DOSERRORS
64 #include "support/os2_errortable.h"
69 // provide an empty mkfifo() if we do not have one. This disables the
72 int mkfifo(char const * __path, mode_t __mode) {
78 void LyXComm::openConnection()
80 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
82 // If we are up, that's an error
84 lyxerr << "LyXComm: Already connected" << endl;
87 // We assume that we don't make it
90 if (pipename.empty()) {
91 lyxerr[Debug::LYXSERVER]
92 << "LyXComm: server is disabled, nothing to do"
97 if ((infd = startPipe(inPipeName(), false)) == -1)
100 if ((outfd = startPipe(outPipeName(), true)) == -1) {
101 endPipe(infd, inPipeName(), false);
105 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
106 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
107 << '\n' << strerror(errno) << endl;
113 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
118 void LyXComm::closeConnection()
120 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
122 if (pipename.empty()) {
123 lyxerr[Debug::LYXSERVER]
124 << "LyXComm: server is disabled, nothing to do"
130 lyxerr << "LyXComm: Already disconnected" << endl;
134 endPipe(infd, inPipeName(), false);
135 endPipe(outfd, outPipeName(), true);
140 int LyXComm::startPipe(string const & filename, bool write)
148 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
149 // The current emx implementation of access() won't work with pipes.
150 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
151 NP_NOWAIT|0x01, 0600, 0600, 0);
152 if (rc == ERROR_PIPE_BUSY) {
153 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
154 << "If no other LyX program is active, please delete"
155 " the pipe by hand and try again." << endl;
160 if (rc != NO_ERROR) {
161 errnum = TranslateOS2Error(rc);
162 lyxerr <<"LyXComm: Could not create pipe " << filename
163 << strerror(errnum) << endl;
167 rc = DosConnectNPipe(os2fd);
168 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
169 errnum = TranslateOS2Error(rc);
170 lyxerr <<"LyXComm: Could not create pipe " << filename
171 << strerror(errnum) << endl;
174 // Imported handles can be used both with OS/2 APIs and emx
175 // library functions.
176 fd = _imphandle(os2fd);
178 if (::access(filename.c_str(), F_OK) == 0) {
179 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
180 << "If no other LyX program is active, please delete"
181 " the pipe by hand and try again." << endl;
186 if (::mkfifo(filename.c_str(), 0600) < 0) {
187 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
188 << strerror(errno) << endl;
191 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
195 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
196 << strerror(errno) << endl;
197 lyx::unlink(filename);
202 lyx_gui::set_read_callback(fd, this);
209 void LyXComm::endPipe(int & fd, string const & filename, bool write)
215 lyx_gui::remove_read_callback(fd);
222 rc = DosDisConnectNPipe(fd);
223 if (rc != NO_ERROR) {
224 errnum = TranslateOS2Error(rc);
225 lyxerr << "LyXComm: Could not disconnect pipe " << filename
226 << '\n' << strerror(errnum) << endl;
231 if (::close(fd) < 0) {
232 lyxerr << "LyXComm: Could not close pipe " << filename
233 << '\n' << strerror(errno) << endl;
236 // OS/2 pipes are deleted automatically
238 if (lyx::unlink(filename) < 0) {
239 lyxerr << "LyXComm: Could not remove pipe " << filename
240 << '\n' << strerror(errno) << endl;
248 void LyXComm::emergencyCleanup()
250 if (!pipename.empty()) {
251 endPipe(infd, inPipeName(), false);
252 endPipe(outfd, outPipeName(), true);
257 // Receives messages and sends then to client
258 void LyXComm::read_ready()
260 // nb! make read_buffer_ a class-member for multiple sessions
261 static string read_buffer_;
262 read_buffer_.erase();
264 int const charbuf_size = 100;
265 char charbuf[charbuf_size];
269 // the single = is intended here.
270 while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
273 charbuf[status] = '\0'; // turn it into a c string
274 read_buffer_ += rtrim(charbuf, "\r");
275 // commit any commands read
276 while (read_buffer_.find('\n') != string::npos) {
277 // split() grabs the entire string if
278 // the delim /wasn't/ found. ?:-P
280 read_buffer_= split(read_buffer_, cmd,'\n');
281 lyxerr[Debug::LYXSERVER]
282 << "LyXComm: status:" << status
283 << ", read_buffer_:" << read_buffer_
284 << ", cmd:" << cmd << endl;
286 clientcb(client, cmd);
290 if (errno == EAGAIN) {
295 lyxerr << "LyXComm: " << strerror(errno) << endl;
296 if (!read_buffer_.empty()) {
297 lyxerr << "LyXComm: truncated command: "
298 << read_buffer_ << endl;
299 read_buffer_.erase();
301 break; // reset connection
305 // The connection gets reset in errno != EAGAIN
306 // Why does it need to be reset if errno == 0?
313 void LyXComm::send(string const & msg)
316 lyxerr << "LyXComm: Request to send empty string. Ignoring."
321 if (lyxerr.debugging(Debug::LYXSERVER)) {
322 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
325 if (pipename.empty()) return;
328 lyxerr << "LyXComm: Pipes are closed. Could not send "
330 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
331 lyxerr << "LyXComm: Error sending message: " << msg
332 << '\n' << strerror(errno)
333 << "\nLyXComm: Resetting connection" << endl;
340 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
341 if (rc != NO_ERROR) {
342 errnum = TranslateOS2Error(rc);
343 lyxerr << "LyXComm: Message could not be flushed: " << msg
344 << '\n' << strerror(errnum) << endl;
352 LyXServer::~LyXServer()
354 // say goodbye to clients so they stop sending messages
355 // modified june 1999 by stefano@zool.su.se to send as many bye
356 // messages as there are clients, each with client's name.
358 for (int i= 0; i<numclients; ++i) {
359 message = "LYXSRV:" + clients[i] + ":bye\n";
365 /* ---F+------------------------------------------------------------------ *\
366 Function : ServerCallback
368 Purpose : handle data gotten from communication
369 \* ---F------------------------------------------------------------------- */
371 void LyXServer::callback(LyXServer * serv, string const & msg)
373 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
374 << msg << '\'' << endl;
376 char const * p = msg.c_str();
378 // --- parse the string --------------------------------------------
380 // Format: LYXCMD:<client>:<func>:<argstring>\n
382 bool server_only = false;
384 // --- 1. check 'header' ---
386 if (compare(p, "LYXSRV:", 7) == 0) {
388 } else if (0 != compare(p, "LYXCMD:", 7)) {
389 lyxerr << "LyXServer: Unknown request \""
395 // --- 2. for the moment ignore the client name ---
397 while (*p && *p != ':')
398 client += char(*p++);
402 // --- 3. get function name ---
404 while (*p && *p != ':')
407 // --- 4. parse the argument ---
409 if (!server_only && *p == ':' && *(++p)) {
410 while (*p && *p != '\n')
415 lyxerr[Debug::LYXSERVER]
416 << "LyXServer: Client: '" << client
417 << "' Command: '" << cmd
418 << "' Argument: '" << arg << '\'' << endl;
420 // --- lookup and exec the command ------------------
424 // return the greeting to inform the client that
426 if (cmd == "hello") {
428 if (serv->numclients == MAX_CLIENTS) { //paranoid check
429 lyxerr[Debug::LYXSERVER]
430 << "LyXServer: too many clients..."
434 int i= 0; //find place in clients[]
435 while (!serv->clients[i].empty()
436 && i<serv->numclients)
438 serv->clients[i] = client;
440 buf = "LYXSRV:" + client + ":hello\n";
441 lyxerr[Debug::LYXSERVER]
442 << "LyXServer: Greeting "
444 serv->pipes.send(buf);
445 } else if (cmd == "bye") {
446 // If clients == 0 maybe we should reset the pipes
447 // to prevent fake callbacks
448 int i = 0; //look if client is registered
449 for (; i < serv->numclients; ++i) {
450 if (serv->clients[i] == client) break;
452 if (i < serv->numclients) {
454 serv->clients[i].erase();
455 lyxerr[Debug::LYXSERVER]
456 << "LyXServer: Client "
457 << client << " said goodbye"
460 lyxerr[Debug::LYXSERVER]
461 << "LyXServer: ignoring bye messge from unregistered client"
465 lyxerr <<"LyXServer: Undefined server command "
466 << cmd << '.' << endl;
472 // which lyxfunc should we let it connect to?
473 // The correct solution would be to have a
474 // specialized (non-gui) BufferView. But how do
475 // we do it now? Probably we should just let it
476 // connect to the lyxfunc in the single LyXView we
477 // support currently. (Lgb)
480 serv->func->dispatch(cmd + ' ' + arg);
481 string const rval = serv->func->getMessage();
483 //modified june 1999 stefano@zool.su.se:
484 //all commands produce an INFO or ERROR message
485 //in the output pipe, even if they do not return
486 //anything. See chapter 4 of Customization doc.
488 if (serv->func->errorStat())
492 buf += client + ':' + cmd + ':' + rval + '\n';
493 serv->pipes.send(buf);
495 // !!! we don't do any error checking -
496 // if the client won't listen, the
497 // message is lost and others too
498 // maybe; so the client should empty
499 // the outpipe before issuing a request.
507 /* ---F+------------------------------------------------------------------ *\
508 Function : LyXNotifyClient
509 Called by : WorkAreaKeyPress
510 Purpose : send a notify messge to a client
511 Parameters: s - string to send
513 \* ---F------------------------------------------------------------------- */
515 void LyXServer::notifyClient(string const & s)
517 string buf = string("NOTIFY:") + s + "\n";