2 /* This file is part of
3 * ======================================================
5 * LyX, The Document Processor
7 * Copyright 1995 Matthias Ettrich
8 * Copyright 1995-1999 The LyX Team.
10 * ======================================================*/
13 Docu : To use the lyxserver define the name of the pipe in your
15 \serverpipe "/home/myhome/.lyxpipe"
16 Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
17 Each message consists of a single line in ASCII. Input lines
18 (client -> LyX) have the following format:
19 "LYXCMD:<clientname>:<functionname>:<argument>"
20 Answers from LyX look like this:
21 "INFO:<clientname>:<functionname>:<data>"
22 [asierra970531] Or like this in case of error:
23 "ERROR:<clientname>:<functionname>:<error message>"
24 where <clientname> and <functionname> are just echoed.
25 If LyX notifies about a user defined extension key-sequence,
26 the line looks like this:
27 "NOTIFY:<key-sequence>"
28 [asierra970531] New server-only messages to implement a simple protocol
29 "LYXSRV:<clientname>:<protocol message>"
30 where <protocol message> can be "hello" or "bye". If hello is
31 received LyX will inform the client that it's listening its
32 messages, and 'bye' will inform that lyx is closing.
34 See development/server_monitor.c for an example client.
35 Purpose: implement a client/server lib for LyX
42 #include <sys/types.h>
47 #include FORMS_H_LOCATION
50 #pragma implementation
53 #include "lyxserver.h"
57 #include "support/lstrings.h"
62 #define OS2EMX_PLAIN_CHAR
63 #define INCL_DOSNMPIPES
64 #define INCL_DOSERRORS
66 #include "os2_errortable.h"
69 // provide an empty mkfifo() if we do not have one. This disables the
72 int mkfifo( char *__path, mode_t __mode ) {
78 /* === variables ========================================================= */
80 extern LyXAction lyxaction;
85 void LyXComm::openConnection() {
86 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
88 // If we are up, that's an error
90 lyxerr << "LyXComm: Already connected" << endl;
93 // We assume that we don't make it
96 if (pipename.empty()) return;
98 // --- prepare input pipe ---------------------------------------
100 string tmp = pipename + ".in";
106 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
107 // The current emx implementation of access() won't work with pipes.
108 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND,
109 NP_NOWAIT|0x01, 0600, 0600, 0);
110 if (rc == ERROR_PIPE_BUSY) {
112 if (access(tmp.c_str(), F_OK) == 0) {
114 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
115 << "If no other LyX program is active, please delete"
116 " the pipe by hand and try again." << endl;
121 if (mkfifo(tmp.c_str(), 0600) < 0) {
122 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
123 << strerror(errno) << endl;
126 infd = open(tmp.c_str(), O_RDONLY|O_NONBLOCK);
128 if (rc != NO_ERROR) {
129 errnum = TranslateOS2Error(rc);
130 lyxerr <<"LyXComm: Could not create pipe " << tmp
131 << strerror(errnum) << endl;
135 rc = DosConnectNPipe(fd);
136 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
137 errnum = TranslateOS2Error(rc);
138 lyxerr <<"LyXComm: Could not create pipe " + tmp);
139 lyxerr <<strerror(errnum);
142 // Imported handles can be used both with OS/2 APIs and emx
143 // library functions.
144 infd = _imphandle(fd);
147 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
148 << strerror(errno) << endl;
151 fl_add_io_callback(infd, FL_READ, callback, (void*)this);
153 // --- prepare output pipe ---------------------------------------
155 tmp = pipename + ".out";
158 if (access(tmp.c_str(), F_OK) == 0) {
160 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX,
161 NP_NOWAIT|0x01, 0600, 0600, 0);
163 if (rc == ERROR_PIPE_BUSY) {
165 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
166 << "If no other LyX program is active, please delete"
167 " the pipe by hand and try again." << endl;
172 if (mkfifo(tmp.c_str(), 0600) < 0) {
173 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
174 << strerror(errno) << endl;
177 if (access(tmp.c_str(), F_OK) != 0) {
178 lyxerr << "LyXComm: Pipe " << tmp
179 << " does not exist" << endl;
182 outfd = open(tmp.c_str(), O_RDWR);
184 if (rc != NO_ERROR) {
185 errnum = TranslateOS2Error(rc);
186 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
187 << strerror(errnum) << endl;
190 rc = DosConnectNPipe(fd);
191 if (rc == ERROR_BAD_PIPE) {
192 lyxerr << "LyXComm: Pipe " << tmp
193 << " does not exist" << endl;
196 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
197 errnum = TranslateOS2Error(rc);
198 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
199 << strerror(errnum) << endl;
202 outfd = _imphandle(fd);
205 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
206 << strerror(errno) << endl;
209 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
210 lyxerr << "LyXComm: Could not set flags on pipe " << tmp
211 << '\n' << strerror(errno) << endl;
216 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
220 void LyXComm::closeConnection() {
225 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
227 if (pipename.empty()) {
232 lyxerr << "LyXComm: Already disconnected" << endl;
237 fl_remove_io_callback(infd, FL_READ, callback);
239 string tmp = pipename + ".in";
240 #ifdef __EMX__ // Notify the operating system.
241 rc = DosDisConnectNPipe(infd);
242 if (rc != NO_ERROR) {
243 errnum = TranslateOS2Error(rc);
244 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
245 << '\n' << strerror(errnum) << endl;
249 if (close(infd) < 0) {
250 lyxerr << "LyXComm: Could not close pipe " << tmp
251 << '\n' << strerror(errno) << endl;
253 #ifndef __EMX__ // OS/2 named pipes will be automatically removed.
254 if (unlink(tmp.c_str()) < 0){
255 lyxerr << "LyXComm: Could not remove pipe " << tmp
256 << '\n' << strerror(errno) << endl;
261 string tmp = pipename + ".out";
263 rc = DosDisConnectNPipe(outfd);
264 if (rc != NO_ERROR) {
265 errnum = TranslateOS2Error(rc);
266 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
267 << '\n' << strerror(errnum) << endl;
271 if (close(outfd) < 0) {
272 lyxerr << "LyXComm: Could not close pipe " << tmp
273 << '\n' << strerror(errno) << endl;
276 if (unlink(tmp.c_str()) < 0){
277 lyxerr << "LyXComm: Could not remove pipe " << tmp
278 << '\n' << strerror(errno) << endl;
285 // Receives messages and sends then to client
286 void LyXComm::callback(int fd, void *v)
288 LyXComm * c = (LyXComm *) v;
290 if (lyxerr.debugging(Debug::LYXSERVER)) {
291 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
294 const int CMDBUFLEN = 100;
295 char charbuf[CMDBUFLEN];
297 // nb! make lsbuf a class-member for multiple sessions
302 // the single = is intended here.
303 while((status = read(fd,charbuf,CMDBUFLEN-1)))
304 {// break and return in loop
305 if(status > 0) // got something
307 charbuf[status]='\0'; // turn it into a c string
308 lsbuf += strip(charbuf, '\r');
309 // commit any commands read
310 while(lsbuf.find('\n') != string::npos) // while still
314 // split() grabs the entire string if
315 // the delim /wasn't/ found. ?:-P
316 lsbuf=split(lsbuf, cmd,'\n');
317 lyxerr[Debug::LYXSERVER]
318 << "LyXComm: status:" << status
319 << ", lsbuf:" << lsbuf
320 << ", cmd:" << cmd << endl;
322 c->clientcb(c->client, cmd);
327 { // EAGAIN is not really an error , it means we're
328 // only reading too fast for the writing process on
329 // the other end of the pipe.
331 return; // up to libforms select-loop (*crunch*)
335 lyxerr << "LyXComm: " << strerror(errno) << endl;
338 lyxerr << "LyxComm: truncated command: "
342 break; // reset connection
345 c->closeConnection();
350 void LyXComm::send(string const & msg) {
352 lyxerr << "LyXComm: Request to send empty string. Ignoring."
357 if (lyxerr.debugging(Debug::LYXSERVER)) {
358 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
361 if (pipename.empty()) return;
364 lyxerr << "LyXComm: Pipes are closed. Could not send "
366 } else if (write(outfd, msg.c_str(), msg.length()) < 0) {
367 lyxerr << "LyXComm: Error sending message: " << msg
368 << '\n' << strerror(errno)
369 << "\nLyXComm: Resetting connection" << endl;
376 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
377 if (rc != NO_ERROR) {
378 errnum = TranslateOS2Error(rc);
379 lyxerr <<"LyXComm: Message could not be flushed: " +msg);
380 lyxerr <<strerror(errnum));
388 LyXServer::~LyXServer()
390 // say goodbye to clients so they stop sending messages
391 // modified june 1999 by stefano@zool.su.se to send as many bye
392 // messages as there are clients, each with client's name.
394 for (int i=0; i<numclients; i++) {
395 message = "LYXSRV:" + clients[i] + ":bye\n";
401 /* ---F+------------------------------------------------------------------ *\
402 Function : ServerCallback
404 Purpose : handle data gotten from communication
405 \* ---F------------------------------------------------------------------- */
407 void LyXServer::callback(LyXServer * serv, string const & msg)
409 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
410 << msg << '\'' << endl;
412 char const *p = msg.c_str();
414 // --- parse the string --------------------------------------------
416 // Format: LYXCMD:<client>:<func>:<argstring>\n
418 bool server_only = false;
420 // --- 1. check 'header' ---
421 if (strncmp(p, "LYXSRV:", 7)==0) {
423 } else if(0!=strncmp(p, "LYXCMD:", 7)) {
424 lyxerr << "LyXServer: Unknown request" << endl;
429 // --- 2. for the moment ignore the client name ---
431 while(*p && *p != ':')
432 client += char(*p++);
436 // --- 3. get function name ---
438 while(*p && *p != ':')
441 // --- 4. parse the argument ---
443 if(!server_only && *p == ':' && *(++p)) {
444 while(*p && *p != '\n')
449 lyxerr[Debug::LYXSERVER]
450 << "LyXServer: Client: '" << client
451 << "' Command: '" << cmd
452 << "' Argument: '" << arg << '\'' << endl;
454 // --- lookup and exec the command ------------------
458 // return the greeting to inform the client that
460 if (cmd == "hello") {
462 if(serv->numclients==MAX_CLIENTS){ //paranoid check
463 lyxerr[Debug::LYXSERVER]
464 << "LyXServer: too many clients..."
468 int i=0; //find place in clients[]
469 while (!serv->clients[i].empty()
470 && i<serv->numclients)
472 serv->clients[i] = client;
474 buf = "LYXSRV:" + client + ":hello\n";
475 lyxerr[Debug::LYXSERVER]
476 << "LyXServer: Greeting "
478 serv->pipes.send(buf);
479 } else if (cmd == "bye") {
480 // If clients==0 maybe we should reset the pipes
481 // to prevent fake callbacks
482 int i; //look if client is registered
483 for (i=0; i<serv->numclients; i++) {
484 if (serv->clients[i] == client) break;
486 if (i<serv->numclients) {
488 serv->clients[i].clear();
489 lyxerr[Debug::LYXSERVER]
490 << "LyXServer: Client "
491 << client << " said goodbye"
494 lyxerr[Debug::LYXSERVER]
495 << "LyXServer: ignoring bye messge from unregistered client"
499 lyxerr <<"LyXServer: Undefined server command "
500 << cmd << "." << endl;
506 // which lyxfunc should we let it connect to?
507 // The correct solution would be to have a
508 // specialized (non-gui) BufferView. But how do
509 // we do it now? Probably we should just let it
510 // connect to the lyxfunc in the single LyXView we
511 // support currently. (Lgb)
513 int action = lyxaction.LookupFunc(cmd.c_str());
518 rval = serv->func->Dispatch(action, arg.c_str());
520 rval = "Unknown command";
523 //modified june 1999 stefano@zool.su.se:
524 //all commands produce an INFO or ERROR message
525 //in the output pipe, even if they do not return
526 //anything. See chapter 4 of Customization doc.
527 if (action<0 || serv->func->errorStat())
531 buf += string(client) + ":" + cmd + ":" + rval + "\n";
532 serv->pipes.send(buf);
534 // !!! we don't do any error checking -
535 // if the client won't listen, the
536 // message is lost and others too
537 // maybe; so the client should empty
538 // the outpipe before issuing a request.
546 /* ---F+------------------------------------------------------------------ *\
547 Function : LyxNotifyClient
548 Called by : WorkAreaKeyPress
549 Purpose : send a notify messge to a client
550 Parameters: s - string to send
552 \* ---F------------------------------------------------------------------- */
554 void LyXServer::notifyClient(string const & s)
556 string buf = string("NOTIFY:") + s + "\n";