1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
12 Docu : To use the lyxserver define the name of the pipe in your
14 \serverpipe "/home/myhome/.lyxpipe"
15 Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
16 Each message consists of a single line in ASCII. Input lines
17 (client -> LyX) have the following format:
18 "LYXCMD:<clientname>:<functionname>:<argument>"
19 Answers from LyX look like this:
20 "INFO:<clientname>:<functionname>:<data>"
21 [asierra970531] Or like this in case of error:
22 "ERROR:<clientname>:<functionname>:<error message>"
23 where <clientname> and <functionname> are just echoed.
24 If LyX notifies about a user defined extension key-sequence,
25 the line looks like this:
26 "NOTIFY:<key-sequence>"
27 [asierra970531] New server-only messages to implement a simple protocol
28 "LYXSRV:<clientname>:<protocol message>"
29 where <protocol message> can be "hello" or "bye". If hello is
30 received LyX will inform the client that it's listening its
31 messages, and 'bye' will inform that lyx is closing.
33 See development/server_monitor.c for an example client.
34 Purpose: implement a client/server lib for LyX
39 #include <sys/types.h>
44 #include FORMS_H_LOCATION
47 #pragma implementation
50 #include "lyxserver.h"
53 #include "LyXAction.h"
55 #include "support/lstrings.h"
56 #include "support/lyxlib.h"
61 #define OS2EMX_PLAIN_CHAR
62 #define INCL_DOSNMPIPES
63 #define INCL_DOSERRORS
65 #include "os2_errortable.h"
70 // provide an empty mkfifo() if we do not have one. This disables the
73 int mkfifo(char const * __path, mode_t __mode) {
79 /* === variables ========================================================= */
81 extern LyXAction lyxaction;
88 void C_LyXComm_callback(int fd, void *v)
90 LyXComm::callback(fd, v);
99 void LyXComm::openConnection()
101 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
103 // If we are up, that's an error
105 lyxerr << "LyXComm: Already connected" << endl;
108 // We assume that we don't make it
111 if (pipename.empty()) return;
113 // --- prepare input pipe ---------------------------------------
115 string tmp = pipename + ".in";
121 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
122 // The current emx implementation of access() won't work with pipes.
123 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND,
124 NP_NOWAIT|0x01, 0600, 0600, 0);
125 if (rc == ERROR_PIPE_BUSY)
127 if (::access(tmp.c_str(), F_OK) == 0)
130 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
131 << "If no other LyX program is active, please delete"
132 " the pipe by hand and try again." << endl;
137 if (::mkfifo(tmp.c_str(), 0600) < 0) {
138 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
139 << strerror(errno) << endl;
142 infd = ::open(tmp.c_str(), O_RDONLY|O_NONBLOCK);
144 if (rc != NO_ERROR) {
145 errnum = TranslateOS2Error(rc);
146 lyxerr <<"LyXComm: Could not create pipe " << tmp
147 << strerror(errnum) << endl;
151 rc = DosConnectNPipe(fd);
152 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
153 errnum = TranslateOS2Error(rc);
154 lyxerr <<"LyXComm: Could not create pipe " << tmp
155 << strerror(errnum) << endl;
158 // Imported handles can be used both with OS/2 APIs and emx
159 // library functions.
160 infd = _imphandle(fd);
163 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
164 << strerror(errno) << endl;
167 fl_add_io_callback(infd, FL_READ, C_LyXComm_callback, this);
169 // --- prepare output pipe ---------------------------------------
171 tmp = pipename + ".out";
174 if (::access(tmp.c_str(), F_OK) == 0)
176 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX,
177 NP_NOWAIT|0x01, 0600, 0600, 0);
179 if (rc == ERROR_PIPE_BUSY)
182 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
183 << "If no other LyX program is active, please delete"
184 " the pipe by hand and try again." << endl;
189 if (::mkfifo(tmp.c_str(), 0600) < 0) {
190 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
191 << strerror(errno) << endl;
194 if (::access(tmp.c_str(), F_OK) != 0) {
195 lyxerr << "LyXComm: Pipe " << tmp
196 << " does not exist" << endl;
199 outfd = ::open(tmp.c_str(), O_RDWR);
201 if (rc != NO_ERROR) {
202 errnum = TranslateOS2Error(rc);
203 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
204 << strerror(errnum) << endl;
207 rc = DosConnectNPipe(fd);
208 if (rc == ERROR_BAD_PIPE) {
209 lyxerr << "LyXComm: Pipe " << tmp
210 << " does not exist" << endl;
213 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
214 errnum = TranslateOS2Error(rc);
215 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
216 << strerror(errnum) << endl;
219 outfd = _imphandle(fd);
222 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
223 << strerror(errno) << endl;
226 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
227 lyxerr << "LyXComm: Could not set flags on pipe " << tmp
228 << '\n' << strerror(errno) << endl;
233 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
238 void LyXComm::closeConnection()
244 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
246 if (pipename.empty()) {
251 lyxerr << "LyXComm: Already disconnected" << endl;
256 fl_remove_io_callback(infd, FL_READ, C_LyXComm_callback);
258 string tmp = pipename + ".in";
259 #ifdef __EMX__ // Notify the operating system.
260 rc = DosDisConnectNPipe(infd);
261 if (rc != NO_ERROR) {
262 errnum = TranslateOS2Error(rc);
263 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
264 << '\n' << strerror(errnum) << endl;
268 if (close(infd) < 0) {
269 lyxerr << "LyXComm: Could not close pipe " << tmp
270 << '\n' << strerror(errno) << endl;
272 #ifndef __EMX__ // OS/2 named pipes will be automatically removed.
273 if (lyx::unlink(tmp) < 0){
274 lyxerr << "LyXComm: Could not remove pipe " << tmp
275 << '\n' << strerror(errno) << endl;
280 string tmp = pipename + ".out";
282 rc = DosDisConnectNPipe(outfd);
283 if (rc != NO_ERROR) {
284 errnum = TranslateOS2Error(rc);
285 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
286 << '\n' << strerror(errnum) << endl;
290 if (::close(outfd) < 0) {
291 lyxerr << "LyXComm: Could not close pipe " << tmp
292 << '\n' << strerror(errno) << endl;
295 if (lyx::unlink(tmp) < 0){
296 lyxerr << "LyXComm: Could not remove pipe " << tmp
297 << '\n' << strerror(errno) << endl;
305 // Receives messages and sends then to client
306 void LyXComm::callback(int fd, void *v)
308 LyXComm * c = static_cast<LyXComm*>(v);
310 if (lyxerr.debugging(Debug::LYXSERVER)) {
311 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
314 const int CMDBUFLEN = 100;
315 char charbuf[CMDBUFLEN];
317 // nb! make lsbuf a class-member for multiple sessions
322 // the single = is intended here.
323 while((status = read(fd, charbuf, CMDBUFLEN-1)))
324 {// break and return in loop
325 if (status > 0) // got something
327 charbuf[status]= '\0'; // turn it into a c string
328 lsbuf += strip(charbuf, '\r');
329 // commit any commands read
330 while(lsbuf.find('\n') != string::npos) // while still
334 // split() grabs the entire string if
335 // the delim /wasn't/ found. ?:-P
336 lsbuf= split(lsbuf, cmd,'\n');
337 lyxerr[Debug::LYXSERVER]
338 << "LyXComm: status:" << status
339 << ", lsbuf:" << lsbuf
340 << ", cmd:" << cmd << endl;
342 c->clientcb(c->client, cmd);
347 { // EAGAIN is not really an error , it means we're
348 // only reading too fast for the writing process on
349 // the other end of the pipe.
351 return; // up to libforms select-loop (*crunch*)
355 lyxerr << "LyXComm: " << strerror(errno) << endl;
358 lyxerr << "LyxComm: truncated command: "
362 break; // reset connection
365 c->closeConnection();
371 void LyXComm::send(string const & msg)
374 lyxerr << "LyXComm: Request to send empty string. Ignoring."
379 if (lyxerr.debugging(Debug::LYXSERVER)) {
380 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
383 if (pipename.empty()) return;
386 lyxerr << "LyXComm: Pipes are closed. Could not send "
388 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
389 lyxerr << "LyXComm: Error sending message: " << msg
390 << '\n' << strerror(errno)
391 << "\nLyXComm: Resetting connection" << endl;
398 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
399 if (rc != NO_ERROR) {
400 errnum = TranslateOS2Error(rc);
401 lyxerr << "LyXComm: Message could not be flushed: " << msg
402 << '\n' << strerror(errnum) << endl;
410 LyXServer::~LyXServer()
412 // say goodbye to clients so they stop sending messages
413 // modified june 1999 by stefano@zool.su.se to send as many bye
414 // messages as there are clients, each with client's name.
416 for (int i= 0; i<numclients; ++i) {
417 message = "LYXSRV:" + clients[i] + ":bye\n";
423 /* ---F+------------------------------------------------------------------ *\
424 Function : ServerCallback
426 Purpose : handle data gotten from communication
427 \* ---F------------------------------------------------------------------- */
429 void LyXServer::callback(LyXServer * serv, string const & msg)
431 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
432 << msg << '\'' << endl;
434 char const * p = msg.c_str();
436 // --- parse the string --------------------------------------------
438 // Format: LYXCMD:<client>:<func>:<argstring>\n
440 bool server_only = false;
442 // --- 1. check 'header' ---
444 if (compare(p, "LYXSRV:", 7) == 0) {
446 } else if (0 != compare(p, "LYXCMD:", 7)) {
447 lyxerr << "LyXServer: Unknown request" << endl;
452 // --- 2. for the moment ignore the client name ---
454 while(*p && *p != ':')
455 client += char(*p++);
459 // --- 3. get function name ---
461 while(*p && *p != ':')
464 // --- 4. parse the argument ---
466 if (!server_only && *p == ':' && *(++p)) {
467 while(*p && *p != '\n')
472 lyxerr[Debug::LYXSERVER]
473 << "LyXServer: Client: '" << client
474 << "' Command: '" << cmd
475 << "' Argument: '" << arg << '\'' << endl;
477 // --- lookup and exec the command ------------------
481 // return the greeting to inform the client that
483 if (cmd == "hello") {
485 if (serv->numclients == MAX_CLIENTS){ //paranoid check
486 lyxerr[Debug::LYXSERVER]
487 << "LyXServer: too many clients..."
491 int i= 0; //find place in clients[]
492 while (!serv->clients[i].empty()
493 && i<serv->numclients)
495 serv->clients[i] = client;
497 buf = "LYXSRV:" + client + ":hello\n";
498 lyxerr[Debug::LYXSERVER]
499 << "LyXServer: Greeting "
501 serv->pipes.send(buf);
502 } else if (cmd == "bye") {
503 // If clients == 0 maybe we should reset the pipes
504 // to prevent fake callbacks
505 int i = 0; //look if client is registered
506 for (; i < serv->numclients; ++i) {
507 if (serv->clients[i] == client) break;
509 if (i < serv->numclients) {
511 serv->clients[i].erase();
512 lyxerr[Debug::LYXSERVER]
513 << "LyXServer: Client "
514 << client << " said goodbye"
517 lyxerr[Debug::LYXSERVER]
518 << "LyXServer: ignoring bye messge from unregistered client"
522 lyxerr <<"LyXServer: Undefined server command "
523 << cmd << "." << endl;
529 // which lyxfunc should we let it connect to?
530 // The correct solution would be to have a
531 // specialized (non-gui) BufferView. But how do
532 // we do it now? Probably we should just let it
533 // connect to the lyxfunc in the single LyXView we
534 // support currently. (Lgb)
536 int action = lyxaction.LookupFunc(cmd);
541 rval = serv->func->dispatch(action, arg);
543 rval = "Unknown command";
546 //modified june 1999 stefano@zool.su.se:
547 //all commands produce an INFO or ERROR message
548 //in the output pipe, even if they do not return
549 //anything. See chapter 4 of Customization doc.
550 if (action<0 || serv->func->errorStat())
554 buf += string(client) + ":" + cmd + ":" + rval + "\n";
555 serv->pipes.send(buf);
557 // !!! we don't do any error checking -
558 // if the client won't listen, the
559 // message is lost and others too
560 // maybe; so the client should empty
561 // the outpipe before issuing a request.
569 /* ---F+------------------------------------------------------------------ *\
570 Function : LyxNotifyClient
571 Called by : WorkAreaKeyPress
572 Purpose : send a notify messge to a client
573 Parameters: s - string to send
575 \* ---F------------------------------------------------------------------- */
577 void LyXServer::notifyClient(string const & s)
579 string buf = string("NOTIFY:") + s + "\n";