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_; }
96 bool closing_ = false;
107 DWORD error = GetLastError();
108 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
109 FORMAT_MESSAGE_FROM_SYSTEM |
110 FORMAT_MESSAGE_IGNORE_INSERTS,
111 NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
112 (LPTSTR) &msgbuf, 0, NULL);
113 return static_cast<char *>(msgbuf);
119 DWORD WINAPI pipeServerWrapper(void * arg)
121 LyXComm * lyxcomm = reinterpret_cast<LyXComm *>(arg);
122 lyxcomm->pipeServer();
130 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
131 : pipename_(pip), client_(cli), clientcb_(ccb), stopserver_(0)
133 // Ask Qt to notify us on quit.
134 qAddPostRoutine(closing);
140 void LyXComm::pipeServer()
144 for (i = 0; i <= MAX_PIPES; ++i) {
146 string const pipename = external_path(pipeName(i));
149 open_mode = PIPE_ACCESS_INBOUND;
152 open_mode = PIPE_ACCESS_OUTBOUND;
156 // Manual-reset event, initial state = signaled
157 event_[i] = CreateEvent(NULL, TRUE, TRUE, NULL);
160 LYXERR0("LyXComm: Could not create event for pipe "
161 << pipename.c_str() << '\n' << errormsg());
166 pipe_[i].overlap.hEvent = event_[i];
167 pipe_[i].handle = CreateNamedPipe(pipename.c_str(),
168 open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT,
169 MAX_PIPES, PIPE_BUFSIZE, PIPE_BUFSIZE,
172 if (pipe_[i].handle == INVALID_HANDLE_VALUE) {
173 LYXERR0("LyXComm: Could not create pipe "
174 << pipename.c_str() << '\n' << errormsg());
180 pipe_[i].state = pipe_[i].pending_io ?
181 CONNECTING_STATE : (i < MAX_PIPES ? READING_STATE
185 // Add the stopserver_ event
186 event_[MAX_PIPES + 1] = stopserver_;
189 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
194 while (!checkStopServer()) {
195 // Indefinitely wait for the completion of an overlapped
196 // read, write, or connect operation.
197 DWORD wait = WaitForMultipleObjects(MAX_PIPES + 2, event_,
200 // Determine which pipe instance completed the operation.
201 i = wait - WAIT_OBJECT_0;
202 LASSERT(i >= 0 && i <= MAX_PIPES + 1, /**/);
204 // Check whether we were waked up for stopping the pipe server.
205 if (i == MAX_PIPES + 1)
208 // Get the result if the operation was pending.
209 if (pipe_[i].pending_io) {
210 success = GetOverlappedResult(pipe_[i].handle,
211 &pipe_[i].overlap, &status, FALSE);
213 switch (pipe_[i].state) {
214 case CONNECTING_STATE:
215 // Pending connect operation
217 DWORD const err = GetLastError();
219 && err == ERROR_IO_INCOMPLETE) {
220 // A reply on the output pipe
221 // has not been read, still.
222 // As we have only one instance
223 // for output, we risk a stalled
224 // pipe if no one reads it.
225 // So, if a reader doesn't
226 // appear within about 5 or 6
227 // seconds, we reset it.
228 static int count = 0;
235 LYXERR0("LyXComm: " << errormsg());
238 pipe_[i].state = i < MAX_PIPES ? READING_STATE
243 // Pending read operation
244 LASSERT(i < MAX_PIPES, /**/);
245 if (!success || status == 0) {
249 pipe_[i].nbytes = status;
250 pipe_[i].state = WRITING_STATE;
254 // Pending write operation
255 LASSERT(i == MAX_PIPES, /**/);
256 if (!success || status != writebuf_.length()) {
264 // Operate according to the pipe state
265 switch (pipe_[i].state) {
267 // The pipe instance is connected to a client
268 // and is ready to read a request.
269 LASSERT(i < MAX_PIPES, /**/);
270 success = ReadFile(pipe_[i].handle,
271 pipe_[i].pipebuf, PIPE_BUFSIZE - 1,
272 &pipe_[i].nbytes, &pipe_[i].overlap);
274 if (success && pipe_[i].nbytes != 0) {
275 // The read operation completed successfully.
276 pipe_[i].pending_io = false;
277 pipe_[i].state = WRITING_STATE;
281 if (!success && GetLastError() == ERROR_IO_PENDING) {
282 // The read operation is still pending.
283 pipe_[i].pending_io = true;
287 // Client closed connection (ERROR_BROKEN_PIPE) or
288 // an error occurred; in either case, reset the pipe.
289 if (GetLastError() != ERROR_BROKEN_PIPE) {
290 LYXERR0("LyXComm: " << errormsg());
291 if (!readbuf_[i].empty()) {
292 LYXERR0("LyXComm: truncated command: "
303 // The request was successfully read
304 // from the client; commit it.
305 ReadReadyEvent * event = new ReadReadyEvent(i);
306 QCoreApplication::postEvent(this,
307 static_cast<QEvent *>(event));
308 // Wait for completion
309 while (pipe_[i].nbytes && !checkStopServer())
311 pipe_[i].pending_io = false;
312 pipe_[i].state = READING_STATE;
315 // Let's see whether we have a reply.
316 if (writebuf_.length() == 0) {
317 // No, nothing to do.
318 pipe_[i].pending_io = false;
322 success = WriteFile(pipe_[i].handle,
323 writebuf_.c_str(), writebuf_.length(),
324 &status, &pipe_[i].overlap);
326 if (success && status == writebuf_.length()) {
327 // The write operation completed successfully.
329 pipe_[i].pending_io = false;
334 if (!success && (GetLastError() == ERROR_IO_PENDING)) {
335 // The write operation is still pending.
336 pipe_[i].pending_io = true;
340 // Client closed connection (ERROR_NO_DATA) or
341 // an error occurred; in either case, reset the pipe.
342 if (GetLastError() != ERROR_NO_DATA) {
343 LYXERR0("LyXComm: Error sending message: "
344 << writebuf_ << '\n' << errormsg());
353 closeHandles(MAX_PIPES + 1);
357 void LyXComm::closeHandles(DWORD index)
359 for (int i = 0; i <= index; ++i) {
361 ResetEvent(event_[i]);
362 CloseHandle(event_[i]);
364 if (pipe_[i].handle != INVALID_HANDLE_VALUE)
365 CloseHandle(pipe_[i].handle);
370 bool LyXComm::event(QEvent * e)
372 if (e->type() == QEvent::User) {
373 read_ready(static_cast<ReadReadyEvent *>(e)->inpipe());
380 BOOL LyXComm::checkStopServer()
382 return WaitForSingleObject(stopserver_, 0) == WAIT_OBJECT_0 || closing_;
386 void LyXComm::startPipe(DWORD index)
388 pipe_[index].pending_io = false;
390 // Overlapped ConnectNamedPipe should return zero.
391 if (ConnectNamedPipe(pipe_[index].handle, &pipe_[index].overlap)) {
392 // FIXME: What to do? Maybe the pipe server should be reset.
393 LYXERR0("LyXComm: Could not connect pipe "
394 << external_path(pipeName(index)) << '\n'
399 switch (GetLastError()) {
400 case ERROR_IO_PENDING:
401 // The overlapped connection is in progress.
402 pipe_[index].pending_io = true;
405 case ERROR_PIPE_CONNECTED:
406 // Client is already connected, so signal an event.
407 if (SetEvent(pipe_[index].overlap.hEvent))
410 // Anything else is an error.
411 // FIXME: What to do? Maybe the pipe server should be reset.
412 LYXERR0("LyXComm: An error occurred while connecting pipe "
413 << external_path(pipeName(index)) << '\n'
419 void LyXComm::resetPipe(DWORD index, bool close_handle)
421 // This method is called when an error occurs or when a client
422 // closes the connection. We first disconnect the pipe instance,
423 // then reconnect it, ready to wait for another client.
425 if (!DisconnectNamedPipe(pipe_[index].handle)) {
426 LYXERR0("LyXComm: Could not disconnect pipe "
427 << external_path(pipeName(index)) << '\n'
429 // What to do now? Let's try whether re-creating the pipe helps.
433 DWORD const open_mode = index < MAX_PIPES ?
434 PIPE_ACCESS_INBOUND : PIPE_ACCESS_OUTBOUND;
435 string const name = external_path(pipeName(index));
437 CloseHandle(pipe_[index].handle);
439 pipe_[index].handle = CreateNamedPipe(name.c_str(),
440 open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT,
441 MAX_PIPES, PIPE_BUFSIZE, PIPE_BUFSIZE,
444 if (pipe_[index].handle == INVALID_HANDLE_VALUE) {
445 LYXERR0("LyXComm: Could not reset pipe "
446 << name << '\n' << errormsg());
449 if (index == MAX_PIPES)
452 readbuf_[index].erase();
455 pipe_[index].state = pipe_[index].pending_io ?
456 CONNECTING_STATE : (index < MAX_PIPES ? READING_STATE
461 void LyXComm::openConnection()
463 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
465 // If we are up, that's an error
467 LYXERR0("LyXComm: Already connected");
470 // We assume that we don't make it
473 if (pipename_.empty()) {
474 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
478 // Check whether the pipe name is being used by some other program.
479 if (!stopserver_ && WaitNamedPipe(inPipeName().c_str(), 0)) {
480 LYXERR0("LyXComm: Pipe " << external_path(inPipeName())
481 << " already exists.\nMaybe another instance of LyX"
487 // Manual-reset event, initial state = not signaled
488 stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL);
489 server_thread_ = CreateThread(NULL, 0, pipeServerWrapper,
490 static_cast<void *>(this), 0, NULL);
491 if (!server_thread_) {
492 LYXERR0("LyXComm: Could not create pipe server thread\n"
501 void LyXComm::closeConnection()
503 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
505 if (pipename_.empty()) {
506 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
511 LYXERR0("LyXComm: Already disconnected");
519 void LyXComm::emergencyCleanup()
522 SetEvent(stopserver_);
523 // Wait for the pipe server to finish
524 WaitForSingleObject(server_thread_, INFINITE);
525 CloseHandle(server_thread_);
526 ResetEvent(stopserver_);
527 CloseHandle(stopserver_);
532 void LyXComm::read_ready(DWORD inpipe)
534 // Turn the pipe buffer into a C string
535 DWORD const nbytes = pipe_[inpipe].nbytes;
536 pipe_[inpipe].pipebuf[nbytes] = '\0';
538 readbuf_[inpipe] += rtrim(pipe_[inpipe].pipebuf, "\r");
540 // Commit any commands read
541 while (readbuf_[inpipe].find('\n') != string::npos) {
542 // split() grabs the entire string if
543 // the delim /wasn't/ found. ?:-P
545 readbuf_[inpipe] = split(readbuf_[inpipe], cmd, '\n');
546 cmd = rtrim(cmd, "\r");
547 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << nbytes
548 << ", readbuf_:" << readbuf_[inpipe]
551 clientcb_(client_, cmd);
554 // Signal that we are done.
555 pipe_[inpipe].nbytes = 0;
559 void LyXComm::send(string const & msg)
562 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
566 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
568 if (pipename_.empty())
572 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
576 // Wait a couple of secs for completion of a previous write operation.
577 for (int count = 0; writebuf_.length() && count < 20; ++count)
580 if (!writebuf_.length()) {
582 // Tell the pipe server he has a job to do.
583 SetEvent(pipe_[MAX_PIPES].overlap.hEvent);
585 // Nope, output pipe is still busy. Most likely, a bad client
586 // did not care to read the answer (JabRef is one such client).
587 // Let's do a reset, otherwise the output pipe could remain
588 // stalled if the pipe server failed to reset it.
589 // This will remedy the output pipe stall, but the client will
590 // get a broken pipe error.
591 LYXERR0("LyXComm: Error sending message: " << msg
592 << "\nLyXComm: Output pipe is stalled\n"
593 "LyXComm: Resetting connection");
595 if (!checkStopServer())
601 string const LyXComm::pipeName(DWORD index) const
603 return index < MAX_PIPES ? inPipeName() : outPipeName();
607 #elif !defined (HAVE_MKFIFO)
608 // We provide a stub class that disables the lyxserver.
610 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
613 void LyXComm::openConnection()
617 void LyXComm::closeConnection()
621 int LyXComm::startPipe(string const & filename, bool write)
627 void LyXComm::endPipe(int & fd, string const & filename, bool write)
631 void LyXComm::emergencyCleanup()
634 void LyXComm::read_ready()
638 void LyXComm::send(string const & msg)
642 #else // defined (HAVE_MKFIFO)
645 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
646 : pipename_(pip), client_(cli), clientcb_(ccb)
653 void LyXComm::openConnection()
655 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
657 // If we are up, that's an error
659 lyxerr << "LyXComm: Already connected" << endl;
662 // We assume that we don't make it
665 if (pipename_.empty()) {
666 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
670 infd_ = startPipe(inPipeName(), false);
674 outfd_ = startPipe(outPipeName(), true);
676 endPipe(infd_, inPipeName(), false);
680 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
681 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
682 << '\n' << strerror(errno) << endl;
688 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
693 void LyXComm::closeConnection()
695 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
697 if (pipename_.empty()) {
698 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
703 LYXERR0("LyXComm: Already disconnected");
707 endPipe(infd_, inPipeName(), false);
708 endPipe(outfd_, outPipeName(), true);
714 int LyXComm::startPipe(string const & file, bool write)
716 static bool stalepipe = false;
717 FileName const filename(file);
718 if (filename.exists()) {
720 // Let's see whether we have a stale pipe.
721 int fd = ::open(filename.toFilesystemEncoding().c_str(),
722 O_WRONLY | O_NONBLOCK);
724 // Another LyX instance is using it.
726 } else if (errno == ENXIO) {
727 // No process is reading from the other end.
729 LYXERR(Debug::LYXSERVER,
730 "LyXComm: trying to remove "
732 filename.removeFile();
734 } else if (stalepipe) {
735 LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
737 filename.removeFile();
740 if (filename.exists()) {
741 lyxerr << "LyXComm: Pipe " << filename
742 << " already exists.\nIf no other LyX program"
743 " is active, please delete the pipe by hand"
751 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
752 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
753 << strerror(errno) << endl;
756 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
757 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
760 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
761 << strerror(errno) << endl;
762 filename.removeFile();
767 theApp()->registerSocketCallback(fd,
768 boost::bind(&LyXComm::read_ready, this));
775 void LyXComm::endPipe(int & fd, string const & filename, bool write)
781 theApp()->unregisterSocketCallback(fd);
783 if (::close(fd) < 0) {
784 lyxerr << "LyXComm: Could not close pipe " << filename
785 << '\n' << strerror(errno) << endl;
788 if (FileName(filename).removeFile() < 0) {
789 lyxerr << "LyXComm: Could not remove pipe " << filename
790 << '\n' << strerror(errno) << endl;
797 void LyXComm::emergencyCleanup()
799 if (!pipename_.empty()) {
800 endPipe(infd_, inPipeName(), false);
801 endPipe(outfd_, outPipeName(), true);
806 // Receives messages and sends then to client
807 void LyXComm::read_ready()
809 // FIXME: make read_buffer_ a class-member for multiple sessions
810 static string read_buffer_;
811 read_buffer_.erase();
813 int const charbuf_size = 100;
814 char charbuf[charbuf_size];
816 // As O_NONBLOCK is set, until no data is available for reading,
817 // read() doesn't block but returns -1 and set errno to EAGAIN.
818 // After a client that opened the pipe for writing, closes it
819 // (and no other client is using the pipe), read() would always
820 // return 0 and thus the connection has to be reset.
824 // the single = is intended here.
825 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
828 charbuf[status] = '\0'; // turn it into a c string
829 read_buffer_ += rtrim(charbuf, "\r");
830 // commit any commands read
831 while (read_buffer_.find('\n') != string::npos) {
832 // split() grabs the entire string if
833 // the delim /wasn't/ found. ?:-P
835 read_buffer_= split(read_buffer_, cmd,'\n');
836 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
837 << ", read_buffer_:" << read_buffer_
840 clientcb_(client_, cmd);
844 if (errno == EAGAIN) {
845 // Nothing to read, continue
849 // An error occurred, better bailing out
850 LYXERR0("LyXComm: " << strerror(errno));
851 if (!read_buffer_.empty()) {
852 LYXERR0("LyXComm: truncated command: " << read_buffer_);
853 read_buffer_.erase();
855 break; // reset connection
859 // The connection gets reset when read() returns 0 (meaning that the
860 // last client closed the pipe) or an error occurred, in which case
861 // read() returns -1 and errno != EAGAIN.
868 void LyXComm::send(string const & msg)
871 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
875 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
877 if (pipename_.empty()) return;
880 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
881 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
882 lyxerr << "LyXComm: Error sending message: " << msg
883 << '\n' << strerror(errno)
884 << "\nLyXComm: Resetting connection" << endl;
890 #endif // defined (HAVE_MKFIFO)
893 string const LyXComm::inPipeName() const
895 return pipename_ + ".in";
899 string const LyXComm::outPipeName() const
901 return pipename_ + ".out";
905 /////////////////////////////////////////////////////////////////////
909 /////////////////////////////////////////////////////////////////////
911 void ServerCallback(Server * server, string const & msg)
913 server->callback(msg);
916 Server::Server(LyXFunc * f, string const & pipes)
917 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
923 // say goodbye to clients so they stop sending messages
924 // send as many bye messages as there are clients,
925 // each with client's name.
927 for (int i = 0; i != numclients_; ++i) {
928 message = "LYXSRV:" + clients_[i] + ":bye\n";
929 pipes_.send(message);
934 int compare(char const * a, char const * b, unsigned int len)
937 return strncmp(a, b, len);
941 // Handle data gotten from communication, called by LyXComm
942 void Server::callback(string const & msg)
944 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
946 char const * p = msg.c_str();
948 // --- parse the string --------------------------------------------
950 // Format: LYXCMD:<client>:<func>:<argstring>\n
952 bool server_only = false;
954 // --- 1. check 'header' ---
956 if (compare(p, "LYXSRV:", 7) == 0) {
958 } else if (0 != compare(p, "LYXCMD:", 7)) {
959 lyxerr << "Server: Unknown request \""
965 // --- 2. for the moment ignore the client name ---
967 while (*p && *p != ':')
968 client += char(*p++);
974 // --- 3. get function name ---
976 while (*p && *p != ':')
979 // --- 4. parse the argument ---
981 if (!server_only && *p == ':' && *(++p)) {
982 while (*p && *p != '\n')
987 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
988 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
990 // --- lookup and exec the command ------------------
994 // return the greeting to inform the client that
996 if (cmd == "hello") {
998 if (numclients_ == MAX_CLIENTS) { //paranoid check
999 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
1003 while (!clients_[i].empty() && i < numclients_)
1005 clients_[i] = client;
1007 buf = "LYXSRV:" + client + ":hello\n";
1008 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
1010 } else if (cmd == "bye") {
1011 // If clients_ == 0 maybe we should reset the pipes
1012 // to prevent fake callbacks
1013 int i = 0; //look if client is registered
1014 for (; i < numclients_; ++i) {
1015 if (clients_[i] == client)
1018 if (i < numclients_) {
1020 clients_[i].erase();
1021 LYXERR(Debug::LYXSERVER, "Server: Client "
1022 << client << " said goodbye");
1024 LYXERR(Debug::LYXSERVER,
1025 "Server: ignoring bye messge from unregistered client" << client);
1028 LYXERR0("Server: Undefined server command " << cmd << '.');
1034 // which lyxfunc should we let it connect to?
1035 // The correct solution would be to have a
1036 // specialized (non-gui) BufferView. But how do
1037 // we do it now? Probably we should just let it
1038 // connect to the lyxfunc in the single LyXView we
1039 // support currently. (Lgb)
1041 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
1042 string const rval = to_utf8(func_->getMessage());
1044 // all commands produce an INFO or ERROR message
1045 // in the output pipe, even if they do not return
1046 // anything. See chapter 4 of Customization doc.
1048 if (func_->errorStat())
1052 buf += client + ':' + cmd + ':' + rval + '\n';
1055 // !!! we don't do any error checking -
1056 // if the client won't listen, the
1057 // message is lost and others too
1058 // maybe; so the client should empty
1059 // the outpipe before issuing a request.
1067 // Send a notify message to a client, called by WorkAreaKeyPress
1068 void Server::notifyClient(string const & s)
1070 pipes_.send("NOTIFY:" + s + "\n");
1077 #include "moc_Server.cpp"