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