mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-12-13 17:20:55 +00:00
Implement the LyXServer on Windows.
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/branches/BRANCH_1_6_X@31389 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
parent
3fda114784
commit
6ffeab01b7
@ -610,6 +610,7 @@ AC_ARG_WITH(packaging,
|
|||||||
AC_MSG_RESULT($lyx_use_packaging)
|
AC_MSG_RESULT($lyx_use_packaging)
|
||||||
lyx_install_macosx=false
|
lyx_install_macosx=false
|
||||||
lyx_install_cygwin=false
|
lyx_install_cygwin=false
|
||||||
|
lyx_install_windows=false
|
||||||
case $lyx_use_packaging in
|
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])
|
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}
|
PACKAGE=LyX${version_suffix}
|
||||||
@ -627,7 +628,8 @@ case $lyx_use_packaging in
|
|||||||
libdir='${prefix}/Resources'
|
libdir='${prefix}/Resources'
|
||||||
datarootdir='${prefix}/Resources'
|
datarootdir='${prefix}/Resources'
|
||||||
pkgdatadir='${datadir}'
|
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])
|
posix) AC_DEFINE(USE_POSIX_PACKAGING, 1, [Define to 1 if LyX should use a POSIX-style file layout])
|
||||||
PACKAGE=lyx${version_suffix}
|
PACKAGE=lyx${version_suffix}
|
||||||
program_suffix=$version_suffix
|
program_suffix=$version_suffix
|
||||||
@ -640,6 +642,7 @@ case $lyx_use_packaging in
|
|||||||
esac
|
esac
|
||||||
AM_CONDITIONAL(INSTALL_MACOSX, $lyx_install_macosx)
|
AM_CONDITIONAL(INSTALL_MACOSX, $lyx_install_macosx)
|
||||||
AM_CONDITIONAL(INSTALL_CYGWIN, $lyx_install_cygwin)
|
AM_CONDITIONAL(INSTALL_CYGWIN, $lyx_install_cygwin)
|
||||||
|
AM_CONDITIONAL(INSTALL_WINDOWS, $lyx_install_windows)
|
||||||
dnl Next two lines are only for autoconf <= 2.59
|
dnl Next two lines are only for autoconf <= 2.59
|
||||||
datadir='${datarootdir}'
|
datadir='${datarootdir}'
|
||||||
AC_SUBST(datarootdir)
|
AC_SUBST(datarootdir)
|
||||||
|
@ -105,9 +105,12 @@ macro(LYX_AUTOMOC)
|
|||||||
|
|
||||||
set(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_current_MOC})
|
set(_moc ${CMAKE_CURRENT_BINARY_DIR}/${_current_MOC})
|
||||||
#set(_moc ${_abs_PATH}/${_current_MOC})
|
#set(_moc ${_abs_PATH}/${_current_MOC})
|
||||||
|
if (WIN32)
|
||||||
|
set(_def -D_WIN32)
|
||||||
|
endif()
|
||||||
add_custom_command(OUTPUT ${_moc}
|
add_custom_command(OUTPUT ${_moc}
|
||||||
COMMAND ${QT_MOC_EXECUTABLE}
|
COMMAND ${QT_MOC_EXECUTABLE}
|
||||||
ARGS ${_moc_INCS} ${_header} -o ${_moc}
|
ARGS ${_def} ${_moc_INCS} ${_header} -o ${_moc}
|
||||||
MAIN_DEPENDENCY ${_header})
|
MAIN_DEPENDENCY ${_header})
|
||||||
macro_add_file_dependencies(${_abs_FILE} ${_moc})
|
macro_add_file_dependencies(${_abs_FILE} ${_moc})
|
||||||
endforeach (_current_MOC_INC)
|
endforeach (_current_MOC_INC)
|
||||||
|
@ -34,8 +34,10 @@ if (ASPELL_FOUND)
|
|||||||
set(lyx_sources ${lyx_sources} ${TOP_SRC_DIR}/src/ASpell.cpp)
|
set(lyx_sources ${lyx_sources} ${TOP_SRC_DIR}/src/ASpell.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
lyx_automoc(${TOP_SRC_DIR}/src/Server.cpp)
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR}
|
include_directories(${CMAKE_CURRENT_BINARY_DIR}
|
||||||
${ZLIB_INCLUDE_DIR})
|
${ZLIB_INCLUDE_DIR} ${QT_INCLUDES})
|
||||||
|
|
||||||
lyx_add_msvc_pch(lyx)
|
lyx_add_msvc_pch(lyx)
|
||||||
|
|
||||||
|
@ -903,6 +903,7 @@ result = utils.createConfigFile(conf,
|
|||||||
('locale.h', 'HAVE_LOCALE_H', 'c'),
|
('locale.h', 'HAVE_LOCALE_H', 'c'),
|
||||||
('process.h', 'HAVE_PROCESS_H', 'c'),
|
('process.h', 'HAVE_PROCESS_H', 'c'),
|
||||||
('stdlib.h', 'HAVE_STDLIB_H', 'c'),
|
('stdlib.h', 'HAVE_STDLIB_H', 'c'),
|
||||||
|
('string.h', 'HAVE_STRING_H', 'c'),
|
||||||
('sys/stat.h', 'HAVE_SYS_STAT_H', 'c'),
|
('sys/stat.h', 'HAVE_SYS_STAT_H', 'c'),
|
||||||
('sys/time.h', 'HAVE_SYS_TIME_H', 'c'),
|
('sys/time.h', 'HAVE_SYS_TIME_H', 'c'),
|
||||||
('sys/types.h', 'HAVE_SYS_TYPES_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
|
# to be built with all the include directories etc
|
||||||
#
|
#
|
||||||
if frontend == 'qt4':
|
if frontend == 'qt4':
|
||||||
frontend_env = env.Clone()
|
env['BUILDERS']['qtResource'] = Builder(action = utils.env_qtResource)
|
||||||
frontend_env['BUILDERS']['qtResource'] = Builder(action = utils.env_qtResource)
|
|
||||||
|
|
||||||
# handle qt related user specified paths
|
# handle qt related user specified paths
|
||||||
# set environment so that moc etc can be found even if its path is not set properly
|
# 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']:
|
if env.has_key('qt_dir') and env['qt_dir']:
|
||||||
frontend_env['QTDIR'] = frontend_env['qt_dir']
|
env['QTDIR'] = env['qt_dir']
|
||||||
if os.path.isdir(os.path.join(frontend_env['qt_dir'], 'bin')):
|
if os.path.isdir(os.path.join(env['qt_dir'], 'bin')):
|
||||||
os.environ['PATH'] += os.pathsep + os.path.join(frontend_env['qt_dir'], 'bin')
|
os.environ['PATH'] += os.pathsep + os.path.join(env['qt_dir'], 'bin')
|
||||||
frontend_env.PrependENVPath('PATH', os.path.join(frontend_env['qt_dir'], 'bin'))
|
env.PrependENVPath('PATH', os.path.join(env['qt_dir'], 'bin'))
|
||||||
if os.path.isdir(os.path.join(frontend_env['qt_dir'], 'lib')):
|
if os.path.isdir(os.path.join(env['qt_dir'], 'lib')):
|
||||||
frontend_env.PrependENVPath('PKG_CONFIG_PATH', os.path.join(frontend_env['qt_dir'], 'lib'))
|
env.PrependENVPath('PKG_CONFIG_PATH', os.path.join(env['qt_dir'], 'lib'))
|
||||||
|
|
||||||
# if separate qt_lib_path is given
|
# if separate qt_lib_path is given
|
||||||
if frontend_env.has_key('qt_lib_path') and frontend_env['qt_lib_path']:
|
if env.has_key('qt_lib_path') and env['qt_lib_path']:
|
||||||
qt_lib_path = frontend_env.subst('$qt_lib_path')
|
qt_lib_path = env.subst('$qt_lib_path')
|
||||||
frontend_env.AppendUnique(LIBPATH = [qt_lib_path])
|
env.AppendUnique(LIBPATH = [qt_lib_path])
|
||||||
frontend_env.PrependENVPath('PKG_CONFIG_PATH', qt_lib_path)
|
env.PrependENVPath('PKG_CONFIG_PATH', qt_lib_path)
|
||||||
else:
|
else:
|
||||||
qt_lib_path = None
|
qt_lib_path = None
|
||||||
|
|
||||||
# if separate qt_inc_path is given
|
# if separate qt_inc_path is given
|
||||||
if frontend_env.has_key('qt_inc_path') and frontend_env['qt_inc_path']:
|
if env.has_key('qt_inc_path') and env['qt_inc_path']:
|
||||||
qt_inc_path = frontend_env['qt_inc_path']
|
qt_inc_path = env['qt_inc_path']
|
||||||
else:
|
else:
|
||||||
qt_inc_path = None
|
qt_inc_path = None
|
||||||
|
|
||||||
@ -1320,18 +1320,20 @@ if frontend == 'qt4':
|
|||||||
# NOTE: I have to patch qt4.py since it does not automatically
|
# NOTE: I have to patch qt4.py since it does not automatically
|
||||||
# process .C file!!! (add to cxx_suffixes )
|
# process .C file!!! (add to cxx_suffixes )
|
||||||
#
|
#
|
||||||
frontend_env.Tool('qt4', [scons_dir])
|
env.Tool('qt4', [scons_dir])
|
||||||
frontend_env['QT_AUTOSCAN'] = 0
|
env['QT_AUTOSCAN'] = 0
|
||||||
frontend_env['QT4_AUTOSCAN'] = 0
|
env['QT4_AUTOSCAN'] = 0
|
||||||
frontend_env['QT4_UICDECLFLAGS'] = '-tr lyx::qt_'
|
env['QT4_UICDECLFLAGS'] = '-tr lyx::qt_'
|
||||||
|
if platform_name == 'win32':
|
||||||
|
env['QT4_MOCFROMHFLAGS'] = '-D_WIN32'
|
||||||
|
|
||||||
if qt_lib_path is None:
|
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:
|
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 = {
|
custom_tests = {
|
||||||
'CheckPackage' : utils.checkPackage,
|
'CheckPackage' : utils.checkPackage,
|
||||||
'CheckCommand' : utils.checkCommand,
|
'CheckCommand' : utils.checkCommand,
|
||||||
@ -1340,10 +1342,10 @@ if frontend == 'qt4':
|
|||||||
|
|
||||||
succ = False
|
succ = False
|
||||||
# first: try pkg_config
|
# first: try pkg_config
|
||||||
if frontend_env['HAS_PKG_CONFIG']:
|
if env['HAS_PKG_CONFIG']:
|
||||||
succ = conf.CheckPackage('QtCore') or conf.CheckPackage('QtCore4')
|
succ = conf.CheckPackage('QtCore') or conf.CheckPackage('QtCore4')
|
||||||
# FIXME: use pkg_config information?
|
# FIXME: use pkg_config information?
|
||||||
#frontend_env['QT4_PKG_CONFIG'] = succ
|
#env['QT4_PKG_CONFIG'] = succ
|
||||||
# second: try to link to it
|
# second: try to link to it
|
||||||
if not succ:
|
if not succ:
|
||||||
# Under linux, I can test the following perfectly
|
# Under linux, I can test the following perfectly
|
||||||
@ -1375,7 +1377,7 @@ if frontend == 'qt4':
|
|||||||
else:
|
else:
|
||||||
qt_lib_suffix = ''
|
qt_lib_suffix = ''
|
||||||
use_qt_debug_libs = False
|
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]
|
frontend_libs = [x + qt_lib_suffix for x in qt_libs]
|
||||||
qtcore_lib = ['QtCore' + qt_lib_suffix]
|
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.
|
# 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.
|
# For some unknown changes in msvc or qt, this is no longer needed.
|
||||||
if use_vc:
|
if use_vc:
|
||||||
frontend_env['LINKCOM'] = [frontend_env['LINKCOM'], \
|
env['LINKCOM'] = [env['LINKCOM'], \
|
||||||
'mt.exe /MANIFEST %s /outputresource:$TARGET;1' % \
|
'mt.exe /MANIFEST %s /outputresource:$TARGET;1' % \
|
||||||
env.File('$BUILDDIR/lyx.exe.manifest').path]
|
env.File('$BUILDDIR/lyx.exe.manifest').path]
|
||||||
|
|
||||||
frontend_env = conf.Finish()
|
env = conf.Finish()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Report results
|
# 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/boost', '$TOP_SRCDIR/boost/libs', duplicate = 0)
|
||||||
env.BuildDir('$BUILDDIR/intl', '$TOP_SRCDIR/intl', duplicate = 0)
|
env.BuildDir('$BUILDDIR/intl', '$TOP_SRCDIR/intl', duplicate = 0)
|
||||||
env.BuildDir('$BUILDDIR/src', '$TOP_SRCDIR/src', 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"
|
print "Building all targets recursively"
|
||||||
|
|
||||||
@ -1493,12 +1495,12 @@ if (included_gettext and not libExists('included_intl')) or 'intl' in BUILD_TARG
|
|||||||
#
|
#
|
||||||
# src/support
|
# src/support
|
||||||
#
|
#
|
||||||
frontend_env['QT4_MOCHPREFIX'] = ''
|
env['QT4_MOCHPREFIX'] = ''
|
||||||
frontend_env['QT4_MOCHSUFFIX'] = '_moc.cpp'
|
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 ]
|
for x in src_support_header_files ]
|
||||||
support = frontend_env.StaticLibrary(
|
support = env.StaticLibrary(
|
||||||
target = '$LOCALLIBPATH/support',
|
target = '$LOCALLIBPATH/support',
|
||||||
source = ['$BUILDDIR/src/support/%s' % x for x in src_support_files],
|
source = ['$BUILDDIR/src/support/%s' % x for x in src_support_files],
|
||||||
CCFLAGS = [
|
CCFLAGS = [
|
||||||
@ -1511,67 +1513,81 @@ support = frontend_env.StaticLibrary(
|
|||||||
)
|
)
|
||||||
Alias('support', support)
|
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(
|
for file in src_tex2lyx_copied_files + src_tex2lyx_copied_header_files:
|
||||||
target = '$LOCALLIBPATH/mathed',
|
env.Command('$BUILDDIR/src/tex2lyx/'+file, '$TOP_SRCDIR/src/'+file,
|
||||||
source = ['$BUILDDIR/src/mathed/%s' % x for x in src_mathed_files]
|
[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(
|
if env.has_key('USE_ASPELL') and env['USE_ASPELL']:
|
||||||
target = '$LOCALLIBPATH/insets',
|
src_post_files.append('ASpell.cpp')
|
||||||
source = ['$BUILDDIR/src/insets/%s' % x for x in src_insets_files]
|
elif env.has_key('USE_PSPELL') and env['USE_PSPELL']:
|
||||||
)
|
src_post_files.append('PSpell.cpp')
|
||||||
Alias('insets', insets)
|
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
|
# tells scons how to get these moced files, although not all moced files are needed
|
||||||
# (or are actually generated).
|
# (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 ]
|
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]
|
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',
|
'$BUILDDIR/src/frontends/qt4/Resource.qrc',
|
||||||
['$TOP_SRCDIR/lib/images/%s' % x for x in lib_images_files] +
|
['$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/math/%s' % x for x in lib_images_math_files] +
|
||||||
['$TOP_SRCDIR/lib/images/commands/%s' % x for x in lib_images_commands_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
|
lyx = env.Program(
|
||||||
#
|
target = '$BUILDDIR/lyx',
|
||||||
qt4 = frontend_env.StaticLibrary(
|
source = ['$BUILDDIR/src/main.cpp'] +
|
||||||
target = '$LOCALLIBPATH/qt4',
|
['$BUILDDIR/src/frontends/qt4/%s' % x for x in src_frontends_qt4_files] +
|
||||||
source = ['$BUILDDIR/src/frontends/qt4/%s' % x for x in src_frontends_qt4_files] + resource,
|
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 = [
|
||||||
'$CPPPATH',
|
'$CPPPATH',
|
||||||
'$BUILDDIR/src',
|
'$BUILDDIR/src',
|
||||||
@ -1585,91 +1601,11 @@ qt4 = frontend_env.StaticLibrary(
|
|||||||
'-DHAVE_CONFIG_H',
|
'-DHAVE_CONFIG_H',
|
||||||
'-DQT_NO_STL',
|
'-DQT_NO_STL',
|
||||||
'-DQT_NO_KEYWORDS',
|
'-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 = [
|
LIBS = [
|
||||||
'lyxbase_pre',
|
|
||||||
'mathed',
|
|
||||||
'insets',
|
|
||||||
'frontends',
|
|
||||||
frontend,
|
|
||||||
'graphics',
|
|
||||||
'support',
|
'support',
|
||||||
] +
|
] +
|
||||||
boost_libraries + ['lyxbase_post'] +
|
boost_libraries +
|
||||||
frontend_libs +
|
frontend_libs +
|
||||||
intl_libs +
|
intl_libs +
|
||||||
socket_libs +
|
socket_libs +
|
||||||
|
@ -1168,6 +1168,7 @@ void LyXFunc::dispatch(FuncRequest const & cmd)
|
|||||||
int row;
|
int row;
|
||||||
istringstream is(argument);
|
istringstream is(argument);
|
||||||
is >> file_name >> row;
|
is >> file_name >> row;
|
||||||
|
file_name = os::internal_path(file_name);
|
||||||
Buffer * buf = 0;
|
Buffer * buf = 0;
|
||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
string const abstmp = package().temp_dir().absFilename();
|
string const abstmp = package().temp_dir().absFilename();
|
||||||
|
@ -5,6 +5,7 @@ include $(top_srcdir)/config/common.am
|
|||||||
DISTCLEANFILES += config.h libintl.h
|
DISTCLEANFILES += config.h libintl.h
|
||||||
|
|
||||||
AM_CPPFLAGS += $(PCH_FLAGS) -I$(top_srcdir)/src $(BOOST_INCLUDES)
|
AM_CPPFLAGS += $(PCH_FLAGS) -I$(top_srcdir)/src $(BOOST_INCLUDES)
|
||||||
|
AM_CPPFLAGS += $(QT4_CPPFLAGS) $(QT4_CORE_INCLUDES)
|
||||||
|
|
||||||
if BUILD_CLIENT_SUBDIR
|
if BUILD_CLIENT_SUBDIR
|
||||||
CLIENT = client
|
CLIENT = client
|
||||||
@ -289,6 +290,22 @@ liblyxcore_la_SOURCES = $(SOURCEFILESCORE) $(STANDALONEFILES) $(HEADERFILESCORE)
|
|||||||
|
|
||||||
endif
|
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 ##############################
|
############################### Graphics ##############################
|
||||||
|
|
||||||
noinst_LTLIBRARIES += liblyxgraphics.la
|
noinst_LTLIBRARIES += liblyxgraphics.la
|
||||||
|
614
src/Server.cpp
614
src/Server.cpp
@ -7,6 +7,7 @@
|
|||||||
* \author Jean-Marc Lasgouttes
|
* \author Jean-Marc Lasgouttes
|
||||||
* \author Angus Leeming
|
* \author Angus Leeming
|
||||||
* \author John Levon
|
* \author John Levon
|
||||||
|
* \author Enrico Forestieri
|
||||||
*
|
*
|
||||||
* Full author contact details are available in file CREDITS.
|
* Full author contact details are available in file CREDITS.
|
||||||
*/
|
*/
|
||||||
@ -48,10 +49,16 @@
|
|||||||
|
|
||||||
#include "support/debug.h"
|
#include "support/debug.h"
|
||||||
#include "support/FileName.h"
|
#include "support/FileName.h"
|
||||||
|
#include "support/lassert.h"
|
||||||
#include "support/lstrings.h"
|
#include "support/lstrings.h"
|
||||||
|
#include "support/os.h"
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#ifdef HAVE_SYS_STAT_H
|
#ifdef HAVE_SYS_STAT_H
|
||||||
# include <sys/stat.h>
|
# include <sys/stat.h>
|
||||||
@ -60,6 +67,7 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace lyx::support;
|
using namespace lyx::support;
|
||||||
|
using os::external_path;
|
||||||
|
|
||||||
namespace lyx {
|
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<char *>(msgbuf);
|
||||||
|
LocalFree(msgbuf);
|
||||||
|
} else
|
||||||
|
message = "Unknown error";
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace anon
|
||||||
|
|
||||||
|
|
||||||
|
DWORD WINAPI pipeServerWrapper(void * arg)
|
||||||
|
{
|
||||||
|
LyXComm * lyxcomm = reinterpret_cast<LyXComm *>(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<QEvent *>(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<ReadReadyEvent *>(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<void *>(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.
|
// We provide a stub class that disables the lyxserver.
|
||||||
|
|
||||||
LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
|
LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
|
||||||
@ -537,3 +1145,7 @@ void Server::notifyClient(string const & s)
|
|||||||
|
|
||||||
|
|
||||||
} // namespace lyx
|
} // namespace lyx
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "Server_moc.cpp"
|
||||||
|
#endif
|
||||||
|
95
src/Server.h
95
src/Server.h
@ -6,6 +6,7 @@
|
|||||||
*
|
*
|
||||||
* \author Lars Gullik Bjønnes
|
* \author Lars Gullik Bjønnes
|
||||||
* \author Jean-Marc Lasgouttes
|
* \author Jean-Marc Lasgouttes
|
||||||
|
* \author Enrico Forestieri
|
||||||
*
|
*
|
||||||
* Full author contact details are available in file CREDITS.
|
* Full author contact details are available in file CREDITS.
|
||||||
*/
|
*/
|
||||||
@ -15,6 +16,12 @@
|
|||||||
|
|
||||||
#include <boost/signals/trackable.hpp>
|
#include <boost/signals/trackable.hpp>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QEvent>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace lyx {
|
namespace lyx {
|
||||||
|
|
||||||
@ -29,7 +36,46 @@ class Server;
|
|||||||
This class encapsulates all the dirty communication and thus provides
|
This class encapsulates all the dirty communication and thus provides
|
||||||
a clean string interface.
|
a clean string interface.
|
||||||
*/
|
*/
|
||||||
|
#ifndef _WIN32
|
||||||
class LyXComm : public boost::signals::trackable {
|
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:
|
public:
|
||||||
/** When we receive a message, we send it to a client.
|
/** When we receive a message, we send it to a client.
|
||||||
This is one of the small things that would have been a lot
|
This is one of the small things that would have been a lot
|
||||||
@ -50,7 +96,11 @@ public:
|
|||||||
void send(std::string const &);
|
void send(std::string const &);
|
||||||
|
|
||||||
/// asynch ready-to-be-read notification
|
/// asynch ready-to-be-read notification
|
||||||
|
#ifndef _WIN32
|
||||||
void read_ready();
|
void read_ready();
|
||||||
|
#else
|
||||||
|
void read_ready(DWORD);
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// the filename of the in pipe
|
/// the filename of the in pipe
|
||||||
@ -65,6 +115,7 @@ private:
|
|||||||
/// Close pipes
|
/// Close pipes
|
||||||
void closeConnection();
|
void closeConnection();
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
/// start a pipe
|
/// start a pipe
|
||||||
int startPipe(std::string const &, bool);
|
int startPipe(std::string const &, bool);
|
||||||
|
|
||||||
@ -76,6 +127,46 @@ private:
|
|||||||
|
|
||||||
/// This is -1 if not open
|
/// This is -1 if not open
|
||||||
int outfd_;
|
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?
|
/// Are we up and running?
|
||||||
bool ready_;
|
bool ready_;
|
||||||
@ -118,7 +209,11 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/// Names and number of current clients
|
/// Names and number of current clients
|
||||||
|
#ifndef _WIN32
|
||||||
enum { MAX_CLIENTS = 10 };
|
enum { MAX_CLIENTS = 10 };
|
||||||
|
#else
|
||||||
|
enum { MAX_CLIENTS = LyXComm::MAX_CLIENTS };
|
||||||
|
#endif
|
||||||
///
|
///
|
||||||
std::string clients_[MAX_CLIENTS];
|
std::string clients_[MAX_CLIENTS];
|
||||||
///
|
///
|
||||||
|
@ -31,6 +31,9 @@ What's new
|
|||||||
|
|
||||||
* USER INTERFACE
|
* 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).
|
- Complete tooltips in Spellchecker preferences (bug 6185).
|
||||||
|
|
||||||
- A "Reload" menu item is added to the Graphics context menu to
|
- A "Reload" menu item is added to the Graphics context menu to
|
||||||
|
Loading…
Reference in New Issue
Block a user