2 /* This file is part of
3 * ======================================================
5 * LyX, The Document Processor
7 * Copyright 1995 Matthias Ettrich
8 * Copyright 1995-2001 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"
55 #include "LyXAction.h"
57 #include "support/lstrings.h"
58 #include "support/lyxlib.h"
63 #define OS2EMX_PLAIN_CHAR
64 #define INCL_DOSNMPIPES
65 #define INCL_DOSERRORS
67 #include "os2_errortable.h"
72 // provide an empty mkfifo() if we do not have one. This disables the
75 int mkfifo(char const * __path, mode_t __mode) {
81 /* === variables ========================================================= */
83 extern LyXAction lyxaction;
86 extern "C" void C_LyXComm_callback(int fd, void *v);
92 void LyXComm::openConnection() {
93 lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
95 // If we are up, that's an error
97 lyxerr << "LyXComm: Already connected" << endl;
100 // We assume that we don't make it
103 if (pipename.empty()) return;
105 // --- prepare input pipe ---------------------------------------
107 string tmp = pipename + ".in";
113 // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
114 // The current emx implementation of access() won't work with pipes.
115 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND,
116 NP_NOWAIT|0x01, 0600, 0600, 0);
117 if (rc == ERROR_PIPE_BUSY) {
119 if (::access(tmp.c_str(), F_OK) == 0) {
121 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
122 << "If no other LyX program is active, please delete"
123 " the pipe by hand and try again." << endl;
128 if (::mkfifo(tmp.c_str(), 0600) < 0) {
129 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
130 << strerror(errno) << endl;
133 infd = ::open(tmp.c_str(), O_RDONLY|O_NONBLOCK);
135 if (rc != NO_ERROR) {
136 errnum = TranslateOS2Error(rc);
137 lyxerr <<"LyXComm: Could not create pipe " << tmp
138 << strerror(errnum) << endl;
142 rc = DosConnectNPipe(fd);
143 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
144 errnum = TranslateOS2Error(rc);
145 lyxerr <<"LyXComm: Could not create pipe " << tmp
146 << strerror(errnum) << endl;
149 // Imported handles can be used both with OS/2 APIs and emx
150 // library functions.
151 infd = _imphandle(fd);
154 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
155 << strerror(errno) << endl;
158 fl_add_io_callback(infd, FL_READ, C_LyXComm_callback, this);
160 // --- prepare output pipe ---------------------------------------
162 tmp = pipename + ".out";
165 if (::access(tmp.c_str(), F_OK) == 0) {
167 rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX,
168 NP_NOWAIT|0x01, 0600, 0600, 0);
170 if (rc == ERROR_PIPE_BUSY) {
172 lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n"
173 << "If no other LyX program is active, please delete"
174 " the pipe by hand and try again." << endl;
179 if (::mkfifo(tmp.c_str(), 0600) < 0) {
180 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
181 << strerror(errno) << endl;
184 if (::access(tmp.c_str(), F_OK) != 0) {
185 lyxerr << "LyXComm: Pipe " << tmp
186 << " does not exist" << endl;
189 outfd = ::open(tmp.c_str(), O_RDWR);
191 if (rc != NO_ERROR) {
192 errnum = TranslateOS2Error(rc);
193 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
194 << strerror(errnum) << endl;
197 rc = DosConnectNPipe(fd);
198 if (rc == ERROR_BAD_PIPE) {
199 lyxerr << "LyXComm: Pipe " << tmp
200 << " does not exist" << endl;
203 if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
204 errnum = TranslateOS2Error(rc);
205 lyxerr << "LyXComm: Could not create pipe " << tmp << '\n'
206 << strerror(errnum) << endl;
209 outfd = _imphandle(fd);
212 lyxerr << "LyXComm: Could not open pipe " << tmp << '\n'
213 << strerror(errno) << endl;
216 if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
217 lyxerr << "LyXComm: Could not set flags on pipe " << tmp
218 << '\n' << strerror(errno) << endl;
223 lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
227 void LyXComm::closeConnection() {
232 lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
234 if (pipename.empty()) {
239 lyxerr << "LyXComm: Already disconnected" << endl;
244 fl_remove_io_callback(infd, FL_READ, C_LyXComm_callback);
246 string tmp = pipename + ".in";
247 #ifdef __EMX__ // Notify the operating system.
248 rc = DosDisConnectNPipe(infd);
249 if (rc != NO_ERROR) {
250 errnum = TranslateOS2Error(rc);
251 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
252 << '\n' << strerror(errnum) << endl;
256 if (close(infd) < 0) {
257 lyxerr << "LyXComm: Could not close pipe " << tmp
258 << '\n' << strerror(errno) << endl;
260 #ifndef __EMX__ // OS/2 named pipes will be automatically removed.
261 if (lyx::unlink(tmp) < 0){
262 lyxerr << "LyXComm: Could not remove pipe " << tmp
263 << '\n' << strerror(errno) << endl;
268 string tmp = pipename + ".out";
270 rc = DosDisConnectNPipe(outfd);
271 if (rc != NO_ERROR) {
272 errnum = TranslateOS2Error(rc);
273 lyxerr << "LyXComm: Could not disconnect pipe " << tmp
274 << '\n' << strerror(errnum) << endl;
278 if (::close(outfd) < 0) {
279 lyxerr << "LyXComm: Could not close pipe " << tmp
280 << '\n' << strerror(errno) << endl;
283 if (lyx::unlink(tmp) < 0){
284 lyxerr << "LyXComm: Could not remove pipe " << tmp
285 << '\n' << strerror(errno) << endl;
292 // Receives messages and sends then to client
293 void LyXComm::callback(int fd, void *v)
295 LyXComm * c = static_cast<LyXComm*>(v);
297 if (lyxerr.debugging(Debug::LYXSERVER)) {
298 lyxerr << "LyXComm: Receiving from fd " << fd << endl;
301 const int CMDBUFLEN = 100;
302 char charbuf[CMDBUFLEN];
304 // nb! make lsbuf a class-member for multiple sessions
309 // the single = is intended here.
310 while((status = read(fd, charbuf, CMDBUFLEN-1)))
311 {// break and return in loop
312 if (status > 0) // got something
314 charbuf[status]= '\0'; // turn it into a c string
315 lsbuf += strip(charbuf, '\r');
316 // commit any commands read
317 while(lsbuf.find('\n') != string::npos) // while still
321 // split() grabs the entire string if
322 // the delim /wasn't/ found. ?:-P
323 lsbuf= split(lsbuf, cmd,'\n');
324 lyxerr[Debug::LYXSERVER]
325 << "LyXComm: status:" << status
326 << ", lsbuf:" << lsbuf
327 << ", cmd:" << cmd << endl;
329 c->clientcb(c->client, cmd);
334 { // EAGAIN is not really an error , it means we're
335 // only reading too fast for the writing process on
336 // the other end of the pipe.
338 return; // up to libforms select-loop (*crunch*)
342 lyxerr << "LyXComm: " << strerror(errno) << endl;
345 lyxerr << "LyxComm: truncated command: "
349 break; // reset connection
352 c->closeConnection();
357 extern "C" void C_LyXComm_callback(int fd, void *v)
359 LyXComm::callback(fd, v);
363 void LyXComm::send(string const & msg) {
365 lyxerr << "LyXComm: Request to send empty string. Ignoring."
370 if (lyxerr.debugging(Debug::LYXSERVER)) {
371 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
374 if (pipename.empty()) return;
377 lyxerr << "LyXComm: Pipes are closed. Could not send "
379 } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
380 lyxerr << "LyXComm: Error sending message: " << msg
381 << '\n' << strerror(errno)
382 << "\nLyXComm: Resetting connection" << endl;
389 rc = DosResetBuffer(outfd); // To avoid synchronization problems.
390 if (rc != NO_ERROR) {
391 errnum = TranslateOS2Error(rc);
392 lyxerr << "LyXComm: Message could not be flushed: " << msg
393 << '\n' << strerror(errnum) << endl;
401 LyXServer::~LyXServer()
403 // say goodbye to clients so they stop sending messages
404 // modified june 1999 by stefano@zool.su.se to send as many bye
405 // messages as there are clients, each with client's name.
407 for (int i= 0; i<numclients; ++i) {
408 message = "LYXSRV:" + clients[i] + ":bye\n";
414 /* ---F+------------------------------------------------------------------ *\
415 Function : ServerCallback
417 Purpose : handle data gotten from communication
418 \* ---F------------------------------------------------------------------- */
420 void LyXServer::callback(LyXServer * serv, string const & msg)
422 lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
423 << msg << '\'' << endl;
425 char const * p = msg.c_str();
427 // --- parse the string --------------------------------------------
429 // Format: LYXCMD:<client>:<func>:<argstring>\n
431 bool server_only = false;
433 // --- 1. check 'header' ---
435 if (compare(p, "LYXSRV:", 7) == 0) {
437 } else if (0 != compare(p, "LYXCMD:", 7)) {
438 lyxerr << "LyXServer: Unknown request" << endl;
443 // --- 2. for the moment ignore the client name ---
445 while(*p && *p != ':')
446 client += char(*p++);
450 // --- 3. get function name ---
452 while(*p && *p != ':')
455 // --- 4. parse the argument ---
457 if (!server_only && *p == ':' && *(++p)) {
458 while(*p && *p != '\n')
463 lyxerr[Debug::LYXSERVER]
464 << "LyXServer: Client: '" << client
465 << "' Command: '" << cmd
466 << "' Argument: '" << arg << '\'' << endl;
468 // --- lookup and exec the command ------------------
472 // return the greeting to inform the client that
474 if (cmd == "hello") {
476 if (serv->numclients == MAX_CLIENTS){ //paranoid check
477 lyxerr[Debug::LYXSERVER]
478 << "LyXServer: too many clients..."
482 int i= 0; //find place in clients[]
483 while (!serv->clients[i].empty()
484 && i<serv->numclients)
486 serv->clients[i] = client;
488 buf = "LYXSRV:" + client + ":hello\n";
489 lyxerr[Debug::LYXSERVER]
490 << "LyXServer: Greeting "
492 serv->pipes.send(buf);
493 } else if (cmd == "bye") {
494 // If clients == 0 maybe we should reset the pipes
495 // to prevent fake callbacks
496 int i = 0; //look if client is registered
497 for (; i < serv->numclients; ++i) {
498 if (serv->clients[i] == client) break;
500 if (i < serv->numclients) {
502 serv->clients[i].erase();
503 lyxerr[Debug::LYXSERVER]
504 << "LyXServer: Client "
505 << client << " said goodbye"
508 lyxerr[Debug::LYXSERVER]
509 << "LyXServer: ignoring bye messge from unregistered client"
513 lyxerr <<"LyXServer: Undefined server command "
514 << cmd << "." << endl;
520 // which lyxfunc should we let it connect to?
521 // The correct solution would be to have a
522 // specialized (non-gui) BufferView. But how do
523 // we do it now? Probably we should just let it
524 // connect to the lyxfunc in the single LyXView we
525 // support currently. (Lgb)
527 int action = lyxaction.LookupFunc(cmd);
532 rval = serv->func->Dispatch(action, arg);
534 rval = "Unknown command";
537 //modified june 1999 stefano@zool.su.se:
538 //all commands produce an INFO or ERROR message
539 //in the output pipe, even if they do not return
540 //anything. See chapter 4 of Customization doc.
541 if (action<0 || serv->func->errorStat())
545 buf += string(client) + ":" + cmd + ":" + rval + "\n";
546 serv->pipes.send(buf);
548 // !!! we don't do any error checking -
549 // if the client won't listen, the
550 // message is lost and others too
551 // maybe; so the client should empty
552 // the outpipe before issuing a request.
560 /* ---F+------------------------------------------------------------------ *\
561 Function : LyxNotifyClient
562 Called by : WorkAreaKeyPress
563 Purpose : send a notify messge to a client
564 Parameters: s - string to send
566 \* ---F------------------------------------------------------------------- */
568 void LyXServer::notifyClient(string const & s)
570 string buf = string("NOTIFY:") + s + "\n";