// -*- C++ -*- /* This file is part of * ====================================================== * * LyX, The Document Processor * * Copyright 1995 Matthias Ettrich * Copyright 1995-1999 The LyX Team. * * ====================================================== */ /** Docu : To use the lyxserver define the name of the pipe in your lyxrc: \serverpipe "/home/myhome/.lyxpipe" Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX. Each message consists of a single line in ASCII. Input lines (client -> LyX) have the following format: "LYXCMD:::" Answers from LyX look like this: "INFO:::" [asierra970531] Or like this in case of error: "ERROR:::" where and are just echoed. If LyX notifies about a user defined extension key-sequence, the line looks like this: "NOTIFY:" [asierra970531] New server-only messages to implement a simple protocol "LYXSRV::" where can be "hello" or "bye". If hello is received LyX will inform the client that it's listening its messages, and 'bye' will inform that lyx is closing. See development/server_monitor.c for an example client. Purpose: implement a client/server lib for LyX */ #include #include #include #include #include #include #include #include FORMS_H_LOCATION #ifdef __GNUG__ #pragma implementation #endif #include "lyxserver.h" #include "lyxfunc.h" #include "lyx_main.h" #include "debug.h" #include "LyXAction.h" #include "support/lstrings.h" #ifdef __EMX__ #include #include #define OS2EMX_PLAIN_CHAR #define INCL_DOSNMPIPES #define INCL_DOSERRORS #include #include "os2_errortable.h" #endif // provide an empty mkfifo() if we do not have one. This disables the // lyxserver. #ifndef HAVE_MKFIFO int mkfifo( char *__path, mode_t __mode ) { return 0; } #endif /* === variables ========================================================= */ extern LyXAction lyxaction; // C wrapper extern "C" void C_LyXComm_callback(int fd, void *v); // LyXComm class // Open pipes void LyXComm::openConnection() { lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl; // If we are up, that's an error if (ready) { lyxerr << "LyXComm: Already connected" << endl; return; } // We assume that we don't make it ready = false; if (pipename.empty()) return; // --- prepare input pipe --------------------------------------- string tmp = pipename + ".in"; #ifdef __EMX__ HPIPE fd; APIRET rc; int errnum; // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK. // The current emx implementation of access() won't work with pipes. rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_INBOUND, NP_NOWAIT|0x01, 0600, 0600, 0); if (rc == ERROR_PIPE_BUSY) { #else if (access(tmp.c_str(), F_OK) == 0) { #endif lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n" << "If no other LyX program is active, please delete" " the pipe by hand and try again." << endl; pipename.clear(); return; } #ifndef __EMX__ if (mkfifo(tmp.c_str(), 0600) < 0) { lyxerr << "LyXComm: Could not create pipe " << tmp << '\n' << strerror(errno) << endl; return; }; infd = open(tmp.c_str(), O_RDONLY|O_NONBLOCK); #else if (rc != NO_ERROR) { errnum = TranslateOS2Error(rc); lyxerr <<"LyXComm: Could not create pipe " << tmp << strerror(errnum) << endl; return; }; // Listen to it. rc = DosConnectNPipe(fd); if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) { errnum = TranslateOS2Error(rc); lyxerr <<"LyXComm: Could not create pipe " << tmp << strerror(errnum) << endl; return; }; // Imported handles can be used both with OS/2 APIs and emx // library functions. infd = _imphandle(fd); #endif if (infd < 0) { lyxerr << "LyXComm: Could not open pipe " << tmp << '\n' << strerror(errno) << endl; return; } fl_add_io_callback(infd, FL_READ, C_LyXComm_callback, this); // --- prepare output pipe --------------------------------------- tmp = pipename + ".out"; #ifndef __EMX__ if (access(tmp.c_str(), F_OK) == 0) { #else rc = DosCreateNPipe(tmp.c_str(), &fd, NP_ACCESS_DUPLEX, NP_NOWAIT|0x01, 0600, 0600, 0); if (rc == ERROR_PIPE_BUSY) { #endif lyxerr << "LyXComm: Pipe " << tmp << " already exists.\n" << "If no other LyX program is active, please delete" " the pipe by hand and try again." << endl; pipename.clear(); return; } #ifndef __EMX__ if (mkfifo(tmp.c_str(), 0600) < 0) { lyxerr << "LyXComm: Could not create pipe " << tmp << '\n' << strerror(errno) << endl; return; }; if (access(tmp.c_str(), F_OK) != 0) { lyxerr << "LyXComm: Pipe " << tmp << " does not exist" << endl; return; } outfd = open(tmp.c_str(), O_RDWR); #else if (rc != NO_ERROR) { errnum = TranslateOS2Error(rc); lyxerr << "LyXComm: Could not create pipe " << tmp << '\n' << strerror(errnum) << endl; return; } rc = DosConnectNPipe(fd); if (rc == ERROR_BAD_PIPE) { lyxerr << "LyXComm: Pipe " << tmp << " does not exist" << endl; return; } if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) { errnum = TranslateOS2Error(rc); lyxerr << "LyXComm: Could not create pipe " << tmp << '\n' << strerror(errnum) << endl; return; } outfd = _imphandle(fd); #endif if (outfd < 0) { lyxerr << "LyXComm: Could not open pipe " << tmp << '\n' << strerror(errno) << endl; return; } if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) { lyxerr << "LyXComm: Could not set flags on pipe " << tmp << '\n' << strerror(errno) << endl; return; }; // We made it! ready = true; lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl; } /// Close pipes void LyXComm::closeConnection() { #ifdef __EMX__ APIRET rc; int errnum; #endif lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl; if (pipename.empty()) { return; } if (!ready) { lyxerr << "LyXComm: Already disconnected" << endl; return; } if(infd > -1) { fl_remove_io_callback(infd, FL_READ, C_LyXComm_callback); string tmp = pipename + ".in"; #ifdef __EMX__ // Notify the operating system. rc = DosDisConnectNPipe(infd); if (rc != NO_ERROR) { errnum = TranslateOS2Error(rc); lyxerr << "LyXComm: Could not disconnect pipe " << tmp << '\n' << strerror(errnum) << endl; return; } #endif if (close(infd) < 0) { lyxerr << "LyXComm: Could not close pipe " << tmp << '\n' << strerror(errno) << endl; } #ifndef __EMX__ // OS/2 named pipes will be automatically removed. if (unlink(tmp.c_str()) < 0){ lyxerr << "LyXComm: Could not remove pipe " << tmp << '\n' << strerror(errno) << endl; }; #endif } if(outfd > -1) { string tmp = pipename + ".out"; #ifdef __EMX__ rc = DosDisConnectNPipe(outfd); if (rc != NO_ERROR) { errnum = TranslateOS2Error(rc); lyxerr << "LyXComm: Could not disconnect pipe " << tmp << '\n' << strerror(errnum) << endl; return; } #endif if (close(outfd) < 0) { lyxerr << "LyXComm: Could not close pipe " << tmp << '\n' << strerror(errno) << endl; } #ifndef __EMX__ if (unlink(tmp.c_str()) < 0){ lyxerr << "LyXComm: Could not remove pipe " << tmp << '\n' << strerror(errno) << endl; }; #endif } ready = false; } // Receives messages and sends then to client void LyXComm::callback(int fd, void *v) { LyXComm * c = static_cast(v); if (lyxerr.debugging(Debug::LYXSERVER)) { lyxerr << "LyXComm: Receiving from fd " << fd << endl; } const int CMDBUFLEN = 100; char charbuf[CMDBUFLEN]; string cmd; // nb! make lsbuf a class-member for multiple sessions static string lsbuf; errno = 0; int status; // the single = is intended here. while((status = read(fd, charbuf, CMDBUFLEN-1))) {// break and return in loop if(status > 0) // got something { charbuf[status]= '\0'; // turn it into a c string lsbuf += strip(charbuf, '\r'); // commit any commands read while(lsbuf.find('\n') != string::npos) // while still // commands // left { // split() grabs the entire string if // the delim /wasn't/ found. ?:-P lsbuf= split(lsbuf, cmd,'\n'); lyxerr[Debug::LYXSERVER] << "LyXComm: status:" << status << ", lsbuf:" << lsbuf << ", cmd:" << cmd << endl; if(!cmd.empty()) c->clientcb(c->client, cmd); //\n or not \n? } } if(errno == EAGAIN) { // EAGAIN is not really an error , it means we're // only reading too fast for the writing process on // the other end of the pipe. errno = 0; return; // up to libforms select-loop (*crunch*) } if(errno != 0 ) { lyxerr << "LyXComm: " << strerror(errno) << endl; if(!lsbuf.empty()) { lyxerr << "LyxComm: truncated command: " << lsbuf << endl; lsbuf.clear(); } break; // reset connection } } c->closeConnection(); c->openConnection(); errno= 0; } extern "C" void C_LyXComm_callback(int fd, void *v) { LyXComm::callback(fd, v); } void LyXComm::send(string const & msg) { if (msg.empty()) { lyxerr << "LyXComm: Request to send empty string. Ignoring." << endl; return; } if (lyxerr.debugging(Debug::LYXSERVER)) { lyxerr << "LyXComm: Sending '" << msg << '\'' << endl; } if (pipename.empty()) return; if (!ready) { lyxerr << "LyXComm: Pipes are closed. Could not send " << msg << endl; } else if (write(outfd, msg.c_str(), msg.length()) < 0) { lyxerr << "LyXComm: Error sending message: " << msg << '\n' << strerror(errno) << "\nLyXComm: Resetting connection" << endl; closeConnection(); openConnection(); } #ifdef __EMX__ APIRET rc; int errnum; rc = DosResetBuffer(outfd); // To avoid synchronization problems. if (rc != NO_ERROR) { errnum = TranslateOS2Error(rc); lyxerr << "LyXComm: Message could not be flushed: " << msg << '\n' << strerror(errnum) << endl; } #endif } // LyXServer class LyXServer::~LyXServer() { // say goodbye to clients so they stop sending messages // modified june 1999 by stefano@zool.su.se to send as many bye // messages as there are clients, each with client's name. string message; for (int i= 0; i::\n // bool server_only = false; while(*p) { // --- 1. check 'header' --- if (strncmp(p, "LYXSRV:", 7) == 0) { server_only = true; } else if(0!= strncmp(p, "LYXCMD:", 7)) { lyxerr << "LyXServer: Unknown request" << endl; return; } p += 7; // --- 2. for the moment ignore the client name --- string client; while(*p && *p != ':') client += char(*p++); if(*p == ':') ++p; if(!*p) return; // --- 3. get function name --- string cmd; while(*p && *p != ':') cmd += char(*p++); // --- 4. parse the argument --- string arg; if(!server_only && *p == ':' && *(++p)) { while(*p && *p != '\n') arg += char(*p++); if(*p) ++p; } lyxerr[Debug::LYXSERVER] << "LyXServer: Client: '" << client << "' Command: '" << cmd << "' Argument: '" << arg << '\'' << endl; // --- lookup and exec the command ------------------ if (server_only) { string buf; // return the greeting to inform the client that // we are listening. if (cmd == "hello") { // One more client if(serv->numclients == MAX_CLIENTS){ //paranoid check lyxerr[Debug::LYXSERVER] << "LyXServer: too many clients..." << endl; return; } int i= 0; //find place in clients[] while (!serv->clients[i].empty() && inumclients) ++i; serv->clients[i] = client; serv->numclients++; buf = "LYXSRV:" + client + ":hello\n"; lyxerr[Debug::LYXSERVER] << "LyXServer: Greeting " << client << endl; serv->pipes.send(buf); } else if (cmd == "bye") { // If clients == 0 maybe we should reset the pipes // to prevent fake callbacks int i = 0; //look if client is registered for (; i < serv->numclients; ++i) { if (serv->clients[i] == client) break; } if (i < serv->numclients) { serv->numclients--; serv->clients[i].clear(); lyxerr[Debug::LYXSERVER] << "LyXServer: Client " << client << " said goodbye" << endl; } else { lyxerr[Debug::LYXSERVER] << "LyXServer: ignoring bye messge from unregistered client" << client << endl; } } else { lyxerr <<"LyXServer: Undefined server command " << cmd << "." << endl; } return; } if (!cmd.empty()) { // which lyxfunc should we let it connect to? // The correct solution would be to have a // specialized (non-gui) BufferView. But how do // we do it now? Probably we should just let it // connect to the lyxfunc in the single LyXView we // support currently. (Lgb) int action = lyxaction.LookupFunc(cmd.c_str()); //int action = -1; string rval, buf; if (action>= 0) { rval = serv->func->Dispatch(cmd); } else { rval = "Unknown command"; } //modified june 1999 stefano@zool.su.se: //all commands produce an INFO or ERROR message //in the output pipe, even if they do not return //anything. See chapter 4 of Customization doc. if (action<0 || serv->func->errorStat()) buf = "ERROR:"; else buf = "INFO:"; buf += string(client) + ":" + cmd + ":" + rval + "\n"; serv->pipes.send(buf); // !!! we don't do any error checking - // if the client won't listen, the // message is lost and others too // maybe; so the client should empty // the outpipe before issuing a request. // not found } } /* while *p */ } /* ---F+------------------------------------------------------------------ *\ Function : LyxNotifyClient Called by : WorkAreaKeyPress Purpose : send a notify messge to a client Parameters: s - string to send Returns : nothing \* ---F------------------------------------------------------------------- */ void LyXServer::notifyClient(string const & s) { string buf = string("NOTIFY:") + s + "\n"; pipes.send(buf); }