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' ---
434 if (strncmp(p, "LYXSRV:", 7) == 0) {
436 } else if(0!= strncmp(p, "LYXCMD:", 7)) {
437 lyxerr << "LyXServer: Unknown request" << endl;
442 // --- 2. for the moment ignore the client name ---
444 while(*p && *p != ':')
445 client += char(*p++);
449 // --- 3. get function name ---
451 while(*p && *p != ':')
454 // --- 4. parse the argument ---
456 if(!server_only && *p == ':' && *(++p)) {
457 while(*p && *p != '\n')
462 lyxerr[Debug::LYXSERVER]
463 << "LyXServer: Client: '" << client
464 << "' Command: '" << cmd
465 << "' Argument: '" << arg << '\'' << endl;
467 // --- lookup and exec the command ------------------
471 // return the greeting to inform the client that
473 if (cmd == "hello") {
475 if(serv->numclients == MAX_CLIENTS){ //paranoid check
476 lyxerr[Debug::LYXSERVER]
477 << "LyXServer: too many clients..."
481 int i= 0; //find place in clients[]
482 while (!serv->clients[i].empty()
483 && i<serv->numclients)
485 serv->clients[i] = client;
487 buf = "LYXSRV:" + client + ":hello\n";
488 lyxerr[Debug::LYXSERVER]
489 << "LyXServer: Greeting "
491 serv->pipes.send(buf);
492 } else if (cmd == "bye") {
493 // If clients == 0 maybe we should reset the pipes
494 // to prevent fake callbacks
495 int i = 0; //look if client is registered
496 for (; i < serv->numclients; ++i) {
497 if (serv->clients[i] == client) break;
499 if (i < serv->numclients) {
501 serv->clients[i].erase();
502 lyxerr[Debug::LYXSERVER]
503 << "LyXServer: Client "
504 << client << " said goodbye"
507 lyxerr[Debug::LYXSERVER]
508 << "LyXServer: ignoring bye messge from unregistered client"
512 lyxerr <<"LyXServer: Undefined server command "
513 << cmd << "." << endl;
519 // which lyxfunc should we let it connect to?
520 // The correct solution would be to have a
521 // specialized (non-gui) BufferView. But how do
522 // we do it now? Probably we should just let it
523 // connect to the lyxfunc in the single LyXView we
524 // support currently. (Lgb)
526 int action = lyxaction.LookupFunc(cmd.c_str());
531 rval = serv->func->Dispatch(action, arg.c_str());
533 rval = "Unknown command";
536 //modified june 1999 stefano@zool.su.se:
537 //all commands produce an INFO or ERROR message
538 //in the output pipe, even if they do not return
539 //anything. See chapter 4 of Customization doc.
540 if (action<0 || serv->func->errorStat())
544 buf += string(client) + ":" + cmd + ":" + rval + "\n";
545 serv->pipes.send(buf);
547 // !!! we don't do any error checking -
548 // if the client won't listen, the
549 // message is lost and others too
550 // maybe; so the client should empty
551 // the outpipe before issuing a request.
559 /* ---F+------------------------------------------------------------------ *\
560 Function : LyxNotifyClient
561 Called by : WorkAreaKeyPress
562 Purpose : send a notify messge to a client
563 Parameters: s - string to send
565 \* ---F------------------------------------------------------------------- */
567 void LyXServer::notifyClient(string const & s)
569 string buf = string("NOTIFY:") + s + "\n";