2 /* This file is part of
3 * ======================================================
5 * LyX, The Document Processor
7 * Copyright 1995 Matthias Ettrich
8 * Copyright 1995-2000 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
41 #include <sys/types.h>
46 #include FORMS_H_LOCATION
49 #pragma implementation
52 #include "lyxserver.h"
56 #include "LyXAction.h"
57 #include "support/lstrings.h"
62 #define OS2EMX_PLAIN_CHAR
63 #define INCL_DOSNMPIPES
64 #define INCL_DOSERRORS
66 #include "os2_errortable.h"
71 // provide an empty mkfifo() if we do not have one. This disables the
74 int mkfifo( char *__path, mode_t __mode ) {
80 /* === variables ========================================================= */
82 extern LyXAction lyxaction;
85 extern "C" void C_LyXComm_callback(int fd, void *v);
91 void LyXComm::openConnection() {
92 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
94 // If we are up, that's an error
96 lyxerr << "LyXComm: Already connected" << endl;
99 // We assume that we don't make it
102 if (pipename.empty()) return;
104 // --- prepare input pipe ---------------------------------------
106 string tmp = pipename + ".in";
112 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
113 // The current emx implementation of access() won't work with pipes.
114 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND,
115 NP_NOWAIT|0x01, 0600, 0600, 0);
116 if (rc == ERROR_PIPE_BUSY) {
118 if (access(tmp.c_str(), F_OK) == 0) {
120 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
121 << "If no other LyX program is active, please delete"
122 " the pipe by hand and try again." << endl;
127 if (mkfifo(tmp.c_str(), 0600) < 0) {
128 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
129 << strerror(errno) << endl;
132 infd = open(tmp.c_str(), O_RDONLY|O_NONBLOCK);
134 if (rc != NO_ERROR) {
135 errnum = TranslateOS2Error(rc);
136 lyxerr <<"LyXComm: Could not create pipe " << tmp
137 << strerror(errnum) << endl;
141 rc = DosConnectNPipe(fd);
142 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
143 errnum = TranslateOS2Error(rc);
144 lyxerr <<"LyXComm: Could not create pipe " << tmp
145 << strerror(errnum) << endl;
148 // Imported handles can be used both with OS/2 APIs and emx
149 // library functions.
150 infd = _imphandle(fd);
153 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
154 << strerror(errno) << endl;
157 fl_add_io_callback(infd, FL_READ, C_LyXComm_callback, this);
159 // --- prepare output pipe ---------------------------------------
161 tmp = pipename + ".out";
164 if (access(tmp.c_str(), F_OK) == 0) {
166 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX,
167 NP_NOWAIT|0x01, 0600, 0600, 0);
169 if (rc == ERROR_PIPE_BUSY) {
171 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
172 << "If no other LyX program is active, please delete"
173 " the pipe by hand and try again." << endl;
178 if (mkfifo(tmp.c_str(), 0600) < 0) {
179 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
180 << strerror(errno) << endl;
183 if (access(tmp.c_str(), F_OK) != 0) {
184 lyxerr << "LyXComm: Pipe " << tmp
185 << " does not exist" << endl;
188 outfd = open(tmp.c_str(), O_RDWR);
190 if (rc != NO_ERROR) {
191 errnum = TranslateOS2Error(rc);
192 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
193 << strerror(errnum) << endl;
196 rc = DosConnectNPipe(fd);
197 if (rc == ERROR_BAD_PIPE) {
198 lyxerr << "LyXComm: Pipe " << tmp
199 << " does not exist" << endl;
202 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
203 errnum = TranslateOS2Error(rc);
204 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
205 << strerror(errnum) << endl;
208 outfd = _imphandle(fd);
211 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
212 << strerror(errno) << endl;
215 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
216 lyxerr << "LyXComm: Could not set flags on pipe " << tmp
217 << '\n' << strerror(errno) << endl;
222 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
226 void LyXComm::closeConnection() {
231 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
233 if (pipename.empty()) {
238 lyxerr << "LyXComm: Already disconnected" << endl;
243 fl_remove_io_callback(infd, FL_READ, C_LyXComm_callback);
245 string tmp = pipename + ".in";
246 #ifdef __EMX__ // Notify the operating system.
247 rc = DosDisConnectNPipe(infd);
248 if (rc != NO_ERROR) {
249 errnum = TranslateOS2Error(rc);
250 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
251 << '\n' << strerror(errnum) << endl;
255 if (close(infd) < 0) {
256 lyxerr << "LyXComm: Could not close pipe " << tmp
257 << '\n' << strerror(errno) << endl;
259 #ifndef __EMX__ // OS/2 named pipes will be automatically removed.
260 if (unlink(tmp.c_str()) < 0){
261 lyxerr << "LyXComm: Could not remove pipe " << tmp
262 << '\n' << strerror(errno) << endl;
267 string tmp = pipename + ".out";
269 rc = DosDisConnectNPipe(outfd);
270 if (rc != NO_ERROR) {
271 errnum = TranslateOS2Error(rc);
272 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
273 << '\n' << strerror(errnum) << endl;
277 if (close(outfd) < 0) {
278 lyxerr << "LyXComm: Could not close pipe " << tmp
279 << '\n' << strerror(errno) << endl;
282 if (unlink(tmp.c_str()) < 0){
283 lyxerr << "LyXComm: Could not remove pipe " << tmp
284 << '\n' << strerror(errno) << endl;
291 // Receives messages and sends then to client
292 void LyXComm::callback(int fd, void *v)
294 LyXComm * c = static_cast<LyXComm*>(v);
296 if (lyxerr.debugging(Debug::LYXSERVER)) {
297 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
300 const int CMDBUFLEN = 100;
301 char charbuf[CMDBUFLEN];
303 // nb! make lsbuf a class-member for multiple sessions
308 // the single = is intended here.
309 while((status = read(fd, charbuf, CMDBUFLEN-1)))
310 {// break and return in loop
311 if(status > 0) // got something
313 charbuf[status]= '\0'; // turn it into a c string
314 lsbuf += strip(charbuf, '\r');
315 // commit any commands read
316 while(lsbuf.find('\n') != string::npos) // while still
320 // split() grabs the entire string if
321 // the delim /wasn't/ found. ?:-P
322 lsbuf= split(lsbuf, cmd,'\n');
323 lyxerr[Debug::LYXSERVER]
324 << "LyXComm: status:" << status
325 << ", lsbuf:" << lsbuf
326 << ", cmd:" << cmd << endl;
328 c->clientcb(c->client, cmd);
333 { // EAGAIN is not really an error , it means we're
334 // only reading too fast for the writing process on
335 // the other end of the pipe.
337 return; // up to libforms select-loop (*crunch*)
341 lyxerr << "LyXComm: " << strerror(errno) << endl;
344 lyxerr << "LyxComm: truncated command: "
348 break; // reset connection
351 c->closeConnection();
356 extern "C" void C_LyXComm_callback(int fd, void *v)
358 LyXComm::callback(fd, v);
362 void LyXComm::send(string const & msg) {
364 lyxerr << "LyXComm: Request to send empty string. Ignoring."
369 if (lyxerr.debugging(Debug::LYXSERVER)) {
370 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
373 if (pipename.empty()) return;
376 lyxerr << "LyXComm: Pipes are closed. Could not send "
378 } else if (write(outfd, msg.c_str(), msg.length()) < 0) {
379 lyxerr << "LyXComm: Error sending message: " << msg
380 << '\n' << strerror(errno)
381 << "\nLyXComm: Resetting connection" << endl;
388 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
389 if (rc != NO_ERROR) {
390 errnum = TranslateOS2Error(rc);
391 lyxerr << "LyXComm: Message could not be flushed: " << msg
392 << '\n' << strerror(errnum) << endl;
400 LyXServer::~LyXServer()
402 // say goodbye to clients so they stop sending messages
403 // modified june 1999 by stefano@zool.su.se to send as many bye
404 // messages as there are clients, each with client's name.
406 for (int i= 0; i<numclients; ++i) {
407 message = "LYXSRV:" + clients[i] + ":bye\n";
413 /* ---F+------------------------------------------------------------------ *\
414 Function : ServerCallback
416 Purpose : handle data gotten from communication
417 \* ---F------------------------------------------------------------------- */
419 void LyXServer::callback(LyXServer * serv, string const & msg)
421 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
422 << msg << '\'' << endl;
424 char const *p = msg.c_str();
426 // --- parse the string --------------------------------------------
428 // Format: LYXCMD:<client>:<func>:<argstring>\n
430 bool server_only = false;
432 // --- 1. check 'header' ---
433 if (strncmp(p, "LYXSRV:", 7) == 0) {
435 } else if(0!= strncmp(p, "LYXCMD:", 7)) {
436 lyxerr << "LyXServer: Unknown request" << endl;
441 // --- 2. for the moment ignore the client name ---
443 while(*p && *p != ':')
444 client += char(*p++);
448 // --- 3. get function name ---
450 while(*p && *p != ':')
453 // --- 4. parse the argument ---
455 if(!server_only && *p == ':' && *(++p)) {
456 while(*p && *p != '\n')
461 lyxerr[Debug::LYXSERVER]
462 << "LyXServer: Client: '" << client
463 << "' Command: '" << cmd
464 << "' Argument: '" << arg << '\'' << endl;
466 // --- lookup and exec the command ------------------
470 // return the greeting to inform the client that
472 if (cmd == "hello") {
474 if(serv->numclients == MAX_CLIENTS){ //paranoid check
475 lyxerr[Debug::LYXSERVER]
476 << "LyXServer: too many clients..."
480 int i= 0; //find place in clients[]
481 while (!serv->clients[i].empty()
482 && i<serv->numclients)
484 serv->clients[i] = client;
486 buf = "LYXSRV:" + client + ":hello\n";
487 lyxerr[Debug::LYXSERVER]
488 << "LyXServer: Greeting "
490 serv->pipes.send(buf);
491 } else if (cmd == "bye") {
492 // If clients == 0 maybe we should reset the pipes
493 // to prevent fake callbacks
494 int i = 0; //look if client is registered
495 for (; i < serv->numclients; ++i) {
496 if (serv->clients[i] == client) break;
498 if (i < serv->numclients) {
500 serv->clients[i].clear();
501 lyxerr[Debug::LYXSERVER]
502 << "LyXServer: Client "
503 << client << " said goodbye"
506 lyxerr[Debug::LYXSERVER]
507 << "LyXServer: ignoring bye messge from unregistered client"
511 lyxerr <<"LyXServer: Undefined server command "
512 << cmd << "." << endl;
518 // which lyxfunc should we let it connect to?
519 // The correct solution would be to have a
520 // specialized (non-gui) BufferView. But how do
521 // we do it now? Probably we should just let it
522 // connect to the lyxfunc in the single LyXView we
523 // support currently. (Lgb)
525 int action = lyxaction.LookupFunc(cmd.c_str());
530 rval = serv->func->Dispatch(cmd);
532 rval = "Unknown command";
535 //modified june 1999 stefano@zool.su.se:
536 //all commands produce an INFO or ERROR message
537 //in the output pipe, even if they do not return
538 //anything. See chapter 4 of Customization doc.
539 if (action<0 || serv->func->errorStat())
543 buf += string(client) + ":" + cmd + ":" + rval + "\n";
544 serv->pipes.send(buf);
546 // !!! we don't do any error checking -
547 // if the client won't listen, the
548 // message is lost and others too
549 // maybe; so the client should empty
550 // the outpipe before issuing a request.
558 /* ---F+------------------------------------------------------------------ *\
559 Function : LyxNotifyClient
560 Called by : WorkAreaKeyPress
561 Purpose : send a notify messge to a client
562 Parameters: s - string to send
564 \* ---F------------------------------------------------------------------- */
566 void LyXServer::notifyClient(string const & s)
568 string buf = string("NOTIFY:") + s + "\n";