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
This commit is contained in:
Enrico Forestieri 2009-08-21 22:58:38 +00:00
parent 818ae93e0c
commit fc97861803
4 changed files with 350 additions and 2 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 <boost/bind.hpp>
#ifdef _WIN32
#include <QCoreApplication>
#endif
#include <cerrno>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
@ -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<char *>(msgbuf);
}
extern "C" {
DWORD WINAPI pipeServerWrapper(void * arg)
{
LyXComm * lyxcomm = reinterpret_cast<LyXComm *>(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<QEvent *>(event));
} else
CloseHandle(inpipe);
}
CloseHandle(outpipe_);
}
bool LyXComm::event(QEvent * e)
{
if (e->type() == QEvent::User) {
read_ready(static_cast<PipeEvent *>(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<void *>(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

View File

@ -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 <boost/signals/trackable.hpp>
#ifdef _WIN32
#include <windows.h>
#include <QObject>
#include <QEvent>
#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
};