]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
add missing funcrequest.h
[lyx.git] / src / lyxserver.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2001 The LyX Team.
8  *
9  * ====================================================== */
10
11 /**
12   Docu   : To use the lyxserver define the name of the pipe in your
13            lyxrc:
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.
32
33            See development/server_monitor.c for an example client.
34   Purpose: implement a client/server lib for LyX
35 */
36
37 #include <config.h>
38
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <cerrno>
44 #include FORMS_H_LOCATION
45
46 #ifdef __GNUG__
47 #pragma implementation
48 #endif
49
50 #include "lyxserver.h"
51 #include "lyx_main.h"
52 #include "debug.h"
53 #include "lyxfunc.h"
54 #include "support/lstrings.h"
55 #include "support/lyxlib.h"
56 #include "frontends/lyx_gui.h"
57
58 #ifdef __EMX__
59 #include <cstdlib>
60 #include <io.h>
61 #define OS2EMX_PLAIN_CHAR
62 #define INCL_DOSNMPIPES
63 #define INCL_DOSERRORS
64 #include <os2.h>
65 #include "support/os2_errortable.h"
66 #endif
67
68 using std::endl;
69
70 // provide an empty mkfifo() if we do not have one. This disables the
71 // lyxserver.
72 #ifndef HAVE_MKFIFO
73 int mkfifo(char const * __path, mode_t __mode) {
74         return 0;
75 }
76 #endif
77
78
79 void LyXComm::openConnection()
80 {
81         lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
82
83         // If we are up, that's an error
84         if (ready) {
85                 lyxerr << "LyXComm: Already connected" << endl;
86                 return;
87         }
88         // We assume that we don't make it
89         ready = false;
90
91         if (pipename.empty()) {
92                 lyxerr[Debug::LYXSERVER]
93                         << "LyXComm: server is disabled, nothing to do"
94                         << endl;
95                 return;
96         }
97
98         if ((infd = startPipe(inPipeName(), false)) == -1)
99                 return;
100
101         if ((outfd = startPipe(outPipeName(), true)) == -1) {
102                 endPipe(infd, inPipeName(), false);
103                 return;
104         }
105
106         if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
107                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
108                        << '\n' << strerror(errno) << endl;
109                 return;
110         }
111
112         // We made it!
113         ready = true;
114         lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
115 }
116
117
118 /// Close pipes
119 void LyXComm::closeConnection()
120 {
121         lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
122
123         if (pipename.empty()) {
124                 lyxerr[Debug::LYXSERVER]
125                         << "LyXComm: server is disabled, nothing to do"
126                         << endl;
127                 return;
128         }
129
130         if (!ready) {
131                 lyxerr << "LyXComm: Already disconnected" << endl;
132                 return;
133         }
134
135         endPipe(infd, inPipeName(), false);
136         endPipe(outfd, outPipeName(), true);
137
138         ready = false;
139 }
140
141 int LyXComm::startPipe(string const & filename, bool write)
142 {
143         int fd;
144
145 #ifdef __EMX__
146         HPIPE os2fd;
147         APIRET rc;
148         int errnum;
149         // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
150         // The current emx implementation of access() won't work with pipes.
151         rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
152                 NP_NOWAIT|0x01, 0600, 0600, 0);
153         if (rc == ERROR_PIPE_BUSY) {
154                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
155                        << "If no other LyX program is active, please delete"
156                         " the pipe by hand and try again." << endl;
157                 pipename.erase();
158                 return -1;
159         }
160
161         if (rc != NO_ERROR) {
162                 errnum = TranslateOS2Error(rc);
163                 lyxerr <<"LyXComm: Could not create pipe " << filename
164                        << strerror(errnum) << endl;
165                 return -1;
166         };
167         // Listen to it.
168         rc = DosConnectNPipe(os2fd);
169         if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
170                 errnum = TranslateOS2Error(rc);
171                 lyxerr <<"LyXComm: Could not create pipe " << filename
172                        << strerror(errnum) << endl;
173                 return -1;
174         };
175         // Imported handles can be used both with OS/2 APIs and emx
176         // library functions.
177         fd = _imphandle(os2fd);
178 #else
179         if (::access(filename.c_str(), F_OK) == 0) {
180                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
181                        << "If no other LyX program is active, please delete"
182                         " the pipe by hand and try again." << endl;
183                 pipename.erase();
184                 return -1;
185         }
186
187         if (::mkfifo(filename.c_str(), 0600) < 0) {
188                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
189                        << strerror(errno) << endl;
190                 return -1;
191         };
192         fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
193 #endif
194
195         if (fd < 0) {
196                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
197                        << strerror(errno) << endl;
198                 lyx::unlink(filename);
199                 return -1;
200         }
201
202         if (!write) {
203                 lyx_gui::set_read_callback(fd, this);
204         }
205
206         return fd;
207 }
208
209
210 void LyXComm::endPipe(int & fd, string const & filename, bool write)
211 {
212         if (fd < 0)
213                 return;
214
215         if (!write) {
216                 lyx_gui::remove_read_callback(fd);
217         }
218  
219 #ifdef __EMX__
220         APIRET rc;
221         int errnum;
222
223         rc = DosDisConnectNPipe(fd);
224         if (rc != NO_ERROR) {
225                 errnum = TranslateOS2Error(rc);
226                 lyxerr << "LyXComm: Could not disconnect pipe " << filename
227                        << '\n' << strerror(errnum) << endl;
228                 return;
229         }
230 #endif
231
232         if (::close(fd) < 0) {
233                 lyxerr << "LyXComm: Could not close pipe " << filename
234                        << '\n' << strerror(errno) << endl;
235         }
236
237 // OS/2 pipes are deleted automatically
238 #ifndef __EMX__
239         if (lyx::unlink(filename) < 0) {
240                 lyxerr << "LyXComm: Could not remove pipe " << filename
241                        << '\n' << strerror(errno) << endl;
242         };
243 #endif
244
245         fd = -1;
246 }
247
248
249 void LyXComm::emergencyCleanup()
250 {
251         if (!pipename.empty()) {
252                 endPipe(infd, inPipeName(), false);
253                 endPipe(outfd, outPipeName(), true);
254         }
255 }
256
257
258 // Receives messages and sends then to client
259 void LyXComm::read_ready()
260 {
261         if (lyxerr.debugging(Debug::LYXSERVER)) {
262                 lyxerr << "LyXComm: Receiving from fd " << infd << endl;
263         }
264
265         int const CMDBUFLEN = 100;
266         char charbuf[CMDBUFLEN];
267         string cmd;
268 // nb! make lsbuf a class-member for multiple sessions
269         static string lsbuf;
270
271         errno = 0;
272         int status;
273         // the single = is intended here.
274         while ((status = read(infd, charbuf, CMDBUFLEN - 1))) {
275                 int rerrno = errno;
276  
277                 if (status > 0) {
278                         charbuf[status]= '\0'; // turn it into a c string
279                         lsbuf += rtrim(charbuf, "\r");
280                         // commit any commands read
281                         while (lsbuf.find('\n') != string::npos) {
282                                 // split() grabs the entire string if
283                                 // the delim /wasn't/ found. ?:-P
284                                 lsbuf= split(lsbuf, cmd,'\n');
285                                 lyxerr[Debug::LYXSERVER]
286                                         << "LyXComm: status:" << status
287                                         << ", lsbuf:" << lsbuf
288                                         << ", cmd:" << cmd << endl;
289                                 if (!cmd.empty())
290                                         clientcb(client, cmd);
291                                         //\n or not \n?
292                         }
293                 }
294                 if (rerrno == EAGAIN) {
295                         errno = 0;
296                         return;
297                 }
298                 if (rerrno != 0) {
299                         lyxerr << "LyXComm: " << strerror(rerrno) << endl;
300                         if (!lsbuf.empty()) {
301                                 lyxerr << "LyXComm: truncated command: "
302                                        << lsbuf << endl;
303                                 lsbuf.erase();
304                         }
305                         break; // reset connection
306                 }
307         }
308         closeConnection();
309         openConnection();
310         errno= 0;
311 }
312
313
314 void LyXComm::send(string const & msg)
315 {
316         if (msg.empty()) {
317                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
318                        << endl;
319                 return;
320         }
321
322         if (lyxerr.debugging(Debug::LYXSERVER)) {
323                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
324         }
325
326         if (pipename.empty()) return;
327
328         if (!ready) {
329                 lyxerr << "LyXComm: Pipes are closed. Could not send "
330                        << msg << endl;
331         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
332                 lyxerr << "LyXComm: Error sending message: " << msg
333                        << '\n' << strerror(errno)
334                        << "\nLyXComm: Resetting connection" << endl;
335                 closeConnection();
336                 openConnection();
337         }
338 #ifdef __EMX__
339         APIRET rc;
340         int errnum;
341         rc = DosResetBuffer(outfd);     // To avoid synchronization problems.
342         if (rc != NO_ERROR) {
343                 errnum = TranslateOS2Error(rc);
344                 lyxerr << "LyXComm: Message could not be flushed: " << msg
345                        << '\n' << strerror(errnum) << endl;
346         }
347 #endif
348 }
349
350
351 // LyXServer class
352
353 LyXServer::~LyXServer()
354 {
355         // say goodbye to clients so they stop sending messages
356         // modified june 1999 by stefano@zool.su.se to send as many bye
357         // messages as there are clients, each with client's name.
358         string message;
359         for (int i= 0; i<numclients; ++i) {
360                 message = "LYXSRV:" + clients[i] + ":bye\n";
361                 pipes.send(message);
362         }
363 }
364
365
366 /* ---F+------------------------------------------------------------------ *\
367    Function  : ServerCallback
368     Called by : LyXComm
369     Purpose   : handle data gotten from communication
370 \* ---F------------------------------------------------------------------- */
371
372 void LyXServer::callback(LyXServer * serv, string const & msg)
373 {
374         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
375                                  << msg << '\'' << endl;
376
377         char const * p = msg.c_str();
378
379         // --- parse the string --------------------------------------------
380         //
381         //  Format: LYXCMD:<client>:<func>:<argstring>\n
382         //
383         bool server_only = false;
384         while (*p) {
385                 // --- 1. check 'header' ---
386
387                 if (compare(p, "LYXSRV:", 7) == 0) {
388                         server_only = true;
389                 } else if (0 != compare(p, "LYXCMD:", 7)) {
390                         lyxerr << "LyXServer: Unknown request \"" << p << "\"" << endl;
391                         return;
392                 }
393                 p += 7;
394
395                 // --- 2. for the moment ignore the client name ---
396                 string client;
397                 while (*p && *p != ':')
398                         client += char(*p++);
399                 if (*p == ':') ++p;
400                 if (!*p) return;
401
402                 // --- 3. get function name ---
403                 string cmd;
404                 while (*p && *p != ':')
405                         cmd += char(*p++);
406
407                 // --- 4. parse the argument ---
408                 string arg;
409                 if (!server_only && *p == ':' && *(++p)) {
410                         while (*p && *p != '\n')
411                                 arg += char(*p++);
412                         if (*p) ++p;
413                 }
414
415                 lyxerr[Debug::LYXSERVER]
416                         << "LyXServer: Client: '" << client
417                         << "' Command: '" << cmd
418                         << "' Argument: '" << arg << '\'' << endl;
419
420                 // --- lookup and exec the command ------------------
421
422                 if (server_only) {
423                         string buf;
424                         // return the greeting to inform the client that
425                         // we are listening.
426                         if (cmd == "hello") {
427                                 // One more client
428                                 if (serv->numclients == MAX_CLIENTS) { //paranoid check
429                                         lyxerr[Debug::LYXSERVER]
430                                                 << "LyXServer: too many clients..."
431                                                 << endl;
432                                         return;
433                                 }
434                                 int i= 0; //find place in clients[]
435                                 while (!serv->clients[i].empty()
436                                        && i<serv->numclients)
437                                         ++i;
438                                 serv->clients[i] = client;
439                                 serv->numclients++;
440                                 buf = "LYXSRV:" + client + ":hello\n";
441                                 lyxerr[Debug::LYXSERVER]
442                                         << "LyXServer: Greeting "
443                                         << client << endl;
444                                 serv->pipes.send(buf);
445                         } else if (cmd == "bye") {
446                                 // If clients == 0 maybe we should reset the pipes
447                                 // to prevent fake callbacks
448                                 int i = 0; //look if client is registered
449                                 for (; i < serv->numclients; ++i) {
450                                         if (serv->clients[i] == client) break;
451                                 }
452                                 if (i < serv->numclients) {
453                                         serv->numclients--;
454                                         serv->clients[i].erase();
455                                         lyxerr[Debug::LYXSERVER]
456                                                 << "LyXServer: Client "
457                                                 << client << " said goodbye"
458                                                 << endl;
459                                 } else {
460                                         lyxerr[Debug::LYXSERVER]
461                                                 << "LyXServer: ignoring bye messge from unregistered client"
462                                                 << client << endl;
463                                 }
464                         } else {
465                                 lyxerr <<"LyXServer: Undefined server command "
466                                        << cmd << "." << endl;
467                         }
468                         return;
469                 }
470
471                 if (!cmd.empty()) {
472                         // which lyxfunc should we let it connect to?
473                         // The correct solution would be to have a
474                         // specialized (non-gui) BufferView. But how do
475                         // we do it now? Probably we should just let it
476                         // connect to the lyxfunc in the single LyXView we
477                         // support currently. (Lgb)
478
479
480                         serv->func->dispatch(cmd + ' ' + arg);
481                         string const rval = serv->func->getMessage();
482
483                         //modified june 1999 stefano@zool.su.se:
484                         //all commands produce an INFO or ERROR message
485                         //in the output pipe, even if they do not return
486                         //anything. See chapter 4 of Customization doc.
487                         string buf;
488                         if (serv->func->errorStat())
489                                 buf = "ERROR:";
490                         else
491                                 buf = "INFO:";
492                         buf += client + ":" + cmd + ":" +  rval + "\n";
493                         serv->pipes.send(buf);
494
495                         // !!! we don't do any error checking -
496                         //  if the client won't listen, the
497                         //  message is lost and others too
498                         //  maybe; so the client should empty
499                         //  the outpipe before issuing a request.
500
501                         // not found
502                 }
503         }  /* while *p */
504 }
505
506
507 /* ---F+------------------------------------------------------------------ *\
508    Function  : LyXNotifyClient
509    Called by : WorkAreaKeyPress
510    Purpose   : send a notify messge to a client
511    Parameters: s - string to send
512    Returns   : nothing
513    \* ---F------------------------------------------------------------------- */
514
515 void LyXServer::notifyClient(string const & s)
516 {
517         string buf = string("NOTIFY:") + s + "\n";
518         pipes.send(buf);
519 }