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
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"
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;
83 extern "C" void C_LyXComm_callback(int fd, void *v);
89 void LyXComm::openConnection() {
90 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
92 // If we are up, that's an error
94 lyxerr << "LyXComm: Already connected" << endl;
97 // We assume that we don't make it
100 if (pipename.empty()) return;
102 // --- prepare input pipe ---------------------------------------
104 string tmp = pipename + ".in";
110 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
111 // The current emx implementation of access() won't work with pipes.
112 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND,
113 NP_NOWAIT|0x01, 0600, 0600, 0);
114 if (rc == ERROR_PIPE_BUSY) {
116 if (access(tmp.c_str(), F_OK) == 0) {
118 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
119 << "If no other LyX program is active, please delete"
120 " the pipe by hand and try again." << endl;
125 if (mkfifo(tmp.c_str(), 0600) < 0) {
126 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
127 << strerror(errno) << endl;
130 infd = open(tmp.c_str(), O_RDONLY|O_NONBLOCK);
132 if (rc != NO_ERROR) {
133 errnum = TranslateOS2Error(rc);
134 lyxerr <<"LyXComm: Could not create pipe " << tmp
135 << strerror(errnum) << endl;
139 rc = DosConnectNPipe(fd);
140 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
141 errnum = TranslateOS2Error(rc);
142 lyxerr <<"LyXComm: Could not create pipe " << tmp
143 << strerror(errnum) << endl;
146 // Imported handles can be used both with OS/2 APIs and emx
147 // library functions.
148 infd = _imphandle(fd);
151 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
152 << strerror(errno) << endl;
155 fl_add_io_callback(infd, FL_READ, C_LyXComm_callback, this);
157 // --- prepare output pipe ---------------------------------------
159 tmp = pipename + ".out";
162 if (access(tmp.c_str(), F_OK) == 0) {
164 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX,
165 NP_NOWAIT|0x01, 0600, 0600, 0);
167 if (rc == ERROR_PIPE_BUSY) {
169 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
170 << "If no other LyX program is active, please delete"
171 " the pipe by hand and try again." << endl;
176 if (mkfifo(tmp.c_str(), 0600) < 0) {
177 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
178 << strerror(errno) << endl;
181 if (access(tmp.c_str(), F_OK) != 0) {
182 lyxerr << "LyXComm: Pipe " << tmp
183 << " does not exist" << endl;
186 outfd = open(tmp.c_str(), O_RDWR);
188 if (rc != NO_ERROR) {
189 errnum = TranslateOS2Error(rc);
190 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
191 << strerror(errnum) << endl;
194 rc = DosConnectNPipe(fd);
195 if (rc == ERROR_BAD_PIPE) {
196 lyxerr << "LyXComm: Pipe " << tmp
197 << " does not exist" << endl;
200 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
201 errnum = TranslateOS2Error(rc);
202 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
203 << strerror(errnum) << endl;
206 outfd = _imphandle(fd);
209 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
210 << strerror(errno) << endl;
213 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
214 lyxerr << "LyXComm: Could not set flags on pipe " << tmp
215 << '\n' << strerror(errno) << endl;
220 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
224 void LyXComm::closeConnection() {
229 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
231 if (pipename.empty()) {
236 lyxerr << "LyXComm: Already disconnected" << endl;
241 fl_remove_io_callback(infd, FL_READ, C_LyXComm_callback);
243 string tmp = pipename + ".in";
244 #ifdef __EMX__ // Notify the operating system.
245 rc = DosDisConnectNPipe(infd);
246 if (rc != NO_ERROR) {
247 errnum = TranslateOS2Error(rc);
248 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
249 << '\n' << strerror(errnum) << endl;
253 if (close(infd) < 0) {
254 lyxerr << "LyXComm: Could not close pipe " << tmp
255 << '\n' << strerror(errno) << endl;
257 #ifndef __EMX__ // OS/2 named pipes will be automatically removed.
258 if (unlink(tmp.c_str()) < 0){
259 lyxerr << "LyXComm: Could not remove pipe " << tmp
260 << '\n' << strerror(errno) << endl;
265 string tmp = pipename + ".out";
267 rc = DosDisConnectNPipe(outfd);
268 if (rc != NO_ERROR) {
269 errnum = TranslateOS2Error(rc);
270 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
271 << '\n' << strerror(errnum) << endl;
275 if (close(outfd) < 0) {
276 lyxerr << "LyXComm: Could not close pipe " << tmp
277 << '\n' << strerror(errno) << endl;
280 if (unlink(tmp.c_str()) < 0){
281 lyxerr << "LyXComm: Could not remove pipe " << tmp
282 << '\n' << strerror(errno) << endl;
289 // Receives messages and sends then to client
290 void LyXComm::callback(int fd, void *v)
292 LyXComm * c = static_cast<LyXComm*>(v);
294 if (lyxerr.debugging(Debug::LYXSERVER)) {
295 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
298 const int CMDBUFLEN = 100;
299 char charbuf[CMDBUFLEN];
301 // nb! make lsbuf a class-member for multiple sessions
306 // the single = is intended here.
307 while((status = read(fd, charbuf, CMDBUFLEN-1)))
308 {// break and return in loop
309 if(status > 0) // got something
311 charbuf[status]= '\0'; // turn it into a c string
312 lsbuf += strip(charbuf, '\r');
313 // commit any commands read
314 while(lsbuf.find('\n') != string::npos) // while still
318 // split() grabs the entire string if
319 // the delim /wasn't/ found. ?:-P
320 lsbuf= split(lsbuf, cmd,'\n');
321 lyxerr[Debug::LYXSERVER]
322 << "LyXComm: status:" << status
323 << ", lsbuf:" << lsbuf
324 << ", cmd:" << cmd << endl;
326 c->clientcb(c->client, cmd);
331 { // EAGAIN is not really an error , it means we're
332 // only reading too fast for the writing process on
333 // the other end of the pipe.
335 return; // up to libforms select-loop (*crunch*)
339 lyxerr << "LyXComm: " << strerror(errno) << endl;
342 lyxerr << "LyxComm: truncated command: "
346 break; // reset connection
349 c->closeConnection();
354 extern "C" void C_LyXComm_callback(int fd, void *v)
356 LyXComm::callback(fd, v);
360 void LyXComm::send(string const & msg) {
362 lyxerr << "LyXComm: Request to send empty string. Ignoring."
367 if (lyxerr.debugging(Debug::LYXSERVER)) {
368 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
371 if (pipename.empty()) return;
374 lyxerr << "LyXComm: Pipes are closed. Could not send "
376 } else if (write(outfd, msg.c_str(), msg.length()) < 0) {
377 lyxerr << "LyXComm: Error sending message: " << msg
378 << '\n' << strerror(errno)
379 << "\nLyXComm: Resetting connection" << endl;
386 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
387 if (rc != NO_ERROR) {
388 errnum = TranslateOS2Error(rc);
389 lyxerr << "LyXComm: Message could not be flushed: " << msg
390 << '\n' << strerror(errnum) << endl;
398 LyXServer::~LyXServer()
400 // say goodbye to clients so they stop sending messages
401 // modified june 1999 by stefano@zool.su.se to send as many bye
402 // messages as there are clients, each with client's name.
404 for (int i= 0; i<numclients; i++) {
405 message = "LYXSRV:" + clients[i] + ":bye\n";
411 /* ---F+------------------------------------------------------------------ *\
412 Function : ServerCallback
414 Purpose : handle data gotten from communication
415 \* ---F------------------------------------------------------------------- */
417 void LyXServer::callback(LyXServer * serv, string const & msg)
419 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
420 << msg << '\'' << endl;
422 char const *p = msg.c_str();
424 // --- parse the string --------------------------------------------
426 // Format: LYXCMD:<client>:<func>:<argstring>\n
428 bool server_only = false;
430 // --- 1. check 'header' ---
431 if (strncmp(p, "LYXSRV:", 7) == 0) {
433 } else if(0!= strncmp(p, "LYXCMD:", 7)) {
434 lyxerr << "LyXServer: Unknown request" << endl;
439 // --- 2. for the moment ignore the client name ---
441 while(*p && *p != ':')
442 client += char(*p++);
446 // --- 3. get function name ---
448 while(*p && *p != ':')
451 // --- 4. parse the argument ---
453 if(!server_only && *p == ':' && *(++p)) {
454 while(*p && *p != '\n')
459 lyxerr[Debug::LYXSERVER]
460 << "LyXServer: Client: '" << client
461 << "' Command: '" << cmd
462 << "' Argument: '" << arg << '\'' << endl;
464 // --- lookup and exec the command ------------------
468 // return the greeting to inform the client that
470 if (cmd == "hello") {
472 if(serv->numclients == MAX_CLIENTS){ //paranoid check
473 lyxerr[Debug::LYXSERVER]
474 << "LyXServer: too many clients..."
478 int i= 0; //find place in clients[]
479 while (!serv->clients[i].empty()
480 && i<serv->numclients)
482 serv->clients[i] = client;
484 buf = "LYXSRV:" + client + ":hello\n";
485 lyxerr[Debug::LYXSERVER]
486 << "LyXServer: Greeting "
488 serv->pipes.send(buf);
489 } else if (cmd == "bye") {
490 // If clients == 0 maybe we should reset the pipes
491 // to prevent fake callbacks
492 int i; //look if client is registered
493 for (i= 0; i<serv->numclients; i++) {
494 if (serv->clients[i] == client) break;
496 if (i<serv->numclients) {
498 serv->clients[i].clear();
499 lyxerr[Debug::LYXSERVER]
500 << "LyXServer: Client "
501 << client << " said goodbye"
504 lyxerr[Debug::LYXSERVER]
505 << "LyXServer: ignoring bye messge from unregistered client"
509 lyxerr <<"LyXServer: Undefined server command "
510 << cmd << "." << endl;
516 // which lyxfunc should we let it connect to?
517 // The correct solution would be to have a
518 // specialized (non-gui) BufferView. But how do
519 // we do it now? Probably we should just let it
520 // connect to the lyxfunc in the single LyXView we
521 // support currently. (Lgb)
523 int action = lyxaction.LookupFunc(cmd.c_str());
528 rval = serv->func->Dispatch(cmd);
530 rval = "Unknown command";
533 //modified june 1999 stefano@zool.su.se:
534 //all commands produce an INFO or ERROR message
535 //in the output pipe, even if they do not return
536 //anything. See chapter 4 of Customization doc.
537 if (action<0 || serv->func->errorStat())
541 buf += string(client) + ":" + cmd + ":" + rval + "\n";
542 serv->pipes.send(buf);
544 // !!! we don't do any error checking -
545 // if the client won't listen, the
546 // message is lost and others too
547 // maybe; so the client should empty
548 // the outpipe before issuing a request.
556 /* ---F+------------------------------------------------------------------ *\
557 Function : LyxNotifyClient
558 Called by : WorkAreaKeyPress
559 Purpose : send a notify messge to a client
560 Parameters: s - string to send
562 \* ---F------------------------------------------------------------------- */
564 void LyXServer::notifyClient(string const & s)
566 string buf = string("NOTIFY:") + s + "\n";