From fc978618030ebfaa4b624764330a0d7be4bd23e8 Mon Sep 17 00:00:00 2001 From: Enrico Forestieri Date: Fri, 21 Aug 2009 22:58:38 +0000 Subject: [PATCH] Implement the LyXServer on Windows. Only for autotools, I don't know how to update cmake and scons, sorry. For cmake and scons, you should make sure that moc is called on Server.h using the -D_WIN32 option. In order to enable the server, specify the LyXServer pipe in Tools->Preferences->Paths. The path to be entered there must have the form "\\.\pipe\nameofyourchoice" (without quotes). After that, you can send commands to LyX. For example, if the pipe path is \\.\pipe\lyxpipe, typing the following in a terminal: echo LYXCMD:test:file-open > \\.\pipe\lyxpipe.in type \\.\pipe\lyxpipe.out brings up the file dialog and returns the acknowledgment from LyX. Beware of spaces when using cmd.exe. For example, the following: echo LYXCMD:test:file-open:foo.lyx> \\.\pipe\lyxpipe.in will correctly load the document named foo.lyx, but echo LYXCMD:test:file-open:foo.lyx > \\.\pipe\lyxpipe.in (notice the space before the redirection) will try to load a document whose name is "foo.lyx .lyx" because cmd.exe will also pass the space (sigh). git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@31189 a592a061-630c-0410-9148-cb99ea01b6c8 --- config/lyxinclude.m4 | 5 +- src/Makefile.am | 16 +++ src/Server.cpp | 297 ++++++++++++++++++++++++++++++++++++++++++- src/Server.h | 34 +++++ 4 files changed, 350 insertions(+), 2 deletions(-) diff --git a/config/lyxinclude.m4 b/config/lyxinclude.m4 index 446dce894e..6e438e36da 100644 --- a/config/lyxinclude.m4 +++ b/config/lyxinclude.m4 @@ -611,6 +611,7 @@ AC_ARG_WITH(packaging, AC_MSG_RESULT($lyx_use_packaging) lyx_install_macosx=false lyx_install_cygwin=false +lyx_install_windows=false case $lyx_use_packaging in macosx) AC_DEFINE(USE_MACOSX_PACKAGING, 1, [Define to 1 if LyX should use a MacOS X application bundle file layout]) PACKAGE=LyX${version_suffix} @@ -628,7 +629,8 @@ case $lyx_use_packaging in libdir='${prefix}/Resources' datarootdir='${prefix}/Resources' pkgdatadir='${datadir}' - mandir='${prefix}/Resources/man' ;; + mandir='${prefix}/Resources/man' + lyx_install_windows=true ;; posix) AC_DEFINE(USE_POSIX_PACKAGING, 1, [Define to 1 if LyX should use a POSIX-style file layout]) PACKAGE=lyx${version_suffix} program_suffix=$version_suffix @@ -641,6 +643,7 @@ case $lyx_use_packaging in esac AM_CONDITIONAL(INSTALL_MACOSX, $lyx_install_macosx) AM_CONDITIONAL(INSTALL_CYGWIN, $lyx_install_cygwin) +AM_CONDITIONAL(INSTALL_WINDOWS, $lyx_install_windows) dnl Next two lines are only for autoconf <= 2.59 datadir='${datarootdir}' AC_SUBST(datarootdir) diff --git a/src/Makefile.am b/src/Makefile.am index 6bcfca1e4e..b6c4242226 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -297,6 +297,22 @@ liblyxcore_a_SOURCES = $(SOURCEFILESCORE) $(STANDALONEFILES) $(HEADERFILESCORE) endif +if INSTALL_WINDOWS + +MOCHEADER = Server.h + +MOCEDFILES = $(MOCHEADER:%.h=moc_%.cpp) + +BUILT_SOURCES += $(MOCEDFILES) +CLEANFILES += $(MOCEDFILES) + +moc_%.cpp: %.h + $(MOC4) -D_WIN32 -o $@ $< + +liblyxcore_a_DEPENDENCIES = $(MOCEDFILES) + +endif + ############################### Graphics ############################## noinst_LIBRARIES += liblyxgraphics.a diff --git a/src/Server.cpp b/src/Server.cpp index d7a2e2514c..77c1a5981a 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -7,6 +7,7 @@ * \author Jean-Marc Lasgouttes * \author Angus Leeming * \author John Levon + * \author Enrico Forestieri * * Full author contact details are available in file CREDITS. */ @@ -49,9 +50,14 @@ #include "support/debug.h" #include "support/FileName.h" #include "support/lstrings.h" +#include "support/os.h" #include +#ifdef _WIN32 +#include +#endif + #include #ifdef HAVE_SYS_STAT_H # include @@ -60,6 +66,7 @@ using namespace std; using namespace lyx::support; +using os::external_path; namespace lyx { @@ -69,7 +76,291 @@ namespace lyx { // ///////////////////////////////////////////////////////////////////// -#if !defined (HAVE_MKFIFO) +#if defined(_WIN32) + +class PipeEvent : public QEvent { +public: + /// + PipeEvent(HANDLE inpipe) : QEvent(QEvent::User), inpipe_(inpipe) + {} + /// + HANDLE pipe() const { return inpipe_; } + +private: + HANDLE inpipe_; +}; + +namespace { + +char * errormsg() +{ + void * msgbuf; + DWORD error = GetLastError(); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msgbuf, 0, NULL); + return static_cast(msgbuf); +} + + +extern "C" { + +DWORD WINAPI pipeServerWrapper(void * arg) +{ + LyXComm * lyxcomm = reinterpret_cast(arg); + lyxcomm->pipeServer(); + return 1; +} + +} + +} // namespace anon + +LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb) + : pipename_(pip), client_(cli), clientcb_(ccb) +{ + ready_ = false; + openConnection(); +} + + +void LyXComm::pipeServer() +{ + int const bufsize = 1024; + HANDLE inpipe; + + outpipe_ = CreateNamedPipe(external_path(outPipeName()).c_str(), + PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT, + PIPE_UNLIMITED_INSTANCES, + bufsize, 0, 0, NULL); + if (outpipe_ == INVALID_HANDLE_VALUE) { + lyxerr << "LyXComm: Could not create pipe " + << outPipeName() << '\n' << errormsg() << endl; + return; + } + ConnectNamedPipe(outpipe_, NULL); + // Now change to blocking mode + DWORD mode = PIPE_WAIT; + SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL); + + while (!checkStopServerEvent()) { + inpipe = CreateNamedPipe(external_path(inPipeName()).c_str(), + PIPE_ACCESS_INBOUND, PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + 0, bufsize, 0, NULL); + if (inpipe == INVALID_HANDLE_VALUE) { + lyxerr << "LyXComm: Could not create pipe " + << inPipeName() << '\n' << errormsg() << endl; + break; + } + + BOOL connected = ConnectNamedPipe(inpipe, NULL); + // Check whether we are signaled to shutdown the pipe server. + if (checkStopServerEvent()) { + CloseHandle(inpipe); + break; + } + if (connected || GetLastError() == ERROR_PIPE_CONNECTED) { + PipeEvent * event = new PipeEvent(inpipe); + QCoreApplication::postEvent(this, + static_cast(event)); + } else + CloseHandle(inpipe); + } + CloseHandle(outpipe_); +} + + +bool LyXComm::event(QEvent * e) +{ + if (e->type() == QEvent::User) { + read_ready(static_cast(e)->pipe()); + return true; + } + return false; +} + + +BOOL LyXComm::checkStopServerEvent() +{ + return WaitForSingleObject(stopserver_, 0) == WAIT_OBJECT_0; +} + + +void LyXComm::openConnection() +{ + LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection"); + + // 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()) { + LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do"); + return; + } + + stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL); + DWORD tid; + HANDLE thread = CreateThread(NULL, 0, pipeServerWrapper, + static_cast(this), 0, &tid); + if (!thread) { + lyxerr << "LyXComm: Could not create pipe server thread: " + << errormsg() << endl; + return; + } else + CloseHandle(thread); + + // We made it! + ready_ = true; + LYXERR(Debug::LYXSERVER, "LyXComm: Connection established"); +} + + +/// Close pipes +void LyXComm::closeConnection() +{ + LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection"); + + if (pipename_.empty()) { + LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do"); + return; + } + + if (!ready_) { + LYXERR0("LyXComm: Already disconnected"); + return; + } + + SetEvent(stopserver_); + HANDLE hpipe = CreateFile(external_path(inPipeName()).c_str(), + GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hpipe != INVALID_HANDLE_VALUE) + CloseHandle(hpipe); + + ready_ = false; +} + + +void LyXComm::emergencyCleanup() +{ + if (!pipename_.empty()) { + SetEvent(stopserver_); + HANDLE hpipe = CreateFile(external_path(inPipeName()).c_str(), + GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hpipe != INVALID_HANDLE_VALUE) + CloseHandle(hpipe); + } +} + +void LyXComm::read_ready(HANDLE inpipe) +{ + string read_buffer_; + read_buffer_.erase(); + + int const charbuf_size = 100; + char charbuf[charbuf_size]; + + DWORD status; + while (ReadFile(inpipe, charbuf, charbuf_size - 1, &status, NULL)) { + if (status > 0) { + charbuf[status] = '\0'; // turn it into a c string + read_buffer_ += rtrim(charbuf, "\r"); + // commit any commands read + while (read_buffer_.find('\n') != string::npos) { + // split() grabs the entire string if + // the delim /wasn't/ found. ?:-P + string cmd; + read_buffer_= split(read_buffer_, cmd, '\n'); + cmd = rtrim(cmd, "\r"); + LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status + << ", read_buffer_:" << read_buffer_ + << ", cmd:" << cmd); + if (!cmd.empty()) + clientcb_(client_, cmd); + //\n or not \n? + } + } + } + if (GetLastError() != ERROR_BROKEN_PIPE) { + // An error occurred + LYXERR0("LyXComm: " << errormsg()); + if (!read_buffer_.empty()) { + LYXERR0("LyXComm: truncated command: " << read_buffer_); + read_buffer_.erase(); + } + } + // Client closed the pipe, so disconnect and close. + DisconnectNamedPipe(inpipe); + CloseHandle(inpipe); + FlushFileBuffers(outpipe_); + DisconnectNamedPipe(outpipe_); + // Temporarily change to non-blocking mode otherwise + // ConnectNamedPipe() would block waiting for a connection. + DWORD mode = PIPE_NOWAIT; + SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL); + ConnectNamedPipe(outpipe_, NULL); + mode = PIPE_WAIT; + SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL); +} + + +void LyXComm::send(string const & msg) +{ + if (msg.empty()) { + LYXERR0("LyXComm: Request to send empty string. Ignoring."); + return; + } + + LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\''); + + if (pipename_.empty()) return; + + if (!ready_) { + LYXERR0("LyXComm: Pipes are closed. Could not send " << msg); + return; + } + + bool success; + int count = 0; + do { + DWORD sent; + success = WriteFile(outpipe_, msg.c_str(), msg.length(), &sent, NULL); + if (!success) { + DWORD error = GetLastError(); + if (error == ERROR_NO_DATA) { + DisconnectNamedPipe(outpipe_); + DWORD mode = PIPE_NOWAIT; + SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL); + ConnectNamedPipe(outpipe_, NULL); + mode = PIPE_WAIT; + SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL); + } else if (error != ERROR_PIPE_LISTENING) + break; + } + Sleep(100); + ++count; + } while (!success && count < 100); + + if (!success) { + lyxerr << "LyXComm: Error sending message: " << msg + << '\n' << errormsg() + << "\nLyXComm: Resetting connection" << endl; + closeConnection(); + openConnection(); + } +} + + +#elif !defined (HAVE_MKFIFO) // We provide a stub class that disables the lyxserver. LyXComm::LyXComm(string const &, Server *, ClientCallbackfct) @@ -537,3 +828,7 @@ void Server::notifyClient(string const & s) } // namespace lyx + +#ifdef _WIN32 +#include "moc_Server.cpp" +#endif diff --git a/src/Server.h b/src/Server.h index 0c595c861f..31e7c4b7c4 100644 --- a/src/Server.h +++ b/src/Server.h @@ -6,6 +6,7 @@ * * \author Lars Gullik Bjønnes * \author Jean-Marc Lasgouttes + * \author Enrico Forestieri * * Full author contact details are available in file CREDITS. */ @@ -15,6 +16,12 @@ #include +#ifdef _WIN32 +#include +#include +#include +#endif + namespace lyx { @@ -29,7 +36,12 @@ class Server; This class encapsulates all the dirty communication and thus provides a clean string interface. */ +#ifndef _WIN32 class LyXComm : public boost::signals::trackable { +#else +class LyXComm : public QObject { + Q_OBJECT +#endif public: /** When we receive a message, we send it to a client. This is one of the small things that would have been a lot @@ -50,7 +62,14 @@ public: void send(std::string const &); /// asynch ready-to-be-read notification +#ifndef _WIN32 void read_ready(); +#else + void read_ready(HANDLE); + + /// The pipe server + void pipeServer(); +#endif private: /// the filename of the in pipe @@ -65,6 +84,7 @@ private: /// Close pipes void closeConnection(); +#ifndef _WIN32 /// start a pipe int startPipe(std::string const &, bool); @@ -76,6 +96,9 @@ private: /// This is -1 if not open int outfd_; +#else + HANDLE outpipe_; +#endif /// Are we up and running? bool ready_; @@ -88,6 +111,17 @@ private: /// The client callback function ClientCallbackfct clientcb_; + +#ifdef _WIN32 + /// Catch pipe ready-to-be-read notification + bool event(QEvent *); + + /// Check whether the pipe server must be stopped + BOOL checkStopServerEvent(); + + /// Windows event for stopping the pipe server + HANDLE stopserver_; +#endif }; -- 2.39.5