3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jean-Marc Lasgouttes
8 * \author Angus Leeming
10 * \author Enrico Forestieri
12 * Full author contact details are available in file CREDITS.
16 Docu : To use the lyxserver define the name of the pipe in your
18 \serverpipe "/home/myhome/.lyxpipe"
19 Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
20 Each message consists of a single line in ASCII. Input lines
21 (client -> LyX) have the following format:
22 "LYXCMD:<clientname>:<functionname>:<argument>"
23 Answers from LyX look like this:
24 "INFO:<clientname>:<functionname>:<data>"
25 [asierra970531] Or like this in case of error:
26 "ERROR:<clientname>:<functionname>:<error message>"
27 where <clientname> and <functionname> are just echoed.
28 If LyX notifies about a user defined extension key-sequence,
29 the line looks like this:
30 "NOTIFY:<key-sequence>"
31 [asierra970531] New server-only messages to implement a simple protocol
32 "LYXSRV:<clientname>:<protocol message>"
33 where <protocol message> can be "hello" or "bye". If hello is
34 received LyX will inform the client that it's listening its
35 messages, and 'bye' will inform that lyx is closing.
37 See development/server_monitor.c for an example client.
38 Purpose: implement a client/server lib for LyX
44 #include "FuncRequest.h"
45 #include "LyXAction.h"
48 #include "frontends/Application.h"
50 #include "support/debug.h"
51 #include "support/FileName.h"
52 #include "support/lassert.h"
53 #include "support/lstrings.h"
54 #include "support/os.h"
56 #include <boost/bind.hpp>
59 #include <QCoreApplication>
63 #ifdef HAVE_SYS_STAT_H
64 # include <sys/stat.h>
69 using namespace lyx::support;
70 using os::external_path;
74 /////////////////////////////////////////////////////////////////////
78 /////////////////////////////////////////////////////////////////////
82 class ReadReadyEvent : public QEvent {
85 ReadReadyEvent(DWORD inpipe) : QEvent(QEvent::User), inpipe_(inpipe)
88 DWORD inpipe() const { return inpipe_; }
99 DWORD error = GetLastError();
100 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
101 FORMAT_MESSAGE_FROM_SYSTEM |
102 FORMAT_MESSAGE_IGNORE_INSERTS,
103 NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
104 (LPTSTR) &msgbuf, 0, NULL);
105 return static_cast<char *>(msgbuf);
111 DWORD WINAPI pipeServerWrapper(void * arg)
113 LyXComm * lyxcomm = reinterpret_cast<LyXComm *>(arg);
114 lyxcomm->pipeServer();
122 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
123 : pipename_(pip), client_(cli), clientcb_(ccb), stopserver_(0)
130 void LyXComm::pipeServer()
134 for (i = 0; i <= MAX_PIPES; ++i) {
136 string const pipename = external_path(pipeName(i));
139 open_mode = PIPE_ACCESS_INBOUND;
142 open_mode = PIPE_ACCESS_OUTBOUND;
146 // Manual-reset event, initial state = signaled
147 event_[i] = CreateEvent(NULL, TRUE, TRUE, NULL);
150 LYXERR0("LyXComm: Could not create event for pipe "
151 << pipename.c_str() << '\n' << errormsg());
156 pipe_[i].overlap.hEvent = event_[i];
157 pipe_[i].handle = CreateNamedPipe(pipename.c_str(),
158 open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT,
159 MAX_PIPES, PIPE_BUFSIZE, PIPE_BUFSIZE,
162 if (pipe_[i].handle == INVALID_HANDLE_VALUE) {
163 LYXERR0("LyXComm: Could not create pipe "
164 << pipename.c_str() << '\n' << errormsg());
170 pipe_[i].state = pipe_[i].pending_io ?
171 CONNECTING_STATE : (i < MAX_PIPES ? READING_STATE
175 // Add the stopserver_ event
176 event_[MAX_PIPES + 1] = stopserver_;
179 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
184 while (!checkStopServer()) {
185 // Indefinitely wait for the completion of an overlapped
186 // read, write, or connect operation.
187 DWORD wait = WaitForMultipleObjects(MAX_PIPES + 2, event_,
190 // Determine which pipe instance completed the operation.
191 i = wait - WAIT_OBJECT_0;
192 LASSERT(i >= 0 && i <= MAX_PIPES + 1, /**/);
194 // Check whether we were waked up for stopping the pipe server.
195 if (i == MAX_PIPES + 1)
198 // Get the result if the operation was pending.
199 if (pipe_[i].pending_io) {
200 success = GetOverlappedResult(pipe_[i].handle,
201 &pipe_[i].overlap, &status, FALSE);
203 switch (pipe_[i].state) {
204 case CONNECTING_STATE:
205 // Pending connect operation
207 DWORD const err = GetLastError();
209 && err == ERROR_IO_INCOMPLETE) {
210 // A reply on the output pipe
211 // has not been read, still.
212 // As we have only one instance
213 // for output, we risk a stalled
214 // pipe if no one reads it.
215 // So, if a reader doesn't
216 // appear within about 5 or 6
217 // seconds, we reset it.
218 static int count = 0;
225 LYXERR0("LyXComm: " << errormsg());
228 pipe_[i].state = i < MAX_PIPES ? READING_STATE
233 // Pending read operation
234 LASSERT(i < MAX_PIPES, /**/);
235 if (!success || status == 0) {
239 pipe_[i].nbytes = status;
240 pipe_[i].state = WRITING_STATE;
244 // Pending write operation
245 LASSERT(i == MAX_PIPES, /**/);
246 if (!success || status != writebuf_.length()) {
254 // Operate according to the pipe state
255 switch (pipe_[i].state) {
257 // The pipe instance is connected to a client
258 // and is ready to read a request.
259 LASSERT(i < MAX_PIPES, /**/);
260 success = ReadFile(pipe_[i].handle,
261 pipe_[i].pipebuf, PIPE_BUFSIZE - 1,
262 &pipe_[i].nbytes, &pipe_[i].overlap);
264 if (success && pipe_[i].nbytes != 0) {
265 // The read operation completed successfully.
266 pipe_[i].pending_io = false;
267 pipe_[i].state = WRITING_STATE;
271 if (!success && GetLastError() == ERROR_IO_PENDING) {
272 // The read operation is still pending.
273 pipe_[i].pending_io = true;
277 // Client closed connection (ERROR_BROKEN_PIPE) or
278 // an error occurred; in either case, reset the pipe.
279 if (GetLastError() != ERROR_BROKEN_PIPE) {
280 LYXERR0("LyXComm: " << errormsg());
281 if (!readbuf_[i].empty()) {
282 LYXERR0("LyXComm: truncated command: "
293 // The request was successfully read
294 // from the client; commit it.
295 ReadReadyEvent * event = new ReadReadyEvent(i);
296 QCoreApplication::postEvent(this,
297 static_cast<QEvent *>(event));
298 // Wait for completion
299 while (pipe_[i].nbytes && !checkStopServer())
301 pipe_[i].pending_io = false;
302 pipe_[i].state = READING_STATE;
305 // Let's see whether we have a reply.
306 if (writebuf_.length() == 0) {
307 // No, nothing to do.
308 pipe_[i].pending_io = false;
312 success = WriteFile(pipe_[i].handle,
313 writebuf_.c_str(), writebuf_.length(),
314 &status, &pipe_[i].overlap);
316 if (success && status == writebuf_.length()) {
317 // The write operation completed successfully.
319 pipe_[i].pending_io = false;
324 if (!success && (GetLastError() == ERROR_IO_PENDING)) {
325 // The write operation is still pending.
326 pipe_[i].pending_io = true;
330 // Client closed connection (ERROR_NO_DATA) or
331 // an error occurred; in either case, reset the pipe.
332 if (GetLastError() != ERROR_NO_DATA) {
333 LYXERR0("LyXComm: Error sending message: "
334 << writebuf_ << '\n' << errormsg());
343 closeHandles(MAX_PIPES + 1);
347 void LyXComm::closeHandles(DWORD index)
349 for (int i = 0; i <= index; ++i) {
351 ResetEvent(event_[i]);
352 CloseHandle(event_[i]);
354 if (pipe_[i].handle != INVALID_HANDLE_VALUE)
355 CloseHandle(pipe_[i].handle);
360 bool LyXComm::event(QEvent * e)
362 if (e->type() == QEvent::User) {
363 read_ready(static_cast<ReadReadyEvent *>(e)->inpipe());
370 BOOL LyXComm::checkStopServer()
372 return WaitForSingleObject(stopserver_, 0) == WAIT_OBJECT_0;
376 void LyXComm::startPipe(DWORD index)
378 pipe_[index].pending_io = false;
380 // Overlapped ConnectNamedPipe should return zero.
381 if (ConnectNamedPipe(pipe_[index].handle, &pipe_[index].overlap)) {
382 // FIXME: What to do? Maybe the pipe server should be reset.
383 LYXERR0("LyXComm: Could not connect pipe "
384 << external_path(pipeName(index)) << '\n'
389 switch (GetLastError()) {
390 case ERROR_IO_PENDING:
391 // The overlapped connection is in progress.
392 pipe_[index].pending_io = true;
395 case ERROR_PIPE_CONNECTED:
396 // Client is already connected, so signal an event.
397 if (SetEvent(pipe_[index].overlap.hEvent))
400 // Anything else is an error.
401 // FIXME: What to do? Maybe the pipe server should be reset.
402 LYXERR0("LyXComm: An error occurred while connecting pipe "
403 << external_path(pipeName(index)) << '\n'
409 void LyXComm::resetPipe(DWORD index, bool close_handle)
411 // This method is called when an error occurs or when a client
412 // closes the connection. We first disconnect the pipe instance,
413 // then reconnect it, ready to wait for another client.
415 if (!DisconnectNamedPipe(pipe_[index].handle)) {
416 LYXERR0("LyXComm: Could not disconnect pipe "
417 << external_path(pipeName(index)) << '\n'
419 // What to do now? Let's try whether re-creating the pipe helps.
423 DWORD const open_mode = index < MAX_PIPES ?
424 PIPE_ACCESS_INBOUND : PIPE_ACCESS_OUTBOUND;
425 string const name = external_path(pipeName(index));
427 CloseHandle(pipe_[index].handle);
429 pipe_[index].handle = CreateNamedPipe(name.c_str(),
430 open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT,
431 MAX_PIPES, PIPE_BUFSIZE, PIPE_BUFSIZE,
434 if (pipe_[index].handle == INVALID_HANDLE_VALUE) {
435 LYXERR0("LyXComm: Could not reset pipe "
436 << name << '\n' << errormsg());
439 if (index == MAX_PIPES)
442 readbuf_[index].erase();
445 pipe_[index].state = pipe_[index].pending_io ?
446 CONNECTING_STATE : (index < MAX_PIPES ? READING_STATE
451 void LyXComm::openConnection()
453 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
455 // If we are up, that's an error
457 LYXERR0("LyXComm: Already connected");
460 // We assume that we don't make it
463 if (pipename_.empty()) {
464 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
468 // Check whether the pipe name is being used by some other program.
469 if (!stopserver_ && WaitNamedPipe(inPipeName().c_str(), 0)) {
470 LYXERR0("LyXComm: Pipe " << external_path(inPipeName())
471 << " already exists.\nMaybe another instance of LyX"
477 // Manual-reset event, initial state = not signaled
478 stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL);
479 server_thread_ = CreateThread(NULL, 0, pipeServerWrapper,
480 static_cast<void *>(this), 0, NULL);
481 if (!server_thread_) {
482 LYXERR0("LyXComm: Could not create pipe server thread\n"
491 void LyXComm::closeConnection()
493 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
495 if (pipename_.empty()) {
496 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
501 LYXERR(Debug::LYXSERVER, "LyXComm: Already disconnected");
509 void LyXComm::emergencyCleanup()
512 SetEvent(stopserver_);
513 // Wait for the pipe server to finish
514 WaitForSingleObject(server_thread_, INFINITE);
515 CloseHandle(server_thread_);
516 ResetEvent(stopserver_);
517 CloseHandle(stopserver_);
522 void LyXComm::read_ready(DWORD inpipe)
524 // Turn the pipe buffer into a C string
525 DWORD const nbytes = pipe_[inpipe].nbytes;
526 pipe_[inpipe].pipebuf[nbytes] = '\0';
528 readbuf_[inpipe] += rtrim(pipe_[inpipe].pipebuf, "\r");
530 // Commit any commands read
531 while (readbuf_[inpipe].find('\n') != string::npos) {
532 // split() grabs the entire string if
533 // the delim /wasn't/ found. ?:-P
535 readbuf_[inpipe] = split(readbuf_[inpipe], cmd, '\n');
536 cmd = rtrim(cmd, "\r");
537 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << nbytes
538 << ", readbuf_:" << readbuf_[inpipe]
541 clientcb_(client_, cmd);
544 // Signal that we are done.
545 pipe_[inpipe].nbytes = 0;
549 void LyXComm::send(string const & msg)
552 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
556 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
558 if (pipename_.empty())
562 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
566 // Wait a couple of secs for completion of a previous write operation.
567 for (int count = 0; writebuf_.length() && count < 20; ++count)
570 if (!writebuf_.length()) {
572 // Tell the pipe server he has a job to do.
573 SetEvent(pipe_[MAX_PIPES].overlap.hEvent);
575 // Nope, output pipe is still busy. Most likely, a bad client
576 // did not care to read the answer (JabRef is one such client).
577 // Let's do a reset, otherwise the output pipe could remain
578 // stalled if the pipe server failed to reset it.
579 // This will remedy the output pipe stall, but the client will
580 // get a broken pipe error.
581 LYXERR0("LyXComm: Error sending message: " << msg
582 << "\nLyXComm: Output pipe is stalled\n"
583 "LyXComm: Resetting connection");
585 if (!checkStopServer())
591 string const LyXComm::pipeName(DWORD index) const
593 return index < MAX_PIPES ? inPipeName() : outPipeName();
597 #elif !defined (HAVE_MKFIFO)
598 // We provide a stub class that disables the lyxserver.
600 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
603 void LyXComm::openConnection()
607 void LyXComm::closeConnection()
611 int LyXComm::startPipe(string const & filename, bool write)
617 void LyXComm::endPipe(int & fd, string const & filename, bool write)
621 void LyXComm::emergencyCleanup()
624 void LyXComm::read_ready()
628 void LyXComm::send(string const & msg)
632 #else // defined (HAVE_MKFIFO)
635 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
636 : pipename_(pip), client_(cli), clientcb_(ccb)
643 void LyXComm::openConnection()
645 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
647 // If we are up, that's an error
649 lyxerr << "LyXComm: Already connected" << endl;
652 // We assume that we don't make it
655 if (pipename_.empty()) {
656 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
660 infd_ = startPipe(inPipeName(), false);
664 outfd_ = startPipe(outPipeName(), true);
666 endPipe(infd_, inPipeName(), false);
670 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
671 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
672 << '\n' << strerror(errno) << endl;
678 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
683 void LyXComm::closeConnection()
685 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
687 if (pipename_.empty()) {
688 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
693 LYXERR0("LyXComm: Already disconnected");
697 endPipe(infd_, inPipeName(), false);
698 endPipe(outfd_, outPipeName(), true);
704 int LyXComm::startPipe(string const & file, bool write)
706 static bool stalepipe = false;
707 FileName const filename(file);
708 if (filename.exists()) {
710 // Let's see whether we have a stale pipe.
711 int fd = ::open(filename.toFilesystemEncoding().c_str(),
712 O_WRONLY | O_NONBLOCK);
714 // Another LyX instance is using it.
716 } else if (errno == ENXIO) {
717 // No process is reading from the other end.
719 LYXERR(Debug::LYXSERVER,
720 "LyXComm: trying to remove "
722 filename.removeFile();
724 } else if (stalepipe) {
725 LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
727 filename.removeFile();
730 if (filename.exists()) {
731 lyxerr << "LyXComm: Pipe " << filename
732 << " already exists.\nIf no other LyX program"
733 " is active, please delete the pipe by hand"
741 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
742 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
743 << strerror(errno) << endl;
746 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
747 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
750 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
751 << strerror(errno) << endl;
752 filename.removeFile();
757 theApp()->registerSocketCallback(fd,
758 boost::bind(&LyXComm::read_ready, this));
765 void LyXComm::endPipe(int & fd, string const & filename, bool write)
771 theApp()->unregisterSocketCallback(fd);
773 if (::close(fd) < 0) {
774 lyxerr << "LyXComm: Could not close pipe " << filename
775 << '\n' << strerror(errno) << endl;
778 if (FileName(filename).removeFile() < 0) {
779 lyxerr << "LyXComm: Could not remove pipe " << filename
780 << '\n' << strerror(errno) << endl;
787 void LyXComm::emergencyCleanup()
789 if (!pipename_.empty()) {
790 endPipe(infd_, inPipeName(), false);
791 endPipe(outfd_, outPipeName(), true);
796 // Receives messages and sends then to client
797 void LyXComm::read_ready()
799 // FIXME: make read_buffer_ a class-member for multiple sessions
800 static string read_buffer_;
801 read_buffer_.erase();
803 int const charbuf_size = 100;
804 char charbuf[charbuf_size];
806 // As O_NONBLOCK is set, until no data is available for reading,
807 // read() doesn't block but returns -1 and set errno to EAGAIN.
808 // After a client that opened the pipe for writing, closes it
809 // (and no other client is using the pipe), read() would always
810 // return 0 and thus the connection has to be reset.
814 // the single = is intended here.
815 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
818 charbuf[status] = '\0'; // turn it into a c string
819 read_buffer_ += rtrim(charbuf, "\r");
820 // commit any commands read
821 while (read_buffer_.find('\n') != string::npos) {
822 // split() grabs the entire string if
823 // the delim /wasn't/ found. ?:-P
825 read_buffer_= split(read_buffer_, cmd,'\n');
826 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
827 << ", read_buffer_:" << read_buffer_
830 clientcb_(client_, cmd);
834 if (errno == EAGAIN) {
835 // Nothing to read, continue
839 // An error occurred, better bailing out
840 LYXERR0("LyXComm: " << strerror(errno));
841 if (!read_buffer_.empty()) {
842 LYXERR0("LyXComm: truncated command: " << read_buffer_);
843 read_buffer_.erase();
845 break; // reset connection
849 // The connection gets reset when read() returns 0 (meaning that the
850 // last client closed the pipe) or an error occurred, in which case
851 // read() returns -1 and errno != EAGAIN.
858 void LyXComm::send(string const & msg)
861 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
865 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
867 if (pipename_.empty()) return;
870 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
871 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
872 lyxerr << "LyXComm: Error sending message: " << msg
873 << '\n' << strerror(errno)
874 << "\nLyXComm: Resetting connection" << endl;
880 #endif // defined (HAVE_MKFIFO)
883 string const LyXComm::inPipeName() const
885 return pipename_ + ".in";
889 string const LyXComm::outPipeName() const
891 return pipename_ + ".out";
895 /////////////////////////////////////////////////////////////////////
899 /////////////////////////////////////////////////////////////////////
901 void ServerCallback(Server * server, string const & msg)
903 server->callback(msg);
906 Server::Server(LyXFunc * f, string const & pipes)
907 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
913 // say goodbye to clients so they stop sending messages
914 // send as many bye messages as there are clients,
915 // each with client's name.
917 for (int i = 0; i != numclients_; ++i) {
918 message = "LYXSRV:" + clients_[i] + ":bye\n";
919 pipes_.send(message);
924 int compare(char const * a, char const * b, unsigned int len)
927 return strncmp(a, b, len);
931 // Handle data gotten from communication, called by LyXComm
932 void Server::callback(string const & msg)
934 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
936 char const * p = msg.c_str();
938 // --- parse the string --------------------------------------------
940 // Format: LYXCMD:<client>:<func>:<argstring>\n
942 bool server_only = false;
944 // --- 1. check 'header' ---
946 if (compare(p, "LYXSRV:", 7) == 0) {
948 } else if (0 != compare(p, "LYXCMD:", 7)) {
949 lyxerr << "Server: Unknown request \""
955 // --- 2. for the moment ignore the client name ---
957 while (*p && *p != ':')
958 client += char(*p++);
964 // --- 3. get function name ---
966 while (*p && *p != ':')
969 // --- 4. parse the argument ---
971 if (!server_only && *p == ':' && *(++p)) {
972 while (*p && *p != '\n')
977 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
978 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
980 // --- lookup and exec the command ------------------
984 // return the greeting to inform the client that
986 if (cmd == "hello") {
988 if (numclients_ == MAX_CLIENTS) { //paranoid check
989 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
993 while (!clients_[i].empty() && i < numclients_)
995 clients_[i] = client;
997 buf = "LYXSRV:" + client + ":hello\n";
998 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
1000 } else if (cmd == "bye") {
1001 // If clients_ == 0 maybe we should reset the pipes
1002 // to prevent fake callbacks
1003 int i = 0; //look if client is registered
1004 for (; i < numclients_; ++i) {
1005 if (clients_[i] == client)
1008 if (i < numclients_) {
1010 clients_[i].erase();
1011 LYXERR(Debug::LYXSERVER, "Server: Client "
1012 << client << " said goodbye");
1014 LYXERR(Debug::LYXSERVER,
1015 "Server: ignoring bye messge from unregistered client" << client);
1018 LYXERR0("Server: Undefined server command " << cmd << '.');
1024 // which lyxfunc should we let it connect to?
1025 // The correct solution would be to have a
1026 // specialized (non-gui) BufferView. But how do
1027 // we do it now? Probably we should just let it
1028 // connect to the lyxfunc in the single LyXView we
1029 // support currently. (Lgb)
1031 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
1032 string const rval = to_utf8(func_->getMessage());
1034 // all commands produce an INFO or ERROR message
1035 // in the output pipe, even if they do not return
1036 // anything. See chapter 4 of Customization doc.
1038 if (func_->errorStat())
1042 buf += client + ':' + cmd + ':' + rval + '\n';
1045 // !!! we don't do any error checking -
1046 // if the client won't listen, the
1047 // message is lost and others too
1048 // maybe; so the client should empty
1049 // the outpipe before issuing a request.
1057 // Send a notify message to a client, called by WorkAreaKeyPress
1058 void Server::notifyClient(string const & s)
1060 pipes_.send("NOTIFY:" + s + "\n");
1067 #include "moc_Server.cpp"