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>
44 #include FORMS_H_LOCATION
47 #pragma implementation
50 #include "lyxserver.h"
54 #include "support/lstrings.h"
55 #include "support/lyxlib.h"
56 #include "frontends/lyx_gui.h"
61 #define OS2EMX_PLAIN_CHAR
62 #define INCL_DOSNMPIPES
63 #define INCL_DOSERRORS
65 #include "support/os2_errortable.h"
70 // provide an empty mkfifo() if we do not have one. This disables the
73 int mkfifo(char const * __path, mode_t __mode) {
79 void LyXComm::openConnection()
81 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
83 // If we are up, that's an error
85 lyxerr << "LyXComm: Already connected" << endl;
88 // We assume that we don't make it
91 if (pipename.empty()) {
92 lyxerr[Debug::LYXSERVER]
93 << "LyXComm: server is disabled, nothing to do"
98 if ((infd = startPipe(inPipeName(), false)) == -1)
101 if ((outfd = startPipe(outPipeName(), true)) == -1) {
102 endPipe(infd, inPipeName(), false);
106 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
107 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
108 << '\n' << strerror(errno) << endl;
114 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
119 void LyXComm::closeConnection()
121 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
123 if (pipename.empty()) {
124 lyxerr[Debug::LYXSERVER]
125 << "LyXComm: server is disabled, nothing to do"
131 lyxerr << "LyXComm: Already disconnected" << endl;
135 endPipe(infd, inPipeName(), false);
136 endPipe(outfd, outPipeName(), true);
141 int LyXComm::startPipe(string const & filename, bool write)
149 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
150 // The current emx implementation of access() won't work with pipes.
151 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
152 NP_NOWAIT|0x01, 0600, 0600, 0);
153 if (rc == ERROR_PIPE_BUSY) {
154 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
155 << "If no other LyX program is active, please delete"
156 " the pipe by hand and try again." << endl;
161 if (rc != NO_ERROR) {
162 errnum = TranslateOS2Error(rc);
163 lyxerr <<"LyXComm: Could not create pipe " << filename
164 << strerror(errnum) << endl;
168 rc = DosConnectNPipe(os2fd);
169 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
170 errnum = TranslateOS2Error(rc);
171 lyxerr <<"LyXComm: Could not create pipe " << filename
172 << strerror(errnum) << endl;
175 // Imported handles can be used both with OS/2 APIs and emx
176 // library functions.
177 fd = _imphandle(os2fd);
179 if (::access(filename.c_str(), F_OK) == 0) {
180 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
181 << "If no other LyX program is active, please delete"
182 " the pipe by hand and try again." << endl;
187 if (::mkfifo(filename.c_str(), 0600) < 0) {
188 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
189 << strerror(errno) << endl;
192 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
196 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
197 << strerror(errno) << endl;
198 lyx::unlink(filename);
203 lyx_gui::set_read_callback(fd, this);
210 void LyXComm::endPipe(int & fd, string const & filename, bool write)
216 lyx_gui::remove_read_callback(fd);
223 rc = DosDisConnectNPipe(fd);
224 if (rc != NO_ERROR) {
225 errnum = TranslateOS2Error(rc);
226 lyxerr << "LyXComm: Could not disconnect pipe " << filename
227 << '\n' << strerror(errnum) << endl;
232 if (::close(fd) < 0) {
233 lyxerr << "LyXComm: Could not close pipe " << filename
234 << '\n' << strerror(errno) << endl;
237 // OS/2 pipes are deleted automatically
239 if (lyx::unlink(filename) < 0) {
240 lyxerr << "LyXComm: Could not remove pipe " << filename
241 << '\n' << strerror(errno) << endl;
249 void LyXComm::emergencyCleanup()
251 if (!pipename.empty()) {
252 endPipe(infd, inPipeName(), false);
253 endPipe(outfd, outPipeName(), true);
258 // Receives messages and sends then to client
259 void LyXComm::read_ready()
261 if (lyxerr.debugging(Debug::LYXSERVER)) {
262 lyxerr << "LyXComm: Receiving from fd " << infd << endl;
265 int const CMDBUFLEN = 100;
266 char charbuf[CMDBUFLEN];
268 // nb! make lsbuf a class-member for multiple sessions
273 // the single = is intended here.
274 while ((status = read(infd, charbuf, CMDBUFLEN - 1))) {
278 charbuf[status]= '\0'; // turn it into a c string
279 lsbuf += rtrim(charbuf, "\r");
280 // commit any commands read
281 while (lsbuf.find('\n') != string::npos) {
282 // split() grabs the entire string if
283 // the delim /wasn't/ found. ?:-P
284 lsbuf= split(lsbuf, cmd,'\n');
285 lyxerr[Debug::LYXSERVER]
286 << "LyXComm: status:" << status
287 << ", lsbuf:" << lsbuf
288 << ", cmd:" << cmd << endl;
290 clientcb(client, cmd);
294 if (rerrno == EAGAIN) {
299 lyxerr << "LyXComm: " << strerror(rerrno) << endl;
300 if (!lsbuf.empty()) {
301 lyxerr << "LyXComm: truncated command: "
305 break; // reset connection
314 void LyXComm::send(string const & msg)
317 lyxerr << "LyXComm: Request to send empty string. Ignoring."
322 if (lyxerr.debugging(Debug::LYXSERVER)) {
323 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
326 if (pipename.empty()) return;
329 lyxerr << "LyXComm: Pipes are closed. Could not send "
331 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
332 lyxerr << "LyXComm: Error sending message: " << msg
333 << '\n' << strerror(errno)
334 << "\nLyXComm: Resetting connection" << endl;
341 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
342 if (rc != NO_ERROR) {
343 errnum = TranslateOS2Error(rc);
344 lyxerr << "LyXComm: Message could not be flushed: " << msg
345 << '\n' << strerror(errnum) << endl;
353 LyXServer::~LyXServer()
355 // say goodbye to clients so they stop sending messages
356 // modified june 1999 by stefano@zool.su.se to send as many bye
357 // messages as there are clients, each with client's name.
359 for (int i= 0; i<numclients; ++i) {
360 message = "LYXSRV:" + clients[i] + ":bye\n";
366 /* ---F+------------------------------------------------------------------ *\
367 Function : ServerCallback
369 Purpose : handle data gotten from communication
370 \* ---F------------------------------------------------------------------- */
372 void LyXServer::callback(LyXServer * serv, string const & msg)
374 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
375 << msg << '\'' << endl;
377 char const * p = msg.c_str();
379 // --- parse the string --------------------------------------------
381 // Format: LYXCMD:<client>:<func>:<argstring>\n
383 bool server_only = false;
385 // --- 1. check 'header' ---
387 if (compare(p, "LYXSRV:", 7) == 0) {
389 } else if (0 != compare(p, "LYXCMD:", 7)) {
390 lyxerr << "LyXServer: Unknown request \"" << p << "\"" << endl;
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";