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"
53 #include "LyXAction.h"
55 #include "support/lstrings.h"
56 #include "support/lyxlib.h"
61 #define OS2EMX_PLAIN_CHAR
62 #define INCL_DOSNMPIPES
63 #define INCL_DOSERRORS
65 #include "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 /* === variables ========================================================= */
81 extern LyXAction lyxaction;
88 void C_LyXComm_callback(int fd, void *v)
90 LyXComm::callback(fd, v);
99 void LyXComm::openConnection()
101 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
103 // If we are up, that's an error
105 lyxerr << "LyXComm: Already connected" << endl;
108 // We assume that we don't make it
111 if (pipename.empty()) return;
113 if ((infd = startPipe(inPipeName(), false)) == -1)
116 if ((outfd = startPipe(outPipeName(), true)) == -1) {
117 endPipe(infd, inPipeName());
121 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
122 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
123 << '\n' << strerror(errno) << endl;
129 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
134 void LyXComm::closeConnection()
136 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
138 if (pipename.empty()) {
143 lyxerr << "LyXComm: Already disconnected" << endl;
147 endPipe(infd, inPipeName());
148 endPipe(outfd, outPipeName());
153 int LyXComm::startPipe(string const & filename, bool write)
161 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
162 // The current emx implementation of access() won't work with pipes.
163 rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
164 NP_NOWAIT|0x01, 0600, 0600, 0);
165 if (rc == ERROR_PIPE_BUSY) {
166 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
167 << "If no other LyX program is active, please delete"
168 " the pipe by hand and try again." << endl;
173 if (rc != NO_ERROR) {
174 errnum = TranslateOS2Error(rc);
175 lyxerr <<"LyXComm: Could not create pipe " << filename
176 << strerror(errnum) << endl;
180 rc = DosConnectNPipe(os2fd);
181 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
182 errnum = TranslateOS2Error(rc);
183 lyxerr <<"LyXComm: Could not create pipe " << filename
184 << strerror(errnum) << endl;
187 // Imported handles can be used both with OS/2 APIs and emx
188 // library functions.
189 fd = _imphandle(os2fd);
191 if (::access(filename.c_str(), F_OK) == 0) {
192 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
193 << "If no other LyX program is active, please delete"
194 " the pipe by hand and try again." << endl;
199 if (::mkfifo(filename.c_str(), 0600) < 0) {
200 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
201 << strerror(errno) << endl;
204 fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
208 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
209 << strerror(errno) << endl;
210 lyx::unlink(filename);
215 fl_add_io_callback(fd, FL_READ, C_LyXComm_callback, this);
221 void LyXComm::endPipe(int & fd, string const & filename)
230 rc = DosDisConnectNPipe(fd);
231 if (rc != NO_ERROR) {
232 errnum = TranslateOS2Error(rc);
233 lyxerr << "LyXComm: Could not disconnect pipe " << filename
234 << '\n' << strerror(errnum) << endl;
239 if (::close(fd) < 0) {
240 lyxerr << "LyXComm: Could not close pipe " << filename
241 << '\n' << strerror(errno) << endl;
244 // OS/2 pipes are deleted automatically
246 if (lyx::unlink(filename) < 0){
247 lyxerr << "LyXComm: Could not remove pipe " << filename
248 << '\n' << strerror(errno) << endl;
256 void LyXComm::emergencyCleanup()
258 endPipe(infd, inPipeName());
259 endPipe(outfd, outPipeName());
263 // Receives messages and sends then to client
264 void LyXComm::callback(int fd, void *v)
266 LyXComm * c = static_cast<LyXComm*>(v);
268 if (lyxerr.debugging(Debug::LYXSERVER)) {
269 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
272 const int CMDBUFLEN = 100;
273 char charbuf[CMDBUFLEN];
275 // nb! make lsbuf a class-member for multiple sessions
280 // the single = is intended here.
281 while((status = read(fd, charbuf, CMDBUFLEN-1)))
282 {// break and return in loop
283 if (status > 0) // got something
285 charbuf[status]= '\0'; // turn it into a c string
286 lsbuf += strip(charbuf, '\r');
287 // commit any commands read
288 while(lsbuf.find('\n') != string::npos) // while still
292 // split() grabs the entire string if
293 // the delim /wasn't/ found. ?:-P
294 lsbuf= split(lsbuf, cmd,'\n');
295 lyxerr[Debug::LYXSERVER]
296 << "LyXComm: status:" << status
297 << ", lsbuf:" << lsbuf
298 << ", cmd:" << cmd << endl;
300 c->clientcb(c->client, cmd);
305 { // EAGAIN is not really an error , it means we're
306 // only reading too fast for the writing process on
307 // the other end of the pipe.
309 return; // up to libforms select-loop (*crunch*)
313 lyxerr << "LyXComm: " << strerror(errno) << endl;
316 lyxerr << "LyxComm: truncated command: "
320 break; // reset connection
323 c->closeConnection();
329 void LyXComm::send(string const & msg)
332 lyxerr << "LyXComm: Request to send empty string. Ignoring."
337 if (lyxerr.debugging(Debug::LYXSERVER)) {
338 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
341 if (pipename.empty()) return;
344 lyxerr << "LyXComm: Pipes are closed. Could not send "
346 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
347 lyxerr << "LyXComm: Error sending message: " << msg
348 << '\n' << strerror(errno)
349 << "\nLyXComm: Resetting connection" << endl;
356 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
357 if (rc != NO_ERROR) {
358 errnum = TranslateOS2Error(rc);
359 lyxerr << "LyXComm: Message could not be flushed: " << msg
360 << '\n' << strerror(errnum) << endl;
368 LyXServer::~LyXServer()
370 // say goodbye to clients so they stop sending messages
371 // modified june 1999 by stefano@zool.su.se to send as many bye
372 // messages as there are clients, each with client's name.
374 for (int i= 0; i<numclients; ++i) {
375 message = "LYXSRV:" + clients[i] + ":bye\n";
381 /* ---F+------------------------------------------------------------------ *\
382 Function : ServerCallback
384 Purpose : handle data gotten from communication
385 \* ---F------------------------------------------------------------------- */
387 void LyXServer::callback(LyXServer * serv, string const & msg)
389 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
390 << msg << '\'' << endl;
392 char const * p = msg.c_str();
394 // --- parse the string --------------------------------------------
396 // Format: LYXCMD:<client>:<func>:<argstring>\n
398 bool server_only = false;
400 // --- 1. check 'header' ---
402 if (compare(p, "LYXSRV:", 7) == 0) {
404 } else if (0 != compare(p, "LYXCMD:", 7)) {
405 lyxerr << "LyXServer: Unknown request \"" << p << "\"" << endl;
410 // --- 2. for the moment ignore the client name ---
412 while(*p && *p != ':')
413 client += char(*p++);
417 // --- 3. get function name ---
419 while(*p && *p != ':')
422 // --- 4. parse the argument ---
424 if (!server_only && *p == ':' && *(++p)) {
425 while(*p && *p != '\n')
430 lyxerr[Debug::LYXSERVER]
431 << "LyXServer: Client: '" << client
432 << "' Command: '" << cmd
433 << "' Argument: '" << arg << '\'' << endl;
435 // --- lookup and exec the command ------------------
439 // return the greeting to inform the client that
441 if (cmd == "hello") {
443 if (serv->numclients == MAX_CLIENTS){ //paranoid check
444 lyxerr[Debug::LYXSERVER]
445 << "LyXServer: too many clients..."
449 int i= 0; //find place in clients[]
450 while (!serv->clients[i].empty()
451 && i<serv->numclients)
453 serv->clients[i] = client;
455 buf = "LYXSRV:" + client + ":hello\n";
456 lyxerr[Debug::LYXSERVER]
457 << "LyXServer: Greeting "
459 serv->pipes.send(buf);
460 } else if (cmd == "bye") {
461 // If clients == 0 maybe we should reset the pipes
462 // to prevent fake callbacks
463 int i = 0; //look if client is registered
464 for (; i < serv->numclients; ++i) {
465 if (serv->clients[i] == client) break;
467 if (i < serv->numclients) {
469 serv->clients[i].erase();
470 lyxerr[Debug::LYXSERVER]
471 << "LyXServer: Client "
472 << client << " said goodbye"
475 lyxerr[Debug::LYXSERVER]
476 << "LyXServer: ignoring bye messge from unregistered client"
480 lyxerr <<"LyXServer: Undefined server command "
481 << cmd << "." << endl;
487 // which lyxfunc should we let it connect to?
488 // The correct solution would be to have a
489 // specialized (non-gui) BufferView. But how do
490 // we do it now? Probably we should just let it
491 // connect to the lyxfunc in the single LyXView we
492 // support currently. (Lgb)
494 int action = lyxaction.LookupFunc(cmd);
499 rval = serv->func->dispatch(action, arg);
501 rval = "Unknown command";
504 //modified june 1999 stefano@zool.su.se:
505 //all commands produce an INFO or ERROR message
506 //in the output pipe, even if they do not return
507 //anything. See chapter 4 of Customization doc.
508 if (action<0 || serv->func->errorStat())
512 buf += string(client) + ":" + cmd + ":" + rval + "\n";
513 serv->pipes.send(buf);
515 // !!! we don't do any error checking -
516 // if the client won't listen, the
517 // message is lost and others too
518 // maybe; so the client should empty
519 // the outpipe before issuing a request.
527 /* ---F+------------------------------------------------------------------ *\
528 Function : LyxNotifyClient
529 Called by : WorkAreaKeyPress
530 Purpose : send a notify messge to a client
531 Parameters: s - string to send
533 \* ---F------------------------------------------------------------------- */
535 void LyXServer::notifyClient(string const & s)
537 string buf = string("NOTIFY:") + s + "\n";