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 "LyXAction.h"
58 #include "support/lstrings.h"
63 #define OS2EMX_PLAIN_CHAR
64 #define INCL_DOSNMPIPES
65 #define INCL_DOSERRORS
67 #include "os2_errortable.h"
70 // provide an empty mkfifo() if we do not have one. This disables the
73 int mkfifo( char *__path, mode_t __mode ) {
79 /* === variables ========================================================= */
81 extern LyXAction lyxaction;
84 extern "C" void C_LyXComm_callback(int fd, void *v);
90 void LyXComm::openConnection() {
91 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
93 // If we are up, that's an error
95 lyxerr << "LyXComm: Already connected" << endl;
98 // We assume that we don't make it
101 if (pipename.empty()) return;
103 // --- prepare input pipe ---------------------------------------
105 string tmp = pipename + ".in";
111 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
112 // The current emx implementation of access() won't work with pipes.
113 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND,
114 NP_NOWAIT|0x01, 0600, 0600, 0);
115 if (rc == ERROR_PIPE_BUSY) {
117 if (access(tmp.c_str(), F_OK) == 0) {
119 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
120 << "If no other LyX program is active, please delete"
121 " the pipe by hand and try again." << endl;
126 if (mkfifo(tmp.c_str(), 0600) < 0) {
127 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
128 << strerror(errno) << endl;
131 infd = open(tmp.c_str(), O_RDONLY|O_NONBLOCK);
133 if (rc != NO_ERROR) {
134 errnum = TranslateOS2Error(rc);
135 lyxerr <<"LyXComm: Could not create pipe " << tmp
136 << strerror(errnum) << endl;
140 rc = DosConnectNPipe(fd);
141 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
142 errnum = TranslateOS2Error(rc);
143 lyxerr <<"LyXComm: Could not create pipe " << tmp
144 << strerror(errnum) << endl;
147 // Imported handles can be used both with OS/2 APIs and emx
148 // library functions.
149 infd = _imphandle(fd);
152 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
153 << strerror(errno) << endl;
156 fl_add_io_callback(infd, FL_READ, C_LyXComm_callback, (void*)this);
158 // --- prepare output pipe ---------------------------------------
160 tmp = pipename + ".out";
163 if (access(tmp.c_str(), F_OK) == 0) {
165 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX,
166 NP_NOWAIT|0x01, 0600, 0600, 0);
168 if (rc == ERROR_PIPE_BUSY) {
170 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
171 << "If no other LyX program is active, please delete"
172 " the pipe by hand and try again." << endl;
177 if (mkfifo(tmp.c_str(), 0600) < 0) {
178 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
179 << strerror(errno) << endl;
182 if (access(tmp.c_str(), F_OK) != 0) {
183 lyxerr << "LyXComm: Pipe " << tmp
184 << " does not exist" << endl;
187 outfd = open(tmp.c_str(), O_RDWR);
189 if (rc != NO_ERROR) {
190 errnum = TranslateOS2Error(rc);
191 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
192 << strerror(errnum) << endl;
195 rc = DosConnectNPipe(fd);
196 if (rc == ERROR_BAD_PIPE) {
197 lyxerr << "LyXComm: Pipe " << tmp
198 << " does not exist" << endl;
201 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
202 errnum = TranslateOS2Error(rc);
203 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
204 << strerror(errnum) << endl;
207 outfd = _imphandle(fd);
210 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
211 << strerror(errno) << endl;
214 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
215 lyxerr << "LyXComm: Could not set flags on pipe " << tmp
216 << '\n' << strerror(errno) << endl;
221 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
225 void LyXComm::closeConnection() {
230 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
232 if (pipename.empty()) {
237 lyxerr << "LyXComm: Already disconnected" << endl;
242 fl_remove_io_callback(infd, FL_READ, C_LyXComm_callback);
244 string tmp = pipename + ".in";
245 #ifdef __EMX__ // Notify the operating system.
246 rc = DosDisConnectNPipe(infd);
247 if (rc != NO_ERROR) {
248 errnum = TranslateOS2Error(rc);
249 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
250 << '\n' << strerror(errnum) << endl;
254 if (close(infd) < 0) {
255 lyxerr << "LyXComm: Could not close pipe " << tmp
256 << '\n' << strerror(errno) << endl;
258 #ifndef __EMX__ // OS/2 named pipes will be automatically removed.
259 if (unlink(tmp.c_str()) < 0){
260 lyxerr << "LyXComm: Could not remove pipe " << tmp
261 << '\n' << strerror(errno) << endl;
266 string tmp = pipename + ".out";
268 rc = DosDisConnectNPipe(outfd);
269 if (rc != NO_ERROR) {
270 errnum = TranslateOS2Error(rc);
271 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
272 << '\n' << strerror(errnum) << endl;
276 if (close(outfd) < 0) {
277 lyxerr << "LyXComm: Could not close pipe " << tmp
278 << '\n' << strerror(errno) << endl;
281 if (unlink(tmp.c_str()) < 0){
282 lyxerr << "LyXComm: Could not remove pipe " << tmp
283 << '\n' << strerror(errno) << endl;
290 // Receives messages and sends then to client
291 void LyXComm::callback(int fd, void *v)
293 LyXComm * c = static_cast<LyXComm*>(v);
295 if (lyxerr.debugging(Debug::LYXSERVER)) {
296 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
299 const int CMDBUFLEN = 100;
300 char charbuf[CMDBUFLEN];
302 // nb! make lsbuf a class-member for multiple sessions
307 // the single = is intended here.
308 while((status = read(fd, charbuf, CMDBUFLEN-1)))
309 {// break and return in loop
310 if(status > 0) // got something
312 charbuf[status]= '\0'; // turn it into a c string
313 lsbuf += strip(charbuf, '\r');
314 // commit any commands read
315 while(lsbuf.find('\n') != string::npos) // while still
319 // split() grabs the entire string if
320 // the delim /wasn't/ found. ?:-P
321 lsbuf= split(lsbuf, cmd,'\n');
322 lyxerr[Debug::LYXSERVER]
323 << "LyXComm: status:" << status
324 << ", lsbuf:" << lsbuf
325 << ", cmd:" << cmd << endl;
327 c->clientcb(c->client, cmd);
332 { // EAGAIN is not really an error , it means we're
333 // only reading too fast for the writing process on
334 // the other end of the pipe.
336 return; // up to libforms select-loop (*crunch*)
340 lyxerr << "LyXComm: " << strerror(errno) << endl;
343 lyxerr << "LyxComm: truncated command: "
347 break; // reset connection
350 c->closeConnection();
355 extern "C" void C_LyXComm_callback(int fd, void *v)
357 LyXComm::callback(fd, v);
361 void LyXComm::send(string const & msg) {
363 lyxerr << "LyXComm: Request to send empty string. Ignoring."
368 if (lyxerr.debugging(Debug::LYXSERVER)) {
369 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
372 if (pipename.empty()) return;
375 lyxerr << "LyXComm: Pipes are closed. Could not send "
377 } else if (write(outfd, msg.c_str(), msg.length()) < 0) {
378 lyxerr << "LyXComm: Error sending message: " << msg
379 << '\n' << strerror(errno)
380 << "\nLyXComm: Resetting connection" << endl;
387 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
388 if (rc != NO_ERROR) {
389 errnum = TranslateOS2Error(rc);
390 lyxerr << "LyXComm: Message could not be flushed: " << msg
391 << '\n' << strerror(errnum) << endl;
399 LyXServer::~LyXServer()
401 // say goodbye to clients so they stop sending messages
402 // modified june 1999 by stefano@zool.su.se to send as many bye
403 // messages as there are clients, each with client's name.
405 for (int i= 0; i<numclients; i++) {
406 message = "LYXSRV:" + clients[i] + ":bye\n";
412 /* ---F+------------------------------------------------------------------ *\
413 Function : ServerCallback
415 Purpose : handle data gotten from communication
416 \* ---F------------------------------------------------------------------- */
418 void LyXServer::callback(LyXServer * serv, string const & msg)
420 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
421 << msg << '\'' << endl;
423 char const *p = msg.c_str();
425 // --- parse the string --------------------------------------------
427 // Format: LYXCMD:<client>:<func>:<argstring>\n
429 bool server_only = false;
431 // --- 1. check 'header' ---
432 if (strncmp(p, "LYXSRV:", 7) == 0) {
434 } else if(0!= strncmp(p, "LYXCMD:", 7)) {
435 lyxerr << "LyXServer: Unknown request" << endl;
440 // --- 2. for the moment ignore the client name ---
442 while(*p && *p != ':')
443 client += char(*p++);
447 // --- 3. get function name ---
449 while(*p && *p != ':')
452 // --- 4. parse the argument ---
454 if(!server_only && *p == ':' && *(++p)) {
455 while(*p && *p != '\n')
460 lyxerr[Debug::LYXSERVER]
461 << "LyXServer: Client: '" << client
462 << "' Command: '" << cmd
463 << "' Argument: '" << arg << '\'' << endl;
465 // --- lookup and exec the command ------------------
469 // return the greeting to inform the client that
471 if (cmd == "hello") {
473 if(serv->numclients == MAX_CLIENTS){ //paranoid check
474 lyxerr[Debug::LYXSERVER]
475 << "LyXServer: too many clients..."
479 int i= 0; //find place in clients[]
480 while (!serv->clients[i].empty()
481 && i<serv->numclients)
483 serv->clients[i] = client;
485 buf = "LYXSRV:" + client + ":hello\n";
486 lyxerr[Debug::LYXSERVER]
487 << "LyXServer: Greeting "
489 serv->pipes.send(buf);
490 } else if (cmd == "bye") {
491 // If clients == 0 maybe we should reset the pipes
492 // to prevent fake callbacks
493 int i; //look if client is registered
494 for (i= 0; i<serv->numclients; i++) {
495 if (serv->clients[i] == client) break;
497 if (i<serv->numclients) {
499 serv->clients[i].clear();
500 lyxerr[Debug::LYXSERVER]
501 << "LyXServer: Client "
502 << client << " said goodbye"
505 lyxerr[Debug::LYXSERVER]
506 << "LyXServer: ignoring bye messge from unregistered client"
510 lyxerr <<"LyXServer: Undefined server command "
511 << cmd << "." << endl;
517 // which lyxfunc should we let it connect to?
518 // The correct solution would be to have a
519 // specialized (non-gui) BufferView. But how do
520 // we do it now? Probably we should just let it
521 // connect to the lyxfunc in the single LyXView we
522 // support currently. (Lgb)
524 int action = lyxaction.LookupFunc(cmd.c_str());
529 rval = serv->func->Dispatch(cmd);
531 rval = "Unknown command";
534 //modified june 1999 stefano@zool.su.se:
535 //all commands produce an INFO or ERROR message
536 //in the output pipe, even if they do not return
537 //anything. See chapter 4 of Customization doc.
538 if (action<0 || serv->func->errorStat())
542 buf += string(client) + ":" + cmd + ":" + rval + "\n";
543 serv->pipes.send(buf);
545 // !!! we don't do any error checking -
546 // if the client won't listen, the
547 // message is lost and others too
548 // maybe; so the client should empty
549 // the outpipe before issuing a request.
557 /* ---F+------------------------------------------------------------------ *\
558 Function : LyxNotifyClient
559 Called by : WorkAreaKeyPress
560 Purpose : send a notify messge to a client
561 Parameters: s - string to send
563 \* ---F------------------------------------------------------------------- */
565 void LyXServer::notifyClient(string const & s)
567 string buf = string("NOTIFY:") + s + "\n";