diff --git a/config/lyxinclude.m4 b/config/lyxinclude.m4 index f2af87023c..e1d5d04456 100644 --- a/config/lyxinclude.m4 +++ b/config/lyxinclude.m4 @@ -610,6 +610,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} @@ -627,7 +628,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 @@ -640,6 +642,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/development/cmake/modules/LyXMacros.cmake b/development/cmake/modules/LyXMacros.cmake index 10797b3f28..7aa8dcb5ba 100644 --- a/development/cmake/modules/LyXMacros.cmake +++ b/development/cmake/modules/LyXMacros.cmake @@ -105,9 +105,12 @@ macro(LYX_AUTOMOC) set(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_current_MOC}) #set(_moc ${_abs_PATH}/${_current_MOC}) + if (WIN32) + set(_def -D_WIN32) + endif() add_custom_command(OUTPUT ${_moc} COMMAND ${QT_MOC_EXECUTABLE} - ARGS ${_moc_INCS} ${_header} -o ${_moc} + ARGS ${_def} ${_moc_INCS} ${_header} -o ${_moc} MAIN_DEPENDENCY ${_header}) macro_add_file_dependencies(${_abs_FILE} ${_moc}) endforeach (_current_MOC_INC) diff --git a/development/cmake/src/CMakeLists.txt b/development/cmake/src/CMakeLists.txt index 6db9f1d1e8..8d2704776f 100644 --- a/development/cmake/src/CMakeLists.txt +++ b/development/cmake/src/CMakeLists.txt @@ -34,8 +34,10 @@ if (ASPELL_FOUND) set(lyx_sources ${lyx_sources} ${TOP_SRC_DIR}/src/ASpell.cpp) endif() +lyx_automoc(${TOP_SRC_DIR}/src/Server.cpp) + include_directories(${CMAKE_CURRENT_BINARY_DIR} - ${ZLIB_INCLUDE_DIR}) + ${ZLIB_INCLUDE_DIR} ${QT_INCLUDES}) lyx_add_msvc_pch(lyx) diff --git a/development/scons/SConstruct b/development/scons/SConstruct index d587bd4b9d..4bbb7b2643 100644 --- a/development/scons/SConstruct +++ b/development/scons/SConstruct @@ -903,6 +903,7 @@ result = utils.createConfigFile(conf, ('locale.h', 'HAVE_LOCALE_H', 'c'), ('process.h', 'HAVE_PROCESS_H', 'c'), ('stdlib.h', 'HAVE_STDLIB_H', 'c'), + ('string.h', 'HAVE_STRING_H', 'c'), ('sys/stat.h', 'HAVE_SYS_STAT_H', 'c'), ('sys/time.h', 'HAVE_SYS_TIME_H', 'c'), ('sys/types.h', 'HAVE_SYS_TYPES_H', 'c'), @@ -1287,30 +1288,29 @@ if platform_name == 'cygwin': # to be built with all the include directories etc # if frontend == 'qt4': - frontend_env = env.Clone() - frontend_env['BUILDERS']['qtResource'] = Builder(action = utils.env_qtResource) + env['BUILDERS']['qtResource'] = Builder(action = utils.env_qtResource) # handle qt related user specified paths # set environment so that moc etc can be found even if its path is not set properly - if frontend_env.has_key('qt_dir') and frontend_env['qt_dir']: - frontend_env['QTDIR'] = frontend_env['qt_dir'] - if os.path.isdir(os.path.join(frontend_env['qt_dir'], 'bin')): - os.environ['PATH'] += os.pathsep + os.path.join(frontend_env['qt_dir'], 'bin') - frontend_env.PrependENVPath('PATH', os.path.join(frontend_env['qt_dir'], 'bin')) - if os.path.isdir(os.path.join(frontend_env['qt_dir'], 'lib')): - frontend_env.PrependENVPath('PKG_CONFIG_PATH', os.path.join(frontend_env['qt_dir'], 'lib')) + if env.has_key('qt_dir') and env['qt_dir']: + env['QTDIR'] = env['qt_dir'] + if os.path.isdir(os.path.join(env['qt_dir'], 'bin')): + os.environ['PATH'] += os.pathsep + os.path.join(env['qt_dir'], 'bin') + env.PrependENVPath('PATH', os.path.join(env['qt_dir'], 'bin')) + if os.path.isdir(os.path.join(env['qt_dir'], 'lib')): + env.PrependENVPath('PKG_CONFIG_PATH', os.path.join(env['qt_dir'], 'lib')) # if separate qt_lib_path is given - if frontend_env.has_key('qt_lib_path') and frontend_env['qt_lib_path']: - qt_lib_path = frontend_env.subst('$qt_lib_path') - frontend_env.AppendUnique(LIBPATH = [qt_lib_path]) - frontend_env.PrependENVPath('PKG_CONFIG_PATH', qt_lib_path) + if env.has_key('qt_lib_path') and env['qt_lib_path']: + qt_lib_path = env.subst('$qt_lib_path') + env.AppendUnique(LIBPATH = [qt_lib_path]) + env.PrependENVPath('PKG_CONFIG_PATH', qt_lib_path) else: qt_lib_path = None # if separate qt_inc_path is given - if frontend_env.has_key('qt_inc_path') and frontend_env['qt_inc_path']: - qt_inc_path = frontend_env['qt_inc_path'] + if env.has_key('qt_inc_path') and env['qt_inc_path']: + qt_inc_path = env['qt_inc_path'] else: qt_inc_path = None @@ -1320,18 +1320,20 @@ if frontend == 'qt4': # NOTE: I have to patch qt4.py since it does not automatically # process .C file!!! (add to cxx_suffixes ) # - frontend_env.Tool('qt4', [scons_dir]) - frontend_env['QT_AUTOSCAN'] = 0 - frontend_env['QT4_AUTOSCAN'] = 0 - frontend_env['QT4_UICDECLFLAGS'] = '-tr lyx::qt_' + env.Tool('qt4', [scons_dir]) + env['QT_AUTOSCAN'] = 0 + env['QT4_AUTOSCAN'] = 0 + env['QT4_UICDECLFLAGS'] = '-tr lyx::qt_' + if platform_name == 'win32': + env['QT4_MOCFROMHFLAGS'] = '-D_WIN32' if qt_lib_path is None: - qt_lib_path = os.path.join(frontend_env.subst('$QTDIR'), 'lib') + qt_lib_path = os.path.join(env.subst('$QTDIR'), 'lib') if qt_inc_path is None: - qt_inc_path = os.path.join(frontend_env.subst('$QTDIR'), 'include') + qt_inc_path = os.path.join(env.subst('$QTDIR'), 'include') - conf = Configure(frontend_env, + conf = Configure(env, custom_tests = { 'CheckPackage' : utils.checkPackage, 'CheckCommand' : utils.checkCommand, @@ -1340,10 +1342,10 @@ if frontend == 'qt4': succ = False # first: try pkg_config - if frontend_env['HAS_PKG_CONFIG']: + if env['HAS_PKG_CONFIG']: succ = conf.CheckPackage('QtCore') or conf.CheckPackage('QtCore4') # FIXME: use pkg_config information? - #frontend_env['QT4_PKG_CONFIG'] = succ + #env['QT4_PKG_CONFIG'] = succ # second: try to link to it if not succ: # Under linux, I can test the following perfectly @@ -1375,7 +1377,7 @@ if frontend == 'qt4': else: qt_lib_suffix = '' use_qt_debug_libs = False - frontend_env.EnableQt4Modules(qt_libs, debug = (mode == 'debug' and use_qt_debug_libs)) + env.EnableQt4Modules(qt_libs, debug = (mode == 'debug' and use_qt_debug_libs)) frontend_libs = [x + qt_lib_suffix for x in qt_libs] qtcore_lib = ['QtCore' + qt_lib_suffix] @@ -1388,11 +1390,11 @@ if frontend == 'qt4': # NOTE: previously, lyx.exe had to be linked to some qt manifest to work. # For some unknown changes in msvc or qt, this is no longer needed. if use_vc: - frontend_env['LINKCOM'] = [frontend_env['LINKCOM'], \ + env['LINKCOM'] = [env['LINKCOM'], \ 'mt.exe /MANIFEST %s /outputresource:$TARGET;1' % \ env.File('$BUILDDIR/lyx.exe.manifest').path] - frontend_env = conf.Finish() + env = conf.Finish() # # Report results @@ -1435,7 +1437,7 @@ env.SConsignFile(os.path.join(Dir(env['BUILDDIR']).abspath, '.sconsign')) env.BuildDir('$BUILDDIR/boost', '$TOP_SRCDIR/boost/libs', duplicate = 0) env.BuildDir('$BUILDDIR/intl', '$TOP_SRCDIR/intl', duplicate = 0) env.BuildDir('$BUILDDIR/src', '$TOP_SRCDIR/src', duplicate = 0) -frontend_env.BuildDir('$BUILDDIR/src', '$TOP_SRCDIR/src', duplicate = 0) +env.BuildDir('$BUILDDIR/src', '$TOP_SRCDIR/src', duplicate = 0) print "Building all targets recursively" @@ -1493,12 +1495,12 @@ if (included_gettext and not libExists('included_intl')) or 'intl' in BUILD_TARG # # src/support # -frontend_env['QT4_MOCHPREFIX'] = '' -frontend_env['QT4_MOCHSUFFIX'] = '_moc.cpp' +env['QT4_MOCHPREFIX'] = '' +env['QT4_MOCHSUFFIX'] = '_moc.cpp' -support_moced_files = [frontend_env.Moc4('$BUILDDIR/src/support/%s' % x) +support_moced_files = [env.Moc4('$BUILDDIR/src/support/%s' % x) for x in src_support_header_files ] -support = frontend_env.StaticLibrary( +support = env.StaticLibrary( target = '$LOCALLIBPATH/support', source = ['$BUILDDIR/src/support/%s' % x for x in src_support_files], CCFLAGS = [ @@ -1511,67 +1513,81 @@ support = frontend_env.StaticLibrary( ) Alias('support', support) +# +# lyxclient +# +if env['HAVE_FCNTL']: + client = env.Program( + target = '$BUILDDIR/src/client/lyxclient', + LIBS = ['support'] + intl_libs + system_libs + + socket_libs + boost_libraries + qtcore_lib, + source = ['$BUILDDIR/src/client/%s' % x for x in src_client_files] + \ + utils.createResFromIcon(env, 'lyx.ico', '$LOCALLIBPATH/client.rc') + ) + Alias('client', env.Command(os.path.join('$BUILDDIR', os.path.split(str(client[0]))[1]), + client, [Copy('$TARGET', '$SOURCE')])) +else: + client = None +Alias('client', client) + # -# src/mathed +# tex2lyx # -mathed = env.StaticLibrary( - target = '$LOCALLIBPATH/mathed', - source = ['$BUILDDIR/src/mathed/%s' % x for x in src_mathed_files] +for file in src_tex2lyx_copied_files + src_tex2lyx_copied_header_files: + env.Command('$BUILDDIR/src/tex2lyx/'+file, '$TOP_SRCDIR/src/'+file, + [Copy('$TARGET', '$SOURCE')]) + +tex2lyx = env.Program( + target = '$BUILDDIR/src/tex2lyx/tex2lyx', + LIBS = ['support'] + boost_libraries + intl_libs + system_libs + qtcore_lib, + source = ['$BUILDDIR/src/tex2lyx/%s' % x for x in src_tex2lyx_files + src_tex2lyx_copied_files] + \ + utils.createResFromIcon(env, 'lyx.ico', '$LOCALLIBPATH/tex2lyx.rc'), + CPPPATH = ['$BUILDDIR/src/tex2lyx', '$BUILDDIR/src', '$CPPPATH'], + LIBPATH = ['#$LOCALLIBPATH', '$LIBPATH'], + CCFLAGS = ['$CCFLAGS', '-DTEX2LYX'], ) -Alias('mathed', mathed) +Alias('tex2lyx', env.Command(os.path.join('$BUILDDIR', os.path.split(str(tex2lyx[0]))[1]), + tex2lyx, [Copy('$TARGET', '$SOURCE')])) +Alias('tex2lyx', tex2lyx) # -# src/insets +# Build lyx with given frontend # -insets = env.StaticLibrary( - target = '$LOCALLIBPATH/insets', - source = ['$BUILDDIR/src/insets/%s' % x for x in src_insets_files] -) -Alias('insets', insets) +if env.has_key('USE_ASPELL') and env['USE_ASPELL']: + src_post_files.append('ASpell.cpp') +elif env.has_key('USE_PSPELL') and env['USE_PSPELL']: + src_post_files.append('PSpell.cpp') +elif env.has_key('USE_ISPELL') and env['USE_ISPELL']: + src_post_files.append('ISpell.cpp') - -# -# src/frontends -# -frontends = env.StaticLibrary( - target = '$LOCALLIBPATH/frontends', - source = ['$BUILDDIR/src/frontends/%s' % x for x in src_frontends_files] -) -Alias('frontends', frontends) - - -# -# src/graphics -# -graphics = env.StaticLibrary( - target = '$LOCALLIBPATH/graphics', - source = ['$BUILDDIR/src/graphics/%s' % x for x in src_graphics_files] -) -Alias('graphics', graphics) - - -# -# src/frontend/qt4 -# # tells scons how to get these moced files, although not all moced files are needed # (or are actually generated). -qt4_moced_files = [frontend_env.Moc4('$BUILDDIR/src/frontends/qt4/%s' % x) +qt4_moced_files = [env.Moc4('$BUILDDIR/src/frontends/qt4/%s' % x) for x in src_frontends_qt4_header_files ] -ui_files = [frontend_env.Uic4('$BUILDDIR/src/frontends/qt4/ui/%s' % x.split('.')[0]) +src_moced_files = [env.Moc4('$BUILDDIR/src/%s' % x) + for x in src_header_files ] +ui_files = [env.Uic4('$BUILDDIR/src/frontends/qt4/ui/%s' % x.split('.')[0]) for x in src_frontends_qt4_ui_files] -resource = frontend_env.Qrc(frontend_env.qtResource( +resource = env.Qrc(env.qtResource( '$BUILDDIR/src/frontends/qt4/Resource.qrc', ['$TOP_SRCDIR/lib/images/%s' % x for x in lib_images_files] + ['$TOP_SRCDIR/lib/images/math/%s' % x for x in lib_images_math_files] + ['$TOP_SRCDIR/lib/images/commands/%s' % x for x in lib_images_commands_files])) -# -# moc qt4_moc_files, the moced files are included in the original files -# -qt4 = frontend_env.StaticLibrary( - target = '$LOCALLIBPATH/qt4', - source = ['$BUILDDIR/src/frontends/qt4/%s' % x for x in src_frontends_qt4_files] + resource, + +lyx = env.Program( + target = '$BUILDDIR/lyx', + source = ['$BUILDDIR/src/main.cpp'] + + ['$BUILDDIR/src/frontends/qt4/%s' % x for x in src_frontends_qt4_files] + + resource + + ['$BUILDDIR/src/graphics/%s' % x for x in src_graphics_files] + + ['$BUILDDIR/src/mathed/%s' % x for x in src_mathed_files] + + ['$BUILDDIR/src/insets/%s' % x for x in src_insets_files] + + ['$BUILDDIR/src/frontends/%s' % x for x in src_frontends_files] + + ['$BUILDDIR/src/%s' % x for x in src_pre_files] + + ["$BUILDDIR/src/%s" % x for x in src_post_files] + + utils.createResFromIcon(env, 'lyx.ico', '$LOCALLIBPATH/lyx.rc'), CPPPATH = [ '$CPPPATH', '$BUILDDIR/src', @@ -1585,91 +1601,11 @@ qt4 = frontend_env.StaticLibrary( '-DHAVE_CONFIG_H', '-DQT_NO_STL', '-DQT_NO_KEYWORDS', - ] -) -Alias('qt4', qt4) - - -# -# src/client -# -if env['HAVE_FCNTL']: - client = frontend_env.Program( - target = '$BUILDDIR/src/client/lyxclient', - LIBS = ['support'] + intl_libs + system_libs + - socket_libs + boost_libraries + qtcore_lib, - source = ['$BUILDDIR/src/client/%s' % x for x in src_client_files] + \ - utils.createResFromIcon(frontend_env, 'lyx.ico', '$LOCALLIBPATH/client.rc') - ) - Alias('client', frontend_env.Command(os.path.join('$BUILDDIR', os.path.split(str(client[0]))[1]), - client, [Copy('$TARGET', '$SOURCE')])) -else: - client = None -Alias('client', client) - - -# -# tex2lyx -# -for file in src_tex2lyx_copied_files + src_tex2lyx_copied_header_files: - frontend_env.Command('$BUILDDIR/src/tex2lyx/'+file, '$TOP_SRCDIR/src/'+file, - [Copy('$TARGET', '$SOURCE')]) - -tex2lyx = frontend_env.Program( - target = '$BUILDDIR/src/tex2lyx/tex2lyx', - LIBS = ['support'] + boost_libraries + intl_libs + system_libs + qtcore_lib, - source = ['$BUILDDIR/src/tex2lyx/%s' % x for x in src_tex2lyx_files + src_tex2lyx_copied_files] + \ - utils.createResFromIcon(frontend_env, 'lyx.ico', '$LOCALLIBPATH/tex2lyx.rc'), - CPPPATH = ['$BUILDDIR/src/tex2lyx', '$BUILDDIR/src', '$CPPPATH'], - LIBPATH = ['#$LOCALLIBPATH', '$LIBPATH'], - CCFLAGS = ['$CCFLAGS', '-DTEX2LYX'], -) -Alias('tex2lyx', frontend_env.Command(os.path.join('$BUILDDIR', os.path.split(str(tex2lyx[0]))[1]), - tex2lyx, [Copy('$TARGET', '$SOURCE')])) -Alias('tex2lyx', tex2lyx) - - -# -# src/ -# -if env.has_key('USE_ASPELL') and env['USE_ASPELL']: - src_post_files.append('ASpell.cpp') -elif env.has_key('USE_PSPELL') and env['USE_PSPELL']: - src_post_files.append('PSpell.cpp') -elif env.has_key('USE_ISPELL') and env['USE_ISPELL']: - src_post_files.append('ISpell.cpp') - -# msvc requires at least one source file with main() -# so I exclude main.cpp from lyxbase -lyxbase_pre = env.StaticLibrary( - target = '$LOCALLIBPATH/lyxbase_pre', - source = ['$BUILDDIR/src/%s' % x for x in src_pre_files] -) -lyxbase_post = env.StaticLibrary( - target = '$LOCALLIBPATH/lyxbase_post', - source = ["$BUILDDIR/src/%s" % x for x in src_post_files] -) -Alias('lyxbase', lyxbase_pre) -Alias('lyxbase', lyxbase_post) - - -# -# Build lyx with given frontend -# -lyx = frontend_env.Program( - target = '$BUILDDIR/lyx', - source = ['$BUILDDIR/src/main.cpp'] + \ - utils.createResFromIcon(frontend_env, 'lyx.ico', '$LOCALLIBPATH/lyx.rc'), + ], LIBS = [ - 'lyxbase_pre', - 'mathed', - 'insets', - 'frontends', - frontend, - 'graphics', 'support', ] + - boost_libraries + ['lyxbase_post'] + + boost_libraries + frontend_libs + intl_libs + socket_libs + diff --git a/src/LyXFunc.cpp b/src/LyXFunc.cpp index 493c457afb..99f2394d14 100644 --- a/src/LyXFunc.cpp +++ b/src/LyXFunc.cpp @@ -1168,6 +1168,7 @@ void LyXFunc::dispatch(FuncRequest const & cmd) int row; istringstream is(argument); is >> file_name >> row; + file_name = os::internal_path(file_name); Buffer * buf = 0; bool loaded = false; string const abstmp = package().temp_dir().absFilename(); diff --git a/src/Makefile.am b/src/Makefile.am index 1361a00c10..9fc4d2f685 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,6 +5,7 @@ include $(top_srcdir)/config/common.am DISTCLEANFILES += config.h libintl.h AM_CPPFLAGS += $(PCH_FLAGS) -I$(top_srcdir)/src $(BOOST_INCLUDES) +AM_CPPFLAGS += $(QT4_CPPFLAGS) $(QT4_CORE_INCLUDES) if BUILD_CLIENT_SUBDIR CLIENT = client @@ -289,6 +290,22 @@ liblyxcore_la_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_la_DEPENDENCIES = $(MOCEDFILES) + +endif + ############################### Graphics ############################## noinst_LTLIBRARIES += liblyxgraphics.la diff --git a/src/Server.cpp b/src/Server.cpp index 8e464caf10..c42eb21d33 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. */ @@ -48,10 +49,16 @@ #include "support/debug.h" #include "support/FileName.h" +#include "support/lassert.h" #include "support/lstrings.h" +#include "support/os.h" #include +#ifdef _WIN32 +#include +#endif + #include #ifdef HAVE_SYS_STAT_H # include @@ -60,6 +67,7 @@ using namespace std; using namespace lyx::support; +using os::external_path; namespace lyx { @@ -69,7 +77,607 @@ namespace lyx { // ///////////////////////////////////////////////////////////////////// -#if !defined (HAVE_MKFIFO) +#if defined(_WIN32) + +class ReadReadyEvent : public QEvent { +public: + /// + ReadReadyEvent(DWORD inpipe) : QEvent(QEvent::User), inpipe_(inpipe) + {} + /// + DWORD inpipe() const { return inpipe_; } + +private: + DWORD inpipe_; +}; + +namespace { + +string errormsg(DWORD const error) +{ + void * msgbuf; + string message; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msgbuf, 0, NULL)) { + message = static_cast(msgbuf); + LocalFree(msgbuf); + } else + message = "Unknown error"; + + return message; +} + +} // namespace anon + + +DWORD WINAPI pipeServerWrapper(void * arg) +{ + LyXComm * lyxcomm = reinterpret_cast(arg); + if (!lyxcomm->pipeServer()) { + // Error exit; perform cleanup. + lyxcomm->ready_ = false; + lyxcomm->closeHandles(); + CloseHandle(lyxcomm->server_thread_); + CloseHandle(lyxcomm->stopserver_); + CloseHandle(lyxcomm->outbuf_mutex_); + lyxerr << "LyXComm: Closing connection" << endl; + } + return 1; +} + + +LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb) + : pipename_(pip), client_(cli), clientcb_(ccb), stopserver_(0) +{ + for (int i = 0; i < MAX_PIPES; ++i) { + event_[i] = 0; + pipe_[i].handle = INVALID_HANDLE_VALUE; + } + ready_ = false; + openConnection(); +} + + +bool LyXComm::pipeServer() +{ + DWORD i; + DWORD error; + + for (i = 0; i < MAX_PIPES; ++i) { + bool const is_outpipe = i >= MAX_CLIENTS; + DWORD const open_mode = is_outpipe ? PIPE_ACCESS_OUTBOUND + : PIPE_ACCESS_INBOUND; + string const pipename = external_path(pipeName(i)); + + // Manual-reset event, initial state = signaled + event_[i] = CreateEvent(NULL, TRUE, TRUE, NULL); + if (!event_[i]) { + error = GetLastError(); + lyxerr << "LyXComm: Could not create event for pipe " + << pipename << "\nLyXComm: " + << errormsg(error) << endl; + return false; + } + + pipe_[i].overlap.hEvent = event_[i]; + pipe_[i].iobuf.erase(); + pipe_[i].handle = CreateNamedPipe(pipename.c_str(), + open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT, + MAX_CLIENTS, PIPE_BUFSIZE, PIPE_BUFSIZE, + PIPE_TIMEOUT, NULL); + + if (pipe_[i].handle == INVALID_HANDLE_VALUE) { + error = GetLastError(); + lyxerr << "LyXComm: Could not create pipe " + << pipename << "\nLyXComm: " + << errormsg(error) << endl; + return false; + } + + if (!startPipe(i)) + return false; + pipe_[i].state = pipe_[i].pending_io ? + CONNECTING_STATE : (is_outpipe ? WRITING_STATE + : READING_STATE); + } + + // Add the stopserver_ event + event_[MAX_PIPES] = stopserver_; + + // We made it! + LYXERR(Debug::LYXSERVER, "LyXComm: Connection established"); + ready_ = true; + outbuf_.erase(); + DWORD status; + bool success; + + while (!checkStopServer()) { + // Indefinitely wait for the completion of an overlapped + // read, write, or connect operation. + DWORD wait = WaitForMultipleObjects(MAX_PIPES + 1, event_, + FALSE, INFINITE); + + // Determine which pipe instance completed the operation. + i = wait - WAIT_OBJECT_0; + LASSERT(i >= 0 && i <= MAX_PIPES, /**/); + + // Check whether we were waked up for stopping the pipe server. + if (i == MAX_PIPES) + break; + + bool const is_outpipe = i >= MAX_CLIENTS; + + // Get the result if the operation was pending. + if (pipe_[i].pending_io) { + success = GetOverlappedResult(pipe_[i].handle, + &pipe_[i].overlap, &status, FALSE); + + switch (pipe_[i].state) { + case CONNECTING_STATE: + // Pending connect operation + if (!success) { + error = GetLastError(); + lyxerr << "LyXComm: " + << errormsg(error) << endl; + if (!resetPipe(i, true)) + return false; + continue; + } + pipe_[i].state = is_outpipe ? WRITING_STATE + : READING_STATE; + break; + + case READING_STATE: + // Pending read operation + LASSERT(!is_outpipe, /**/); + if (!success || status == 0) { + if (!resetPipe(i, !success)) + return false; + continue; + } + pipe_[i].nbytes = status; + pipe_[i].state = WRITING_STATE; + break; + + case WRITING_STATE: + // Pending write operation + LASSERT(is_outpipe, /**/); + // Let's see whether we have a reply + if (!outbuf_.empty()) { + // Yep. Deliver it to all pipe + // instances if we get ownership + // of the mutex, otherwise we'll + // try again the next round. + DWORD result = WaitForSingleObject( + outbuf_mutex_, 200); + if (result == WAIT_OBJECT_0) { + DWORD j = MAX_CLIENTS; + while (j < MAX_PIPES) { + pipe_[j].iobuf = outbuf_; + ++j; + } + outbuf_.erase(); + } + ReleaseMutex(outbuf_mutex_); + } + if (pipe_[i].iobuf.empty()) + pipe_[i].pending_io = false; + break; + } + } + + // Operate according to the pipe state + switch (pipe_[i].state) { + case READING_STATE: + // The pipe instance is connected to a client + // and is ready to read a request. + LASSERT(!is_outpipe, /**/); + success = ReadFile(pipe_[i].handle, + pipe_[i].readbuf, PIPE_BUFSIZE - 1, + &pipe_[i].nbytes, &pipe_[i].overlap); + + if (success && pipe_[i].nbytes != 0) { + // The read operation completed successfully. + pipe_[i].pending_io = false; + pipe_[i].state = WRITING_STATE; + continue; + } + + error = GetLastError(); + + if (!success && error == ERROR_IO_PENDING) { + // The read operation is still pending. + pipe_[i].pending_io = true; + continue; + } + + success = error == ERROR_BROKEN_PIPE; + + // Client closed connection (ERROR_BROKEN_PIPE) or + // an error occurred; in either case, reset the pipe. + if (!success) { + lyxerr << "LyXComm: " << errormsg(error) << endl; + if (!pipe_[i].iobuf.empty()) { + lyxerr << "LyXComm: truncated command: " + << pipe_[i].iobuf << endl; + pipe_[i].iobuf.erase(); + } + } + if (!resetPipe(i, !success)) + return false; + break; + + case WRITING_STATE: + if (!is_outpipe) { + // The request was successfully read + // from the client; commit it. + ReadReadyEvent * event = new ReadReadyEvent(i); + QCoreApplication::postEvent(this, + static_cast(event)); + // Wait for completion + while (pipe_[i].nbytes && !checkStopServer(100)) + ; + pipe_[i].pending_io = false; + pipe_[i].state = READING_STATE; + continue; + } + + // This is an output pipe instance. Initiate the + // overlapped write operation or monitor its progress. + + if (pipe_[i].pending_io) { + success = WriteFile(pipe_[i].handle, + pipe_[i].iobuf.c_str(), + pipe_[i].iobuf.length(), + &status, + &pipe_[i].overlap); + } + + if (success && !pipe_[i].iobuf.empty() + && status == pipe_[i].iobuf.length()) { + // The write operation completed successfully. + pipe_[i].iobuf.erase(); + pipe_[i].pending_io = false; + if (!resetPipe(i)) + return false; + continue; + } + + error = GetLastError(); + + if (success && error == ERROR_IO_PENDING) { + // The write operation is still pending. + // We get here when a reader is started + // well before a reply is ready, so delay + // a bit in order to not burden the cpu. + checkStopServer(100); + pipe_[i].pending_io = true; + continue; + } + + success = error == ERROR_NO_DATA; + + // Client closed connection (ERROR_NO_DATA) or + // an error occurred; in either case, reset the pipe. + if (!success) { + lyxerr << "LyXComm: Error sending message: " + << pipe_[i].iobuf << "\nLyXComm: " + << errormsg(error) << endl; + } + if (!resetPipe(i, !success)) + return false; + break; + } + } + + ready_ = false; + closeHandles(); + return true; +} + + +void LyXComm::closeHandles() +{ + for (int i = 0; i < MAX_PIPES; ++i) { + if (event_[i]) { + ResetEvent(event_[i]); + CloseHandle(event_[i]); + event_[i] = 0; + } + if (pipe_[i].handle != INVALID_HANDLE_VALUE) { + CloseHandle(pipe_[i].handle); + pipe_[i].handle = INVALID_HANDLE_VALUE; + } + } +} + + +bool LyXComm::event(QEvent * e) +{ + if (e->type() == QEvent::User) { + read_ready(static_cast(e)->inpipe()); + return true; + } + return false; +} + + +bool LyXComm::checkStopServer(DWORD timeout) +{ + return WaitForSingleObject(stopserver_, timeout) == WAIT_OBJECT_0; +} + + +bool LyXComm::startPipe(DWORD index) +{ + pipe_[index].pending_io = false; + pipe_[index].overlap.Offset = 0; + pipe_[index].overlap.OffsetHigh = 0; + + // Overlapped ConnectNamedPipe should return zero. + if (ConnectNamedPipe(pipe_[index].handle, &pipe_[index].overlap)) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not connect pipe " + << external_path(pipeName(index)) + << "\nLyXComm: " << errormsg(error) << endl; + return false; + } + + switch (GetLastError()) { + case ERROR_IO_PENDING: + // The overlapped connection is in progress. + pipe_[index].pending_io = true; + break; + + case ERROR_PIPE_CONNECTED: + // Client is already connected, so signal an event. + if (SetEvent(pipe_[index].overlap.hEvent)) + break; + // fall through + default: + // Anything else is an error. + DWORD const error = GetLastError(); + lyxerr << "LyXComm: An error occurred while connecting pipe " + << external_path(pipeName(index)) + << "\nLyXComm: " << errormsg(error) << endl; + return false; + } + + return true; +} + + +bool LyXComm::resetPipe(DWORD index, bool close_handle) +{ + // This method is called when an error occurs or when a client + // closes the connection. We first disconnect the pipe instance, + // then reconnect it, ready to wait for another client. + + if (!DisconnectNamedPipe(pipe_[index].handle)) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not disconnect pipe " + << external_path(pipeName(index)) + << "\nLyXComm: " << errormsg(error) << endl; + // What to do now? Let's try whether re-creating the pipe helps. + close_handle = true; + } + + bool const is_outpipe = index >= MAX_CLIENTS; + + if (close_handle) { + DWORD const open_mode = is_outpipe ? PIPE_ACCESS_OUTBOUND + : PIPE_ACCESS_INBOUND; + string const name = external_path(pipeName(index)); + + CloseHandle(pipe_[index].handle); + + pipe_[index].iobuf.erase(); + pipe_[index].handle = CreateNamedPipe(name.c_str(), + open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT, + MAX_CLIENTS, PIPE_BUFSIZE, PIPE_BUFSIZE, + PIPE_TIMEOUT, NULL); + + if (pipe_[index].handle == INVALID_HANDLE_VALUE) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not reset pipe " << name + << "\nLyXComm: " << errormsg(error) << endl; + return false; + } + } + + if (!startPipe(index)) + return false; + pipe_[index].state = pipe_[index].pending_io ? + CONNECTING_STATE : (is_outpipe ? WRITING_STATE + : READING_STATE); + return true; +} + + +void LyXComm::openConnection() +{ + LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection"); + + // If we are up, that's an error + if (ready_) { + LYXERR(Debug::LYXSERVER, "LyXComm: Already connected"); + return; + } + + if (pipename_.empty()) { + LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do"); + return; + } + + // Check whether the pipe name is being used by some other program. + if (!stopserver_ && WaitNamedPipe(inPipeName().c_str(), 0)) { + lyxerr << "LyXComm: Pipe " << external_path(inPipeName()) + << " already exists.\nMaybe another instance of LyX" + " is using it." << endl; + pipename_.erase(); + return; + } + + // Mutex with no initial owner for synchronized access to outbuf_ + outbuf_mutex_ = CreateMutex(NULL, FALSE, NULL); + if (!outbuf_mutex_) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not create output buffer mutex" + << "\nLyXComm: " << errormsg(error) << endl; + pipename_.erase(); + return; + } + + // Manual-reset event, initial state = not signaled + stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!stopserver_) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not create stop server event" + << "\nLyXComm: " << errormsg(error) << endl; + pipename_.erase(); + CloseHandle(outbuf_mutex_); + return; + } + + server_thread_ = CreateThread(NULL, 0, pipeServerWrapper, + static_cast(this), 0, NULL); + if (!server_thread_) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not create pipe server thread" + << "\nLyXComm: " << errormsg(error) << endl; + pipename_.erase(); + CloseHandle(stopserver_); + CloseHandle(outbuf_mutex_); + return; + } +} + + +/// 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_) { + LYXERR(Debug::LYXSERVER, "LyXComm: Already disconnected"); + return; + } + + SetEvent(stopserver_); + // Wait for the pipe server to finish + WaitForSingleObject(server_thread_, INFINITE); + CloseHandle(server_thread_); + ResetEvent(stopserver_); + CloseHandle(stopserver_); + CloseHandle(outbuf_mutex_); +} + + +void LyXComm::emergencyCleanup() +{ + if (ready_) { + SetEvent(stopserver_); + // Forcibly terminate the pipe server thread if it does + // not finish quickly. + if (WaitForSingleObject(server_thread_, 200) != WAIT_OBJECT_0) { + TerminateThread(server_thread_, 0); + ready_ = false; + closeHandles(); + } + CloseHandle(server_thread_); + ResetEvent(stopserver_); + CloseHandle(stopserver_); + CloseHandle(outbuf_mutex_); + } +} + + +void LyXComm::read_ready(DWORD inpipe) +{ + // Turn the pipe buffer into a C string + DWORD const nbytes = pipe_[inpipe].nbytes; + pipe_[inpipe].readbuf[nbytes] = '\0'; + + pipe_[inpipe].iobuf += rtrim(pipe_[inpipe].readbuf, "\r"); + + // Commit any commands read + while (pipe_[inpipe].iobuf.find('\n') != string::npos) { + // split() grabs the entire string if + // the delim /wasn't/ found. ?:-P + string cmd; + pipe_[inpipe].iobuf = split(pipe_[inpipe].iobuf, cmd, '\n'); + cmd = rtrim(cmd, "\r"); + LYXERR(Debug::LYXSERVER, "LyXComm: nbytes:" << nbytes + << ", iobuf:" << pipe_[inpipe].iobuf + << ", cmd:" << cmd); + if (!cmd.empty()) + clientcb_(client_, cmd); + //\n or not \n? + } + // Signal that we are done. + pipe_[inpipe].nbytes = 0; +} + + +void LyXComm::send(string const & msg) +{ + if (msg.empty()) { + lyxerr << "LyXComm: Request to send empty string. Ignoring." + << endl; + return; + } + + LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\''); + + if (pipename_.empty()) + return; + + if (!ready_) { + lyxerr << "LyXComm: Pipes are closed. Could not send " + << msg << endl; + return; + } + + // Request ownership of the outbuf_mutex_ + DWORD result = WaitForSingleObject(outbuf_mutex_, PIPE_TIMEOUT); + + if (result == WAIT_OBJECT_0) { + // If a client doesn't care to read a reply (JabRef is one + // such client), the output buffer could grow without limit. + // So, we empty it when its size is larger than PIPE_BUFSIZE. + if (outbuf_.size() > PIPE_BUFSIZE) + outbuf_.erase(); + outbuf_ += msg; + ReleaseMutex(outbuf_mutex_); + } else { + // Something is fishy, better resetting the connection. + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Error sending message: " << msg + << "\nLyXComm: " << errormsg(error) + << "\nLyXComm: Resetting connection" << endl; + ReleaseMutex(outbuf_mutex_); + closeConnection(); + openConnection(); + } +} + + +string const LyXComm::pipeName(DWORD index) const +{ + return index < MAX_CLIENTS ? inPipeName() : outPipeName(); +} + + +#elif !defined (HAVE_MKFIFO) // We provide a stub class that disables the lyxserver. LyXComm::LyXComm(string const &, Server *, ClientCallbackfct) @@ -537,3 +1145,7 @@ void Server::notifyClient(string const & s) } // namespace lyx + +#ifdef _WIN32 +#include "Server_moc.cpp" +#endif diff --git a/src/Server.h b/src/Server.h index 7acee1a5c5..468b464fca 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,46 @@ 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 + + friend DWORD WINAPI pipeServerWrapper(void *); + +public: + /// Max number of clients + enum { MAX_CLIENTS = 10 }; + +private: + /// Max number of pipe instances + enum { MAX_PIPES = 2 * MAX_CLIENTS }; + + /// I/O buffer size + enum { PIPE_BUFSIZE = 512 }; + + /// Pipe client time-out + enum { PIPE_TIMEOUT = 5000 }; + + /// Pipe states + enum PipeState { + CONNECTING_STATE, + READING_STATE, + WRITING_STATE + }; + + /// Pipe instances + typedef struct { + OVERLAPPED overlap; + HANDLE handle; + std::string iobuf; + char readbuf[PIPE_BUFSIZE]; + DWORD nbytes; + PipeState state; + bool pending_io; + } PipeInst; +#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 +96,11 @@ public: void send(std::string const &); /// asynch ready-to-be-read notification +#ifndef _WIN32 void read_ready(); +#else + void read_ready(DWORD); +#endif private: /// the filename of the in pipe @@ -65,6 +115,7 @@ private: /// Close pipes void closeConnection(); +#ifndef _WIN32 /// start a pipe int startPipe(std::string const &, bool); @@ -76,6 +127,46 @@ private: /// This is -1 if not open int outfd_; +#else + /// The pipe server returns false when exiting due to an error + bool pipeServer(); + + /// Start an overlapped connection + bool startPipe(DWORD); + + /// Reset an overlapped connection + bool resetPipe(DWORD, bool close_handle = false); + + /// Close event and pipe handles + void closeHandles(); + + /// Catch pipe ready-to-be-read notification + bool event(QEvent *); + + /// Check whether the pipe server must be stopped + bool checkStopServer(DWORD timeout = 0); + + /// The filename of a (in or out) pipe instance + std::string const pipeName(DWORD) const; + + /// Pipe instances + PipeInst pipe_[MAX_PIPES]; + + /// Pipe server control events + HANDLE event_[MAX_PIPES + 1]; + + /// Reply buffer + std::string outbuf_; + + /// Synchronize access to outbuf_ + HANDLE outbuf_mutex_; + + /// Windows event for stopping the pipe server + HANDLE stopserver_; + + /// Pipe server thread handle + HANDLE server_thread_; +#endif /// Are we up and running? bool ready_; @@ -118,7 +209,11 @@ public: private: /// Names and number of current clients +#ifndef _WIN32 enum { MAX_CLIENTS = 10 }; +#else + enum { MAX_CLIENTS = LyXComm::MAX_CLIENTS }; +#endif /// std::string clients_[MAX_CLIENTS]; /// diff --git a/status.16x b/status.16x index bac85636c3..cda4e14d64 100644 --- a/status.16x +++ b/status.16x @@ -31,6 +31,9 @@ What's new * USER INTERFACE +- The LyX server is now available also on Windows. See Chapter 4 in the + Additional Features manual for how to enable it. + - Complete tooltips in Spellchecker preferences (bug 6185). - A "Reload" menu item is added to the Graphics context menu to