diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..ea648387 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +See doc/authors.html. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..26a5086f --- /dev/null +++ b/Makefile.am @@ -0,0 +1,97 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + lib \ + cmd \ + doc \ + dist \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + examples/synergy.conf \ + win32util/autodep.cpp \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + aclocal.m4 \ + config.h \ + config.h.in \ + config.log \ + config.status \ + configure \ + stamp-h.in \ + stamp-h1 \ + $(NULL) + +PKG_FILES = \ + ChangeLog \ + README \ + cmd/synergyc/synergyc \ + cmd/synergys/synergys \ + examples/synergy.conf \ + $(NULL) +PKG_DOC_FILES = \ + doc/PORTING \ + doc/*.html \ + doc/*.css \ + $(NULL) +PKG_PROG_FILES = \ + synergyc \ + synergys \ + $(NULL) + +# build doxygen documentation +doxygen: + doxygen doc/doxygen.cfg + +# build RPMs +RPMTOPDIR=/var/tmp/@PACKAGE@-@VERSION@ +dist-rpm: dist + rm -rf $(RPMTOPDIR) + mkdir $(RPMTOPDIR) + (cd $(RPMTOPDIR); mkdir BUILD SOURCES SPECS SRPMS RPMS) + cp @PACKAGE@-@VERSION@.tar.gz $(RPMTOPDIR)/SOURCES + rpm --define '_topdir $(RPMTOPDIR)' -ba dist/rpm/synergy.spec && \ + mv -f $(RPMTOPDIR)/SRPMS/*.rpm . && \ + mv -f $(RPMTOPDIR)/RPMS/*/*.rpm . && \ + rm -rf $(RPMTOPDIR) + +# build zip +# FIXME -- have automake generate this rule for us +dist-zip: distdir + zip -r $(distdir).zip $(distdir) + -chmod -R a+w $(distdir) >/dev/null 2>&1; rm -rf $(distdir) + +# build binary package. owner/group of packaged files will be +# owner/group of user running make. +PKGTOPDIR=/var/tmp/@PACKAGE@-@VERSION@ +dist-pkg: all + rm -rf $(PKGTOPDIR) + mkdir $(PKGTOPDIR) + mkdir $(PKGTOPDIR)/@PACKAGE@-@VERSION@ + mkdir $(PKGTOPDIR)/@PACKAGE@-@VERSION@/doc + cp $(PKG_FILES) $(PKGTOPDIR)/@PACKAGE@-@VERSION@ + cp $(PKG_DOC_FILES) $(PKGTOPDIR)/@PACKAGE@-@VERSION@/doc + (cd $(PKGTOPDIR)/@PACKAGE@-@VERSION@; \ + chmod 644 *; \ + chmod 755 doc $(PKG_PROG_FILES); \ + strip $(PKG_PROG_FILES) ) + type=`uname -s -m | tr '[A-Z] ' '[a-z].'`; \ + (cd $(PKGTOPDIR); tar cf - @PACKAGE@-@VERSION@ | \ + gzip - ) > @PACKAGE@-@VERSION@-1.$${type}.tar.gz && \ + rm -rf $(PKGTOPDIR) diff --git a/Makefile.win b/Makefile.win new file mode 100644 index 00000000..d232b58a --- /dev/null +++ b/Makefile.win @@ -0,0 +1,145 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# Name of this file for recursive make +MAKEFILE = Makefile.win + +# Default build is release is NODEBUG is defined, debug otherwise. +!if !DEFINED(DEBUG) +NODEBUG = 1 +!else +!undef NODEBUG +!endif + +# Build all by default +default: all + +# Redefine implicit rule suffixes +.SUFFIXES: +.SUFFIXES: .cpp .rc .obj + +# Shut up +.SILENT: + +# Include system macros +#APPVER = 5.0 +#TARGETOS = WINNT +!include + +# Be explicit about C++ compiler +cpp = $(cc) +cppdebug = $(cdebug) +cppflags = $(cflags) +cppvarsmt = $(cvarsmt) + +# Library tool options +ildebug = +ilflags = /nologo + +# Handy macro for defining list macros +NULL = + +# System commands +ECHO = echo +MKDIR = mkdir +RM = del /f +RMR = rmdir /q /s + +# Local build utilities +UTIL_DIR = win32util +AUTODEP = "$(UTIL_DIR)\autodep.exe" + +# Destination for intermediate build targets +BUILD_DIR = build +BUILD_DEBUG_DIR = $(BUILD_DIR)\Debug +BUILD_RELEASE_DIR = $(BUILD_DIR)\Release +!if DEFINED(NODEBUG) +BUILD_DST = $(BUILD_RELEASE_DIR) +!else +BUILD_DST = $(BUILD_DEBUG_DIR) +!endif + +# Compiler argument changes +cflags = $(cflags:-W3=-W4) /WX +cflags = $(cflags) -D_CRT_SECURE_NO_DEPRECATE +cflags = $(cflags) /GR +!if !DEFINED(OLDCOMPILER) +cflags = $(cflags) /EHsc +!else +cflags = $(cflags) /GX +!endif +!if !DEFINED(NODEBUG) +!if !DEFINED(OLDCOMPILER) +cdebug = $(cdebug) /RTC1 +!else +cdebug = $(cdebug) /GZ +!endif +!endif + +# Initialize variables for library and program makefiles +C_FILES = +CPP_FILES = +OBJ_FILES = +LIB_FILES = +PROGRAMS = +OPTPROGRAMS = $(AUTODEP) + +# Include subdirectory makefiles +!include lib\common\$(MAKEFILE) +!include lib\arch\$(MAKEFILE) +!include lib\base\$(MAKEFILE) +!include lib\mt\$(MAKEFILE) +!include lib\io\$(MAKEFILE) +!include lib\net\$(MAKEFILE) +!include lib\synergy\$(MAKEFILE) +!include lib\platform\$(MAKEFILE) +!include lib\client\$(MAKEFILE) +!include lib\server\$(MAKEFILE) +!include cmd\synergyc\$(MAKEFILE) +!include cmd\synergys\$(MAKEFILE) +!include cmd\launcher\$(MAKEFILE) +!include dist\nullsoft\$(MAKEFILE) + +# Collect library and program variables +INTERMEDIATES = $(OBJ_FILES) $(AUTODEP:.exe=.obj) +TARGETS = $(LIB_FILES) $(PROGRAMS) +OPTTARGETS = $(OPTPROGRAMS) + +# Build release by reinvoking make with NODEBUG defined +release: + @$(MAKE) /nologo /f $(MAKEFILE) NODEBUG=1 + +# Build debug by reinvoking make with DEBUG defined +debug: + @$(MAKE) /nologo /f $(MAKEFILE) DEBUG=1 + +# Build all targets +all: $(TARGETS) + +# Clean intermediate targets +clean: + -$(RMR) $(BUILD_DEBUG_DIR) + -$(RMR) $(BUILD_RELEASE_DIR) + +# Clean all targets +clobber: clean + -$(RMR) $(BUILD_DIR) + +# Utility command build rules +$(AUTODEP): $(AUTODEP:.exe=.cpp) +!if DEFINED(NODEBUG) + @$(ECHO) Build $(@F) + $(cpp) $(cppdebug) $(cppflags) $(cppvars) /Fo"$(**:.cpp=.obj)" $** + $(link) $(ldebug) $(conflags) -out:$@ $(**:.cpp=.obj) $(conlibs) +!else + @$(MAKE) /nologo /f $(MAKEFILE) NODEBUG=1 $@ +!endif diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..e9aa7916 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See doc/news.html. diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 00000000..29163d5a --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,571 @@ +dnl synergy -- mouse and keyboard sharing utility +dnl Copyright (C) 2002 Chris Schoeneman +dnl +dnl This package is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU General Public License +dnl found in the file COPYING that should have accompanied this file. +dnl +dnl This package is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. + +AC_DEFUN([ACX_CHECK_SOCKLEN_T], [ + AC_MSG_CHECKING([for socklen_t]) + AC_TRY_COMPILE([ + #include + #include + ], + [socklen_t len;],[acx_socklen_t_ok=yes],[acx_socklen_t_ok=no]) + AC_MSG_RESULT($acx_socklen_t_ok) + if test x"$acx_socklen_t_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_SOCKLEN_T,1,[Define if your compiler defines socklen_t.]),[$1]) + : + else + acx_socklen_t_ok=no + $2 + fi +])dnl ACX_CHECK_SOCKLEN_T + +# HP-UX defines socklen_t but doesn't use it in arg 3 for accept(). +AC_DEFUN([ACX_FUNC_ACCEPT], [ + AC_MSG_CHECKING([for type of arg 3 for accept]) + acx_accept_socklen_t_arg3=int + if test x"$acx_socklen_t_ok" = xyes; then + AC_TRY_COMPILE([ + #include + #include + ], + [struct sockaddr addr; socklen_t len; accept(0, &addr, &len);], + [acx_accept_socklen_t_arg3=socklen_t], + [acx_accept_socklen_t_arg3=int]) + fi + AC_MSG_RESULT($acx_accept_socklen_t_arg3) + AC_DEFINE_UNQUOTED(ACCEPT_TYPE_ARG3,$acx_accept_socklen_t_arg3,[Define to the base type of arg 3 for `accept'.]) +])dnl ACX_FUNC_ACCEPT + +AC_DEFUN([ACX_CHECK_CXX], [ + AC_MSG_CHECKING([if g++ defines correct C++ macro]) + AC_TRY_COMPILE(, [ + #if defined(_LANGUAGE_C) && !defined(_LANGUAGE_C_PLUS_PLUS) + #error wrong macro + #endif],[acx_cxx_macro_ok=yes],[acx_cxx_macro_ok=no]) + AC_MSG_RESULT($acx_cxx_macro_ok) + if test x"$acx_cxx_macro_ok" = xyes; then + SYNERGY_CXXFLAGS="" + else + SYNERGY_CXXFLAGS="-U_LANGUAGE_C -D_LANGUAGE_C_PLUS_PLUS" + fi +])dnl ACX_CHECK_CXX + +AC_DEFUN([ACX_CHECK_CXX_BOOL], [ + AC_MSG_CHECKING([for bool support]) + AC_TRY_COMPILE(, [bool t = true, f = false;], + [acx_cxx_bool_ok=yes],[acx_cxx_bool_ok=no]) + AC_MSG_RESULT($acx_cxx_bool_ok) + if test x"$acx_cxx_bool_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_BOOL,1,[Define if your compiler has bool support.]),[$1]) + : + else + acx_cxx_bool_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_BOOL + +AC_DEFUN([ACX_CHECK_CXX_EXCEPTIONS], [ + AC_MSG_CHECKING([for exception support]) + AC_TRY_COMPILE(, [try{throw int(4);}catch(int){throw;}catch(...){}], + [acx_cxx_exception_ok=yes],[acx_cxx_exception_ok=no]) + AC_MSG_RESULT($acx_cxx_exception_ok) + if test x"$acx_cxx_exception_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_EXCEPTIONS,1,[Define if your compiler has exceptions support.]),[$1]) + : + else + acx_cxx_exception_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_EXCEPTIONS + +AC_DEFUN([ACX_CHECK_CXX_CASTS], [ + AC_MSG_CHECKING([for C++ cast support]) + AC_TRY_COMPILE(, [const char* f="a";const_cast(f); + reinterpret_cast(f);static_cast(4.5);], + [acx_cxx_cast_ok=yes],[acx_cxx_cast_ok=no]) + AC_MSG_RESULT($acx_cxx_cast_ok) + if test x"$acx_cxx_cast_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_CASTS,1,[Define if your compiler has C++ cast support.]),[$1]) + : + else + acx_cxx_cast_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_CASTS + +AC_DEFUN([ACX_CHECK_CXX_MUTABLE], [ + AC_MSG_CHECKING([for mutable support]) + AC_TRY_COMPILE(, [struct A{mutable int b;void f() const {b=0;}}; + A a;a.f();],[acx_cxx_mutable_ok=yes],[acx_cxx_mutable_ok=no]) + AC_MSG_RESULT($acx_cxx_mutable_ok) + if test x"$acx_cxx_mutable_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_MUTABLE,1,[Define if your compiler has mutable support.]),[$1]) + : + else + acx_cxx_mutable_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_MUTABLE + +AC_DEFUN([ACX_CHECK_CXX_STDLIB], [ + AC_MSG_CHECKING([for C++ standard library]) + AC_TRY_LINK([#include ], [std::set a; a.insert(3);], + [acx_cxx_stdlib_ok=yes],[acx_cxx_stdlib_ok=no]) + AC_MSG_RESULT($acx_cxx_stdlib_ok) + if test x"$acx_cxx_stdlib_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_CXX_STDLIB,1,[Define if your compiler has standard C++ library support.]),[$1]) + : + else + acx_cxx_stdlib_ok=no + $2 + fi +])dnl ACX_CHECK_CXX_STDLIB + +AC_DEFUN([ACX_CHECK_GETPWUID_R], [ + AC_MSG_CHECKING([for working getpwuid_r]) + AC_TRY_LINK([#include ], + [char buffer[4096]; struct passwd pwd, *pwdp; + getpwuid_r(0, &pwd, buffer, sizeof(buffer), &pwdp);], + acx_getpwuid_r_ok=yes, acx_getpwuid_r_ok=no) + AC_MSG_RESULT($acx_getpwuid_r_ok) + if test x"$acx_getpwuid_r_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_GETPWUID_R,1,[Define if you have a working \`getpwuid_r\' function.]),[$1]) + : + else + acx_getpwuid_r_ok=no + $2 + fi +])dnl ACX_CHECK_GETPWUID_R + +AC_DEFUN([ACX_CHECK_POLL], [ + AC_MSG_CHECKING([for poll]) + AC_TRY_LINK([#include ], + [#if defined(_POLL_EMUL_H_) + #error emulated poll + #endif + struct pollfd ufds[] = { 0, POLLIN, 0 }; poll(ufds, 1, 10);], + acx_poll_ok=yes, acx_poll_ok=no) + AC_MSG_RESULT($acx_poll_ok) + if test x"$acx_poll_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_POLL,1,[Define if you have the \`poll\' function.]),[$1]) + : + else + acx_poll_ok=no + $2 + fi +])dnl ACX_CHECK_POLL + +dnl See if we need extra libraries for nanosleep +AC_DEFUN([ACX_CHECK_NANOSLEEP], [ + acx_nanosleep_ok=no + acx_nanosleep_list="" + + dnl check if user has set NANOSLEEP_LIBS + save_user_NANOSLEEP_LIBS="$NANOSLEEP_LIBS" + if test x"$NANOSLEEP_LIBS" != x; then + acx_nanosleep_list=user + fi + + dnl check various libraries (including no extra libraries) for + dnl nanosleep. `none' should appear first. + acx_nanosleep_list="none $acx_nanosleep_list rt" + for flag in $acx_nanosleep_list; do + case $flag in + none) + AC_MSG_CHECKING([for nanosleep]) + NANOSLEEP_LIBS="" + ;; + + user) + AC_MSG_CHECKING([for nanosleep in $save_user_NANOSLEEP_LIBS]) + NANOSLEEP_LIBS="$save_user_NANOSLEEP_LIBS" + ;; + + *) + AC_MSG_CHECKING([for nanosleep in -l$flag]) + NANOSLEEP_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + LIBS="$NANOSLEEP_LIBS $LIBS" + AC_TRY_LINK([#include ], + [struct timespec t = { 1, 1000 }; nanosleep(&t, NULL);], + acx_nanosleep_ok=yes, acx_nanosleep_ok=no) + LIBS="$save_LIBS" + AC_MSG_RESULT($acx_nanosleep_ok) + if test x"$acx_nanosleep_ok" = xyes; then + break; + fi + NANOSLEEP_LIBS="" + done + + AC_SUBST(NANOSLEEP_LIBS) + + # execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: + if test x"$acx_nanosleep_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_NANOSLEEP,1,[Define if you have the \`nanosleep\' function.]),[$1]) + : + else + acx_nanosleep_ok=no + $2 + fi +])dnl ACX_CHECK_NANOSLEEP + +dnl See if we need extra libraries for inet_aton +AC_DEFUN([ACX_CHECK_INET_ATON], [ + acx_inet_aton_ok=no + acx_inet_aton_list="" + + dnl check if user has set INET_ATON_LIBS + save_user_INET_ATON_LIBS="$INET_ATON_LIBS" + if test x"$INET_ATON_LIBS" != x; then + acx_inet_aton_list=user + fi + + dnl check various libraries (including no extra libraries) for + dnl inet_aton. `none' should appear first. + acx_inet_aton_list="none $acx_inet_aton_list resolv" + for flag in $acx_inet_aton_list; do + case $flag in + none) + AC_MSG_CHECKING([for inet_aton]) + INET_ATON_LIBS="" + ;; + + user) + AC_MSG_CHECKING([for inet_aton in $save_user_INET_ATON_LIBS]) + INET_ATON_LIBS="$save_user_INET_ATON_LIBS" + ;; + + *) + AC_MSG_CHECKING([for inet_aton in -l$flag]) + INET_ATON_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + LIBS="$INET_ATON_LIBS $LIBS" + AC_TRY_LINK([#include + #include + #include + #include ], + [struct in_addr addr; inet_aton("foo.bar", &addr);], + acx_inet_aton_ok=yes, acx_inet_aton_ok=no) + LIBS="$save_LIBS" + AC_MSG_RESULT($acx_inet_aton_ok) + if test x"$acx_inet_aton_ok" = xyes; then + AC_DEFINE(HAVE_INET_ATON,1,[Define if you have the \`inet_aton\' function.]) + break; + fi + INET_ATON_LIBS="" + done + + AC_SUBST(INET_ATON_LIBS) +])dnl ACX_CHECK_INET_ATON + +dnl The following macros are from http://www.gnu.org/software/ac-archive/ +dnl which distributes them under the following license: +dnl +dnl Every Autoconf macro presented on this web site is free software; you can +dnl redistribute it and/or modify it under the terms of the GNU General +dnl Public License as published by the Free Software Foundation; either +dnl version 2, or (at your option) any later version. +dnl +dnl They are distributed in the hope that they will be useful, but WITHOUT +dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +dnl more details. (You should have received a copy of the GNU General Public +dnl License along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place -- Suite 330, Boston, MA 02111-1307, +dnl USA.) +dnl +dnl As a special exception, the Free Software Foundation gives unlimited +dnl permission to copy, distribute and modify the configure scripts that are +dnl the output of Autoconf. You need not follow the terms of the GNU General +dnl Public License when using or distributing such scripts, even though +dnl portions of the text of Autoconf appear in them. The GNU General Public +dnl License (GPL) does govern all other use of the material that constitutes +dnl the Autoconf program. +dnl +dnl Certain portions of the Autoconf source text are designed to be copied +dnl (in certain cases, depending on the input) into the output of Autoconf. +dnl We call these the "data" portions. The rest of the Autoconf source text +dnl consists of comments plus executable code that decides which of the data +dnl portions to output in any given case. We call these comments and +dnl executable code the "non-data" portions. Autoconf never copies any of the +dnl non-data portions into its output. +dnl +dnl This special exception to the GPL applies to versions of Autoconf +dnl released by the Free Software Foundation. When you make and distribute a +dnl modified version of Autoconf, you may extend this special exception to +dnl the GPL to apply to your modified version as well, *unless* your modified +dnl version has the potential to copy into its output some of the text that +dnl was the non-data portion of the version that you started with. (In other +dnl words, unless your change moves or copies text from the non-data portions +dnl to the data portions.) If your modification has such potential, you must +dnl delete any notice of this special exception to the GPL from your modified +dnl version + +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthread or + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_TRY_LINK([#include ], [int attr=$attr;], + [attr_name=$attr; break]) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + # Detect POSIX sigwait() + AC_MSG_CHECKING([for POSIX sigwait]) + AC_TRY_LINK([#include + #include ], + [sigset_t sigset; int signal; sigwait(&sigset, &signal);], + ok=yes, ok=unknown) + if test x"$ok" = xunknown; then + save_CFLAGS2="$CFLAGS" + CFLAGS="$CFLAGS -D_POSIX_PTHREAD_SEMANTICS" + AC_TRY_LINK([#include + #include ], + [sigset_t sigset; int signal; sigwait(&sigset, &signal);], + ok=-D_POSIX_PTHREAD_SEMANTICS, ok=no) + CFLAGS="$save_CFLAGS2" + fi + AC_MSG_RESULT(${ok}) + if test x"$ok" != xno; then + AC_DEFINE(HAVE_POSIX_SIGWAIT,1,[Define if you have a POSIX \`sigwait\' function.]) + if test x"$ok" != xyes; then + PTHREAD_CFLAGS="$ok $PTHREAD_CFLAGS" + fi + fi + + # Detect pthread signal functions + AC_MSG_CHECKING([for pthread signal functions]) + AC_TRY_LINK([#include + #include ], + [pthread_kill(pthread_self(), SIGTERM);], + ok=yes, ok=no) + AC_MSG_RESULT(${ok}) + if test x"$ok" = xyes; then + AC_DEFINE(HAVE_PTHREAD_SIGNAL,1,[Define if you have \`pthread_sigmask\' and \`pthread_kill\' functions.]) + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with cc_r + AC_CHECK_PROG(PTHREAD_CC, cc_r, cc_r, ${CC}) +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD + +dnl enable maximum compiler warnings. must ignore unknown pragmas to +dnl build on solaris. +dnl we only know how to do this for g++ +AC_DEFUN([ACX_CXX_WARNINGS], [ + AC_MSG_CHECKING([for C++ compiler warning flags]) + if test "$GXX" = "yes"; then + acx_cxx_warnings="-Wall -Wno-unknown-pragmas" + fi + if test -n "$acx_cxx_warnings"; then + CXXFLAGS="$CXXFLAGS $acx_cxx_warnings" + else + acx_cxx_warnings="unknown" + fi + AC_MSG_RESULT($acx_cxx_warnings) +])dnl ACX_CXX_WARNINGS + +dnl enable compiler warnings are errors +dnl we only know how to do this for g++ +AC_DEFUN([ACX_CXX_WARNINGS_ARE_ERRORS], [ + AC_MSG_CHECKING([for C++ compiler warning are errors flags]) + if test "$GXX" = "yes"; then + acx_cxx_warnings_are_errors="-Werror" + fi + if test -n "$acx_cxx_warnings_are_errors"; then + CXXFLAGS="$CXXFLAGS $acx_cxx_warnings_are_errors" + else + acx_cxx_warnings_are_errors="unknown" + fi + AC_MSG_RESULT($acx_cxx_warnings_are_errors) +])dnl ACX_CXX_WARNINGS_ARE_ERRORS diff --git a/cmd/Makefile.am b/cmd/Makefile.am new file mode 100644 index 00000000..1e43b156 --- /dev/null +++ b/cmd/Makefile.am @@ -0,0 +1,27 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + launcher \ + synergyc \ + synergys \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/cmd/launcher/CAddScreen.cpp b/cmd/launcher/CAddScreen.cpp new file mode 100644 index 00000000..5a876b77 --- /dev/null +++ b/cmd/launcher/CAddScreen.cpp @@ -0,0 +1,427 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "KeyTypes.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CAddScreen.h" +#include "LaunchUtil.h" +#include "resource.h" + +struct CModifierInfo { +public: + int m_ctrlID; + const char* m_name; + KeyModifierID m_modifierID; + OptionID m_optionID; +}; + +static const CModifierInfo s_modifiers[] = { + { IDC_ADD_MOD_SHIFT, "Shift", + kKeyModifierIDShift, kOptionModifierMapForShift }, + { IDC_ADD_MOD_CTRL, "Ctrl", + kKeyModifierIDControl, kOptionModifierMapForControl }, + { IDC_ADD_MOD_ALT, "Alt", + kKeyModifierIDAlt, kOptionModifierMapForAlt }, + { IDC_ADD_MOD_META, "Meta", + kKeyModifierIDMeta, kOptionModifierMapForMeta }, + { IDC_ADD_MOD_SUPER, "Super", + kKeyModifierIDSuper, kOptionModifierMapForSuper } +}; + +static const KeyModifierID baseModifier = kKeyModifierIDShift; + +// +// CAddScreen +// + +CAddScreen* CAddScreen::s_singleton = NULL; + +CAddScreen::CAddScreen(HWND parent, CConfig* config, const CString& name) : + m_parent(parent), + m_config(config), + m_name(name) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CAddScreen::~CAddScreen() +{ + s_singleton = NULL; +} + +bool +CAddScreen::doModal() +{ + // do dialog + return (DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_ADD), + m_parent, (DLGPROC)dlgProc, (LPARAM)this) != 0); +} + +CString +CAddScreen::getName() const +{ + return m_name; +} + +void +CAddScreen::init(HWND hwnd) +{ + // set title + CString title; + if (m_name.empty()) { + title = getString(IDS_ADD_SCREEN); + } + else { + title = CStringUtil::format( + getString(IDS_EDIT_SCREEN).c_str(), + m_name.c_str()); + } + SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)title.c_str()); + + // fill in screen name + HWND child = getItem(hwnd, IDC_ADD_SCREEN_NAME_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_name.c_str()); + + // fill in aliases + CString aliases; + for (CConfig::all_const_iterator index = m_config->beginAll(); + index != m_config->endAll(); ++index) { + if (CStringUtil::CaselessCmp::equal(index->second, m_name) && + !CStringUtil::CaselessCmp::equal(index->second, index->first)) { + if (!aliases.empty()) { + aliases += "\r\n"; + } + aliases += index->first; + } + } + child = getItem(hwnd, IDC_ADD_ALIASES_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)aliases.c_str()); + + // set options + CConfig::CScreenOptions options; + getOptions(options); + CConfig::CScreenOptions::const_iterator index; + child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); + index = options.find(kOptionHalfDuplexCapsLock); + setItemChecked(child, (index != options.end() && index->second != 0)); + child = getItem(hwnd, IDC_ADD_HD_NUM_CHECK); + index = options.find(kOptionHalfDuplexNumLock); + setItemChecked(child, (index != options.end() && index->second != 0)); + child = getItem(hwnd, IDC_ADD_HD_SCROLL_CHECK); + index = options.find(kOptionHalfDuplexScrollLock); + setItemChecked(child, (index != options.end() && index->second != 0)); + + // modifier options + for (UInt32 i = 0; i < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++i) { + child = getItem(hwnd, s_modifiers[i].m_ctrlID); + + // fill in options + for (UInt32 j = 0; j < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++j) { + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)s_modifiers[j].m_name); + } + + // choose current value + index = options.find(s_modifiers[i].m_optionID); + KeyModifierID id = s_modifiers[i].m_modifierID; + if (index != options.end()) { + id = index->second; + } + SendMessage(child, CB_SETCURSEL, id - baseModifier, 0); + } + + // dead corners + UInt32 corners = 0; + index = options.find(kOptionScreenSwitchCorners); + if (index != options.end()) { + corners = index->second; + } + child = getItem(hwnd, IDC_ADD_DC_TOP_LEFT); + setItemChecked(child, (corners & kTopLeftMask) != 0); + child = getItem(hwnd, IDC_ADD_DC_TOP_RIGHT); + setItemChecked(child, (corners & kTopRightMask) != 0); + child = getItem(hwnd, IDC_ADD_DC_BOTTOM_LEFT); + setItemChecked(child, (corners & kBottomLeftMask) != 0); + child = getItem(hwnd, IDC_ADD_DC_BOTTOM_RIGHT); + setItemChecked(child, (corners & kBottomRightMask) != 0); + index = options.find(kOptionScreenSwitchCornerSize); + SInt32 size = 0; + if (index != options.end()) { + size = index->second; + } + char buffer[20]; + sprintf(buffer, "%d", size); + child = getItem(hwnd, IDC_ADD_DC_SIZE); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)buffer); +} + +bool +CAddScreen::save(HWND hwnd) +{ + // get the old aliases and options + CStringList oldAliases; + getAliases(oldAliases); + CConfig::CScreenOptions options; + getOptions(options); + + // extract name and aliases + CString newName; + HWND child = getItem(hwnd, IDC_ADD_SCREEN_NAME_EDIT); + newName = getWindowText(child); + CStringList newAliases; + child = getItem(hwnd, IDC_ADD_ALIASES_EDIT); + tokenize(newAliases, getWindowText(child)); + + // name must be valid + if (!m_config->isValidScreenName(newName)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + newName.c_str())); + return false; + } + + // aliases must be valid + for (CStringList::const_iterator index = newAliases.begin(); + index != newAliases.end(); ++index) { + if (!m_config->isValidScreenName(*index)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + index->c_str())); + return false; + } + } + + // new name may not be in the new alias list + if (isNameInList(newAliases, newName)) { + showError(hwnd, CStringUtil::format( + getString(IDS_SCREEN_NAME_IS_ALIAS).c_str(), + newName.c_str())); + return false; + } + + // name must not exist in config but allow same name. also + // allow name if it exists in the old alias list but not the + // new one. + if (m_config->isScreen(newName) && + !CStringUtil::CaselessCmp::equal(newName, m_name) && + !isNameInList(oldAliases, newName)) { + showError(hwnd, CStringUtil::format( + getString(IDS_DUPLICATE_SCREEN_NAME).c_str(), + newName.c_str())); + return false; + } + + // aliases must not exist in config but allow same aliases and + // allow an alias to be the old name. + for (CStringList::const_iterator index = newAliases.begin(); + index != newAliases.end(); ++index) { + if (m_config->isScreen(*index) && + !CStringUtil::CaselessCmp::equal(*index, m_name) && + !isNameInList(oldAliases, *index)) { + showError(hwnd, CStringUtil::format( + getString(IDS_DUPLICATE_SCREEN_NAME).c_str(), + index->c_str())); + return false; + } + } + + // dead corner size must be non-negative + child = getItem(hwnd, IDC_ADD_DC_SIZE); + CString valueString = getWindowText(child); + int cornerSize = atoi(valueString.c_str()); + if (cornerSize < 0) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_CORNER_SIZE).c_str(), + valueString.c_str())); + SetFocus(child); + return false; + } + + // collect options + child = getItem(hwnd, IDC_ADD_HD_CAPS_CHECK); + if (isItemChecked(child)) { + options[kOptionHalfDuplexCapsLock] = 1; + } + else { + options.erase(kOptionHalfDuplexCapsLock); + } + child = getItem(hwnd, IDC_ADD_HD_NUM_CHECK); + if (isItemChecked(child)) { + options[kOptionHalfDuplexNumLock] = 1; + } + else { + options.erase(kOptionHalfDuplexNumLock); + } + child = getItem(hwnd, IDC_ADD_HD_SCROLL_CHECK); + if (isItemChecked(child)) { + options[kOptionHalfDuplexScrollLock] = 1; + } + else { + options.erase(kOptionHalfDuplexScrollLock); + } + + // save modifier options + for (UInt32 i = 0; i < sizeof(s_modifiers) / + sizeof(s_modifiers[0]); ++i) { + child = getItem(hwnd, s_modifiers[i].m_ctrlID); + KeyModifierID id = static_cast( + SendMessage(child, CB_GETCURSEL, 0, 0) + + baseModifier); + if (id != s_modifiers[i].m_modifierID) { + options[s_modifiers[i].m_optionID] = id; + } + else { + options.erase(s_modifiers[i].m_optionID); + } + } + + // save dead corner options + UInt32 corners = 0; + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_TOP_LEFT))) { + corners |= kTopLeftMask; + } + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_TOP_RIGHT))) { + corners |= kTopRightMask; + } + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_BOTTOM_LEFT))) { + corners |= kBottomLeftMask; + } + if (isItemChecked(getItem(hwnd, IDC_ADD_DC_BOTTOM_RIGHT))) { + corners |= kBottomRightMask; + } + options[kOptionScreenSwitchCorners] = corners; + options[kOptionScreenSwitchCornerSize] = cornerSize; + + // save new data to config + if (m_name.empty()) { + // added screen + m_config->addScreen(newName); + } + else { + // edited screen + m_config->removeAliases(m_name); + m_config->removeOptions(m_name); + m_config->renameScreen(m_name, newName); + } + m_name = newName; + for (CStringList::const_iterator index = newAliases.begin(); + index != newAliases.end(); ++index) { + m_config->addAlias(m_name, *index); + } + for (CConfig::CScreenOptions::const_iterator + index = options.begin(); + index != options.end(); ++index) { + m_config->addOption(m_name, index->first, index->second); + } + + return true; +} + +BOOL +CAddScreen::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 1); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAddScreen::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} + +void +CAddScreen::getAliases(CStringList& aliases) const +{ + for (CConfig::all_const_iterator index = m_config->beginAll(); + index != m_config->endAll(); ++index) { + if (CStringUtil::CaselessCmp::equal(index->second, m_name) && + !CStringUtil::CaselessCmp::equal(index->second, index->first)) { + aliases.push_back(index->first); + } + } +} + +void +CAddScreen::getOptions(CConfig::CScreenOptions& optionsOut) const +{ + const CConfig::CScreenOptions* options = m_config->getOptions(m_name); + if (options == NULL) { + optionsOut = CConfig::CScreenOptions(); + } + else { + optionsOut = *options; + } +} + +void +CAddScreen::tokenize(CStringList& tokens, const CString& src) +{ + // find first non-whitespace + CString::size_type x = src.find_first_not_of(" \t\r\n"); + if (x == CString::npos) { + return; + } + + // find next whitespace + do { + CString::size_type y = src.find_first_of(" \t\r\n", x); + if (y == CString::npos) { + y = src.size(); + } + tokens.push_back(src.substr(x, y - x)); + x = src.find_first_not_of(" \t\r\n", y); + } while (x != CString::npos); +} + +bool +CAddScreen::isNameInList(const CStringList& names, const CString& name) +{ + for (CStringList::const_iterator index = names.begin(); + index != names.end(); ++index) { + if (CStringUtil::CaselessCmp::equal(name, *index)) { + return true; + } + } + return false; +} diff --git a/cmd/launcher/CAddScreen.h b/cmd/launcher/CAddScreen.h new file mode 100644 index 00000000..e926c498 --- /dev/null +++ b/cmd/launcher/CAddScreen.h @@ -0,0 +1,74 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CADDSCREEN_H +#define CADDSCREEN_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Add screen dialog for Microsoft Windows launcher +class CAddScreen { +public: + CAddScreen(HWND parent, CConfig*, const CString& name); + ~CAddScreen(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. Return + \c true if the user accepted the changes, false otherwise. + */ + bool doModal(); + + //@} + //! @name accessors + //@{ + + CString getName() const; + + //@} + +private: + typedef std::vector CStringList; + + void getAliases(CStringList&) const; + void getOptions(CConfig::CScreenOptions&) const; + + static void tokenize(CStringList& tokens, const CString& src); + static bool isNameInList(const CStringList& tokens, + const CString& src); + + void init(HWND hwnd); + bool save(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAddScreen* s_singleton; + + HWND m_parent; + CConfig* m_config; + CString m_name; +}; + +#endif diff --git a/cmd/launcher/CAdvancedOptions.cpp b/cmd/launcher/CAdvancedOptions.cpp new file mode 100644 index 00000000..c1ea83ef --- /dev/null +++ b/cmd/launcher/CAdvancedOptions.cpp @@ -0,0 +1,269 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include "CAdvancedOptions.h" +#include "LaunchUtil.h" +#include "XArch.h" +#include "resource.h" + +// +// CAdvancedOptions +// + +CAdvancedOptions* CAdvancedOptions::s_singleton = NULL; + +CAdvancedOptions::CAdvancedOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config), + m_isClient(false), + m_screenName(ARCH->getHostName()), + m_port(kDefaultPort), + m_interface() +{ + assert(s_singleton == NULL); + s_singleton = this; + init(); +} + +CAdvancedOptions::~CAdvancedOptions() +{ + s_singleton = NULL; +} + +void +CAdvancedOptions::doModal(bool isClient) +{ + // save state + m_isClient = isClient; + + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_ADVANCED_OPTIONS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +CString +CAdvancedOptions::getScreenName() const +{ + return m_screenName; +} + +int +CAdvancedOptions::getPort() const +{ + return m_port; +} + +CString +CAdvancedOptions::getInterface() const +{ + return m_interface; +} + +CString +CAdvancedOptions::getCommandLine(bool isClient, const CString& serverName) const +{ + CString cmdLine; + + // screen name + if (!m_screenName.empty()) { + cmdLine += " --name "; + cmdLine += m_screenName; + } + + // port + char portString[20]; + sprintf(portString, "%d", m_port); + if (isClient) { + cmdLine += " "; + cmdLine += serverName; + cmdLine += ":"; + cmdLine += portString; + } + else { + cmdLine += " --address "; + if (!m_interface.empty()) { + cmdLine += m_interface; + } + cmdLine += ":"; + cmdLine += portString; + } + + return cmdLine; +} + +void +CAdvancedOptions::init() +{ + // get values from registry + HKEY key = CArchMiscWindows::openKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + DWORD newPort = CArchMiscWindows::readValueInt(key, "port"); + CString newName = CArchMiscWindows::readValueString(key, "name"); + CString newInterface = + CArchMiscWindows::readValueString(key, "interface"); + if (newPort != 0) { + m_port = static_cast(newPort); + } + if (!newName.empty()) { + m_screenName = newName; + } + if (!newInterface.empty()) { + m_interface = newInterface; + } + CArchMiscWindows::closeKey(key); + } +} + +void +CAdvancedOptions::doInit(HWND hwnd) +{ + // set values in GUI + HWND child; + char buffer[20]; + sprintf(buffer, "%d", m_port); + child = getItem(hwnd, IDC_ADVANCED_PORT_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)buffer); + + child = getItem(hwnd, IDC_ADVANCED_NAME_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_screenName.c_str()); + + child = getItem(hwnd, IDC_ADVANCED_INTERFACE_EDIT); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)m_interface.c_str()); +} + +bool +CAdvancedOptions::save(HWND hwnd) +{ + SetCursor(LoadCursor(NULL, IDC_WAIT)); + + HWND child = getItem(hwnd, IDC_ADVANCED_NAME_EDIT); + CString name = getWindowText(child); + if (!m_config->isValidScreenName(name)) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SCREEN_NAME).c_str(), + name.c_str())); + SetFocus(child); + return false; + } + if (!m_isClient && !m_config->isScreen(name)) { + showError(hwnd, CStringUtil::format( + getString(IDS_UNKNOWN_SCREEN_NAME).c_str(), + name.c_str())); + SetFocus(child); + return false; + } + + child = getItem(hwnd, IDC_ADVANCED_INTERFACE_EDIT); + CString iface = getWindowText(child); + if (!m_isClient) { + try { + if (!iface.empty()) { + ARCH->nameToAddr(iface); + } + } + catch (XArchNetworkName& e) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_INTERFACE_NAME).c_str(), + iface.c_str(), e.what().c_str())); + SetFocus(child); + return false; + } + } + + // get and verify port + child = getItem(hwnd, IDC_ADVANCED_PORT_EDIT); + CString portString = getWindowText(child); + int port = atoi(portString.c_str()); + if (port < 1 || port > 65535) { + CString defaultPortString = CStringUtil::print("%d", kDefaultPort); + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_PORT).c_str(), + portString.c_str(), + defaultPortString.c_str())); + SetFocus(child); + return false; + } + + // save state + m_screenName = name; + m_port = port; + m_interface = iface; + + // save values to registry + HKEY key = CArchMiscWindows::addKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + CArchMiscWindows::setValue(key, "port", m_port); + CArchMiscWindows::setValue(key, "name", m_screenName); + CArchMiscWindows::setValue(key, "interface", m_interface); + CArchMiscWindows::closeKey(key); + } + + return true; +} + +void +CAdvancedOptions::setDefaults(HWND hwnd) +{ + // restore defaults + m_screenName = ARCH->getHostName(); + m_port = kDefaultPort; + m_interface = ""; + + // update GUI + doInit(hwnd); +} + +BOOL +CAdvancedOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_ADVANCED_DEFAULTS: + setDefaults(hwnd); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAdvancedOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CAdvancedOptions.h b/cmd/launcher/CAdvancedOptions.h new file mode 100644 index 00000000..1dd9dc44 --- /dev/null +++ b/cmd/launcher/CAdvancedOptions.h @@ -0,0 +1,80 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CADVANCEDOPTIONS_H +#define CADVANCEDOPTIONS_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Advanced options dialog for Microsoft Windows launcher +class CAdvancedOptions { +public: + CAdvancedOptions(HWND parent, CConfig*); + ~CAdvancedOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(bool isClient); + + //@} + //! @name accessors + //@{ + + //! Get the screen name + CString getScreenName() const; + + //! Get the port + int getPort() const; + + //! Get the interface + CString getInterface() const; + + //! Convert options to command line string + CString getCommandLine(bool isClient, + const CString& serverName) const; + + //@} + +private: + void init(); + void doInit(HWND hwnd); + bool save(HWND hwnd); + void setDefaults(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAdvancedOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + bool m_isClient; + CString m_screenName; + int m_port; + CString m_interface; +}; + +#endif diff --git a/cmd/launcher/CAutoStart.cpp b/cmd/launcher/CAutoStart.cpp new file mode 100644 index 00000000..eb18a6b0 --- /dev/null +++ b/cmd/launcher/CAutoStart.cpp @@ -0,0 +1,361 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CLog.h" +#include "ILogOutputter.h" +#include "CArch.h" +#include "CStringUtil.h" +#include "XArch.h" +#include "CAutoStart.h" +#include "LaunchUtil.h" +#include "resource.h" + +static const char* CLIENT_DAEMON_NAME = "Synergy Client"; +static const char* SERVER_DAEMON_NAME = "Synergy Server"; +static const char* CLIENT_DAEMON_INFO = "Uses a shared mouse and keyboard."; +static const char* SERVER_DAEMON_INFO = "Shares this system's mouse and keyboard with others."; + +// +// CAutoStartOutputter +// +// This class detects a message above a certain level and saves it +// + +class CAutoStartOutputter : public ILogOutputter { +public: + CAutoStartOutputter(CString* msg) : m_msg(msg) { } + virtual ~CAutoStartOutputter() { } + + // ILogOutputter overrides + virtual void open(const char*) { } + virtual void close() { } + virtual void show(bool) { } + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const { return ""; } + +private: + CString* m_msg; +}; + +bool +CAutoStartOutputter::write(ELevel level, const char* message) +{ + if (level <= CLog::kERROR) { + *m_msg = message; + } + return false; +} + + +// +// CAutoStart +// + +CAutoStart* CAutoStart::s_singleton = NULL; + +CAutoStart::CAutoStart(HWND parent, bool isServer, const CString& cmdLine) : + m_parent(parent), + m_isServer(isServer), + m_cmdLine(cmdLine), + m_name(isServer ? SERVER_DAEMON_NAME : CLIENT_DAEMON_NAME) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CAutoStart::~CAutoStart() +{ + s_singleton = NULL; +} + +void +CAutoStart::doModal() +{ + // install our log outputter + CLOG->insert(new CAutoStartOutputter(&m_errorMessage)); + + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_AUTOSTART), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); + + // remove log outputter + CLOG->pop_front(); +} + +void +CAutoStart::reinstallDaemon(bool isClient, const CString& cmdLine) +{ + // get installation state + const char* name = (isClient ? CLIENT_DAEMON_NAME : SERVER_DAEMON_NAME); + bool installedSystem = ARCH->isDaemonInstalled(name, true); + bool installedUser = ARCH->isDaemonInstalled(name, false); + + // reinstall if anything is installed + if (installedSystem || installedUser) { + ARCH->installDaemon(name, + isClient ? CLIENT_DAEMON_INFO : SERVER_DAEMON_INFO, + getAppPath(isClient ? CLIENT_APP : SERVER_APP).c_str(), + cmdLine.c_str(), + NULL, + installedSystem); + } +} + +void +CAutoStart::uninstallDaemons(bool client) +{ + if (client) { + try { + ARCH->uninstallDaemon(CLIENT_DAEMON_NAME, true); + } + catch (...) { + } + try { + ARCH->uninstallDaemon(CLIENT_DAEMON_NAME, false); + } + catch (...) { + } + } + else { + try { + ARCH->uninstallDaemon(SERVER_DAEMON_NAME, true); + } + catch (...) { + } + try { + ARCH->uninstallDaemon(SERVER_DAEMON_NAME, false); + } + catch (...) { + } + } +} + +bool +CAutoStart::startDaemon() +{ + const char* name = NULL; + if (ARCH->isDaemonInstalled(CLIENT_DAEMON_NAME, true)) { + name = CLIENT_DAEMON_NAME; + } + else if (ARCH->isDaemonInstalled(SERVER_DAEMON_NAME, true)) { + name = SERVER_DAEMON_NAME; + } + if (name == NULL) { + return false; + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + return false; + } + + // open the service + SC_HANDLE service = OpenService(mgr, name, SERVICE_START); + if (service == NULL) { + CloseServiceHandle(mgr); + return false; + } + + // start the service + BOOL okay = StartService(service, 0, NULL); + + // clean up + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + return (okay != 0); +} + +bool +CAutoStart::isDaemonInstalled() +{ + return (ARCH->isDaemonInstalled(CLIENT_DAEMON_NAME, false) || + ARCH->isDaemonInstalled(CLIENT_DAEMON_NAME, true) || + ARCH->isDaemonInstalled(SERVER_DAEMON_NAME, false) || + ARCH->isDaemonInstalled(SERVER_DAEMON_NAME, true)); +} + +void +CAutoStart::update() +{ + // get installation state + const bool installedSystem = ARCH->isDaemonInstalled( + m_name.c_str(), true); + const bool installedUser = ARCH->isDaemonInstalled( + m_name.c_str(), false); + + // get user's permissions + const bool canInstallSystem = ARCH->canInstallDaemon( + m_name.c_str(), true); + const bool canInstallUser = ARCH->canInstallDaemon( + m_name.c_str(), false); + + // update messages + CString msg, label; + if (canInstallSystem) { + if (canInstallUser) { + msg = getString(IDS_AUTOSTART_PERMISSION_ALL); + } + else { + msg = getString(IDS_AUTOSTART_PERMISSION_SYSTEM); + } + } + else if (canInstallUser) { + msg = getString(IDS_AUTOSTART_PERMISSION_USER); + } + else { + msg = getString(IDS_AUTOSTART_PERMISSION_NONE); + } + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_PERMISSION_MSG), msg); + if (installedSystem) { + msg = getString(IDS_AUTOSTART_INSTALLED_SYSTEM); + label = getString(IDS_UNINSTALL_LABEL); + } + else if (installedUser) { + msg = getString(IDS_AUTOSTART_INSTALLED_USER); + label = getString(IDS_UNINSTALL_LABEL); + } + else { + msg = getString(IDS_AUTOSTART_INSTALLED_NONE); + label = getString(IDS_INSTALL_LABEL); + } + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_INSTALLED_MSG), msg); + + // update buttons + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM), label); + setWindowText(getItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER), label); + if (installedSystem) { + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM, canInstallSystem); + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER, false); + m_install = false; + } + else if (installedUser) { + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM, false); + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER, canInstallUser); + m_install = false; + } + else { + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_SYSTEM, canInstallSystem); + enableItem(m_hwnd, IDC_AUTOSTART_INSTALL_USER, canInstallUser); + m_install = true; + } +} + +bool +CAutoStart::onInstall(bool allUsers) +{ + if (!m_install) { + return onUninstall(allUsers); + } + + // get the app path + CString appPath = getAppPath(m_isServer ? SERVER_APP : CLIENT_APP); + + // clear error message + m_errorMessage = ""; + + // install + try { + ARCH->installDaemon(m_name.c_str(), + m_isServer ? SERVER_DAEMON_INFO : CLIENT_DAEMON_INFO, + appPath.c_str(), m_cmdLine.c_str(), + NULL, allUsers); + askOkay(m_hwnd, getString(IDS_INSTALL_TITLE), + getString(allUsers ? + IDS_INSTALLED_SYSTEM : + IDS_INSTALLED_USER)); + return true; + } + catch (XArchDaemon& e) { + if (m_errorMessage.empty()) { + m_errorMessage = CStringUtil::format( + getString(IDS_INSTALL_GENERIC_ERROR).c_str(), + e.what().c_str()); + } + showError(m_hwnd, m_errorMessage); + return false; + } +} + +bool +CAutoStart::onUninstall(bool allUsers) +{ + // clear error message + m_errorMessage = ""; + + // uninstall + try { + ARCH->uninstallDaemon(m_name.c_str(), allUsers); + askOkay(m_hwnd, getString(IDS_UNINSTALL_TITLE), + getString(allUsers ? + IDS_UNINSTALLED_SYSTEM : + IDS_UNINSTALLED_USER)); + return true; + } + catch (XArchDaemon& e) { + if (m_errorMessage.empty()) { + m_errorMessage = CStringUtil::format( + getString(IDS_UNINSTALL_GENERIC_ERROR).c_str(), + e.what().c_str()); + } + showError(m_hwnd, m_errorMessage); + return false; + } +} + +BOOL +CAutoStart::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + // save our hwnd + m_hwnd = hwnd; + + // update the controls + update(); + + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_AUTOSTART_INSTALL_SYSTEM: + onInstall(true); + update(); + return TRUE; + + case IDC_AUTOSTART_INSTALL_USER: + onInstall(false); + update(); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + m_hwnd = NULL; + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CAutoStart::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CAutoStart.h b/cmd/launcher/CAutoStart.h new file mode 100644 index 00000000..46aad100 --- /dev/null +++ b/cmd/launcher/CAutoStart.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CAUTOSTART_H +#define CAUTOSTART_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +//! Auto start dialog for Microsoft Windows launcher +class CAutoStart { +public: + CAutoStart(HWND parent, bool isServer, const CString& cmdLine); + ~CAutoStart(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //! Reinstall daemon + /*! + Reinstalls the currently installed daemon. + */ + static void reinstallDaemon(bool isClient, const CString& cmdLine); + + //! Uninstalls daemon + /*! + Uninstalls all installed client (\p client is \c true) or server daemons. + */ + static void uninstallDaemons(bool client); + + //! Starts an installed daemon + /*! + Returns \c true iff a daemon was started. This will only start daemons + installed for all users. + */ + static bool startDaemon(); + + //@} + //! @name accessors + //@{ + + //! Tests if any daemons are installed + /*! + Returns \c true if any daemons are installed. + */ + static bool isDaemonInstalled(); + + //@} + +private: + void update(); + bool onInstall(bool allUsers); + bool onUninstall(bool allUsers); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CAutoStart* s_singleton; + + HWND m_parent; + bool m_isServer; + CString m_cmdLine; + CString m_name; + HWND m_hwnd; + bool m_install; + CString m_errorMessage; +}; + +#endif diff --git a/cmd/launcher/CGlobalOptions.cpp b/cmd/launcher/CGlobalOptions.cpp new file mode 100644 index 00000000..8237a07f --- /dev/null +++ b/cmd/launcher/CGlobalOptions.cpp @@ -0,0 +1,281 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CGlobalOptions.h" +#include "LaunchUtil.h" +#include "resource.h" + +static const int s_defaultDelay = 250; +static const int s_defaultHeartbeat = 5000; + +// +// CGlobalOptions +// + +CGlobalOptions* CGlobalOptions::s_singleton = NULL; + +CGlobalOptions::CGlobalOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config), + m_delayTime(s_defaultDelay), + m_twoTapTime(s_defaultDelay), + m_heartbeatTime(s_defaultHeartbeat) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CGlobalOptions::~CGlobalOptions() +{ + s_singleton = NULL; +} + +void +CGlobalOptions::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_GLOBAL_OPTIONS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CGlobalOptions::init(HWND hwnd) +{ + HWND child; + char buffer[30]; + + // reset options + sprintf(buffer, "%d", m_delayTime); + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + setWindowText(child, buffer); + sprintf(buffer, "%d", m_twoTapTime); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + setWindowText(child, buffer); + sprintf(buffer, "%d", m_heartbeatTime); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + setWindowText(child, buffer); + child = getItem(hwnd, IDC_GLOBAL_SCREENSAVER_SYNC); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_RELATIVE_MOVES); + setItemChecked(child, false); + child = getItem(hwnd, IDC_GLOBAL_LEAVE_FOREGROUND); + setItemChecked(child, false); + + // get the global options + const CConfig::CScreenOptions* options = m_config->getOptions(""); + if (options != NULL) { + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionScreenSwitchTwoTap) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionHeartbeat) { + if (value > 0) { + sprintf(buffer, "%d", value); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + setItemChecked(child, true); + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + setWindowText(child, buffer); + } + } + else if (id == kOptionScreenSaverSync) { + child = getItem(hwnd, IDC_GLOBAL_SCREENSAVER_SYNC); + setItemChecked(child, (value != 0)); + } + else if (id == kOptionRelativeMouseMoves) { + child = getItem(hwnd, IDC_GLOBAL_RELATIVE_MOVES); + setItemChecked(child, (value != 0)); + } + else if (id == kOptionWin32KeepForeground) { + child = getItem(hwnd, IDC_GLOBAL_LEAVE_FOREGROUND); + setItemChecked(child, (value != 0)); + } + } + } +} + +bool +CGlobalOptions::save(HWND hwnd) +{ + HWND child; + int newDelayTime = 0; + int newTwoTapTime = 0; + int newHeartbeatTime = 0; + + // get requested options + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + newDelayTime = getTime(hwnd, child, true); + if (newDelayTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_DELAY_TIME); + newDelayTime = getTime(hwnd, child, false); + if (newDelayTime == 0) { + newDelayTime = s_defaultDelay; + } + } + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + newTwoTapTime = getTime(hwnd, child, true); + if (newTwoTapTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_TIME); + newTwoTapTime = getTime(hwnd, child, false); + if (newTwoTapTime == 0) { + newTwoTapTime = s_defaultDelay; + } + } + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + if (isItemChecked(child)) { + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + newHeartbeatTime = getTime(hwnd, child, true); + if (newHeartbeatTime == 0) { + return false; + } + } + else { + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_TIME); + newHeartbeatTime = getTime(hwnd, child, false); + if (newHeartbeatTime == 0) { + newHeartbeatTime = s_defaultHeartbeat; + } + } + + // remove existing config options + m_config->removeOption("", kOptionScreenSwitchDelay); + m_config->removeOption("", kOptionScreenSwitchTwoTap); + m_config->removeOption("", kOptionHeartbeat); + m_config->removeOption("", kOptionScreenSaverSync); + m_config->removeOption("", kOptionRelativeMouseMoves); + m_config->removeOption("", kOptionWin32KeepForeground); + + // add requested options + child = getItem(hwnd, IDC_GLOBAL_DELAY_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionScreenSwitchDelay, newDelayTime); + } + child = getItem(hwnd, IDC_GLOBAL_TWO_TAP_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionScreenSwitchTwoTap, newTwoTapTime); + } + child = getItem(hwnd, IDC_GLOBAL_HEARTBEAT_CHECK); + if (isItemChecked(child)) { + m_config->addOption("", kOptionHeartbeat, newHeartbeatTime); + } + child = getItem(hwnd, IDC_GLOBAL_SCREENSAVER_SYNC); + if (!isItemChecked(child)) { + m_config->addOption("", kOptionScreenSaverSync, 0); + } + child = getItem(hwnd, IDC_GLOBAL_RELATIVE_MOVES); + if (isItemChecked(child)) { + m_config->addOption("", kOptionRelativeMouseMoves, 1); + } + child = getItem(hwnd, IDC_GLOBAL_LEAVE_FOREGROUND); + if (isItemChecked(child)) { + m_config->addOption("", kOptionWin32KeepForeground, 1); + } + + // save last values + m_delayTime = newDelayTime; + m_twoTapTime = newTwoTapTime; + m_heartbeatTime = newHeartbeatTime; + return true; +} + +int +CGlobalOptions::getTime(HWND hwnd, HWND child, bool reportError) +{ + CString valueString = getWindowText(child); + int value = atoi(valueString.c_str()); + if (value < 1) { + if (reportError) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_TIME).c_str(), + valueString.c_str())); + SetFocus(child); + } + return 0; + } + return value; +} + +BOOL +CGlobalOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CGlobalOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CGlobalOptions.h b/cmd/launcher/CGlobalOptions.h new file mode 100644 index 00000000..f04f1bae --- /dev/null +++ b/cmd/launcher/CGlobalOptions.h @@ -0,0 +1,67 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CGLOBALOPTIONS_H +#define CGLOBALOPTIONS_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Global options dialog for Microsoft Windows launcher +class CGlobalOptions { +public: + CGlobalOptions(HWND parent, CConfig*); + ~CGlobalOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + + //@} + +private: + void init(HWND hwnd); + bool save(HWND hwnd); + + int getTime(HWND hwnd, HWND child, bool reportError); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CGlobalOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + int m_delayTime; + int m_twoTapTime; + int m_heartbeatTime; +}; + +#endif diff --git a/cmd/launcher/CHotkeyOptions.cpp b/cmd/launcher/CHotkeyOptions.cpp new file mode 100644 index 00000000..5aa981e0 --- /dev/null +++ b/cmd/launcher/CHotkeyOptions.cpp @@ -0,0 +1,1938 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchMiscWindows.h" +#include "CMSWindowsKeyState.h" +#include "CConfig.h" +#include "CHotkeyOptions.h" +#include "CStringUtil.h" +#include "LaunchUtil.h" +#include "resource.h" + +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif + +// +// CAdvancedOptions +// + +CHotkeyOptions* CHotkeyOptions::s_singleton = NULL; + +CHotkeyOptions::CHotkeyOptions(HWND parent, CConfig* config) : + m_parent(parent), + m_config(config) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CHotkeyOptions::~CHotkeyOptions() +{ + s_singleton = NULL; +} + +void +CHotkeyOptions::doModal() +{ + // do dialog + m_inputFilter = m_config->getInputFilter(); + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_OPTIONS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CHotkeyOptions::doInit(HWND hwnd) +{ + m_activeRuleIndex = (UInt32)-1; + fillHotkeys(hwnd); + openRule(hwnd); +} + +void +CHotkeyOptions::fillHotkeys(HWND hwnd, UInt32 select) +{ + HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + + SendMessage(rules, LB_RESETCONTENT, 0, 0); + for (UInt32 i = 0, n = m_inputFilter->getNumRules(); i < n; ++i) { + CInputFilter::CRule& rule = m_inputFilter->getRule(i); + SendMessage(rules, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)rule.getCondition()->format().c_str()); + } + + if (select < m_inputFilter->getNumRules()) { + SendMessage(rules, LB_SETCURSEL, select, 0); + } + + updateHotkeysControls(hwnd); +} + +void +CHotkeyOptions::updateHotkeysControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + bool selected = (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR); + + enableItem(hwnd, IDC_HOTKEY_ADD_HOTKEY, TRUE); + enableItem(hwnd, IDC_HOTKEY_EDIT_HOTKEY, selected); + enableItem(hwnd, IDC_HOTKEY_REMOVE_HOTKEY, selected); +} + +void +CHotkeyOptions::addHotkey(HWND hwnd) +{ + closeRule(hwnd); + CInputFilter::CCondition* condition = NULL; + if (editCondition(hwnd, condition)) { + m_inputFilter->addFilterRule(CInputFilter::CRule(condition)); + fillHotkeys(hwnd, m_inputFilter->getNumRules() - 1); + } + else { + delete condition; + } + openRule(hwnd); +} + +void +CHotkeyOptions::removeHotkey(HWND hwnd) +{ + UInt32 ruleIndex = m_activeRuleIndex; + closeRule(hwnd); + + m_inputFilter->removeFilterRule(ruleIndex); + UInt32 n = m_inputFilter->getNumRules(); + if (n > 0 && ruleIndex >= n) { + ruleIndex = n - 1; + } + fillHotkeys(hwnd, ruleIndex); + + openRule(hwnd); +} + +void +CHotkeyOptions::editHotkey(HWND hwnd) +{ + // save selected item in action list + HWND actions = getItem(hwnd, IDC_HOTKEY_ACTIONS); + LRESULT aIndex = SendMessage(actions, LB_GETCURSEL, 0, 0); + + UInt32 index = m_activeRuleIndex; + closeRule(hwnd); + + CInputFilter::CRule& rule = m_inputFilter->getRule(index); + CInputFilter::CCondition* condition = rule.getCondition()->clone(); + if (editCondition(hwnd, condition)) { + rule.setCondition(condition); + fillHotkeys(hwnd, index); + } + else { + delete condition; + } + + openRule(hwnd); + + // restore selected item in action list + if (aIndex != LB_ERR) { + SendMessage(actions, LB_SETCURSEL, aIndex, 0); + } +} + +void +CHotkeyOptions::fillActions(HWND hwnd, UInt32 select) +{ + HWND actions = getItem(hwnd, IDC_HOTKEY_ACTIONS); + SendMessage(actions, LB_RESETCONTENT, 0, 0); + if (m_activeRuleIndex != (UInt32)-1) { + UInt32 n = m_activeRule.getNumActions(true); + UInt32 n2 = m_activeRule.getNumActions(false); + for (UInt32 i = 0; i < n; ++i) { + const CInputFilter::CAction& action = + m_activeRule.getAction(true, i); + CString line("A "); + line += action.format(); + SendMessage(actions, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)line.c_str()); + SendMessage(actions, LB_SETITEMDATA, (WPARAM)i, (LPARAM)i); + } + for (UInt32 i = 0; i < n2; ++i) { + const CInputFilter::CAction& action = + m_activeRule.getAction(false, i); + CString line("D "); + line += action.format(); + SendMessage(actions, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)line.c_str()); + SendMessage(actions, LB_SETITEMDATA, (WPARAM)i + n, + (LPARAM)(i | 0x80000000u)); + } + + if (select < n + n2) { + SendMessage(actions, LB_SETCURSEL, select, 0); + } + } + + updateActionsControls(hwnd); +} + +void +CHotkeyOptions::updateActionsControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + bool active = (m_activeRuleIndex != (UInt32)-1); + + child = getItem(hwnd, IDC_HOTKEY_ACTIONS); + bool selected = + (active && (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR)); + + enableItem(hwnd, IDC_HOTKEY_ADD_ACTION, active); + enableItem(hwnd, IDC_HOTKEY_EDIT_ACTION, selected); + enableItem(hwnd, IDC_HOTKEY_REMOVE_ACTION, selected); +} + +void +CHotkeyOptions::addAction(HWND hwnd) +{ + CInputFilter::CAction* action = NULL; + bool onActivate = true; + if (editAction(hwnd, action, onActivate)) { + m_activeRule.adoptAction(action, onActivate); + + UInt32 actionIndex = m_activeRule.getNumActions(true) - 1; + if (!onActivate) { + actionIndex += m_activeRule.getNumActions(false); + } + fillActions(hwnd, actionIndex); + } + else { + delete action; + } +} + +void +CHotkeyOptions::removeAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTIONS); + LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { + UInt32 actionIndex = + (UInt32)SendMessage(child, LB_GETITEMDATA, index, 0); + bool onActivate = ((actionIndex & 0x80000000u) == 0); + actionIndex &= ~0x80000000u; + + m_activeRule.removeAction(onActivate, actionIndex); + + actionIndex = static_cast(index); + UInt32 n = m_activeRule.getNumActions(true) + + m_activeRule.getNumActions(false); + if (n > 0 && actionIndex >= n) { + actionIndex = n - 1; + } + fillActions(hwnd, actionIndex); + } +} + +void +CHotkeyOptions::editAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTIONS); + LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { + UInt32 actionIndex = + (UInt32)SendMessage(child, LB_GETITEMDATA, index, 0); + bool onActivate = ((actionIndex & 0x80000000u) == 0); + actionIndex &= ~0x80000000u; + + CInputFilter::CAction* action = + m_activeRule.getAction(onActivate, actionIndex).clone(); + bool newOnActivate = onActivate; + if (editAction(hwnd, action, newOnActivate)) { + if (onActivate == newOnActivate) { + m_activeRule.replaceAction(action, onActivate, actionIndex); + actionIndex = static_cast(index); + } + else { + m_activeRule.removeAction(onActivate, actionIndex); + m_activeRule.adoptAction(action, newOnActivate); + actionIndex = m_activeRule.getNumActions(true) - 1; + if (!newOnActivate) { + actionIndex += m_activeRule.getNumActions(false); + } + } + fillActions(hwnd, actionIndex); + } + else { + delete action; + } + } +} + +bool +CHotkeyOptions::editCondition(HWND hwnd, CInputFilter::CCondition*& condition) +{ + return CConditionDialog::doModal(hwnd, condition); +} + +bool +CHotkeyOptions::editAction(HWND hwnd, CInputFilter::CAction*& action, + bool& onActivate) +{ + return CActionDialog::doModal(hwnd, m_config, action, onActivate); +} + +void +CHotkeyOptions::openRule(HWND hwnd) +{ + // get the active rule and copy it, merging down/up pairs of keystroke + // and mouse button actions into single actions for the convenience of + // of the user. + HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + LRESULT index = SendMessage(rules, LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { + // copy the rule as is + m_activeRuleIndex = (SInt32)index; + m_activeRule = m_inputFilter->getRule(m_activeRuleIndex); + + // look for actions to combine + for (UInt32 i = 0, n = m_activeRule.getNumActions(true); i < n; ++i) { + // get next activate action + const CInputFilter::CAction* action = + &m_activeRule.getAction(true, i); + + // check if it's a key or mouse action + const CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(action); + const CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(action); + if (keyAction == NULL && mouseAction == NULL) { + continue; + } + + // check for matching deactivate action + UInt32 j = (UInt32)-1; + CInputFilter::CAction* newAction = NULL; + if (keyAction != NULL) { + j = findMatchingAction(keyAction); + if (j != (UInt32)-1) { + // found a match + const IPlatformScreen::CKeyInfo* oldInfo = + keyAction->getInfo(); + IPlatformScreen::CKeyInfo* newInfo = + IKeyState::CKeyInfo::alloc(*oldInfo); + newAction = new CKeystrokeDownUpAction(newInfo); + } + } + else if (mouseAction != NULL) { + j = findMatchingAction(mouseAction); + if (j != (UInt32)-1) { + // found a match + const IPlatformScreen::CButtonInfo* oldInfo = + mouseAction->getInfo(); + IPlatformScreen::CButtonInfo* newInfo = + IPrimaryScreen::CButtonInfo::alloc(*oldInfo); + newAction = new CMouseButtonDownUpAction(newInfo); + } + } + + // perform merge + if (newAction != NULL) { + m_activeRule.replaceAction(newAction, true, i); + m_activeRule.removeAction(false, j); + } + } + } + else { + m_activeRuleIndex = (UInt32)-1; + } + fillActions(hwnd); +} + +void +CHotkeyOptions::closeRule(HWND) +{ + // copy rule back to input filter, expanding merged actions into the + // two component actions. + if (m_activeRuleIndex != (UInt32)-1) { + // expand merged rules + for (UInt32 i = 0, n = m_activeRule.getNumActions(true); i < n; ++i) { + // get action + const CInputFilter::CAction* action = + &m_activeRule.getAction(true, i); + + // check if it's a merged key or mouse action + const CKeystrokeDownUpAction* keyAction = + dynamic_cast(action); + const CMouseButtonDownUpAction* mouseAction = + dynamic_cast(action); + if (keyAction == NULL && mouseAction == NULL) { + continue; + } + + // expand + if (keyAction != NULL) { + const IPlatformScreen::CKeyInfo* oldInfo = + keyAction->getInfo(); + IPlatformScreen::CKeyInfo* newInfo = + IKeyState::CKeyInfo::alloc(*oldInfo); + CInputFilter::CKeystrokeAction* downAction = + new CInputFilter::CKeystrokeAction(newInfo, true); + newInfo = IKeyState::CKeyInfo::alloc(*oldInfo); + CInputFilter::CKeystrokeAction* upAction = + new CInputFilter::CKeystrokeAction(newInfo, false); + m_activeRule.replaceAction(downAction, true, i); + m_activeRule.adoptAction(upAction, false); + } + else if (mouseAction != NULL) { + const IPlatformScreen::CButtonInfo* oldInfo = + mouseAction->getInfo(); + IPlatformScreen::CButtonInfo* newInfo = + IPrimaryScreen::CButtonInfo::alloc(*oldInfo); + CInputFilter::CMouseButtonAction* downAction = + new CInputFilter::CMouseButtonAction(newInfo, true); + newInfo = IPrimaryScreen::CButtonInfo::alloc(*oldInfo); + CInputFilter::CMouseButtonAction* upAction = + new CInputFilter::CMouseButtonAction(newInfo, false); + m_activeRule.replaceAction(downAction, true, i); + m_activeRule.adoptAction(upAction, false); + } + } + + // copy it back + m_inputFilter->getRule(m_activeRuleIndex) = m_activeRule; + } + m_activeRuleIndex = (UInt32)-1; +} + +UInt32 +CHotkeyOptions::findMatchingAction( + const CInputFilter::CKeystrokeAction* src) const +{ + for (UInt32 i = 0, n = m_activeRule.getNumActions(false); i < n; ++i) { + const CInputFilter::CKeystrokeAction* dst = + dynamic_cast( + &m_activeRule.getAction(false, i)); + if (dst != NULL && + IKeyState::CKeyInfo::equal(src->getInfo(), dst->getInfo())) { + return i; + } + } + return (UInt32)-1; +} + +UInt32 +CHotkeyOptions::findMatchingAction( + const CInputFilter::CMouseButtonAction* src) const +{ + for (UInt32 i = 0, n = m_activeRule.getNumActions(false); i < n; ++i) { + const CInputFilter::CMouseButtonAction* dst = + dynamic_cast( + &m_activeRule.getAction(false, i)); + if (dst != NULL && + IPrimaryScreen::CButtonInfo::equal( + src->getInfo(), dst->getInfo())) { + return i; + } + } + return (UInt32)-1; +} + +BOOL +CHotkeyOptions::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + closeRule(hwnd); + EndDialog(hwnd, 0); + return TRUE; + + case IDC_HOTKEY_HOTKEYS: + switch (HIWORD(wParam)) { + case LBN_DBLCLK: + editHotkey(hwnd); + return TRUE; + + case LBN_SELCHANGE: { + HWND rules = getItem(hwnd, IDC_HOTKEY_HOTKEYS); + LRESULT index = SendMessage(rules, LB_GETCURSEL, 0, 0); + if (m_activeRuleIndex != (UInt32)index) { + closeRule(hwnd); + updateHotkeysControls(hwnd); + openRule(hwnd); + } + return TRUE; + } + } + break; + + case IDC_HOTKEY_ADD_HOTKEY: + addHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_REMOVE_HOTKEY: + removeHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_EDIT_HOTKEY: + editHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTIONS: + switch (HIWORD(wParam)) { + case LBN_DBLCLK: + editAction(hwnd); + return TRUE; + + case LBN_SELCHANGE: + updateActionsControls(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ADD_ACTION: + addAction(hwnd); + return TRUE; + + case IDC_HOTKEY_REMOVE_ACTION: + removeAction(hwnd); + return TRUE; + + case IDC_HOTKEY_EDIT_ACTION: + editAction(hwnd); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CHotkeyOptions::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} + + +// +// CHotkeyOptions::CConditionDialog +// + +CInputFilter::CCondition* + CHotkeyOptions::CConditionDialog::s_condition = NULL; +CInputFilter::CCondition* + CHotkeyOptions::CConditionDialog::s_lastGoodCondition = NULL; +WNDPROC CHotkeyOptions::CConditionDialog::s_editWndProc = NULL; + +bool +CHotkeyOptions::CConditionDialog::doModal(HWND parent, + CInputFilter::CCondition*& condition) +{ + s_condition = condition; + if (s_condition != NULL) { + s_lastGoodCondition = s_condition->clone(); + } + else { + s_lastGoodCondition = NULL; + } + int n = DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_CONDITION), + parent, dlgProc); + + condition = s_condition; + delete s_lastGoodCondition; + s_condition = NULL; + s_lastGoodCondition = NULL; + + // user effectively cancelled if the condition is NULL + if (condition == NULL) { + n = 0; + } + + return (n == 1); +} + +void +CHotkeyOptions::CConditionDialog::doInit(HWND hwnd) +{ + // subclass edit control + HWND child = getItem(hwnd, IDC_HOTKEY_CONDITION_HOTKEY); + s_editWndProc = (WNDPROC)GetWindowLong(child, GWL_WNDPROC); + SetWindowLong(child, GWL_WNDPROC, (LONG)editProc); + + // fill control + fillHotkey(hwnd); +} + +void +CHotkeyOptions::CConditionDialog::fillHotkey(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_CONDITION_HOTKEY); + if (s_condition != NULL) { + setWindowText(child, s_condition->format().c_str()); + } + else { + setWindowText(child, ""); + } +} + +void +CHotkeyOptions::CConditionDialog::onButton(HWND hwnd, ButtonID button) +{ + delete s_condition; + s_condition = + new CInputFilter::CMouseButtonCondition(button, getModifiers()); + + fillHotkey(GetParent(hwnd)); +} + +void +CHotkeyOptions::CConditionDialog::onKey(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + // ignore key repeats + if ((lParam & 0xc0000000u) == 0x40000000u) { + return; + } + + // ignore key releases if the condition is complete and for the tab + // key (in case we were just tabbed to) + if ((lParam & 0x80000000u) != 0) { + if (isGoodCondition() || wParam == VK_TAB) { + return; + } + } + + KeyID key = kKeyNone; + KeyModifierMask mask = getModifiers(); + switch (wParam) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + break; + + case VK_TAB: + // allow tabbing out of control + if ((mask & (KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) == 0) { + HWND next = hwnd; + if ((mask & KeyModifierShift) == 0) { + do { + next = GetWindow(next, GW_HWNDNEXT); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDFIRST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + else { + do { + next = GetWindow(next, GW_HWNDPREV); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDLAST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + SetFocus(next); + return; + } + // fall through + + default: + key = CMSWindowsKeyState::getKeyID(wParam, + static_cast((lParam & 0x1ff0000u) >> 16)); + switch (key) { + case kKeyNone: + // could be a character + key = getChar(wParam, lParam); + if (key == kKeyNone) { + return; + } + break; + + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + // bogus + return; + } + break; + } + + delete s_condition; + s_condition = new CInputFilter::CKeystrokeCondition(key, mask); + + fillHotkey(GetParent(hwnd)); +} + +KeyID +CHotkeyOptions::CConditionDialog::getChar(WPARAM wParam, LPARAM lParam) +{ + BYTE keyState[256]; + UINT virtualKey = (UINT)wParam; + UINT scanCode = (UINT)((lParam & 0x0ff0000u) >> 16); + GetKeyboardState(keyState); + + // reset modifier state + keyState[VK_SHIFT] = 0; + keyState[VK_LSHIFT] = 0; + keyState[VK_RSHIFT] = 0; + keyState[VK_CONTROL] = 0; + keyState[VK_LCONTROL] = 0; + keyState[VK_RCONTROL] = 0; + keyState[VK_MENU] = 0; + keyState[VK_LMENU] = 0; + keyState[VK_RMENU] = 0; + keyState[VK_LWIN] = 0; + keyState[VK_RWIN] = 0; + + // translate virtual key to character + int n; + KeyID id; + if (CArchMiscWindows::isWindows95Family()) { + // XXX -- how do we get characters not in Latin-1? + WORD ascii; + n = ToAscii(virtualKey, scanCode, keyState, &ascii, 0); + id = static_cast(ascii & 0xffu); + } + else { + typedef int (WINAPI *ToUnicode_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags); + ToUnicode_t s_ToUnicode = NULL; + if (s_ToUnicode == NULL) { + HMODULE userModule = GetModuleHandle("user32.dll"); + s_ToUnicode = + (ToUnicode_t)GetProcAddress(userModule, "ToUnicode"); + } + + WCHAR unicode[2]; + n = s_ToUnicode(virtualKey, scanCode, keyState, + unicode, sizeof(unicode) / sizeof(unicode[0]), + 0); + id = static_cast(unicode[0]); + } + switch (n) { + case -1: + // no hot keys on dead keys + return kKeyNone; + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + } +} + +KeyModifierMask +CHotkeyOptions::CConditionDialog::getModifiers() +{ + KeyModifierMask mask = 0; + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) { + mask |= KeyModifierShift; + } + if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) { + mask |= KeyModifierControl; + } + if ((GetKeyState(VK_MENU) & 0x8000) != 0) { + mask |= KeyModifierAlt; + } + if ((GetKeyState(VK_LWIN) & 0x8000) != 0 || + (GetKeyState(VK_RWIN) & 0x8000) != 0) { + mask |= KeyModifierSuper; + } + return mask; +} + +bool +CHotkeyOptions::CConditionDialog::isGoodCondition() +{ + CInputFilter::CKeystrokeCondition* keyCondition = + dynamic_cast(s_condition); + return (keyCondition == NULL || keyCondition->getKey() != kKeyNone); +} + +BOOL CALLBACK +CHotkeyOptions::CConditionDialog::dlgProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 1); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +LRESULT CALLBACK +CHotkeyOptions::CConditionDialog::editProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_LBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonLeft); + } + else { + SetFocus(hwnd); + } + return 0; + + case WM_MBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonMiddle); + } + return 0; + + case WM_RBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonRight); + } + return 0; + + case WM_XBUTTONDOWN: + if (GetFocus() == hwnd) { + switch (HIWORD(wParam)) { + case XBUTTON1: + onButton(hwnd, kButtonExtra0 + 0); + break; + + case XBUTTON2: + onButton(hwnd, kButtonExtra0 + 1); + break; + } + } + return 0; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + onKey(hwnd, wParam, lParam); + return 0; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return 0; + + case WM_SETFOCUS: + if (s_condition != NULL) { + delete s_lastGoodCondition; + s_lastGoodCondition = s_condition->clone(); + } + break; + + case WM_KILLFOCUS: + if (!isGoodCondition()) { + delete s_condition; + if (s_lastGoodCondition != NULL) { + s_condition = s_lastGoodCondition->clone(); + } + else { + s_condition = NULL; + } + } + fillHotkey(GetParent(hwnd)); + break; + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS; + + default: + break; + } + return CallWindowProc(s_editWndProc, hwnd, message, wParam, lParam); +} + + +// +// CHotkeyOptions::CActionDialog +// + +CConfig* CHotkeyOptions::CActionDialog::s_config = NULL; +bool CHotkeyOptions::CActionDialog::s_onActivate = false; +CInputFilter::CAction* + CHotkeyOptions::CActionDialog::s_action = NULL; +CInputFilter::CAction* + CHotkeyOptions::CActionDialog::s_lastGoodAction = NULL; +std::set + CHotkeyOptions::CActionDialog::s_screens; +WNDPROC CHotkeyOptions::CActionDialog::s_editWndProc = NULL; + +bool +CHotkeyOptions::CActionDialog::doModal(HWND parent, CConfig* config, + CInputFilter::CAction*& action, bool& onActivate) +{ + s_config = config; + s_onActivate = onActivate; + s_action = action; + if (s_action != NULL) { + s_lastGoodAction = s_action->clone(); + } + else { + s_lastGoodAction = NULL; + } + + int n = DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_ACTION), + parent, dlgProc); + + onActivate = s_onActivate; + action = s_action; + delete s_lastGoodAction; + s_action = NULL; + s_lastGoodAction = NULL; + + return (n == 1); +} + +void +CHotkeyOptions::CActionDialog::doInit(HWND hwnd) +{ + // subclass edit control + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY); + s_editWndProc = (WNDPROC)GetWindowLong(child, GWL_WNDPROC); + SetWindowLong(child, GWL_WNDPROC, (LONG)editProc); + setWindowText(getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY), ""); + fillHotkey(hwnd); + + // fill screens + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST); + SendMessage(child, CB_RESETCONTENT, 0, 0); + for (CConfig::const_iterator index = s_config->begin(); + index != s_config->end(); ) { + const CString& name = *index; + ++index; + if (index != s_config->end()) { + SendMessage(child, CB_INSERTSTRING, + (WPARAM)-1, (LPARAM)name.c_str()); + } + else { + SendMessage(child, CB_ADDSTRING, 0, (LPARAM)name.c_str()); + } + } + SendMessage(child, CB_SETCURSEL, 0, 0); + + // fill directions + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_LEFT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_RIGHT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_TOP).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_BOTTOM).c_str()); + SendMessage(child, CB_SETCURSEL, 0, 0); + + // fill lock modes + child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_OFF).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_ON).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_TOGGLE).c_str()); + SendMessage(child, CB_SETCURSEL, 0, 0); + + // fill keyboard broadcast modes + child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_OFF).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_ON).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_MODE_TOGGLE).c_str()); + SendMessage(child, CB_SETCURSEL, 0, 0); + + // select when + if (s_onActivate) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_ON_ACTIVATE); + } + else { + child = getItem(hwnd, IDC_HOTKEY_ACTION_ON_DEACTIVATE); + } + setItemChecked(child, true); + + // no screens by default + s_screens.clear(); + + // select mode + child = NULL; + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_action); + CInputFilter::CLockCursorToScreenAction* lockAction = + dynamic_cast(s_action); + CInputFilter::CSwitchToScreenAction* switchToAction = + dynamic_cast(s_action); + CInputFilter::CSwitchInDirectionAction* switchInAction = + dynamic_cast(s_action); + CInputFilter::CKeyboardBroadcastAction* keyboardBroadcastAction= + dynamic_cast(s_action); + if (keyAction != NULL) { + if (dynamic_cast(s_action) != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP); + } + else if (keyAction->isOnPress()) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWN); + } + else { + child = getItem(hwnd, IDC_HOTKEY_ACTION_UP); + } + } + else if (mouseAction != NULL) { + if (dynamic_cast(s_action) != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP); + } + else if (keyAction->isOnPress()) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_DOWN); + } + else { + child = getItem(hwnd, IDC_HOTKEY_ACTION_UP); + } + } + else if (lockAction != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST); + SendMessage(child, CB_SETCURSEL, lockAction->getMode(), 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK); + } + else if (switchToAction != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST); + DWORD i = SendMessage(child, CB_FINDSTRINGEXACT, (WPARAM)-1, + (LPARAM)switchToAction->getScreen().c_str()); + if (i == CB_ERR) { + i = 0; + } + SendMessage(child, CB_SETCURSEL, i, 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO); + } + else if (switchInAction != NULL) { + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST); + SendMessage(child, CB_SETCURSEL, + switchInAction->getDirection() - kLeft, 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN); + } + else if (keyboardBroadcastAction != NULL) { + // Save the screens we're broadcasting to + s_screens = keyboardBroadcastAction->getScreens(); + + child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST); + SendMessage(child, CB_SETCURSEL, keyboardBroadcastAction->getMode(), 0); + child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST); + } + if (child != NULL) { + setItemChecked(child, true); + } + + updateControls(hwnd); +} + +void +CHotkeyOptions::CActionDialog::fillHotkey(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY); + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_action); + if (keyAction != NULL || mouseAction != NULL) { + setWindowText(child, s_action->format().c_str()); + } + else { + setWindowText(child, ""); + } + + // can only set screens in key actions + enableItem(hwnd, IDC_HOTKEY_ACTION_SCREENS, keyAction != NULL); +} + +void +CHotkeyOptions::CActionDialog::updateControls(HWND hwnd) +{ + // determine which mode we're in + UInt32 mode = 0; + if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP)) || + isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN)) || + isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) { + mode = 1; + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO))) { + mode = 2; + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN))) { + mode = 3; + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_LOCK))) { + mode = 4; + } + else if (isItemChecked(getItem(hwnd, + IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST))) { + mode = 5; + } + + // enable/disable all mode specific controls + enableItem(hwnd, IDC_HOTKEY_ACTION_HOTKEY, mode == 1); + enableItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST, mode == 2); + enableItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST, mode == 3); + enableItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST, mode == 4); + enableItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST, mode == 5); + enableItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS, mode == 5); + + // can only set screens in key actions + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + enableItem(hwnd, IDC_HOTKEY_ACTION_SCREENS, keyAction != NULL); +} + +void +CHotkeyOptions::CActionDialog::onButton(HWND hwnd, ButtonID button) +{ + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(button, getModifiers()); + delete s_action; + HWND parent = GetParent(hwnd); + if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CMouseButtonDownUpAction(info); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CMouseButtonAction(info, true); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CMouseButtonAction(info, false); + } + else { + s_action = NULL; + } + + fillHotkey(parent); +} + +void +CHotkeyOptions::CActionDialog::onKey(HWND hwnd, WPARAM wParam, LPARAM lParam) +{ + // ignore key repeats + if ((lParam & 0xc0000000u) == 0x40000000u) { + return; + } + + // ignore key releases if the action is complete and for the tab + // key (in case we were just tabbed to) + if ((lParam & 0x80000000u) != 0) { + if (isGoodAction() || wParam == VK_TAB) { + return; + } + } + + KeyID key = kKeyNone; + KeyModifierMask mask = getModifiers(); + switch (wParam) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + break; + + case VK_TAB: + // allow tabbing out of control + if ((mask & (KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) == 0) { + HWND next = hwnd; + if ((mask & KeyModifierShift) == 0) { + do { + next = GetWindow(next, GW_HWNDNEXT); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDFIRST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + else { + do { + next = GetWindow(next, GW_HWNDPREV); + if (next == NULL) { + next = GetWindow(hwnd, GW_HWNDLAST); + } + } while (next != hwnd && + (!IsWindowVisible(next) || + (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP) == 0)); + } + SetFocus(next); + return; + } + // fall through + + default: + key = CMSWindowsKeyState::getKeyID(wParam, + static_cast((lParam & 0x1ff0000u) >> 16)); + switch (key) { + case kKeyNone: + // could be a character + key = getChar(wParam, lParam); + if (key == kKeyNone) { + return; + } + break; + + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + // bogus + return; + } + break; + } + + // get old screen list + std::set screens; + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + if (keyAction == NULL) { + keyAction = + dynamic_cast(s_lastGoodAction); + } + if (keyAction != NULL) { + IKeyState::CKeyInfo::split(keyAction->getInfo()->m_screens, screens); + } + + // create new action + IPlatformScreen::CKeyInfo* info = + IKeyState::CKeyInfo::alloc(key, mask, 0, 0, screens); + delete s_action; + HWND parent = GetParent(hwnd); + if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CKeystrokeDownUpAction(info); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CKeystrokeAction(info, true); + } + else if (isItemChecked(getItem(parent, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CKeystrokeAction(info, false); + } + else { + s_action = NULL; + } + + fillHotkey(parent); +} + +void +CHotkeyOptions::CActionDialog::onLockAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_LOCK_LIST); + LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0); + if (index != CB_ERR) { + delete s_action; + s_action = new CInputFilter::CLockCursorToScreenAction( + (CInputFilter::CLockCursorToScreenAction::Mode)index); + } +} + +void +CHotkeyOptions::CActionDialog::onSwitchToAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_TO_LIST); + CString screen = getWindowText(child); + delete s_action; + s_action = new CInputFilter::CSwitchToScreenAction(screen); +} + +void +CHotkeyOptions::CActionDialog::onSwitchInAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_SWITCH_IN_LIST); + LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0); + if (index != CB_ERR) { + delete s_action; + s_action = new CInputFilter::CSwitchInDirectionAction( + (EDirection)(index + kLeft)); + } +} + +void +CHotkeyOptions::CActionDialog::onKeyboardBroadcastAction(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST); + LRESULT index = SendMessage(child, CB_GETCURSEL, 0, 0); + if (index != CB_ERR) { + delete s_action; + s_action = new CInputFilter::CKeyboardBroadcastAction( + (CInputFilter::CKeyboardBroadcastAction::Mode)index, s_screens); + } +} + +KeyID +CHotkeyOptions::CActionDialog::getChar(WPARAM wParam, LPARAM lParam) +{ + BYTE keyState[256]; + UINT virtualKey = (UINT)wParam; + UINT scanCode = (UINT)((lParam & 0x0ff0000u) >> 16); + GetKeyboardState(keyState); + + // reset modifier state + keyState[VK_SHIFT] = 0; + keyState[VK_LSHIFT] = 0; + keyState[VK_RSHIFT] = 0; + keyState[VK_CONTROL] = 0; + keyState[VK_LCONTROL] = 0; + keyState[VK_RCONTROL] = 0; + keyState[VK_MENU] = 0; + keyState[VK_LMENU] = 0; + keyState[VK_RMENU] = 0; + keyState[VK_LWIN] = 0; + keyState[VK_RWIN] = 0; + + // translate virtual key to character + int n; + KeyID id; + if (CArchMiscWindows::isWindows95Family()) { + // XXX -- how do we get characters not in Latin-1? + WORD ascii; + n = ToAscii(virtualKey, scanCode, keyState, &ascii, 0); + id = static_cast(ascii & 0xffu); + } + else { + typedef int (WINAPI *ToUnicode_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags); + ToUnicode_t s_ToUnicode = NULL; + if (s_ToUnicode == NULL) { + HMODULE userModule = GetModuleHandle("user32.dll"); + s_ToUnicode = + (ToUnicode_t)GetProcAddress(userModule, "ToUnicode"); + } + + WCHAR unicode[2]; + n = s_ToUnicode(virtualKey, scanCode, keyState, + unicode, sizeof(unicode) / sizeof(unicode[0]), + 0); + id = static_cast(unicode[0]); + } + switch (n) { + case -1: + // no hot keys on dead keys + return kKeyNone; + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + } +} + +KeyModifierMask +CHotkeyOptions::CActionDialog::getModifiers() +{ + KeyModifierMask mask = 0; + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) { + mask |= KeyModifierShift; + } + if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) { + mask |= KeyModifierControl; + } + if ((GetKeyState(VK_MENU) & 0x8000) != 0) { + mask |= KeyModifierAlt; + } + if ((GetKeyState(VK_LWIN) & 0x8000) != 0 || + (GetKeyState(VK_RWIN) & 0x8000) != 0) { + mask |= KeyModifierSuper; + } + return mask; +} + +bool +CHotkeyOptions::CActionDialog::isGoodAction() +{ + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_action); + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_action); + return (mouseAction == NULL || keyAction == NULL || + keyAction->getInfo()->m_key != kKeyNone); +} + +void +CHotkeyOptions::CActionDialog::convertAction(HWND hwnd) +{ + if (s_lastGoodAction != NULL) { + CInputFilter::CMouseButtonAction* mouseAction = + dynamic_cast(s_lastGoodAction); + CInputFilter::CKeystrokeAction* keyAction = + dynamic_cast(s_lastGoodAction); + if (mouseAction != NULL) { + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(*mouseAction->getInfo()); + delete s_action; + if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CMouseButtonDownUpAction(info); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CMouseButtonAction(info, true); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CMouseButtonAction(info, false); + } + else { + free(info); + s_action = NULL; + } + } + else if (keyAction != NULL) { + IPlatformScreen::CKeyInfo* info = + IKeyState::CKeyInfo::alloc(*keyAction->getInfo()); + delete s_action; + if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWNUP))) { + s_action = new CKeystrokeDownUpAction(info); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_DOWN))) { + s_action = new CInputFilter::CKeystrokeAction(info, true); + } + else if (isItemChecked(getItem(hwnd, IDC_HOTKEY_ACTION_UP))) { + s_action = new CInputFilter::CKeystrokeAction(info, false); + } + else { + free(info); + s_action = NULL; + } + } + } +} + +bool +CHotkeyOptions::CActionDialog::isDownUpAction() +{ + return (dynamic_cast(s_action) != NULL || + dynamic_cast(s_action) != NULL); +} + +BOOL CALLBACK +CHotkeyOptions::CActionDialog::dlgProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (isDownUpAction()) { + s_onActivate = true; + } + EndDialog(hwnd, 1); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_HOTKEY_ACTION_ON_ACTIVATE: + s_onActivate = true; + return TRUE; + + case IDC_HOTKEY_ACTION_ON_DEACTIVATE: + s_onActivate = false; + return TRUE; + + case IDC_HOTKEY_ACTION_DOWNUP: + case IDC_HOTKEY_ACTION_DOWN: + case IDC_HOTKEY_ACTION_UP: + convertAction(hwnd); + fillHotkey(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_LOCK: + onLockAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_SWITCH_TO: + onSwitchToAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_SWITCH_IN: + onSwitchInAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST: + onKeyboardBroadcastAction(hwnd); + updateControls(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_LOCK_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onLockAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_SWITCH_TO_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onSwitchToAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_SWITCH_IN_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onSwitchInAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + onKeyboardBroadcastAction(hwnd); + return TRUE; + } + break; + + case IDC_HOTKEY_ACTION_SCREENS: + CScreensDialog::doModal(hwnd, s_config, + dynamic_cast(s_action)); + fillHotkey(hwnd); + return TRUE; + + case IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS: { + // convert screens to form that CScreenDialog::doModal() wants + IPlatformScreen::CKeyInfo* tmpInfo = + IPlatformScreen::CKeyInfo::alloc(0, 0, 0, 1, s_screens); + CInputFilter::CKeystrokeAction tmpAction(tmpInfo, true); + + // get the screens + CScreensDialog::doModal(hwnd, s_config, &tmpAction); + + // convert screens back + IPlatformScreen::CKeyInfo::split( + tmpAction.getInfo()->m_screens, s_screens); + + // update + onKeyboardBroadcastAction(hwnd); + return TRUE; + } + } + break; + + default: + break; + } + + return FALSE; +} + +LRESULT CALLBACK +CHotkeyOptions::CActionDialog::editProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_LBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonLeft); + } + else { + SetFocus(hwnd); + } + return 0; + + case WM_MBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonMiddle); + } + return 0; + + case WM_RBUTTONDOWN: + if (GetFocus() == hwnd) { + onButton(hwnd, kButtonRight); + } + return 0; + + case WM_XBUTTONDOWN: + if (GetFocus() == hwnd) { + switch (HIWORD(wParam)) { + case XBUTTON1: + onButton(hwnd, kButtonExtra0 + 0); + break; + + case XBUTTON2: + onButton(hwnd, kButtonExtra0 + 1); + break; + } + } + return 0; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + onKey(hwnd, wParam, lParam); + return 0; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return 0; + + case WM_SETFOCUS: + if (s_action != NULL) { + delete s_lastGoodAction; + s_lastGoodAction = s_action->clone(); + } + break; + + case WM_KILLFOCUS: + if (!isGoodAction()) { + delete s_action; + if (s_lastGoodAction != NULL) { + s_action = s_lastGoodAction->clone(); + } + else { + s_action = NULL; + } + } + else if (s_action != NULL) { + delete s_lastGoodAction; + s_lastGoodAction = s_action->clone(); + } + fillHotkey(GetParent(hwnd)); + break; + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS; + + default: + break; + } + return CallWindowProc(s_editWndProc, hwnd, message, wParam, lParam); +} + + +// +// CHotkeyOptions::CScreensDialog +// + +CConfig* CHotkeyOptions::CScreensDialog::s_config = NULL; +CInputFilter::CKeystrokeAction* + CHotkeyOptions::CScreensDialog::s_action = NULL; +CHotkeyOptions::CScreensDialog::CScreens + CHotkeyOptions::CScreensDialog::s_nonTargets; +CHotkeyOptions::CScreensDialog::CScreens + CHotkeyOptions::CScreensDialog::s_targets; +CString CHotkeyOptions::CScreensDialog::s_allScreens; + +void +CHotkeyOptions::CScreensDialog::doModal(HWND parent, CConfig* config, + CInputFilter::CKeystrokeAction* action) +{ + s_allScreens = getString(IDS_ALL_SCREENS); + s_config = config; + s_action = action; + DialogBox(s_instance, MAKEINTRESOURCE(IDD_HOTKEY_SCREENS), + parent, dlgProc); + s_config = NULL; + s_action = NULL; +} + +void +CHotkeyOptions::CScreensDialog::doInit(HWND hwnd) +{ + s_nonTargets.clear(); + s_targets.clear(); + + // get screens from config + s_nonTargets.insert("*"); + for (CConfig::const_iterator i = s_config->begin(); + i != s_config->end(); ++i) { + s_nonTargets.insert(*i); + } + + // get screens in action + IKeyState::CKeyInfo::split(s_action->getInfo()->m_screens, s_targets); + + // remove screens in action from screens in config + for (CScreens::const_iterator i = s_targets.begin(); + i != s_targets.end(); ++i) { + s_nonTargets.erase(*i); + } + + // fill dialog + fillScreens(hwnd); + updateControls(hwnd); +} + +void +CHotkeyOptions::CScreensDialog::doFini(HWND) +{ + // put screens into action + const IPlatformScreen::CKeyInfo* oldInfo = s_action->getInfo(); + IPlatformScreen::CKeyInfo* newInfo = + IKeyState::CKeyInfo::alloc(oldInfo->m_key, + oldInfo->m_mask, 0, 0, s_targets); + s_action->adoptInfo(newInfo); +} + +void +CHotkeyOptions::CScreensDialog::fillScreens(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_SCREENS_SRC); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CScreens::const_iterator i = s_nonTargets.begin(); + i != s_nonTargets.end(); ++i) { + CString name = *i; + if (name == "*") { + name = s_allScreens; + } + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + } + + child = getItem(hwnd, IDC_HOTKEY_SCREENS_DST); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CScreens::const_iterator i = s_targets.begin(); + i != s_targets.end(); ++i) { + CString name = *i; + if (name == "*") { + name = s_allScreens; + } + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + } + if (s_targets.empty()) { + // if no targets then add a special item so the user knows + // what'll happen + CString activeScreenLabel = getString(IDS_ACTIVE_SCREEN); + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)activeScreenLabel.c_str()); + } +} + +void +CHotkeyOptions::CScreensDialog::updateControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_HOTKEY_SCREENS_SRC); + bool canAdd = (SendMessage(child, LB_GETSELCOUNT, 0, 0) != 0); + child = getItem(hwnd, IDC_HOTKEY_SCREENS_DST); + bool canRemove = (!s_targets.empty() && + (SendMessage(child, LB_GETSELCOUNT, 0, 0) != 0)); + + enableItem(hwnd, IDC_HOTKEY_SCREENS_ADD, canAdd); + enableItem(hwnd, IDC_HOTKEY_SCREENS_REMOVE, canRemove); +} + +void +CHotkeyOptions::CScreensDialog::add(HWND hwnd) +{ + CScreens selected; + getSelected(hwnd, IDC_HOTKEY_SCREENS_SRC, s_nonTargets, selected); + for (CScreens::const_iterator i = selected.begin(); + i != selected.end(); ++i) { + s_targets.insert(*i); + s_nonTargets.erase(*i); + } + fillScreens(hwnd); + updateControls(hwnd); +} + +void +CHotkeyOptions::CScreensDialog::remove(HWND hwnd) +{ + CScreens selected; + getSelected(hwnd, IDC_HOTKEY_SCREENS_DST, s_targets, selected); + for (CScreens::const_iterator i = selected.begin(); + i != selected.end(); ++i) { + s_nonTargets.insert(*i); + s_targets.erase(*i); + } + fillScreens(hwnd); + updateControls(hwnd); +} + +void +CHotkeyOptions::CScreensDialog::getSelected(HWND hwnd, UINT id, + const CScreens& inScreens, CScreens& outScreens) +{ + // get the selected item indices + HWND child = getItem(hwnd, id); + UInt32 n = (UInt32)SendMessage(child, LB_GETSELCOUNT, 0, 0); + int* index = new int[n]; + SendMessage(child, LB_GETSELITEMS, (WPARAM)n, (LPARAM)index); + + // get the items in a vector + std::vector tmpList; + for (CScreens::const_iterator i = inScreens.begin(); + i != inScreens.end(); ++i) { + tmpList.push_back(*i); + } + + // get selected items into the output set + outScreens.clear(); + for (UInt32 i = 0; i < n; ++i) { + outScreens.insert(tmpList[index[i]]); + } + + // clean up + delete[] index; +} + +BOOL CALLBACK +CHotkeyOptions::CScreensDialog::dlgProc(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_INITDIALOG: + doInit(hwnd); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + doFini(hwnd); + EndDialog(hwnd, 0); + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_HOTKEY_SCREENS_ADD: + add(hwnd); + return TRUE; + + case IDC_HOTKEY_SCREENS_REMOVE: + remove(hwnd); + return TRUE; + + case IDC_HOTKEY_SCREENS_SRC: + case IDC_HOTKEY_SCREENS_DST: + switch (HIWORD(wParam)) { + case LBN_SELCANCEL: + case LBN_SELCHANGE: + updateControls(hwnd); + return TRUE; + } + break; + } + break; + + case WM_CTLCOLORLISTBOX: + if (s_targets.empty() && + (HWND)lParam == getItem(hwnd, IDC_HOTKEY_SCREENS_DST)) { + // override colors + HDC dc = (HDC)wParam; + SetTextColor(dc, GetSysColor(COLOR_GRAYTEXT)); + return (BOOL)GetSysColorBrush(COLOR_WINDOW); + } + break; + + default: + break; + } + + return FALSE; +} diff --git a/cmd/launcher/CHotkeyOptions.h b/cmd/launcher/CHotkeyOptions.h new file mode 100644 index 00000000..dec5367a --- /dev/null +++ b/cmd/launcher/CHotkeyOptions.h @@ -0,0 +1,227 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CHOTKEYOPTIONS_H +#define CHOTKEYOPTIONS_H + +#include "CString.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CInputFilter.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +class CConfig; + +//! Hotkey options dialog for Microsoft Windows launcher +class CHotkeyOptions { +public: + CHotkeyOptions(HWND parent, CConfig*); + ~CHotkeyOptions(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + //@} + +private: + void doInit(HWND hwnd); + + void fillHotkeys(HWND hwnd, UInt32 select = (UInt32)-1); + void updateHotkeysControls(HWND hwnd); + + void addHotkey(HWND hwnd); + void removeHotkey(HWND hwnd); + void editHotkey(HWND hwnd); + + void fillActions(HWND hwnd, UInt32 select = (UInt32)-1); + void updateActionsControls(HWND hwnd); + + void addAction(HWND hwnd); + void removeAction(HWND hwnd); + void editAction(HWND hwnd); + + bool editCondition(HWND hwnd, CInputFilter::CCondition*&); + bool editAction(HWND hwnd, CInputFilter::CAction*&, + bool& onActivate); + + void openRule(HWND hwnd); + void closeRule(HWND hwnd); + UInt32 findMatchingAction( + const CInputFilter::CKeystrokeAction*) const; + UInt32 findMatchingAction( + const CInputFilter::CMouseButtonAction*) const; + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + + // special actions we use to combine matching down/up actions into a + // single action for the convenience of the user. + class CKeystrokeDownUpAction : public CInputFilter::CKeystrokeAction { + public: + CKeystrokeDownUpAction(IPlatformScreen::CKeyInfo* adoptedInfo) : + CInputFilter::CKeystrokeAction(adoptedInfo, true) { } + + // CAction overrides + virtual CInputFilter::CAction* clone() const + { + IKeyState::CKeyInfo* info = IKeyState::CKeyInfo::alloc(*getInfo()); + return new CKeystrokeDownUpAction(info); + } + + protected: + // CKeystrokeAction overrides + virtual const char* formatName() const { return "keystroke"; } + }; + class CMouseButtonDownUpAction : public CInputFilter::CMouseButtonAction { + public: + CMouseButtonDownUpAction(IPrimaryScreen::CButtonInfo* adoptedInfo) : + CInputFilter::CMouseButtonAction(adoptedInfo, true) { } + + // CAction overrides + virtual CInputFilter::CAction* clone() const + { + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(*getInfo()); + return new CMouseButtonDownUpAction(info); + } + + protected: + // CMouseButtonAction overrides + virtual const char* formatName() const { return "mousebutton"; } + }; + + class CConditionDialog { + public: + static bool doModal(HWND parent, CInputFilter::CCondition*&); + + private: + static void doInit(HWND hwnd); + static void fillHotkey(HWND hwnd); + + static void onButton(HWND hwnd, ButtonID button); + static void onKey(HWND hwnd, WPARAM wParam, LPARAM lParam); + static KeyID getChar(WPARAM wParam, LPARAM lParam); + static KeyModifierMask + getModifiers(); + + static bool isGoodCondition(); + + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK editProc(HWND hwnd, UINT, WPARAM, LPARAM); + + private: + static CInputFilter::CCondition* + s_condition; + static CInputFilter::CCondition* + s_lastGoodCondition; + static WNDPROC s_editWndProc; + }; + + class CActionDialog { + public: + static bool doModal(HWND parent, CConfig* config, + CInputFilter::CAction*&, bool& onActivate); + + private: + static void doInit(HWND hwnd); + static void fillHotkey(HWND hwnd); + static void updateControls(HWND hwnd); + + static void onButton(HWND hwnd, ButtonID button); + static void onKey(HWND hwnd, WPARAM wParam, LPARAM lParam); + static void onLockAction(HWND hwnd); + static void onSwitchToAction(HWND hwnd); + static void onSwitchInAction(HWND hwnd); + static void onKeyboardBroadcastAction(HWND hwnd); + + static KeyID getChar(WPARAM wParam, LPARAM lParam); + static KeyModifierMask + getModifiers(); + + static bool isGoodAction(); + static void convertAction(HWND hwnd); + + static bool isDownUpAction(); + + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK editProc(HWND hwnd, UINT, WPARAM, LPARAM); + + private: + static CConfig* s_config; + static bool s_onActivate; + static CInputFilter::CAction* + s_action; + static CInputFilter::CAction* + s_lastGoodAction; + static std::set s_screens; + static WNDPROC s_editWndProc; + }; + +// public to allow CActionDialog to use it +public: + class CScreensDialog { + public: + static void doModal(HWND parent, CConfig* config, + CInputFilter::CKeystrokeAction*); + + // public due to compiler brokenness + typedef std::set CScreens; + + private: + + static void doInit(HWND hwnd); + static void doFini(HWND hwnd); + static void fillScreens(HWND hwnd); + static void updateControls(HWND hwnd); + + static void add(HWND hwnd); + static void remove(HWND hwnd); + + static void getSelected(HWND hwnd, UINT id, + const CScreens& inScreens, CScreens& outScreens); + + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + + private: + static CConfig* s_config; + static CInputFilter::CKeystrokeAction* s_action; + static CScreens s_nonTargets; + static CScreens s_targets; + static CString s_allScreens; + }; + +private: + static CHotkeyOptions* s_singleton; + + HWND m_parent; + CConfig* m_config; + CInputFilter* m_inputFilter; + CInputFilter::CRule m_activeRule; + UInt32 m_activeRuleIndex; +}; + +#endif diff --git a/cmd/launcher/CInfo.cpp b/cmd/launcher/CInfo.cpp new file mode 100644 index 00000000..669da0e2 --- /dev/null +++ b/cmd/launcher/CInfo.cpp @@ -0,0 +1,111 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "Version.h" +#include "CArch.h" +#include "CInfo.h" +#include "LaunchUtil.h" +#include "resource.h" + +// +// CInfo +// + +CInfo* CInfo::s_singleton = NULL; + +CInfo::CInfo(HWND parent) : + m_parent(parent) +{ + assert(s_singleton == NULL); + s_singleton = this; +} + +CInfo::~CInfo() +{ + s_singleton = NULL; +} + +void +CInfo::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_INFO), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CInfo::init(HWND hwnd) +{ + // collect info + CString version = + CStringUtil::format(getString(IDS_TITLE).c_str(), VERSION); + CString hostname = ARCH->getHostName(); + CString address = ARCH->addrToString(ARCH->nameToAddr(hostname)); + CString userConfig = ARCH->getUserDirectory(); + if (!userConfig.empty()) { + userConfig = ARCH->concatPath(userConfig, CONFIG_NAME); + } + CString sysConfig = ARCH->getSystemDirectory(); + if (!sysConfig.empty()) { + sysConfig = ARCH->concatPath(sysConfig, CONFIG_NAME); + } + + // set info + HWND child; + child = getItem(hwnd, IDC_INFO_VERSION); + setWindowText(child, version); + child = getItem(hwnd, IDC_INFO_HOSTNAME); + setWindowText(child, hostname); + child = getItem(hwnd, IDC_INFO_IP_ADDRESS); + setWindowText(child, address); + child = getItem(hwnd, IDC_INFO_USER_CONFIG); + setWindowText(child, userConfig); + child = getItem(hwnd, IDC_INFO_SYS_CONFIG); + setWindowText(child, sysConfig); + + // focus on okay button + SetFocus(getItem(hwnd, IDOK)); +} + +BOOL +CInfo::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return FALSE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + } + break; + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CInfo::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} diff --git a/cmd/launcher/CInfo.h b/cmd/launcher/CInfo.h new file mode 100644 index 00000000..2d85ec7d --- /dev/null +++ b/cmd/launcher/CInfo.h @@ -0,0 +1,57 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CINFO_H +#define CINFO_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +//! Info dialog for Microsoft Windows launcher +class CInfo { +public: + CInfo(HWND parent); + ~CInfo(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + //@} + +private: + void init(HWND hwnd); + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CInfo* s_singleton; + + HWND m_parent; +}; + +#endif diff --git a/cmd/launcher/CScreensLinks.cpp b/cmd/launcher/CScreensLinks.cpp new file mode 100644 index 00000000..c7e58a04 --- /dev/null +++ b/cmd/launcher/CScreensLinks.cpp @@ -0,0 +1,855 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CScreensLinks.h" +#include "CAddScreen.h" +#include "LaunchUtil.h" +#include "resource.h" + +// +// CScreensLinks +// + +CScreensLinks* CScreensLinks::s_singleton = NULL; + +CScreensLinks::CScreensLinks(HWND parent, CConfig* config) : + m_parent(parent), + m_mainConfig(config), + m_config(&m_scratchConfig) +{ + assert(s_singleton == NULL); + s_singleton = this; + + // get formatting strings + m_linkFormat = getString(IDS_LINK_FORMAT); + m_intervalFormat = getString(IDS_LINK_INTERVAL_FORMAT); + m_newLinkLabel = getString(IDS_NEW_LINK); + m_sideLabel[kLeft - kFirstDirection] = getString(IDS_SIDE_LEFT); + m_sideLabel[kRight - kFirstDirection] = getString(IDS_SIDE_RIGHT); + m_sideLabel[kTop - kFirstDirection] = getString(IDS_SIDE_TOP); + m_sideLabel[kBottom - kFirstDirection] = getString(IDS_SIDE_BOTTOM); + + // GDI objects + m_redPen = CreatePen(PS_INSIDEFRAME, 1, RGB(255, 0, 0)); +} + +CScreensLinks::~CScreensLinks() +{ + DeleteObject(m_redPen); + s_singleton = NULL; +} + +void +CScreensLinks::doModal() +{ + // do dialog + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_SCREENS_LINKS), + m_parent, (DLGPROC)dlgProc, (LPARAM)this); +} + +void +CScreensLinks::init(HWND hwnd) +{ + // get initial config + m_scratchConfig = *m_mainConfig; + + // fill side list box (in EDirection order) + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SIDE); + SendMessage(child, CB_ADDSTRING, 0, (LPARAM)TEXT("---")); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_LEFT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_RIGHT).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_TOP).c_str()); + SendMessage(child, CB_ADDSTRING, 0, + (LPARAM)getString(IDS_EDGE_BOTTOM).c_str()); + + // create error boxes + m_srcSideError = createErrorBox(hwnd); + m_srcScreenError = createErrorBox(hwnd); + m_dstScreenError = createErrorBox(hwnd); + resizeErrorBoxes(); + + m_selectedLink = -1; + m_editedLink = CEdgeLink(); + m_edgeLinks.clear(); + updateScreens(hwnd, ""); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); +} + +bool +CScreensLinks::save(HWND /*hwnd*/) +{ + *m_mainConfig = m_scratchConfig; + return true; +} + +BOOL +CScreensLinks::doDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_INITDIALOG: + init(hwnd); + return TRUE; + + case WM_SIZE: + resizeErrorBoxes(); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + SetFocus(getItem(hwnd, IDOK)); + if (save(hwnd)) { + EndDialog(hwnd, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + + case IDC_SCREENS_SCREENS: + switch (HIWORD(wParam)) { + case LBN_DBLCLK: + editScreen(hwnd); + return TRUE; + + case LBN_SELCHANGE: + updateScreensControls(hwnd); + updateLinkView(hwnd); + return TRUE; + + case LBN_SELCANCEL: + updateScreensControls(hwnd); + updateLinkView(hwnd); + return TRUE; + } + break; + + case IDC_SCREENS_ADD_SCREEN: + addScreen(hwnd); + return TRUE; + + case IDC_SCREENS_REMOVE_SCREEN: + removeScreen(hwnd); + return TRUE; + + case IDC_SCREENS_EDIT_SCREEN: + editScreen(hwnd); + return TRUE; + + case IDC_SCREENS_LINKS: + switch (HIWORD(wParam)) { + case LBN_SELCHANGE: + editLink(hwnd); + return TRUE; + + case LBN_SELCANCEL: + editLink(hwnd); + return TRUE; + } + break; + + case IDC_SCREENS_ADD_LINK: + addLink(hwnd); + return TRUE; + + case IDC_SCREENS_REMOVE_LINK: + removeLink(hwnd); + return TRUE; + + case IDC_SCREENS_SRC_SIDE: + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: + changeSrcSide(hwnd); + break; + } + break; + + case IDC_SCREENS_SRC_SCREEN: + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: + changeSrcScreen(hwnd); + break; + } + break; + + case IDC_SCREENS_DST_SCREEN: + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: + changeDstScreen(hwnd); + break; + } + break; + + case IDC_SCREENS_SRC_START: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalStart(hwnd, LOWORD(wParam), + m_editedLink.m_srcInterval); + break; + } + break; + + case IDC_SCREENS_SRC_END: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalEnd(hwnd, LOWORD(wParam), + m_editedLink.m_srcInterval); + break; + } + break; + + case IDC_SCREENS_DST_START: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalStart(hwnd, LOWORD(wParam), + m_editedLink.m_dstInterval); + break; + } + break; + + case IDC_SCREENS_DST_END: + switch (HIWORD(wParam)) { + case EN_KILLFOCUS: + changeIntervalEnd(hwnd, LOWORD(wParam), + m_editedLink.m_dstInterval); + break; + } + break; + } + + break; + + case WM_CTLCOLORSTATIC: + switch (GetDlgCtrlID((HWND)lParam)) { + case IDC_SCREENS_OVERLAP_ERROR: + SetBkColor((HDC)wParam, GetSysColor(COLOR_3DFACE)); + SetTextColor((HDC)wParam, RGB(255, 0, 0)); + return (BOOL)GetSysColorBrush(COLOR_3DFACE); + } + break; + + // error outlines + case WM_DRAWITEM: { + DRAWITEMSTRUCT* di = (DRAWITEMSTRUCT*)lParam; + if (di->CtlType == ODT_STATIC) { + HGDIOBJ oldPen = SelectObject(di->hDC, m_redPen); + HGDIOBJ oldBrush = SelectObject(di->hDC, + GetStockObject(NULL_BRUSH)); + Rectangle(di->hDC, di->rcItem.left, di->rcItem.top, + di->rcItem.right, di->rcItem.bottom); + SelectObject(di->hDC, oldPen); + SelectObject(di->hDC, oldBrush); + return TRUE; + } + break; + } + + default: + break; + } + + return FALSE; +} + +BOOL CALLBACK +CScreensLinks::dlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + return s_singleton->doDlgProc(hwnd, message, wParam, lParam); +} + +CString +CScreensLinks::getSelectedScreen(HWND hwnd) const +{ + HWND child = getItem(hwnd, IDC_SCREENS_SCREENS); + LRESULT index = SendMessage(child, LB_GETCURSEL, 0, 0); + if (index == LB_ERR) { + return CString(); + } + + LRESULT size = SendMessage(child, LB_GETTEXTLEN, index, 0); + char* buffer = new char[size + 1]; + SendMessage(child, LB_GETTEXT, index, (LPARAM)buffer); + buffer[size] = '\0'; + CString result(buffer); + delete[] buffer; + return result; +} + +void +CScreensLinks::addScreen(HWND hwnd) +{ + CAddScreen dialog(hwnd, m_config, ""); + if (dialog.doModal()) { + updateScreens(hwnd, dialog.getName()); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::editScreen(HWND hwnd) +{ + CString oldName = getSelectedScreen(hwnd); + CAddScreen dialog(hwnd, m_config, oldName); + if (dialog.doModal()) { + CString newName = dialog.getName(); + + // rename screens in the edge list + if (newName != oldName) { + for (size_t i = 0; i < m_edgeLinks.size(); ++i) { + m_edgeLinks[i].rename(oldName, newName); + } + m_editedLink.rename(oldName, newName); + } + + updateScreens(hwnd, newName); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::removeScreen(HWND hwnd) +{ + // remove screen from config (this also removes aliases) + m_config->removeScreen(getSelectedScreen(hwnd)); + + // update dialog + updateScreens(hwnd, ""); + updateScreensControls(hwnd); + updateLinks(hwnd); + updateLinksControls(hwnd); +} + +void +CScreensLinks::addLink(HWND hwnd) +{ + if (m_editedLink.connect(m_config)) { + m_editedLink = CEdgeLink(); + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::editLink(HWND hwnd) +{ + // get selection + HWND child = getItem(hwnd, IDC_SCREENS_LINKS); + DWORD i = SendMessage(child, LB_GETCURSEL, 0, 0); + if (i != LB_ERR && i != (DWORD)m_edgeLinks.size()) { + // existing link + m_selectedLink = (SInt32)SendMessage(child, LB_GETITEMDATA, i, 0); + m_editedLink = m_edgeLinks[m_selectedLink]; + } + else { + // new link + m_selectedLink = -1; + m_editedLink = CEdgeLink(); + } + updateLinksControls(hwnd); +} + +void +CScreensLinks::removeLink(HWND hwnd) +{ + if (m_editedLink.disconnect(m_config)) { + updateLinks(hwnd); + updateLinksControls(hwnd); + } +} + +void +CScreensLinks::updateScreens(HWND hwnd, const CString& selectName) +{ + HWND child; + + // set screen list + child = getItem(hwnd, IDC_SCREENS_SCREENS); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CConfig::const_iterator index = m_config->begin(); + index != m_config->end(); ) { + const CString& name = *index; + ++index; + if (index != m_config->end()) { + SendMessage(child, LB_INSERTSTRING, + (WPARAM)-1, (LPARAM)name.c_str()); + } + else { + SendMessage(child, LB_ADDSTRING, 0, (LPARAM)name.c_str()); + } + } + + // find the named screen + if (!selectName.empty()) { + DWORD i = SendMessage(child, LB_FINDSTRINGEXACT, + (UINT)-1, (LPARAM)selectName.c_str()); + if (i != LB_ERR) { + SendMessage(child, LB_SETSEL, TRUE, i); + } + } +} + +void +CScreensLinks::updateScreensControls(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_SCREENS); + bool screenSelected = (SendMessage(child, LB_GETCURSEL, 0, 0) != LB_ERR); + + enableItem(hwnd, IDC_SCREENS_ADD_SCREEN, TRUE); + enableItem(hwnd, IDC_SCREENS_EDIT_SCREEN, screenSelected); + enableItem(hwnd, IDC_SCREENS_REMOVE_SCREEN, screenSelected); +} + +void +CScreensLinks::updateLinks(HWND hwnd) +{ + HWND links = getItem(hwnd, IDC_SCREENS_LINKS); + HWND srcScreens = getItem(hwnd, IDC_SCREENS_SRC_SCREEN); + HWND dstScreens = getItem(hwnd, IDC_SCREENS_DST_SCREEN); + + // get old selection + CEdgeLink oldLink; + if (m_selectedLink != -1) { + oldLink = m_edgeLinks[m_selectedLink]; + } + + // clear links and screens + SendMessage(links, LB_RESETCONTENT, 0, 0); + SendMessage(srcScreens, CB_RESETCONTENT, 0, 0); + SendMessage(dstScreens, CB_RESETCONTENT, 0, 0); + m_edgeLinks.clear(); + + // add "no screen" items + SendMessage(srcScreens, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)TEXT("----")); + SendMessage(dstScreens, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)TEXT("----")); + + // add links and screens + for (CConfig::const_iterator i = m_config->begin(); + i != m_config->end(); ++i) { + const CString& name = *i; + + // add screen + SendMessage(srcScreens, CB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + SendMessage(dstScreens, CB_INSERTSTRING, (WPARAM)-1, + (LPARAM)name.c_str()); + + // add links for screen + for (CConfig::link_const_iterator j = m_config->beginNeighbor(name), + n = m_config->endNeighbor(name); + j != n; ++j) { + DWORD k = m_edgeLinks.size(); + m_edgeLinks.push_back(CEdgeLink(name, *j)); + SendMessage(links, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)formatLink(m_edgeLinks.back()).c_str()); + SendMessage(links, LB_SETITEMDATA, (WPARAM)k, (LPARAM)k); + } + } + + // add "new link" item to sort + SendMessage(links, LB_ADDSTRING, 0, (LPARAM)m_newLinkLabel.c_str()); + + // remove the "new link" item then insert it on the end + DWORD i = SendMessage(links, LB_FINDSTRINGEXACT, + (UINT)-1, (LPARAM)m_newLinkLabel.c_str()); + if (i != LB_ERR) { + SendMessage(links, LB_DELETESTRING, i, 0); + } + SendMessage(links, LB_INSERTSTRING, (WPARAM)-1, + (LPARAM)getString(IDS_NEW_LINK).c_str()); + SendMessage(links, LB_SETITEMDATA, (WPARAM)m_edgeLinks.size(), + (LPARAM)-1); + + // select the same link as before + SendMessage(links, LB_SETCURSEL, (WPARAM)m_edgeLinks.size(), 0); + if (m_selectedLink != -1) { + m_selectedLink = -1; + for (size_t j = 0; j < m_edgeLinks.size(); ++j) { + if (m_edgeLinks[j] == oldLink) { + // found matching link + m_selectedLink = j; + for (size_t k = 0; k < m_edgeLinks.size(); ++k) { + if (SendMessage(links, LB_GETITEMDATA, k, 0) == (int)j) { + SendMessage(links, LB_SETCURSEL, k, 0); + break; + } + } + break; + } + } + + // if we can't find the link anymore then reset edited link + if (m_selectedLink == -1) { + m_editedLink = CEdgeLink(); + } + } +} + +void +CScreensLinks::updateLinksControls(HWND hwnd) +{ + // get selection. select "new link" if nothing is selected. + HWND child = getItem(hwnd, IDC_SCREENS_LINKS); + if (m_selectedLink == -1) { + SendMessage(child, LB_SETCURSEL, m_edgeLinks.size(), 0); + } + + // enable/disable remove button + enableItem(hwnd, IDC_SCREENS_REMOVE_LINK, m_selectedLink != -1); + + // fill link entry controls from m_editedLink + updateLinkEditControls(hwnd, m_editedLink); + updateLinkValid(hwnd, m_editedLink); + updateLinkView(hwnd); +} + +void +CScreensLinks::changeSrcSide(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SIDE); + m_editedLink.m_srcSide = (EDirection)SendMessage(child, CB_GETCURSEL, 0, 0); + updateLink(hwnd); +} + +void +CScreensLinks::changeSrcScreen(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SCREEN); + m_editedLink.m_srcName = getWindowText(child); + updateLink(hwnd); +} + +void +CScreensLinks::changeDstScreen(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_SCREENS_DST_SCREEN); + m_editedLink.m_dstName = getWindowText(child); + updateLink(hwnd); +} + +void +CScreensLinks::changeIntervalStart(HWND hwnd, int id, CConfig::CInterval& i) +{ + int x = (int)GetDlgItemInt(hwnd, id, NULL, FALSE); + if (x < 0) { + x = 0; + } + else if (x > 99) { + x = 99; + } + + i.first = 0.01f * (float)x; + if (i.first >= i.second) { + i.second = 0.01f * (float)(x + 1); + } + + updateLinkIntervalControls(hwnd, m_editedLink); + updateLink(hwnd); +} + +void +CScreensLinks::changeIntervalEnd(HWND hwnd, int id, CConfig::CInterval& i) +{ + int x = (int)GetDlgItemInt(hwnd, id, NULL, FALSE); + if (x < 1) { + x = 1; + } + else if (x > 100) { + x = 100; + } + + i.second = 0.01f * (float)x; + if (i.first >= i.second) { + i.first = 0.01f * (float)(x - 1); + } + + updateLinkIntervalControls(hwnd, m_editedLink); + updateLink(hwnd); +} + +void +CScreensLinks::selectScreen(HWND hwnd, int id, const CString& name) +{ + HWND child = getItem(hwnd, id); + DWORD i = SendMessage(child, CB_FINDSTRINGEXACT, (WPARAM)-1, + (LPARAM)name.c_str()); + if (i == CB_ERR) { + // no match, select no screen + SendMessage(child, CB_SETCURSEL, 0, 0); + } + else { + SendMessage(child, CB_SETCURSEL, i, 0); + } +} + +void +CScreensLinks::updateLinkEditControls(HWND hwnd, const CEdgeLink& link) +{ + // fill link entry controls from link + HWND child = getItem(hwnd, IDC_SCREENS_SRC_SIDE); + SendMessage(child, CB_SETCURSEL, link.m_srcSide, 0); + selectScreen(hwnd, IDC_SCREENS_SRC_SCREEN, link.m_srcName); + selectScreen(hwnd, IDC_SCREENS_DST_SCREEN, link.m_dstName); + updateLinkIntervalControls(hwnd, link); +} + +void +CScreensLinks::updateLinkIntervalControls(HWND hwnd, const CEdgeLink& link) +{ + HWND child; + + // src interval + child = getItem(hwnd, IDC_SCREENS_SRC_START); + setWindowText(child, formatIntervalValue(link.m_srcInterval.first)); + child = getItem(hwnd, IDC_SCREENS_SRC_END); + setWindowText(child, formatIntervalValue(link.m_srcInterval.second)); + + // dst interval + child = getItem(hwnd, IDC_SCREENS_DST_START); + setWindowText(child, formatIntervalValue(link.m_dstInterval.first)); + child = getItem(hwnd, IDC_SCREENS_DST_END); + setWindowText(child, formatIntervalValue(link.m_dstInterval.second)); +} + +void +CScreensLinks::updateLink(HWND hwnd) +{ + updateLinkValid(hwnd, m_editedLink); + + // update link in config + if (m_selectedLink != -1 && m_editedLinkIsValid) { + // editing an existing link and entry is valid + if (m_edgeLinks[m_selectedLink].disconnect(m_config)) { + // successfully removed old link + if (!m_editedLink.connect(m_config)) { + // couldn't set new link so restore old link + m_edgeLinks[m_selectedLink].connect(m_config); + } + else { + m_edgeLinks[m_selectedLink] = m_editedLink; + updateLinks(hwnd); + updateLinkEditControls(hwnd, m_editedLink); + } + } + } + + updateLinkView(hwnd); +} + +void +CScreensLinks::updateLinkValid(HWND hwnd, const CEdgeLink& link) +{ + m_editedLinkIsValid = true; + + // check source side and screen + if (link.m_srcSide == kNoDirection) { + m_editedLinkIsValid = false; + ShowWindow(m_srcSideError, SW_SHOWNA); + } + else { + ShowWindow(m_srcSideError, SW_HIDE); + } + if (!m_config->isCanonicalName(link.m_srcName)) { + m_editedLinkIsValid = false; + ShowWindow(m_srcScreenError, SW_SHOWNA); + } + else { + ShowWindow(m_srcScreenError, SW_HIDE); + } + + // check for overlap. if editing a link we must remove it, then + // check for overlap and restore the old link. + bool overlap = false; + if (m_editedLinkIsValid) { + if (m_selectedLink == -1) { + if (link.overlaps(m_config)) { + m_editedLinkIsValid = false; + overlap = true; + } + } + else { + if (m_edgeLinks[m_selectedLink].disconnect(m_config)) { + overlap = link.overlaps(m_config); + m_edgeLinks[m_selectedLink].connect(m_config); + if (overlap) { + m_editedLinkIsValid = false; + } + } + } + } + ShowWindow(getItem(hwnd, IDC_SCREENS_OVERLAP_ERROR), + overlap ? SW_SHOWNA : SW_HIDE); + + // check dst screen + if (!m_config->isCanonicalName(link.m_dstName)) { + m_editedLinkIsValid = false; + ShowWindow(m_dstScreenError, SW_SHOWNA); + } + else { + ShowWindow(m_dstScreenError, SW_HIDE); + } + + // update add link button + enableItem(hwnd, IDC_SCREENS_ADD_LINK, + m_selectedLink == -1 && m_editedLinkIsValid); +} + +void +CScreensLinks::updateLinkView(HWND /*hwnd*/) +{ + // XXX -- draw visual of selected screen, highlighting selected link +} + +HWND +CScreensLinks::createErrorBox(HWND parent) +{ + return CreateWindow(TEXT("STATIC"), TEXT(""), + WS_CHILD | SS_OWNERDRAW, + 0, 0, 1, 1, + parent, (HMENU)-1, + s_instance, NULL); +} + +void +CScreensLinks::resizeErrorBoxes() +{ + HWND hwnd = GetParent(m_srcSideError); + resizeErrorBox(m_srcSideError, getItem(hwnd, IDC_SCREENS_SRC_SIDE)); + resizeErrorBox(m_srcScreenError, getItem(hwnd, IDC_SCREENS_SRC_SCREEN)); + resizeErrorBox(m_dstScreenError, getItem(hwnd, IDC_SCREENS_DST_SCREEN)); +} + +void +CScreensLinks::resizeErrorBox(HWND box, HWND assoc) +{ + RECT rect; + GetWindowRect(assoc, &rect); + MapWindowPoints(NULL, GetParent(box), (POINT*)&rect, 2); + SetWindowPos(box, HWND_TOP, rect.left - 1, rect.top - 1, + rect.right - rect.left + 2, + rect.bottom - rect.top + 2, SWP_NOACTIVATE); +} + +CString +CScreensLinks::formatIntervalValue(float x) const +{ + return CStringUtil::print("%d", (int)(x * 100.0f + 0.5f)); +} + +CString +CScreensLinks::formatInterval(const CConfig::CInterval& i) const +{ + if (i.first == 0.0f && i.second == 1.0f) { + return ""; + } + else { + CString start = formatIntervalValue(i.first); + CString end = formatIntervalValue(i.second); + return CStringUtil::format(m_intervalFormat.c_str(), + start.c_str(), end.c_str()); + } +} + +CString +CScreensLinks::formatLink(const CEdgeLink& link) const +{ + CString srcInterval = formatInterval(link.m_srcInterval); + CString dstInterval = formatInterval(link.m_dstInterval); + return CStringUtil::format(m_linkFormat.c_str(), + link.m_srcName.c_str(), srcInterval.c_str(), + m_sideLabel[link.m_srcSide - kFirstDirection].c_str(), + link.m_dstName.c_str(), dstInterval.c_str()); +} + +// +// CScreensLinks::CEdgeLink +// + +CScreensLinks::CEdgeLink::CEdgeLink() : + m_srcName(), + m_srcSide(kNoDirection), + m_srcInterval(0.0f, 1.0f), + m_dstName(), + m_dstInterval(0.0f, 1.0f) +{ + // do nothing +} + +CScreensLinks::CEdgeLink::CEdgeLink(const CString& name, + const CConfigLink& link) : + m_srcName(name), + m_srcSide(link.first.getSide()), + m_srcInterval(link.first.getInterval()), + m_dstName(link.second.getName()), + m_dstInterval(link.second.getInterval()) +{ + // do nothing +} + +bool +CScreensLinks::CEdgeLink::connect(CConfig* config) +{ + return config->connect(m_srcName, m_srcSide, + m_srcInterval.first, m_srcInterval.second, + m_dstName, + m_dstInterval.first, m_dstInterval.second); +} + +bool +CScreensLinks::CEdgeLink::disconnect(CConfig* config) +{ + return config->disconnect(m_srcName, m_srcSide, 0.5f * + (m_srcInterval.first + m_srcInterval.second)); +} + +void +CScreensLinks::CEdgeLink::rename(const CString& oldName, const CString& newName) +{ + if (m_srcName == oldName) { + m_srcName = newName; + } + if (m_dstName == oldName) { + m_dstName = newName; + } +} + +bool +CScreensLinks::CEdgeLink::overlaps(const CConfig* config) const +{ + return config->hasNeighbor(m_srcName, m_srcSide, + m_srcInterval.first, m_srcInterval.second); +} + +bool +CScreensLinks::CEdgeLink::operator==(const CEdgeLink& x) const +{ + return (m_srcName == x.m_srcName && + m_srcSide == x.m_srcSide && + m_srcInterval == x.m_srcInterval && + m_dstName == x.m_dstName && + m_dstInterval == x.m_dstInterval); +} diff --git a/cmd/launcher/CScreensLinks.h b/cmd/launcher/CScreensLinks.h new file mode 100644 index 00000000..8ffd0089 --- /dev/null +++ b/cmd/launcher/CScreensLinks.h @@ -0,0 +1,138 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSCREENSLINKS_H +#define CSCREENSLINKS_H + +#include "CConfig.h" +#include "ProtocolTypes.h" +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include + +//! Screens and links dialog for Microsoft Windows launcher +class CScreensLinks { +public: + CScreensLinks(HWND parent, CConfig*); + ~CScreensLinks(); + + //! @name manipulators + //@{ + + //! Run dialog + /*! + Display and handle the dialog until closed by the user. + */ + void doModal(); + + //@} + //! @name accessors + //@{ + + + //@} + +private: + typedef std::pair CConfigLink; + struct CEdgeLink { + public: + CEdgeLink(); + CEdgeLink(const CString& name, const CConfigLink&); + + bool connect(CConfig*); + bool disconnect(CConfig*); + void rename(const CString& oldName, const CString& newName); + + bool overlaps(const CConfig* config) const; + bool operator==(const CEdgeLink&) const; + + public: + CString m_srcName; + EDirection m_srcSide; + CConfig::CInterval m_srcInterval; + CString m_dstName; + CConfig::CInterval m_dstInterval; + }; + typedef std::vector CEdgeLinkList; + + void init(HWND hwnd); + bool save(HWND hwnd); + + CString getSelectedScreen(HWND hwnd) const; + void addScreen(HWND hwnd); + void editScreen(HWND hwnd); + void removeScreen(HWND hwnd); + void addLink(HWND hwnd); + void editLink(HWND hwnd); + void removeLink(HWND hwnd); + + void updateScreens(HWND hwnd, const CString& name); + void updateScreensControls(HWND hwnd); + void updateLinks(HWND hwnd); + void updateLinksControls(HWND hwnd); + + void changeSrcSide(HWND hwnd); + void changeSrcScreen(HWND hwnd); + void changeDstScreen(HWND hwnd); + void changeIntervalStart(HWND hwnd, int id, + CConfig::CInterval&); + void changeIntervalEnd(HWND hwnd, int id, + CConfig::CInterval&); + + void selectScreen(HWND hwnd, int id, const CString& name); + void updateLinkEditControls(HWND hwnd, + const CEdgeLink& link); + void updateLinkIntervalControls(HWND hwnd, + const CEdgeLink& link); + void updateLink(HWND hwnd); + void updateLinkValid(HWND hwnd, const CEdgeLink& link); + + void updateLinkView(HWND hwnd); + + HWND createErrorBox(HWND parent); + void resizeErrorBoxes(); + void resizeErrorBox(HWND box, HWND assoc); + + CString formatIntervalValue(float) const; + CString formatInterval(const CConfig::CInterval&) const; + CString formatLink(const CEdgeLink&) const; + + // message handling + BOOL doDlgProc(HWND, UINT, WPARAM, LPARAM); + static BOOL CALLBACK dlgProc(HWND, UINT, WPARAM, LPARAM); + +private: + static CScreensLinks* s_singleton; + + HWND m_parent; + CConfig* m_mainConfig; + CConfig m_scratchConfig; + CConfig* m_config; + + CString m_linkFormat; + CString m_intervalFormat; + CString m_newLinkLabel; + CString m_sideLabel[kNumDirections]; + CEdgeLinkList m_edgeLinks; + SInt32 m_selectedLink; + CEdgeLink m_editedLink; + bool m_editedLinkIsValid; + HPEN m_redPen; + HWND m_srcSideError; + HWND m_srcScreenError; + HWND m_dstScreenError; +}; + +#endif diff --git a/cmd/launcher/LaunchUtil.cpp b/cmd/launcher/LaunchUtil.cpp new file mode 100644 index 00000000..05b517e7 --- /dev/null +++ b/cmd/launcher/LaunchUtil.cpp @@ -0,0 +1,261 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "LaunchUtil.h" +#include "CMSWindowsUtil.h" +#include "CArch.h" +#include "resource.h" +#include "stdfstream.h" + +size_t s_showingDialog = 0; + +CString +getString(DWORD id) +{ + return CMSWindowsUtil::getString(s_instance, id); +} + +CString +getErrorString(DWORD error) +{ + return CMSWindowsUtil::getErrorString(s_instance, error, IDS_ERROR); +} + +void +showError(HWND hwnd, const CString& msg) +{ + CString title = getString(IDS_ERROR); + ++s_showingDialog; + MessageBox(hwnd, msg.c_str(), title.c_str(), MB_OK | MB_APPLMODAL); + --s_showingDialog; +} + +void +askOkay(HWND hwnd, const CString& title, const CString& msg) +{ + ++s_showingDialog; + MessageBox(hwnd, msg.c_str(), title.c_str(), MB_OK | MB_APPLMODAL); + --s_showingDialog; +} + +bool +askVerify(HWND hwnd, const CString& msg) +{ + CString title = getString(IDS_VERIFY); + ++s_showingDialog; + int result = MessageBox(hwnd, msg.c_str(), + title.c_str(), MB_OKCANCEL | MB_APPLMODAL); + --s_showingDialog; + return (result == IDOK); +} + +bool +isShowingDialog() +{ + return (s_showingDialog != 0); +} + +void +setWindowText(HWND hwnd, const CString& msg) +{ + SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)msg.c_str()); +} + +CString +getWindowText(HWND hwnd) +{ + LRESULT size = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0); + char* buffer = new char[size + 1]; + SendMessage(hwnd, WM_GETTEXT, size + 1, (LPARAM)buffer); + buffer[size] = '\0'; + CString result(buffer); + delete[] buffer; + return result; +} + +HWND +getItem(HWND hwnd, int id) +{ + return GetDlgItem(hwnd, id); +} + +void +enableItem(HWND hwnd, int id, bool enabled) +{ + EnableWindow(GetDlgItem(hwnd, id), enabled); +} + +void +setItemChecked(HWND hwnd, bool checked) +{ + SendMessage(hwnd, BM_SETCHECK, checked ? BST_CHECKED : BST_UNCHECKED, 0); +} + +bool +isItemChecked(HWND hwnd) +{ + return (SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED); +} + +CString +getAppPath(const CString& appName) +{ + // prepare path to app + char myPathname[MAX_PATH]; + GetModuleFileName(s_instance, myPathname, MAX_PATH); + const char* myBasename = ARCH->getBasename(myPathname); + CString appPath = CString(myPathname, myBasename - myPathname); + appPath += appName; + return appPath; +} + +static +void +getFileTime(const CString& path, time_t& t) +{ + struct _stat s; + if (_stat(path.c_str(), &s) != -1) { + t = s.st_mtime; + } +} + +bool +isConfigNewer(time_t& oldTime, bool userConfig) +{ + time_t newTime = oldTime; + if (userConfig) { + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + getFileTime(path, newTime); + } + } + else { + CString path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + getFileTime(path, newTime); + } + } + bool result = (newTime > oldTime); + oldTime = newTime; + return result; +} + +static +bool +loadConfig(const CString& pathname, CConfig& config) +{ + try { + std::ifstream stream(pathname.c_str()); + if (stream) { + stream >> config; + return true; + } + } + catch (...) { + // ignore + } + return false; +} + +bool +loadConfig(CConfig& config, time_t& t, bool& userConfig) +{ + // load configuration + bool configLoaded = false; + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + // try loading the user's configuration + path = ARCH->concatPath(path, CONFIG_NAME); + if (loadConfig(path, config)) { + configLoaded = true; + userConfig = true; + getFileTime(path, t); + } + else { + // try the system-wide config file + path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + if (loadConfig(path, config)) { + configLoaded = true; + userConfig = false; + getFileTime(path, t); + } + } + } + } + return configLoaded; +} + +static +bool +saveConfig(const CString& pathname, const CConfig& config) +{ + try { + std::ofstream stream(pathname.c_str()); + if (stream) { + stream << config; + return !!stream; + } + } + catch (...) { + // ignore + } + return false; +} + +bool +saveConfig(const CConfig& config, bool sysOnly, time_t& t) +{ + // try saving the user's configuration + if (!sysOnly) { + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + if (saveConfig(path, config)) { + getFileTime(path, t); + return true; + } + } + } + + // try the system-wide config file + else { + CString path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, CONFIG_NAME); + if (saveConfig(path, config)) { + getFileTime(path, t); + return true; + } + } + } + + return false; +} + +const TCHAR* const* +getSettingsPath() +{ + static const TCHAR* s_keyNames[] = { + TEXT("Software"), + TEXT("Synergy"), + TEXT("Synergy"), + NULL + }; + return s_keyNames; +} diff --git a/cmd/launcher/LaunchUtil.h b/cmd/launcher/LaunchUtil.h new file mode 100644 index 00000000..75954046 --- /dev/null +++ b/cmd/launcher/LaunchUtil.h @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LAUNCHUTIL_H +#define LAUNCHUTIL_H + +#include "CString.h" + +#define WINDOWS_LEAN_AND_MEAN +#include +#include +#include + +#define CLIENT_APP "synergyc.exe" +#define SERVER_APP "synergys.exe" +#define CONFIG_NAME "synergy.sgc" + +class CConfig; + +// client must define this and set it before calling any function here +extern HINSTANCE s_instance; + +CString getString(DWORD id); +CString getErrorString(DWORD error); + +void showError(HWND hwnd, const CString& msg); +void askOkay(HWND hwnd, const CString& title, + const CString& msg); +bool askVerify(HWND hwnd, const CString& msg); +bool isShowingDialog(); + +void setWindowText(HWND hwnd, const CString& msg); +CString getWindowText(HWND hwnd); + +HWND getItem(HWND hwnd, int id); +void enableItem(HWND hwnd, int id, bool enabled); + +void setItemChecked(HWND, bool); +bool isItemChecked(HWND); + +CString getAppPath(const CString& appName); + +bool isConfigNewer(time_t&, bool userConfig); +bool loadConfig(CConfig& config, time_t&, bool& userConfig); +bool saveConfig(const CConfig& config, + bool sysOnly, time_t&); + +const TCHAR* const* getSettingsPath(); + +#endif diff --git a/cmd/launcher/Makefile.am b/cmd/launcher/Makefile.am new file mode 100644 index 00000000..6fc879e3 --- /dev/null +++ b/cmd/launcher/Makefile.am @@ -0,0 +1,75 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +MSWINDOWS_SOURCE_FILES = \ + CAddScreen.cpp \ + CAdvancedOptions.cpp \ + CAutoStart.cpp \ + CGlobalOptions.cpp \ + CHotkeyOptions.cpp \ + CInfo.cpp \ + CScreensLinks.cpp \ + LaunchUtil.cpp \ + launcher.cpp \ + CAddScreen.h \ + CAdvancedOptions.h \ + CAutoStart.h \ + CGlobalOptions.h \ + CHotkeyOptions.h \ + CInfo.h \ + CScreensLinks.h \ + LaunchUtil.h \ + resource.h \ + launcher.rc \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + synergy.ico \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +if MSWINDOWS +bin_PROGRAMS = synergy +synergy_SOURCES = \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +synergy_LDADD = \ + $(top_builddir)/lib/server/libserver.a \ + $(top_builddir)/lib/platform/libplatform.a \ + $(top_builddir)/lib/synergy/libsynergy.a \ + $(top_builddir)/lib/net/libnet.a \ + $(top_builddir)/lib/io/libio.a \ + $(top_builddir)/lib/mt/libmt.a \ + $(top_builddir)/lib/base/libbase.a \ + $(top_builddir)/lib/common/libcommon.a \ + $(top_builddir)/lib/arch/libarch.a \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + -I$(top_srcdir)/lib/server \ + $(NULL) diff --git a/cmd/launcher/Makefile.win b/cmd/launcher/Makefile.win new file mode 100644 index 00000000..3d4f277a --- /dev/null +++ b/cmd/launcher/Makefile.win @@ -0,0 +1,101 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +BIN_LAUNCHER_SRC = cmd\launcher +BIN_LAUNCHER_DST = $(BUILD_DST)\$(BIN_LAUNCHER_SRC) +BIN_LAUNCHER_EXE = "$(BUILD_DST)\synergy.exe" +BIN_LAUNCHER_CPP = \ + "CAddScreen.cpp" \ + "CAdvancedOptions.cpp" \ + "CAutoStart.cpp" \ + "CGlobalOptions.cpp" \ + "CHotkeyOptions.cpp" \ + "CInfo.cpp" \ + "CScreensLinks.cpp" \ + "LaunchUtil.cpp" \ + "launcher.cpp" \ + $(NULL) +BIN_LAUNCHER_OBJ = \ + "$(BIN_LAUNCHER_DST)\CAddScreen.obj" \ + "$(BIN_LAUNCHER_DST)\CAdvancedOptions.obj" \ + "$(BIN_LAUNCHER_DST)\CAutoStart.obj" \ + "$(BIN_LAUNCHER_DST)\CGlobalOptions.obj" \ + "$(BIN_LAUNCHER_DST)\CHotkeyOptions.obj" \ + "$(BIN_LAUNCHER_DST)\CInfo.obj" \ + "$(BIN_LAUNCHER_DST)\CScreensLinks.obj" \ + "$(BIN_LAUNCHER_DST)\LaunchUtil.obj" \ + "$(BIN_LAUNCHER_DST)\launcher.obj" \ + $(NULL) +BIN_LAUNCHER_RC = "$(BIN_LAUNCHER_SRC)\launcher.rc" +BIN_LAUNCHER_RES = "$(BIN_LAUNCHER_DST)\launcher.res" +BIN_LAUNCHER_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + /I"lib\server" \ + $(NULL) +BIN_LAUNCHER_LIB = \ + $(LIB_SERVER_LIB) \ + $(LIB_PLATFORM_LIB) \ + $(LIB_SYNERGY_LIB) \ + $(LIB_NET_LIB) \ + $(LIB_IO_LIB) \ + $(LIB_MT_LIB) \ + $(LIB_BASE_LIB) \ + $(LIB_ARCH_LIB) \ + $(LIB_COMMON_LIB) \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(BIN_LAUNCHER_CPP) +OBJ_FILES = $(OBJ_FILES) $(BIN_LAUNCHER_OBJ) +PROGRAMS = $(PROGRAMS) $(BIN_LAUNCHER_EXE) + +# Need shell functions. +guilibs = $(guilibs) shell32.lib + +# Dependency rules +$(BIN_LAUNCHER_OBJ): $(AUTODEP) +!if EXIST($(BIN_LAUNCHER_DST)\deps.mak) +!include $(BIN_LAUNCHER_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(BIN_LAUNCHER_SRC)\}.cpp{$(BIN_LAUNCHER_DST)\}.obj:: +!else +{$(BIN_LAUNCHER_SRC)\}.cpp{$(BIN_LAUNCHER_DST)\}.obj: +!endif + @$(ECHO) Compile in $(BIN_LAUNCHER_SRC) + -@$(MKDIR) $(BIN_LAUNCHER_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(BIN_LAUNCHER_INC) \ + /Fo$(BIN_LAUNCHER_DST)\ \ + /Fd$(BIN_LAUNCHER_DST)\src.pdb \ + $< | $(AUTODEP) $(BIN_LAUNCHER_SRC) $(BIN_LAUNCHER_DST) +$(BIN_LAUNCHER_RES): $(BIN_LAUNCHER_RC) + @$(ECHO) Compile $(**F) + -@$(MKDIR) $(BIN_LAUNCHER_DST) 2>NUL: + $(rc) $(rcflags) $(rcvars) \ + /fo$@ \ + $** +$(BIN_LAUNCHER_EXE): $(BIN_LAUNCHER_OBJ) $(BIN_LAUNCHER_RES) $(BIN_LAUNCHER_LIB) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(guilflags) $(guilibsmt) \ + /out:$@ \ + $** + $(AUTODEP) $(BIN_LAUNCHER_SRC) $(BIN_LAUNCHER_DST) \ + $(BIN_LAUNCHER_OBJ:.obj=.d) diff --git a/cmd/launcher/launcher.cpp b/cmd/launcher/launcher.cpp new file mode 100644 index 00000000..938822da --- /dev/null +++ b/cmd/launcher/launcher.cpp @@ -0,0 +1,755 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "KeyTypes.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "CLog.h" +#include "CStringUtil.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include "XArch.h" +#include "Version.h" +#include "stdvector.h" +#include "resource.h" + +// these must come after the above because it includes windows.h +#include "LaunchUtil.h" +#include "CAddScreen.h" +#include "CAdvancedOptions.h" +#include "CAutoStart.h" +#include "CGlobalOptions.h" +#include "CHotkeyOptions.h" +#include "CInfo.h" +#include "CScreensLinks.h" + +typedef std::vector CStringList; + +class CChildWaitInfo { +public: + HWND m_dialog; + HANDLE m_child; + DWORD m_childID; + HANDLE m_ready; + HANDLE m_stop; +}; + +static const char* s_debugName[][2] = { + { TEXT("Error"), "ERROR" }, + { TEXT("Warning"), "WARNING" }, + { TEXT("Note"), "NOTE" }, + { TEXT("Info"), "INFO" }, + { TEXT("Debug"), "DEBUG" }, + { TEXT("Debug1"), "DEBUG1" }, + { TEXT("Debug2"), "DEBUG2" } +}; +static const int s_defaultDebug = 1; // WARNING +static const int s_minTestDebug = 3; // INFO + +HINSTANCE s_instance = NULL; + +static CGlobalOptions* s_globalOptions = NULL; +static CAdvancedOptions* s_advancedOptions = NULL; +static CHotkeyOptions* s_hotkeyOptions = NULL; +static CScreensLinks* s_screensLinks = NULL; +static CInfo* s_info = NULL; + +static bool s_userConfig = true; +static time_t s_configTime = 0; +static CConfig s_lastConfig; + +static const TCHAR* s_mainClass = TEXT("GoSynergy"); +static const TCHAR* s_layoutClass = TEXT("SynergyLayout"); + +enum SaveMode { + SAVE_QUITING, + SAVE_NORMAL, + SAVE_QUIET +}; + +// +// program arguments +// + +#define ARG CArgs::s_instance + +class CArgs { +public: + CArgs() { s_instance = this; } + ~CArgs() { s_instance = NULL; } + +public: + static CArgs* s_instance; + CConfig m_config; + CStringList m_screens; +}; + +CArgs* CArgs::s_instance = NULL; + + +static +BOOL CALLBACK +addDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +static +bool +isClientChecked(HWND hwnd) +{ + HWND child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); + return isItemChecked(child); +} + +static +void +enableMainWindowControls(HWND hwnd) +{ + bool client = isClientChecked(hwnd); + enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_LABEL, client); + enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT, client); + enableItem(hwnd, IDC_MAIN_SERVER_SCREENS_LABEL, !client); + enableItem(hwnd, IDC_MAIN_SCREENS, !client); + enableItem(hwnd, IDC_MAIN_OPTIONS, !client); + enableItem(hwnd, IDC_MAIN_HOTKEYS, !client); +} + +static +bool +execApp(const char* app, const CString& cmdLine, PROCESS_INFORMATION* procInfo) +{ + // prepare startup info + STARTUPINFO startup; + startup.cb = sizeof(startup); + startup.lpReserved = NULL; + startup.lpDesktop = NULL; + startup.lpTitle = NULL; + startup.dwX = (DWORD)CW_USEDEFAULT; + startup.dwY = (DWORD)CW_USEDEFAULT; + startup.dwXSize = (DWORD)CW_USEDEFAULT; + startup.dwYSize = (DWORD)CW_USEDEFAULT; + startup.dwXCountChars = 0; + startup.dwYCountChars = 0; + startup.dwFillAttribute = 0; + startup.dwFlags = STARTF_FORCEONFEEDBACK; + startup.wShowWindow = SW_SHOWDEFAULT; + startup.cbReserved2 = 0; + startup.lpReserved2 = NULL; + startup.hStdInput = NULL; + startup.hStdOutput = NULL; + startup.hStdError = NULL; + + // prepare path to app + CString appPath = getAppPath(app); + + // put path to app in command line + CString commandLine = "\""; + commandLine += appPath; + commandLine += "\" "; + commandLine += cmdLine; + + // start child + if (CreateProcess(NULL, (char*)commandLine.c_str(), + NULL, + NULL, + FALSE, + CREATE_DEFAULT_ERROR_MODE | + CREATE_NEW_PROCESS_GROUP | + NORMAL_PRIORITY_CLASS, + NULL, + NULL, + &startup, + procInfo) == 0) { + return false; + } + else { + return true; + } +} + +static +CString +getCommandLine(HWND hwnd, bool testing, bool silent) +{ + CString cmdLine; + + // add constant testing args + if (testing) { + cmdLine += " -z --no-restart --no-daemon"; + } + + // can't start as service on NT + else if (!CArchMiscWindows::isWindows95Family()) { + cmdLine += " --no-daemon"; + } + + // get the server name + CString server; + bool isClient = isClientChecked(hwnd); + if (isClient) { + // check server name + HWND child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + server = getWindowText(child); + if (!ARG->m_config.isValidScreenName(server)) { + if (!silent) { + showError(hwnd, CStringUtil::format( + getString(IDS_INVALID_SERVER_NAME).c_str(), + server.c_str())); + } + SetFocus(child); + return CString(); + } + + // compare server name to local host. a common error + // is to provide the client's name for the server. we + // don't bother to check the addresses though that'd be + // more accurate. + if (CStringUtil::CaselessCmp::equal(ARCH->getHostName(), server)) { + if (!silent) { + showError(hwnd, CStringUtil::format( + getString(IDS_SERVER_IS_CLIENT).c_str(), + server.c_str())); + } + SetFocus(child); + return CString(); + } + } + + // debug level. always include this. + if (true) { + HWND child = getItem(hwnd, IDC_MAIN_DEBUG); + int debug = (int)SendMessage(child, CB_GETCURSEL, 0, 0); + + // if testing then we force the debug level to be no less than + // s_minTestDebug. what's the point of testing if you can't + // see the debugging info? + if (testing && debug < s_minTestDebug) { + debug = s_minTestDebug; + } + + cmdLine += " --debug "; + cmdLine += s_debugName[debug][1]; + } + + // add advanced options + cmdLine += s_advancedOptions->getCommandLine(isClient, server); + + return cmdLine; +} + +static +bool +launchApp(HWND hwnd, bool testing, HANDLE* thread, DWORD* threadID) +{ + if (thread != NULL) { + *thread = NULL; + } + if (threadID != NULL) { + *threadID = 0; + } + + // start daemon if it's installed and we're not testing + if (!testing && CAutoStart::startDaemon()) { + return true; + } + + // decide if client or server + const bool isClient = isClientChecked(hwnd); + const char* app = isClient ? CLIENT_APP : SERVER_APP; + + // prepare command line + CString cmdLine = getCommandLine(hwnd, testing, false); + if (cmdLine.empty()) { + return false; + } + + // start child + PROCESS_INFORMATION procInfo; + if (!execApp(app, cmdLine, &procInfo)) { + showError(hwnd, CStringUtil::format( + getString(IDS_STARTUP_FAILED).c_str(), + getErrorString(GetLastError()).c_str())); + return false; + } + + // don't need process handle + CloseHandle(procInfo.hProcess); + + // save thread handle and thread ID if desired + if (thread != NULL) { + *thread = procInfo.hThread; + } + if (threadID != NULL) { + *threadID = procInfo.dwThreadId; + } + + return true; +} + +static +BOOL CALLBACK +waitDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + // only one wait dialog at a time! + static CChildWaitInfo* info = NULL; + + switch (message) { + case WM_INITDIALOG: + // save info pointer + info = reinterpret_cast(lParam); + + // save hwnd + info->m_dialog = hwnd; + + // signal ready + SetEvent(info->m_ready); + + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + case IDOK: + // signal stop + SetEvent(info->m_stop); + + // done + EndDialog(hwnd, 0); + return TRUE; + } + } + + return FALSE; +} + +static +DWORD WINAPI +waitForChildThread(LPVOID vinfo) +{ + CChildWaitInfo* info = reinterpret_cast(vinfo); + + // wait for ready + WaitForSingleObject(info->m_ready, INFINITE); + + // wait for thread to complete or stop event + HANDLE handles[2]; + handles[0] = info->m_child; + handles[1] = info->m_stop; + DWORD n = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + + // if stop was raised then terminate child and wait for it + if (n == WAIT_OBJECT_0 + 1) { + PostThreadMessage(info->m_childID, WM_QUIT, 0, 0); + WaitForSingleObject(info->m_child, INFINITE); + } + + // otherwise post IDOK to dialog box + else { + PostMessage(info->m_dialog, WM_COMMAND, MAKEWPARAM(IDOK, 0), 0); + } + + return 0; +} + +static +void +waitForChild(HWND hwnd, HANDLE thread, DWORD threadID) +{ + // prepare info for child wait dialog and thread + CChildWaitInfo info; + info.m_dialog = NULL; + info.m_child = thread; + info.m_childID = threadID; + info.m_ready = CreateEvent(NULL, TRUE, FALSE, NULL); + info.m_stop = CreateEvent(NULL, TRUE, FALSE, NULL); + + // create a thread to wait on the child thread and event + DWORD id; + HANDLE waiter = CreateThread(NULL, 0, &waitForChildThread, &info,0, &id); + + // do dialog that let's the user terminate the test + DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_WAIT), hwnd, + (DLGPROC)waitDlgProc, (LPARAM)&info); + + // force the waiter thread to finish and wait for it + SetEvent(info.m_ready); + SetEvent(info.m_stop); + WaitForSingleObject(waiter, INFINITE); + + // clean up + CloseHandle(waiter); + CloseHandle(info.m_ready); + CloseHandle(info.m_stop); +} + +static +void +initMainWindow(HWND hwnd) +{ + // append version number to title + CString titleFormat = getString(IDS_TITLE); + setWindowText(hwnd, CStringUtil::format(titleFormat.c_str(), VERSION)); + + // load configuration + bool configLoaded = + loadConfig(ARG->m_config, s_configTime, s_userConfig); + if (configLoaded) { + s_lastConfig = ARG->m_config; + } + + // get settings from registry + bool isServer = configLoaded; + int debugLevel = s_defaultDebug; + CString server; + HKEY key = CArchMiscWindows::openKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + if (isServer && CArchMiscWindows::hasValue(key, "isServer")) { + isServer = (CArchMiscWindows::readValueInt(key, "isServer") != 0); + } + if (CArchMiscWindows::hasValue(key, "debug")) { + debugLevel = static_cast( + CArchMiscWindows::readValueInt(key, "debug")); + if (debugLevel < 0) { + debugLevel = 0; + } + else if (debugLevel > CLog::kDEBUG2) { + debugLevel = CLog::kDEBUG2; + } + } + server = CArchMiscWindows::readValueString(key, "server"); + CArchMiscWindows::closeKey(key); + } + + // choose client/server radio buttons + HWND child; + child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO); + setItemChecked(child, !isServer); + child = getItem(hwnd, IDC_MAIN_SERVER_RADIO); + setItemChecked(child, isServer); + + // set server name + child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + setWindowText(child, server); + + // debug level + child = getItem(hwnd, IDC_MAIN_DEBUG); + for (unsigned int i = 0; i < sizeof(s_debugName) / + sizeof(s_debugName[0]); ++i) { + SendMessage(child, CB_ADDSTRING, 0, (LPARAM)s_debugName[i][0]); + } + SendMessage(child, CB_SETCURSEL, debugLevel, 0); + + // update controls + enableMainWindowControls(hwnd); +} + +static +bool +saveMainWindow(HWND hwnd, SaveMode mode, CString* cmdLineOut = NULL) +{ + DWORD errorID = 0; + CString arg; + CString cmdLine; + + // save dialog state + bool isClient = isClientChecked(hwnd); + HKEY key = CArchMiscWindows::addKey(HKEY_CURRENT_USER, getSettingsPath()); + if (key != NULL) { + HWND child; + child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT); + CArchMiscWindows::setValue(key, "server", getWindowText(child)); + child = getItem(hwnd, IDC_MAIN_DEBUG); + CArchMiscWindows::setValue(key, "debug", + SendMessage(child, CB_GETCURSEL, 0, 0)); + CArchMiscWindows::setValue(key, "isServer", isClient ? 0 : 1); + CArchMiscWindows::closeKey(key); + } + + // save user's configuration + if (!s_userConfig || ARG->m_config != s_lastConfig) { + time_t t; + if (!saveConfig(ARG->m_config, false, t)) { + errorID = IDS_SAVE_FAILED; + arg = getErrorString(GetLastError()); + goto failed; + } + if (s_userConfig) { + s_configTime = t; + s_lastConfig = ARG->m_config; + } + } + + // save autostart configuration + if (CAutoStart::isDaemonInstalled()) { + if (s_userConfig || ARG->m_config != s_lastConfig) { + time_t t; + if (!saveConfig(ARG->m_config, true, t)) { + errorID = IDS_AUTOSTART_SAVE_FAILED; + arg = getErrorString(GetLastError()); + goto failed; + } + if (!s_userConfig) { + s_configTime = t; + s_lastConfig = ARG->m_config; + } + } + } + + // get autostart command + cmdLine = getCommandLine(hwnd, false, mode == SAVE_QUITING); + if (cmdLineOut != NULL) { + *cmdLineOut = cmdLine; + } + if (cmdLine.empty()) { + return (mode == SAVE_QUITING); + } + + // save autostart command + if (CAutoStart::isDaemonInstalled()) { + try { + CAutoStart::reinstallDaemon(isClient, cmdLine); + CAutoStart::uninstallDaemons(!isClient); + } + catch (XArchDaemon& e) { + errorID = IDS_INSTALL_GENERIC_ERROR; + arg = e.what(); + goto failed; + } + } + + return true; + +failed: + CString errorMessage = + CStringUtil::format(getString(errorID).c_str(), arg.c_str()); + if (mode == SAVE_QUITING) { + errorMessage += "\n"; + errorMessage += getString(IDS_UNSAVED_DATA_REALLY_QUIT); + if (askVerify(hwnd, errorMessage)) { + return true; + } + } + else if (mode == SAVE_NORMAL) { + showError(hwnd, errorMessage); + } + return false; +} + +static +LRESULT CALLBACK +mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_ACTIVATE: + if (LOWORD(wParam) != WA_INACTIVE) { + // activated + + // see if the configuration changed + if (isConfigNewer(s_configTime, s_userConfig)) { + CString message = getString(IDS_CONFIG_CHANGED); + if (askVerify(hwnd, message)) { + time_t configTime; + bool userConfig; + CConfig newConfig; + if (loadConfig(newConfig, configTime, userConfig) && + userConfig == s_userConfig) { + ARG->m_config = newConfig; + s_lastConfig = ARG->m_config; + } + else { + message = getString(IDS_LOAD_FAILED); + showError(hwnd, message); + s_lastConfig = CConfig(); + } + } + } + } + else { + // deactivated; write configuration + if (!isShowingDialog()) { + saveMainWindow(hwnd, SAVE_QUIET); + } + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + // save data + if (saveMainWindow(hwnd, SAVE_QUITING)) { + // quit + PostQuitMessage(0); + } + return 0; + + case IDOK: + case IDC_MAIN_TEST: { + // note if testing + const bool testing = (LOWORD(wParam) == IDC_MAIN_TEST); + + // save data + if (saveMainWindow(hwnd, SAVE_NORMAL)) { + // launch child app + DWORD threadID; + HANDLE thread; + if (!launchApp(hwnd, testing, &thread, &threadID)) { + return 0; + } + + // handle child program + if (testing) { + // wait for process to stop, allowing the user to kill it + waitForChild(hwnd, thread, threadID); + + // clean up + CloseHandle(thread); + } + else { + // don't need thread handle + if (thread != NULL) { + CloseHandle(thread); + } + + // notify of success + askOkay(hwnd, getString(IDS_STARTED_TITLE), + getString(IDS_STARTED)); + + // quit + PostQuitMessage(0); + } + } + return 0; + } + + case IDC_MAIN_AUTOSTART: { + CString cmdLine; + if (saveMainWindow(hwnd, SAVE_NORMAL, &cmdLine)) { + // run dialog + CAutoStart autoStart(hwnd, !isClientChecked(hwnd), cmdLine); + autoStart.doModal(); + } + return 0; + } + + case IDC_MAIN_CLIENT_RADIO: + case IDC_MAIN_SERVER_RADIO: + enableMainWindowControls(hwnd); + return 0; + + case IDC_MAIN_SCREENS: + s_screensLinks->doModal(); + break; + + case IDC_MAIN_OPTIONS: + s_globalOptions->doModal(); + break; + + case IDC_MAIN_ADVANCED: + s_advancedOptions->doModal(isClientChecked(hwnd)); + break; + + case IDC_MAIN_HOTKEYS: + s_hotkeyOptions->doModal(); + break; + + case IDC_MAIN_INFO: + s_info->doModal(); + break; + } + + default: + break; + } + return DefDlgProc(hwnd, message, wParam, lParam); +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmdLine, int nCmdShow) +{ + CArch arch(instance); + CLOG; + CArgs args; + + s_instance = instance; + + // if "/uninstall" is on the command line then just stop and + // uninstall the service and quit. this is the only option + // but we ignore any others. + if (CString(cmdLine).find("/uninstall") != CString::npos) { + CAutoStart::uninstallDaemons(false); + CAutoStart::uninstallDaemons(true); + return 0; + } + + // register main window (dialog) class + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_HREDRAW | CS_VREDRAW; + classInfo.lpfnWndProc = &mainWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = DLGWINDOWEXTRA; + classInfo.hInstance = instance; + classInfo.hIcon = (HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 32, 32, LR_SHARED); + classInfo.hCursor = LoadCursor(NULL, IDC_ARROW); + classInfo.hbrBackground = reinterpret_cast(COLOR_3DFACE + 1); + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = s_mainClass; + classInfo.hIconSm = (HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 16, 16, LR_SHARED); + RegisterClassEx(&classInfo); + + // create main window + HWND mainWindow = CreateDialog(s_instance, + MAKEINTRESOURCE(IDD_MAIN), 0, NULL); + + // prep windows + initMainWindow(mainWindow); + s_globalOptions = new CGlobalOptions(mainWindow, &ARG->m_config); + s_advancedOptions = new CAdvancedOptions(mainWindow, &ARG->m_config); + s_hotkeyOptions = new CHotkeyOptions(mainWindow, &ARG->m_config); + s_screensLinks = new CScreensLinks(mainWindow, &ARG->m_config); + s_info = new CInfo(mainWindow); + + // show window + ShowWindow(mainWindow, nCmdShow); + + // main loop + MSG msg; + bool done = false; + do { + switch (GetMessage(&msg, NULL, 0, 0)) { + case -1: + // error + break; + + case 0: + // quit + done = true; + break; + + default: + if (!IsDialogMessage(mainWindow, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + break; + } + } while (!done); + + return msg.wParam; +} diff --git a/cmd/launcher/launcher.rc b/cmd/launcher/launcher.rc new file mode 100644 index 00000000..ff72d069 --- /dev/null +++ b/cmd/launcher/launcher.rc @@ -0,0 +1,617 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#if !defined(IDC_STATIC) +#define IDC_STATIC (-1) +#endif + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MAIN DIALOG DISCARDABLE 32768, 0, 300, 199 +STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU +CAPTION "Synergy" +CLASS "GoSynergy" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Choose to share or use a shared keyboard and mouse, provide the requested information, then click Test to check your settings or Start to save your settings and start Synergy.", + IDC_STATIC,7,7,286,19 + GROUPBOX "",IDC_STATIC,7,29,286,36 + GROUPBOX "",IDC_STATIC,7,72,286,36 + GROUPBOX "Options",IDC_STATIC,7,115,286,56 + CONTROL "&Use another computer's shared keyboard and mouse (client)", + IDC_MAIN_CLIENT_RADIO,"Button",BS_AUTORADIOBUTTON | + WS_GROUP | WS_TABSTOP,11,29,205,10 + CONTROL "Share this computer's keyboard and mouse (server)", + IDC_MAIN_SERVER_RADIO,"Button",BS_AUTORADIOBUTTON,11,72, + 177,10 + LTEXT "Other Computer's &Host Name:", + IDC_MAIN_CLIENT_SERVER_NAME_LABEL,12,46,94,8 + EDITTEXT IDC_MAIN_CLIENT_SERVER_NAME_EDIT,111,44,106,12, + ES_AUTOHSCROLL + LTEXT "&Screens && Links:",IDC_MAIN_SERVER_SCREENS_LABEL,12,89, + 54,8 + PUSHBUTTON "Configure...",IDC_MAIN_SCREENS,71,86,50,14 + PUSHBUTTON "&Options...",IDC_MAIN_OPTIONS,12,129,50,14 + PUSHBUTTON "Hot &Keys...",IDC_MAIN_HOTKEYS,68,129,50,14 + PUSHBUTTON "Adva&nced...",IDC_MAIN_ADVANCED,124,129,50,14 + PUSHBUTTON "&AutoStart...",IDC_MAIN_AUTOSTART,180,129,50,14 + LTEXT "&Logging Level:",IDC_STATIC,12,154,48,8 + COMBOBOX IDC_MAIN_DEBUG,68,151,61,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Info",IDC_MAIN_INFO,7,178,50,14 + DEFPUSHBUTTON "&Test",IDC_MAIN_TEST,131,179,50,14 + PUSHBUTTON "Start",IDOK,187,179,50,14 + PUSHBUTTON "Quit",IDCANCEL,243,179,50,14 +END + +IDD_ADD DIALOG DISCARDABLE 0, 0, 192, 254 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION +CAPTION "Add Screen" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Screen Name:",IDC_STATIC,7,9,46,8 + EDITTEXT IDC_ADD_SCREEN_NAME_EDIT,79,7,106,12,ES_AUTOHSCROLL + LTEXT "&Aliases:",IDC_STATIC,7,25,25,8 + EDITTEXT IDC_ADD_ALIASES_EDIT,79,26,106,24,ES_MULTILINE | + ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN + GROUPBOX "Options",IDC_STATIC,7,55,178,54 + LTEXT "If your Caps, Num, or Scroll Lock keys behave strangely on this client screen then try turning the half-duplex options on and reconnect the client.", + IDC_STATIC,13,65,165,25 + CONTROL "&Caps Lock",IDC_ADD_HD_CAPS_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,13,93,51,10 + CONTROL "&Num Lock",IDC_ADD_HD_NUM_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,69,93,51,10 + CONTROL "Sc&roll Lock",IDC_ADD_HD_SCROLL_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,125,93,51,10 + GROUPBOX "Modifiers",IDC_STATIC,7,113,178,65 + LTEXT "Shift",IDC_STATIC,13,129,15,8 + COMBOBOX IDC_ADD_MOD_SHIFT,37,126,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Ctrl",IDC_STATIC,13,144,11,8 + COMBOBOX IDC_ADD_MOD_CTRL,37,142,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Alt",IDC_STATIC,13,160,9,8 + COMBOBOX IDC_ADD_MOD_ALT,37,158,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Meta",IDC_STATIC,101,128,17,8 + COMBOBOX IDC_ADD_MOD_META,125,126,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "Super",IDC_STATIC,101,144,20,8 + COMBOBOX IDC_ADD_MOD_SUPER,125,142,48,60,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + GROUPBOX "Dead Corners",IDC_STATIC,7,183,178,43 + LTEXT "Don't switch in these corners:",IDC_STATIC,14,198,52,18 + CONTROL "",IDC_STATIC,"Static",SS_BLACKFRAME,68,193,47,28 + CONTROL "",IDC_ADD_DC_TOP_LEFT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,76,197,16,8 + CONTROL "",IDC_ADD_DC_TOP_RIGHT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,98,197,16,8 + CONTROL "",IDC_ADD_DC_BOTTOM_LEFT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,76,210,16,8 + CONTROL "",IDC_ADD_DC_BOTTOM_RIGHT,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,98,210,16,8 + LTEXT "Size",IDC_STATIC,120,202,14,8 + EDITTEXT IDC_ADD_DC_SIZE,139,200,40,12,ES_AUTOHSCROLL | ES_NUMBER + DEFPUSHBUTTON "OK",IDOK,79,233,50,14 + PUSHBUTTON "Cancel",IDCANCEL,135,233,50,14 +END + +IDD_WAIT DIALOG DISCARDABLE 0, 0, 186, 54 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION +CAPTION "Running Test..." +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Stop",IDOK,129,33,50,14 + LTEXT "Running synergy. Press Stop to end the test.", + IDC_STATIC,7,7,172,15 +END + +IDD_AUTOSTART DIALOG DISCARDABLE 0, 0, 195, 189 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Auto Start" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Close",IDCANCEL,138,168,50,14 + LTEXT "Synergy can be configured to start automatically when you log in. If you have sufficient access rights, you can instead configure synergy to start automatically when your computer starts.", + IDC_STATIC,7,7,181,33 + LTEXT "You have sufficient access rights to install and uninstall Auto Start for all users or for just yourself.", + IDC_AUTOSTART_PERMISSION_MSG,7,69,181,17 + LTEXT "Synergy is configured to start automatically when the system starts.", + IDC_AUTOSTART_INSTALLED_MSG,7,93,181,17 + GROUPBOX "When &You Log In",IDC_STATIC,7,119,84,40 + PUSHBUTTON "Install",IDC_AUTOSTART_INSTALL_USER,23,133,50,14 + GROUPBOX "When &Computer Starts",IDC_STATIC,104,119,84,40 + PUSHBUTTON "Install",IDC_AUTOSTART_INSTALL_SYSTEM,119,134,50,14 + LTEXT "Synergy can be configured to start automatically when the computer starts or when you log in but not both.", + IDC_STATIC,7,43,181,17 +END + +IDD_GLOBAL_OPTIONS DIALOG DISCARDABLE 0, 0, 207, 290 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "It's easy to unintentionally switch screens when the pointer is near a screen's edge. Synergy can prevent switching until certain conditions are met to reduce unintentional switching.", + IDC_STATIC,7,7,191,26 + LTEXT "Synergy can wait to switch until the cursor has been at a screen's edge for some amount of time.", + IDC_STATIC,7,37,193,16 + CONTROL "Switch after waiting",IDC_GLOBAL_DELAY_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,59,77,10 + EDITTEXT IDC_GLOBAL_DELAY_TIME,112,58,45,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,159,60,10,8 + LTEXT "Synergy can switch only when the cursor hits a screen edge twice within some amount of time.", + IDC_STATIC,7,77,193,16 + CONTROL "Switch on double tap within",IDC_GLOBAL_TWO_TAP_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,99,103,10 + EDITTEXT IDC_GLOBAL_TWO_TAP_TIME,112,98,45,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,159,100,10,8 + LTEXT "Synergy can periodically check that clients are still alive and connected. Use this only if synergy doesn't detect when clients disconnect.", + IDC_STATIC,7,122,193,24 + CONTROL "Check clients every",IDC_GLOBAL_HEARTBEAT_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,153,78,10 + EDITTEXT IDC_GLOBAL_HEARTBEAT_TIME,112,152,45,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "ms",IDC_STATIC,159,154,10,8 + LTEXT "Synergy can synchronize screen savers across all screens.", + IDC_STATIC,7,176,193,8 + CONTROL "Synchronize screen savers",IDC_GLOBAL_SCREENSAVER_SYNC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,192,101,10 + LTEXT "Relative mouse moves on secondary screens.",IDC_STATIC, + 7,213,193,8 + CONTROL "Use relative mouse moves",IDC_GLOBAL_RELATIVE_MOVES, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,229,99,10 + CONTROL "Don't take foreground window on Windows servers", + IDC_GLOBAL_LEAVE_FOREGROUND,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,7,250,177,10 + DEFPUSHBUTTON "OK",IDOK,94,269,50,14 + PUSHBUTTON "Cancel",IDCANCEL,150,269,50,14 +END + +IDD_ADVANCED_OPTIONS DIALOG DISCARDABLE 0, 0, 230, 186 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced Options" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Synergy normally uses this computer's name as its screen name. Enter another name here if you want to use a different screen name.", + IDC_STATIC,7,7,216,19 + LTEXT "Screen &Name:",IDC_STATIC,7,34,46,8 + EDITTEXT IDC_ADVANCED_NAME_EDIT,63,32,106,12,ES_AUTOHSCROLL + LTEXT "Synergy normally uses a particular network port number. Enter an alternative port here. (The server and all clients must use the same port number.)", + IDC_STATIC,7,56,216,26 + LTEXT "&Port:",IDC_STATIC,7,90,16,8 + EDITTEXT IDC_ADVANCED_PORT_EDIT,63,88,40,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "The server normally listens for client connections on all network interfaces. Enter the address of a particular interface to listen on just that interface.", + IDC_STATIC,7,110,216,26 + LTEXT "&Interface:",IDC_STATIC,7,144,31,8 + EDITTEXT IDC_ADVANCED_INTERFACE_EDIT,63,142,81,12,ES_AUTOHSCROLL + PUSHBUTTON "&Defaults",IDC_ADVANCED_DEFAULTS,7,165,50,14 + DEFPUSHBUTTON "OK",IDOK,118,165,50,14 + PUSHBUTTON "Cancel",IDCANCEL,173,165,50,14 +END + +IDD_SCREENS_LINKS DIALOG DISCARDABLE 0, 0, 354, 213 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Screens & Links" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Screens:",IDC_STATIC,7,7,29,8 + LISTBOX IDC_SCREENS_SCREENS,7,18,100,36,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_SCREENS_ADD_SCREEN,7,57,17,14 + PUSHBUTTON "-",IDC_SCREENS_REMOVE_SCREEN,28,57,17,14 + PUSHBUTTON "Edit",IDC_SCREENS_EDIT_SCREEN,49,57,24,14 + LTEXT "&Links:",IDC_STATIC,7,83,20,8 + LISTBOX IDC_SCREENS_LINKS,7,94,339,59,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_SCREENS_SRC_START,7,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "to",IDC_STATIC,25,158,8,8 + EDITTEXT IDC_SCREENS_SRC_END,33,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "% of the",IDC_STATIC,52,158,27,8 + COMBOBOX IDC_SCREENS_SRC_SIDE,80,156,48,69,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "of",IDC_STATIC,129,158,8,8 + COMBOBOX IDC_SCREENS_SRC_SCREEN,139,156,59,53,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + LTEXT "goes to",IDC_STATIC,200,158,24,8 + EDITTEXT IDC_SCREENS_DST_START,225,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "to",IDC_STATIC,243,158,8,8 + EDITTEXT IDC_SCREENS_DST_END,251,156,16,12,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "% of",IDC_STATIC,270,158,15,8 + COMBOBOX IDC_SCREENS_DST_SCREEN,287,156,59,53,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_SCREENS_ADD_LINK,7,172,17,14 + PUSHBUTTON "-",IDC_SCREENS_REMOVE_LINK,28,172,17,14 + DEFPUSHBUTTON "OK",IDOK,241,192,50,14 + PUSHBUTTON "Cancel",IDCANCEL,297,192,50,14 + LTEXT "Source edge overlaps an existing edge.", + IDC_SCREENS_OVERLAP_ERROR,72,175,126,8,NOT WS_VISIBLE | + NOT WS_GROUP +END + +IDD_INFO DIALOG DISCARDABLE 0, 0, 186, 95 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Info" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Version:",IDC_STATIC,7,7,26,8 + EDITTEXT IDC_INFO_VERSION,52,7,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "Hostname:",IDC_STATIC,7,19,35,8 + EDITTEXT IDC_INFO_HOSTNAME,52,19,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "IP Address:",IDC_STATIC,7,31,37,8 + EDITTEXT IDC_INFO_IP_ADDRESS,52,31,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "User Config:",IDC_STATIC,7,43,40,8 + EDITTEXT IDC_INFO_USER_CONFIG,52,43,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LTEXT "Sys Config:",IDC_STATIC,7,55,36,8 + EDITTEXT IDC_INFO_SYS_CONFIG,52,55,127,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + DEFPUSHBUTTON "OK",IDOK,129,74,50,14 +END + +IDD_HOTKEY_OPTIONS DIALOG DISCARDABLE 0, 0, 360, 151 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Hot Keys" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Hot Keys:",IDC_STATIC,7,7,32,8 + LISTBOX IDC_HOTKEY_HOTKEYS,7,18,169,88,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_HOTKEY_ADD_HOTKEY,7,109,17,14 + PUSHBUTTON "-",IDC_HOTKEY_REMOVE_HOTKEY,28,109,17,14 + PUSHBUTTON "Edit",IDC_HOTKEY_EDIT_HOTKEY,49,109,24,14 + LTEXT "&Actions:",IDC_STATIC,183,7,26,8 + LISTBOX IDC_HOTKEY_ACTIONS,183,18,169,88,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "+",IDC_HOTKEY_ADD_ACTION,183,109,17,14 + PUSHBUTTON "-",IDC_HOTKEY_REMOVE_ACTION,204,109,17,14 + PUSHBUTTON "Edit",IDC_HOTKEY_EDIT_ACTION,225,109,24,14 + DEFPUSHBUTTON "OK",IDOK,302,130,50,14 +END + +IDD_HOTKEY_CONDITION DIALOG DISCARDABLE 0, 0, 183, 58 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Hot Key" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Enter &new hot key or mouse button:",IDC_STATIC,7,7,113, + 8 + EDITTEXT IDC_HOTKEY_CONDITION_HOTKEY,7,17,169,12,ES_WANTRETURN + DEFPUSHBUTTON "OK",IDOK,70,37,50,14 + PUSHBUTTON "Cancel",IDCANCEL,126,37,50,14 +END + +IDD_HOTKEY_ACTION DIALOG DISCARDABLE 0, 0, 183, 218 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Action" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Action:",IDC_STATIC,7,7,23,8 + CONTROL "Press:",IDC_HOTKEY_ACTION_DOWN,"Button", + BS_AUTORADIOBUTTON | WS_TABSTOP,7,19,35,10 + CONTROL "Release:",IDC_HOTKEY_ACTION_UP,"Button", + BS_AUTORADIOBUTTON,7,31,44,10 + CONTROL "Press && Release:",IDC_HOTKEY_ACTION_DOWNUP,"Button", + BS_AUTORADIOBUTTON,7,43,69,10 + CONTROL "Switch To Screen:",IDC_HOTKEY_ACTION_SWITCH_TO,"Button", + BS_AUTORADIOBUTTON,7,85,75,10 + CONTROL "Switch In Direction:",IDC_HOTKEY_ACTION_SWITCH_IN, + "Button",BS_AUTORADIOBUTTON,7,101,77,10 + CONTROL "Lock Cursor to Screen:",IDC_HOTKEY_ACTION_LOCK,"Button", + BS_AUTORADIOBUTTON,7,117,89,10 + CONTROL "Keyboard broadcasting:", + IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST,"Button", + BS_AUTORADIOBUTTON,7,133,89,10 + LTEXT "&Hot key or mouse button:",IDC_STATIC,7,55,80,8 + EDITTEXT IDC_HOTKEY_ACTION_HOTKEY,7,67,152,12,ES_WANTRETURN + PUSHBUTTON "...",IDC_HOTKEY_ACTION_SCREENS,162,67,14,12 + COMBOBOX IDC_HOTKEY_ACTION_SWITCH_TO_LIST,87,83,89,62, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_HOTKEY_ACTION_SWITCH_IN_LIST,106,99,70,66, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_HOTKEY_ACTION_LOCK_LIST,106,115,70,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST,106,131,53,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "...",IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS, + 162,131,14,12 + LTEXT "Action takes place &when:",IDC_STATIC,7,153,81,8 + CONTROL "Hot key is pressed",IDC_HOTKEY_ACTION_ON_ACTIVATE, + "Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,165,74,10 + CONTROL "Hot key is released",IDC_HOTKEY_ACTION_ON_DEACTIVATE, + "Button",BS_AUTORADIOBUTTON,7,177,76,10 + DEFPUSHBUTTON "OK",IDOK,70,197,50,14 + PUSHBUTTON "Cancel",IDCANCEL,126,197,50,14 +END + +IDD_HOTKEY_SCREENS DIALOG DISCARDABLE 0, 0, 237, 79 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Target Screens" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "&Available screens:",IDC_STATIC,7,7,58,8 + LISTBOX IDC_HOTKEY_SCREENS_SRC,7,17,100,36,LBS_NOINTEGRALHEIGHT | + LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_HOTKEY_SCREENS_DST,130,17,100,36, + LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | + WS_TABSTOP + PUSHBUTTON "-->",IDC_HOTKEY_SCREENS_ADD,109,21,17,14 + PUSHBUTTON "<--",IDC_HOTKEY_SCREENS_REMOVE,109,38,17,14 + DEFPUSHBUTTON "OK",IDOK,124,58,50,14 + PUSHBUTTON "Cancel",IDCANCEL,180,58,50,14 + LTEXT "&Send action to screens:",IDC_STATIC,130,7,76,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_MAIN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 192 + END + + IDD_ADD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 185 + TOPMARGIN, 7 + BOTTOMMARGIN, 247 + END + + IDD_WAIT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 47 + END + + IDD_AUTOSTART, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 188 + TOPMARGIN, 7 + BOTTOMMARGIN, 182 + END + + IDD_GLOBAL_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 200 + TOPMARGIN, 7 + BOTTOMMARGIN, 283 + END + + IDD_ADVANCED_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 223 + TOPMARGIN, 7 + BOTTOMMARGIN, 179 + END + + IDD_SCREENS_LINKS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 347 + TOPMARGIN, 7 + BOTTOMMARGIN, 206 + END + + IDD_INFO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 88 + END + + IDD_HOTKEY_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 353 + TOPMARGIN, 7 + BOTTOMMARGIN, 144 + END + + IDD_HOTKEY_CONDITION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 176 + TOPMARGIN, 7 + BOTTOMMARGIN, 51 + END + + IDD_HOTKEY_ACTION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 176 + TOPMARGIN, 7 + BOTTOMMARGIN, 195 + END + + IDD_HOTKEY_SCREENS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 230 + TOPMARGIN, 7 + BOTTOMMARGIN, 72 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SYNERGY ICON DISCARDABLE "synergy.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_ERROR "Error" + IDS_INVALID_SCREEN_NAME "Screen name `%{1}' is invalid." + IDS_DUPLICATE_SCREEN_NAME "The screen name `%{1}' is already being used." + IDS_SCREEN_NAME_IS_ALIAS "A name may not be an alias of itself." + IDS_VERIFY "Confirm" + IDS_UNSAVED_DATA_REALLY_QUIT "You have unsaved changes. Really quit?" + IDS_UNKNOWN_SCREEN_NAME "The screen name `%{1}' is not in the layout." + IDS_INVALID_PORT "The port `%{1}' is invalid. It must be between 1 and 65535 inclusive. %{2} is the standard port." + IDS_SAVE_FAILED "Failed to save configuration: %{1}" + IDS_STARTUP_FAILED "Failed to start synergy: %{1}" + IDS_STARTED_TITLE "Started" + IDS_STARTED "Synergy was successfully started. Use the task manager or tray icon to terminate it." + IDS_UNINSTALL_TITLE "Removed Auto-Start" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_AUTOSTART_PERMISSION_SYSTEM + "You have sufficient access rights to install and uninstall Auto Start for all users." + IDS_AUTOSTART_PERMISSION_USER + "You have sufficient access rights to install and uninstall Auto Start for just yourself." + IDS_AUTOSTART_PERMISSION_NONE + "You do not have sufficient access rights to install or uninstall Auto Start." + IDS_AUTOSTART_INSTALLED_SYSTEM + "Synergy is configured to start automatically when the system starts." + IDS_AUTOSTART_INSTALLED_USER + "Synergy is configured to start automatically when you log in." + IDS_AUTOSTART_INSTALLED_NONE + "Synergy is not configured to start automatically." + IDS_INSTALL_LABEL "Install" + IDS_UNINSTALL_LABEL "Uninstall" + IDS_INSTALL_GENERIC_ERROR "Install failed: %{1}" + IDS_UNINSTALL_GENERIC_ERROR "Uninstall failed: %{1}" + IDS_INSTALL_TITLE "Installed Auto-Start" + IDS_INSTALLED_SYSTEM "Installed auto-start. Synergy will automatically start each time you start your computer." + IDS_INSTALLED_USER "Installed auto-start. Synergy will automatically start each time you log in." +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_UNINSTALLED_SYSTEM "Removed auto-start. Synergy will not automatically start each time you start or reboot your computer." + IDS_UNINSTALLED_USER "Removed auto-start. Synergy will not automatically start each time you log in." + IDS_INVALID_SERVER_NAME "Server name `%{1}' is invalid." + IDS_TITLE "Synergy - Version %{1}" + IDS_SERVER_IS_CLIENT "Please enter the name of the computer sharing a\nkeyboard and mouse, not the name of this computer,\nin the Other Computer's Host Name field." + IDS_ADD_SCREEN "Add Screen" + IDS_EDIT_SCREEN "Edit Screen %{1}" + IDS_ERROR_CODE "Error code: %{1}" + IDS_AUTOSTART_PERMISSION_ALL + "You have sufficient access rights to install and uninstall Auto Start for all users or for just yourself." + IDS_INVALID_INTERFACE_NAME "The interface '%{1}' is invalid: %{2}" + IDS_INVALID_CORNER_SIZE "The dead corner size %{1} is invalid; it must be 0 or higher." + IDS_NEW_LINK "[New Link]" + IDS_SIDE_LEFT "left of" + IDS_SIDE_RIGHT "right of" + IDS_SIDE_TOP "above" + IDS_SIDE_BOTTOM "below" +END + +STRINGTABLE DISCARDABLE +BEGIN + IDS_LINK_FORMAT "%{4}%{5} is %{3} %{1}%{2}" + IDS_LINK_INTERVAL_FORMAT "(%{1},%{2})" + IDS_EDGE_LEFT "left" + IDS_EDGE_RIGHT "right" + IDS_EDGE_TOP "top" + IDS_EDGE_BOTTOM "bottom" + IDS_AUTOSTART_SAVE_FAILED "Failed to save autostart configuration: %{1}" + IDS_LOAD_FAILED "Failed to load configuration." + IDS_CONFIG_CHANGED "Configuration changed on disk. Reload?" + IDS_MODE_OFF "off" + IDS_MODE_ON "on" + IDS_MODE_TOGGLE "toggle" + IDS_ALL_SCREENS "All Screens" + IDS_ACTIVE_SCREEN "Active Screen" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/cmd/launcher/resource.h b/cmd/launcher/resource.h new file mode 100644 index 00000000..f284fd62 --- /dev/null +++ b/cmd/launcher/resource.h @@ -0,0 +1,186 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by launcher.rc +// +#define IDS_ERROR 1 +#define IDS_INVALID_SCREEN_NAME 2 +#define IDS_DUPLICATE_SCREEN_NAME 3 +#define IDS_SCREEN_NAME_IS_ALIAS 4 +#define IDS_VERIFY 5 +#define IDS_UNSAVED_DATA_REALLY_QUIT 6 +#define IDS_UNKNOWN_SCREEN_NAME 7 +#define IDS_INVALID_PORT 8 +#define IDS_SAVE_FAILED 9 +#define IDS_STARTUP_FAILED 10 +#define IDS_STARTED_TITLE 11 +#define IDS_STARTED 12 +#define IDS_INSTALL_FAILED 13 +#define IDS_UNINSTALL_TITLE 14 +#define IDS_UNINSTALLED 15 +#define IDS_UNINSTALL_FAILED 16 +#define IDS_CLIENT 17 +#define IDS_SERVER 18 +#define IDS_AUTOSTART_PERMISSION_SYSTEM 19 +#define IDS_AUTOSTART_PERMISSION_USER 20 +#define IDS_AUTOSTART_PERMISSION_NONE 21 +#define IDS_AUTOSTART_INSTALLED_SYSTEM 22 +#define IDS_AUTOSTART_INSTALLED_USER 23 +#define IDS_AUTOSTART_INSTALLED_NONE 24 +#define IDS_INSTALL_LABEL 25 +#define IDS_UNINSTALL_LABEL 26 +#define IDS_INSTALL_GENERIC_ERROR 27 +#define IDS_UNINSTALL_GENERIC_ERROR 28 +#define IDS_INSTALL_TITLE 29 +#define IDS_INSTALLED_SYSTEM 30 +#define IDS_INSTALLED_USER 31 +#define IDS_UNINSTALLED_SYSTEM 32 +#define IDS_UNINSTALLED_USER 33 +#define IDS_INVALID_SERVER_NAME 34 +#define IDS_TITLE 35 +#define IDS_SERVER_IS_CLIENT 36 +#define IDS_ADD_SCREEN 37 +#define IDS_EDIT_SCREEN 38 +#define IDS_INVALID_TIME 39 +#define IDS_ERROR_CODE 39 +#define IDS_AUTOSTART_PERMISSION_ALL 40 +#define IDS_INVALID_INTERFACE_NAME 41 +#define IDS_INVALID_CORNER_SIZE 42 +#define IDS_NEW_LINK 43 +#define IDS_SIDE_LEFT 44 +#define IDS_SIDE_RIGHT 45 +#define IDS_SIDE_TOP 46 +#define IDS_SIDE_BOTTOM 47 +#define IDS_LINK_FORMAT 48 +#define IDS_LINK_INTERVAL_FORMAT 49 +#define IDS_EDGE_LEFT 50 +#define IDS_EDGE_RIGHT 51 +#define IDS_EDGE_TOP 52 +#define IDS_EDGE_BOTTOM 53 +#define IDS_AUTOSTART_SAVE_FAILED 54 +#define IDS_LOAD_FAILED 55 +#define IDS_CONFIG_CHANGED 56 +#define IDS_MODE_OFF 57 +#define IDS_MODE_ON 58 +#define IDS_MODE_TOGGLE 59 +#define IDS_ALL_SCREENS 60 +#define IDS_ACTIVE_SCREEN 61 +#define IDD_MAIN 101 +#define IDD_ADD 102 +#define IDD_WAIT 103 +#define IDI_SYNERGY 104 +#define IDD_AUTOSTART 105 +#define IDD_ADVANCED_OPTIONS 106 +#define IDD_GLOBAL_OPTIONS 107 +#define IDD_SCREENS_LINKS 110 +#define IDD_INFO 111 +#define IDD_HOTKEY_OPTIONS 112 +#define IDD_HOTKEY_CONDITION 113 +#define IDD_HOTKEY_ACTION 114 +#define IDD_HOTKEY_SCREENS 115 +#define IDC_MAIN_CLIENT_RADIO 1000 +#define IDC_MAIN_SERVER_RADIO 1001 +#define IDC_MAIN_CLIENT_SERVER_NAME_LABEL 1002 +#define IDC_MAIN_CLIENT_SERVER_NAME_EDIT 1003 +#define IDC_MAIN_SERVER_SCREENS_LABEL 1004 +#define IDC_MAIN_SCREENS 1005 +#define IDC_MAIN_OPTIONS 1006 +#define IDC_MAIN_ADVANCED 1007 +#define IDC_MAIN_AUTOSTART 1008 +#define IDC_MAIN_TEST 1009 +#define IDC_MAIN_SAVE 1010 +#define IDC_MAIN_HOTKEYS 1010 +#define IDC_MAIN_DEBUG 1011 +#define IDC_ADD_SCREEN_NAME_EDIT 1020 +#define IDC_ADD_ALIASES_EDIT 1021 +#define IDC_AUTOSTART_INSTALLED_MSG 1031 +#define IDC_AUTOSTART_PERMISSION_MSG 1032 +#define IDC_AUTOSTART_INSTALL_USER 1033 +#define IDC_AUTOSTART_INSTALL_SYSTEM 1034 +#define IDC_ADD_HD_CAPS_CHECK 1037 +#define IDC_ADD_HD_NUM_CHECK 1038 +#define IDC_ADVANCED_NAME_EDIT 1038 +#define IDC_ADVANCED_PORT_EDIT 1039 +#define IDC_ADD_HD_SCROLL_CHECK 1039 +#define IDC_ADVANCED_INTERFACE_EDIT 1040 +#define IDC_GLOBAL_DELAY_CHECK 1041 +#define IDC_GLOBAL_DELAY_TIME 1042 +#define IDC_GLOBAL_TWO_TAP_CHECK 1043 +#define IDC_ADD_MOD_SHIFT 1043 +#define IDC_GLOBAL_TWO_TAP_TIME 1044 +#define IDC_ADD_MOD_CTRL 1044 +#define IDC_ADD_MOD_ALT 1045 +#define IDC_GLOBAL_HEARTBEAT_CHECK 1045 +#define IDC_ADD_MOD_META 1046 +#define IDC_GLOBAL_HEARTBEAT_TIME 1046 +#define IDC_ADD_MOD_SUPER 1047 +#define IDC_GLOBAL_SCREENSAVER_SYNC 1047 +#define IDC_GLOBAL_RELATIVE_MOVES 1048 +#define IDC_ADVANCED_DEFAULTS 1049 +#define IDC_GLOBAL_LEAVE_FOREGROUND 1049 +#define IDC_ADD_DC_SIZE 1052 +#define IDC_ADD_DC_TOP_LEFT 1053 +#define IDC_ADD_DC_TOP_RIGHT 1054 +#define IDC_ADD_DC_BOTTOM_LEFT 1055 +#define IDC_ADD_DC_BOTTOM_RIGHT 1056 +#define IDC_SCREENS_SRC_SIDE 1057 +#define IDC_SCREENS_SRC_START 1058 +#define IDC_SCREENS_SRC_END 1059 +#define IDC_SCREENS_SRC_SCREEN 1060 +#define IDC_SCREENS_SCREENS 1061 +#define IDC_SCREENS_ADD_SCREEN 1062 +#define IDC_SCREENS_LINKS 1063 +#define IDC_SCREENS_DST_START 1064 +#define IDC_SCREENS_DST_END 1065 +#define IDC_SCREENS_DST_SCREEN 1066 +#define IDC_SCREENS_REMOVE_SCREEN 1067 +#define IDC_SCREENS_EDIT_SCREEN 1068 +#define IDC_SCREENS_ADD_LINK 1069 +#define IDC_SCREENS_REMOVE_LINK 1070 +#define IDC_SCREENS_OVERLAP_ERROR 1071 +#define IDC_INFO_VERSION 1073 +#define IDC_MAIN_INFO 1074 +#define IDC_INFO_HOSTNAME 1076 +#define IDC_HOTKEY_HOTKEYS 1076 +#define IDC_INFO_IP_ADDRESS 1077 +#define IDC_HOTKEY_ADD_HOTKEY 1077 +#define IDC_INFO_USER_CONFIG 1078 +#define IDC_HOTKEY_REMOVE_HOTKEY 1078 +#define IDC_INFO_SYS_CONFIG 1079 +#define IDC_HOTKEY_EDIT_HOTKEY 1079 +#define IDC_HOTKEY_ACTIONS 1080 +#define IDC_HOTKEY_CONDITION_HOTKEY 1080 +#define IDC_HOTKEY_ACTION_DOWNUP 1081 +#define IDC_HOTKEY_ADD_ACTION 1082 +#define IDC_HOTKEY_ACTION_DOWN 1082 +#define IDC_HOTKEY_REMOVE_ACTION 1083 +#define IDC_HOTKEY_ACTION_UP 1083 +#define IDC_HOTKEY_EDIT_ACTION 1084 +#define IDC_HOTKEY_ACTION_HOTKEY 1085 +#define IDC_HOTKEY_ACTION_SWITCH_TO_LIST 1086 +#define IDC_HOTKEY_ACTION_SWITCH_TO 1087 +#define IDC_HOTKEY_ACTION_SWITCH_IN 1088 +#define IDC_HOTKEY_ACTION_LOCK 1089 +#define IDC_HOTKEY_ACTION_SWITCH_IN_LIST 1090 +#define IDC_HOTKEY_ACTION_LOCK_LIST 1091 +#define IDC_HOTKEY_ACTION_ON_ACTIVATE 1092 +#define IDC_HOTKEY_ACTION_ON_DEACTIVATE 1093 +#define IDC_HOTKEY_ACTION_SCREENS 1094 +#define IDC_HOTKEY_SCREENS_SRC 1095 +#define IDC_HOTKEY_SCREENS_DST 1096 +#define IDC_HOTKEY_SCREENS_ADD 1097 +#define IDC_HOTKEY_SCREENS_REMOVE 1098 +#define IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST 1099 +#define IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_LIST 1100 +#define IDC_HOTKEY_ACTION_KEYBOARD_BROADCAST_SCREENS 1101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 116 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1102 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cmd/launcher/synergy.ico b/cmd/launcher/synergy.ico new file mode 100644 index 00000000..89f965f4 Binary files /dev/null and b/cmd/launcher/synergy.ico differ diff --git a/cmd/synergyc/CClientTaskBarReceiver.cpp b/cmd/synergyc/CClientTaskBarReceiver.cpp new file mode 100644 index 00000000..025b43f6 --- /dev/null +++ b/cmd/synergyc/CClientTaskBarReceiver.cpp @@ -0,0 +1,136 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientTaskBarReceiver.h" +#include "CClient.h" +#include "CLock.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "CArch.h" +#include "Version.h" + +// +// CClientTaskBarReceiver +// + +CClientTaskBarReceiver::CClientTaskBarReceiver() : + m_state(kNotRunning) +{ + // do nothing +} + +CClientTaskBarReceiver::~CClientTaskBarReceiver() +{ + // do nothing +} + +void +CClientTaskBarReceiver::updateStatus(CClient* client, const CString& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (client == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_server = client->getServerAddress().getHostname(); + + if (client->isConnected()) { + m_state = kConnected; + } + else if (client->isConnecting()) { + m_state = kConnecting; + } + else { + m_state = kNotConnected; + } + } + + // let subclasses have a go + onStatusChanged(client); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +CClientTaskBarReceiver::EState +CClientTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const CString& +CClientTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +void +CClientTaskBarReceiver::quit() +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CClientTaskBarReceiver::onStatusChanged(CClient*) +{ + // do nothing +} + +void +CClientTaskBarReceiver::lock() const +{ + // do nothing +} + +void +CClientTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +CClientTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return CStringUtil::print("%s: Not running", kAppVersion); + + case kNotWorking: + return CStringUtil::print("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return CStringUtil::print("%s: Not connected: %s", + kAppVersion, m_errorMessage.c_str()); + + case kConnecting: + return CStringUtil::print("%s: Connecting to %s...", + kAppVersion, m_server.c_str()); + + case kConnected: + return CStringUtil::print("%s: Connected to %s", + kAppVersion, m_server.c_str()); + + default: + return ""; + } +} diff --git a/cmd/synergyc/CClientTaskBarReceiver.h b/cmd/synergyc/CClientTaskBarReceiver.h new file mode 100644 index 00000000..0e3440e1 --- /dev/null +++ b/cmd/synergyc/CClientTaskBarReceiver.h @@ -0,0 +1,83 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTTASKBARRECEIVER_H +#define CCLIENTTASKBARRECEIVER_H + +#include "CString.h" +#include "IArchTaskBarReceiver.h" + +class CClient; + +//! Implementation of IArchTaskBarReceiver for the synergy server +class CClientTaskBarReceiver : public IArchTaskBarReceiver { +public: + CClientTaskBarReceiver(); + virtual ~CClientTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the client. + */ + void updateStatus(CClient*, const CString& errorMsg); + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnecting, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const CString& getErrorMessage() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does nothing. + */ + virtual void onStatusChanged(CClient* client); + +private: + EState m_state; + CString m_errorMessage; + CString m_server; +}; + +#endif diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp new file mode 100644 index 00000000..c002b9f1 --- /dev/null +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.cpp @@ -0,0 +1,346 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClientTaskBarReceiver.h" +#include "CClient.h" +#include "CMSWindowsClipboard.h" +#include "LogOutputters.h" +#include "BasicTypes.h" +#include "CArch.h" +#include "CArchTaskBarWindows.h" +#include "resource.h" + +// +// CMSWindowsClientTaskBarReceiver +// + +const UINT CMSWindowsClientTaskBarReceiver::s_stateToIconID[kMaxState] = +{ + IDI_TASKBAR_NOT_RUNNING, + IDI_TASKBAR_NOT_WORKING, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_CONNECTED +}; + +CMSWindowsClientTaskBarReceiver::CMSWindowsClientTaskBarReceiver( + HINSTANCE appInstance, const CBufferedLogOutputter* logBuffer) : + CClientTaskBarReceiver(), + m_appInstance(appInstance), + m_window(NULL), + m_logBuffer(logBuffer) +{ + for (UInt32 i = 0; i < kMaxState; ++i) { + m_icon[i] = loadIcon(s_stateToIconID[i]); + } + m_menu = LoadMenu(m_appInstance, MAKEINTRESOURCE(IDR_TASKBAR)); + + // don't create the window yet. we'll create it on demand. this + // has the side benefit of being created in the thread used for + // the task bar. that's good because it means the existence of + // the window won't prevent changing the main thread's desktop. + + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CMSWindowsClientTaskBarReceiver::~CMSWindowsClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); + for (UInt32 i = 0; i < kMaxState; ++i) { + deleteIcon(m_icon[i]); + } + DestroyMenu(m_menu); + destroyWindow(); +} + +void +CMSWindowsClientTaskBarReceiver::showStatus() +{ + // create the window + createWindow(); + + // lock self while getting status + lock(); + + // get the current status + std::string status = getToolTip(); + + // done getting status + unlock(); + + // update dialog + HWND child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_STATUS); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)status.c_str()); + + if (!IsWindowVisible(m_window)) { + // position it by the mouse + POINT cursorPos; + GetCursorPos(&cursorPos); + RECT windowRect; + GetWindowRect(m_window, &windowRect); + int x = cursorPos.x; + int y = cursorPos.y; + int fw = GetSystemMetrics(SM_CXDLGFRAME); + int fh = GetSystemMetrics(SM_CYDLGFRAME); + int ww = windowRect.right - windowRect.left; + int wh = windowRect.bottom - windowRect.top; + int sw = GetSystemMetrics(SM_CXFULLSCREEN); + int sh = GetSystemMetrics(SM_CYFULLSCREEN); + if (fw < 1) { + fw = 1; + } + if (fh < 1) { + fh = 1; + } + if (x + ww - fw > sw) { + x -= ww - fw; + } + else { + x -= fw; + } + if (x < 0) { + x = 0; + } + if (y + wh - fh > sh) { + y -= wh - fh; + } + else { + y -= fh; + } + if (y < 0) { + y = 0; + } + SetWindowPos(m_window, HWND_TOPMOST, x, y, ww, wh, + SWP_SHOWWINDOW); + } +} + +void +CMSWindowsClientTaskBarReceiver::runMenu(int x, int y) +{ + // do popup menu. we need a window to pass to TrackPopupMenu(). + // the SetForegroundWindow() and SendMessage() calls around + // TrackPopupMenu() are to get the menu to be dismissed when + // another window gets activated and are just one of those + // win32 weirdnesses. + createWindow(); + SetForegroundWindow(m_window); + HMENU menu = GetSubMenu(m_menu, 0); + SetMenuDefaultItem(menu, IDC_TASKBAR_STATUS, FALSE); + HMENU logLevelMenu = GetSubMenu(menu, 3); + CheckMenuRadioItem(logLevelMenu, 0, 6, + CLOG->getFilter() - CLog::kERROR, MF_BYPOSITION); + int n = TrackPopupMenu(menu, + TPM_NONOTIFY | + TPM_RETURNCMD | + TPM_LEFTBUTTON | + TPM_RIGHTBUTTON, + x, y, 0, m_window, NULL); + SendMessage(m_window, WM_NULL, 0, 0); + + // perform the requested operation + switch (n) { + case IDC_TASKBAR_STATUS: + showStatus(); + break; + + case IDC_TASKBAR_LOG: + copyLog(); + break; + + case IDC_TASKBAR_SHOW_LOG: + ARCH->showConsole(true); + break; + + case IDC_TASKBAR_LOG_LEVEL_ERROR: + CLOG->setFilter(CLog::kERROR); + break; + + case IDC_TASKBAR_LOG_LEVEL_WARNING: + CLOG->setFilter(CLog::kWARNING); + break; + + case IDC_TASKBAR_LOG_LEVEL_NOTE: + CLOG->setFilter(CLog::kNOTE); + break; + + case IDC_TASKBAR_LOG_LEVEL_INFO: + CLOG->setFilter(CLog::kINFO); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG: + CLOG->setFilter(CLog::kDEBUG); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG1: + CLOG->setFilter(CLog::kDEBUG1); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG2: + CLOG->setFilter(CLog::kDEBUG2); + break; + + case IDC_TASKBAR_QUIT: + quit(); + break; + } +} + +void +CMSWindowsClientTaskBarReceiver::primaryAction() +{ + showStatus(); +} + +const IArchTaskBarReceiver::Icon +CMSWindowsClientTaskBarReceiver::getIcon() const +{ + return reinterpret_cast(m_icon[getStatus()]); +} + +void +CMSWindowsClientTaskBarReceiver::copyLog() const +{ + if (m_logBuffer != NULL) { + // collect log buffer + CString data; + for (CBufferedLogOutputter::const_iterator index = m_logBuffer->begin(); + index != m_logBuffer->end(); ++index) { + data += *index; + data += "\n"; + } + + // copy log to clipboard + if (!data.empty()) { + CMSWindowsClipboard clipboard(m_window); + clipboard.open(0); + clipboard.emptyUnowned(); + clipboard.add(IClipboard::kText, data); + clipboard.close(); + } + } +} + +void +CMSWindowsClientTaskBarReceiver::onStatusChanged() +{ + if (IsWindowVisible(m_window)) { + showStatus(); + } +} + +HICON +CMSWindowsClientTaskBarReceiver::loadIcon(UINT id) +{ + HANDLE icon = LoadImage(m_appInstance, + MAKEINTRESOURCE(id), + IMAGE_ICON, + 0, 0, + LR_DEFAULTCOLOR); + return reinterpret_cast(icon); +} + +void +CMSWindowsClientTaskBarReceiver::deleteIcon(HICON icon) +{ + if (icon != NULL) { + DestroyIcon(icon); + } +} + +void +CMSWindowsClientTaskBarReceiver::createWindow() +{ + // ignore if already created + if (m_window != NULL) { + return; + } + + // get the status dialog + m_window = CreateDialogParam(m_appInstance, + MAKEINTRESOURCE(IDD_TASKBAR_STATUS), + NULL, + (DLGPROC)&CMSWindowsClientTaskBarReceiver::staticDlgProc, + reinterpret_cast( + reinterpret_cast(this))); + + // window should appear on top of everything, including (especially) + // the task bar. + LONG_PTR style = GetWindowLongPtr(m_window, GWL_EXSTYLE); + style |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + SetWindowLongPtr(m_window, GWL_EXSTYLE, style); + + // tell the task bar about this dialog + CArchTaskBarWindows::addDialog(m_window); +} + +void +CMSWindowsClientTaskBarReceiver::destroyWindow() +{ + if (m_window != NULL) { + CArchTaskBarWindows::removeDialog(m_window); + DestroyWindow(m_window); + m_window = NULL; + } +} + +BOOL +CMSWindowsClientTaskBarReceiver::dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + // use default focus + return TRUE; + + case WM_ACTIVATE: + // hide when another window is activated + if (LOWORD(wParam) == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + return FALSE; +} + +BOOL CALLBACK +CMSWindowsClientTaskBarReceiver::staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_INITDIALOG, extract the CMSWindowsClientTaskBarReceiver* + // and put it in the extra window data then forward the call. + CMSWindowsClientTaskBarReceiver* self = NULL; + if (msg == WM_INITDIALOG) { + self = reinterpret_cast( + reinterpret_cast(lParam)); + SetWindowLong(hwnd, GWL_USERDATA, lParam); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, GWL_USERDATA); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->dlgProc(hwnd, msg, wParam, lParam); + } + else { + return (msg == WM_INITDIALOG) ? TRUE : FALSE; + } +} diff --git a/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h new file mode 100644 index 00000000..72d33be5 --- /dev/null +++ b/cmd/synergyc/CMSWindowsClientTaskBarReceiver.h @@ -0,0 +1,64 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIENTTASKBARRECEIVER_H +#define CMSWINDOWSCLIENTTASKBARRECEIVER_H + +#define WIN32_LEAN_AND_MEAN + +#include "CClientTaskBarReceiver.h" +#include + +class CBufferedLogOutputter; + +//! Implementation of CClientTaskBarReceiver for Microsoft Windows +class CMSWindowsClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + CMSWindowsClientTaskBarReceiver(HINSTANCE, const CBufferedLogOutputter*); + virtual ~CMSWindowsClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + void copyLog() const; + + // CClientTaskBarReceiver overrides + virtual void onStatusChanged(); + +private: + HICON loadIcon(UINT); + void deleteIcon(HICON); + void createWindow(); + void destroyWindow(); + + BOOL dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + static BOOL CALLBACK + staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + +private: + HINSTANCE m_appInstance; + HWND m_window; + HMENU m_menu; + HICON m_icon[kMaxState]; + const CBufferedLogOutputter* m_logBuffer; + static const UINT s_stateToIconID[]; +}; + +#endif diff --git a/cmd/synergyc/COSXClientTaskBarReceiver.cpp b/cmd/synergyc/COSXClientTaskBarReceiver.cpp new file mode 100644 index 00000000..c380ac4d --- /dev/null +++ b/cmd/synergyc/COSXClientTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXClientTaskBarReceiver.h" +#include "CArch.h" + +// +// COSXClientTaskBarReceiver +// + +COSXClientTaskBarReceiver::COSXClientTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +COSXClientTaskBarReceiver::~COSXClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +COSXClientTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +COSXClientTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +COSXClientTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +COSXClientTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergyc/COSXClientTaskBarReceiver.h b/cmd/synergyc/COSXClientTaskBarReceiver.h new file mode 100644 index 00000000..59bca97c --- /dev/null +++ b/cmd/synergyc/COSXClientTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIENTTASKBARRECEIVER_H +#define COSXCLIENTTASKBARRECEIVER_H + +#include "CClientTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CClientTaskBarReceiver for OS X +class COSXClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + COSXClientTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~COSXClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp new file mode 100644 index 00000000..681f9be5 --- /dev/null +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClientTaskBarReceiver.h" +#include "CArch.h" + +// +// CXWindowsClientTaskBarReceiver +// + +CXWindowsClientTaskBarReceiver::CXWindowsClientTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CXWindowsClientTaskBarReceiver::~CXWindowsClientTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +CXWindowsClientTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +CXWindowsClientTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +CXWindowsClientTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +CXWindowsClientTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergyc/CXWindowsClientTaskBarReceiver.h b/cmd/synergyc/CXWindowsClientTaskBarReceiver.h new file mode 100644 index 00000000..fa9da471 --- /dev/null +++ b/cmd/synergyc/CXWindowsClientTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIENTTASKBARRECEIVER_H +#define CXWINDOWSCLIENTTASKBARRECEIVER_H + +#include "CClientTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CClientTaskBarReceiver for X Windows +class CXWindowsClientTaskBarReceiver : public CClientTaskBarReceiver { +public: + CXWindowsClientTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~CXWindowsClientTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergyc/Makefile.am b/cmd/synergyc/Makefile.am new file mode 100644 index 00000000..b6e8c83d --- /dev/null +++ b/cmd/synergyc/Makefile.am @@ -0,0 +1,98 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +COMMON_SOURCE_FILES = \ + CClientTaskBarReceiver.cpp \ + CClientTaskBarReceiver.h \ + synergyc.cpp \ + $(NULL) +XWINDOWS_SOURCE_FILES = \ + CXWindowsClientTaskBarReceiver.cpp \ + CXWindowsClientTaskBarReceiver.h \ + $(NULL) +MSWINDOWS_SOURCE_FILES = \ + CMSWindowsClientTaskBarReceiver.cpp \ + CMSWindowsClientTaskBarReceiver.h \ + resource.h \ + synergyc.rc \ + $(NULL) +CARBON_SOURCE_FILES = \ + COSXClientTaskBarReceiver.cpp \ + COSXClientTaskBarReceiver.h \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + synergyc.ico \ + tb_error.ico \ + tb_idle.ico \ + tb_run.ico \ + tb_wait.ico \ + $(XWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +bin_PROGRAMS = synergyc +if XWINDOWS +synergyc_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(XWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if MSWINDOWS +synergyc_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if CARBON +synergyc_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) +synergyc_LDFLAGS = \ + -framework ScreenSaver \ + -framework IOKit \ + -framework ApplicationServices \ + -framework Foundation \ + $(NULL) +endif +synergyc_LDADD = \ + $(top_builddir)/lib/client/libclient.a \ + $(top_builddir)/lib/platform/libplatform.a \ + $(top_builddir)/lib/synergy/libsynergy.a \ + $(top_builddir)/lib/net/libnet.a \ + $(top_builddir)/lib/io/libio.a \ + $(top_builddir)/lib/mt/libmt.a \ + $(top_builddir)/lib/base/libbase.a \ + $(top_builddir)/lib/common/libcommon.a \ + $(top_builddir)/lib/arch/libarch.a \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + -I$(top_srcdir)/lib/client \ + $(NULL) diff --git a/cmd/synergyc/Makefile.win b/cmd/synergyc/Makefile.win new file mode 100644 index 00000000..29f2e516 --- /dev/null +++ b/cmd/synergyc/Makefile.win @@ -0,0 +1,89 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +BIN_SYNERGYC_SRC = cmd\synergyc +BIN_SYNERGYC_DST = $(BUILD_DST)\$(BIN_SYNERGYC_SRC) +BIN_SYNERGYC_EXE = "$(BUILD_DST)\synergyc.exe" +BIN_SYNERGYC_CPP = \ + "CClientTaskBarReceiver.cpp" \ + "CMSWindowsClientTaskBarReceiver.cpp" \ + "synergyc.cpp" \ + $(NULL) +BIN_SYNERGYC_OBJ = \ + "$(BIN_SYNERGYC_DST)\CClientTaskBarReceiver.obj" \ + "$(BIN_SYNERGYC_DST)\CMSWindowsClientTaskBarReceiver.obj" \ + "$(BIN_SYNERGYC_DST)\synergyc.obj" \ + $(NULL) +BIN_SYNERGYC_RC = "$(BIN_SYNERGYC_SRC)\synergyc.rc" +BIN_SYNERGYC_RES = "$(BIN_SYNERGYC_DST)\synergyc.res" +BIN_SYNERGYC_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + /I"lib\client" \ + $(NULL) +BIN_SYNERGYC_LIB = \ + $(LIB_CLIENT_LIB) \ + $(LIB_PLATFORM_LIB) \ + $(LIB_SYNERGY_LIB) \ + $(LIB_NET_LIB) \ + $(LIB_IO_LIB) \ + $(LIB_MT_LIB) \ + $(LIB_BASE_LIB) \ + $(LIB_ARCH_LIB) \ + $(LIB_COMMON_LIB) \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(BIN_SYNERGYC_CPP) +OBJ_FILES = $(OBJ_FILES) $(BIN_SYNERGYC_OBJ) +PROGRAMS = $(PROGRAMS) $(BIN_SYNERGYC_EXE) + +# Need shell functions. +guilibs = $(guilibs) shell32.lib + +# Dependency rules +$(BIN_SYNERGYC_OBJ): $(AUTODEP) +!if EXIST($(BIN_SYNERGYC_DST)\deps.mak) +!include $(BIN_SYNERGYC_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(BIN_SYNERGYC_SRC)\}.cpp{$(BIN_SYNERGYC_DST)\}.obj:: +!else +{$(BIN_SYNERGYC_SRC)\}.cpp{$(BIN_SYNERGYC_DST)\}.obj: +!endif + @$(ECHO) Compile in $(BIN_SYNERGYC_SRC) + -@$(MKDIR) $(BIN_SYNERGYC_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(BIN_SYNERGYC_INC) \ + /Fo$(BIN_SYNERGYC_DST)\ \ + /Fd$(BIN_SYNERGYC_DST)\src.pdb \ + $< | $(AUTODEP) $(BIN_SYNERGYC_SRC) $(BIN_SYNERGYC_DST) +$(BIN_SYNERGYC_RES): $(BIN_SYNERGYC_RC) + @$(ECHO) Compile $(**F) + -@$(MKDIR) $(BIN_SYNERGYC_DST) 2>NUL: + $(rc) $(rcflags) $(rcvars) \ + /fo$@ \ + $** +$(BIN_SYNERGYC_EXE): $(BIN_SYNERGYC_OBJ) $(BIN_SYNERGYC_RES) $(BIN_SYNERGYC_LIB) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(guilflags) $(guilibsmt) \ + /out:$@ \ + $** + $(AUTODEP) $(BIN_SYNERGYC_SRC) $(BIN_SYNERGYC_DST) \ + $(BIN_SYNERGYC_OBJ:.obj=.d) diff --git a/cmd/synergyc/resource.h b/cmd/synergyc/resource.h new file mode 100644 index 00000000..eeee6e1e --- /dev/null +++ b/cmd/synergyc/resource.h @@ -0,0 +1,37 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by synergyc.rc +// +#define IDS_FAILED 1 +#define IDS_INIT_FAILED 2 +#define IDS_UNCAUGHT_EXCEPTION 3 +#define IDI_SYNERGY 101 +#define IDI_TASKBAR_NOT_RUNNING 102 +#define IDI_TASKBAR_NOT_WORKING 103 +#define IDI_TASKBAR_NOT_CONNECTED 104 +#define IDI_TASKBAR_CONNECTED 105 +#define IDR_TASKBAR 107 +#define IDD_TASKBAR_STATUS 108 +#define IDC_TASKBAR_STATUS_STATUS 1000 +#define IDC_TASKBAR_QUIT 40001 +#define IDC_TASKBAR_STATUS 40002 +#define IDC_TASKBAR_LOG 40003 +#define IDC_TASKBAR_SHOW_LOG 40004 +#define IDC_TASKBAR_LOG_LEVEL_ERROR 40009 +#define IDC_TASKBAR_LOG_LEVEL_WARNING 40010 +#define IDC_TASKBAR_LOG_LEVEL_NOTE 40011 +#define IDC_TASKBAR_LOG_LEVEL_INFO 40012 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG 40013 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG1 40014 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG2 40015 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40016 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cmd/synergyc/synergyc.cpp b/cmd/synergyc/synergyc.cpp new file mode 100644 index 00000000..1a230f0d --- /dev/null +++ b/cmd/synergyc/synergyc.cpp @@ -0,0 +1,910 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClient.h" +#include "CScreen.h" +#include "ProtocolTypes.h" +#include "Version.h" +#include "XScreen.h" +#include "CNetworkAddress.h" +#include "CSocketMultiplexer.h" +#include "CTCPSocketFactory.h" +#include "XSocket.h" +#include "CThread.h" +#include "CEventQueue.h" +#include "CFunctionEventJob.h" +#include "CFunctionJob.h" +#include "CLog.h" +#include "CString.h" +#include "CStringUtil.h" +#include "LogOutputters.h" +#include "CArch.h" +#include "XArch.h" +#include + +#define DAEMON_RUNNING(running_) +#if WINAPI_MSWINDOWS +#include "CArchMiscWindows.h" +#include "CMSWindowsScreen.h" +#include "CMSWindowsUtil.h" +#include "CMSWindowsClientTaskBarReceiver.h" +#include "resource.h" +#undef DAEMON_RUNNING +#define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) +#elif WINAPI_XWINDOWS +#include "CXWindowsScreen.h" +#include "CXWindowsClientTaskBarReceiver.h" +#elif WINAPI_CARBON +#include "COSXScreen.h" +#include "COSXClientTaskBarReceiver.h" +#endif + +// platform dependent name of a daemon +#if SYSAPI_WIN32 +#define DAEMON_NAME "Synergy Client" +#elif SYSAPI_UNIX +#define DAEMON_NAME "synergyc" +#endif + +typedef int (*StartupFunc)(int, char**); +static bool startClient(); +static void parse(int argc, const char* const* argv); + +// +// program arguments +// + +#define ARG CArgs::s_instance + +class CArgs { +public: + CArgs() : + m_pname(NULL), + m_backend(false), + m_restartable(true), + m_daemon(true), + m_logFilter(NULL), + m_display(NULL), + m_serverAddress(NULL) + { s_instance = this; } + ~CArgs() { s_instance = NULL; } + +public: + static CArgs* s_instance; + const char* m_pname; + bool m_backend; + bool m_restartable; + bool m_daemon; + const char* m_logFilter; + const char* m_display; + CString m_name; + CNetworkAddress* m_serverAddress; +}; + +CArgs* CArgs::s_instance = NULL; + + +// +// platform dependent factories +// + +static +CScreen* +createScreen() +{ +#if WINAPI_MSWINDOWS + return new CScreen(new CMSWindowsScreen(false)); +#elif WINAPI_XWINDOWS + return new CScreen(new CXWindowsScreen(ARG->m_display, false)); +#elif WINAPI_CARBON + return new CScreen(new COSXScreen(false)); +#endif +} + +static +CClientTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ +#if WINAPI_MSWINDOWS + return new CMSWindowsClientTaskBarReceiver( + CMSWindowsScreen::getInstance(), logBuffer); +#elif WINAPI_XWINDOWS + return new CXWindowsClientTaskBarReceiver(logBuffer); +#elif WINAPI_CARBON + return new COSXClientTaskBarReceiver(logBuffer); +#endif +} + + +// +// platform independent main +// + +static CClient* s_client = NULL; +static CScreen* s_clientScreen = NULL; +static CClientTaskBarReceiver* s_taskBarReceiver = NULL; +static double s_retryTime = 0.0; +static bool s_suspened = false; + +static +void +updateStatus() +{ + s_taskBarReceiver->updateStatus(s_client, ""); +} + +static +void +updateStatus(const CString& msg) +{ + s_taskBarReceiver->updateStatus(s_client, msg); +} + +static +void +resetRestartTimeout() +{ + s_retryTime = 0.0; +} + +static +double +nextRestartTimeout() +{ + // choose next restart timeout. we start with rapid retries + // then slow down. + if (s_retryTime < 1.0) { + s_retryTime = 1.0; + } + else if (s_retryTime < 3.0) { + s_retryTime = 3.0; + } + else if (s_retryTime < 5.0) { + s_retryTime = 5.0; + } + else if (s_retryTime < 15.0) { + s_retryTime = 15.0; + } + else if (s_retryTime < 30.0) { + s_retryTime = 30.0; + } + else { + s_retryTime = 60.0; + } + return s_retryTime; +} + +static +void +handleScreenError(const CEvent&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +static +CScreen* +openClientScreen() +{ + CScreen* screen = createScreen(); + EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), + screen->getEventTarget(), + new CFunctionEventJob( + &handleScreenError)); + return screen; +} + +static +void +closeClientScreen(CScreen* screen) +{ + if (screen != NULL) { + EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), + screen->getEventTarget()); + delete screen; + } +} + +static +void +handleClientRestart(const CEvent&, void* vtimer) +{ + // discard old timer + CEventQueueTimer* timer = reinterpret_cast(vtimer); + EVENTQUEUE->deleteTimer(timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, timer); + + // reconnect + startClient(); +} + +static +void +scheduleClientRestart(double retryTime) +{ + // install a timer and handler to retry later + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new CFunctionEventJob(&handleClientRestart, timer)); +} + +static +void +handleClientConnected(const CEvent&, void*) +{ + LOG((CLOG_NOTE "connected to server")); + resetRestartTimeout(); + updateStatus(); +} + +static +void +handleClientFailed(const CEvent& e, void*) +{ + CClient::CFailInfo* info = + reinterpret_cast(e.getData()); + + updateStatus(CString("Failed to connect to server: ") + info->m_what); + if (!ARG->m_restartable || !info->m_retry) { + LOG((CLOG_ERR "failed to connect to server: %s", info->m_what)); + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + else { + LOG((CLOG_WARN "failed to connect to server: %s", info->m_what)); + if (!s_suspened) { + scheduleClientRestart(nextRestartTimeout()); + } + } +} + +static +void +handleClientDisconnected(const CEvent&, void*) +{ + LOG((CLOG_NOTE "disconnected from server")); + if (!ARG->m_restartable) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + else if (!s_suspened) { + s_client->connect(); + } + updateStatus(); +} + +static +CClient* +openClient(const CString& name, const CNetworkAddress& address, CScreen* screen) +{ + CClient* client = new CClient(name, address, + new CTCPSocketFactory, NULL, screen); + EVENTQUEUE->adoptHandler(CClient::getConnectedEvent(), + client->getEventTarget(), + new CFunctionEventJob(handleClientConnected)); + EVENTQUEUE->adoptHandler(CClient::getConnectionFailedEvent(), + client->getEventTarget(), + new CFunctionEventJob(handleClientFailed)); + EVENTQUEUE->adoptHandler(CClient::getDisconnectedEvent(), + client->getEventTarget(), + new CFunctionEventJob(handleClientDisconnected)); + return client; +} + +static +void +closeClient(CClient* client) +{ + if (client == NULL) { + return; + } + + EVENTQUEUE->removeHandler(CClient::getConnectedEvent(), client); + EVENTQUEUE->removeHandler(CClient::getConnectionFailedEvent(), client); + EVENTQUEUE->removeHandler(CClient::getDisconnectedEvent(), client); + delete client; +} + +static +bool +startClient() +{ + double retryTime; + CScreen* clientScreen = NULL; + try { + if (s_clientScreen == NULL) { + clientScreen = openClientScreen(); + s_client = openClient(ARG->m_name, + *ARG->m_serverAddress, clientScreen); + s_clientScreen = clientScreen; + LOG((CLOG_NOTE "started client")); + } + s_client->connect(); + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "cannot open secondary screen: %s", e.what())); + closeClientScreen(clientScreen); + updateStatus(CString("Cannot open secondary screen: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "cannot open secondary screen: %s", e.what())); + closeClientScreen(clientScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start client: %s", e.what())); + closeClientScreen(clientScreen); + return false; + } + + if (ARG->m_restartable) { + scheduleClientRestart(retryTime); + return true; + } + else { + // don't try again + return false; + } +} + +static +void +stopClient() +{ + closeClient(s_client); + closeClientScreen(s_clientScreen); + s_client = NULL; + s_clientScreen = NULL; +} + +static +int +mainLoop() +{ + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + CSocketMultiplexer multiplexer; + + // create the event queue + CEventQueue eventQueue; + + // start the client. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting client")); + if (!startClient()) { + return kExitFailed; + } + + // run event loop. if startClient() failed we're supposed to retry + // later. the timer installed by startClient() will take care of + // that. + CEvent event; + DAEMON_RUNNING(true); + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping client")); + stopClient(); + updateStatus(); + LOG((CLOG_NOTE "stopped client")); + + return kExitSuccess; +} + +static +int +daemonMainLoop(int, const char**) +{ +#if SYSAPI_WIN32 + CSystemLogger sysLogger(DAEMON_NAME, false); +#else + CSystemLogger sysLogger(DAEMON_NAME, true); +#endif + return mainLoop(); +} + +static +int +standardStartup(int argc, char** argv) +{ + if (!ARG->m_daemon) { + ARCH->showConsole(false); + } + + // parse command line + parse(argc, argv); + + // daemonize if requested + if (ARG->m_daemon) { + return ARCH->daemonize(DAEMON_NAME, &daemonMainLoop); + } + else { + return mainLoop(); + } +} + +static +int +run(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) +{ + // general initialization + ARG->m_serverAddress = new CNetworkAddress; + ARG->m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + // save log messages + CBufferedLogOutputter logBuffer(1000); + CLOG->insert(&logBuffer, true); + + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = createTaskBarReceiver(&logBuffer); + + // run + int result = startup(argc, argv); + + // done with task bar receiver + delete s_taskBarReceiver; + + // done with log buffer + CLOG->remove(&logBuffer); + + delete ARG->m_serverAddress; + return result; +} + + +// +// command line parsing +// + +#define BYE "\nTry `%s --help' for more information." + +static void (*bye)(int) = &exit; + +static +void +version() +{ + LOG((CLOG_PRINT "%s %s, protocol version %d.%d\n%s", + ARG->m_pname, + kVersion, + kProtocolMajorVersion, + kProtocolMinorVersion, + kCopyright)); +} + +static +void +help() +{ +#if WINAPI_XWINDOWS +# define USAGE_DISPLAY_ARG \ +" [--display ]" +# define USAGE_DISPLAY_INFO \ +" --display connect to the X server at \n" +#else +# define USAGE_DISPLAY_ARG +# define USAGE_DISPLAY_INFO +#endif + + LOG((CLOG_PRINT +"Usage: %s" +" [--daemon|--no-daemon]" +" [--debug ]" +USAGE_DISPLAY_ARG +" [--name ]" +" [--restart|--no-restart]" +" " +"\n\n" +"Start the synergy mouse/keyboard sharing server.\n" +"\n" +" -d, --debug filter out log messages with priorty below level.\n" +" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" +" DEBUG, DEBUG1, DEBUG2.\n" +USAGE_DISPLAY_INFO +" -f, --no-daemon run the client in the foreground.\n" +"* --daemon run the client as a daemon.\n" +" -n, --name use screen-name instead the hostname to identify\n" +" ourself to the server.\n" +" -1, --no-restart do not try to restart the client if it fails for\n" +" some reason.\n" +"* --restart restart the client automatically if it fails.\n" +" -h, --help display this help and exit.\n" +" --version display version information and exit.\n" +"\n" +"* marks defaults.\n" +"\n" +"The server address is of the form: [][:]. The hostname\n" +"must be the address or hostname of the server. The port overrides the\n" +"default port, %d.\n" +"\n" +"Where log messages go depends on the platform and whether or not the\n" +"client is running as a daemon.", + ARG->m_pname, kDefaultPort)); + +} + +static +bool +isArg(int argi, int argc, const char* const* argv, + const char* name1, const char* name2, + int minRequiredParameters = 0) +{ + if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || + (name2 != NULL && strcmp(argv[argi], name2) == 0)) { + // match. check args left. + if (argi + minRequiredParameters >= argc) { + LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE, + ARG->m_pname, argv[argi], ARG->m_pname)); + bye(kExitArgs); + } + return true; + } + + // no match + return false; +} + +static +void +parse(int argc, const char* const* argv) +{ + assert(ARG->m_pname != NULL); + assert(argv != NULL); + assert(argc >= 1); + + // set defaults + ARG->m_name = ARCH->getHostName(); + + // parse options + int i; + for (i = 1; i < argc; ++i) { + if (isArg(i, argc, argv, "-d", "--debug", 1)) { + // change logging level + ARG->m_logFilter = argv[++i]; + } + + else if (isArg(i, argc, argv, "-n", "--name", 1)) { + // save screen name + ARG->m_name = argv[++i]; + } + + else if (isArg(i, argc, argv, NULL, "--camp")) { + // ignore -- included for backwards compatibility + } + + else if (isArg(i, argc, argv, NULL, "--no-camp")) { + // ignore -- included for backwards compatibility + } + + else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + // not a daemon + ARG->m_daemon = false; + } + + else if (isArg(i, argc, argv, NULL, "--daemon")) { + // daemonize + ARG->m_daemon = true; + } + +#if WINAPI_XWINDOWS + else if (isArg(i, argc, argv, "-display", "--display", 1)) { + // use alternative display + ARG->m_display = argv[++i]; + } +#endif + + else if (isArg(i, argc, argv, "-1", "--no-restart")) { + // don't try to restart + ARG->m_restartable = false; + } + + else if (isArg(i, argc, argv, NULL, "--restart")) { + // try to restart + ARG->m_restartable = true; + } + + else if (isArg(i, argc, argv, "-z", NULL)) { + ARG->m_backend = true; + } + + else if (isArg(i, argc, argv, "-h", "--help")) { + help(); + bye(kExitSuccess); + } + + else if (isArg(i, argc, argv, NULL, "--version")) { + version(); + bye(kExitSuccess); + } + + else if (isArg(i, argc, argv, "--", NULL)) { + // remaining arguments are not options + ++i; + break; + } + + else if (argv[i][0] == '-') { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + ARG->m_pname, argv[i], ARG->m_pname)); + bye(kExitArgs); + } + + else { + // this and remaining arguments are not options + break; + } + } + + // exactly one non-option argument (server-address) + if (i == argc) { + LOG((CLOG_PRINT "%s: a server address or name is required" BYE, + ARG->m_pname, ARG->m_pname)); + bye(kExitArgs); + } + if (i + 1 != argc) { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + ARG->m_pname, argv[i], ARG->m_pname)); + bye(kExitArgs); + } + + // save server address + try { + *ARG->m_serverAddress = CNetworkAddress(argv[i], kDefaultPort); + ARG->m_serverAddress->resolve(); + } + catch (XSocketAddress& e) { + // allow an address that we can't look up if we're restartable. + // we'll try to resolve the address each time we connect to the + // server. a bad port will never get better. patch by Brent + // Priddy. + if (!ARG->m_restartable || e.getError() == XSocketAddress::kBadPort) { + LOG((CLOG_PRINT "%s: %s" BYE, + ARG->m_pname, e.what(), ARG->m_pname)); + bye(kExitFailed); + } + } + + // increase default filter level for daemon. the user must + // explicitly request another level for a daemon. + if (ARG->m_daemon && ARG->m_logFilter == NULL) { +#if SYSAPI_WIN32 + if (CArchMiscWindows::isWindows95Family()) { + // windows 95 has no place for logging so avoid showing + // the log console window. + ARG->m_logFilter = "FATAL"; + } + else +#endif + { + ARG->m_logFilter = "NOTE"; + } + } + + // set log filter + if (!CLOG->setFilter(ARG->m_logFilter)) { + LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + ARG->m_pname, ARG->m_logFilter, ARG->m_pname)); + bye(kExitArgs); + } + + // identify system + LOG((CLOG_INFO "Synergy client %s on %s", kVersion, ARCH->getOSName().c_str())); +} + + +// +// platform dependent entry points +// + +#if SYSAPI_WIN32 + +static bool s_hasImportantLogMessages = false; + +// +// CMessageBoxOutputter +// +// This class writes severe log messages to a message box +// + +class CMessageBoxOutputter : public ILogOutputter { +public: + CMessageBoxOutputter() { } + virtual ~CMessageBoxOutputter() { } + + // ILogOutputter overrides + virtual void open(const char*) { } + virtual void close() { } + virtual void show(bool) { } + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const { return ""; } +}; + +bool +CMessageBoxOutputter::write(ELevel level, const char* message) +{ + // note any important messages the user may need to know about + if (level <= CLog::kWARNING) { + s_hasImportantLogMessages = true; + } + + // FATAL and PRINT messages get a dialog box if not running as + // backend. if we're running as a backend the user will have + // a chance to see the messages when we exit. + if (!ARG->m_backend && level <= CLog::kFATAL) { + MessageBox(NULL, message, ARG->m_pname, MB_OK | MB_ICONWARNING); + return false; + } + else { + return true; + } +} + +static +void +byeThrow(int x) +{ + CArchMiscWindows::daemonFailed(x); +} + +static +int +daemonNTMainLoop(int argc, const char** argv) +{ + parse(argc, argv); + ARG->m_backend = false; + return CArchMiscWindows::runDaemon(mainLoop); +} + +static +int +daemonNTStartup(int, char**) +{ + CSystemLogger sysLogger(DAEMON_NAME, false); + bye = &byeThrow; + return ARCH->daemonize(DAEMON_NAME, &daemonNTMainLoop); +} + +static +int +foregroundStartup(int argc, char** argv) +{ + ARCH->showConsole(false); + + // parse command line + parse(argc, argv); + + // never daemonize + return mainLoop(); +} + +static +void +showError(HINSTANCE instance, const char* title, UINT id, const char* arg) +{ + CString fmt = CMSWindowsUtil::getString(instance, id); + CString msg = CStringUtil::format(fmt.c_str(), arg); + MessageBox(NULL, msg.c_str(), title, MB_OK | MB_ICONWARNING); +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) +{ + try { + CArchMiscWindows::setIcons((HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 32, 32, LR_SHARED), + (HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 16, 16, LR_SHARED)); + CArch arch(instance); + CMSWindowsScreen::init(instance); + CLOG; + CThread::getCurrentThread().setPriority(-14); + CArgs args; + + // set title on log window + ARCH->openConsole((CString(kAppVersion) + " " + "Client").c_str()); + + // windows NT family starts services using no command line options. + // since i'm not sure how to tell the difference between that and + // a user providing no options we'll assume that if there are no + // arguments and we're on NT then we're being invoked as a service. + // users on NT can use `--daemon' or `--no-daemon' to force us out + // of the service code path. + StartupFunc startup = &standardStartup; + if (!CArchMiscWindows::isWindows95Family()) { + if (__argc <= 1) { + startup = &daemonNTStartup; + } + else { + startup = &foregroundStartup; + } + } + + // send PRINT and FATAL output to a message box + int result = run(__argc, __argv, new CMessageBoxOutputter, startup); + + // let user examine any messages if we're running as a backend + // by putting up a dialog box before exiting. + if (args.m_backend && s_hasImportantLogMessages) { + showError(instance, args.m_pname, IDS_FAILED, ""); + } + + delete CLOG; + return result; + } + catch (XBase& e) { + showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, e.what()); + //throw; + } + catch (XArch& e) { + showError(instance, __argv[0], IDS_INIT_FAILED, e.what().c_str()); + } + catch (...) { + showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, ""); + //throw; + } + return kExitFailed; +} + +#elif SYSAPI_UNIX + +int +main(int argc, char** argv) +{ + CArgs args; + try { + int result; + CArch arch; + CLOG; + CArgs args; + result = run(argc, argv, NULL, &standardStartup); + delete CLOG; + return result; + } + catch (XBase& e) { + LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what())); + throw; + } + catch (XArch& e) { + LOG((CLOG_CRIT "Initialization failed: %s" BYE, e.what().c_str())); + return kExitFailed; + } + catch (...) { + LOG((CLOG_CRIT "Uncaught exception: \n")); + throw; + } +} + +#else + +#error no main() for platform + +#endif diff --git a/cmd/synergyc/synergyc.ico b/cmd/synergyc/synergyc.ico new file mode 100644 index 00000000..89f965f4 Binary files /dev/null and b/cmd/synergyc/synergyc.ico differ diff --git a/cmd/synergyc/synergyc.rc b/cmd/synergyc/synergyc.rc new file mode 100644 index 00000000..7f2a5dc1 --- /dev/null +++ b/cmd/synergyc/synergyc.rc @@ -0,0 +1,141 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#if !defined(IDC_STATIC) +#define IDC_STATIC (-1) +#endif + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SYNERGY ICON DISCARDABLE "synergyc.ico" +IDI_TASKBAR_NOT_RUNNING ICON DISCARDABLE "tb_idle.ico" +IDI_TASKBAR_NOT_WORKING ICON DISCARDABLE "tb_error.ico" +IDI_TASKBAR_NOT_CONNECTED ICON DISCARDABLE "tb_wait.ico" +IDI_TASKBAR_CONNECTED ICON DISCARDABLE "tb_run.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TASKBAR_STATUS DIALOG DISCARDABLE 0, 0, 145, 18 +STYLE DS_MODALFRAME | WS_POPUP +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_TASKBAR_STATUS_STATUS,3,3,139,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_TASKBAR MENU DISCARDABLE +BEGIN + POPUP "Synergy" + BEGIN + MENUITEM "Show Status", IDC_TASKBAR_STATUS + MENUITEM "Show Log", IDC_TASKBAR_SHOW_LOG + MENUITEM "Copy Log To Clipboard", IDC_TASKBAR_LOG + POPUP "Set Log Level" + BEGIN + MENUITEM "Error", IDC_TASKBAR_LOG_LEVEL_ERROR + + MENUITEM "Warning", IDC_TASKBAR_LOG_LEVEL_WARNING + + MENUITEM "Note", IDC_TASKBAR_LOG_LEVEL_NOTE + + MENUITEM "Info", IDC_TASKBAR_LOG_LEVEL_INFO + + MENUITEM "Debug", IDC_TASKBAR_LOG_LEVEL_DEBUG + + MENUITEM "Debug1", IDC_TASKBAR_LOG_LEVEL_DEBUG1 + + MENUITEM "Debug2", IDC_TASKBAR_LOG_LEVEL_DEBUG2 + + END + MENUITEM SEPARATOR + MENUITEM "Quit", IDC_TASKBAR_QUIT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_FAILED "Synergy is about to quit with errors or warnings. Please check the log then click OK." + IDS_INIT_FAILED "Synergy failed to initialize: %{1}" + IDS_UNCAUGHT_EXCEPTION "Uncaught exception: %{1}" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/cmd/synergyc/tb_error.ico b/cmd/synergyc/tb_error.ico new file mode 100644 index 00000000..746a87c9 Binary files /dev/null and b/cmd/synergyc/tb_error.ico differ diff --git a/cmd/synergyc/tb_idle.ico b/cmd/synergyc/tb_idle.ico new file mode 100644 index 00000000..4e13a264 Binary files /dev/null and b/cmd/synergyc/tb_idle.ico differ diff --git a/cmd/synergyc/tb_run.ico b/cmd/synergyc/tb_run.ico new file mode 100644 index 00000000..88e160cb Binary files /dev/null and b/cmd/synergyc/tb_run.ico differ diff --git a/cmd/synergyc/tb_wait.ico b/cmd/synergyc/tb_wait.ico new file mode 100644 index 00000000..257be0a1 Binary files /dev/null and b/cmd/synergyc/tb_wait.ico differ diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp new file mode 100644 index 00000000..e332ccea --- /dev/null +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.cpp @@ -0,0 +1,374 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsServerTaskBarReceiver.h" +#include "CServer.h" +#include "CMSWindowsClipboard.h" +#include "IEventQueue.h" +#include "LogOutputters.h" +#include "BasicTypes.h" +#include "CArch.h" +#include "CArchTaskBarWindows.h" +#include "resource.h" + +extern CEvent::Type getReloadConfigEvent(); +extern CEvent::Type getForceReconnectEvent(); + +// +// CMSWindowsServerTaskBarReceiver +// + +const UINT CMSWindowsServerTaskBarReceiver::s_stateToIconID[kMaxState] = +{ + IDI_TASKBAR_NOT_RUNNING, + IDI_TASKBAR_NOT_WORKING, + IDI_TASKBAR_NOT_CONNECTED, + IDI_TASKBAR_CONNECTED +}; + +CMSWindowsServerTaskBarReceiver::CMSWindowsServerTaskBarReceiver( + HINSTANCE appInstance, const CBufferedLogOutputter* logBuffer) : + CServerTaskBarReceiver(), + m_appInstance(appInstance), + m_window(NULL), + m_logBuffer(logBuffer) +{ + for (UInt32 i = 0; i < kMaxState; ++i) { + m_icon[i] = loadIcon(s_stateToIconID[i]); + } + m_menu = LoadMenu(m_appInstance, MAKEINTRESOURCE(IDR_TASKBAR)); + + // don't create the window yet. we'll create it on demand. this + // has the side benefit of being created in the thread used for + // the task bar. that's good because it means the existence of + // the window won't prevent changing the main thread's desktop. + + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CMSWindowsServerTaskBarReceiver::~CMSWindowsServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); + for (UInt32 i = 0; i < kMaxState; ++i) { + deleteIcon(m_icon[i]); + } + DestroyMenu(m_menu); + destroyWindow(); +} + +void +CMSWindowsServerTaskBarReceiver::showStatus() +{ + // create the window + createWindow(); + + // lock self while getting status + lock(); + + // get the current status + std::string status = getToolTip(); + + // get the connect clients, if any + const CClients& clients = getClients(); + + // done getting status + unlock(); + + // update dialog + HWND child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_STATUS); + SendMessage(child, WM_SETTEXT, 0, (LPARAM)status.c_str()); + child = GetDlgItem(m_window, IDC_TASKBAR_STATUS_CLIENTS); + SendMessage(child, LB_RESETCONTENT, 0, 0); + for (CClients::const_iterator index = clients.begin(); + index != clients.end(); ) { + const char* client = index->c_str(); + if (++index == clients.end()) { + SendMessage(child, LB_ADDSTRING, 0, (LPARAM)client); + } + else { + SendMessage(child, LB_INSERTSTRING, (WPARAM)-1, (LPARAM)client); + } + } + + if (!IsWindowVisible(m_window)) { + // position it by the mouse + POINT cursorPos; + GetCursorPos(&cursorPos); + RECT windowRect; + GetWindowRect(m_window, &windowRect); + int x = cursorPos.x; + int y = cursorPos.y; + int fw = GetSystemMetrics(SM_CXDLGFRAME); + int fh = GetSystemMetrics(SM_CYDLGFRAME); + int ww = windowRect.right - windowRect.left; + int wh = windowRect.bottom - windowRect.top; + int sw = GetSystemMetrics(SM_CXFULLSCREEN); + int sh = GetSystemMetrics(SM_CYFULLSCREEN); + if (fw < 1) { + fw = 1; + } + if (fh < 1) { + fh = 1; + } + if (x + ww - fw > sw) { + x -= ww - fw; + } + else { + x -= fw; + } + if (x < 0) { + x = 0; + } + if (y + wh - fh > sh) { + y -= wh - fh; + } + else { + y -= fh; + } + if (y < 0) { + y = 0; + } + SetWindowPos(m_window, HWND_TOPMOST, x, y, ww, wh, + SWP_SHOWWINDOW); + } +} + +void +CMSWindowsServerTaskBarReceiver::runMenu(int x, int y) +{ + // do popup menu. we need a window to pass to TrackPopupMenu(). + // the SetForegroundWindow() and SendMessage() calls around + // TrackPopupMenu() are to get the menu to be dismissed when + // another window gets activated and are just one of those + // win32 weirdnesses. + createWindow(); + SetForegroundWindow(m_window); + HMENU menu = GetSubMenu(m_menu, 0); + SetMenuDefaultItem(menu, IDC_TASKBAR_STATUS, FALSE); + HMENU logLevelMenu = GetSubMenu(menu, 3); + CheckMenuRadioItem(logLevelMenu, 0, 6, + CLOG->getFilter() - CLog::kERROR, MF_BYPOSITION); + int n = TrackPopupMenu(menu, + TPM_NONOTIFY | + TPM_RETURNCMD | + TPM_LEFTBUTTON | + TPM_RIGHTBUTTON, + x, y, 0, m_window, NULL); + SendMessage(m_window, WM_NULL, 0, 0); + + // perform the requested operation + switch (n) { + case IDC_TASKBAR_STATUS: + showStatus(); + break; + + case IDC_TASKBAR_LOG: + copyLog(); + break; + + case IDC_TASKBAR_SHOW_LOG: + ARCH->showConsole(true); + break; + + case IDC_RELOAD_CONFIG: + EVENTQUEUE->addEvent(CEvent(getReloadConfigEvent(), + IEventQueue::getSystemTarget())); + break; + + case IDC_FORCE_RECONNECT: + EVENTQUEUE->addEvent(CEvent(getForceReconnectEvent(), + IEventQueue::getSystemTarget())); + break; + + case IDC_TASKBAR_LOG_LEVEL_ERROR: + CLOG->setFilter(CLog::kERROR); + break; + + case IDC_TASKBAR_LOG_LEVEL_WARNING: + CLOG->setFilter(CLog::kWARNING); + break; + + case IDC_TASKBAR_LOG_LEVEL_NOTE: + CLOG->setFilter(CLog::kNOTE); + break; + + case IDC_TASKBAR_LOG_LEVEL_INFO: + CLOG->setFilter(CLog::kINFO); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG: + CLOG->setFilter(CLog::kDEBUG); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG1: + CLOG->setFilter(CLog::kDEBUG1); + break; + + case IDC_TASKBAR_LOG_LEVEL_DEBUG2: + CLOG->setFilter(CLog::kDEBUG2); + break; + + case IDC_TASKBAR_QUIT: + quit(); + break; + } +} + +void +CMSWindowsServerTaskBarReceiver::primaryAction() +{ + showStatus(); +} + +const IArchTaskBarReceiver::Icon +CMSWindowsServerTaskBarReceiver::getIcon() const +{ + return reinterpret_cast(m_icon[getStatus()]); +} + +void +CMSWindowsServerTaskBarReceiver::copyLog() const +{ + if (m_logBuffer != NULL) { + // collect log buffer + CString data; + for (CBufferedLogOutputter::const_iterator index = m_logBuffer->begin(); + index != m_logBuffer->end(); ++index) { + data += *index; + data += "\n"; + } + + // copy log to clipboard + if (!data.empty()) { + CMSWindowsClipboard clipboard(m_window); + clipboard.open(0); + clipboard.emptyUnowned(); + clipboard.add(IClipboard::kText, data); + clipboard.close(); + } + } +} + +void +CMSWindowsServerTaskBarReceiver::onStatusChanged() +{ + if (IsWindowVisible(m_window)) { + showStatus(); + } +} + +HICON +CMSWindowsServerTaskBarReceiver::loadIcon(UINT id) +{ + HANDLE icon = LoadImage(m_appInstance, + MAKEINTRESOURCE(id), + IMAGE_ICON, + 0, 0, + LR_DEFAULTCOLOR); + return reinterpret_cast(icon); +} + +void +CMSWindowsServerTaskBarReceiver::deleteIcon(HICON icon) +{ + if (icon != NULL) { + DestroyIcon(icon); + } +} + +void +CMSWindowsServerTaskBarReceiver::createWindow() +{ + // ignore if already created + if (m_window != NULL) { + return; + } + + // get the status dialog + m_window = CreateDialogParam(m_appInstance, + MAKEINTRESOURCE(IDD_TASKBAR_STATUS), + NULL, + (DLGPROC)&CMSWindowsServerTaskBarReceiver::staticDlgProc, + reinterpret_cast( + reinterpret_cast(this))); + + // window should appear on top of everything, including (especially) + // the task bar. + LONG_PTR style = GetWindowLongPtr(m_window, GWL_EXSTYLE); + style |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST; + SetWindowLongPtr(m_window, GWL_EXSTYLE, style); + + // tell the task bar about this dialog + CArchTaskBarWindows::addDialog(m_window); +} + +void +CMSWindowsServerTaskBarReceiver::destroyWindow() +{ + if (m_window != NULL) { + CArchTaskBarWindows::removeDialog(m_window); + DestroyWindow(m_window); + m_window = NULL; + } +} + +BOOL +CMSWindowsServerTaskBarReceiver::dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + // use default focus + return TRUE; + + case WM_ACTIVATE: + // hide when another window is activated + if (LOWORD(wParam) == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + return FALSE; +} + +BOOL CALLBACK +CMSWindowsServerTaskBarReceiver::staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_INITDIALOG, extract the CMSWindowsServerTaskBarReceiver* + // and put it in the extra window data then forward the call. + CMSWindowsServerTaskBarReceiver* self = NULL; + if (msg == WM_INITDIALOG) { + self = reinterpret_cast( + reinterpret_cast(lParam)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->dlgProc(hwnd, msg, wParam, lParam); + } + else { + return (msg == WM_INITDIALOG) ? TRUE : FALSE; + } +} diff --git a/cmd/synergys/CMSWindowsServerTaskBarReceiver.h b/cmd/synergys/CMSWindowsServerTaskBarReceiver.h new file mode 100644 index 00000000..ab679077 --- /dev/null +++ b/cmd/synergys/CMSWindowsServerTaskBarReceiver.h @@ -0,0 +1,64 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSSERVERTASKBARRECEIVER_H +#define CMSWINDOWSSERVERTASKBARRECEIVER_H + +#define WIN32_LEAN_AND_MEAN + +#include "CServerTaskBarReceiver.h" +#include + +class CBufferedLogOutputter; + +//! Implementation of CServerTaskBarReceiver for Microsoft Windows +class CMSWindowsServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + CMSWindowsServerTaskBarReceiver(HINSTANCE, const CBufferedLogOutputter*); + virtual ~CMSWindowsServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; + +protected: + void copyLog() const; + + // CServerTaskBarReceiver overrides + virtual void onStatusChanged(); + +private: + HICON loadIcon(UINT); + void deleteIcon(HICON); + void createWindow(); + void destroyWindow(); + + BOOL dlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + static BOOL CALLBACK + staticDlgProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam); + +private: + HINSTANCE m_appInstance; + HWND m_window; + HMENU m_menu; + HICON m_icon[kMaxState]; + const CBufferedLogOutputter* m_logBuffer; + static const UINT s_stateToIconID[]; +}; + +#endif diff --git a/cmd/synergys/COSXServerTaskBarReceiver.cpp b/cmd/synergys/COSXServerTaskBarReceiver.cpp new file mode 100644 index 00000000..8195b84f --- /dev/null +++ b/cmd/synergys/COSXServerTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXServerTaskBarReceiver.h" +#include "CArch.h" + +// +// COSXServerTaskBarReceiver +// + +COSXServerTaskBarReceiver::COSXServerTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +COSXServerTaskBarReceiver::~COSXServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +COSXServerTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +COSXServerTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +COSXServerTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +COSXServerTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergys/COSXServerTaskBarReceiver.h b/cmd/synergys/COSXServerTaskBarReceiver.h new file mode 100644 index 00000000..7f6dc298 --- /dev/null +++ b/cmd/synergys/COSXServerTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSERVERTASKBARRECEIVER_H +#define COSXSERVERTASKBARRECEIVER_H + +#include "CServerTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CServerTaskBarReceiver for OS X +class COSXServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + COSXServerTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~COSXServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergys/CServerTaskBarReceiver.cpp b/cmd/synergys/CServerTaskBarReceiver.cpp new file mode 100644 index 00000000..6555b214 --- /dev/null +++ b/cmd/synergys/CServerTaskBarReceiver.cpp @@ -0,0 +1,133 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CServerTaskBarReceiver.h" +#include "CServer.h" +#include "CLock.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "CArch.h" +#include "Version.h" + +// +// CServerTaskBarReceiver +// + +CServerTaskBarReceiver::CServerTaskBarReceiver() : + m_state(kNotRunning) +{ + // do nothing +} + +CServerTaskBarReceiver::~CServerTaskBarReceiver() +{ + // do nothing +} + +void +CServerTaskBarReceiver::updateStatus(CServer* server, const CString& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (server == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_clients.clear(); + server->getClients(m_clients); + if (m_clients.size() <= 1) { + m_state = kNotConnected; + } + else { + m_state = kConnected; + } + } + + // let subclasses have a go + onStatusChanged(server); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +CServerTaskBarReceiver::EState +CServerTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const CString& +CServerTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +const CServerTaskBarReceiver::CClients& +CServerTaskBarReceiver::getClients() const +{ + return m_clients; +} + +void +CServerTaskBarReceiver::quit() +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +void +CServerTaskBarReceiver::onStatusChanged(CServer*) +{ + // do nothing +} + +void +CServerTaskBarReceiver::lock() const +{ + // do nothing +} + +void +CServerTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +CServerTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return CStringUtil::print("%s: Not running", kAppVersion); + + case kNotWorking: + return CStringUtil::print("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return CStringUtil::print("%s: Waiting for clients", kAppVersion); + + case kConnected: + return CStringUtil::print("%s: Connected", kAppVersion); + + default: + return ""; + } +} diff --git a/cmd/synergys/CServerTaskBarReceiver.h b/cmd/synergys/CServerTaskBarReceiver.h new file mode 100644 index 00000000..d6ec8571 --- /dev/null +++ b/cmd/synergys/CServerTaskBarReceiver.h @@ -0,0 +1,88 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSERVERTASKBARRECEIVER_H +#define CSERVERTASKBARRECEIVER_H + +#include "CString.h" +#include "IArchTaskBarReceiver.h" +#include "stdvector.h" + +class CServer; + +//! Implementation of IArchTaskBarReceiver for the synergy server +class CServerTaskBarReceiver : public IArchTaskBarReceiver { +public: + CServerTaskBarReceiver(); + virtual ~CServerTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the server. + */ + void updateStatus(CServer*, const CString& errorMsg); + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + typedef std::vector CClients; + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const CString& getErrorMessage() const; + + //! Get connected clients + const CClients& getClients() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does + nothing. + */ + virtual void onStatusChanged(CServer* server); + +private: + EState m_state; + CString m_errorMessage; + CClients m_clients; +}; + +#endif diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp new file mode 100644 index 00000000..861d2f8c --- /dev/null +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.cpp @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsServerTaskBarReceiver.h" +#include "CArch.h" + +// +// CXWindowsServerTaskBarReceiver +// + +CXWindowsServerTaskBarReceiver::CXWindowsServerTaskBarReceiver( + const CBufferedLogOutputter*) +{ + // add ourself to the task bar + ARCH->addReceiver(this); +} + +CXWindowsServerTaskBarReceiver::~CXWindowsServerTaskBarReceiver() +{ + ARCH->removeReceiver(this); +} + +void +CXWindowsServerTaskBarReceiver::showStatus() +{ + // do nothing +} + +void +CXWindowsServerTaskBarReceiver::runMenu(int, int) +{ + // do nothing +} + +void +CXWindowsServerTaskBarReceiver::primaryAction() +{ + // do nothing +} + +const IArchTaskBarReceiver::Icon +CXWindowsServerTaskBarReceiver::getIcon() const +{ + return NULL; +} diff --git a/cmd/synergys/CXWindowsServerTaskBarReceiver.h b/cmd/synergys/CXWindowsServerTaskBarReceiver.h new file mode 100644 index 00000000..73234123 --- /dev/null +++ b/cmd/synergys/CXWindowsServerTaskBarReceiver.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSSERVERTASKBARRECEIVER_H +#define CXWINDOWSSERVERTASKBARRECEIVER_H + +#include "CServerTaskBarReceiver.h" + +class CBufferedLogOutputter; + +//! Implementation of CServerTaskBarReceiver for X Windows +class CXWindowsServerTaskBarReceiver : public CServerTaskBarReceiver { +public: + CXWindowsServerTaskBarReceiver(const CBufferedLogOutputter*); + virtual ~CXWindowsServerTaskBarReceiver(); + + // IArchTaskBarReceiver overrides + virtual void showStatus(); + virtual void runMenu(int x, int y); + virtual void primaryAction(); + virtual const Icon getIcon() const; +}; + +#endif diff --git a/cmd/synergys/Makefile.am b/cmd/synergys/Makefile.am new file mode 100644 index 00000000..4ad48ce5 --- /dev/null +++ b/cmd/synergys/Makefile.am @@ -0,0 +1,98 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +COMMON_SOURCE_FILES = \ + CServerTaskBarReceiver.cpp \ + CServerTaskBarReceiver.h \ + synergys.cpp \ + $(NULL) +XWINDOWS_SOURCE_FILES = \ + CXWindowsServerTaskBarReceiver.cpp \ + CXWindowsServerTaskBarReceiver.h \ + $(NULL) +MSWINDOWS_SOURCE_FILES = \ + CMSWindowsServerTaskBarReceiver.cpp \ + CMSWindowsServerTaskBarReceiver.h \ + resource.h \ + synergys.rc \ + $(NULL) +CARBON_SOURCE_FILES = \ + COSXServerTaskBarReceiver.cpp \ + COSXServerTaskBarReceiver.h \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + synergys.ico \ + tb_error.ico \ + tb_idle.ico \ + tb_run.ico \ + tb_wait.ico \ + $(XWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +bin_PROGRAMS = synergys +if XWINDOWS +synergys_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(XWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if MSWINDOWS +synergys_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(NULL) +endif +if CARBON +synergys_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) +synergys_LDFLAGS = \ + -framework ScreenSaver \ + -framework IOKit \ + -framework ApplicationServices \ + -framework Foundation \ + $(NULL) +endif +synergys_LDADD = \ + $(top_builddir)/lib/server/libserver.a \ + $(top_builddir)/lib/platform/libplatform.a \ + $(top_builddir)/lib/synergy/libsynergy.a \ + $(top_builddir)/lib/net/libnet.a \ + $(top_builddir)/lib/io/libio.a \ + $(top_builddir)/lib/mt/libmt.a \ + $(top_builddir)/lib/base/libbase.a \ + $(top_builddir)/lib/common/libcommon.a \ + $(top_builddir)/lib/arch/libarch.a \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + -I$(top_srcdir)/lib/server \ + $(NULL) diff --git a/cmd/synergys/Makefile.win b/cmd/synergys/Makefile.win new file mode 100644 index 00000000..09d39958 --- /dev/null +++ b/cmd/synergys/Makefile.win @@ -0,0 +1,89 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +BIN_SYNERGYS_SRC = cmd\synergys +BIN_SYNERGYS_DST = $(BUILD_DST)\$(BIN_SYNERGYS_SRC) +BIN_SYNERGYS_EXE = "$(BUILD_DST)\synergys.exe" +BIN_SYNERGYS_CPP = \ + "CServerTaskBarReceiver.cpp" \ + "CMSWindowsServerTaskBarReceiver.cpp" \ + "synergys.cpp" \ + $(NULL) +BIN_SYNERGYS_OBJ = \ + "$(BIN_SYNERGYS_DST)\CServerTaskBarReceiver.obj" \ + "$(BIN_SYNERGYS_DST)\CMSWindowsServerTaskBarReceiver.obj" \ + "$(BIN_SYNERGYS_DST)\synergys.obj" \ + $(NULL) +BIN_SYNERGYS_RC = "$(BIN_SYNERGYS_SRC)\synergys.rc" +BIN_SYNERGYS_RES = "$(BIN_SYNERGYS_DST)\synergys.res" +BIN_SYNERGYS_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + /I"lib\server" \ + $(NULL) +BIN_SYNERGYS_LIB = \ + $(LIB_SERVER_LIB) \ + $(LIB_PLATFORM_LIB) \ + $(LIB_SYNERGY_LIB) \ + $(LIB_NET_LIB) \ + $(LIB_IO_LIB) \ + $(LIB_MT_LIB) \ + $(LIB_BASE_LIB) \ + $(LIB_ARCH_LIB) \ + $(LIB_COMMON_LIB) \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(BIN_SYNERGYS_CPP) +OBJ_FILES = $(OBJ_FILES) $(BIN_SYNERGYS_OBJ) +PROGRAMS = $(PROGRAMS) $(BIN_SYNERGYS_EXE) + +# Need shell functions. +guilibs = $(guilibs) shell32.lib + +# Dependency rules +$(BIN_SYNERGYS_OBJ): $(AUTODEP) +!if EXIST($(BIN_SYNERGYS_DST)\deps.mak) +!include $(BIN_SYNERGYS_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(BIN_SYNERGYS_SRC)\}.cpp{$(BIN_SYNERGYS_DST)\}.obj:: +!else +{$(BIN_SYNERGYS_SRC)\}.cpp{$(BIN_SYNERGYS_DST)\}.obj: +!endif + @$(ECHO) Compile in $(BIN_SYNERGYS_SRC) + -@$(MKDIR) $(BIN_SYNERGYS_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(BIN_SYNERGYS_INC) \ + /Fo$(BIN_SYNERGYS_DST)\ \ + /Fd$(BIN_SYNERGYS_DST)\src.pdb \ + $< | $(AUTODEP) $(BIN_SYNERGYS_SRC) $(BIN_SYNERGYS_DST) +$(BIN_SYNERGYS_RES): $(BIN_SYNERGYS_RC) + @$(ECHO) Compile $(**F) + -@$(MKDIR) $(BIN_SYNERGYS_DST) 2>NUL: + $(rc) $(rcflags) $(rcvars) \ + /fo$@ \ + $** +$(BIN_SYNERGYS_EXE): $(BIN_SYNERGYS_OBJ) $(BIN_SYNERGYS_RES) $(BIN_SYNERGYS_LIB) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(guilflags) $(guilibsmt) \ + /out:$@ \ + $** + $(AUTODEP) $(BIN_SYNERGYS_SRC) $(BIN_SYNERGYS_DST) \ + $(BIN_SYNERGYS_OBJ:.obj=.d) diff --git a/cmd/synergys/resource.h b/cmd/synergys/resource.h new file mode 100644 index 00000000..0ad5868a --- /dev/null +++ b/cmd/synergys/resource.h @@ -0,0 +1,40 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by synergys.rc +// +#define IDS_FAILED 1 +#define IDS_INIT_FAILED 2 +#define IDS_UNCAUGHT_EXCEPTION 3 +#define IDI_SYNERGY 101 +#define IDI_TASKBAR_NOT_RUNNING 102 +#define IDI_TASKBAR_NOT_WORKING 103 +#define IDI_TASKBAR_NOT_CONNECTED 104 +#define IDI_TASKBAR_CONNECTED 105 +#define IDR_TASKBAR 107 +#define IDD_TASKBAR_STATUS 108 +#define IDC_TASKBAR_STATUS_STATUS 1000 +#define IDC_TASKBAR_STATUS_CLIENTS 1001 +#define IDC_TASKBAR_QUIT 40003 +#define IDC_TASKBAR_STATUS 40004 +#define IDC_TASKBAR_LOG 40005 +#define IDC_RELOAD_CONFIG 40006 +#define IDC_FORCE_RECONNECT 40007 +#define IDC_TASKBAR_SHOW_LOG 40008 +#define IDC_TASKBAR_LOG_LEVEL_ERROR 40009 +#define IDC_TASKBAR_LOG_LEVEL_WARNING 40010 +#define IDC_TASKBAR_LOG_LEVEL_NOTE 40011 +#define IDC_TASKBAR_LOG_LEVEL_INFO 40012 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG 40013 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG1 40014 +#define IDC_TASKBAR_LOG_LEVEL_DEBUG2 40015 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40016 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp new file mode 100644 index 00000000..4319af1e --- /dev/null +++ b/cmd/synergys/synergys.cpp @@ -0,0 +1,1312 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientListener.h" +#include "CClientProxy.h" +#include "CConfig.h" +#include "CPrimaryClient.h" +#include "CServer.h" +#include "CScreen.h" +#include "ProtocolTypes.h" +#include "Version.h" +#include "XScreen.h" +#include "CSocketMultiplexer.h" +#include "CTCPSocketFactory.h" +#include "XSocket.h" +#include "CThread.h" +#include "CEventQueue.h" +#include "CFunctionEventJob.h" +#include "CLog.h" +#include "CString.h" +#include "CStringUtil.h" +#include "LogOutputters.h" +#include "CArch.h" +#include "XArch.h" +#include "stdfstream.h" +#include + +#define DAEMON_RUNNING(running_) +#if WINAPI_MSWINDOWS +#include "CArchMiscWindows.h" +#include "CMSWindowsScreen.h" +#include "CMSWindowsUtil.h" +#include "CMSWindowsServerTaskBarReceiver.h" +#include "resource.h" +#undef DAEMON_RUNNING +#define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) +#elif WINAPI_XWINDOWS +#include "CXWindowsScreen.h" +#include "CXWindowsServerTaskBarReceiver.h" +#elif WINAPI_CARBON +#include "COSXScreen.h" +#include "COSXServerTaskBarReceiver.h" +#endif + +// platform dependent name of a daemon +#if SYSAPI_WIN32 +#define DAEMON_NAME "Synergy Server" +#elif SYSAPI_UNIX +#define DAEMON_NAME "synergys" +#endif + +// configuration file name +#if SYSAPI_WIN32 +#define USR_CONFIG_NAME "synergy.sgc" +#define SYS_CONFIG_NAME "synergy.sgc" +#elif SYSAPI_UNIX +#define USR_CONFIG_NAME ".synergy.conf" +#define SYS_CONFIG_NAME "synergy.conf" +#endif + +typedef int (*StartupFunc)(int, char**); +static void parse(int argc, const char* const* argv); +static bool loadConfig(const CString& pathname); +static void loadConfig(); + +// +// program arguments +// + +#define ARG CArgs::s_instance + +class CArgs { +public: + CArgs() : + m_pname(NULL), + m_backend(false), + m_restartable(true), + m_daemon(true), + m_configFile(), + m_logFilter(NULL), + m_display(NULL), + m_synergyAddress(NULL), + m_config(NULL) + { s_instance = this; } + ~CArgs() { s_instance = NULL; } + +public: + static CArgs* s_instance; + const char* m_pname; + bool m_backend; + bool m_restartable; + bool m_daemon; + CString m_configFile; + const char* m_logFilter; + const char* m_display; + CString m_name; + CNetworkAddress* m_synergyAddress; + CConfig* m_config; +}; + +CArgs* CArgs::s_instance = NULL; + + +// +// platform dependent factories +// + +static +CScreen* +createScreen() +{ +#if WINAPI_MSWINDOWS + return new CScreen(new CMSWindowsScreen(true)); +#elif WINAPI_XWINDOWS + return new CScreen(new CXWindowsScreen(ARG->m_display, true)); +#elif WINAPI_CARBON + return new CScreen(new COSXScreen(true)); +#endif +} + +static +CServerTaskBarReceiver* +createTaskBarReceiver(const CBufferedLogOutputter* logBuffer) +{ +#if WINAPI_MSWINDOWS + return new CMSWindowsServerTaskBarReceiver( + CMSWindowsScreen::getInstance(), logBuffer); +#elif WINAPI_XWINDOWS + return new CXWindowsServerTaskBarReceiver(logBuffer); +#elif WINAPI_CARBON + return new COSXServerTaskBarReceiver(logBuffer); +#endif +} + + +// +// platform independent main +// + +enum EServerState { + kUninitialized, + kInitializing, + kInitializingToStart, + kInitialized, + kStarting, + kStarted +}; + +static EServerState s_serverState = kUninitialized; +static CServer* s_server = NULL; +static CScreen* s_serverScreen = NULL; +static CPrimaryClient* s_primaryClient = NULL; +static CClientListener* s_listener = NULL; +static CServerTaskBarReceiver* s_taskBarReceiver = NULL; +static CEvent::Type s_reloadConfigEvent = CEvent::kUnknown; +static CEvent::Type s_forceReconnectEvent = CEvent::kUnknown; +static bool s_suspended = false; +static CEventQueueTimer* s_timer = NULL; + +CEvent::Type +getReloadConfigEvent() +{ + return CEvent::registerTypeOnce(s_reloadConfigEvent, "reloadConfig"); +} + +CEvent::Type +getForceReconnectEvent() +{ + return CEvent::registerTypeOnce(s_forceReconnectEvent, "forceReconnect"); +} + +static +void +updateStatus() +{ + s_taskBarReceiver->updateStatus(s_server, ""); +} + +static +void +updateStatus(const CString& msg) +{ + s_taskBarReceiver->updateStatus(s_server, msg); +} + +static +void +handleClientConnected(const CEvent&, void* vlistener) +{ + CClientListener* listener = reinterpret_cast(vlistener); + CClientProxy* client = listener->getNextClient(); + if (client != NULL) { + s_server->adoptClient(client); + updateStatus(); + } +} + +static +CClientListener* +openClientListener(const CNetworkAddress& address) +{ + CClientListener* listen = + new CClientListener(address, new CTCPSocketFactory, NULL); + EVENTQUEUE->adoptHandler(CClientListener::getConnectedEvent(), listen, + new CFunctionEventJob( + &handleClientConnected, listen)); + return listen; +} + +static +void +closeClientListener(CClientListener* listen) +{ + if (listen != NULL) { + EVENTQUEUE->removeHandler(CClientListener::getConnectedEvent(), listen); + delete listen; + } +} + +static +void +handleScreenError(const CEvent&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + + +static void handleSuspend(const CEvent& event, void*); +static void handleResume(const CEvent& event, void*); + +static +CScreen* +openServerScreen() +{ + CScreen* screen = createScreen(); + EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(), + screen->getEventTarget(), + new CFunctionEventJob( + &handleScreenError)); + EVENTQUEUE->adoptHandler(IScreen::getSuspendEvent(), + screen->getEventTarget(), + new CFunctionEventJob( + &handleSuspend)); + EVENTQUEUE->adoptHandler(IScreen::getResumeEvent(), + screen->getEventTarget(), + new CFunctionEventJob( + &handleResume)); + return screen; +} + +static +void +closeServerScreen(CScreen* screen) +{ + if (screen != NULL) { + EVENTQUEUE->removeHandler(IScreen::getErrorEvent(), + screen->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getSuspendEvent(), + screen->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getResumeEvent(), + screen->getEventTarget()); + delete screen; + } +} + +static +CPrimaryClient* +openPrimaryClient(const CString& name, CScreen* screen) +{ + LOG((CLOG_DEBUG1 "creating primary screen")); + return new CPrimaryClient(name, screen); +} + +static +void +closePrimaryClient(CPrimaryClient* primaryClient) +{ + delete primaryClient; +} + +static +void +handleNoClients(const CEvent&, void*) +{ + updateStatus(); +} + +static +void +handleClientsDisconnected(const CEvent&, void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + +static +CServer* +openServer(const CConfig& config, CPrimaryClient* primaryClient) +{ + CServer* server = new CServer(config, primaryClient); + EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, + new CFunctionEventJob(handleNoClients)); + return server; +} + +static +void +closeServer(CServer* server) +{ + if (server == NULL) { + return; + } + + // tell all clients to disconnect + server->disconnect(); + + // wait for clients to disconnect for up to timeout seconds + double timeout = 3.0; + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new CFunctionEventJob(handleClientsDisconnected)); + EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server, + new CFunctionEventJob(handleClientsDisconnected)); + CEvent event; + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + EVENTQUEUE->removeHandler(CEvent::kTimer, timer); + EVENTQUEUE->deleteTimer(timer); + EVENTQUEUE->removeHandler(CServer::getDisconnectedEvent(), server); + + // done with server + delete server; +} + +static bool initServer(); +static bool startServer(); + +static +void +stopRetryTimer() +{ + if (s_timer != NULL) { + EVENTQUEUE->deleteTimer(s_timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, NULL); + s_timer = NULL; + } +} + +static +void +retryHandler(const CEvent&, void*) +{ + // discard old timer + assert(s_timer != NULL); + stopRetryTimer(); + + // try initializing/starting the server again + switch (s_serverState) { + case kUninitialized: + case kInitialized: + case kStarted: + assert(0 && "bad internal server state"); + break; + + case kInitializing: + LOG((CLOG_DEBUG1 "retry server initialization")); + s_serverState = kUninitialized; + if (!initServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + break; + + case kInitializingToStart: + LOG((CLOG_DEBUG1 "retry server initialization")); + s_serverState = kUninitialized; + if (!initServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + else if (s_serverState == kInitialized) { + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + } + break; + + case kStarting: + LOG((CLOG_DEBUG1 "retry starting server")); + s_serverState = kInitialized; + if (!startServer()) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + break; + } +} + +static +bool +initServer() +{ + // skip if already initialized or initializing + if (s_serverState != kUninitialized) { + return true; + } + + double retryTime; + CScreen* serverScreen = NULL; + CPrimaryClient* primaryClient = NULL; + try { + CString name = ARG->m_config->getCanonicalName(ARG->m_name); + serverScreen = openServerScreen(); + primaryClient = openPrimaryClient(name, serverScreen); + s_serverScreen = serverScreen; + s_primaryClient = primaryClient; + s_serverState = kInitialized; + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "cannot open primary screen: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + updateStatus(CString("cannot open primary screen: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "cannot open primary screen: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + + if (ARG->m_restartable) { + // install a timer and handler to retry later + assert(s_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer, + new CFunctionEventJob(&retryHandler, NULL)); + s_serverState = kInitializing; + return true; + } + else { + // don't try again + return false; + } +} + +static +bool +startServer() +{ + // skip if already started or starting + if (s_serverState == kStarting || s_serverState == kStarted) { + return true; + } + + // initialize if necessary + if (s_serverState != kInitialized) { + if (!initServer()) { + // hard initialization failure + return false; + } + if (s_serverState == kInitializing) { + // not ready to start + s_serverState = kInitializingToStart; + return true; + } + assert(s_serverState == kInitialized); + } + + double retryTime; + CClientListener* listener = NULL; + try { + listener = openClientListener(ARG->m_config->getSynergyAddress()); + s_server = openServer(*ARG->m_config, s_primaryClient); + s_listener = listener; + updateStatus(); + LOG((CLOG_NOTE "started server")); + s_serverState = kStarted; + return true; + } + catch (XSocketAddressInUse& e) { + LOG((CLOG_WARN "cannot listen for clients: %s", e.what())); + closeClientListener(listener); + updateStatus(CString("cannot listen for clients: ") + e.what()); + retryTime = 10.0; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closeClientListener(listener); + return false; + } + + if (ARG->m_restartable) { + // install a timer and handler to retry later + assert(s_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer, + new CFunctionEventJob(&retryHandler, NULL)); + s_serverState = kStarting; + return true; + } + else { + // don't try again + return false; + } +} + +static +void +stopServer() +{ + if (s_serverState == kStarted) { + closeClientListener(s_listener); + closeServer(s_server); + s_server = NULL; + s_listener = NULL; + s_serverState = kInitialized; + } + else if (s_serverState == kStarting) { + stopRetryTimer(); + s_serverState = kInitialized; + } + assert(s_server == NULL); + assert(s_listener == NULL); +} + +static +void +cleanupServer() +{ + stopServer(); + if (s_serverState == kInitialized) { + closePrimaryClient(s_primaryClient); + closeServerScreen(s_serverScreen); + s_primaryClient = NULL; + s_serverScreen = NULL; + s_serverState = kUninitialized; + } + else if (s_serverState == kInitializing || + s_serverState == kInitializingToStart) { + stopRetryTimer(); + s_serverState = kUninitialized; + } + assert(s_primaryClient == NULL); + assert(s_serverScreen == NULL); + assert(s_serverState == kUninitialized); +} + +static +void +handleSuspend(const CEvent&, void*) +{ + if (!s_suspended) { + LOG((CLOG_INFO "suspend")); + stopServer(); + s_suspended = true; + } +} + +static +void +handleResume(const CEvent&, void*) +{ + if (s_suspended) { + LOG((CLOG_INFO "resume")); + startServer(); + s_suspended = false; + } +} + +static +void +reloadSignalHandler(CArch::ESignal, void*) +{ + EVENTQUEUE->addEvent(CEvent(getReloadConfigEvent(), + IEventQueue::getSystemTarget())); +} + +static +void +reloadConfig(const CEvent&, void*) +{ + LOG((CLOG_DEBUG "reload configuration")); + if (loadConfig(ARG->m_configFile)) { + if (s_server != NULL) { + s_server->setConfig(*ARG->m_config); + } + LOG((CLOG_NOTE "reloaded configuration")); + } +} + +static +void +forceReconnect(const CEvent&, void*) +{ + if (s_server != NULL) { + s_server->disconnect(); + } +} + +static +int +mainLoop() +{ + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + CSocketMultiplexer multiplexer; + + // create the event queue + CEventQueue eventQueue; + + // if configuration has no screens then add this system + // as the default + if (ARG->m_config->begin() == ARG->m_config->end()) { + ARG->m_config->addScreen(ARG->m_name); + } + + // set the contact address, if provided, in the config. + // otherwise, if the config doesn't have an address, use + // the default. + if (ARG->m_synergyAddress->isValid()) { + ARG->m_config->setSynergyAddress(*ARG->m_synergyAddress); + } + else if (!ARG->m_config->getSynergyAddress().isValid()) { + ARG->m_config->setSynergyAddress(CNetworkAddress(kDefaultPort)); + } + + // canonicalize the primary screen name + CString primaryName = ARG->m_config->getCanonicalName(ARG->m_name); + if (primaryName.empty()) { + LOG((CLOG_CRIT "unknown screen name `%s'", ARG->m_name.c_str())); + return kExitFailed; + } + + // start the server. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + return kExitFailed; + } + + // handle hangup signal by reloading the server's configuration + ARCH->setSignalHandler(CArch::kHANGUP, &reloadSignalHandler, NULL); + EVENTQUEUE->adoptHandler(getReloadConfigEvent(), + IEventQueue::getSystemTarget(), + new CFunctionEventJob(&reloadConfig)); + + // handle force reconnect event by disconnecting clients. they'll + // reconnect automatically. + EVENTQUEUE->adoptHandler(getForceReconnectEvent(), + IEventQueue::getSystemTarget(), + new CFunctionEventJob(&forceReconnect)); + + // run event loop. if startServer() failed we're supposed to retry + // later. the timer installed by startServer() will take care of + // that. + CEvent event; + DAEMON_RUNNING(true); + EVENTQUEUE->getEvent(event); + while (event.getType() != CEvent::kQuit) { + EVENTQUEUE->dispatchEvent(event); + CEvent::deleteData(event); + EVENTQUEUE->getEvent(event); + } + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping server")); + EVENTQUEUE->removeHandler(getForceReconnectEvent(), + IEventQueue::getSystemTarget()); + EVENTQUEUE->removeHandler(getReloadConfigEvent(), + IEventQueue::getSystemTarget()); + cleanupServer(); + updateStatus(); + LOG((CLOG_NOTE "stopped server")); + + return kExitSuccess; +} + +static +int +daemonMainLoop(int, const char**) +{ +#if SYSAPI_WIN32 + CSystemLogger sysLogger(DAEMON_NAME, false); +#else + CSystemLogger sysLogger(DAEMON_NAME, true); +#endif + return mainLoop(); +} + +static +int +standardStartup(int argc, char** argv) +{ + if (!ARG->m_daemon) { + ARCH->showConsole(false); + } + + // parse command line + parse(argc, argv); + + // load configuration + loadConfig(); + + // daemonize if requested + if (ARG->m_daemon) { + return ARCH->daemonize(DAEMON_NAME, &daemonMainLoop); + } + else { + return mainLoop(); + } +} + +static +int +run(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) +{ + // general initialization + ARG->m_synergyAddress = new CNetworkAddress; + ARG->m_config = new CConfig; + ARG->m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + // save log messages + CBufferedLogOutputter logBuffer(1000); + CLOG->insert(&logBuffer, true); + + // make the task bar receiver. the user can control this app + // through the task bar. + s_taskBarReceiver = createTaskBarReceiver(&logBuffer); + + // run + int result = startup(argc, argv); + + // done with task bar receiver + delete s_taskBarReceiver; + + // done with log buffer + CLOG->remove(&logBuffer); + + delete ARG->m_config; + delete ARG->m_synergyAddress; + return result; +} + + +// +// command line parsing +// + +#define BYE "\nTry `%s --help' for more information." + +static void (*bye)(int) = &exit; + +static +void +version() +{ + LOG((CLOG_PRINT +"%s %s, protocol version %d.%d\n" +"%s", + ARG->m_pname, + kVersion, + kProtocolMajorVersion, + kProtocolMinorVersion, + kCopyright)); +} + +static +void +help() +{ +#if WINAPI_XWINDOWS +# define USAGE_DISPLAY_ARG \ +" [--display ]" +# define USAGE_DISPLAY_INFO \ +" --display connect to the X server at \n" +#else +# define USAGE_DISPLAY_ARG +# define USAGE_DISPLAY_INFO +#endif + +#if SYSAPI_WIN32 + +# define PLATFORM_ARGS \ +" [--daemon|--no-daemon]" +# define PLATFORM_DESC +# define PLATFORM_EXTRA \ +"At least one command line argument is required. If you don't otherwise\n" \ +"need an argument use `--daemon'.\n" \ +"\n" + +#else + +# define PLATFORM_ARGS \ +" [--daemon|--no-daemon]" +# define PLATFORM_DESC +# define PLATFORM_EXTRA + +#endif + + LOG((CLOG_PRINT +"Usage: %s" +" [--address
]" +" [--config ]" +" [--debug ]" +USAGE_DISPLAY_ARG +" [--name ]" +" [--restart|--no-restart]" +PLATFORM_ARGS +"\n\n" +"Start the synergy mouse/keyboard sharing server.\n" +"\n" +" -a, --address
listen for clients on the given address.\n" +" -c, --config use the named configuration file instead.\n" +" -d, --debug filter out log messages with priorty below level.\n" +" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" +" DEBUG, DEBUG1, DEBUG2.\n" +USAGE_DISPLAY_INFO +" -f, --no-daemon run the server in the foreground.\n" +"* --daemon run the server as a daemon.\n" +" -n, --name use screen-name instead the hostname to identify\n" +" this screen in the configuration.\n" +" -1, --no-restart do not try to restart the server if it fails for\n" +" some reason.\n" +"* --restart restart the server automatically if it fails.\n" +PLATFORM_DESC +" -h, --help display this help and exit.\n" +" --version display version information and exit.\n" +"\n" +"* marks defaults.\n" +"\n" +PLATFORM_EXTRA +"The argument for --address is of the form: [][:]. The\n" +"hostname must be the address or hostname of an interface on the system.\n" +"The default is to listen on all interfaces. The port overrides the\n" +"default port, %d.\n" +"\n" +"If no configuration file pathname is provided then the first of the\n" +"following to load successfully sets the configuration:\n" +" %s\n" +" %s\n" +"If no configuration file can be loaded then the configuration uses its\n" +"defaults with just the server screen.\n" +"\n" +"Where log messages go depends on the platform and whether or not the\n" +"server is running as a daemon.", + ARG->m_pname, + kDefaultPort, + ARCH->concatPath( + ARCH->getUserDirectory(), + USR_CONFIG_NAME).c_str(), + ARCH->concatPath( + ARCH->getSystemDirectory(), + SYS_CONFIG_NAME).c_str())); +} + +static +bool +isArg(int argi, int argc, const char* const* argv, + const char* name1, const char* name2, + int minRequiredParameters = 0) +{ + if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || + (name2 != NULL && strcmp(argv[argi], name2) == 0)) { + // match. check args left. + if (argi + minRequiredParameters >= argc) { + LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE, + ARG->m_pname, argv[argi], ARG->m_pname)); + bye(kExitArgs); + } + return true; + } + + // no match + return false; +} + +static +void +parse(int argc, const char* const* argv) +{ + assert(ARG->m_pname != NULL); + assert(argv != NULL); + assert(argc >= 1); + + // set defaults + ARG->m_name = ARCH->getHostName(); + + // parse options + int i = 1; + for (; i < argc; ++i) { + if (isArg(i, argc, argv, "-d", "--debug", 1)) { + // change logging level + ARG->m_logFilter = argv[++i]; + } + + else if (isArg(i, argc, argv, "-a", "--address", 1)) { + // save listen address + try { + *ARG->m_synergyAddress = CNetworkAddress(argv[i + 1], + kDefaultPort); + ARG->m_synergyAddress->resolve(); + } + catch (XSocketAddress& e) { + LOG((CLOG_PRINT "%s: %s" BYE, + ARG->m_pname, e.what(), ARG->m_pname)); + bye(kExitArgs); + } + ++i; + } + + else if (isArg(i, argc, argv, "-n", "--name", 1)) { + // save screen name + ARG->m_name = argv[++i]; + } + + else if (isArg(i, argc, argv, "-c", "--config", 1)) { + // save configuration file path + ARG->m_configFile = argv[++i]; + } + +#if WINAPI_XWINDOWS + else if (isArg(i, argc, argv, "-display", "--display", 1)) { + // use alternative display + ARG->m_display = argv[++i]; + } +#endif + + else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + // not a daemon + ARG->m_daemon = false; + } + + else if (isArg(i, argc, argv, NULL, "--daemon")) { + // daemonize + ARG->m_daemon = true; + } + + else if (isArg(i, argc, argv, "-1", "--no-restart")) { + // don't try to restart + ARG->m_restartable = false; + } + + else if (isArg(i, argc, argv, NULL, "--restart")) { + // try to restart + ARG->m_restartable = true; + } + + else if (isArg(i, argc, argv, "-z", NULL)) { + ARG->m_backend = true; + } + + else if (isArg(i, argc, argv, "-h", "--help")) { + help(); + bye(kExitSuccess); + } + + else if (isArg(i, argc, argv, NULL, "--version")) { + version(); + bye(kExitSuccess); + } + + else if (isArg(i, argc, argv, "--", NULL)) { + // remaining arguments are not options + ++i; + break; + } + + else if (argv[i][0] == '-') { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + ARG->m_pname, argv[i], ARG->m_pname)); + bye(kExitArgs); + } + + else { + // this and remaining arguments are not options + break; + } + } + + // no non-option arguments are allowed + if (i != argc) { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + ARG->m_pname, argv[i], ARG->m_pname)); + bye(kExitArgs); + } + + // increase default filter level for daemon. the user must + // explicitly request another level for a daemon. + if (ARG->m_daemon && ARG->m_logFilter == NULL) { +#if SYSAPI_WIN32 + if (CArchMiscWindows::isWindows95Family()) { + // windows 95 has no place for logging so avoid showing + // the log console window. + ARG->m_logFilter = "FATAL"; + } + else +#endif + { + ARG->m_logFilter = "NOTE"; + } + } + + // set log filter + if (!CLOG->setFilter(ARG->m_logFilter)) { + LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + ARG->m_pname, ARG->m_logFilter, ARG->m_pname)); + bye(kExitArgs); + } + + // identify system + LOG((CLOG_INFO "Synergy server %s on %s", kVersion, ARCH->getOSName().c_str())); +} + +static +bool +loadConfig(const CString& pathname) +{ + try { + // load configuration + LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str())); + std::ifstream configStream(pathname.c_str()); + if (!configStream.is_open()) { + // report failure to open configuration as a debug message + // since we try several paths and we expect some to be + // missing. + LOG((CLOG_DEBUG "cannot open configuration \"%s\"", + pathname.c_str())); + return false; + } + configStream >> *ARG->m_config; + LOG((CLOG_DEBUG "configuration read successfully")); + return true; + } + catch (XConfigRead& e) { + // report error in configuration file + LOG((CLOG_ERR "cannot read configuration \"%s\": %s", + pathname.c_str(), e.what())); + } + return false; +} + +static +void +loadConfig() +{ + bool loaded = false; + + // load the config file, if specified + if (!ARG->m_configFile.empty()) { + loaded = loadConfig(ARG->m_configFile); + } + + // load the default configuration if no explicit file given + else { + // get the user's home directory + CString path = ARCH->getUserDirectory(); + if (!path.empty()) { + // complete path + path = ARCH->concatPath(path, USR_CONFIG_NAME); + + // now try loading the user's configuration + if (loadConfig(path)) { + loaded = true; + ARG->m_configFile = path; + } + } + if (!loaded) { + // try the system-wide config file + path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, SYS_CONFIG_NAME); + if (loadConfig(path)) { + loaded = true; + ARG->m_configFile = path; + } + } + } + } + + if (!loaded) { + LOG((CLOG_PRINT "%s: no configuration available", ARG->m_pname)); + bye(kExitConfig); + } +} + + +// +// platform dependent entry points +// + +#if SYSAPI_WIN32 + +static bool s_hasImportantLogMessages = false; + +// +// CMessageBoxOutputter +// +// This class writes severe log messages to a message box +// + +class CMessageBoxOutputter : public ILogOutputter { +public: + CMessageBoxOutputter() { } + virtual ~CMessageBoxOutputter() { } + + // ILogOutputter overrides + virtual void open(const char*) { } + virtual void close() { } + virtual void show(bool) { } + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const { return ""; } +}; + +bool +CMessageBoxOutputter::write(ELevel level, const char* message) +{ + // note any important messages the user may need to know about + if (level <= CLog::kWARNING) { + s_hasImportantLogMessages = true; + } + + // FATAL and PRINT messages get a dialog box if not running as + // backend. if we're running as a backend the user will have + // a chance to see the messages when we exit. + if (!ARG->m_backend && level <= CLog::kFATAL) { + MessageBox(NULL, message, ARG->m_pname, MB_OK | MB_ICONWARNING); + return false; + } + else { + return true; + } +} + +static +void +byeThrow(int x) +{ + CArchMiscWindows::daemonFailed(x); +} + +static +int +daemonNTMainLoop(int argc, const char** argv) +{ + parse(argc, argv); + ARG->m_backend = false; + loadConfig(); + return CArchMiscWindows::runDaemon(mainLoop); +} + +static +int +daemonNTStartup(int, char**) +{ + CSystemLogger sysLogger(DAEMON_NAME, false); + bye = &byeThrow; + return ARCH->daemonize(DAEMON_NAME, &daemonNTMainLoop); +} + +static +int +foregroundStartup(int argc, char** argv) +{ + ARCH->showConsole(false); + + // parse command line + parse(argc, argv); + + // load configuration + loadConfig(); + + // never daemonize + return mainLoop(); +} + +static +void +showError(HINSTANCE instance, const char* title, UINT id, const char* arg) +{ + CString fmt = CMSWindowsUtil::getString(instance, id); + CString msg = CStringUtil::format(fmt.c_str(), arg); + MessageBox(NULL, msg.c_str(), title, MB_OK | MB_ICONWARNING); +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) +{ + try { + CArchMiscWindows::setIcons((HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 32, 32, LR_SHARED), + (HICON)LoadImage(instance, + MAKEINTRESOURCE(IDI_SYNERGY), + IMAGE_ICON, + 16, 16, LR_SHARED)); + CArch arch(instance); + CMSWindowsScreen::init(instance); + CLOG; + CThread::getCurrentThread().setPriority(-14); + CArgs args; + + // set title on log window + ARCH->openConsole((CString(kAppVersion) + " " + "Server").c_str()); + + // windows NT family starts services using no command line options. + // since i'm not sure how to tell the difference between that and + // a user providing no options we'll assume that if there are no + // arguments and we're on NT then we're being invoked as a service. + // users on NT can use `--daemon' or `--no-daemon' to force us out + // of the service code path. + StartupFunc startup = &standardStartup; + if (!CArchMiscWindows::isWindows95Family()) { + if (__argc <= 1) { + startup = &daemonNTStartup; + } + else { + startup = &foregroundStartup; + } + } + + // send PRINT and FATAL output to a message box + int result = run(__argc, __argv, new CMessageBoxOutputter, startup); + + // let user examine any messages if we're running as a backend + // by putting up a dialog box before exiting. + if (args.m_backend && s_hasImportantLogMessages) { + showError(instance, args.m_pname, IDS_FAILED, ""); + } + + delete CLOG; + return result; + } + catch (XBase& e) { + showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, e.what()); + //throw; + } + catch (XArch& e) { + showError(instance, __argv[0], IDS_INIT_FAILED, e.what().c_str()); + } + catch (...) { + showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, ""); + //throw; + } + return kExitFailed; +} + +#elif SYSAPI_UNIX + +int +main(int argc, char** argv) +{ + CArgs args; + try { + int result; + CArch arch; + CLOG; + CArgs args; + result = run(argc, argv, NULL, &standardStartup); + delete CLOG; + return result; + } + catch (XBase& e) { + LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what())); + throw; + } + catch (XArch& e) { + LOG((CLOG_CRIT "Initialization failed: %s" BYE, e.what().c_str())); + return kExitFailed; + } + catch (...) { + LOG((CLOG_CRIT "Uncaught exception: \n")); + throw; + } +} + +#else + +#error no main() for platform + +#endif diff --git a/cmd/synergys/synergys.ico b/cmd/synergys/synergys.ico new file mode 100644 index 00000000..89f965f4 Binary files /dev/null and b/cmd/synergys/synergys.ico differ diff --git a/cmd/synergys/synergys.rc b/cmd/synergys/synergys.rc new file mode 100644 index 00000000..d56a4313 --- /dev/null +++ b/cmd/synergys/synergys.rc @@ -0,0 +1,146 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#if !defined(IDC_STATIC) +#define IDC_STATIC (-1) +#endif + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SYNERGY ICON DISCARDABLE "synergys.ico" +IDI_TASKBAR_NOT_RUNNING ICON DISCARDABLE "tb_idle.ico" +IDI_TASKBAR_NOT_WORKING ICON DISCARDABLE "tb_error.ico" +IDI_TASKBAR_NOT_CONNECTED ICON DISCARDABLE "tb_wait.ico" +IDI_TASKBAR_CONNECTED ICON DISCARDABLE "tb_run.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_TASKBAR MENU DISCARDABLE +BEGIN + POPUP "Synergy" + BEGIN + MENUITEM "Show Status", IDC_TASKBAR_STATUS + MENUITEM "Show Log", IDC_TASKBAR_SHOW_LOG + MENUITEM "Copy Log To Clipboard", IDC_TASKBAR_LOG + POPUP "Set Log Level" + BEGIN + MENUITEM "Error", IDC_TASKBAR_LOG_LEVEL_ERROR + + MENUITEM "Warning", IDC_TASKBAR_LOG_LEVEL_WARNING + + MENUITEM "Note", IDC_TASKBAR_LOG_LEVEL_NOTE + + MENUITEM "Info", IDC_TASKBAR_LOG_LEVEL_INFO + + MENUITEM "Debug", IDC_TASKBAR_LOG_LEVEL_DEBUG + + MENUITEM "Debug1", IDC_TASKBAR_LOG_LEVEL_DEBUG1 + + MENUITEM "Debug2", IDC_TASKBAR_LOG_LEVEL_DEBUG2 + + END + MENUITEM "Reload Configuration", IDC_RELOAD_CONFIG + MENUITEM "Force Reconnect", IDC_FORCE_RECONNECT + MENUITEM SEPARATOR + MENUITEM "Quit", IDC_TASKBAR_QUIT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_TASKBAR_STATUS DIALOG DISCARDABLE 0, 0, 145, 60 +STYLE DS_MODALFRAME | WS_POPUP +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_TASKBAR_STATUS_STATUS,3,3,139,12,ES_AUTOHSCROLL | + ES_READONLY | NOT WS_BORDER + LISTBOX IDC_TASKBAR_STATUS_CLIENTS,3,17,139,40,NOT LBS_NOTIFY | + LBS_SORT | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_VSCROLL | + WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_FAILED "Synergy is about to quit with errors or warnings. Please check the log then click OK." + IDS_INIT_FAILED "Synergy failed to initialize: %{1}" + IDS_UNCAUGHT_EXCEPTION "Uncaught exception: %{1}" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/cmd/synergys/tb_error.ico b/cmd/synergys/tb_error.ico new file mode 100644 index 00000000..746a87c9 Binary files /dev/null and b/cmd/synergys/tb_error.ico differ diff --git a/cmd/synergys/tb_idle.ico b/cmd/synergys/tb_idle.ico new file mode 100644 index 00000000..4e13a264 Binary files /dev/null and b/cmd/synergys/tb_idle.ico differ diff --git a/cmd/synergys/tb_run.ico b/cmd/synergys/tb_run.ico new file mode 100644 index 00000000..88e160cb Binary files /dev/null and b/cmd/synergys/tb_run.ico differ diff --git a/cmd/synergys/tb_wait.ico b/cmd/synergys/tb_wait.ico new file mode 100644 index 00000000..257be0a1 Binary files /dev/null and b/cmd/synergys/tb_wait.ico differ diff --git a/config/config.guess b/config/config.guess new file mode 100755 index 00000000..6ead80a0 --- /dev/null +++ b/config/config.guess @@ -0,0 +1,1327 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2001-08-21' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Written by Per Bothner . +# Please send patches to . +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + + +dummy=dummy-$$ +trap 'rm -f $dummy.c $dummy.o $dummy.rel $dummy; exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +set_cc_for_build='case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int dummy(){}" > $dummy.c ; + for c in cc gcc c89 ; do + ($c $dummy.c -c -o $dummy.o) >/dev/null 2>&1 ; + if test $? = 0 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + rm -f $dummy.c $dummy.o $dummy.rel ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_MACHINE}" in + i?86) + test -z "$VENDOR" && VENDOR=pc + ;; + *) + test -z "$VENDOR" && VENDOR=unknown + ;; +esac +test -f /etc/SuSE-release && VENDOR=suse + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # Netbsd (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # Determine the machine/vendor (is the vendor relevant). + case "${UNAME_MACHINE}" in + amiga) machine=m68k-unknown ;; + arm32) machine=arm-unknown ;; + atari*) machine=m68k-atari ;; + sun3*) machine=m68k-sun ;; + mac68k) machine=m68k-apple ;; + macppc) machine=powerpc-apple ;; + hp3[0-9][05]) machine=m68k-hp ;; + ibmrt|romp-ibm) machine=romp-ibm ;; + *) machine=${UNAME_MACHINE}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE}" in + i386|sparc|amiga|arm*|hp300|mvme68k|vax|atari|luna68k|mac68k|news68k|next68k|pc532|sun3*|x68k) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + alpha:OSF1:*:*) + if test $UNAME_RELEASE = "V4.0"; then + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + fi + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + cat <$dummy.s + .data +\$Lformat: + .byte 37,100,45,37,120,10,0 # "%d-%x\n" + + .text + .globl main + .align 4 + .ent main +main: + .frame \$30,16,\$26,0 + ldgp \$29,0(\$27) + .prologue 1 + .long 0x47e03d80 # implver \$0 + lda \$2,-1 + .long 0x47e20c21 # amask \$2,\$1 + lda \$16,\$Lformat + mov \$0,\$17 + not \$1,\$18 + jsr \$26,printf + ldgp \$29,0(\$26) + mov 0,\$16 + jsr \$26,exit + .end main +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.s -o $dummy 2>/dev/null + if test "$?" = 0 ; then + case `./$dummy` in + 0-0) + UNAME_MACHINE="alpha" + ;; + 1-0) + UNAME_MACHINE="alphaev5" + ;; + 1-1) + UNAME_MACHINE="alphaev56" + ;; + 1-101) + UNAME_MACHINE="alphapca56" + ;; + 2-303) + UNAME_MACHINE="alphaev6" + ;; + 2-307) + UNAME_MACHINE="alphaev67" + ;; + 2-1307) + UNAME_MACHINE="alphaev68" + ;; + esac + fi + rm -f $dummy.s $dummy + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[VTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + arc64:OpenBSD:*:*) + echo mips64el-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hkmips:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mips-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(head -1 /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + sparc*:NetBSD:*) + echo `uname -p`-unknown-netbsd${UNAME_RELEASE} + exit 0 ;; + atari*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + sun3*:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy \ + && ./$dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | head -1 | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + case "${HPUX_REV}" in + 11.[0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + esac ;; + esac + fi ;; + esac + if [ "${HP_ARCH}" = "" ]; then + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + eval $set_cc_for_build + (CCOPTS= $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null ) && HP_ARCH=`./$dummy` + if test -z "$HP_ARCH"; then HP_ARCH=hppa; fi + rm -f $dummy.c $dummy + fi ;; + esac + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy && ./$dummy && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + hppa*:OpenBSD:*:*) + echo hppa-unknown-openbsd + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*X-MP:*:*:*) + echo xmp-cray-unicos + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3D:*:*:*) + echo alpha-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY-2:*:*:*) + echo cray2-cray-unicos + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i386-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + mips:Linux:*:*) + case `sed -n '/^byte/s/^.*: \(.*\) endian/\1/p' < /proc/cpuinfo` in + big) echo mips-${VENDOR}-linux && exit 0 ;; + little) echo mipsel-${VENDOR}-linux && exit 0 ;; + esac + case `sed -n '/^system type/s/^.*: \([^ ]*\).*/\1/p' < /proc/cpuinfo` in + SGI|sgi) echo mips-${VENDOR}-linux-gnu && exit 0 ;; + esac + ;; + ppc:Linux:*:*|ppc64:Linux:*:*) + echo powerpc-${VENDOR}-linux + exit 0 ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit 0 ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-${VENDOR}-linux${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-${VENDOR}-linux ;; + PA8*) echo hppa2.0-${VENDOR}-linux ;; + *) echo hppa-${VENDOR}-linux ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-${VENDOR}-linux + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-${VENDOR}-linux + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-${VENDOR}-linux + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + ld_supported_targets=`cd /; ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-${VENDOR}-linux" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-${VENDOR}-linuxaout" + exit 0 ;; + coff-i386) + echo "${UNAME_MACHINE}-${VENDOR}-linuxcoff" + exit 0 ;; + "") + # Either a pre-BFD a.out linker (linuxoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-${VENDOR}-linuxoldld" + exit 0 ;; + esac + # Determine whether the default compiler is a.out or elf + cat >$dummy.c < +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif +#ifdef __ELF__ +# ifdef __GLIBC__ +# if __GLIBC__ >= 2 + printf ("%s-${VENDOR}-linux\n", argv[1]); +# else + printf ("%s-${VENDOR}-linuxlibc1\n", argv[1]); +# endif +# else + printf ("%s-${VENDOR}-linuxlibc1\n", argv[1]); +# endif +#else + printf ("%s-${VENDOR}-linuxaout\n", argv[1]); +#endif + return 0; +} +EOF + eval $set_cc_for_build + $CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy "${UNAME_MACHINE}" && rm -f $dummy.c $dummy && exit 0 + rm -f $dummy.c $dummy + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:[78]*) + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|egrep Release|sed -e 's/.*= //')` + (/bin/uname -X|egrep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|egrep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|egrep '^Machine.*Pent ?II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|egrep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[34]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 4850:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + echo `uname -p`-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + if test "${UNAME_MACHINE}" = "x86pc"; then + UNAME_MACHINE=pc + fi + echo `uname -p`-${UNAME_MACHINE}-nto-qnx + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-[KW]:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +eval $set_cc_for_build +$CC_FOR_BUILD $dummy.c -o $dummy 2>/dev/null && ./$dummy && rm -f $dummy.c $dummy && exit 0 +rm -f $dummy.c $dummy + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config/config.sub b/config/config.sub new file mode 100755 index 00000000..83f4b015 --- /dev/null +++ b/config/config.sub @@ -0,0 +1,1410 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +# Free Software Foundation, Inc. + +timestamp='2001-08-13' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit 0;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | storm-chaos* | os2-emx* | windows32-*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | c4x | clipper \ + | d10v | d30v | dsp16xx \ + | fr30 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | m32r | m68000 | m68k | m88k | mcore \ + | mips16 | mips64 | mips64el | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el | mips64vr4300 \ + | mips64vr4300el | mips64vr5000 | mips64vr5000el \ + | mipsbe | mipsel | mipsle | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | ns16k | ns32k \ + | openrisc \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | s390 | s390x \ + | sh | sh[34] | sh[34]eb | shbe | shle \ + | sparc | sparc64 | sparclet | sparclite | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic80 | tron \ + | v850 \ + | we32k \ + | x86 | xscale \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alphapca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armv*-* \ + | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c54x-* \ + | clipper-* | cray2-* | cydra-* \ + | d10v-* | d30v-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | m32r-* \ + | m68000-* | m680[01234]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | mcore-* \ + | mips-* | mips16-* | mips64-* | mips64el-* | mips64orion-* \ + | mips64orionel-* | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* | mipsbe-* | mipsel-* \ + | mipsle-* | mipstx39-* | mipstx39el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | s390-* | s390x-* \ + | sh-* | sh[34]-* | sh[34]eb-* | shbe-* | shle-* \ + | sparc-* | sparc64-* | sparc86x-* | sparclite-* \ + | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* \ + | t3e-* | tahoe-* | thumb-* | tic30-* | tic54x-* | tic80-* | tron-* \ + | v850-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xmp-* | xps100-* | xscale-* \ + | ymp-* \ + | z8k-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | ymp) + basic_machine=ymp-cray + os=-unicos + ;; + cray2) + basic_machine=cray2-cray + os=-unicos + ;; + [cjt]90) + basic_machine=${basic_machine}-cray + os=-unicos + ;; + crds | unos) + basic_machine=m68k-crds + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mipsel*-linux*) + basic_machine=mipsel-unknown + ;; + mips*-linux*) + basic_machine=mips-unknown + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + mmix*) + basic_machine=mmix-knuth + os=-mmixware + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon) + basic_machine=i686-pc + ;; + pentiumii | pentium2) + basic_machine=i686-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc64) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sparclite-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=t3e-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + windows32) + basic_machine=i386-pc + os=-windows32-msvcrt + ;; + xmp) + basic_machine=xmp-cray + os=-unicos + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + mips) + case $os in + linux*) + basic_machine=mips-unknown + ;; + *) + basic_machine=mips-mips + ;; + esac + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh3 | sh4 | sh3eb | sh4eb) + basic_machine=sh-unknown + ;; + sparc | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + c4x*) + basic_machine=c4x-none + os=-coff + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -netbsd* | -openbsd* | -freebsd* | -riscix* \ + | -lynxos* | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto*) + os=-nto-qnx + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -vxsim* | -vxworks*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config/depcomp b/config/depcomp new file mode 100755 index 00000000..65899658 --- /dev/null +++ b/config/depcomp @@ -0,0 +1,411 @@ +#! /bin/sh + +# depcomp - compile a program generating dependencies as side-effects +# Copyright 1999, 2000 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi +# `libtool' can also be set to `yes' or `no'. + +depfile=${depfile-`echo "$object" | sed 's,\([^/]*\)$,.deps/\1,;s/\.\([^.]*\)$/.P\1/'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. + "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz +## The second -e expression handles DOS-style file names with drive letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the `deleted header file' problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. + tr ' ' ' +' < "$tmpdepfile" | +## Some versions of gcc put a space before the `:'. On the theory +## that the space means something, we add a space to the output as +## well. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like `#:fec' to the end of the + # dependency line. + tr ' ' ' +' < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ + tr ' +' ' ' >> $depfile + echo >> $depfile + + # The second pass generates a dummy entry for each header file. + tr ' ' ' +' < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> $depfile + else + # The sourcefile does not contain any dependencies, so just + # store a dummy comment line, to avoid errors with the Makefile + # "include basename.Plo" scheme. + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. This file always lives in the current directory. + # Also, the AIX compiler puts `$object:' at the start of each line; + # $object doesn't have directory information. + stripped=`echo "$object" | sed -e 's,^.*/,,' -e 's/\(.*\)\..*$/\1/'` + tmpdepfile="$stripped.u" + outname="$stripped.o" + if test "$libtool" = yes; then + "$@" -Wc,-M + else + "$@" -M + fi + + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + + if test -f "$tmpdepfile"; then + # Each line is of the form `foo.o: dependent.h'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile" + sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" + else + # The sourcefile does not contain any dependencies, so just + # store a dummy comment line, to avoid errors with the Makefile + # "include basename.Plo" scheme. + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +tru64) + # The Tru64 AIX compiler uses -MD to generate dependencies as a side + # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in `foo.d' instead, so we check for that too. + # Subdirectories are respected. + + tmpdepfile1="$object.d" + tmpdepfile2=`echo "$object" | sed -e 's/.o$/.d/'` + if test "$libtool" = yes; then + "$@" -Wc,-MD + else + "$@" -MD + fi + + stat=$? + if test $stat -eq 0; then : + else + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + if test -f "$tmpdepfile1"; then + tmpdepfile="$tmpdepfile1" + else + tmpdepfile="$tmpdepfile2" + fi + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" + # That's a space and a tab in the []. + sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" + else + echo "#dummy" > "$depfile" + fi + rm -f "$tmpdepfile" + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the proprocessed file to stdout, regardless of -o, + # because we must use -o when running libtool. + test -z "$dashmflag" && dashmflag=-M + ( IFS=" " + case " $* " in + *" --mode=compile "*) # this is libtool, let us make it quiet + for arg + do # cycle over the arguments + case "$arg" in + "--mode=compile") + # insert --quiet before "--mode=compile" + set fnord "$@" --quiet + shift # fnord + ;; + esac + set fnord "$@" "$arg" + shift # fnord + shift # "$arg" + done + ;; + esac + "$@" $dashmflag | sed 's:^[^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + tr ' ' ' +' < "$tmpdepfile" | \ +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + # X makedepend + ( + shift + cleared=no + for arg in "$@"; do + case $cleared in no) + set ""; shift + cleared=yes + esac + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift;; + -*) + ;; + *) + set fnord "$@" "$arg"; shift;; + esac + done + obj_suffix="`echo $object | sed 's/^.*\././'`" + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} 2>/dev/null -o"$obj_suffix" -f"$tmpdepfile" "$@" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + tail +3 "$tmpdepfile" | tr ' ' ' +' | \ +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the proprocessed file to stdout, regardless of -o, + # because we must use -o when running libtool. + ( IFS=" " + case " $* " in + *" --mode=compile "*) + for arg + do # cycle over the arguments + case $arg in + "--mode=compile") + # insert --quiet before "--mode=compile" + set fnord "$@" --quiet + shift # fnord + ;; + esac + set fnord "$@" "$arg" + shift # fnord + shift # "$arg" + done + ;; + esac + "$@" -E | + sed -n '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' | + sed '$ s: \\$::' > "$tmpdepfile" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the proprocessed file to stdout, regardless of -o, + # because we must use -o when running libtool. + ( IFS=" " + case " $* " in + *" --mode=compile "*) + for arg + do # cycle over the arguments + case $arg in + "--mode=compile") + # insert --quiet before "--mode=compile" + set fnord "$@" --quiet + shift # fnord + ;; + esac + set fnord "$@" "$arg" + shift # fnord + shift # "$arg" + done + ;; + esac + "$@" -E | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::echo "`cygpath -u \\"\1\\"`":p' | sort | uniq > "$tmpdepfile" + ) & + proc=$! + "$@" + stat=$? + wait "$proc" + if test "$stat" != 0; then exit $stat; fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" + echo " " >> "$depfile" + . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 diff --git a/config/install-sh b/config/install-sh new file mode 100755 index 00000000..e9de2384 --- /dev/null +++ b/config/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/config/missing b/config/missing new file mode 100755 index 00000000..0a7fb5a2 --- /dev/null +++ b/config/missing @@ -0,0 +1,283 @@ +#! /bin/sh +# Common stub for a few missing GNU programs while installing. +# Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc. +# Originally by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 +fi + +run=: + +# In the cases where this matters, `missing' is being run in the +# srcdir already. +if test -f configure.ac; then + configure_ac=configure.ac +else + configure_ac=configure.in +fi + +case "$1" in +--run) + # Try to run requested program, and just exit if it succeeds. + run= + shift + "$@" && exit 0 + ;; +esac + +# If it does not exist, or fails to run (possibly an outdated version), +# try to emulate it. +case "$1" in + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an +error status if there is no known handling for PROGRAM. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + --run try to run the given command, and emulate it if it fails + +Supported PROGRAM values: + aclocal touch file \`aclocal.m4' + autoconf touch file \`configure' + autoheader touch file \`config.h.in' + automake touch all \`Makefile.in' files + bison create \`y.tab.[ch]', if possible, from existing .[ch] + flex create \`lex.yy.c', if possible, from existing .c + help2man touch the output file + lex create \`lex.yy.c', if possible, from existing .c + makeinfo touch the output file + tar try tar, gnutar, gtar, then tar without non-portable flags + yacc create \`y.tab.[ch]', if possible, from existing .[ch]" + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing 0.3 - GNU automake" + ;; + + -*) + echo 1>&2 "$0: Unknown \`$1' option" + echo 1>&2 "Try \`$0 --help' for more information" + exit 1 + ;; + + aclocal) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`acinclude.m4' or \`${configure_ac}'. You might want + to install the \`Automake' and \`Perl' packages. Grab them from + any GNU archive site." + touch aclocal.m4 + ;; + + autoconf) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`${configure_ac}'. You might want to install the + \`Autoconf' and \`GNU m4' packages. Grab them from any GNU + archive site." + touch configure + ;; + + autoheader) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`acconfig.h' or \`${configure_ac}'. You might want + to install the \`Autoconf' and \`GNU m4' packages. Grab them + from any GNU archive site." + files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` + test -z "$files" && files="config.h" + touch_files= + for f in $files; do + case "$f" in + *:*) touch_files="$touch_files "`echo "$f" | + sed -e 's/^[^:]*://' -e 's/:.*//'`;; + *) touch_files="$touch_files $f.in";; + esac + done + touch $touch_files + ;; + + automake) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. + You might want to install the \`Automake' and \`Perl' packages. + Grab them from any GNU archive site." + find . -type f -name Makefile.am -print | + sed 's/\.am$/.in/' | + while read f; do touch "$f"; done + ;; + + bison|yacc) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a \`.y' file. You may need the \`Bison' package + in order for those modifications to take effect. You can get + \`Bison' from any GNU archive site." + rm -f y.tab.c y.tab.h + if [ $# -ne 1 ]; then + eval LASTARG="\${$#}" + case "$LASTARG" in + *.y) + SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" y.tab.c + fi + SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" y.tab.h + fi + ;; + esac + fi + if [ ! -f y.tab.h ]; then + echo >y.tab.h + fi + if [ ! -f y.tab.c ]; then + echo 'main() { return 0; }' >y.tab.c + fi + ;; + + lex|flex) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a \`.l' file. You may need the \`Flex' package + in order for those modifications to take effect. You can get + \`Flex' from any GNU archive site." + rm -f lex.yy.c + if [ $# -ne 1 ]; then + eval LASTARG="\${$#}" + case "$LASTARG" in + *.l) + SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` + if [ -f "$SRCFILE" ]; then + cp "$SRCFILE" lex.yy.c + fi + ;; + esac + fi + if [ ! -f lex.yy.c ]; then + echo 'main() { return 0; }' >lex.yy.c + fi + ;; + + help2man) + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a dependency of a manual page. You may need the + \`Help2man' package in order for those modifications to take + effect. You can get \`Help2man' from any GNU archive site." + + file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` + if test -z "$file"; then + file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'` + fi + if [ -f "$file" ]; then + touch $file + else + test -z "$file" || exec >$file + echo ".ab help2man is required to generate this page" + exit 1 + fi + ;; + + makeinfo) + if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then + # We have makeinfo, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is missing on your system. You should only need it if + you modified a \`.texi' or \`.texinfo' file, or any other file + indirectly affecting the aspect of the manual. The spurious + call might also be the consequence of using a buggy \`make' (AIX, + DU, IRIX). You might want to install the \`Texinfo' package or + the \`GNU make' package. Grab either from any GNU archive site." + file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` + if test -z "$file"; then + file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` + file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` + fi + touch $file + ;; + + tar) + shift + if test -n "$run"; then + echo 1>&2 "ERROR: \`tar' requires --run" + exit 1 + fi + + # We have already tried tar in the generic part. + # Look for gnutar/gtar before invocation to avoid ugly error + # messages. + if (gnutar --version > /dev/null 2>&1); then + gnutar ${1+"$@"} && exit 0 + fi + if (gtar --version > /dev/null 2>&1); then + gtar ${1+"$@"} && exit 0 + fi + firstarg="$1" + if shift; then + case "$firstarg" in + *o*) + firstarg=`echo "$firstarg" | sed s/o//` + tar "$firstarg" ${1+"$@"} && exit 0 + ;; + esac + case "$firstarg" in + *h*) + firstarg=`echo "$firstarg" | sed s/h//` + tar "$firstarg" ${1+"$@"} && exit 0 + ;; + esac + fi + + echo 1>&2 "\ +WARNING: I can't seem to be able to run \`tar' with the given arguments. + You may want to install GNU tar or Free paxutils, or check the + command line arguments." + exit 1 + ;; + + *) + echo 1>&2 "\ +WARNING: \`$1' is needed, and you do not seem to have it handy on your + system. You might have modified some files without having the + proper tools for further handling them. Check the \`README' file, + it often tells you about the needed prerequirements for installing + this package. You may also peek at any GNU archive site, in case + some other package would contain this missing \`$1' program." + exit 1 + ;; +esac + +exit 0 diff --git a/config/mkinstalldirs b/config/mkinstalldirs new file mode 100755 index 00000000..6b3b5fc5 --- /dev/null +++ b/config/mkinstalldirs @@ -0,0 +1,40 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Public domain + +# $Id$ + +errstatus=0 + +for file +do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d + do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" + + mkdir "$pathcomp" || lasterr=$? + + if test ! -d "$pathcomp"; then + errstatus=$lasterr + fi + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..14367546 --- /dev/null +++ b/configure.in @@ -0,0 +1,289 @@ +dnl synergy -- mouse and keyboard sharing utility +dnl Copyright (C) 2002 Chris Schoeneman +dnl +dnl This package is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU General Public License +dnl found in the file COPYING that should have accompanied this file. +dnl +dnl This package is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. + +dnl Process this file with autoconf to produce a configure script. + +dnl initialize +AC_INIT(lib/common/common.h) +AC_CONFIG_AUX_DIR(config) + +dnl current version, extracted from $srcdir/lib/common/Version.h +MAJOR_VERSION=`grep '#.*define VERSION "' $srcdir/lib/common/Version.h | sed -e 's/.*"\([0-9]*\)\.[0-9]*\.[0-9]*".*/\1/'` +MINOR_VERSION=`grep '#.*define VERSION "' $srcdir/lib/common/Version.h | sed -e 's/.*"[0-9]*\.\([0-9]*\)\.[0-9]*".*/\1/'` +RELEASE_VERSION=`grep '#.*define VERSION "' $srcdir/lib/common/Version.h | sed -e 's/.*"[0-9]*\.[0-9]*\.\([0-9]*\)".*/\1/'` + +dnl initialize automake +AM_INIT_AUTOMAKE(synergy, $MAJOR_VERSION.$MINOR_VERSION.$RELEASE_VERSION) +AM_CONFIG_HEADER(config.h) + +dnl information on the package + +dnl decide on platform +ARCH_LIBS="" +ARCH_CFLAGS="" +AC_CANONICAL_HOST +case $host in +*-*-mingw32* | *-*-windows*) + acx_host_arch="WIN32" + acx_host_winapi="MSWINDOWS" + ;; +*-*-darwin*) + acx_host_arch="UNIX" + acx_host_winapi="CARBON" + ;; +*) + acx_host_arch="UNIX" + acx_host_winapi="XWINDOWS" + ;; +esac +ARCH_CFLAGS="$ARCH_CFLAGS -DSYSAPI_$acx_host_arch=1 -DWINAPI_$acx_host_winapi=1" +AM_CONDITIONAL(WIN32, test x$acx_host_arch = xWIN32) +AM_CONDITIONAL(UNIX, test x$acx_host_arch = xUNIX) +AM_CONDITIONAL(MSWINDOWS, test x$acx_host_winapi = xMSWINDOWS) +AM_CONDITIONAL(CARBON, test x$acx_host_winapi = xCARBON) +AM_CONDITIONAL(XWINDOWS, test x$acx_host_winapi = xXWINDOWS) + +dnl checks for programs +AC_PROG_CC +AC_PROG_CXX +AC_PROG_RANLIB +AC_CHECK_PROG(HAVE_DOT, dot, YES, NO) + +dnl AC_PROG_OBJC doesn't exist. Borrow some ideas from KDE. +dnl AC_MSG_CHECKING(for an Objective-C compiler) +OBJC="${CC}" +OBJCFLAGS="${CFLAGS}" +AC_SUBST(OBJC) +AC_SUBST(OBJCFLAGS) +_AM_DEPENDENCIES(OBJC) + +dnl do checks using C++ +AC_LANG_CPLUSPLUS + +dnl our files end in .cpp not .C so tests should also end in .cpp +ac_ext=cpp + +dnl enable debugging or disable asserts +AC_ARG_ENABLE([debug], [ --enable-debug enable debugging]) +if test "x$enable_debug" != xno; then + CXXFLAGS="$CXXFLAGS -g" +else + CXXFLAGS="$CXXFLAGS -DNDEBUG" +fi + +dnl check compiler +ACX_CHECK_CXX + +dnl checks for libraries +if test x"$acx_host_arch" = xUNIX; then + ACX_PTHREAD(,AC_MSG_ERROR(You must have pthreads to compile synergy)) + ARCH_LIBS="$PTHREAD_LIBS $ARCH_LIBS" + ARCH_CFLAGS="$ARCH_CFLAGS $PTHREAD_CFLAGS" +fi +if test x"$acx_host_winapi" = xCARBON; then + ARCH_LIBS="-framework Carbon $ARCH_LIBS" +fi +ACX_CHECK_NANOSLEEP +ACX_CHECK_INET_ATON + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS([unistd.h sys/time.h sys/types.h locale.h wchar.h]) +AC_CHECK_HEADERS([sys/socket.h sys/select.h]) +AC_CHECK_HEADERS([sys/utsname.h]) +AC_CHECK_HEADERS([istream ostream sstream]) +AC_HEADER_TIME +if test x"$acx_host_winapi" = xXWINDOWS; then + AC_PATH_X + AC_PATH_XTRA + save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$X_CFLAGS $CPPFLAGS" + XEXT_LDADD= + + AC_CHECK_LIB(Xtst, + XTestQueryExtension, + [XEXT_LDADD="$XEXT_LDADD -lXtst"], + AC_MSG_ERROR(You must have the XTest library to build synergy), + [$X_LIBS -lXext -lX11 $X_EXTRA_LIBS]) + AC_CHECK_HEADERS([X11/extensions/XTest.h], + , + AC_MSG_ERROR(You must have the XTest headers to compile synergy)) + + acx_have_xkb=no + AC_CHECK_LIB(X11, + XkbQueryExtension, + [acx_have_xkb=yes], + [acx_have_xkb=no], + [$X_LIBS $X_EXTRA_LIBS]) + if test x"$acx_have_xkb" = xyes; then + AC_CHECK_HEADERS([X11/XKBlib.h X11/extensions/XKBstr.h], + [acx_have_xkb=yes], + [acx_have_xkb=no], + [#include ]) + if test x"$acx_have_xkb" = xyes; then + AC_TRY_COMPILE([ + #include + #include + ],[ + XkbQueryExtension(0, 0, 0, 0, 0, 0); + ], + [acx_have_xkb=yes], + [acx_have_xkb=no]) + fi + fi + if test x"$acx_have_xkb" = xyes; then + AC_DEFINE(HAVE_XKB_EXTENSION, 1, + [Define this if the XKB extension is available.]) + fi + + acx_have_xinerama=yes + AC_CHECK_LIB(Xinerama, + XineramaQueryExtension, + [acx_have_xinerama=yes], + [acx_have_xinerama=no], + [$X_LIBS -lXext -lX11 $X_EXTRA_LIBS]) + if test x"$acx_have_xinerama" = xyes; then + AC_CHECK_HEADERS([X11/extensions/Xinerama.h], + [acx_have_xinerama=yes], + [acx_have_xinerama=no], + [#include ]) + fi + if test x"$acx_have_xinerama" = xyes; then + XEXT_LDADD="$XEXT_LDADD -lXinerama" + fi + + X_DPMS_LDADD= + acx_have_dpms=no + AC_CHECK_LIB(Xext, + DPMSQueryExtension, + [acx_have_dpms=yes], + [acx_have_dpms=no], + [$X_LIBS -lX11 $X_EXTRA_LIBS]) + if test x"$acx_have_dpms" != xyes; then + AC_CHECK_LIB(Xdpms, + DPMSQueryExtension, + [acx_have_dpms=yes; XDPMS_LDADD=-lXdpms], + [acx_have_dpms=no], + [$X_LIBS -lX11 $X_EXTRA_LIBS]) + fi + if test x"$acx_have_dpms" = xyes; then + AC_CHECK_HEADERS([X11/extensions/dpms.h], + [acx_have_dpms_h=yes], + [acx_have_dpms_h=no], + [#include ]) + if test x"$acx_have_dpms_h" = xyes; then + XEXT_LDADD="$XEXT_LDADD $XDPMS_LDADD" + AC_MSG_CHECKING(for prototypes in X11/extensions/dpms.h) + acx_have_dpms_protos=no + AC_TRY_COMPILE([ + #include + extern "C" { + #include + } + ],[ + int s = DPMSModeOn; + DPMSQueryExtension(0, 0, 0); + ], + [acx_have_dpms_protos=yes]) + AC_MSG_RESULT($acx_have_dpms_protos) + if test x"$acx_have_dpms_protos" = xyes; then + AC_DEFINE(HAVE_DPMS_PROTOTYPES,1,[Define if the header file declares function prototypes.]) + fi + fi + fi + + CPPFLAGS="$save_CPPFLAGS" + ARCH_LIBS="$X_LIBS $X_PRE_LIBS $XEXT_LDADD -lXext -lX11 $X_EXTRA_LIBS $ARCH_LIBS" + ARCH_CFLAGS="$ARCH_CFLAGS $X_CFLAGS" +fi + +dnl checks for types +AC_TYPE_SIZE_T +ACX_CHECK_SOCKLEN_T + +dnl checks for structures +AC_STRUCT_TM + +dnl checks for compiler characteristics +AC_CHECK_SIZEOF(char, 1) +AC_CHECK_SIZEOF(short, 2) +AC_CHECK_SIZEOF(int, 2) +AC_CHECK_SIZEOF(long, 4) +ACX_CHECK_CXX_BOOL(,AC_MSG_ERROR(Your compiler must support bool to compile synergy)) +ACX_CHECK_CXX_EXCEPTIONS(,AC_MSG_ERROR(Your compiler must support exceptions to compile synergy)) +ACX_CHECK_CXX_CASTS(,AC_MSG_ERROR(Your compiler must support C++ casts to compile synergy)) +ACX_CHECK_CXX_MUTABLE(,AC_MSG_ERROR(Your compiler must support mutable to compile synergy)) +ACX_CHECK_CXX_STDLIB(,AC_MSG_ERROR(Your compiler must support the C++ standard library to compile synergy)) + +dnl checks for library functions +dnl AC_TYPE_SIGNAL +AC_FUNC_MEMCMP +AC_FUNC_STRFTIME +AC_CHECK_FUNCS(gmtime_r) +ACX_CHECK_GETPWUID_R +AC_CHECK_FUNCS(vsnprintf) +AC_FUNC_SELECT_ARGTYPES +ACX_CHECK_POLL +ACX_FUNC_ACCEPT +dnl use AC_REPLACE_FUNCS() for stuff in string.h + +dnl checks for system services + +dnl enable maximum compiler warnings and warnings are errors. +ACX_CXX_WARNINGS +ACX_CXX_WARNINGS_ARE_ERRORS + +dnl adjust compiler and linker variables +CXXFLAGS="$CXXFLAGS $SYNERGY_CXXFLAGS $ARCH_CFLAGS" +OBJCXXFLAGS="$OBJCXXFLAGS $CXXFLAGS $ARCH_CFLAGS" +LIBS="$NANOSLEEP_LIBS $INET_ATON_LIBS $ARCH_LIBS $LIBS" + +dnl we need to have an environment variable set when building on OS X. +dnl i'm not sure of the right way to do that. writing 'export ...' to +dnl the makefiles isn't portable. here we'll hijack XXXDEPMODE (where +dnl XXX depends on the language) to insert setting the environment +dnl variable when running the compiler. we'd like to put that in CC, +dnl CXX and OBJC but that breaks depcomp. let's hope this works. +if test x"$acx_host_winapi" = xCARBON; then + MACOSX_DEPLOYMENT_TARGET="10.2" + CCDEPMODE="MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET $CCDEPMODE" + CXXDEPMODE="MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET $CXXDEPMODE" + OBJCDEPMODE="MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET $OBJCDEPMODE" +else + MACOSX_DEPLOYMENT_TARGET="5" + CXXDEPMODE="FOO=$MACOSX_DEPLOYMENT_TARGET $CXXDEPMODE" +fi + +AC_OUTPUT([ +Makefile +cmd/Makefile +cmd/launcher/Makefile +cmd/synergyc/Makefile +cmd/synergys/Makefile +dist/Makefile +dist/nullsoft/Makefile +dist/rpm/Makefile +dist/rpm/synergy.spec +doc/Makefile +doc/doxygen.cfg +lib/Makefile +lib/arch/Makefile +lib/base/Makefile +lib/client/Makefile +lib/common/Makefile +lib/io/Makefile +lib/mt/Makefile +lib/net/Makefile +lib/platform/Makefile +lib/server/Makefile +lib/synergy/Makefile +]) diff --git a/dist/Makefile.am b/dist/Makefile.am new file mode 100644 index 00000000..1af99c18 --- /dev/null +++ b/dist/Makefile.am @@ -0,0 +1,26 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + rpm \ + nullsoft \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/dist/nullsoft/Makefile.am b/dist/nullsoft/Makefile.am new file mode 100644 index 00000000..120cd016 --- /dev/null +++ b/dist/nullsoft/Makefile.am @@ -0,0 +1,24 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + synergy.nsi \ + dosify.c \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/dist/nullsoft/Makefile.win b/dist/nullsoft/Makefile.win new file mode 100644 index 00000000..91aa68bb --- /dev/null +++ b/dist/nullsoft/Makefile.win @@ -0,0 +1,63 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +NSIS = "$(PROGRAMFILES)\NSIS\makensis.exe" +NSIS_FLAGS = /NOCD /V1 + +BIN_INSTALLER_SRC = dist\nullsoft +BIN_INSTALLER_DST = $(BUILD_DST)\$(BIN_INSTALLER_SRC) +BIN_DOSIFY_EXE = "$(BIN_INSTALLER_DST)\dosify.exe" +BIN_DOSIFY_C = \ + "$(BIN_INSTALLER_SRC)\dosify.c" \ + $(NULL) +BIN_DOSIFY_OBJ = \ + "$(BIN_INSTALLER_DST)\dosify.obj" \ + $(NULL) + +BIN_INSTALLER_NSI = "$(BIN_INSTALLER_SRC)\synergy.nsi" +BIN_INSTALLER_EXE = "$(BUILD_DST)\SynergyInstaller.exe" +BIN_INSTALLER_DOCS = \ + COPYING \ + ChangeLog \ + $(NULL) +BIN_INSTALLER_DOS_DOCS = \ + $(BUILD_DST)\COPYING.txt \ + $(BUILD_DST)\ChangeLog.txt \ + $(NULL) + +C_FILES = $(C_FILES) $(BIN_DOSIFY_C) +OBJ_FILES = $(OBJ_FILES) $(BIN_DOSIFY_OBJ) +OPTPROGRAMS = $(OPTPROGRAMS) $(BIN_DOSIFY_EXE) + +# Build rules. +$(BIN_DOSIFY_OBJ): $(BIN_DOSIFY_C) + @$(ECHO) Compile $(BIN_DOSIFY_C) + -@$(MKDIR) $(BIN_INSTALLER_DST) 2>NUL: + $(cc) $(cdebug) $(cflags) $(cvars) /Fo$@ /Fd$(@:.obj=.pdb) $** +$(BIN_DOSIFY_EXE): $(BIN_DOSIFY_OBJ) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(conlflags) $(conlibsmt) /out:$@ $** + +# Convert text files from Unix to DOS format. +$(BIN_INSTALLER_DOS_DOCS): $(BIN_DOSIFY_EXE) $(BIN_INSTALLER_DOCS) + @$(ECHO) Convert text files to DOS format + $(BIN_DOSIFY_EXE) "." "$(BUILD_DST)" $(BIN_INSTALLER_DOCS) + +# Allow installers for both debug and release. +$(BIN_INSTALLER_EXE): $(BIN_INSTALLER_NSI) all $(BIN_INSTALLER_DOS_DOCS) + @$(ECHO) Build $(@F) + $(NSIS) $(NSIS_FLAGS) /DOUTPUTDIR=$(@D) /DOUTPUTFILE=$@ $(BIN_INSTALLER_NSI) +installer: $(BIN_INSTALLER_EXE) +debug-installer: + @$(MAKE) /nologo /f $(MAKEFILE) DEBUG=1 installer +release-installer: + @$(MAKE) /nologo /f $(MAKEFILE) NODEBUG=1 installer diff --git a/dist/nullsoft/dosify.c b/dist/nullsoft/dosify.c new file mode 100644 index 00000000..95d0caee --- /dev/null +++ b/dist/nullsoft/dosify.c @@ -0,0 +1,99 @@ +#include +#include +#include + +static +char* +concatPath(const char* dir, const char* name, const char* ext) +{ + size_t nDir = (dir != NULL) ? strlen(dir) : 0; + size_t nPath = nDir + 1 + strlen(name) + strlen(ext?ext:"") + 1; + char* path = malloc(nPath); + + /* directory */ + if (nDir > 0 && strcmp(dir, ".") != 0) { + strcpy(path, dir); + if (path[nDir - 1] != '\\' && path[nDir - 1] != '/') { + strcat(path, "\\"); + } + } + else { + strcpy(path, ""); + } + + + /* name */ + strcat(path, name); + + /* extension */ + if (ext != NULL && strrchr(name, '.') == NULL) { + strcat(path, ext); + } + + return path; +} + +static +int +dosify(const char* srcdir, const char* dstdir, const char* name) +{ + FILE* dFile, *sFile; + char* dName, *sName; + + sName = concatPath(srcdir, name, NULL); + dName = concatPath(dstdir, name, ".txt"); + + sFile = fopen(sName, "rb"); + if (sFile == NULL) { + fprintf(stderr, "Can't open \"%s\" for reading\n", sName); + return 0; + } + else { + dFile = fopen(dName, "w"); + if (dFile == NULL) { + fclose(sFile); + fprintf(stderr, "Can't open \"%s\" for writing\n", dName); + return 0; + } + else { + char buffer[1024]; + while (!ferror(dFile) && + fgets(buffer, sizeof(buffer), sFile) != NULL) { + fprintf(dFile, "%s", buffer); + } + if (ferror(sFile) || ferror(dFile)) { + fprintf(stderr, + "Error copying \"%s\" to \"%s\"\n", sName, dName); + fclose(dFile); + fclose(sFile); + _unlink(dName); + return 0; + } + } + } + + fclose(dFile); + fclose(sFile); + free(dName); + free(sName); + return 1; +} + +#include +int +main(int argc, char** argv) +{ + int i; + + if (argc < 3) { + fprintf(stderr, "usage: %s [files]\n", argv[0]); + return 1; + } + + for (i = 3; i < argc; ++i) { + if (!dosify(argv[1], argv[2], argv[i])) + return 1; + } + + return 0; +} diff --git a/dist/nullsoft/synergy.nsi b/dist/nullsoft/synergy.nsi new file mode 100644 index 00000000..3370d03a --- /dev/null +++ b/dist/nullsoft/synergy.nsi @@ -0,0 +1,179 @@ +; Synergy.nsi +; +; This script is based on example1.nsi, but it remember the directory, +; has uninstall support and (optionally) installs start menu shortcuts. +; +; It will install makensisw.exe into a directory that the user selects, + +;-------------------------------- + +!ifndef OUTPUTDIR +!define OUTPUTDIR "build\Release" +!endif + +; The name of the installer +Name "Synergy" + +; The file to write +OutFile "${OUTPUTFILE}" + +; The default installation directory +InstallDir $PROGRAMFILES\Synergy + +; Registry key to check for directory (so if you install again, it will +; overwrite the old one automatically) +InstallDirRegKey HKLM "Software\Synergy" "Install_Dir" + +;-------------------------------- + +; Pages + +Page components +Page license +Page directory +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +;-------------------------------- + +; Text +ComponentText "This will install Synergy on your computer. Select the optional components you want to install." +DirText "Choose a directory to install Synergy to:" +UninstallText "This will uninstall Synergy from your computer." +LicenseText "Synergy is distributed under the GNU GPL:" +LicenseData ${OUTPUTDIR}\COPYING.txt + +;-------------------------------- + +; The stuff to install +Section "Synergy (required)" + + SectionIn RO + + ; Set output path to the installation directory. + SetOutPath $INSTDIR + + ; Put files there + File "${OUTPUTDIR}\synergy.exe" + File "${OUTPUTDIR}\synergyc.exe" + File "${OUTPUTDIR}\synergys.exe" + File "${OUTPUTDIR}\*.dll" + File "${OUTPUTDIR}\COPYING.txt" + File "${OUTPUTDIR}\ChangeLog.txt" + File doc\PORTING + File doc\about.html + File doc\authors.html + File doc\autostart.html + File doc\banner.html + File doc\compiling.html + File doc\configuration.html + File doc\contact.html + File doc\developer.html + File doc\faq.html + File doc\history.html + File doc\home.html + File doc\index.html + File doc\license.html + File doc\news.html + File doc\roadmap.html + File doc\running.html + File doc\security.html + File doc\synergy.css + File doc\tips.html + File doc\toc.html + File doc\trouble.html + + SetOutPath $INSTDIR\images + File doc\images\logo.gif + File doc\images\warp.gif + + ; Write the installation path into the registry + WriteRegStr HKLM SOFTWARE\Synergy "Install_Dir" "$INSTDIR" + + ; Write the uninstall keys for Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "DisplayName" "Synergy" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" "NoRepair" 1 + WriteUninstaller "uninstall.exe" + +SectionEnd + +; Optional section (can be disabled by the user) +Section "Start Menu Shortcuts" + + CreateDirectory "$SMPROGRAMS\Synergy" + CreateShortCut "$SMPROGRAMS\Synergy\Synergy.lnk" "$INSTDIR\synergy.exe" "" "$INSTDIR\synergy.exe" 0 + CreateShortCut "$SMPROGRAMS\Synergy\README.lnk" "$INSTDIR\index.html" + CreateShortCut "$SMPROGRAMS\Synergy\Synergy Folder.lnk" "$INSTDIR" + CreateShortCut "$SMPROGRAMS\Synergy\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 + +SectionEnd + +; Optional section (can be disabled by the user) +Section "Desktop Icon" + + CreateShortCut "$DESKTOP\Synergy.lnk" "$INSTDIR\synergy.exe" "" "$INSTDIR\synergy.exe" 0 + +SectionEnd + +;-------------------------------- + +; Uninstaller + +Section "Uninstall" + ; Stop and uninstall the daemons + ExecWait '"$INSTDIR\synergy.exe" /uninstall' + + ; Remove autorun registry keys for synergy + DeleteRegKey HKLM "SYSTEM\CurrentControlSet\Services\Synergy Server" + DeleteRegKey HKLM "SYSTEM\CurrentControlSet\Services\Synergy Client" + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\RunServices" "Synergy Server" + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\RunServices" "Synergy Client" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Synergy Server" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Synergy Client" + + ; not all keys will have existed, so errors WILL have happened + ClearErrors + + ; Remove registry keys + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Synergy" + DeleteRegKey HKLM SOFTWARE\Synergy + + ClearErrors + + ; First try to remove files that might be locked (if synergy is running) + Delete /REBOOTOK $INSTDIR\synergy.exe + Delete /REBOOTOK $INSTDIR\synergyc.exe + Delete /REBOOTOK $INSTDIR\synergys.exe + Delete /REBOOTOK $INSTDIR\synrgyhk.dll + + ; Remove files and directory + Delete $INSTDIR\*.* + RMDir $INSTDIR + + ; Remove shortcuts, if any + Delete "$SMPROGRAMS\Synergy\*.*" + Delete "$DESKTOP\Synergy.lnk" + + ; Remove directories used + RMDir "$SMPROGRAMS\Synergy" + RMDir "$INSTDIR" + + IfRebootFlag 0 EndOfAll + MessageBox MB_OKCANCEL "Uninstaller needs to reboot to finish cleaning up. reboot now?" IDCANCEL NoReboot + ClearErrors + Reboot + IfErrors 0 EndOfAll + MessageBox MB_OK "Uninstaller could not reboot. Please reboot manually. Thank you." + Abort "Uninstaller could not reboot. Please reboot manually. Thank you." + NoReboot: + DetailPrint "" + DetailPrint "Uninstaller could not reboot. Please reboot manually. Thank you." + DetailPrint "" + SetDetailsView show + EndOfAll: + +SectionEnd diff --git a/dist/rpm/Makefile.am b/dist/rpm/Makefile.am new file mode 100644 index 00000000..0e86d9ba --- /dev/null +++ b/dist/rpm/Makefile.am @@ -0,0 +1,22 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + synergy.spec.in \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/dist/rpm/synergy.spec.in b/dist/rpm/synergy.spec.in new file mode 100644 index 00000000..0d2b6f48 --- /dev/null +++ b/dist/rpm/synergy.spec.in @@ -0,0 +1,66 @@ +Summary: Mouse and keyboard sharing utility +Name: @PACKAGE@ +Version: @VERSION@ +Release: 1 +License: GPL +Packager: Chris Schoeneman +Group: System Environment/Daemons +Prefixes: /usr/bin +Source: @PACKAGE@-@VERSION@.tar.gz +Buildroot: /var/tmp/@PACKAGE@-@VERSION@-root + +%description +Synergy lets you easily share a single mouse and keyboard between +multiple computers with different operating systems, each with its +own display, without special hardware. It's intended for users +with multiple computers on their desk since each system uses its +own display. + +%prep +%setup +CFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=/usr + +%build +make + +%install +make install DESTDIR=$RPM_BUILD_ROOT +strip $RPM_BUILD_ROOT/usr/bin/synergyc +strip $RPM_BUILD_ROOT/usr/bin/synergys + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-, root, root) +/usr/bin/synergyc +/usr/bin/synergys +%doc AUTHORS +%doc COPYING +%doc ChangeLog +%doc INSTALL +%doc NEWS +%doc README +%doc doc/about.html +%doc doc/authors.html +%doc doc/autostart.html +%doc doc/banner.html +%doc doc/border.html +%doc doc/compiling.html +%doc doc/configuration.html +%doc doc/contact.html +%doc doc/developer.html +%doc doc/faq.html +%doc doc/history.html +%doc doc/home.html +%doc doc/index.html +%doc doc/license.html +%doc doc/news.html +%doc doc/roadmap.html +%doc doc/running.html +%doc doc/security.html +%doc doc/tips.html +%doc doc/toc.html +%doc doc/trouble.html +%doc doc/synergy.css +%doc examples/synergy.conf diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 00000000..2efec24c --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,49 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + PORTING \ + doxygen.cfg.in \ + synergy.css \ + about.html \ + authors.html \ + autostart.html \ + banner.html \ + border.html \ + compiling.html \ + configuration.html \ + contact.html \ + developer.html \ + faq.html \ + history.html \ + home.html \ + index.html \ + license.html \ + news.html \ + roadmap.html \ + running.html \ + security.html \ + tips.html \ + toc.html \ + trouble.html \ + images/logo.gif \ + images/warp.gif \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + doc/doxygen.cfg \ + doc/doxygen/html/* \ + $(NULL) diff --git a/doc/PORTING b/doc/PORTING new file mode 100644 index 00000000..4e2744df --- /dev/null +++ b/doc/PORTING @@ -0,0 +1,419 @@ +Synergy Developer and Porting Guide +=================================== + +This document is under development. + +Code Organization +----------------- + +The synergy source code organization is: + +. -- root makefiles, some standard documentation +cmd -- program source code + launcher -- synergy launcher for Windows + synergyc -- synergy client + synergys -- synergy server +config -- stuff for autoconf/automake +dist -- files for creating distributions + nullsoft -- files for creating Nullsoft NSIS installer (Windows) + rpm -- files for creating RPMs +doc -- placeholder for documentation +examples -- example files +lib -- library source code + arch -- platform dependent utility library + base -- simple utilities + client -- synergy client library + common -- commonly needed header files + io -- I/O + mt -- multithreading + net -- networking + platform -- platform dependent display/window/event stuff + server -- synergy server library + synergy -- synergy shared client/server code library + +Note how the utility code required by the programs is placed into +separate library directories. This makes the makefiles a little +more awkward but makes for a cleaner organization. The top level +directory has only the standard documentation files and the files +necessary to configure and build the rest of the project. + + +Coding Style Guide +------------------ + +Synergy uses many coding conventions. Contributed code should +following these guidelines. + +- Symbol Naming + Names always begin with a letter (never an underscore). The first + letter of interior names are always capitalized. Acronyms should + be all uppercase. For example: myTextAsASCII. + + Names come it two flavors: leading capital and leading lowercase. + The former have the first character capitalized and the latter + don't. In the following table, leading capital names are indicated + by `Name' and leading lowercase names by `name'. + + The naming convention for various things are: + + * Exceptions -- X + Name XMyException + * Interfaces -- I + Name IMyInterface + * Template Classes -- T + Name TMyTemplate<> + * Other Classes -- C + Name CMyClass + * Enumerations -- E + Name EMyEnumeration + * Constants -- k + Name kMyConstant + * Data Members -- m_ + name m_myDataMember + * Methods -- name myMethod + * Functions -- name myFunction + * Variables -- name myVariable + + Exceptions are types that get thrown and are generally derived + (possibly indirectly) from XBase. Interfaces are derived (possibly + indirectly) from IInterface and have only pure virtual functions. + Other classes are classes that aren't exceptions or interfaces. + Constants include global constants and enumerants. + + Method names should usually have the form `verbObject'. For example: + * isGameOn() + * getBeer() + * pressPowerButton() + * setChannel() + In general, use `get' and `set' to read and write state but use `is' + to read boolean state. Note that classes that contain only `is', + `get', and `set' are probably plain old data; you might want to + consider using public data members only or, better, refactor your + design to have classes that actually do something more than just + hold data. + +- File Naming + Each class should have one source and one header file. If the + class is named `CMyClass' then the source file should be named + `CMyClass.cpp' and the header file `CMyClass.h'. + + Headers files not containing a class should have some meaningful + name with a leading capital (e.g. `Version.h'). + + Source files without a header file have a leading lowercase name. + Only files containing the entry point for an application should + lack a header file. + +- Dependencies + * No circular library dependencies + Library dependencies form an acyclic graph. Conceptually + libraries can be arranged in layers where each library only + references libraries in layers below it, not in the same layer + or layers above it. The makefiles build the lowest layer + libraries first and work upwards. + + * Avoid circular uses-a relationships + When possible, design classes with one-way uses-a relationships + and avoid cycles. This makes it easier to understand the code. + However, sometimes it's not always practical so it is permitted. + + * Included files in headers + Headers should #include only the necessary headers. In + particular, if a class is referenced in a header file only as a + pointer or a reference then use `class COtherClass;' instead of + `#include "COtherClass.h".' + + * #include syntax + Non-synergy header files must be included using angle brackets + while synergy header files must be included using double quotes. + #include "CSynergyHeader.h" + #include + The file name in a #include must not be a relative path unless + it's a system header file and it's customary to use a relative + path, e.g. `#include '. Use compiler options to + add necessary directories to the include search path. + + * Included file ordering + Files should be included in the following order: + * Header for source file + The first include for CMyClass.cpp must be CMyClass.h. + * Other headers in directory, sorted alphabetically + * Headers for each library, sorted alphabetically per library + Include headers from the library closest in the dependency graph + first, then the next farthest, etc. Sort alphabetically within + each library. + * System headers + +- C++ + * C++ features + Synergy uses the following more recent C++ features: + * bool + * templates + * exceptions + * mutable + * new scoping rules + * the standard C++ library + + Do not use the following C++ features: + * dynamic_cast + * run time type information + * namespaces and using (use std:: where necessary) + + The new scoping rules say that the scope of a variable declared + in a for statement is limited to the for loop. For example: + + for (int i = 0; i < 10; ++i) { + // i is in scope here + } + // i is not in scope here + + for (int i = -10; i < 0; ++i) { + // an entirely new i is in scope here + } + // i is not in scope here + + This is used routinely in synergy, but only in for loops. There + is a macro for `for' in lib/base/common.h when building under + Microsoft Visual C++ that works around the fact that that compiler + doesn't follow the new scoping rules. Use the macro if your + compiler uses the old scoping rules. + + * Standard C++ library + The standard C++ library containers should always be used in favor + of custom containers wherever reasonable. std::string is used + throughout synergy but only as the CString typedef; always use + CString, never std::string except in the arch library. Synergy + avoids using auto_ptr due to some portability problems. Synergy + makes limited use of standard algorithms and streams but they can + be freely used in new code. + + * Limited multiple inheritance + Classes should inherit implementation from at most one superclass. + Inheriting implementation from multiple classes can have unpleasant + consequences in C++ due to it's limited capabilities. Classes can + inherit from any number of interface classes. An interface class + provides only pure virtual methods. Synergy breaks this rule in + IInterface which implements the virtual destructor for convenience. + + * No globals + Avoid global variables. All global variables must be static, making + it visible only with its source file. Most uses of global variables + are better served by static data members of a class. Global + constants are permitted in some circumstances. + + Also avoid global functions. Use public static member functions in + a class instead. + + These rules are violated by the main source file for each program + (except that the globals are still static). They could easily be + rewritten to put all the variables and functions into a class but + there's little to be gained by that. + + * Private data only + If a class is plain-old-data (i.e. it has no methods) all of its + data members should be public. Otherwise all of its data members + should be private, not public or protected. This makes it much + easier to track the use of a member when reading code. Protected + data is not allowed because `protected' is a synonym for `public + to my subclasses' and public data is a Bad Thing. While it might + seem okay in this limited situation, the situation is not at all + limited since an arbitrary number of classes can be derived, + directly or indirectly, from the class and any of those classes + have full access to the protected data. + + * Plain old data + A class that merely contains data and doesn't perform operations + on that data (other than reads and writes) is plain old data (POD). + POD should have only public data members and non-copy constructors. + It must not have any methods other than constructors, not even a + destructor or assignment operators, nor protected or private data. + Note that this definition of POD is not the definition used in the + C++ standard, which limits the contained data types to types that + have no constructors, destructors, or methods. + + * Avoid using friend + Avoid declaring friend functions or classes. They're sometimes + necessary for operator overloading. If you find it necessary to + add friends to some class C, consider creating a utility class U. + A utility class is declared as the only friend of C and provides + only static methods. Each method forwards to a private method on + an object of C type (passed as a parameter to the U's method). + This makes maintenance easier since only U has friend access to C + and finding any call to U is trivial (they're prefixed by U::). + + * Don't test for NULL when using `delete' or `delete[]' + It's unnecessary since delete does it anyway. + +- Makefiles + Automake's makefiles (named Makefile.am) have a few requirements: + * Define the following macro at the top of the file: + NULL = + * Lists should have one item per line and end in $(NULL). For + example: + EXTRA_DIST = \ + kiwi.txt \ + mango.cpp \ + papaya.h \ + $(NULL) + Indentation must use tabs in a makefile. Line continuations + (backslashes) should be aligned using tabs. + * Lists of files should be sorted alphabetically in groups (e..g + source files, header files, then other files). Lists of + subdirectories must be in the desired build order. + +- Source Formatting + Every project has its own formatting style and no style satisfies + everyone. New code should be consistent with existing code: + + * All files should include the copyright and license notice + * Use tabs to indent + * Tabs are 4 columns + * Lines should not extend past the 80th column + * Open braces ({) go on same line as introducing statement + `for (i = 0; i < 10; ++i) {' not + for (i = 0; i < 10; ++i) + { + * Close braces line up with introducing statement + * Open brace for function is on a line by itself in first column + * Close brace for function lines up with open brace + * Always use braces on: if, else, for, while, do, switch + * `else {' goes on its own line + * Always explicitly test pointers against NULL + e.g. `if (ptr == NULL)' not `if (ptr)' + * Always explicitly test integral values against 0 + e.g. `if (i == 0)' not `if (i)' + * Put spaces around binary operators and after statements + e.g. `if (a == b) {' not `if(a==b){' + * C'tor initializers are one per line, indented one tab stop + * Other indentation should follow existing practice + * Use Qt style comments for extraction by doxygen (i.e. //! and /*!) + * Mark incomplete or buggy code with `FIXME' + +- Other + * calls to LOG() should always be all on one line (even past column 80) + + +Class Relationships +------------------- + +The doxygen documentation can help in understanding the relationships +between objects. Use `make doxygen' in the top level directory to +create the doxygen documentation into doc/doxygen/html. You must have +doxygen installed, of course. + +FIXME -- high level overview of class relationships + + +Portability +----------- + +Synergy is mostly platform independent code but necessarily has +platform dependent parts. The mundane platform dependent parts +come from the usual suspects: networking, multithreading, file +system, high resolution clocks, system logging, etc. Porting +these parts is relatively straightforward. + +Synergy also has more esoteric platform dependent code. The +functions for low-level event interception and insertion, +warping the cursor position, character to keyboard event +translation, clipboard manipulation, and screen saver control +are often obscure and poorly documented. Unfortunately, these +are exactly the functions synergy requires to do its magic. + +Porting synergy to a new platform requires the following steps: + +- Setting up the build +- Adjusting lib/common/common.h +- Implementing lib/arch +- Implementing lib/platform +- Tweaks + +Setting up the build: + +The first phase is simply to create the files necessary to build the +other files. On Unix, synergy uses autoconf/automake which produces +a `configure' script that generates makefiles. On Windows, synergy +uses Visual C++ workspace and project files. If you're porting to +another Unix variant, you may need to adjust `configure.in', +`acinclude.m4', and Unix flavor dependent code in lib/arch. Note +especially the SYSAPI_* and WINAPI_* macro definitions in +ARCH_CFLAGS. Exactly one of each must be defined. It should also +add AM_CONDITIONALs if a new SYSAPI_* or WINAPI_* was added. + +Adjusting lib/common/common.h: + +The lib/common/common.h header file is included directly or indirectly +by every other file. Its primary job is to include config.h, which +defines macros depending on what the 'configure' script discovered +about the system. If the platform does not use the 'configure' script +it must define the appropriate SYSAPI_* and WINAPI_* macro. It may +also do other platform specific setup. + +Adjusting lib/common/BasicTypes.h: + +No changes should be necessary in BasicTypes.h. However, if the +platform's system header files define SInt8, et al. you may need +to adjust the typedefs to match the system's definitions. + +Implementing lib/arch: + +Much platform dependent code lives in lib/arch. There are several +interface classes there and they must all be implemented for each +platform. See the interface header files for more information. + +Platforms requiring special functions should create a class named +CArchMiscXXX where XXX is the platform name. The class should have +only static methods. Clients can include the appropriate header +file and make calls directly, surrounded by a suitable #ifdef/#endif. + +If using automake, the Makefile.am should list the system specific +files in a XXX_SOURCE_FILES macro where XXX matches the appropriate +AM_CONDITIONAL symbol. XXX_SOURCE_FILES must be added to EXTRA_DIST +and the following added above the INCLUDES macro: + + if XXX + libarch_a_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(XXX_SOURCE_FILES) \ + $(NULL) + endif + +Implementing lib/platform: + +Most of the remaining platform dependent code lives in lib/platform. +The code there implements platform dependent window, clipboard, keyboard +and screen saver handling. If a platform is named XXX then the following +classes should be derived and implemented: + + * CXXXClipboard : IClipboard + Provides clipboard operations. Typically, this class will + have helper classes for converting between various clipboard + data formats. + + * CXXXEventQueueBuffer : IEventQueueBuffer + Provides operations for waiting for, posting and retrieving events. + Also provides operations for creating and deleting timers. + + * CXXXKeyState : CKeyState + Provides operations for synthesizing key events and for mapping a + key ID to a sequence of events to generate that key. + + * CXXXScreen : IScreen, IPrimaryScreen, ISecondaryScreen, IPlatformScreen + Provides screen operations. + + * CXXXScreenSaver : IScreenSaver + Provides screen saver operations. + +If using automake, the Makefile.am should list the window system +specific files in a XXX_SOURCE_FILES macro where XXX matches the +appropriate AM_CONDITIONAL symbol. XXX_SOURCE_FILES must be added +to EXTRA_DIST and the following added above the INCLUDES macro: + + if XXX + libplatform_a_SOURCES = $(XXX_SOURCE_FILES) + endif + +Tweaks: + +Finally, each platform typically requires various adjustments here +and there. In particular, synergyc.cpp and synergys.cpp usually +require platform dependent code for the main entry point, parsing +arguments, and reporting errors. Also, some platforms may benefit +from a graphical user interface front end. These are generally +not portable and synergy doesn't provide any infrastructure for +the code common to any platform, though it may do so someday. +There is, however, an implementation of a GUI front end for Windows +that serves as an example. diff --git a/doc/about.html b/doc/about.html new file mode 100644 index 00000000..aadd5764 --- /dev/null +++ b/doc/about.html @@ -0,0 +1,55 @@ + + + + + + + + About Synergy + + +

+With synergy, all the computers on your desktop form a single virtual +screen. You use the mouse and keyboard of only one of the computers +while you use all of the monitors on all of the computers. +You tell synergy how many screens you have and their positions relative +to one another. Synergy then detects when the mouse moves off +the edge of a screen and jumps it instantly to the neighboring screen. +The keyboard works normally on each screen; input goes to whichever +screen has the cursor. +

+In this example, the user is moving the mouse from left to right. +When the cursor reaches the right edge of the left screen it jumps +instantly to the left edge of the right screen. +

+

+

+You can arrange screens side-by-side, above and below one another, +or any combination. You can even have a screen jump to the opposite +edge of itself. Synergy also understands multiple screens attached +to the same computer. +

+Running a game and don't want synergy to jump screens? No problem. +Just toggle Scroll Lock. Synergy keeps the cursor on the same screen +when Scroll Lock is on. (This can be configured to another hot key.) +

+Do you wish you could cut and paste between computers? Now you can! +Just copy text, HTML, or an image as you normally would on one screen +then switch to another screen and paste it. It's as if all your +computers shared a single clipboard (and separate primary selection for +you X11 users). It even converts newlines to each computer's native +form so cut and paste between different operating systems works +seamlessly. And it does it all in Unicode so any text can be copied. +

+

+Do you use a screen saver? With synergy all your screen savers act in +concert. When one starts they all start. When one stops they all +stop. And, if you require a password to unlock the screen, you'll +only have to enter a password on one screen. +

+If you regularly use multiple computers on one desk, give synergy a +try. You'll wonder how you ever lived without it. +

+ + + diff --git a/doc/authors.html b/doc/authors.html new file mode 100644 index 00000000..3fe7b4f1 --- /dev/null +++ b/doc/authors.html @@ -0,0 +1,72 @@ + + + + + + + + Synergy Authors + + +

+

Synergy Authors

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Chris Schoeneman crs23@users.sourceforge.no_spam.net Creator, owner, primary developer
Ryan Breen ryan@ryanbreen.no_spam.com Initial Mac OS X port
Guido Poschta moolder@gmx.no_spam.net Windows installer
Bertrand Landry Hetu bertrand@landryhetu.no_spam.com Mac OS X port
Tom Chadwick vttom@users.sourceforge.no_spam.net PageUp/PageDown on X servers without mouse wheel support
Brent Priddy toopriddy@users.sourceforge.no_spam.net Re-resolving server hostname on each connection
Marc-Antoine Ruel maruel@users.sourceforge.no_spam.net Visual Studio 2005 port
+

+To avoid spam bots, the above email addresses have ".no_spam" +hidden near the end. If you copy and paste the text be sure to +remove it. +

+ + + diff --git a/doc/autostart.html b/doc/autostart.html new file mode 100644 index 00000000..0343048a --- /dev/null +++ b/doc/autostart.html @@ -0,0 +1,428 @@ + + + + + + + + Synergy Autostart Guide + + +

+

Starting synergy automatically

+

+You can configure synergy to start automatically when the computer +starts or when you log in. The steps to do that are different on +each platform. Note that changing these configurations doesn't +actually start or stop synergy. The changes take effect the next +time you start your computer or log in. +

+

Windows

+

+Start synergy and click the Configure... button +by the text Automatic Startup. The +Auto Start dialog will pop up. +If an error occurs then correct the problem and click +Configure again. +

+On the Auto Start dialog you'll configure +synergy to start or not start automatically when the computer starts +or when you log in. You need Administrator access rights to start +synergy automatically when the computer starts. The dialog will let +you know if you have sufficient permission. +

+If synergy is already configured to automatically start then there +will be two Uninstall buttons, at most one +of which is enabled. Click the enabled button, if any, to tell +synergy to not start automatically. +

+If synergy is not configured to start automatically then there will +be two Install buttons. If you have +sufficient permission to have synergy start automatically when the +computer does then the Install button in the +When Computer Starts box will be enabled. +Click it to have synergy start for all users when the computer starts. +In this case, synergy will be available during the login screen. +Otherwise, click the Install button in the +When You Log In box to have synergy +automatically start when you log in. +

+

Unix

+

+Synergy requires an X server. That means a server must be +running and synergy must be authorized to connect to that server. +It's best to have the display manager start synergy. You'll need +the necessary (probably root) permission to modify the display +manager configuration files. If you don't have that permission +you can start synergy after logging in via the +.xsession file. +

+Typically, you need to edit three script files. The first file +will start synergy before a user logs in, the second will kill +that copy of synergy, and the third will start it again after +the user logs in. +

+The contents of the scripts varies greatly between systems so +there's no one definite place where you should insert your edits. +However, these scripts often exit before reaching the bottom so +put the edits near the top of the script. +

+The location and names of these files depend on the operating +system and display manager you're using. A good guess for the +location is /etc/X11. If you use kdm +then try looking in /etc/kde3 or +/usr/kde/version/share/config. +Typical file names are: +

+ + + + + + +
      xdm    kdm    gdm
1 xdm/Xsetup kdm/Xsetup gdm/Init/Default (*)
2 xdm/Xstartup kdm/Xstartup gdm/PostLogin/Default (*)
3 xdm/Xsession kdm/Xsession gdm/Sessions/Default (*, **)
+
+

+*) The Default file is used if no other +suitable file is found. gdm will try +displayname (e.g. :0) +and hostname (e.g. somehost), +in that order, before and instead of Default. +
+**) gdm may use gdm/Xsession, +xdm/Xsession or +dm/Xsession if +gdm/Sessions/Default doesn't exist. +

+For a synergy client, add the following to the first file: + + /usr/bin/killall synergyc + sleep 1 + /usr/bin/synergyc [<options>] synergy-server-hostname + +Of course, the path to synergyc depends on where you installed it +so adjust as necessary. +

+Add to the second file: + + /usr/bin/killall synergyc + sleep 1 + +

+And to the third file: + + /usr/bin/killall synergyc + sleep 1 + /usr/bin/synergyc [<options>] synergy-server-hostname + +Note that <options> +must not include +-f or --no-daemon or +the script will never exit and you won't be able to log in. +

+The changes are the same for the synergy server except replace +synergyc with synergys +and use the appropriate synergys command +line options. Note that the +first script is run as root so synergys will look for the configuration +file in root's home directory then in /etc. +Make sure it exists in one of those places or use the +--config config-pathname +option to specify its location. +

+Note that some display managers (xdm and kdm, but not gdm) grab +the keyboard and do not release it until the user logs in for +security reasons. This prevents a synergy server from sharing +the mouse and keyboard until the user logs in. It doesn't +prevent a synergy client from synthesizing mouse and keyboard +input, though. +

+If you're configuring synergy to start only after you log in then edit +your .xsession file. Add just what you +would add to the third file above. +

+

Mac OS X

+

+[By Tor Slettnes] +

+There are three different ways to automatically start Synergy +(client or server) on Mac OS X: +

+

    +
  1. + The first method involves creating a StartupItem + at the system level, which is executed when the machine starts up + or shuts down. This script will run in the background, and + relaunch synergy as needed. +

    +

    +
    Pros:
    +
    + Synergy is persistent, so this allows for a multi-user + setup and interactive logins. +
    +
    Cons:
    +
    + The synergy process does not have access to the clipboard + of the logged-in user. +
    +
    +
  2. +

    +

  3. + The second method will launch Synergy from the + LoginWindow application, once a particular + user has logged in. +

    +

    +
    Pros:
    +
    + The synergy process inherits the + $SECURITYSESSIONID environment variable, + and therefore copy/paste works. +
    +
    Cons:
    +
    + Once the user logs out, synergy dies, and no remote + control is possible. +
    +
    +
  4. +

    +

  5. + The third method is to launch a startup script from the + "Startup Items" tab under System Preferences -> Accounts. +

    +

    +
    Pros:
    +
    + Does not require root (Administrator) access +
    +
    Cons:
    +
    + Once the user logs out, synergy dies, and no remote + control is possible. +
    +
    +
  6. +
+

+The text below describes how to implement a Synergy client using +the first two methods simultaneously. This way, Synergy is +always running, and the clipboard is available when someone is +logged in. A Mac OS X Synergy server setup will be quite similar. +

+1. Create a System Level Startup Item +

+

    +
  • + Open a Terminal window, and become root: + + $ sudo su - + +
  • +
  • + Create a folder for this item: + + # mkdir -p /Library/StartupItems/Synergy + +
  • +
  • + In this folder, create a new script file by the same name as + the directory itself, Synergy. This script + should contain the following text: +

    + +#!/bin/sh +. /etc/rc.common +  +run=(/usr/local/bin/synergyc -n $(hostname -s) -1 -f synergy-server) +  +KeepAlive () +{ + proc=${1##*/} +  + while [ -x "$1" ] + do + if ! ps axco command | grep -q "^${proc}\$" + then + "$@" + fi +  + sleep 3 + done +} +  +StartService () +{ + ConsoleMessage "Starting Synergy" + KeepAlive "${run[@]}" & +} +  +StopService () +{ + return 0 +} +  +RestartService () +{ + return 0 +} +  +RunService "$1" + +

    + However, replace synergy-server with the actual + name or IP address of your Synergy server. +

    + Note that this scripts takes care not to start + Synergy if another instance is currently running. This + allows it to run in the background even when synergy is also + started independently, e.g. from the LoginWindow + application as described below. +

  • +
  • + Make this script executable: + + # chmod 755 /Library/StartupItems/Synergy/Synergy + +
  • +
  • + In the same folder, create a file named + StartupParameters.plist containing: +

    + +{ + Description = "Synergy Client"; + Provides = ("Synergy"); + Requires = ("Network"); + OrderPreference = "None"; +} + +

  • +
+

+That's it! If you want to test this setup, you can run the +startup script as follows: +

+ + # /Library/StartupItems/Synergy/Synergy start + +

+Any errors, as well as output from Synergy, will be shown in +your terminal window. +

+Next time you reboot, Synergy should start automatically. +

+2. Run Synergy When a User Logs In +

+Each time a user successfully logs in via the console, the +LoginWindow application creates a unique session +cookie and stores it in the environment variable +$SECURITYSESSIONID. For copy and paste operations +to work, Synergy needs access to this environment variable. In +other words, Synergy needs to be launched (directly or +indirectly) via the LoginWindow application. +

+However, in order to kill any synergy processes started at the +system level (as described above), we need root access. Thus, +launching Synergy within the User's environment (e.g. via the +Startup Items tab in System Preferences -> Accounts) is not an +option that work in conjunction with the method above. +

+Fortunately, the LoginWindow application provides +a "hook" for running a custom program (as root, with the username provided as +the first and only argument) once a user has authenticated, but +before the user is logged in. +

+Unfortunately, only one such hook is available. If you have +already installed a Login Hook, you may need to add the text +from below to your existing script, rather than creating a new +one. +

+

    +
  • + Launch a Terminal window, and become root: + + $ sudo su - + +
  • +

    +

  • + Find out if a LoginHook already exists: + + # defaults read com.apple.loginwindow LoginHook + + This will either show the full path to a script or + executable file, or the text: + + The domain/default pair of (com.apple.loginwindow, LoginHook) does not exist + + In the former case, you need to modify your existing script, + and/or create a "superscript" which in turn calls your + existing script plus the one we will create here. +

    + The rest of this text assumes that this item did not already + exist, and that we will create a new script. +

  • +
  • + Create a folder in which we will store our custom startup + script: + + # mkdir -p /Library/LoginWindow + +
  • +
  • + In this folder, create a new script file (let's name it + LoginHook.sh), containing the following text: +

    + +#!/bin/sh +prog=(/usr/local/bin/synergyc -n $(hostname -s) ip-address-of-server) +  +### Stop any currently running Synergy client +killall ${prog[0]##*/} +  +### Start the new client +exec "${prog[@]}" + +

  • +
  • + Make this script executable: + + # chmod 755 /Library/LoginWindow/LoginHook.sh + +
  • +
  • + Create a login hook to call the script you just created: + + # defaults write com.apple.loginwindow LoginHook /Library/LoginWindow/LoginHook.sh + +
  • +
+

+More information on setting up login hooks can be found at +Apple. +

+When running the Synergy client, you may need to use the IP +address of the Synergy server rather than its host name. +Specifically, unless you have listed the server in your +local /etc/hosts file or in your local NetInfo +database, name services (i.e. DNS) may not yet be available by the +time you log in after power-up. synergyc will +quit if it cannot resolve the server name. +

+(This is not an issue with the previous method, because the +StartupParameters.plist file specifies that this +script should not be run until "network" is available). +

+3. Good Luck! +

+Remember to look in your system log on both your server and your +client(s) for clues to any problems you may have +(/var/log/system.log on your OS X box, typically +/var/log/syslog on Linux boxes). +

+ + + diff --git a/doc/banner.html b/doc/banner.html new file mode 100644 index 00000000..eed66918 --- /dev/null +++ b/doc/banner.html @@ -0,0 +1,16 @@ + + + + + + + + Synergy Header + + + + + +
Synergy
+ + diff --git a/doc/border.html b/doc/border.html new file mode 100644 index 00000000..ce45847a --- /dev/null +++ b/doc/border.html @@ -0,0 +1,14 @@ + + + + + + + + Synergy + + + +
+ + diff --git a/doc/compiling.html b/doc/compiling.html new file mode 100644 index 00000000..69321913 --- /dev/null +++ b/doc/compiling.html @@ -0,0 +1,112 @@ + + + + + + + + Building and Installing Synergy + + +

+

Prerequisites for building

+

+To build synergy from the sources you'll need the following: +

    +
  • Windows +
      +
    • Microsoft Windows SDK for Vista; or +
    • VC++ 6.0 or up should work +
    +

    +

  • Unix +
      +
    • gcc 2.95 or up +
    • X11R4 or up headers and libraries +
    +

    +

  • Mac OS X +
      +
    • gcc 2.95 or up +
    • Carbon development headers and libraries +
    +
+

+

Configuring the build

+

+This step is not necessary on Windows. +

+To configure the build for your platform use the configure script: +

+  ./configure
+
+For a list of options to configure use: +
+  ./configure --help
+
+On Solaris you may need to use: +
+  ./configure --x-includes=/usr/openwin/include --x-libraries=/usr/openwin/lib
+
+so synergy can find the X11 includes and libraries. +

+

Building

+

    +
  • Windows +

    + Open a command prompt window (cmd.exe or command.exe). If necessary + run vcvars.bat, created when VC++ or Visual Studio was installed. (Use + search to find it.) It's necessary to run the file if you didn't have + the installer set up environment variables for you. Then enter: +

    +  nmake /nologo /f Makefile.win
    +  
    + This will build the programs into build\Release. +

    +
  • Unix or Mac OS X +

    + Simply enter: +

    +  make
    +  
    + This will build the client and server and leave them in their + respective source directories. +

    +
+

+

Installing

+

    +
  • Windows +

    + You'll need NSIS, + the Nullsoft Scriptable Install System. As in the building on Windows + description above, enter: +

    +  nmake /nologo /f Makefile.win installer
    +  
    + to build build\Release\SynergyInstaller.exe. Run + this to install synergy. +

    + Alternatively, you can simply copy the following files from the + build\Release + directory to a directory you choose (perhaps under the + Program Files directory): +

      +
    • synergy.exe +
    • synergyc.exe +
    • synergys.exe +
    • synrgyhk.dll +
    +

    +
  • Unix or Mac OS X +

    +

    +  make install
    +  
    + will install the client and server into + /usr/local/bin unless you + specified a different directory when you ran configure. +

    + + + diff --git a/doc/configuration.html b/doc/configuration.html new file mode 100644 index 00000000..6c1c8baa --- /dev/null +++ b/doc/configuration.html @@ -0,0 +1,686 @@ + + + + + + + + Synergy Configuration Guide + + +

    +

    Synergy Configuration File Format

    +

    +The synergy server requires configuration. It will try certain +pathnames to load the configuration file if you don't specify a +path using the --config command line +option. synergys --help reports those +pathnames. +

    +The configuration file is a plain text file. Use any text editor +to create the configuration file. The file is broken into sections +and each section has the form: + + section: name + args + end + +Comments are introduced by # and continue to +the end of the line. name must be one of the +following: +

      +
    • screens +
    • aliases +
    • links +
    • options +
    +See below for further explanation of each section type. The +configuration file is case-sensitive so Section, +SECTION, and section +are all different and only the last is valid. Screen names are the +exception; screen names are case-insensitive. +

    +The file is parsed top to bottom and names cannot be used before +they've been defined in the screens or +aliases sections. So the +links and aliases +must appear after the screens and links +cannot refer to aliases unless the aliases +appear before the links. +

    +

    screens

    +

    +args is a list of screen names, one name per +line, each followed by a colon. Names are arbitrary strings but they +must be unique. The hostname of each computer is recommended. (This +is the computer's network name on win32 and the name reported by the +program hostname on Unix and OS X. Note +that OS X may append .local to the name you +gave your computer; e.g. somehost.local.) +There must be a screen name for the server and each client. Each +screen can specify a number of options. Options have the form +name = +value and are listed one per line +after the screen name. +

    +Example: + + section: screens + moe: + larry: + halfDuplexCapsLock = true + halfDuplexNumLock = true + curly: + meta = alt + end + +This declares three screens named moe, +larry, and curly. +Screen larry has half-duplex Caps Lock and +Num Lock keys (see below) and screen curly +converts the meta modifier key to the alt modifier key. +

    +A screen can have the following options: +

      +
    • halfDuplexCapsLock = {true|false} +

      + This computer has a Caps Lock key that doesn't report a + press and a release event when the user presses it but + instead reports a press event when it's turned on and a + release event when it's turned off. If Caps Lock acts + strangely on all screens then you may need to set this + option to true + on the server screen. If it acts strangely on one + screen then that screen may need the option set to + true. +

      +

    • halfDuplexNumLock = {true|false} +

      + This is identical to halfDuplexCapsLock + except it applies to the Num Lock key. +

      +

    • halfDuplexScrollLock = {true|false} +

      + This is identical to halfDuplexCapsLock + except it applies to the Scroll Lock key. Note that, by default, + synergy uses Scroll Lock to keep the cursor on the current screen. That + is, when Scroll Lock is toggled on, the cursor is locked to the screen + that it's currently on. You can use that to prevent accidental switching. + You can also configure other hot keys to do that; see + lockCursorToScreen. +

      +

    • switchCorners = <corners> +

      + See switchCorners below. +

      +

    • switchCornerSize = N +

      + See switchCornerSize below. +

      +

    • xtestIsXineramaUnaware = {true|false} +

      + This option works around a bug in the XTest extension + when used in combination with Xinerama. It affects + X11 clients only. Not all versions of the XTest + extension are aware of the Xinerama extension. As a + result, they do not move the mouse correctly when + using multiple Xinerama screens. This option is + currently true by default. If + you know your XTest extension is Xinerama aware then set + this option to false. +

      +

    • shift = {shift|ctrl|alt|meta|super|none}
      + ctrl = {shift|ctrl|alt|meta|super|none}
      + alt = {shift|ctrl|alt|meta|super|none}
      + meta = {shift|ctrl|alt|meta|super|none}
      + super = {shift|ctrl|alt|meta|super|none}
      +

      + Map a modifier key pressed on the server's keyboard to + a different modifier on this client. This option only + has an effect on a client screen; it's accepted and + ignored on the server screen. +

      + You can map, say, the shift key to shift (the default), + ctrl, alt, meta, super or nothing. Normally, you + wouldn't remap shift or ctrl. You might, however, have + an X11 server with meta bound to the Alt keys. To use + this server effectively with a windows client, which + doesn't use meta but uses alt extensively, you'll want + the windows client to map meta to alt (using + meta = alt). +

      +

    +

    +

    aliases

    +

    + args is a list of screen names just like + in the screens section except each screen + is followed by a list of aliases, one per line, not followed + by a colon. An alias is a screen name and must be unique. During + screen name lookup each alias is equivalent to the screen name it + aliases. So a client can connect using its canonical screen name + or any of its aliases. +

    + Example: + + section: aliases + larry: + larry.stooges.com + curly: + shemp + end + + Screen larry is also known as + larry.stooges.com and can connect as + either name. Screen curly is also + known as shemp (hey, it's just an example). +

    +

    links

    +

    + args is a list of screen names just like + in the screens section except each screen + is followed by a list of links, one per line. Each link has the + form {left|right|up|down}[<range>] = + name[<range>]. A link indicates which + screen is adjacent in the given direction. +

    + Each side of a link can specify a range which defines a portion + of an edge. A range on the direction is the portion of edge you can + leave from while a range on the screen is the portion of edge you'll + enter into. Ranges are optional and default to the entire edge. All + ranges on a particular direction of a particular screen must not + overlap. +

    + A <range> is written as (<start>,<end>). + Both start and end + are percentages in the range 0 to 100, inclusive. The start must be + less than the end. 0 is the left or top of an edge and 100 is the + right or bottom. +

    + Example: + + section: links + moe: + right = larry + up(50,100) = curly(0,50) + larry: + left = moe + up(0,50) = curly(50,100) + curly: + down(0,50) = moe + down(50,100) = larry(0,50) + end + + This indicates that screen larry is to + the right of screen moe (so moving the + cursor off the right edge of moe would + make it appear at the left edge of larry), + the left half of + curly is above the right half of + moe, + moe is to the left of + larry (edges are not necessarily symmetric + so you have to provide both directions), the right half of + curly is above the left half of + larry, all of moe + is below the left half of curly, and the + left half of larry is below the right half of + curly. +

    + Note that links do not have to be + symmetrical; for instance, here the edge between + moe and curly + maps to different ranges depending on if you're going up or down. + In fact links don't have to be bidirectional. You can configure + the right of moe to go to + larry without a link from the left of + larry to moe. + It's possible to configure a screen with no outgoing links; the + cursor will get stuck on that screen unless you have a hot key + configured to switch off of that screen. +

    +

    options

    +

    + args is a list of lines of the form + name = value. These set the global + options. +

    + Example: + + section: options + heartbeat = 5000 + switchDelay = 500 + end + +

    + You can use the following options: +

      +
    • heartbeat = N +

      + The server will expect each client to send a message no + less than every N milliseconds. + If no message arrives from a client within + 3N seconds the server forces that + client to disconnect. +

      + If synergy fails to detect clients disconnecting while + the server is sleeping or vice versa, try using this + option. +

      +

    • switchCorners = <corners> +

      + Synergy won't switch screens when the mouse reaches the edge of + the screen if it's in a listed corner. The size of all corners + is given by the switchCornerSize + option. +

      + Corners are specified by a list using the following names: +

        +
      • none -- no corners +
      • top-left -- the top left corner +
      • top-right -- the top right corner +
      • bottom-left -- the bottom left corner +
      • bottom-right -- the bottom right corner +
      • left -- top and bottom left corners +
      • right -- top and bottom right corners +
      • top -- left and right top corners +
      • bottom -- left and right bottom corners +
      • all -- all corners +
      +

      + The first name in the list is one of the above names and defines + the initial set of corners. Subsequent names are prefixed with + + or - to add the corner to or remove the corner from the set, + respectively. For example: +

      + + all -left +top-left + +

      + starts will all corners, removes the left corners (top and bottom) + then adds the top-left back in, resulting in the top-left, + bottom-left and bottom-right corners. +

      +

    • switchCornerSize = N +

      + Sets the size of all corners in pixels. The cursor must be within + N pixels of the corner to be considered + to be in the corner. +

      +

    • switchDelay = N +

      + Synergy won't switch screens when the mouse reaches the + edge of a screen unless it stays on the edge for + N + milliseconds. This helps prevent unintentional + switching when working near the edge of a screen. +

      +

    • switchDoubleTap = N +

      + Synergy won't switch screens when the mouse reaches the + edge of a screen unless it's moved away from the edge + and then back to the edge within N + milliseconds. With + the option you have to quickly tap the edge twice to + switch. This helps prevent unintentional switching + when working near the edge of a screen. +

      +

    • screenSaverSync = {true|false} +

      + If set to false then synergy + won't synchronize screen savers. Client screen savers + will start according to their individual configurations. + The server screen saver won't start if there is input, + even if that input is directed toward a client screen. +

      +

    • relativeMouseMoves = {true|false} +

      + If set to true then secondary + screens move the mouse using relative rather than absolute + mouse moves when and only when the cursor is locked to the + screen (by Scroll Lock or a configured + hot key). + This is intended to make synergy work better with certain + games. If set to false or not + set then all mouse moves are absolute. +

      +

    • keystroke(key) = actions +

      + Binds the key combination key to the + given actions. key + is an optional list of modifiers (shift, + control, alt, + meta or super) + optionally followed by a character or a key name, all separated by + + (plus signs). You must have either + modifiers or a character/key name or both. See below for + valid key names. +

      + Actions are described below. +

      + Keyboard hot keys are handled while the cursor is on the primary + screen and secondary screens. Separate actions can be assigned + to press and release. +

      +

    • mousebutton(button) = actions +

      + Binds the modifier and mouse button combination + button to the given + actions. button + is an optional list of modifiers (shift, + control, alt, + meta or super) + followed by a button number. The primary button (the + left button for right handed users) is button 1, the middle button + is 2, etc. +

      + Actions are described below. +

      + Mouse button actions are not handled while the cursor is on the + primary screen. You cannot use these to perform an action while + on the primary screen. Separate actions can be assigned to press + and release. +

      +

    + You can use both the switchDelay and + switchDoubleTap options at the same + time. Synergy will switch when either requirement is satisfied. +

    +Actions are two lists of individual actions separated +by commas. The two lists are separated by a semicolon. Either list can be +empty and if the second list is empty then the semicolon is optional. The +first list lists actions to take when the condition becomes true (e.g. the +hot key or mouse button is pressed) and the second lists actions to take +when the condition becomes false (e.g. the hot key or button is released). +The condition becoming true is called activation and becoming false is +called deactivation. +Allowed individual actions are: +

      +
    • keystroke(key[,screens]) +
    • keyDown(key[,screens]) +
    • keyUp(key[,screens]) +

      + Synthesizes the modifiers and key given in key + which has the same form as described in the + keystroke option. If given, + screens lists the screen or screens to + direct the event to, regardless of the active screen. If not + given then the event is directed to the active screen only. + (Due to a bug, keys cannot be directed to the server while on a + client screen.) +

      + keyDown synthesizes a key press and + keyUp synthesizes a key release. + keystroke synthesizes a key press on + activation and a release on deactivation and is equivalent to + a keyDown on activation and + keyUp on deactivation. +

      + screens is either * + to indicate all screens or a colon (:) separated list of screen + names. (Note that the screen name must have already been encountered + in the configuration file so you'll probably want to put actions at + the bottom of the file.) +

      +

    • mousebutton(button) +
    • mouseDown(button) +
    • mouseUp(button) +

      + Synthesizes the modifiers and mouse button given in + button + which has the same form as described in the + mousebutton option. +

      + mouseDown synthesizes a mouse press and + mouseUp synthesizes a mouse release. + mousebutton synthesizes a mouse press on + activation and a release on deactivation and is equivalent to + a mouseDown on activation and + mouseUp on deactivation. +

      +

    • lockCursorToScreen(mode) +

      + Locks the cursor to or unlocks the cursor from the active screen. + mode can be off + to unlock the cursor, on to lock the + cursor, or toggle to toggle the current + state. The default is toggle. If the + configuration has no lockCursorToScreen + action and Scroll Lock is not used as a hot key then Scroll Lock + toggles cursor locking. +

      +

    • switchToScreen(screen) +

      + Jump to screen with name or alias screen. +

      +

    • switchInDirection(dir) +

      + Switch to the screen in the direction dir, + which may be one of left, + right, up or + down. +

      +

    • keyboardBroadcast(mode[,screens]) +

      + Turns broadcasting of keystrokes to multiple screens on and off. When + turned on all key presses and releases are sent to all of the screens + listed in screens. If not given, empty or + * then keystrokes are broadcast to all screens. + (However, due to a bug, keys cannot be sent to the server while on a + client screen.) +

      + mode can be off + to turn broadcasting off, on to turn it + on, or toggle to toggle the current + state. The default is toggle. +

      + screens is either * + to indicate all screens or a colon (:) separated list of screen + names. (Note that the screen name must have already been encountered + in the configuration file so you'll probably want to put actions at + the bottom of the file.) +

      + Multiple keyboardBroadcast actions may be + configured with different screens. The most + recently performed action defines the screens to broadcast to. +

      +

    +

    +Examples: +

      +
    • keystroke(alt+left) = switchInDirection(left) +

      + Switches to the screen to left when the left arrow key is pressed + in combination with the Alt key. +

      +

    • keystroke(shift+control+alt+super) = switchToScreen(moe) +

      + Switches to screen moe when all of the + Shift, Control, Alt, and Super modifier keys are pressed together. +

      +

    • keystroke(alt+f1) = ; lockCursorToScreen(toggle) +

      + Toggles locking the cursor to the screen when Alt+F1 is released. +

      +

    • mousebutton(2) = mouseDown(control+1) ; mouseUp(control+1) +

      + While on a secondary screen clicking the middle mouse button will + become a Control click of the primary button. +

      +

    • keystroke(super+f1) = keystroke(super+L,larry), keystroke(control+alt+delete,curly) +

      + Pressing Super+F1 (on any screen) will synthesize Super+L on screen + larry and Control+Alt+Delete on screen + curly. +

      +

    +

    +Valid key names are: +

      +
    • AppMail +
    • AppMedia +
    • AppUser1 +
    • AppUser2 +
    • AudioDown +
    • AudioMute +
    • AudioNext +
    • AudioPlay +
    • AudioPrev +
    • AudioStop +
    • AudioUp +
    • BackSpace +
    • Begin +
    • Break +
    • Cancel +
    • CapsLock +
    • Clear +
    • Delete +
    • Down +
    • Eject +
    • End +
    • Escape +
    • Execute +
    • F1 +
    • F2 +
    • F3 +
    • F4 +
    • F5 +
    • F6 +
    • F7 +
    • F8 +
    • F9 +
    • F10 +
    • F11 +
    • F12 +
    • F13 +
    • F14 +
    • F15 +
    • F16 +
    • F17 +
    • F18 +
    • F19 +
    • F20 +
    • F21 +
    • F22 +
    • F23 +
    • F24 +
    • F25 +
    • F26 +
    • F27 +
    • F28 +
    • F29 +
    • F30 +
    • F31 +
    • F32 +
    • F33 +
    • F34 +
    • F35 +
    • Find +
    • Help +
    • Home +
    • Insert +
    • KP_0 +
    • KP_1 +
    • KP_2 +
    • KP_3 +
    • KP_4 +
    • KP_5 +
    • KP_6 +
    • KP_7 +
    • KP_8 +
    • KP_9 +
    • KP_Add +
    • KP_Begin +
    • KP_Decimal +
    • KP_Delete +
    • KP_Divide +
    • KP_Down +
    • KP_End +
    • KP_Enter +
    • KP_Equal +
    • KP_F1 +
    • KP_F2 +
    • KP_F3 +
    • KP_F4 +
    • KP_Home +
    • KP_Insert +
    • KP_Left +
    • KP_Multiply +
    • KP_PageDown +
    • KP_PageUp +
    • KP_Right +
    • KP_Separator +
    • KP_Space +
    • KP_Subtract +
    • KP_Tab +
    • KP_Up +
    • Left +
    • LeftTab +
    • Linefeed +
    • Menu +
    • NumLock +
    • PageDown +
    • PageUp +
    • Pause +
    • Print +
    • Redo +
    • Return +
    • Right +
    • ScrollLock +
    • Select +
    • Sleep +
    • Space +
    • SysReq +
    • Tab +
    • Undo +
    • Up +
    • WWWBack +
    • WWWFavorites +
    • WWWForward +
    • WWWHome +
    • WWWRefresh +
    • WWWSearch +
    • WWWStop +
    • Space +
    • Exclaim +
    • DoubleQuote +
    • Number +
    • Dollar +
    • Percent +
    • Ampersand +
    • Apostrophe +
    • ParenthesisL +
    • ParenthesisR +
    • Asterisk +
    • Plus +
    • Comma +
    • Minus +
    • Period +
    • Slash +
    • Colon +
    • Semicolon +
    • Less +
    • Equal +
    • Greater +
    • Question +
    • At +
    • BracketL +
    • Backslash +
    • BracketR +
    • Circumflex +
    • Underscore +
    • Grave +
    • BraceL +
    • Bar +
    • BraceR +
    • Tilde +
    +Additionally, a name of the form \uXXXX where +XXXX is a hexadecimal number is interpreted as +a unicode character code. +Key and modifier names are case-insensitive. Keys that don't exist on +the keyboard or in the default keyboard layout will not work. +

    + + + diff --git a/doc/contact.html b/doc/contact.html new file mode 100644 index 00000000..1b378b85 --- /dev/null +++ b/doc/contact.html @@ -0,0 +1,44 @@ + + + + + + + + Synergy Contact Info + + +

    +Use the following addresses to contact the synergy project: +

    + + + + + + + + + + + + + + + + +
    Bug reports: Add Synergy Bug
    Help: synergy-help@groundhog.pair..no_spamcom
    General: crs23@users.sourceforge..no_spamnet
    +

    +To avoid spam bots, the above email addresses have ".no_spam" +hidden near the end. If you copy and paste the text be sure to +remove it. +

    +Please check the + +bug list before reporting a bug. You may also find answers at the +synergy forums. +Emails for help asking questions answered on this site will go unanswered. +

    + + + diff --git a/doc/developer.html b/doc/developer.html new file mode 100644 index 00000000..acfdff9a --- /dev/null +++ b/doc/developer.html @@ -0,0 +1,81 @@ + + + + + + + + Synergy Developer Documentation + + +

    +Synergy is reasonably well commented so reading the source code +should be enough to understand particular pieces. See the +doc/PORTING +file in the synergy source code for more high-level information. +

    +

    How it works

    +

    +The theory behind synergy is simple: the server captures mouse, +keyboard, clipboard, and screen saver events and forwards them to +one or more clients. If input is directed to the server itself +then the input is delivered normally. In practice, however, many +complications arise. +

    +First, different keyboard mappings can produce different characters. +Synergy attempts to generate the same character on the client as +would've been generated on the server, including appropriate modifier +keys (like Control and Alt). Non-character keys like Shift are also +synthesized if possible. Sometimes the client simply cannot create +the character or doesn't have a corresponding non-character key and +synergy must discard the event. Note that synergy won't necessarily +synthesize an event for the corresponding key on the client's +keyboard. For example, if the client or server can't distinguish +between the left and right shift keys then synergy can't be certain +to synthesize the shift on the same side of the keyboard as the user +pressed. +

    +Second, different systems have different clipboards and clipboard +formats. The X window system has a system-wide selection and +clipboard (and yet other buffers) while Microsoft Windows has only +a system-wide clipboard. Synergy has to choose which of these +buffers correspond to one another. Furthermore, different systems +use different text encodings and line breaks. Synergy mediates and +converts between them. +

    +Finally, there are no standards across operating systems for some +operations that synergy requires. Among these are: intercepting +and synthesizing events; enabling, disabling, starting and stopping +the screen saver; detecting when the screen saver starts; reading +and writing the clipboard(s). +

    +All this means that synergy must be customized to each operating +system (or windowing system in the case of X windows). Synergy +breaks platform differences into two groups. The first includes +the mundane platform dependent things: file system stuff, +multithreading, network I/O, multi-byte and wide character +conversion, time and sleeping, message display and logging, and +running a process detached from a terminal. This code lives in +lib/arch. +

    +The second includes screen and window management handling, user +event handling, event synthesis, the clipboards, and the screen +saver. This code lives in lib/platform. +

    +For both groups, there are particular classes or interfaces that +must be inherited and implemented for each platform. See the +doc/PORTING file in the synergy source +code for more information. +

    +

    Auto-generated Documentation

    +

    +Synergy can automatically generate documentation from the comments +in the code using doxygen. +Use make doxygen to build it yourself +from the source code into the doc/doxygen/html +directory. +

    +

    + + + diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in new file mode 100644 index 00000000..4abf52f9 --- /dev/null +++ b/doc/doxygen.cfg.in @@ -0,0 +1,898 @@ +# Doxyfile 1.2.13.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @PACKAGE@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, +# German, Greek, Hungarian, Italian, Japanese, Korean, Norwegian, Polish, +# Portuguese, Romanian, Russian, Slovak, Slovene, Spanish and Swedish. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl + +FILE_PATTERNS = *.cpp *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse. + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 3 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript and frames is required (for instance Mozilla, Netscape 4.0+, +# or Internet explorer 4.0+). Note that for large projects the tree generation +# can take a very long time. In such cases it is better to disable this feature. +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line and do not end with a semicolon. Such function macros are typically +# used for boiler-plate code, and will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yield more powerful graphs. + +CLASS_DIAGRAMS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = @HAVE_DOT@ + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/doc/faq.html b/doc/faq.html new file mode 100644 index 00000000..b9696391 --- /dev/null +++ b/doc/faq.html @@ -0,0 +1,266 @@ + + + + + + + + Synergy Frequently Asked Questions + + +

    +

    Synergy Frequently Asked Questions

    +

    +

    Questions

    +
      +
    1. Why doesn't ctrl+alt+del work on secondary screens? +
    2. Can the server and client be using different operating systems? +
    3. What's the difference between synergy and x2x, x2vnc, etc? +
    4. What does "Cannot initialize hook library" mean? +
    5. What security/encryption does synergy provide? +
    6. What should I call my screens in the configuration? +
    7. Why do my Caps-Lock, Num-Lock, Scroll-Lock keys act funny? +
    8. Can synergy share the display in addition to the mouse and keyboard? +
    9. Can synergy do drag and drop between computers? +
    10. Do AltGr or Mode-Switch or ISO_Level3_Shift work? +
    11. Why isn't synergy ported to platform XYZ? +
    12. My client can't connect. What's wrong? +
    13. Linking fails on Solaris. What's wrong? +
    14. The screen saver never starts. Why not? +
    15. I can't switch screens anymore for no apparent reason. Why? +
    16. I get the error 'Xlib: No protocol specified'. Why? +
    17. The cursor goes to secondary screen but won't come back. Why? +
    18. The cursor wraps from one edge of the screen to the opposite. Why? +
    19. How do I stop my game from minimizing when I leave the screen? +
    +

    Answers

    +
      +
    1. Why doesn't ctrl+alt+del work on secondary screens? +

      + Synergy isn't able to capture ctrl+alt+del on PC compatible + primary screens because it's handled completely differently than + other keystrokes. However, when the mouse is on a client + screen, pressing ctrl+alt+pause will simulate ctrl+alt+del + on the client. (A client running on Windows NT, 2000, or XP + must be configured to autostart when the computer starts for + this to work.) +

      + On a primary screen running on an OS X system, you can use + ctrl+command+del. Using the pause key isn't necessary since OS X + doesn't treat ctrl+command+del differently. And using the pause + key isn't usually possible because there isn't one on most OS X + systems. Use command instead of option/alt because + the command key, not the option/alt key, maps to alt on windows. + The reason is because the command key is in the same physical + location and performs the same general function (menu shortcuts) + as alt on a windows system. This mapping can be modified in + the configuration. +

      + On mac laptops, the key labeled "delete" is actually backspace + and ctrl+command+delete won't work. However fn+delete really + is delete so fn+ctrl+command+delete will act as ctrl+alt+del + on a windows secondary screen. +

      +
    2. Can the server and client be using different operating systems? +

      + Yes. The synergy network protocol is platform neutral so + synergy doesn't care what operating systems are running on + the server and clients. +

      +
    3. What's the difference between synergy and +x2x, x2vnc, etc? +

      + Unlike x2x, synergy supports any number of computers and + it doesn't require X on Microsoft Windows platforms. It + also has more advanced clipboard support and synchronizes + screensavers. x2vnc is also limited to two computers, + requires the separate vnc package, and is really only + appropriate for using an X system to control a non-X system. + However, the right tool for the job is whatever tool works + best for you. +

      +
    4. What does "Cannot initialize hook library" mean? +

      + This error can occur on a synergy server running on a + Microsoft Windows operating system. It means that synergy + is already running or possibly was not shut down properly. + If it's running then first end the synergy task. If it's + not then try logging off and back on or rebooting then + starting synergy again. +

      +
    5. What security/encryption does synergy provide? +

      + Synergy provides no built-in encryption or authentication. + Given that, synergy should not be used on or over any untrusted + network, especially the Internet. It's generally fine for home + networks. Future versions may provide built-in encryption and + authentication. +

      + Strong encryption and authentication is available through SSH + (secure shell). Run the SSH daemon (i.e. server) on the same + computer that you run the synergy server. It requires no + special configuration to support synergy. On each synergy + client system, run SSH with port forwarding: +

      +

      +        ssh -f -N -L 24800:server-hostname:24800 server-hostname
      +
      +

      + where server-hostname is the name of the + SSH/synergy server. + Once ssh authenticates itself, start the synergy client + normally except use localhost or + 127.0.0.1 as the server's + address. SSH will then encrypt all communication on behalf of + synergy. Authentication is handled by the SSH authentication. +

      + A free implementation of SSH for Linux and many Unix systems is + OpenSSH. For + Windows there's a port of OpenSSH using + Cygwin. +

      +
    6. What should I call my screens in the configuration? +

      + You can use any unique name in the configuration file for each + screen but it's easiest to use the hostname of the computer. + That's the computer name not including the domain. For example, + a computer with the fully qualified domain name xyz.foo.com has + the hostname xyz. There should also be an alias for xyz to + xyz.foo.com. If you don't use the computer's hostname, you + have to tell synergy the name of the screen using a command line + option, or the startup dialog on Windows. +

      + Some systems are configured to report the fully qualified domain + name as the hostname. For those systems it will be easier to use + the FQDN as the screen name. Also note that a Mac OS X system + named xyz may report its hostname as + xyz.local. If that's the case for you + then use xyz.local as the screen name. +

      +
    7. Why do my Caps-Lock, Num-Lock, Scroll-Lock keys act funny? +

      + Some systems treat the Caps-Lock, Num-Lock, and Scroll-Lock keys + differently than all the others. Whereas most keys report going down + when physically pressed and going up when physically released, on + these systems the Caps-Lock and Num-Lock keys report going down + when being activated and going up when being deactivated. That + is, when you press and release, say, Caps-Lock to activate it, it + only reports going down, and when you press and release to + deactivate it, it only reports going up. This confuses synergy. +

      + You can solve the problem by changing your configuration file. + In the screens section, following each screen that has the + problem, any or all of these lines as appropriate: +

      +

      +        halfDuplexCapsLock = true
      +        halfDuplexNumLock = true
      +        halfDuplexScrollLock = true
      +
      +

      + Then restart synergy on the server or reload the configuration. +

      +
    8. Can synergy share the display in addition to the mouse and keyboard? +

      + No. Synergy is a KM solution not a KVM (keyboard, video, mouse) + solution. However, future versions will probably support KVM. + Hopefully, this will make synergy suitable for managing large + numbers of headless servers. +

      +
    9. Can synergy do drag and drop between computers? +

      + No. That's a very cool idea and it'll be explored. However, it's + also clearly difficult and may take a long time to implement. +

      +
    10. Does AltGr/Mode-Switch/ISO_Level3_Shift work? +

      + Yes, as of 1.0.12 synergy has full support for AltGr/Mode-switch. + That includes support for most (all?) European keyboard layouts. + All systems should be using the same keyboard layout, though, for + all characters to work. (Any character missing from a client's + layout cannot be generated by synergy.) There is experimental + support for ISO_Level3_Shift in 1.1.3. +

      +
    11. Why isn't synergy ported to platform XYZ? +

      + Probably because the developers don't have access to platform XYZ + and/or are unfamiliar with development on XYZ. Also, synergy has + inherently non-portable aspects so there's a not insignificant + effort involved in porting. +

      +
    12. My client can't connect. What's wrong? +

      + A common mistake when starting the client is to give the wrong + server host name. The last synergyc command line option (Unix) + or the "Server Host Name" edit field (Windows) should be the + host name (or IP address) of the server not the client's host + name. If you get the error connection failed: cannot connect + socket followed by the attempt to connect was forcefully + rejected or connection refused then the server isn't started, + can't bind the address, or the client is connecting to the wrong + host name/address or port. See the + troublshooting page for more help. +

      +
    13. Linking fails on Solaris. What's wrong? +

      + Did you add +

      +

      +        --x-includes=/usr/openwin/include --x-libraries=/usr/openwin/lib
      +
      +

      + to the configure command line? Solaris puts + the X11 includes and libraries in an unusual place and the above lets + synergy find them. +

      +
    14. The screen saver never starts. Why not? +

      + If the synergy server is on X Windows then the screen saver will + not start while the mouse is on a client screen. This is a + consequence of how X Windows, synergy and xscreensaver work. +

      +
    15. I can't switch screens anymore for no apparent reason. Why? +

      + This should not happen with 1.1.3 and up. Earlier versions of + synergy would not allow switching screens when a key was down and + sometimes it would believe a key was down when it was not. +

      +
    16. I get the error 'Xlib: No protocol specified'. Why? +

      + You're running synergy without authorization to connect to the + X display. Typically the reason is running synergy as root when + logged in as non-root. Just run synergy as the same user that's + logged in. +

      +
    17. The cursor goes to secondary screen but won't come back. Why? +

      + Your configuration is incorrect. You must indicate the neighbors + of every screen. Just because you've configured 'Apple' to be to + the left of 'Orange' does not mean that 'Orange' is to the right + of 'Apple'. You must provide both in the configuration. +

      +
    18. The cursor wraps from one edge of the screen to the opposite. Why? +

      + Because you told it to. If you list 'Orange' to be to the left of + 'Orange' then moving the mouse off the left edge of 'Orange' will + make it jump to the right edge. Remove the offending line from the + configuration if you don't want that behavior. +

      +
    19. How do I stop my game from minimizing when I leave the screen? +

      + Many full screen applications, particularly games, automatically + minimize when they're no longer the active (foreground) application + on Microsoft Windows. The synergy server normally becomes the foreground + when you switch to another screen in order to more reliably capture all + user input causing those full screen applications to minimize. To + prevent synergy from stealing the foreground just click "Options..." + and check "Don't take foreground window on Windows servers." If you + turn this on then be aware that synergy may not function correctly when + certain programs, particularly the command prompt, are the foreground + when you switch to other screens. Simply make a different program the + foreground before switching to work around that. +

      +
    + + + diff --git a/doc/history.html b/doc/history.html new file mode 100644 index 00000000..48f921c3 --- /dev/null +++ b/doc/history.html @@ -0,0 +1,30 @@ + + + + + + + + Synergy History + + +

    +

    Synergy History

    +

    +The first incarnation of synergy was CosmoSynergy, created by +Richard Lee and Adam Feder then at Cosmo Software, Inc., a +subsidiary of SGI (nee Silicon Graphics, Inc.), at the end of +1996. They wrote it, and Chris Schoeneman contributed, to +solve a problem: most of the engineers in Cosmo Software had +both an Irix and a Windows box on their desks and switchboxes +were expensive and annoying. CosmoSynergy was a great success +but Cosmo Software declined to productize it and the company +was later closed. +

    +Synergy is a from-scratch reimplementation of CosmoSynergy. +It provides most of the features of the original and adds a +few improvements. +

    + + + diff --git a/doc/home.html b/doc/home.html new file mode 100644 index 00000000..df0775db --- /dev/null +++ b/doc/home.html @@ -0,0 +1,61 @@ + + + + + + + + Synergy + + +

    +

    Introduction

    +synergy: [noun] a mutually advantageous conjunction of distinct elements +

    +Synergy lets you easily share a single mouse and keyboard between +multiple computers with different operating systems, each with its +own display, without special hardware. It's intended for users +with multiple computers on their desk since each system uses its +own monitor(s). +

    +Redirecting the mouse and keyboard is as simple as moving the mouse +off the edge of your screen. Synergy also merges the clipboards of +all the systems into one, allowing cut-and-paste between systems. +Furthermore, it synchronizes screen savers so they all start and stop +together and, if screen locking is enabled, only one screen requires +a password to unlock them all. Learn more +about how it works. +

    +Synergy is open source and released under the +GNU Public License (GPL). +

    +

    System Requirements

    +

    +

      +
    • Microsoft Windows 95, Windows 98, Windows Me (the Windows 95 family) +
    • Microsoft Windows NT, Windows 2000, Windows XP (the Windows NT family) +
    • Mac OS X 10.2 or higher +
    • Unix +
        +
      • X Windows version 11 revision 4 or up +
      • XTEST extension
        + (use "xdpyinfo | grep XTEST" to check for XTEST) +
      +
    +All systems must support TCP/IP networking. +

    +"Unix" includes Linux, Solaris, Irix and other variants. Synergy has +only been extensively tested on Linux and may not work completely or +at all on other versions of Unix. Patches are welcome (including +patches that package binaries) at the +patches page. +

    +The Mac OS X port is incomplete. It does not synchronize the screen saver, +only text clipboard data works (i.e. HTML and bitmap data do not work), +the cursor won't hide when not on the screen, and there may be problems +with mouse wheel acceleration. Other problems should be +filed as bugs. +

    + + + diff --git a/doc/images/logo.gif b/doc/images/logo.gif new file mode 100644 index 00000000..d9750ba3 Binary files /dev/null and b/doc/images/logo.gif differ diff --git a/doc/images/warp.gif b/doc/images/warp.gif new file mode 100644 index 00000000..fd10033d Binary files /dev/null and b/doc/images/warp.gif differ diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 00000000..9e9ef302 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,33 @@ + + + + + + + + Synergy + + + + diff --git a/doc/license.html b/doc/license.html new file mode 100644 index 00000000..5e748123 --- /dev/null +++ b/doc/license.html @@ -0,0 +1,313 @@ + + + + + + + + Synergy License and Copyright + + +

    +

    Synergy License and Copyright

    +

    +Synergy is copyright (C) 2002 Chris Schoeneman.
    +Synergy is distributed under the GNU GENERAL PUBLIC LICENSE. +

    +

    GNU GENERAL PUBLIC LICENSE

    +Version 2, June 1991 +

    +Copyright (C) 1989, 1991 Free Software Foundation, Inc.
    +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +

    +

    Preamble

    +

    + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. +

    + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. +

    + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. +

    + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. +

    + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. +

    + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. +

    + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. +

    + The precise terms and conditions for copying, distribution and +modification follow. +

    +

    GNU GENERAL PUBLIC LICENSE
    +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

    +

    + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". +

    +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. +

    + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. +

    +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. +

    + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: +

    + +
         +

    + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. +

    + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. +

    + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) +

    +

    +

    +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. +

    +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. +

    +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. +

    + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: +

    + +
         +

    + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, +

    + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, +

    + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) +

    +

    +

    +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. +

    +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. +

    + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. +

    + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. +

    + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. +

    + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. +

    +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. +

    +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. +

    +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. +

    + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. +

    + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. +

    +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. +

    + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. +

    +NO WARRANTY +

    + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE +IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE +COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +

    + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED +TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY +WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED +ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF +THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT +LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. +

    +END OF TERMS AND CONDITIONS +

    + + + diff --git a/doc/news.html b/doc/news.html new file mode 100644 index 00000000..b3729ec6 --- /dev/null +++ b/doc/news.html @@ -0,0 +1,599 @@ + + + + + + + + Synergy News + + +

    +Apr-02-2006 - Synergy 1.3.1 released +

    +Made following changes: +

      +
    • Hot key screen switching now restores last cursor position +
    • Fixed loss of hot keys when reloading configuration +
    • Fixed autorepeating on win32 (no longer sending repeating key releases) +
    • Fixed autorepeating on X11 (non-repeating keys were repeating) +
    • Fixed AltGr issues on X11 +
    • Fixed modifier mapping bug on OS X client (caused wrong characters) +
    • Fixed one way for modifiers to get stuck active on all platforms +
    • Fixed bugs in win32 GUI +
    • Removed alloca() from unix code (should fix FreeBSD build) +
    • Added more debugging output for network problems +
    • Fixed failure to detect some errors on X11 +
    +

    +Mar-22-2006 - Synergy 1.3.0 released +

    +Made following additions: +

      +
    • Console window on win32 can now be closed (reopened from tray menu) +
    • Can now change logging level on the fly from win32 tray menu +
    • Added client keep alive (lost connections are now detected reliably) +
    • Added support for linking portions of screen edges +
    • Added version number to UI in win32 +
    • Added GUI for hot key configuration on win32 +
    • Hot keys can now perform actions on press and/or release +
    • Added key down, key up, mouse down, and mouse up hot key actions +
    • Key actions can be directed to particular screens +
    • Hot keys can each perform multiple actions +
    +

    +Made following fixes: +

      +
    • Fixed AltGr key mappings (again) +
    • Fixed assertion when pasting on X11 +
    • Fixed modifier keys in VMware on X11 +
    • OS X server now treats sends option/alt as AltGr or super depending on key +
    • Improved handling of AltGr on win32 +
    • Fixed not removing client when connection is lost +
    • Clients now detect loss of connection to server and reconnect +
    • Fixed mouse jumping on OS X multimonitor systems +
    • Closing console on win32 no longer quits synergy +
    • Fixed Num Lock breaking certain keys +
    • Fixed Scroll Lock not locking cursor to screen +
    • Fixed mapping of delete key on X11 +
    • Fixed loss of clipboard after a particular copy/paste sequence +
    • Fixed compatibility with windows 95/98/Me (ToUnicodeEx) +
    • Fixed bad argument to function on OS X +
    • Fixed error parsing comments in configuration +
    • Fixed autorepeat on win32 servers +
    • Fixed X11 keyboard focus bug when reentering screen +
    • Fixed (suppressed) hot key autorepeating +
    • Fixed mousebutton action when Caps/Num/Scroll Lock were on +
    • Added documentation on firewalls +
    • Fixed documentation formatting on IE6 +
    +

    +Hot keys support has one known major bug: key actions cannot be directed +to the server (primary) screen. The configuration file syntax has changed +from earlier versions; users will have to modify the configurations by +hand. +

    +Dec-18-2005 - Synergy 1.2.7 released +

    +Made following changes: +

      +
    • Added preliminary support for configurable hot keys (Lorenz Schori) +
    • Major rewrite of keyboard handling code +
    • Fixed non-US keyboard handling (AltGr and ISO_Level3_Shift) +
    • Now supporting all installed keyboard layouts simultaneously +
    • Fixed bug in handling remapped caps-lock on X11 +
    • Fixed control and alt keys getting stuck on on X11 +
    • Fixed desktop focus problems requiring extra clicks on win32 +
    • Fixed alt key event getting passed to server when on client on win32 +
    • Synergy would prevent alt+numpad character entry; this is fixed +
    • Fixed suppression of xscreensaver 2.21 on X11 +
    • Fixed middle mouse button dragging on OSX server (Brian Kendall) +
    • Fixed caps/num/scroll lock toggles getting out of sync +
    • Enhanced support for converting clipboard text to the Latin-1 encoding +
    • Added autostart documentation for KDE users +
    • Added more details about using Terminal for OSX users +
    • Fixed crash when using --help on certain platforms +
    +

    +The hot key support is known to have bugs. The configuration file +syntax for hot keys is likely to change and the documentation for it +is minimal. The graphical UI on windows does not provide any support +for editing hot keys. +

    +Nov-12-2005 - Synergy 1.2.6 released +

    +Made following changes: +

      +
    • Fixed permission problem saving autostart configuration in win32 launcher +
    • Disabled buggy fix for loss of clipboard change detection +
    • Restored pthread signal autoconf code +
    +

    +Oct-17-2005 - Synergy 1.2.5 released +

    +Made following changes: +

      +
    • Win32 launcher now saves configuration automatically +
    • Fixed failure to save autostart configuration on win32 +
    • Fixed output bottom-right configuration flag +
    • Now properly releasing keys when leaving a client screen +
    • Fixed stuck-Alt on win32 +
    • Fixed 64-bit problem with clipboard on X11 +
    • Fixed BadAtom bug on X11 +
    • Fixed loss of detection of clipboard changes on win32 +
    • Added support for the MightyMouse +
    • Added support for buttons 4 and 5 on OSX +
    • Now shutting down win32 services when uninstalling +
    +

    +Aug-07-2005 - Synergy 1.2.4 released +

    +Made following changes: +

      +
    • Fixed gcc 4.0 warnings +
    • Fixed autoconf/automake problems +
    • Fixed scroll-lock on X windows +
    • Added option to suppress foreground window grabbing on win32 +
    • Fixed --daemon option on win32 client +
    • Fixed --no-restart on client +
    • Updated OS X autostart documentation +
    +

    +Jul-27-2005 - Synergy 1.2.3 released +

    +Made following changes: +

      +
    • Added OS X screensaver synchronization support (Lorenz Schori) +
    • Added OS X sleep support (Lorenz Schori) +
    • Added OS X fast user switching support (Lorenz Schori) +
    • Fixed international keyboard support on OS X (Lorenz Schori) +
    • Now capturing global hotkeys (e.g. cmd+tab, etc) on OS X (Lorenz Schori) +
    • Added support for SO_REUSEADDR (Don Eisele) +
    • Added "dead" corners feature +
    • Fixed "resource temporarily unavailable" warning when quiting on OS X +
    • Win32 now defaults to WARNING log level to avoid console window +
    • Now disabling foreground window on win32 when leaving server (Brent Priddy) +
    +

    +Jan-26-2005 - Synergy 1.2.2 released +

    +Made following changes: +

      +
    • Fixed major OS X modifier key handling bug +
    • Fixed handling of ISO_Level3_Shift on X11 +
    +

    +Jan-04-2005 - Synergy 1.2.1 released +

    +Made following changes: +

      +
    • Fixed major OS X keyboard handling bug +
    • Fixed some minor documentation bugs +
    +

    +Dec-30-2004 - Synergy 1.2.0 released +

    +Made following changes: +

      +
    • Improved support for moving laptops between networks (Brent Priddy) +
    • Added ISO_Level3_Shift support on X windows +
    • Now doing PageUp/PageDown if no mouse wheel on X windows (Tom Chadwick) +
    • Fixed handling of number pad number keys on Windows 95/98/Me +
    • Fixed handling of non-existant 4th and 5th mouse buttons on Windows +
    • Added support for Unicode keyboard layouts on OS X +
    • Fixed memory leak on OS X +
    • Added OS X autostart documentation (Tor Slettnes) +
    +

    +Nov-12-2004 - Synergy 1.1.10 released +

    +Made following changes: +

      +
    • Fixed race in condition variable wrapper; caused synergy to hang randomly +
    • Fixed modifier key and caps-lock handling on OSX +
    • System info log message now filtered like all other messages +
    +

    +Nov-07-2004 - Synergy 1.1.9 released +

    +Made following changes: +

      +
    • Fixed compiler error on gcc 3.4 and later +
    • Worked around minor gcc -O3 compiler bug +
    • Now logging system info at startup +
    • Config file errors now logged as errors rather than debug warnings +
    • Added half-duplex scroll lock option +
    • Fixed tracking of half-duplex toggle key state +
    • Now accepting screen names ending in dot (.) for OS X convenience +
    • OS X key mapping now loaded from system resources rather than hard coded +
    • Fixed multimonitor OS X pimary screen bug; multimon OS X should now work +
    • Added experimental workaround for laggy mouse when running linux -> OS X +
    • Fixed bug in win32 installer packaging +
    • Fixed unrequested continuous mouse wheel scrolling on win32 +
    • Added win32 GUI to set server address to listen on +
    • Fixed resource leak on win32 +
    • Fixed screensaver detection on windows 2000 and XP +
    • Fixed flickering mouse on multimon windows NT/2000/XP +
    • Fixed quiting when powerdvd stops playing (may fix other situations, too) +
    • Added tray icon menu item to force clients to reconnect +
    • Fixed handling of number pad keys with num-lock off on win32 +
    • Fixed shift key not working when a console windows has focus on win32 server +
    • Improved configure of Xinerama and DPMS +
    • Improved portability (removed recursive mutexes and _*_SOURCE defines) +
    • Now handling DPMS headers without prototypes +
    • Fixed dead key and AltGr+shift handling on X11 +
    • Fixed use of freed memory on unix +
    • Fixed AltGr mapping to Ctrl and not Ctrl+Alt on X11 without Alt_R mapped +
    • Added -display option for X11 +
    • Added support for X11 compose key (Multi_key) +
    +

    +Aug-05-2004 - Synergy 1.1.8 released +

    +Made following changes: +

      +
    • Removed key event capture on X11 (was breaking terminal keyboard input) +
    • Worked around win32 command prompt stealing shift key events +
    • Fixed handling of pause key on win32 +
    • Fixed handling of backslash on win32 internation keyboard mapping +
    • Fixed handling of ctrl and alt keys on NT/2k/XP +
    • Fixed XCode project (removed cross-compile) +
    • Worked around select() bug in OS X +
    • Worked around bug in ifstream on OS X +
    • Fixed handling of modifier keys on OS X synergy server +
    • Fixed handling of space key on OS X synergy server +
    • Fixed handling of key autorepeat on OS X server +
    • Fixed mouse wheel drift on OS X client +
    • Reorganized documentation and converted to HTML +
    +

    +Jun-13-2004 - Synergy 1.1.7 released +

    +Made following changes: +

      +
    • Added OS X precompiled header file forgotten in last build +
    • Fixed bug in fix for 'unexpected async reply' on X11 +
    • Removed dependency on "browser" service on win32 +
    • Fixed assertion failure when connection fails immediately +
    • Fixed failure to connect on AIX +
    • Fixed error in conversion from multibyte to wide characters +
    • Maybe fixed win32 screen saver detection +
    +

    +May-26-2004 - Synergy 1.1.6 released +

    +Made following changes: +

      +
    • Added preliminary Mac OS X support (client and server) +
    • Fixed ctrl+alt+del emulation on win32 +
    • Fixed ctrl+alt+del on win32 server acting on both client and server +
    • Fixed handling of screen resolution changes on win32 +
    • Fixed 'unexpected async reply' on X11 +
    • Added dependency to win32 service to avoid startup race condition +
    • Fixed reference count bug +
    • Keyboard input focus now restored on X11 (fixes loss of input in some games) +
    +

    +The OS X port does not yet support: +

      +
    • HTML and bitmap clipboard data +
    • Screen saver synchronization +
    • Non-US English keyboards +
    +

    +May-05-2004 - Synergy 1.1.5 released +

    +Made following changes: +

      +
    • No longer switching screens when a mouse button is down +
    • Worked around win32 mouse hook bug, fixing switch on double tap +
    • Added support for HTML and bitmap (image/bmp) clipboard data +
    • Physical mouse no longer necessary on win32 secondary screens to see cursor +
    • Added experimental relative mouse moves on secondary screen option +
    • Fixed win32 lock up when closing server with clients still connected +
    • Fixed bug in handling duplicate connections +
    • Fixed pthread mutex initialization +
    • Made synergy dependent on NetBT on win32 (for service startup order) +
    • Automake fixes; now mostly works on darwin and MinGW +
    • Fixed builds on Solaris 9, FreeBSD, and OpenBSD +
    • Partial support for MSYS/MinGW builds (NOT COMPLETE) +
    • Partial merge of OS X port (NOT COMPLETE) +
    +

    +Mar-31-2004 - Synergy 1.1.4 released +

    +Made following changes: +

      +
    • Fixed lookup of hosts by name of win32 +
    • Reverted tray icon code to 1.0.15 version; seems to fix the bugs +
    • Fixed crash when caps, num, or scroll lock not in key map on X11 +
    • Fixed double tap and wait to switch features +
    +

    +Mar-28-2004 - Synergy 1.1.3 released +

    +Made following changes: +

      +
    • Major code refactoring; reduced use of threads, added event queue +
    • Removed unused HTTP support code +
    • No longer interfering with mouse when scroll lock is toggled on +
    • Fixed minor mispositioning of mouse on win32 +
    • Unix portability fixes +
    • Added support for power management +
    • Improved keyboard handling and bug fixes +
    • Fixed dead key handling +
    +

    +Note: the tray icon on windows is known to not work correctly when +running the synergy server on Windows 95/95/Me. +

    +Aug-24-2003 - Synergy 1.0.14 released +

    +Made following changes: +

      +
    • Fixed bugs in setting win32 process/thread priority +
    • Fixed resource leak in opening win32 system log +
    • Fixed win32 launcher not getting non-default advanced options +
    • Synergy log copied to clipboard now transferred to other screens +
    • Hack to work around lesstif clipboard removed (fixes pasting on X) +
    +

    +Jul-20-2003 - Synergy 1.0.12 released +

    +Made following changes: +

    +This release finally completes support for non-ASCII characters, +fully supporting most (all?) European keyboard layouts including +dead key composition. This release includes changes from several +experimental versions (1.0.9, 1.0.11, 1.1.0, 1.1.1, 1.1.2, and +1.1.3). +

    +Made following changes: +

      +
    • Added non-ASCII support to win32 and X11 +
    • Added dead key support to win32 and X11 +
    • Fixed AltGr handling +
    • Added ctrl+alt+del simulation using ctrl+alt+pause +
    • Fixed loss of key event when user releases ctrl+alt+del +
    • Fixed incorrect synthesis of pointer-keys event on X11 +
    • Fixed Xinerama support +
    • Made some clipboard fixes on win32 and X11 +
    • Add tray icon menu item to copy log to clipboard +
    • Fixed mouse warping on unconnected client +
    • Stopped unconnected client from filling up event logs +
    +

    +May-10-2003 - Synergy 1.0.8 released +

    +Made following changes: +

    +

      +
    • Fixed hook forwarding (fixing interaction with objectbar) +
    • Fixed "Windows" key handling and added support Win+E, Win+F, etc +
    • Added win 95/98/me support for Alt+Tab, Alt+Esc, Ctrl+Esc +
    • Fixed scroll lock locking to server screen +
    • Fixed screen flashing on X11 and Windows +
    • Fixed compile problem on 64 bit systems +
    • Fixed Xinerama support +
    • Now allowing screen names that include underscores +
    • Improved non-ASCII key handling on Windows +
    • Fixed lagginess +
    • Fixed failure to capture all mouse input on Windows +
    • Fixed auto-repeat bugs on X11 +
    • Added option to disable screen saver synchronization +
    • Added support for 4th and 5th mouse buttons on Windows +
    • Added support for "Internet" and "Multimedia" keys +
    • Fixed jumping from client to itself (mouse wrapping) +
    +

    +Apr-26-2003 - Added roadmap +

    +There's now a roadmap for Synergy +describing the plans for further development. +

    +Apr-26-2003 - Added Paypal donation page +

    +There's now a donate button for those +who'd like to make a monetary contribution to the further +development of Synergy. +

    +Apr-26-2003 - Development update +

    +Synergy 1.0.8 will include fixes for the following problems. +These are already fixed and some are in development version 1.0.7. +

    +

      +
    • Mouse events at edge of screen are stolen +
    • Windows key doesn't work on clients +
    • Alt+[Shift+]Tab, Alt+[Shift+]Esc, Ctrl+Esc don't work on Win 95/98/Me +
    • Scroll lock doesn't lock to Windows server screen +
    • Screen flashes every 5 seconds on some X11 systems +
    • Synergy doesn't work properly with Xinerama +
    • Screen names with underscores are not allowed +
    +

    +Synergy 1.0.8 will probably include fixes for these problems: +

    +

      +
    • AltGr/Mode_switch doesn't work +
    • Non-ASCII keys aren't supported +
    • Synergy performs badly on a busy Windows system +
    • Unexpected key repeats on X11 clients +
    +

    +Synergy 1.0.8 should be available in the first half of May. +

    +Mar-27-2003 - Synergy 1.0.6 released +

    +Made following changes: +

    +

      +
    • Added tray icon on win32 +
    • Fixed multi-monitor support on win32 +
    • Fixed win32 screen saver detection on NT/2k/XP +
    • Added per-screen options to remap modifier keys +
    • Added global options for restricting screen jumping +
    • Added global option for detecting unresponsive clients +
    • Added more logging for why screen jump won't happen +
    • Fixed problem sending the CLIPBOARD to motif/lesstif apps +
    • Win32 launcher now remembers non-config-file state +
    +

    +In addition, the version number scheme has been changed. Given a +version number X.Y.Z, release versions will always have Y and Z +even while development versions will have Y and Z odd. +

    +Mar-27-2003 - Synergy featured in Linux Journal. +

    +The April 2003 issue of Linux Journal includes an article on Synergy. +Written by Chris Schoeneman, it describes configuring synergy between +two linux systems. +

    +Mar-27-2003 - Contributions to Synergy. +

    +Many thanks to Girard Thibaut for providing a version of the win32 +launch dialog translated into French. I hope to integrate these +changes into future releases. +

    +Thanks also to "wrhodes" who provided source files for +building an InstallShield installer for Synergy. They'll be +integrated into an upcoming release. +

    +Feb-18-2003 - Synergy 1.0.3 released +

    +Made following changes: +

    +

      +
    • Added support for X11 keymaps with only uppercase letters +
    • Fixed memory leaks +
    • Added documentation on using synergy with SSH +
    • Fixed unnecessary left-handed mouse button swapping +
    • Fixed debug build error on win32 +
    • Reduced frequency of large cursor jumps when leaving win32 server +
    • Changed cursor motion on win32 multimon to relative moves only +
    +

    +Jan-25-2003 - Synergy 1.0.2 released +

    +Made following changes: +

    +

      +
    • Fixed out-of-bounds array lookup in the BSD and Windows network code +
    • Added ability to set screen options from Windows launch dialog +
    +

    +Jan-22-2003 - Synergy 1.0.1 released +

    +Made following changes: +

    +

      +
    • Fixed running as a service on Windows NT family +
    +

    +Jan-20-2003 - Synergy 1.0.0 released +

    +Made following changes: +

    +

      +
    • Refactored to centralize platform dependent code +
    • Added support for mouse wheel on Windows NT (SP3 and up) +
    • Portability improvements +
    • Added more documentation +
    • Fixes for working with xscreensaver +
    • Fixes for circular screen links +
    +

    +This release has been tested on Linux and Windows. It builds and +is believed to run on Solaris and FreeBSD. It is believed to +build and run on Irix and AIX. It builds but does not work on +MacOS X. +

    +Dec-25-2002 - Synergy 0.9.14 released +

    +Made following changes: +

    +

      +
    • Fixed solaris compile problems (untested) +
    • Fixed irix compile problems (untested) +
    • Fixed windows client not reconnecting when server dies bug +
    • Fixed loss of ctrl+alt from windows server to non-windows clients +
    • Fixed handling of password protected windows client screen saver +
    • Now handling any number of pointer buttons on X11 +
    • Toggle key states now restored when leaving clients +
    • Added support for per-screen config options +
    • Added config options for half-duplex toggle keys on X11 +
    • Enabled class diagrams in doxygen documentation +
    +

    +Nov-05-2002 - Synergy 0.9.13 released +

    +Made following changes: +

    +

      +
    • Fixed solaris compile problems (untested) +
    • Fixed MacOS X compile problems (semi-functional) +
    • Fixed gcc-3.2 compile problems +
    • Fixed some thread startup and shutdown bugs +
    • Server now quits if bind() fails with an error other than in use +
    • Fixed bug in moving mouse on Win98 without multiple monitors +
    • Fixed bug in handling TCP socket errors on read and write +
    • Fixed spurious screen saver activation on X11 +
    • Unix platforms can now read Win32 configuration files +
    • Minor error reporting fixes +
    +

    +Sep-14-2002 - Synergy 0.9.12 released +

    +Made following changes: +

    +

      +
    • Win32 was not reporting log messages properly when run from synergy.exe +
    • Network error messages weren't reporting useful information +
    • Synergy won't build on gcc 3.2; added workaround for known problem +
    • X11 wasn't handling some keys/key combinations correctly +
    • Added option to change logging level when testing from synergy.exe +
    +

    +Sep-04-2002 - Synergy 0.9.11 released +

    +Fixed following bugs: +

    +

      +
    • Worked around missing SendInput() on windows 95/NT 4 prior to SP3 +
    • Fixed keyboard mapping on X11 synergy client +
    +

    +Sep-02-2002 - Synergy 0.9.10 released +

    +Fixed following bugs: +

    +

      +
    • The Pause/Break and keypad Enter buttons were not working correctly on windows +
    • Configuration options were being lost on windows after a reboot +
    • Added support for AltGr/ModeSwitch keys +
    • Added support for auto-start on windows when not administrator +
    • Improved autoconf +
    • Added workaround for lack of sstream header on g++ 2.95. +
    +

    +Aug-18-2002 - Synergy 0.9.9 released +

    +Fixed three bugs: +

    +

      +
    • The PrintScrn button was not working correctly on windows +
    • The Win32 server could hang when a client disconnected +
    • Using the mouse wheel could hang the X server +
    +

    +Aug-11-2002 - Synergy 0.9.8 released +

    +Supports any number of clients under Linux or Windows 95 or NT4 +or later. Includes mouse and keyboard sharing, clipboard +synchronization and screen saver synchronization. Supports ASCII +keystrokes, 5 button mouse with wheel, and Unicode text clipboard +format. +

    + + + diff --git a/doc/roadmap.html b/doc/roadmap.html new file mode 100644 index 00000000..7f8681bc --- /dev/null +++ b/doc/roadmap.html @@ -0,0 +1,92 @@ + + + + + + + + Synergy Roadmap + + +

    +

    Synergy Roadmap

    +

    +This page describes the planned development of Synergy. There are +no dates or deadlines. Instead, you'll find the features to come +and the rough order they'll arrive. +

    +

    Short term

    +

    +Synergy should work seamlessly. When it works correctly, it works +transparently so you don't even think about it. When it breaks, +you're forced out of the illusion of a unified desktop. The first +priority is fixing those bugs that break the illusion. +

    +Some of these bugs are pretty minor and some people would rather +have new features first. But I'd rather fix the current +foundation before building on it. That's not to say features +won't get added until after bug fixes; sometimes it's just too +tempting to code up a feature. +

    +The highest priority feature is currently splitting synergy into +front-ends and a back-end. The back-end does the real work. The +front-ends are console, GUI, or background applications that +communicate with the back-end, either controlling it or receiving +notifications from it. +

    +On win32, there'd be a front-end for the tray icon and a dialog to +start, stop, and control the back-end. OS X and X11 would have +similar front-ends. Splitting out the front-end has the added +benefit on X11 of keeping the back-end totally independent of +choice of GUI toolkit (KDE, Gnome, etc.) +

    +One can also imagine a front-end that does nothing but put monitors +into power-saving mode when the cursor is not on them. If you have +one monitor auto-senses two inputs, this would automatically switch +the display when you move the cursor to one screen or another. +

    +

    Medium term

    +

    +Some features fit well into Synergy's current design and may simply +enhance it's current capabilities. +

    +

      +
    • Configurable hot key to pop up a screen switch menu +
    • Configure screen saver synchronization on or off +
    • Graphical interface configuration and control on all platforms +
    • Graphical status feedback on all platforms +
    • More supported clipboard formats (particularly rich text) +
    +

    +A popup menu would be new for Synergy, which currently doesn't have +to do any drawing. That opens up many possibilities. Ideally, +front-ends request hot keys from the back-end and then tell the back +end what to do when they're invoked. This keeps the back-end +independent of the user interface. +

    +

    Long term

    +

    +Two features stand out as long term goals: +

    +

      +
    • Support N computers on +M monitors +
    • Drag and drop across computers +
    +

    +The first feature means sharing a monitor or monitors the way the +keyboard and mouse are shared. With this, Synergy would be a full +KVM solution. Not only would it support a few computers sharing +one screen (still using the mouse to roll from one screen to +another), but it should also support dozens of computers to provide +a solution for server farm administrators. In this capacity, it +may need to support text (as opposed to bitmap graphics) screens. +

    +The second feature would enhance the unified desktop illusion. It +would make it possible to drag a file and possibly other objects +to another screen. The object would be copied (or moved). I expect +this to be a very tricky feature. +

    + + + diff --git a/doc/running.html b/doc/running.html new file mode 100644 index 00000000..4ad2d594 --- /dev/null +++ b/doc/running.html @@ -0,0 +1,394 @@ + + + + + + + + Synergy User Guide + + +

    +

    Running Synergy

    +

    +Synergy lets you use one keyboard and mouse across multiple computers. +To do so it requires that all the computers are connected to each other +via TCP/IP networking. Most systems come with this installed. +

    +

    Step 1 - Choose a server

    +

    +The first step is to pick which keyboard and mouse you want to share. +The computer with that keyboard and mouse is called the "primary +screen" and it runs the synergy server. All of the other computers +are "secondary screens" and run the synergy client. +

    +

    Step 2 - Install the software

    +

    +Second, you install the software. Choose the appropriate package +and install it. For example, on Windows you would run +SynergyInstaller. You must install the +software on all the computers that will share the mouse and keyboard +(clients and server). On OS X you'll just have a folder with some +documentation and two programs. You can put this folder anywhere. +

    +

    Step 3 - Configure and start the server

    +

    +Next you configure the server. You'll tell synergy the name of +the primary and secondary screens, which screens are next to which, +and choose desired options. On Windows there's a dialog box for +setting the configuration. On other systems you'll create a simple +text file. +

    + +Note that when you tell synergy that screen A +is to the left of screen B this does not +imply that B is to the right of +A. You must explicitly indicate both +relations. If you don't do both then when you're running synergy you'll +find you're unable to leave one of the screens. +

    +Windows
    +On Windows run synergy by double clicking on the +synergy file. This brings up a dialog. +Configure the server: +

      +
    • Click the Share this computer's keyboard and mouse (server) radio button +
    • Click the Screens & Links Configure... button +
    • Click the + button to add the server to the + Screens list +
        +
      • Enter the name of server (the computer's name is the recommended name) +
      • Optionally enter other names the server is known by +
      • Click OK +
      +
    • Use the + button to add your other computers +
        +
      • Using a computer's name as its screen name is recommended +
      • Choose desired screen options on the Add Screen dialog +
      +
    • Use the controls under Links to link screens together +
        +
      • Click (once) on the server's name in the Screens list +
      • Choose the screen to the left of the server; use --- + if there is no screen to the left of the server +
      • Choose the screens to the right, above and below the server +
      • Repeat the above steps for all the other screens +
      +
    • Click OK to close the Screens & Links dialog +
    • Use Options... to set desired options +
    • If the server's screen name is not the server's computer name: +
        +
      • Click Advanced... +
      • Enter the server's screen name next to + Screen Name +
      • Click OK +
      +
    +

    +Now click Test. The server will start and +you'll see a console window with log messages telling you about synergy's +progress. If an error occurs you'll get one or more dialog boxes telling +you what the errors are; read the errors to determine the problem then +correct them and try Test again. See Step 5 +for typical errors. +

    +Unix or Mac OS X
    +Create a text file named synergy.conf with the +following: +

    +    section: screens
    +       screen1:
    +       screen2:
    +    end
    +    section: links
    +       screen1:
    +           right = screen2
    +       screen2:
    +           left = screen1
    +    end
    +
    +Replace each occurrence of screen1 with the host name +of the primary screen computer (as reported by the +hostname program) and screen2 +with the host name of a secondary screen computer. In the above example, +screen2 is to the right of +screen1 and screen1 is to the +left of screen2. If necessary you should replace +right and left with +left, right, +up, or down. If you +have more than two computers you can add those too: add each computer's host +name in the screens section and add the +appropriate links. See the configuration +guide for more configuration possibilities. +

    +Now start the server. Normally synergy wants to run "in the background." +It detaches from the terminal and doesn't have a visible window, effectively +disappearing from view. Until you're sure your configuration works, you +should start synergy "in the foreground" using the -f +command line option. +

    +On unix type the command below in a shell. If synergys is not in your +PATH then use the full pathname. +

    +    synergys -f --config synergy.conf
    +
    +On OS X open Terminal in the Utilities folder in the Applications folder. +Drag the synergys program from the synergy folder onto the Terminal window. +The path to the synergys program will appear. Add the following to the +same line, type a space at the end of the line but don't press enter: +
    +    -f --config 
    +
    +Now drag the synergy.conf file onto the Terminal window and press enter. +Check the reported messages for errors. Use ctrl+c to stop synergy if +it didn't stop automatically, correct any problems, and start it again. +

    +

    Step 4 - Start the clients

    +

    +Next you start the client on each computer that will share the server's +keyboard and mouse. +

    +Windows
    +On Windows run synergy by double clicking on the +synergy file. This brings up a dialog. +Configure the client: +

      +
    • Click the Use another computer's shared keyboard and mouse (client) radio button +
    • Enter the server's computer name next to Other Computer's Host Name +
        +
      • This is not the server's screen name, unless you made that the + server's host name as recommended +
      +
    • If the client's screen name is not the client's computer name: +
        +
      • Click Advanced... +
      • Enter the client's screen name next to Screen Name +
      • Click OK +
      +
    +

    +Now click Test. +

    +Unix or Mac OS X
    +To start a client on unix, enter the following: +

    +    synergyc -f server-host-name
    +
    +where server-host-name is replaced by the host +name of the computer running the synergy server. If synergyc is not in +your PATH then use the full pathname. +

    +On OS X open Terminal in the Utilities folder in the Applications folder. +Drag the synergyc program from the synergy folder onto the Terminal window. +The path to the synergys program will appear. Add the following to the +same line and press enter: +

    +    -f server-host-name
    +
    +

    +When you added the client to the server's configuration you chose a +name for the client. If that name was not client's host name then +you must tell the client the name you used. Instead of the above +command use this instead: +

    +    synergyc -f --name name server-host-name
    +
    +where name is the name for the client in +the server's configuration. (On OS X drag the synergyc program to the +Terminal window rather than typing synergyc.) +

    +

    Step 5 - Test

    +

    +Clients should immediately report a successful connection or one or +more error messages. Some typical problems and possible solutions are +below. See the troubleshooting and the +FAQ pages for more help. +

      +
    • failed to open screen (X11 only) +

      + Check permission to open the X display;
      + check that the DISPLAY environment variable is set
      + use the --display command line option. +

      +

    • address already in use +

      + Another program (maybe another copy of synergy) is using the synergy port; + stop the other program or choose a different port in the + Advanced... dialog. If you change the port + you must make the same change on all of the clients, too. +

      +

    • connection forcefully rejected +

      + The synergy client successfully contacted the server but synergy wasn't + running or it's running on a different port. You may also see this if + there's a firewall blocking the host or port. Make sure synergy is + running on the server and check for a firewall. +

      +

    • already connected +

      + Check that the synergy client isn't already running. +

      +

    • refused client +

      + Add the client to the server's configuration file. +

      +

    • connection timed out +

      + Check that server-host-name is correct.
      + Check that you don't have a firewall blocking the server or synergy port. +

      +

    • connection failed +

      + Check that server-host-name is correct. +

      +

    +If you get the error "Xlib: No protocol specified" +you're probably running synergy as root while logged in as another user. +X11 may prevent this for security reasons. Either run synergy as the same +user that's logged in or (not recommended) use +"xhost +" to allow anyone to connect +to the display. +

    +When successful you should be able to move the mouse off the appropriate +edges of your server's screen and have it appear on a client screen. +Try to move the mouse to each screen and check all the configured links. +Check the mouse buttons and wheel and try the keyboard on each client. +You can also cut-and-paste text, HTML, and images across computers (HTML +and images are not supported on OS X yet). +

    +

    Step 6 - Run

    +

    +Once everything works correctly, stop all the clients then the server. +Then start the server with the Start button +on Windows and without the -f option on Unix +and Mac OS X. Finally start the clients similarly. On Windows before +clicking Start you may want to set the +Logging Level to +Warning so the logging window doesn't pop +up (because you currently can't close it, just minimize it). +

    +You can also configure synergy to start automatically when your computer +starts or when you log in. See the autostart +guide for more information. +

    +

    Command Line Options Guide

    +

    +Common Command Line Options
    +The following options are supported by synergys +and synergyc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     -d,--debug level  use debugging level level
     --daemonrun as a daemon (Unix) or background (Windows)
     -f,--no-daemonrun in the foreground
      --display display  connect to X server at display (X11 only)
     -n,--name nameuse name instead of the hostname
     --restartautomatically restart on failures
     -1,--no-restartdo not restart on failure
     -h,--helpprint help and exit
     --versionprint version information and exit
    +

    +Debug levels are from highest to lowest: FATAL, +ERROR, WARNING, +NOTE, INFO, +DEBUG, DEBUG1, and +DEBUG2. Only messages at or above the given +level are logged. Messages are logged to a terminal window when +running in the foreground. Unix logs messages to syslog when running +as a daemon. The Windows NT family logs messages to the event log +when running as a service. The Windows 95 family shows FATAL log +messages in a message box and others in a terminal window when running +as a service. +

    +The --name option lets the client or server +use a name other than its hostname for its screen. This name is used +when checking the configuration. +

    +Neither the client nor server will automatically restart if an error +occurs that is sure to happen every time. For example, the server +will exit immediately if it can't find itself in the configuration. +On X11 both the client and server will also terminate if the +connection to the X server is lost (usually because it died). +

    +Server Command Line Options
    +

    +

    +    synergys [options]
    +
    +The server accepts the common options and: +

    + + + + + + + + + + + +
     -a,--address address  listen for connections on address address
     -c,--config pathname  read configuration from pathname
    +

    +address has one of the following forms: +

    +    hostname
    +    :port
    +    hostname:port
    +
    +hostname is a hostname or IP address of a network +interface on the server system (e.g. somehost +or 192.168.1.100). port +is a port number from 1 to 65535. hostname defaults to +the system's hostname and port defaults to 24800. +

    +Client Command Line Options
    +

    +

    +    synergyc [options] address[:port]
    +
    +address is the hostname or IP address of +the server and port is the optional network +port on the server to connect to. The client accepts the +common options. +

    + + + diff --git a/doc/security.html b/doc/security.html new file mode 100644 index 00000000..c8013c27 --- /dev/null +++ b/doc/security.html @@ -0,0 +1,55 @@ + + + + + + + + Synergy Network Security Guide + + +

    +

    Authentication and Encryption

    +Synergy does not do any authentication or encryption. Any computer +can connect to the synergy server if it provides a screen name known +to the server, and all data is transferred between the server and the +clients unencrypted which means that anyone can, say, extract the +key presses used to type a password. Therefore, synergy should not +be used on untrusted networks. +

    +However, there are tools that can add authentication and encryption +to synergy without modifying either those tools or synergy. One +such tool is SSH (which stands for secure shell). A free implementation +of SSH is called OpenSSH and runs +on Linux, many Unixes, and Windows (in combination with +Cygwin). +

    +

    Configuring the Server

    +Install the OpenSSH server on the same computer as the synergy server. +Configure the OpenSSH server as usual (synergy doesn't demand any +special options in OpenSSH) and start it. Start the synergy server as +usual; the synergy server requires no special options to work with +OpenSSH. +

    +

    Configuring the Clients

    +Install the OpenSSH client on each synergy client computer. Then, on +each client, start the OpenSSH client using port forwarding: +

    +  ssh -f -N -L 24800:server-hostname:24800 server-hostname
    +
    +The server-hostname is the name or address +of the computer with the OpenSSH and synergy servers. +The 24800 is the default network port used by synergy; if you use +a different port then replace both instances of 24800 with the port +number that you use. Finally, start the synergy client normally +except use localhost as the server host +name. For example: +
    +  synergyc -f localhost
    +
    +Synergy will then run normally except all communication is passed +through OpenSSH which decrypts/encrypts it on behalf of synergy. +

    + + + diff --git a/doc/synergy.css b/doc/synergy.css new file mode 100644 index 00000000..f020aa59 --- /dev/null +++ b/doc/synergy.css @@ -0,0 +1,166 @@ +body { + font-family: arial, helvetica, sans-serif; + font-size: small; + font-weight: normal; + margin-left: 0in; + margin-right: 0in; +} + +/* show underline on light blue links only on hover */ +a { + text-decoration: none; + color: #6699ff; +} +a:hover { + text-decoration: underline; +} + +/* heading */ +h3 { + display: block; + margin-top: 0em; + margin-bottom: 1.25em; + font-weight: bold; + font-variant: small-caps; + font-size: 125%; +} + +/* subheading */ +h4 { + display: block; + margin-top: 0em; + margin-bottom: 1em; + font-weight: bold; + font-variant: small-caps; + font-size: 100%; +} + +/* emphasis */ +b { + font-weight: bold; +} + +/* formatted code */ +pre { + display: block; + white-space: pre; + font-family: courier new; + font-size: 87.5%; +} + +.banner { + font-weight: normal; + font-variant: small-caps; + font-size: 400%; + width: 100%; + padding: 0px; + margin: 0px; + border: 0px; +} +.banner a { + color: #000000; +} +.banner a:hover { + text-decoration: none; + color: #000000; +} +.bannerb { + color: #ffffff; + background-color: #ffffff; + width: 100%; + height: 1px; + padding: 0px; + margin: 0px; + border-bottom: solid #6699ff 1px; +} + +.nav { + font-size: x-small; + font-weight: normal; + background-color: #d4d4d4; + + padding: 2px 0px 2px 0px; + margin: 0px; + border-bottom: solid #d4d4d4 300px; +} +.nav a:hover { + text-decoration: none; + color: #666666; +} +.nav td { + padding-right: 20px; + padding-left: 5px; + text-indent: 1em; +} +.nav .section { + width: 120px; + text-indent: 0em; + border-top: 0px; + border-left: 0px; + border-right: 0px; + border-bottom: solid #aaaaaa 1px; + padding-bottom: 0px; + font-weight: bold; + color: #777777; +} + +.main { + font-size: small; + font-weight: normal; + margin-left: 0.1in; + margin-right: 0.25in; +} + +.main table { + font-size: small; + font-weight: normal; + margin-left: 0.1in; + margin-right: 0.25in; +} + +.date { + font-weight: bold; +} + +.arg { + font-style: italic; + font-family: courier new; +} + +.userinput { + display: block; + white-space: pre; + font-family: courier new; + font-size: 87.5%; + font-weight: bold; +} + +.code { + font-family: courier new; +} + +.code table { + font-size: small; +} + +/* block of code */ +.codeblock { + display: block; + white-space: pre; + font-family: courier new; + font-size: 87.5%; + border: 1px solid #000000; + padding: 1em; + padding-top: 0em; + margin: 1em; + background-color: #cccccc; + color: #000000; +} + +.fakelink { + color: #6699ff; +} + +.hide { + display:none +} diff --git a/doc/tips.html b/doc/tips.html new file mode 100644 index 00000000..9f8e9e24 --- /dev/null +++ b/doc/tips.html @@ -0,0 +1,81 @@ + + + + + + + + Synergy Tips and Tricks + + +

    +

    Tips and Tricks

    +

      +
    • + Be aware that not all keystrokes can be handled by synergy. In + particular, ctrl+alt+del is not handled. However, synergy can + convert ctrl+alt+pause into ctrl+alt+del on the client side. + (Synergy must be configured to autostart when the computer starts + on the client for this to work on the Windows NT family.) Some + non-standard keys may not work, especially "multimedia" buttons, + though several are correctly handled. +

      +

    • + A screen can be its own neighbor. That allows a screen to "wrap". + For example, if a configuration linked the left and right sides of + a screen to itself then moving off the left of the screen would put + the mouse at the right of the screen and vice versa. +

      +

    • + You cannot switch screens when the Scroll Lock is toggled on. Use + this to prevent unintentional switching. You can configure other + hot keys to do this instead; see + lockCursorToScreen. +

      +

    • + Turn off mouse driven virtual desktop switching on X windows. It + will interfere with synergy. Use keyboard shortcuts instead. +

      +

    • + Synergy's screen saver synchronization works best with xscreensaver + under X windows. Synergy works better with xscreensaver if it is + using one of the screen saver extensions. Prior to xscreensaver 4.0 + you can use -mit-extension, + -sgi-extension, or + -xidle-extension + command line options to enable an extension (assuming your server has + the extension). Starting with 4.0 you must enable the corresponding + option in your .xscreensaver file. +

      +

    • + Synergy automatically converts newlines in clipboard text (Unix + expects \n to end each line while Windows + expects \r\n). +

      +

    • + Clients can be started and stopped at any time. When a screen is + not connected, the mouse will jump over that screen as if the mouse + had moved all the way across it and jumped to the next screen. +

      +

    • + A client's keyboard and mouse are fully functional while synergy is + running. You can use them in case synergy locks up. +

      +

    • + Strong authentication and encryption is available by using SSH. See + the security guide for more information. + Synergy does not otherwise provide secure communications and it should + not be used on or over untrusted networks. +

      +

    • + Synergy doesn't work if a 16-bit Windows application has the focus + on Windows 95/98/Me. This is due to limitations of Windows. One + commonly used 16-bit application is the command prompt + (command.exe) + and this includes synergy's log window when running in test mode. +

      +

    +

    + + + diff --git a/doc/toc.html b/doc/toc.html new file mode 100644 index 00000000..3c43bd7c --- /dev/null +++ b/doc/toc.html @@ -0,0 +1,43 @@ + + + + + + + + Synergy TOC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/todo.html b/doc/todo.html new file mode 100644 index 00000000..02a12a07 --- /dev/null +++ b/doc/todo.html @@ -0,0 +1,70 @@ + + + + + Synergy To Do List + + +

    Synergy To Do List

    +

    +This page describes the planned development of Synergy. There are +no dates or deadlines. Instead, you'll find the features to come +and the rough order they can be expected to arrive. +

    + +

    Short term

    +

    +Synergy should work seamlessly. When it works correctly, it works +transparently so you don't even think about it. When it breaks, +you're forced out of the illusion of a unified desktop. The first +priority is fixing those bugs that break the illusion. +

    +

    +Some of these bugs are pretty minor and some people would rather +have new features first. But I'd rather fix the current +foundation before building on it. That's not to say features +won't get added until after bug fixes; sometimes it's just too +tempting to code up a feature. +

    + +

    Medium term

    +

    +Some features fit well into Synergy's current design and may simply +enhance it's current capabilities. +

      +
    • Configurable hot key screen switching +
    • Configurable hot key to lock to a screen +
    • Configurable hot key to pop up a screen switch menu +
    • Configure screen saver synchronization on or off +
    • Graphical interface configuration and control on all platforms +
    • Graphical status feedback on all platforms +
    • More supported clipboard formats (particularly rich text) +
    +

    + +

    Long term

    +

    +Two features stand out as long term goals: +

      +
    • Support N computers on +M monitors +
    • Drag and drop across computers +
    +

    +

    +The first feature means sharing a monitor or monitors the way the +keyboard and mouse are shared. With this, Synergy would be a full +KVM solution. Not only would it support a few computers sharing +one screen (still using the mouse to roll from one screen to +another), but it should also support dozens of computers to provide +a solution for server farm administrators. In this capacity, it +may need to support text (as opposed to bitmap graphics) screens. +

    +

    +The second feature would enhance the unified desktop illusion. It +would make it possible to drag a file and possibly other objects +to another screen. The object would be copied (or moved). I expect +this to be a very tricky feature. +

    + + diff --git a/doc/trouble.html b/doc/trouble.html new file mode 100644 index 00000000..f2923a5b --- /dev/null +++ b/doc/trouble.html @@ -0,0 +1,204 @@ + + + + + + + + Synergy Troubleshooting + + +

    +

    Synergy Troubleshooting

    +

    Problems

    +
      +
    1. Cannot read configuration +
    2. Connection forcefully rejected +
    3. Connection timed out +
    4. Cannot listen for clients +
    5. Unknown screen name "XXX" +
    6. Server refused client with name "XXX" +
      A client with name "XXX" is not in the map +
    7. Server already has a connected client with name "XXX" +
      A client with name "XXX" is already connected +
    8. Server has incompatible version +
    9. The cursor goes to secondary screen but won't come back +
    +

    Solutions

    +
      +
    1. Cannot read configuration +

      +There's an error in the configuration file. This error is always +accompanied by another message describing the problem. Use that +message and the configuration documentation +to determine the fix. +

      +
    2. Connection forcefully rejected +

      +The client was able to contact the server computer but the server was +not listening for clients. Possible reasons are: +

      +
        +
      • The client is using the wrong server +

        +Make sure the client is using the hostname or IP address of the computer +running the synergy server. +

        +
      • Synergy isn't running on the server +

        +Make sure the synergy server is running on the server computer. Make +sure the server is ready to accept connections. If another program is +using synergy's port (24800 by default) then synergy can't start unless +you specify a different port. +

        +
      • The client is using the wrong port +

        +Synergy uses port 24800 by default but you can specify a different port. +If you do use a different port you must use that port on the server and +all clients. +

        +
      +
    3. Connection timed out +

      +The most likely reasons for this are: +

      +
        +
      • A firewall +

        +A firewall is a program or device that deliberately blocks network +connections for security reasons. Typically, they'll silently drop +packets they don't want rather than sending a rejection to the sender. +This makes it more difficult for intruders to break in. +

        +When synergy traffic hits a firewall and gets dropped, eventually the +synergy client will give up waiting for a response and time out. To +allow synergy traffic through first find all the firewalls on the +network between and on the synergy client and server computers. +

        +A firewall on the server or any network device between the server and +any client should allow packets to TCP port 24800. (Port 24800 is the +default; use whichever port you've selected.) You'll have to consult +the manual for your operating system, device, or firewall software to +find out how to do this. +

        +Usually you'll won't need to adjust a firewall on client machines. +That's because firewalls normally allow incoming traffic on any port +they've initiated a connection on. The reasoning is, of course, if +you started a conversation you probably want to hear the reply. +

        +
      • The network is down or busy +

        +Correct the network problem and try again. You might try +ping to see if the two computers can see +each other on the network. +

        +
      • The server is frozen +

        +If the synergy server is running but locked up or very busy then the +client may get this message. If the server is locked up then you'll +probably have to restart it. If it's just very busy then the client +should successfully connect automatically once the server settles down. +

        +
      +
    4. Cannot listen for clients +

      +Synergy tried to start listening for clients but the network port is +unavailable for some reason. Typical reasons are: +

      +
        +
      • No network devices +

        +You must have a TCP/IP network device installed and enabled to use +synergy. +

        +
      • A synergy server is already running +

        +Check that a synergy server isn't already running. +

        +
      • Another program is using synergy's port +

        +Only one program at a time can listen for connections on a given port. +If the specific error is that the address is already in use and you've +ruled out the other causes, then it's likely another program is already +using synergy's port. By default synergy uses port 24800. Try having +synergy use a different port number, like 24801 or 24900. Note that +the server and all clients must use the same port number. Alternatively, +find the other program and stop it or have it use another port. +

        +
      +
    5. Unknown screen name "XXX" +

      +This error can be reported when reading the configuration; see +cannot read configuration. If the configuration +was read successfully and you get this error then it means that the +server's screen is not in the configuration. All screens must be listed +in the configuration. +

      +A common reason for this is when you haven't used the system's hostname +as its screen name. By default, synergy uses the hostname as the screen +name. If you used a different screen name in the configuration then you +must tell synergy what that name is. Let's say the hostname is +frederick but the configuration defines a screen +named fred. Then you must tell the server +that its screen name is fred by using the +--name fred command line option or setting +the screen name in the advanced options dialog to +fred. +

      +Alternatively, you can specify one name as an alias of another. See +the configuration documentation +for details. +

      +Another common reason for this is a mismatch between what you think the +hostname is and what synergy thinks it is. Typically this is a problem +with fully qualified domain names (FQDN). Perhaps you think your system +is named fred but synergy thinks it's +fred.nowhere.com or +fred.local. You can use either solution above +to fix this. +

      +
    6. Server refused client with name "XXX" +
      A client with name "XXX" is not in the map +

      +The client is using a screen name not in the server's configuration. +This is essentially the same problem as Unknown +screen name "XXX" and has the same solutions: specify another +screen name or add an alias. +

      +
    7. Server already has a connected client with name "XXX" +
      A client with name "XXX" is already connected +

      +This happens when: +

      +
        +
      • Two clients try use the same screen name +

        +Each client must have a unique screen name. Configure at least one +client to use a different screen name. +

        +
      • One client reconnects without cleanly disconnecting +

        +It's possible for a client to disconnect without the server knowing, +usually by being disconnected from the network or possibly by going +to sleep or even crashing. The server is left thinking the client is +still connected so when the client reconnects the server will think +this is a different client using the same name. Synergy will usually +detect and correct this problem within a few seconds. If it doesn't +then restart the server. +

        +
      +
    8. Server has incompatible version +

      +You're using different versions of synergy on the client and server. +You should use the same version on all systems. +

      +
    9. The cursor goes to secondary screen but won't come back +

      +This is FAQ #17 and is also mentioned in +the documentation for using synergy +and configuration. +

      +
    + + + diff --git a/examples/synergy.conf b/examples/synergy.conf new file mode 100644 index 00000000..2586dfaf --- /dev/null +++ b/examples/synergy.conf @@ -0,0 +1,37 @@ +# sample synergy configuration file +# +# comments begin with the # character and continue to the end of +# line. comments may appear anywhere the syntax permits. + +section: screens + # three hosts named: moe, larry, and curly + moe: + larry: + curly: +end + +section: links + # larry is to the right of moe and curly is above moe + moe: + right = larry + up = curly + + # moe is to the left of larry and curly is above larry. + # note that curly is above both moe and larry and moe + # and larry have a symmetric connection (they're in + # opposite directions of each other). + larry: + left = moe + up = curly + + # larry is below curly. if you move up from moe and then + # down, you'll end up on larry. + curly: + down = larry +end + +section: aliases + # curly is also known as shemp + curly: + shemp +end diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 00000000..2a57133c --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,34 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +SUBDIRS = \ + common \ + arch \ + base \ + mt \ + io \ + net \ + synergy \ + platform \ + client \ + server \ + $(NULL) + +EXTRA_DIST = \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp new file mode 100644 index 00000000..80c613ab --- /dev/null +++ b/lib/arch/CArch.cpp @@ -0,0 +1,639 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "common.h" +#include "CArch.h" + +#undef ARCH_CONSOLE +#undef ARCH_DAEMON +#undef ARCH_FILE +#undef ARCH_LOG +#undef ARCH_MULTITHREAD +#undef ARCH_NETWORK +#undef ARCH_SLEEP +#undef ARCH_STRING +#undef ARCH_SYSTEM +#undef ARCH_TASKBAR +#undef ARCH_TIME + +// include appropriate architecture implementation +#if SYSAPI_WIN32 +# include "CArchConsoleWindows.h" +# include "CArchDaemonWindows.h" +# include "CArchFileWindows.h" +# include "CArchLogWindows.h" +# include "CArchMiscWindows.h" +# include "CArchMultithreadWindows.h" +# include "CArchNetworkWinsock.h" +# include "CArchSleepWindows.h" +# include "CArchStringWindows.h" +# include "CArchSystemWindows.h" +# include "CArchTaskBarWindows.h" +# include "CArchTimeWindows.h" +#elif SYSAPI_UNIX +# include "CArchConsoleUnix.h" +# include "CArchDaemonUnix.h" +# include "CArchFileUnix.h" +# include "CArchLogUnix.h" +# if HAVE_PTHREAD +# include "CArchMultithreadPosix.h" +# endif +# include "CArchNetworkBSD.h" +# include "CArchSleepUnix.h" +# include "CArchStringUnix.h" +# include "CArchSystemUnix.h" +# include "CArchTaskBarXWindows.h" +# include "CArchTimeUnix.h" +#endif + +#if !defined(ARCH_CONSOLE) +# error unsupported platform for console +#endif + +#if !defined(ARCH_DAEMON) +# error unsupported platform for daemon +#endif + +#if !defined(ARCH_FILE) +# error unsupported platform for file +#endif + +#if !defined(ARCH_LOG) +# error unsupported platform for logging +#endif + +#if !defined(ARCH_MULTITHREAD) +# error unsupported platform for multithreading +#endif + +#if !defined(ARCH_NETWORK) +# error unsupported platform for network +#endif + +#if !defined(ARCH_SLEEP) +# error unsupported platform for sleep +#endif + +#if !defined(ARCH_STRING) +# error unsupported platform for string +#endif + +#if !defined(ARCH_SYSTEM) +# error unsupported platform for system +#endif + +#if !defined(ARCH_TASKBAR) +# error unsupported platform for taskbar +#endif + +#if !defined(ARCH_TIME) +# error unsupported platform for time +#endif + +// +// CArch +// + +CArch* CArch::s_instance = NULL; + +CArch::CArch(ARCH_ARGS* args) +{ + // only once instance of CArch + assert(s_instance == NULL); + s_instance = this; + + // create architecture implementation objects + m_mt = new ARCH_MULTITHREAD; + m_system = new ARCH_SYSTEM; + m_file = new ARCH_FILE; + m_log = new ARCH_LOG; + m_net = new ARCH_NETWORK; + m_sleep = new ARCH_SLEEP; + m_string = new ARCH_STRING; + m_time = new ARCH_TIME; + m_console = new ARCH_CONSOLE(args); + m_daemon = new ARCH_DAEMON; + m_taskbar = new ARCH_TASKBAR(args); + +#if SYSAPI_WIN32 + CArchMiscWindows::init(); +#endif +} + +CArch::~CArch() +{ + // clean up + delete m_taskbar; + delete m_daemon; + delete m_console; + delete m_time; + delete m_string; + delete m_sleep; + delete m_net; + delete m_log; + delete m_file; + delete m_system; + delete m_mt; + + // no instance + s_instance = NULL; +} + +CArch* +CArch::getInstance() +{ + assert(s_instance != NULL); + + return s_instance; +} + +void +CArch::openConsole(const char* title) +{ + m_console->openConsole(title); +} + +void +CArch::closeConsole() +{ + m_console->closeConsole(); +} + +void +CArch::showConsole(bool showIfEmpty) +{ + m_console->showConsole(showIfEmpty); +} + +void +CArch::writeConsole(const char* str) +{ + m_console->writeConsole(str); +} + +const char* +CArch::getNewlineForConsole() +{ + return m_console->getNewlineForConsole(); +} + +void +CArch::installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers) +{ + m_daemon->installDaemon(name, description, pathname, + commandLine, dependencies, allUsers); +} + +void +CArch::uninstallDaemon(const char* name, bool allUsers) +{ + m_daemon->uninstallDaemon(name, allUsers); +} + +int +CArch::daemonize(const char* name, DaemonFunc func) +{ + return m_daemon->daemonize(name, func); +} + +bool +CArch::canInstallDaemon(const char* name, bool allUsers) +{ + return m_daemon->canInstallDaemon(name, allUsers); +} + +bool +CArch::isDaemonInstalled(const char* name, bool allUsers) +{ + return m_daemon->isDaemonInstalled(name, allUsers); +} + +const char* +CArch::getBasename(const char* pathname) +{ + return m_file->getBasename(pathname); +} + +std::string +CArch::getUserDirectory() +{ + return m_file->getUserDirectory(); +} + +std::string +CArch::getSystemDirectory() +{ + return m_file->getSystemDirectory(); +} + +std::string +CArch::concatPath(const std::string& prefix, const std::string& suffix) +{ + return m_file->concatPath(prefix, suffix); +} + +void +CArch::openLog(const char* name) +{ + m_log->openLog(name); +} + +void +CArch::closeLog() +{ + m_log->closeLog(); +} + +void +CArch::showLog(bool showIfEmpty) +{ + m_log->showLog(showIfEmpty); +} + +void +CArch::writeLog(ELevel level, const char* msg) +{ + m_log->writeLog(level, msg); +} + +CArchCond +CArch::newCondVar() +{ + return m_mt->newCondVar(); +} + +void +CArch::closeCondVar(CArchCond cond) +{ + m_mt->closeCondVar(cond); +} + +void +CArch::signalCondVar(CArchCond cond) +{ + m_mt->signalCondVar(cond); +} + +void +CArch::broadcastCondVar(CArchCond cond) +{ + m_mt->broadcastCondVar(cond); +} + +bool +CArch::waitCondVar(CArchCond cond, CArchMutex mutex, double timeout) +{ + return m_mt->waitCondVar(cond, mutex, timeout); +} + +CArchMutex +CArch::newMutex() +{ + return m_mt->newMutex(); +} + +void +CArch::closeMutex(CArchMutex mutex) +{ + m_mt->closeMutex(mutex); +} + +void +CArch::lockMutex(CArchMutex mutex) +{ + m_mt->lockMutex(mutex); +} + +void +CArch::unlockMutex(CArchMutex mutex) +{ + m_mt->unlockMutex(mutex); +} + +CArchThread +CArch::newThread(ThreadFunc func, void* data) +{ + return m_mt->newThread(func, data); +} + +CArchThread +CArch::newCurrentThread() +{ + return m_mt->newCurrentThread(); +} + +CArchThread +CArch::copyThread(CArchThread thread) +{ + return m_mt->copyThread(thread); +} + +void +CArch::closeThread(CArchThread thread) +{ + m_mt->closeThread(thread); +} + +void +CArch::cancelThread(CArchThread thread) +{ + m_mt->cancelThread(thread); +} + +void +CArch::setPriorityOfThread(CArchThread thread, int n) +{ + m_mt->setPriorityOfThread(thread, n); +} + +void +CArch::testCancelThread() +{ + m_mt->testCancelThread(); +} + +bool +CArch::wait(CArchThread thread, double timeout) +{ + return m_mt->wait(thread, timeout); +} + +bool +CArch::isSameThread(CArchThread thread1, CArchThread thread2) +{ + return m_mt->isSameThread(thread1, thread2); +} + +bool +CArch::isExitedThread(CArchThread thread) +{ + return m_mt->isExitedThread(thread); +} + +void* +CArch::getResultOfThread(CArchThread thread) +{ + return m_mt->getResultOfThread(thread); +} + +IArchMultithread::ThreadID +CArch::getIDOfThread(CArchThread thread) +{ + return m_mt->getIDOfThread(thread); +} + +void +CArch::setSignalHandler(ESignal signal, SignalFunc func, void* userData) +{ + m_mt->setSignalHandler(signal, func, userData); +} + +void +CArch::raiseSignal(ESignal signal) +{ + m_mt->raiseSignal(signal); +} + +CArchSocket +CArch::newSocket(EAddressFamily family, ESocketType type) +{ + return m_net->newSocket(family, type); +} + +CArchSocket +CArch::copySocket(CArchSocket s) +{ + return m_net->copySocket(s); +} + +void +CArch::closeSocket(CArchSocket s) +{ + m_net->closeSocket(s); +} + +void +CArch::closeSocketForRead(CArchSocket s) +{ + m_net->closeSocketForRead(s); +} + +void +CArch::closeSocketForWrite(CArchSocket s) +{ + m_net->closeSocketForWrite(s); +} + +void +CArch::bindSocket(CArchSocket s, CArchNetAddress addr) +{ + m_net->bindSocket(s, addr); +} + +void +CArch::listenOnSocket(CArchSocket s) +{ + m_net->listenOnSocket(s); +} + +CArchSocket +CArch::acceptSocket(CArchSocket s, CArchNetAddress* addr) +{ + return m_net->acceptSocket(s, addr); +} + +bool +CArch::connectSocket(CArchSocket s, CArchNetAddress name) +{ + return m_net->connectSocket(s, name); +} + +int +CArch::pollSocket(CPollEntry pe[], int num, double timeout) +{ + return m_net->pollSocket(pe, num, timeout); +} + +void +CArch::unblockPollSocket(CArchThread thread) +{ + m_net->unblockPollSocket(thread); +} + +size_t +CArch::readSocket(CArchSocket s, void* buf, size_t len) +{ + return m_net->readSocket(s, buf, len); +} + +size_t +CArch::writeSocket(CArchSocket s, const void* buf, size_t len) +{ + return m_net->writeSocket(s, buf, len); +} + +void +CArch::throwErrorOnSocket(CArchSocket s) +{ + m_net->throwErrorOnSocket(s); +} + +bool +CArch::setNoDelayOnSocket(CArchSocket s, bool noDelay) +{ + return m_net->setNoDelayOnSocket(s, noDelay); +} + +bool +CArch::setReuseAddrOnSocket(CArchSocket s, bool reuse) +{ + return m_net->setReuseAddrOnSocket(s, reuse); +} + +std::string +CArch::getHostName() +{ + return m_net->getHostName(); +} + +CArchNetAddress +CArch::newAnyAddr(EAddressFamily family) +{ + return m_net->newAnyAddr(family); +} + +CArchNetAddress +CArch::copyAddr(CArchNetAddress addr) +{ + return m_net->copyAddr(addr); +} + +CArchNetAddress +CArch::nameToAddr(const std::string& name) +{ + return m_net->nameToAddr(name); +} + +void +CArch::closeAddr(CArchNetAddress addr) +{ + m_net->closeAddr(addr); +} + +std::string +CArch::addrToName(CArchNetAddress addr) +{ + return m_net->addrToName(addr); +} + +std::string +CArch::addrToString(CArchNetAddress addr) +{ + return m_net->addrToString(addr); +} + +IArchNetwork::EAddressFamily +CArch::getAddrFamily(CArchNetAddress addr) +{ + return m_net->getAddrFamily(addr); +} + +void +CArch::setAddrPort(CArchNetAddress addr, int port) +{ + m_net->setAddrPort(addr, port); +} + +int +CArch::getAddrPort(CArchNetAddress addr) +{ + return m_net->getAddrPort(addr); +} + +bool +CArch::isAnyAddr(CArchNetAddress addr) +{ + return m_net->isAnyAddr(addr); +} + +bool +CArch::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return m_net->isEqualAddr(a, b); +} + +void +CArch::sleep(double timeout) +{ + m_sleep->sleep(timeout); +} + +int +CArch::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + return m_string->vsnprintf(str, size, fmt, ap); +} + +int +CArch::convStringMBToWC(wchar_t* dst, const char* src, UInt32 n, bool* errors) +{ + return m_string->convStringMBToWC(dst, src, n, errors); +} + +int +CArch::convStringWCToMB(char* dst, const wchar_t* src, UInt32 n, bool* errors) +{ + return m_string->convStringWCToMB(dst, src, n, errors); +} + +IArchString::EWideCharEncoding +CArch::getWideCharEncoding() +{ + return m_string->getWideCharEncoding(); +} + +std::string +CArch::getOSName() const +{ + return m_system->getOSName(); +} + +void +CArch::addReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->addReceiver(receiver); +} + +void +CArch::removeReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->removeReceiver(receiver); +} + +void +CArch::updateReceiver(IArchTaskBarReceiver* receiver) +{ + m_taskbar->updateReceiver(receiver); +} + +double +CArch::time() +{ + return m_time->time(); +} diff --git a/lib/arch/CArch.h b/lib/arch/CArch.h new file mode 100644 index 00000000..644f015c --- /dev/null +++ b/lib/arch/CArch.h @@ -0,0 +1,218 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCH_H +#define CARCH_H + +#include "IArchConsole.h" +#include "IArchDaemon.h" +#include "IArchFile.h" +#include "IArchLog.h" +#include "IArchMultithread.h" +#include "IArchNetwork.h" +#include "IArchSleep.h" +#include "IArchString.h" +#include "IArchSystem.h" +#include "IArchTaskBar.h" +#include "IArchTime.h" + +/*! +\def ARCH +This macro evaluates to the singleton CArch object. +*/ +#define ARCH (CArch::getInstance()) + +#define ARCH_ARGS void + +//! Delegating mplementation of architecture dependent interfaces +/*! +This class is a centralized interface to all architecture dependent +interface implementations (except miscellaneous functions). It +instantiates an implementation of each interface and delegates calls +to each method to those implementations. Clients should use the +\c ARCH macro to access this object. Clients must also instantiate +exactly one of these objects before attempting to call any method, +typically at the beginning of \c main(). +*/ +class CArch : public IArchConsole, + public IArchDaemon, + public IArchFile, + public IArchLog, + public IArchMultithread, + public IArchNetwork, + public IArchSleep, + public IArchString, + public IArchSystem, + public IArchTaskBar, + public IArchTime { +public: + CArch(ARCH_ARGS* args = NULL); + ~CArch(); + + // + // accessors + // + + //! Return the singleton instance + /*! + The client must have instantiated exactly once CArch object before + calling this function. + */ + static CArch* getInstance(); + + // IArchConsole overrides + virtual void openConsole(const char*); + virtual void closeConsole(); + virtual void showConsole(bool showIfEmpty); + virtual void writeConsole(const char*); + virtual const char* getNewlineForConsole(); + + // IArchDaemon overrides + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers); + virtual void uninstallDaemon(const char* name, bool allUsers); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name, bool allUsers); + virtual bool isDaemonInstalled(const char* name, bool allUsers); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); + + // IArchLog overrides + virtual void openLog(const char*); + virtual void closeLog(); + virtual void showLog(bool showIfEmpty); + virtual void writeLog(ELevel, const char*); + + // IArchMultithread overrides + virtual CArchCond newCondVar(); + virtual void closeCondVar(CArchCond); + virtual void signalCondVar(CArchCond); + virtual void broadcastCondVar(CArchCond); + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout); + virtual CArchMutex newMutex(); + virtual void closeMutex(CArchMutex); + virtual void lockMutex(CArchMutex); + virtual void unlockMutex(CArchMutex); + virtual CArchThread newThread(ThreadFunc, void*); + virtual CArchThread newCurrentThread(); + virtual CArchThread copyThread(CArchThread); + virtual void closeThread(CArchThread); + virtual void cancelThread(CArchThread); + virtual void setPriorityOfThread(CArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(CArchThread, double timeout); + virtual bool isSameThread(CArchThread, CArchThread); + virtual bool isExitedThread(CArchThread); + virtual void* getResultOfThread(CArchThread); + virtual ThreadID getIDOfThread(CArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + + // IArchNetwork overrides + virtual CArchSocket newSocket(EAddressFamily, ESocketType); + virtual CArchSocket copySocket(CArchSocket s); + virtual void closeSocket(CArchSocket s); + virtual void closeSocketForRead(CArchSocket s); + virtual void closeSocketForWrite(CArchSocket s); + virtual void bindSocket(CArchSocket s, CArchNetAddress addr); + virtual void listenOnSocket(CArchSocket s); + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr); + virtual bool connectSocket(CArchSocket s, CArchNetAddress name); + virtual int pollSocket(CPollEntry[], int num, double timeout); + virtual void unblockPollSocket(CArchThread thread); + virtual size_t readSocket(CArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(CArchSocket); + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse); + virtual std::string getHostName(); + virtual CArchNetAddress newAnyAddr(EAddressFamily); + virtual CArchNetAddress copyAddr(CArchNetAddress); + virtual CArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(CArchNetAddress); + virtual std::string addrToName(CArchNetAddress); + virtual std::string addrToString(CArchNetAddress); + virtual EAddressFamily getAddrFamily(CArchNetAddress); + virtual void setAddrPort(CArchNetAddress, int port); + virtual int getAddrPort(CArchNetAddress); + virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); + + // IArchSleep overrides + virtual void sleep(double timeout); + + // IArchString overrides + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + virtual EWideCharEncoding + getWideCharEncoding(); + + // IArchSystem overrides + virtual std::string getOSName() const; + + // IArchTaskBar + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + + // IArchTime overrides + virtual double time(); + +private: + static CArch* s_instance; + + IArchConsole* m_console; + IArchDaemon* m_daemon; + IArchFile* m_file; + IArchLog* m_log; + IArchMultithread* m_mt; + IArchNetwork* m_net; + IArchSleep* m_sleep; + IArchString* m_string; + IArchSystem* m_system; + IArchTaskBar* m_taskbar; + IArchTime* m_time; +}; + +//! Convenience object to lock/unlock an arch mutex +class CArchMutexLock { +public: + CArchMutexLock(CArchMutex mutex) : m_mutex(mutex) + { + ARCH->lockMutex(m_mutex); + } + ~CArchMutexLock() + { + ARCH->unlockMutex(m_mutex); + } + +private: + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/arch/CArchConsoleUnix.cpp b/lib/arch/CArchConsoleUnix.cpp new file mode 100644 index 00000000..dcb6e961 --- /dev/null +++ b/lib/arch/CArchConsoleUnix.cpp @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchConsoleUnix.h" +#include + +// +// CArchConsoleUnix +// + +CArchConsoleUnix::CArchConsoleUnix(void*) +{ + // do nothing +} + +CArchConsoleUnix::~CArchConsoleUnix() +{ + // do nothing +} + +void +CArchConsoleUnix::openConsole(const char*) +{ + // do nothing +} + +void +CArchConsoleUnix::closeConsole() +{ + // do nothing +} + +void +CArchConsoleUnix::showConsole(bool) +{ + // do nothing +} + +void +CArchConsoleUnix::writeConsole(const char* str) +{ + fprintf(stderr, "%s", str); +} + +const char* +CArchConsoleUnix::getNewlineForConsole() +{ + return "\n"; +} diff --git a/lib/arch/CArchConsoleUnix.h b/lib/arch/CArchConsoleUnix.h new file mode 100644 index 00000000..f93630bd --- /dev/null +++ b/lib/arch/CArchConsoleUnix.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHCONSOLEUNIX_H +#define CARCHCONSOLEUNIX_H + +#include "IArchConsole.h" + +#define ARCH_CONSOLE CArchConsoleUnix + +//! Unix implementation of IArchConsole +class CArchConsoleUnix : public IArchConsole { +public: + CArchConsoleUnix(void*); + virtual ~CArchConsoleUnix(); + + // IArchConsole overrides + virtual void openConsole(const char* title); + virtual void closeConsole(); + virtual void showConsole(bool); + virtual void writeConsole(const char*); + virtual const char* getNewlineForConsole(); +}; + +#endif diff --git a/lib/arch/CArchConsoleWindows.cpp b/lib/arch/CArchConsoleWindows.cpp new file mode 100644 index 00000000..14d418ac --- /dev/null +++ b/lib/arch/CArchConsoleWindows.cpp @@ -0,0 +1,438 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchConsoleWindows.h" +#include "IArchMultithread.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include + +#define SYNERGY_MSG_CONSOLE_OPEN WM_APP + 0x0021 +#define SYNERGY_MSG_CONSOLE_CLOSE WM_APP + 0x0022 +#define SYNERGY_MSG_CONSOLE_SHOW WM_APP + 0x0023 +#define SYNERGY_MSG_CONSOLE_WRITE WM_APP + 0x0024 +#define SYNERGY_MSG_CONSOLE_CLEAR WM_APP + 0x0025 + +// +// CArchConsoleWindows +// + +CArchConsoleWindows* CArchConsoleWindows::s_instance = NULL; +HINSTANCE CArchConsoleWindows::s_appInstance = NULL; + +CArchConsoleWindows::CArchConsoleWindows(void* appInstance) : + m_show(false), + m_maxLines(1000), + m_numCharacters(0), + m_maxCharacters(65536) +{ + // save the singleton instance + s_instance = this; + + // save app instance + s_appInstance = reinterpret_cast(appInstance); + + // we need a mutex + m_mutex = ARCH->newMutex(); + + // and a condition variable which uses the above mutex + m_ready = false; + m_condVar = ARCH->newCondVar(); + + // we're going to want to get a result from the thread we're + // about to create to know if it initialized successfully. + // so we lock the condition variable. + ARCH->lockMutex(m_mutex); + + // open a window and run an event loop in a separate thread. + // this has to happen in a separate thread because if we + // create a window on the current desktop with the current + // thread then the current thread won't be able to switch + // desktops if it needs to. + m_thread = ARCH->newThread(&CArchConsoleWindows::threadEntry, this); + + // wait for child thread + while (!m_ready) { + ARCH->waitCondVar(m_condVar, m_mutex, -1.0); + } + + // ready + ARCH->unlockMutex(m_mutex); + +} + +CArchConsoleWindows::~CArchConsoleWindows() +{ + if (m_thread != NULL) { + PostMessage(m_hwnd, WM_QUIT, 0, 0); + ARCH->wait(m_thread, -1.0); + ARCH->closeThread(m_thread); + } + ARCH->closeCondVar(m_condVar); + ARCH->closeMutex(m_mutex); + s_instance = NULL; +} + +void +CArchConsoleWindows::openConsole(const char* title) +{ + SetWindowText(m_frame, title); + SendMessage(m_frame, SYNERGY_MSG_CONSOLE_OPEN, 0, 0); +} + +void +CArchConsoleWindows::closeConsole() +{ + SendMessage(m_frame, SYNERGY_MSG_CONSOLE_CLOSE, 0, 0); + SendMessage(m_frame, SYNERGY_MSG_CONSOLE_CLEAR, 0, 0); +} + +void +CArchConsoleWindows::showConsole(bool showIfEmpty) +{ + SendMessage(m_frame, SYNERGY_MSG_CONSOLE_SHOW, showIfEmpty ? 1 : 0, 0); +} + +void +CArchConsoleWindows::writeConsole(const char* str) +{ + SendMessage(m_frame, SYNERGY_MSG_CONSOLE_WRITE, + reinterpret_cast(str), 0); +} + +const char* +CArchConsoleWindows::getNewlineForConsole() +{ + return "\r\n"; +} + +void +CArchConsoleWindows::clearBuffer() +{ + m_buffer.clear(); + m_numCharacters = 0; + SetWindowText(m_hwnd, ""); +} + +void +CArchConsoleWindows::appendBuffer(const char* msg) +{ + bool wasEmpty = m_buffer.empty(); + + // get current selection + CHARRANGE selection; + SendMessage(m_hwnd, EM_EXGETSEL, 0, reinterpret_cast(&selection)); + + // remove tail of buffer + size_t removedCharacters = 0; + while (m_buffer.size() >= m_maxLines) { + removedCharacters += m_buffer.front().size(); + m_buffer.pop_front(); + } + + // remove lines from top of control + if (removedCharacters > 0) { + CHARRANGE range; + range.cpMin = 0; + range.cpMax = static_cast(removedCharacters); + SendMessage(m_hwnd, EM_EXSETSEL, 0, reinterpret_cast(&range)); + SendMessage(m_hwnd, EM_REPLACESEL, FALSE, reinterpret_cast("")); + + // adjust selection + if (selection.cpMin < static_cast(removedCharacters) || + selection.cpMax < static_cast(removedCharacters)) { + selection.cpMin = 0; + selection.cpMax = 0; + } + else { + selection.cpMin -= static_cast(removedCharacters); + selection.cpMax -= static_cast(removedCharacters); + } + + m_numCharacters -= removedCharacters; + } + + // append message + m_buffer.push_back(msg); + size_t newNumCharacters = m_numCharacters + m_buffer.back().size(); + + // add line to bottom of control + if (newNumCharacters > m_maxCharacters) { + m_maxCharacters = newNumCharacters; + SendMessage(m_hwnd, EM_EXLIMITTEXT, 0, m_maxCharacters); + } + CHARRANGE range; + range.cpMin = m_numCharacters; + range.cpMax = m_numCharacters; + SendMessage(m_hwnd, EM_EXSETSEL, 0, reinterpret_cast(&range)); + SendMessage(m_hwnd, EM_REPLACESEL, FALSE, + reinterpret_cast(m_buffer.back().c_str())); + + // adjust selection + bool atEnd = false; + if (selection.cpMax == static_cast(m_numCharacters)) { + selection.cpMin = static_cast(newNumCharacters); + selection.cpMax = static_cast(newNumCharacters); + atEnd = true; + } + + // restore the selection + SendMessage(m_hwnd, EM_EXSETSEL, 0, reinterpret_cast(&selection)); + if (atEnd) { + SendMessage(m_hwnd, EM_SCROLLCARET, 0, 0); + } + + if (wasEmpty && m_show) { + ShowWindow(m_frame, TRUE); + } + + m_numCharacters = newNumCharacters; +} + +void +CArchConsoleWindows::setSize(int width, int height) +{ + DWORD style = GetWindowLong(m_frame, GWL_STYLE); + DWORD exStyle = GetWindowLong(m_frame, GWL_EXSTYLE); + RECT rect; + rect.left = 100; + rect.top = 100; + rect.right = rect.left + width * m_wChar; + rect.bottom = rect.top + height * m_hChar; + AdjustWindowRectEx(&rect, style, FALSE, exStyle); + SetWindowPos(m_frame, NULL, 0, 0, rect.right - rect.left, + rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER); +} + +LRESULT +CArchConsoleWindows::wndProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_CLOSE: + ShowWindow(m_frame, FALSE); + m_show = false; + return 0; + + case SYNERGY_MSG_CONSOLE_OPEN: + return 0; + + case SYNERGY_MSG_CONSOLE_CLOSE: + SendMessage(m_frame, WM_CLOSE, 0, 0); + m_show = false; + return 0; + + case SYNERGY_MSG_CONSOLE_SHOW: + m_show = true; + if (wParam != 0 || !m_buffer.empty()) { + ShowWindow(m_frame, TRUE); + } + return 0; + + case SYNERGY_MSG_CONSOLE_WRITE: + appendBuffer(reinterpret_cast(wParam)); + return 0; + + case SYNERGY_MSG_CONSOLE_CLEAR: + clearBuffer(); + return 0; + + case WM_SIZE: + if (hwnd == m_frame) { + MoveWindow(m_hwnd, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); + } + break; + + case WM_SIZING: + if (hwnd == m_frame) { + // get window vs client area info + int wBase = 40 * m_wChar; + int hBase = 40 * m_hChar; + DWORD style = GetWindowLong(m_frame, GWL_STYLE); + DWORD exStyle = GetWindowLong(m_frame, GWL_EXSTYLE); + RECT rect; + rect.left = 100; + rect.top = 100; + rect.right = rect.left + wBase; + rect.bottom = rect.top + hBase; + AdjustWindowRectEx(&rect, style, FALSE, exStyle); + wBase = rect.right - rect.left - wBase; + hBase = rect.bottom - rect.top - hBase; + + // get closest size that's a multiple of the character size + RECT* newRect = (RECT*)lParam; + int width = (newRect->right - newRect->left - wBase) / m_wChar; + int height = (newRect->bottom - newRect->top - hBase) / m_hChar; + width = width * m_wChar + wBase; + height = height * m_hChar + hBase; + + // adjust sizing rect + switch (wParam) { + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + case WMSZ_BOTTOMLEFT: + newRect->left = newRect->right - width; + break; + + case WMSZ_RIGHT: + case WMSZ_TOPRIGHT: + case WMSZ_BOTTOMRIGHT: + newRect->right = newRect->left + width; + break; + } + switch (wParam) { + case WMSZ_TOP: + case WMSZ_TOPLEFT: + case WMSZ_TOPRIGHT: + newRect->top = newRect->bottom - height; + break; + + case WMSZ_BOTTOM: + case WMSZ_BOTTOMLEFT: + case WMSZ_BOTTOMRIGHT: + newRect->bottom = newRect->top + height; + break; + } + return TRUE; + } + break; + + default: + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CArchConsoleWindows::staticWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + // forward the message + if (s_instance != NULL) { + return s_instance->wndProc(hwnd, msg, wParam, lParam); + } + else { + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} + +void +CArchConsoleWindows::threadMainLoop() +{ + LoadLibrary("RICHED32.DLL"); + + // get the app icons + HICON largeIcon, smallIcon; + CArchMiscWindows::getIcons(largeIcon, smallIcon); + + // register a window class + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = 0; + classInfo.lpfnWndProc = &CArchConsoleWindows::staticWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = sizeof(CArchConsoleWindows*); + classInfo.hInstance = s_appInstance; + classInfo.hIcon = largeIcon; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = TEXT("SynergyConsole"); + classInfo.hIconSm = smallIcon; + ATOM windowClass = RegisterClassEx(&classInfo); + + // create frame window + m_frame = CreateWindowEx(0, + reinterpret_cast(windowClass), + TEXT("Synergy Log"), + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, + NULL, + NULL, + s_appInstance, + NULL); + + // create log window + m_hwnd = CreateWindowEx(0, + "RichEdit", + TEXT(""), + WS_CHILD | WS_VISIBLE | WS_VSCROLL | + ES_MULTILINE | ES_READONLY, + 0, 0, 1, 1, + m_frame, + (HMENU)1, + s_appInstance, + NULL); + + // select font and get info + HDC hdc = GetDC(m_hwnd); + HGDIOBJ oldFont = SelectObject(hdc, GetStockObject(ANSI_FIXED_FONT)); + TEXTMETRIC metrics; + GetTextMetrics(hdc, &metrics); + CHARFORMAT format; + format.cbSize = sizeof(format); + format.dwMask = CFM_CHARSET | CFM_COLOR | CFM_FACE | + CFM_OFFSET | CFM_SIZE | CFM_PROTECTED | + CFM_BOLD | CFM_ITALIC | + CFM_STRIKEOUT | CFM_UNDERLINE; + format.dwEffects = 0; + format.yHeight = metrics.tmHeight; + format.yOffset = 0; + format.crTextColor = RGB(0, 0, 0); + format.bCharSet = DEFAULT_CHARSET; + format.bPitchAndFamily = FIXED_PITCH | FF_MODERN; + GetTextFace(hdc, sizeof(format.szFaceName), format.szFaceName); + SelectObject(hdc, oldFont); + ReleaseDC(m_hwnd, hdc); + + // prep window + SendMessage(m_hwnd, EM_EXLIMITTEXT, 0, m_maxCharacters); + SendMessage(m_hwnd, EM_SETCHARFORMAT, 0, reinterpret_cast(&format)); + SendMessage(m_hwnd, EM_SETBKGNDCOLOR, 0, RGB(255, 255, 255)); + m_wChar = metrics.tmAveCharWidth; + m_hChar = metrics.tmHeight + metrics.tmExternalLeading; + setSize(80, 25); + + // signal ready + ARCH->lockMutex(m_mutex); + m_ready = true; + ARCH->broadcastCondVar(m_condVar); + ARCH->unlockMutex(m_mutex); + + // handle failure + if (m_hwnd == NULL) { + UnregisterClass(reinterpret_cast(windowClass), s_appInstance); + return; + } + + // main loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // clean up + DestroyWindow(m_hwnd); + UnregisterClass(reinterpret_cast(windowClass), s_appInstance); +} + +void* +CArchConsoleWindows::threadEntry(void* self) +{ + reinterpret_cast(self)->threadMainLoop(); + return NULL; +} diff --git a/lib/arch/CArchConsoleWindows.h b/lib/arch/CArchConsoleWindows.h new file mode 100644 index 00000000..0d59e6ef --- /dev/null +++ b/lib/arch/CArchConsoleWindows.h @@ -0,0 +1,77 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHCONSOLEWINDOWS_H +#define CARCHCONSOLEWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchConsole.h" +#include "IArchMultithread.h" +#include "stddeque.h" +#include + +#define ARCH_CONSOLE CArchConsoleWindows + +//! Win32 implementation of IArchConsole +class CArchConsoleWindows : public IArchConsole { +public: + CArchConsoleWindows(void*); + virtual ~CArchConsoleWindows(); + + // IArchConsole overrides + virtual void openConsole(const char* title); + virtual void closeConsole(); + virtual void showConsole(bool showIfEmpty); + virtual void writeConsole(const char*); + virtual const char* getNewlineForConsole(); + +private: + void clearBuffer(); + void appendBuffer(const char*); + void setSize(int width, int height); + + LRESULT wndProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK + staticWndProc(HWND, UINT, WPARAM, LPARAM); + void threadMainLoop(); + static void* threadEntry(void*); + +private: + typedef std::deque MessageBuffer; + + static CArchConsoleWindows* s_instance; + static HINSTANCE s_appInstance; + + // multithread data + CArchMutex m_mutex; + CArchCond m_condVar; + bool m_ready; + CArchThread m_thread; + + // child thread data + HWND m_frame; + HWND m_hwnd; + LONG m_wChar; + LONG m_hChar; + bool m_show; + + // messages + size_t m_maxLines; + size_t m_maxCharacters; + size_t m_numCharacters; + MessageBuffer m_buffer; +}; + +#endif diff --git a/lib/arch/CArchDaemonNone.cpp b/lib/arch/CArchDaemonNone.cpp new file mode 100644 index 00000000..0281f365 --- /dev/null +++ b/lib/arch/CArchDaemonNone.cpp @@ -0,0 +1,66 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchDaemonNone.h" + +// +// CArchDaemonNone +// + +CArchDaemonNone::CArchDaemonNone() +{ + // do nothing +} + +CArchDaemonNone::~CArchDaemonNone() +{ + // do nothing +} + +void +CArchDaemonNone::installDaemon(const char*, + const char*, + const char*, + const char*, + const char*, + bool) +{ + // do nothing +} + +void +CArchDaemonNone::uninstallDaemon(const char*, bool) +{ + // do nothing +} + +int +CArchDaemonNone::daemonize(const char* name, DaemonFunc func) +{ + // simply forward the call to func. obviously, this doesn't + // do any daemonizing. + return func(1, &name); +} + +bool +CArchDaemonNone::canInstallDaemon(const char*, bool) +{ + return false; +} + +bool +CArchDaemonNone::isDaemonInstalled(const char*, bool) +{ + return false; +} diff --git a/lib/arch/CArchDaemonNone.h b/lib/arch/CArchDaemonNone.h new file mode 100644 index 00000000..1c196c5d --- /dev/null +++ b/lib/arch/CArchDaemonNone.h @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHDAEMONNONE_H +#define CARCHDAEMONNONE_H + +#include "IArchDaemon.h" + +#define ARCH_DAEMON CArchDaemonNone + +//! Dummy implementation of IArchDaemon +/*! +This class implements IArchDaemon for a platform that does not have +daemons. The install and uninstall functions do nothing, the query +functions return false, and \c daemonize() simply calls the passed +function and returns its result. +*/ +class CArchDaemonNone : public IArchDaemon { +public: + CArchDaemonNone(); + virtual ~CArchDaemonNone(); + + // IArchDaemon overrides + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers); + virtual void uninstallDaemon(const char* name, bool allUsers); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name, bool allUsers); + virtual bool isDaemonInstalled(const char* name, bool allUsers); +}; + +#endif diff --git a/lib/arch/CArchDaemonUnix.cpp b/lib/arch/CArchDaemonUnix.cpp new file mode 100644 index 00000000..93d50d4d --- /dev/null +++ b/lib/arch/CArchDaemonUnix.cpp @@ -0,0 +1,78 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchDaemonUnix.h" +#include "XArchUnix.h" +#include +#include +#include +#include +#include + +// +// CArchDaemonUnix +// + +CArchDaemonUnix::CArchDaemonUnix() +{ + // do nothing +} + +CArchDaemonUnix::~CArchDaemonUnix() +{ + // do nothing +} + +int +CArchDaemonUnix::daemonize(const char* name, DaemonFunc func) +{ + // fork so shell thinks we're done and so we're not a process + // group leader + switch (fork()) { + case -1: + // failed + throw XArchDaemonFailed(new XArchEvalUnix(errno)); + + case 0: + // child + break; + + default: + // parent exits + exit(0); + } + + // become leader of a new session + setsid(); + + // chdir to root so we don't keep mounted filesystems points busy + chdir("/"); + + // mask off permissions for any but owner + umask(077); + + // close open files. we only expect stdin, stdout, stderr to be open. + close(0); + close(1); + close(2); + + // attach file descriptors 0, 1, 2 to /dev/null so inadvertent use + // of standard I/O safely goes in the bit bucket. + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDWR); + dup(1); + + // invoke function + return func(1, &name); +} diff --git a/lib/arch/CArchDaemonUnix.h b/lib/arch/CArchDaemonUnix.h new file mode 100644 index 00000000..923004e1 --- /dev/null +++ b/lib/arch/CArchDaemonUnix.h @@ -0,0 +1,33 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHDAEMONUNIX_H +#define CARCHDAEMONUNIX_H + +#include "CArchDaemonNone.h" + +#undef ARCH_DAEMON +#define ARCH_DAEMON CArchDaemonUnix + +//! Unix implementation of IArchDaemon +class CArchDaemonUnix : public CArchDaemonNone { +public: + CArchDaemonUnix(); + virtual ~CArchDaemonUnix(); + + // IArchDaemon overrides + virtual int daemonize(const char* name, DaemonFunc func); +}; + +#endif diff --git a/lib/arch/CArchDaemonWindows.cpp b/lib/arch/CArchDaemonWindows.cpp new file mode 100644 index 00000000..ab42ceab --- /dev/null +++ b/lib/arch/CArchDaemonWindows.cpp @@ -0,0 +1,770 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchDaemonWindows.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include "XArchWindows.h" +#include "stdvector.h" + +// +// CArchDaemonWindows +// + +CArchDaemonWindows* CArchDaemonWindows::s_daemon = NULL; + +CArchDaemonWindows::CArchDaemonWindows() +{ + m_quitMessage = RegisterWindowMessage("SynergyDaemonExit"); +} + +CArchDaemonWindows::~CArchDaemonWindows() +{ + // do nothing +} + +int +CArchDaemonWindows::runDaemon(RunFunc runFunc) +{ + assert(s_daemon != NULL); + + return s_daemon->doRunDaemon(runFunc); +} + +void +CArchDaemonWindows::daemonRunning(bool running) +{ + // if s_daemon is NULL we assume we're running on the windows + // 95 family and we just ignore this call so the caller doesn't + // have to go through the trouble of not calling it on the + // windows 95 family. + if (s_daemon != NULL) { + s_daemon->doDaemonRunning(running); + } +} + +UINT +CArchDaemonWindows::getDaemonQuitMessage() +{ + if (s_daemon != NULL) { + return s_daemon->doGetDaemonQuitMessage(); + } + else { + return 0; + } +} + +void +CArchDaemonWindows::daemonFailed(int result) +{ + // if s_daemon is NULL we assume we're running on the windows + // 95 family and we just ignore this call so the caller doesn't + // have to go through the trouble of not calling it on the + // windows 95 family. + if (s_daemon != NULL) { + throw XArchDaemonRunFailed(result); + } +} + +void +CArchDaemonWindows::installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // open registry + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + if (key == NULL) { + // can't open key + throw XArchDaemonInstallFailed(new XArchEvalWindows); + } + + // construct entry + std::string value; + value += "\""; + value += pathname; + value += "\" "; + value += commandLine; + + // install entry + CArchMiscWindows::setValue(key, name, value); + + // clean up + CArchMiscWindows::closeKey(key); + } + + // windows NT family services + else { + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonInstallFailed(new XArchEvalWindows); + } + + // create the service + SC_HANDLE service = CreateService(mgr, + name, + name, + 0, + SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + pathname, + NULL, + NULL, + dependencies, + NULL, + NULL); + if (service == NULL) { + // can't create service + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_EXISTS) { + CloseServiceHandle(mgr); + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + } + + // done with service and manager + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + // open the registry key for this service + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::addKey(key, name); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + try { + uninstallDaemon(name, allUsers); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + + // set the description + CArchMiscWindows::setValue(key, _T("Description"), description); + + // set command line + key = CArchMiscWindows::addKey(key, _T("Parameters")); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + CArchMiscWindows::closeKey(key); + try { + uninstallDaemon(name, allUsers); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + CArchMiscWindows::setValue(key, _T("CommandLine"), commandLine); + + // done with registry + CArchMiscWindows::closeKey(key); + } +} + +void +CArchDaemonWindows::uninstallDaemon(const char* name, bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // open registry + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + if (key == NULL) { + // can't open key. daemon is probably not installed. + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows); + } + + // remove entry + CArchMiscWindows::deleteValue(key, name); + + // clean up + CArchMiscWindows::closeKey(key); + } + + // windows NT family services + else { + // remove parameters for this service. ignore failures. + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::openKey(key, name); + if (key != NULL) { + CArchMiscWindows::deleteKey(key, _T("Parameters")); + CArchMiscWindows::closeKey(key); + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonUninstallFailed(new XArchEvalWindows); + } + + // open the service. oddly, you must open a service to delete it. + SC_HANDLE service = OpenService(mgr, name, DELETE | SERVICE_STOP); + if (service == NULL) { + DWORD err = GetLastError(); + CloseServiceHandle(mgr); + if (err != ERROR_SERVICE_DOES_NOT_EXIST) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } + + // stop the service. we don't care if we fail. + SERVICE_STATUS status; + ControlService(service, SERVICE_CONTROL_STOP, &status); + + // delete the service + const bool okay = (DeleteService(service) == 0); + const DWORD err = GetLastError(); + + // clean up + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + // handle failure. ignore error if service isn't installed anymore. + if (!okay && isDaemonInstalled(name, allUsers)) { + if (err == ERROR_IO_PENDING) { + // this seems to be a spurious error + return; + } + if (err != ERROR_SERVICE_MARKED_FOR_DELETE) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } + } +} + +int +CArchDaemonWindows::daemonize(const char* name, DaemonFunc func) +{ + assert(name != NULL); + assert(func != NULL); + + // windows 95 family services + if (CArchMiscWindows::isWindows95Family()) { + typedef DWORD (WINAPI *RegisterServiceProcessT)(DWORD, DWORD); + + // mark this process as a service so it's not killed when the + // user logs off. + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows); + } + RegisterServiceProcessT RegisterServiceProcess = + reinterpret_cast( + GetProcAddress(kernel, + "RegisterServiceProcess")); + if (RegisterServiceProcess == NULL) { + // missing RegisterServiceProcess function + DWORD err = GetLastError(); + FreeLibrary(kernel); + throw XArchDaemonFailed(new XArchEvalWindows(err)); + } + if (RegisterServiceProcess(0, 1) == 0) { + // RegisterServiceProcess failed + DWORD err = GetLastError(); + FreeLibrary(kernel); + throw XArchDaemonFailed(new XArchEvalWindows(err)); + } + FreeLibrary(kernel); + + // now simply call the daemon function + return func(1, &name); + } + + // windows NT family services + else { + // save daemon function + m_daemonFunc = func; + + // construct the service entry + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = const_cast(name); + entry[0].lpServiceProc = &CArchDaemonWindows::serviceMainEntry; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + + // hook us up to the service control manager. this won't return + // (if successful) until the processes have terminated. + s_daemon = this; + if (StartServiceCtrlDispatcher(entry) == 0) { + // StartServiceCtrlDispatcher failed + s_daemon = NULL; + throw XArchDaemonFailed(new XArchEvalWindows); + } + + s_daemon = NULL; + return m_daemonResult; + } +} + +bool +CArchDaemonWindows::canInstallDaemon(const char* /*name*/, bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // check if we can open the registry key + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + CArchMiscWindows::closeKey(key); + return (key != NULL); + } + + // windows NT family services + else { + // check if we can open service manager for write + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + return false; + } + CloseServiceHandle(mgr); + + // check if we can open the registry key + HKEY key = openNTServicesKey(); +// key = CArchMiscWindows::addKey(key, name); +// key = CArchMiscWindows::addKey(key, _T("Parameters")); + CArchMiscWindows::closeKey(key); + + return (key != NULL); + } +} + +bool +CArchDaemonWindows::isDaemonInstalled(const char* name, bool allUsers) +{ + // if not for all users then use the user's autostart registry. + // key. if windows 95 family then use windows 95 services key. + if (!allUsers || CArchMiscWindows::isWindows95Family()) { + // check if we can open the registry key + HKEY key = (allUsers && CArchMiscWindows::isWindows95Family()) ? + open95ServicesKey() : openUserStartupKey(); + if (key == NULL) { + return false; + } + + // check for entry + const bool installed = !CArchMiscWindows::readValueString(key, + name).empty(); + + // clean up + CArchMiscWindows::closeKey(key); + + return installed; + } + + // windows NT family services + else { + // check parameters for this service + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::openKey(key, name); + key = CArchMiscWindows::openKey(key, _T("Parameters")); + if (key != NULL) { + const bool installed = !CArchMiscWindows::readValueString(key, + _T("CommandLine")).empty(); + CArchMiscWindows::closeKey(key); + if (!installed) { + return false; + } + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + return false; + } + + // open the service + SC_HANDLE service = OpenService(mgr, name, GENERIC_READ); + + // clean up + if (service != NULL) { + CloseServiceHandle(service); + } + CloseServiceHandle(mgr); + + return (service != NULL); + } +} + +HKEY +CArchDaemonWindows::openNTServicesKey() +{ + static const char* s_keyNames[] = { + _T("SYSTEM"), + _T("CurrentControlSet"), + _T("Services"), + NULL + }; + + return CArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +HKEY +CArchDaemonWindows::open95ServicesKey() +{ + static const char* s_keyNames[] = { + _T("Software"), + _T("Microsoft"), + _T("Windows"), + _T("CurrentVersion"), + _T("RunServices"), + NULL + }; + + return CArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +HKEY +CArchDaemonWindows::openUserStartupKey() +{ + static const char* s_keyNames[] = { + _T("Software"), + _T("Microsoft"), + _T("Windows"), + _T("CurrentVersion"), + _T("Run"), + NULL + }; + + return CArchMiscWindows::addKey(HKEY_CURRENT_USER, s_keyNames); +} + +bool +CArchDaemonWindows::isRunState(DWORD state) +{ + switch (state) { + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: + return true; + + default: + return false; + } +} + +int +CArchDaemonWindows::doRunDaemon(RunFunc run) +{ + // should only be called from DaemonFunc + assert(m_serviceMutex != NULL); + assert(run != NULL); + + // create message queue for this thread + MSG dummy; + PeekMessage(&dummy, NULL, 0, 0, PM_NOREMOVE); + + int result = 0; + ARCH->lockMutex(m_serviceMutex); + m_daemonThreadID = GetCurrentThreadId(); + while (m_serviceState != SERVICE_STOPPED) { + // wait until we're told to start + while (!isRunState(m_serviceState) && + m_serviceState != SERVICE_STOP_PENDING) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + + // run unless told to stop + if (m_serviceState != SERVICE_STOP_PENDING) { + ARCH->unlockMutex(m_serviceMutex); + try { + result = run(); + } + catch (...) { + ARCH->lockMutex(m_serviceMutex); + setStatusError(0); + m_serviceState = SERVICE_STOPPED; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + ARCH->unlockMutex(m_serviceMutex); + throw; + } + ARCH->lockMutex(m_serviceMutex); + } + + // notify of new state + if (m_serviceState == SERVICE_PAUSE_PENDING) { + m_serviceState = SERVICE_PAUSED; + } + else { + m_serviceState = SERVICE_STOPPED; + } + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); + return result; +} + +void +CArchDaemonWindows::doDaemonRunning(bool running) +{ + ARCH->lockMutex(m_serviceMutex); + if (running) { + m_serviceState = SERVICE_RUNNING; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); +} + +UINT +CArchDaemonWindows::doGetDaemonQuitMessage() +{ + return m_quitMessage; +} + +void +CArchDaemonWindows::setStatus(DWORD state) +{ + setStatus(state, 0, 0); +} + +void +CArchDaemonWindows::setStatus(DWORD state, DWORD step, DWORD waitHint) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = state; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = step; + status.dwWaitHint = waitHint; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +CArchDaemonWindows::setStatusError(DWORD error) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = SERVICE_STOPPED; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + status.dwServiceSpecificExitCode = error; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +CArchDaemonWindows::serviceMain(DWORD argc, LPTSTR* argvIn) +{ + typedef std::vector ArgList; + typedef std::vector Arguments; + const char** argv = const_cast(argvIn); + + // create synchronization objects + m_serviceMutex = ARCH->newMutex(); + m_serviceCondVar = ARCH->newCondVar(); + + // register our service handler function + m_statusHandle = RegisterServiceCtrlHandler(argv[0], + &CArchDaemonWindows::serviceHandlerEntry); + if (m_statusHandle == 0) { + // cannot start as service + m_daemonResult = -1; + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); + return; + } + + // tell service control manager that we're starting + m_serviceState = SERVICE_START_PENDING; + setStatus(m_serviceState, 0, 10000); + + // if no arguments supplied then try getting them from the registry. + // the first argument doesn't count because it's the service name. + Arguments args; + ArgList myArgv; + if (argc <= 1) { + // read command line + std::string commandLine; + HKEY key = openNTServicesKey(); + key = CArchMiscWindows::openKey(key, argvIn[0]); + key = CArchMiscWindows::openKey(key, _T("Parameters")); + if (key != NULL) { + commandLine = CArchMiscWindows::readValueString(key, + _T("CommandLine")); + } + + // if the command line isn't empty then parse and use it + if (!commandLine.empty()) { + // parse, honoring double quoted substrings + std::string::size_type i = commandLine.find_first_not_of(" \t"); + while (i != std::string::npos && i != commandLine.size()) { + // find end of string + std::string::size_type e; + if (commandLine[i] == '\"') { + // quoted. find closing quote. + ++i; + e = commandLine.find("\"", i); + + // whitespace must follow closing quote + if (e == std::string::npos || + (e + 1 != commandLine.size() && + commandLine[e + 1] != ' ' && + commandLine[e + 1] != '\t')) { + args.clear(); + break; + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + else { + // unquoted. find next whitespace. + e = commandLine.find_first_of(" \t", i); + if (e == std::string::npos) { + e = commandLine.size(); + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + + // next argument + i = commandLine.find_first_not_of(" \t", i); + } + + // service name goes first + myArgv.push_back(argv[0]); + + // get pointers + for (size_t i = 0; i < args.size(); ++i) { + myArgv.push_back(args[i].c_str()); + } + + // adjust argc/argv + argc = myArgv.size(); + argv = &myArgv[0]; + } + } + + try { + // invoke daemon function + m_daemonResult = m_daemonFunc(static_cast(argc), argv); + } + catch (XArchDaemonRunFailed& e) { + setStatusError(e.m_result); + m_daemonResult = -1; + } + catch (...) { + setStatusError(1); + m_daemonResult = -1; + } + + // clean up + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); +} + +void WINAPI +CArchDaemonWindows::serviceMainEntry(DWORD argc, LPTSTR* argv) +{ + s_daemon->serviceMain(argc, argv); +} + +void +CArchDaemonWindows::serviceHandler(DWORD ctrl) +{ + assert(m_serviceMutex != NULL); + assert(m_serviceCondVar != NULL); + + ARCH->lockMutex(m_serviceMutex); + + // ignore request if service is already stopped + if (s_daemon == NULL || m_serviceState == SERVICE_STOPPED) { + if (s_daemon != NULL) { + setStatus(m_serviceState); + } + ARCH->unlockMutex(m_serviceMutex); + return; + } + + switch (ctrl) { + case SERVICE_CONTROL_PAUSE: + m_serviceState = SERVICE_PAUSE_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + case SERVICE_CONTROL_CONTINUE: + // FIXME -- maybe should flush quit messages from queue + m_serviceState = SERVICE_CONTINUE_PENDING; + setStatus(m_serviceState, 0, 5000); + ARCH->broadcastCondVar(m_serviceCondVar); + break; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + m_serviceState = SERVICE_STOP_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + ARCH->broadcastCondVar(m_serviceCondVar); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + default: + // unknown service command + // fall through + + case SERVICE_CONTROL_INTERROGATE: + setStatus(m_serviceState); + break; + } + + ARCH->unlockMutex(m_serviceMutex); +} + +void WINAPI +CArchDaemonWindows::serviceHandlerEntry(DWORD ctrl) +{ + s_daemon->serviceHandler(ctrl); +} diff --git a/lib/arch/CArchDaemonWindows.h b/lib/arch/CArchDaemonWindows.h new file mode 100644 index 00000000..ed09fab9 --- /dev/null +++ b/lib/arch/CArchDaemonWindows.h @@ -0,0 +1,134 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHDAEMONWINDOWS_H +#define CARCHDAEMONWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchDaemon.h" +#include "IArchMultithread.h" +#include "stdstring.h" +#include +#include + +#define ARCH_DAEMON CArchDaemonWindows + +//! Win32 implementation of IArchDaemon +class CArchDaemonWindows : public IArchDaemon { +public: + typedef int (*RunFunc)(void); + + CArchDaemonWindows(); + virtual ~CArchDaemonWindows(); + + //! Run the daemon + /*! + When the client calls \c daemonize(), the \c DaemonFunc should call this + function after initialization and argument parsing to perform the + daemon processing. The \c runFunc should perform the daemon's + main loop, calling \c daemonRunning(true) when it enters the main loop + (i.e. after initialization) and \c daemonRunning(false) when it leaves + the main loop. The \c runFunc is called in a new thread and when the + daemon must exit the main loop due to some external control the + getDaemonQuitMessage() is posted to the thread. This function returns + what \c runFunc returns. \c runFunc should call \c daemonFailed() if + the daemon fails. + */ + static int runDaemon(RunFunc runFunc); + + //! Indicate daemon is in main loop + /*! + The \c runFunc passed to \c runDaemon() should call this function + to indicate when it has entered (\c running is \c true) or exited + (\c running is \c false) the main loop. + */ + static void daemonRunning(bool running); + + //! Indicate failure of running daemon + /*! + The \c runFunc passed to \c runDaemon() should call this function + to indicate failure. \c result is returned by \c daemonize(). + */ + static void daemonFailed(int result); + + //! Get daemon quit message + /*! + The windows NT daemon tells daemon thread to exit by posting this + message to it. The thread must, of course, have a message queue + for this to work. + */ + static UINT getDaemonQuitMessage(); + + // IArchDaemon overrides + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers); + virtual void uninstallDaemon(const char* name, bool allUsers); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name, bool allUsers); + virtual bool isDaemonInstalled(const char* name, bool allUsers); + +private: + static HKEY openNTServicesKey(); + static HKEY open95ServicesKey(); + static HKEY openUserStartupKey(); + + int doRunDaemon(RunFunc runFunc); + void doDaemonRunning(bool running); + UINT doGetDaemonQuitMessage(); + + static void setStatus(DWORD state); + static void setStatus(DWORD state, DWORD step, DWORD waitHint); + static void setStatusError(DWORD error); + + static bool isRunState(DWORD state); + + void serviceMain(DWORD, LPTSTR*); + static void WINAPI serviceMainEntry(DWORD, LPTSTR*); + + void serviceHandler(DWORD ctrl); + static void WINAPI serviceHandlerEntry(DWORD ctrl); + +private: + class XArchDaemonRunFailed { + public: + XArchDaemonRunFailed(int result) : m_result(result) { } + + public: + int m_result; + }; + +private: + static CArchDaemonWindows* s_daemon; + + CArchMutex m_serviceMutex; + CArchCond m_serviceCondVar; + DWORD m_serviceState; + bool m_serviceHandlerWaiting; + bool m_serviceRunning; + + DWORD m_daemonThreadID; + DaemonFunc m_daemonFunc; + int m_daemonResult; + + SERVICE_STATUS_HANDLE m_statusHandle; + + UINT m_quitMessage; +}; + +#endif diff --git a/lib/arch/CArchFileUnix.cpp b/lib/arch/CArchFileUnix.cpp new file mode 100644 index 00000000..89bb51dc --- /dev/null +++ b/lib/arch/CArchFileUnix.cpp @@ -0,0 +1,98 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchFileUnix.h" +#include +#include +#include +#include +#include + +// +// CArchFileUnix +// + +CArchFileUnix::CArchFileUnix() +{ + // do nothing +} + +CArchFileUnix::~CArchFileUnix() +{ + // do nothing +} + +const char* +CArchFileUnix::getBasename(const char* pathname) +{ + if (pathname == NULL) { + return NULL; + } + + const char* basename = strrchr(pathname, '/'); + if (basename != NULL) { + return basename + 1; + } + else { + return pathname; + } +} + +std::string +CArchFileUnix::getUserDirectory() +{ + char* buffer = NULL; + std::string dir; +#if HAVE_GETPWUID_R + struct passwd pwent; + struct passwd* pwentp; +#if defined(_SC_GETPW_R_SIZE_MAX) + long size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size == -1) { + size = BUFSIZ; + } +#else + long size = BUFSIZ; +#endif + buffer = new char[size]; + getpwuid_r(getuid(), &pwent, buffer, size, &pwentp); +#else + struct passwd* pwentp = getpwuid(getuid()); +#endif + if (pwentp != NULL && pwentp->pw_dir != NULL) { + dir = pwentp->pw_dir; + } + delete[] buffer; + return dir; +} + +std::string +CArchFileUnix::getSystemDirectory() +{ + return "/etc"; +} + +std::string +CArchFileUnix::concatPath(const std::string& prefix, + const std::string& suffix) +{ + std::string path; + path.reserve(prefix.size() + 1 + suffix.size()); + path += prefix; + if (path.size() == 0 || path[path.size() - 1] != '/') { + path += '/'; + } + path += suffix; + return path; +} diff --git a/lib/arch/CArchFileUnix.h b/lib/arch/CArchFileUnix.h new file mode 100644 index 00000000..41d00e90 --- /dev/null +++ b/lib/arch/CArchFileUnix.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHFILEUNIX_H +#define CARCHFILEUNIX_H + +#include "IArchFile.h" + +#define ARCH_FILE CArchFileUnix + +//! Unix implementation of IArchFile +class CArchFileUnix : public IArchFile { +public: + CArchFileUnix(); + virtual ~CArchFileUnix(); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); +}; + +#endif diff --git a/lib/arch/CArchFileWindows.cpp b/lib/arch/CArchFileWindows.cpp new file mode 100644 index 00000000..5debb17b --- /dev/null +++ b/lib/arch/CArchFileWindows.cpp @@ -0,0 +1,132 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchFileWindows.h" +#include +#include +#include +#include + +// +// CArchFileWindows +// + +CArchFileWindows::CArchFileWindows() +{ + // do nothing +} + +CArchFileWindows::~CArchFileWindows() +{ + // do nothing +} + +const char* +CArchFileWindows::getBasename(const char* pathname) +{ + if (pathname == NULL) { + return NULL; + } + + // check for last slash + const char* basename = strrchr(pathname, '/'); + if (basename != NULL) { + ++basename; + } + else { + basename = pathname; + } + + // check for last backslash + const char* basename2 = strrchr(pathname, '\\'); + if (basename2 != NULL && basename2 > basename) { + basename = basename2 + 1; + } + + return basename; +} + +std::string +CArchFileWindows::getUserDirectory() +{ + // try %HOMEPATH% + TCHAR dir[MAX_PATH]; + DWORD size = sizeof(dir) / sizeof(TCHAR); + DWORD result = GetEnvironmentVariable(_T("HOMEPATH"), dir, size); + if (result != 0 && result <= size) { + // sanity check -- if dir doesn't appear to start with a + // drive letter and isn't a UNC name then don't use it + // FIXME -- allow UNC names + if (dir[0] != '\0' && (dir[1] == ':' || + ((dir[0] == '\\' || dir[0] == '/') && + (dir[1] == '\\' || dir[1] == '/')))) { + return dir; + } + } + + // get the location of the personal files. that's as close to + // a home directory as we're likely to find. + ITEMIDLIST* idl; + if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &idl))) { + TCHAR* path = NULL; + if (SHGetPathFromIDList(idl, dir)) { + DWORD attr = GetFileAttributes(dir); + if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + path = dir; + } + + IMalloc* shalloc; + if (SUCCEEDED(SHGetMalloc(&shalloc))) { + shalloc->Free(idl); + shalloc->Release(); + } + + if (path != NULL) { + return path; + } + } + + // use root of C drive as a default + return "C:"; +} + +std::string +CArchFileWindows::getSystemDirectory() +{ + // get windows directory + char dir[MAX_PATH]; + if (GetWindowsDirectory(dir, sizeof(dir)) != 0) { + return dir; + } + else { + // can't get it. use C:\ as a default. + return "C:"; + } +} + +std::string +CArchFileWindows::concatPath(const std::string& prefix, + const std::string& suffix) +{ + std::string path; + path.reserve(prefix.size() + 1 + suffix.size()); + path += prefix; + if (path.size() == 0 || + (path[path.size() - 1] != '\\' && + path[path.size() - 1] != '/')) { + path += '\\'; + } + path += suffix; + return path; +} diff --git a/lib/arch/CArchFileWindows.h b/lib/arch/CArchFileWindows.h new file mode 100644 index 00000000..617b1c40 --- /dev/null +++ b/lib/arch/CArchFileWindows.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHFILEWINDOWS_H +#define CARCHFILEWINDOWS_H + +#include "IArchFile.h" + +#define ARCH_FILE CArchFileWindows + +//! Win32 implementation of IArchFile +class CArchFileWindows : public IArchFile { +public: + CArchFileWindows(); + virtual ~CArchFileWindows(); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); +}; + +#endif diff --git a/lib/arch/CArchLogUnix.cpp b/lib/arch/CArchLogUnix.cpp new file mode 100644 index 00000000..093d89f9 --- /dev/null +++ b/lib/arch/CArchLogUnix.cpp @@ -0,0 +1,79 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchLogUnix.h" +#include + +// +// CArchLogUnix +// + +CArchLogUnix::CArchLogUnix() +{ + // do nothing +} + +CArchLogUnix::~CArchLogUnix() +{ + // do nothing +} + +void +CArchLogUnix::openLog(const char* name) +{ + openlog(name, 0, LOG_DAEMON); +} + +void +CArchLogUnix::closeLog() +{ + closelog(); +} + +void +CArchLogUnix::showLog(bool) +{ + // do nothing +} + +void +CArchLogUnix::writeLog(ELevel level, const char* msg) +{ + // convert level + int priority; + switch (level) { + case kERROR: + priority = LOG_ERR; + break; + + case kWARNING: + priority = LOG_WARNING; + break; + + case kNOTE: + priority = LOG_NOTICE; + break; + + case kINFO: + priority = LOG_INFO; + break; + + default: + priority = LOG_DEBUG; + break; + } + + // log it + syslog(priority, "%s", msg); +} diff --git a/lib/arch/CArchLogUnix.h b/lib/arch/CArchLogUnix.h new file mode 100644 index 00000000..91070b45 --- /dev/null +++ b/lib/arch/CArchLogUnix.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHLOGUNIX_H +#define CARCHLOGUNIX_H + +#include "IArchLog.h" + +#define ARCH_LOG CArchLogUnix + +//! Unix implementation of IArchLog +class CArchLogUnix : public IArchLog { +public: + CArchLogUnix(); + virtual ~CArchLogUnix(); + + // IArchLog overrides + virtual void openLog(const char* name); + virtual void closeLog(); + virtual void showLog(bool); + virtual void writeLog(ELevel, const char*); +}; + +#endif diff --git a/lib/arch/CArchLogWindows.cpp b/lib/arch/CArchLogWindows.cpp new file mode 100644 index 00000000..0ac89131 --- /dev/null +++ b/lib/arch/CArchLogWindows.cpp @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchLogWindows.h" +#include "CArchMiscWindows.h" +#include + +// +// CArchLogWindows +// + +CArchLogWindows::CArchLogWindows() : m_eventLog(NULL) +{ + // do nothing +} + +CArchLogWindows::~CArchLogWindows() +{ + // do nothing +} + +void +CArchLogWindows::openLog(const char* name) +{ + if (m_eventLog == NULL && !CArchMiscWindows::isWindows95Family()) { + m_eventLog = RegisterEventSource(NULL, name); + } +} + +void +CArchLogWindows::closeLog() +{ + if (m_eventLog != NULL) { + DeregisterEventSource(m_eventLog); + m_eventLog = NULL; + } +} + +void +CArchLogWindows::showLog(bool) +{ + // do nothing +} + +void +CArchLogWindows::writeLog(ELevel level, const char* msg) +{ + if (m_eventLog != NULL) { + // convert priority + WORD type; + switch (level) { + case kERROR: + type = EVENTLOG_ERROR_TYPE; + break; + + case kWARNING: + type = EVENTLOG_WARNING_TYPE; + break; + + default: + type = EVENTLOG_INFORMATION_TYPE; + break; + } + + // log it + // FIXME -- win32 wants to use a message table to look up event + // strings. log messages aren't organized that way so we'll + // just dump our string into the raw data section of the event + // so users can at least see the message. note that we use our + // level as the event category. + ReportEvent(m_eventLog, type, static_cast(level), + 0, // event ID + NULL, + 0, + strlen(msg) + 1, // raw data size + NULL, + const_cast(msg));// raw data + } +} diff --git a/lib/arch/CArchLogWindows.h b/lib/arch/CArchLogWindows.h new file mode 100644 index 00000000..e8812536 --- /dev/null +++ b/lib/arch/CArchLogWindows.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHLOGWINDOWS_H +#define CARCHLOGWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchLog.h" +#include + +#define ARCH_LOG CArchLogWindows + +//! Win32 implementation of IArchLog +class CArchLogWindows : public IArchLog { +public: + CArchLogWindows(); + virtual ~CArchLogWindows(); + + // IArchLog overrides + virtual void openLog(const char* name); + virtual void closeLog(); + virtual void showLog(bool showIfEmpty); + virtual void writeLog(ELevel, const char*); + +private: + HANDLE m_eventLog; +}; + +#endif diff --git a/lib/arch/CArchMiscWindows.cpp b/lib/arch/CArchMiscWindows.cpp new file mode 100644 index 00000000..e2fb2dce --- /dev/null +++ b/lib/arch/CArchMiscWindows.cpp @@ -0,0 +1,416 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchMiscWindows.h" +#include "CArchDaemonWindows.h" + +#ifndef ES_SYSTEM_REQUIRED +#define ES_SYSTEM_REQUIRED ((DWORD)0x00000001) +#endif +#ifndef ES_DISPLAY_REQUIRED +#define ES_DISPLAY_REQUIRED ((DWORD)0x00000002) +#endif +#ifndef ES_CONTINUOUS +#define ES_CONTINUOUS ((DWORD)0x80000000) +#endif +typedef DWORD EXECUTION_STATE; + +// +// CArchMiscWindows +// + +CArchMiscWindows::CDialogs* CArchMiscWindows::s_dialogs = NULL; +DWORD CArchMiscWindows::s_busyState = 0; +CArchMiscWindows::STES_t CArchMiscWindows::s_stes = NULL; +HICON CArchMiscWindows::s_largeIcon = NULL; +HICON CArchMiscWindows::s_smallIcon = NULL; + +void +CArchMiscWindows::init() +{ + s_dialogs = new CDialogs; + isWindows95Family(); +} + +bool +CArchMiscWindows::isWindows95Family() +{ + static bool init = false; + static bool result = false; + + if (!init) { + OSVERSIONINFO version; + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) == 0) { + // cannot determine OS; assume windows 95 family + result = true; + } + else { + result = (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + } + init = true; + } + return result; +} + +bool +CArchMiscWindows::isWindowsModern() +{ + static bool init = false; + static bool result = false; + + if (!init) { + OSVERSIONINFO version; + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) == 0) { + // cannot determine OS; assume not modern + result = false; + } + else { + result = ((version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && + version.dwMajorVersion == 4 && + version.dwMinorVersion > 0) || + (version.dwPlatformId == VER_PLATFORM_WIN32_NT && + version.dwMajorVersion > 4)); + } + init = true; + } + return result; +} + +void +CArchMiscWindows::setIcons(HICON largeIcon, HICON smallIcon) +{ + s_largeIcon = largeIcon; + s_smallIcon = smallIcon; +} + +void +CArchMiscWindows::getIcons(HICON& largeIcon, HICON& smallIcon) +{ + largeIcon = s_largeIcon; + smallIcon = s_smallIcon; +} + +int +CArchMiscWindows::runDaemon(RunFunc runFunc) +{ + return CArchDaemonWindows::runDaemon(runFunc); +} + +void +CArchMiscWindows::daemonRunning(bool running) +{ + CArchDaemonWindows::daemonRunning(running); +} + +void +CArchMiscWindows::daemonFailed(int result) +{ + CArchDaemonWindows::daemonFailed(result); +} + +UINT +CArchMiscWindows::getDaemonQuitMessage() +{ + return CArchDaemonWindows::getDaemonQuitMessage(); +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* keyName) +{ + return openKey(key, keyName, false); +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames) +{ + return openKey(key, keyNames, false); +} + +HKEY +CArchMiscWindows::addKey(HKEY key, const TCHAR* keyName) +{ + return openKey(key, keyName, true); +} + +HKEY +CArchMiscWindows::addKey(HKEY key, const TCHAR* const* keyNames) +{ + return openKey(key, keyNames, true); +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* keyName, bool create) +{ + // ignore if parent is NULL + if (key == NULL) { + return NULL; + } + + // open next key + HKEY newKey; + LONG result = RegOpenKeyEx(key, keyName, 0, + KEY_WRITE | KEY_QUERY_VALUE, &newKey); + if (result != ERROR_SUCCESS && create) { + DWORD disp; + result = RegCreateKeyEx(key, keyName, 0, TEXT(""), + 0, KEY_WRITE | KEY_QUERY_VALUE, + NULL, &newKey, &disp); + } + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + return NULL; + } + + // switch to new key + RegCloseKey(key); + return newKey; +} + +HKEY +CArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames, bool create) +{ + for (size_t i = 0; key != NULL && keyNames[i] != NULL; ++i) { + // open next key + key = openKey(key, keyNames[i], create); + } + return key; +} + +void +CArchMiscWindows::closeKey(HKEY key) +{ + assert(key != NULL); + RegCloseKey(key); +} + +void +CArchMiscWindows::deleteKey(HKEY key, const TCHAR* name) +{ + assert(key != NULL); + assert(name != NULL); + RegDeleteKey(key, name); +} + +void +CArchMiscWindows::deleteValue(HKEY key, const TCHAR* name) +{ + assert(key != NULL); + assert(name != NULL); + RegDeleteValue(key, name); +} + +bool +CArchMiscWindows::hasValue(HKEY key, const TCHAR* name) +{ + DWORD type; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); + return (result == ERROR_SUCCESS && + (type == REG_DWORD || type == REG_SZ)); +} + +CArchMiscWindows::EValueType +CArchMiscWindows::typeOfValue(HKEY key, const TCHAR* name) +{ + DWORD type; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); + if (result != ERROR_SUCCESS) { + return kNO_VALUE; + } + switch (type) { + case REG_DWORD: + return kUINT; + + case REG_SZ: + return kSTRING; + + case REG_BINARY: + return kBINARY; + + default: + return kUNKNOWN; + } +} + +void +CArchMiscWindows::setValue(HKEY key, + const TCHAR* name, const std::string& value) +{ + assert(key != NULL); + assert(name != NULL); + RegSetValueEx(key, name, 0, REG_SZ, + reinterpret_cast(value.c_str()), + value.size() + 1); +} + +void +CArchMiscWindows::setValue(HKEY key, const TCHAR* name, DWORD value) +{ + assert(key != NULL); + assert(name != NULL); + RegSetValueEx(key, name, 0, REG_DWORD, + reinterpret_cast(&value), + sizeof(DWORD)); +} + +void +CArchMiscWindows::setValueBinary(HKEY key, + const TCHAR* name, const std::string& value) +{ + assert(key != NULL); + assert(name != NULL); + RegSetValueEx(key, name, 0, REG_BINARY, + reinterpret_cast(value.data()), + value.size()); +} + +std::string +CArchMiscWindows::readBinaryOrString(HKEY key, const TCHAR* name, DWORD type) +{ + // get the size of the string + DWORD actualType; + DWORD size = 0; + LONG result = RegQueryValueEx(key, name, 0, &actualType, NULL, &size); + if (result != ERROR_SUCCESS || actualType != type) { + return std::string(); + } + + // if zero size then return empty string + if (size == 0) { + return std::string(); + } + + // allocate space + char* buffer = new char[size]; + + // read it + result = RegQueryValueEx(key, name, 0, &actualType, + reinterpret_cast(buffer), &size); + if (result != ERROR_SUCCESS || actualType != type) { + delete[] buffer; + return std::string(); + } + + // clean up and return value + if (type == REG_SZ && buffer[size - 1] == '\0') { + // don't include terminating nul; std::string will add one. + --size; + } + std::string value(buffer, size); + delete[] buffer; + return value; +} + +std::string +CArchMiscWindows::readValueString(HKEY key, const TCHAR* name) +{ + return readBinaryOrString(key, name, REG_SZ); +} + +std::string +CArchMiscWindows::readValueBinary(HKEY key, const TCHAR* name) +{ + return readBinaryOrString(key, name, REG_BINARY); +} + +DWORD +CArchMiscWindows::readValueInt(HKEY key, const TCHAR* name) +{ + DWORD type; + DWORD value; + DWORD size = sizeof(value); + LONG result = RegQueryValueEx(key, name, 0, &type, + reinterpret_cast(&value), &size); + if (result != ERROR_SUCCESS || type != REG_DWORD) { + return 0; + } + return value; +} + +void +CArchMiscWindows::addDialog(HWND hwnd) +{ + s_dialogs->insert(hwnd); +} + +void +CArchMiscWindows::removeDialog(HWND hwnd) +{ + s_dialogs->erase(hwnd); +} + +bool +CArchMiscWindows::processDialog(MSG* msg) +{ + for (CDialogs::const_iterator index = s_dialogs->begin(); + index != s_dialogs->end(); ++index) { + if (IsDialogMessage(*index, msg)) { + return true; + } + } + return false; +} + +void +CArchMiscWindows::addBusyState(DWORD busyModes) +{ + s_busyState |= busyModes; + setThreadExecutionState(s_busyState); +} + +void +CArchMiscWindows::removeBusyState(DWORD busyModes) +{ + s_busyState &= ~busyModes; + setThreadExecutionState(s_busyState); +} + +void +CArchMiscWindows::setThreadExecutionState(DWORD busyModes) +{ + // look up function dynamically so we work on older systems + if (s_stes == NULL) { + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel != NULL) { + s_stes = reinterpret_cast(GetProcAddress(kernel, + "SetThreadExecutionState")); + } + if (s_stes == NULL) { + s_stes = &CArchMiscWindows::dummySetThreadExecutionState; + } + } + + // convert to STES form + EXECUTION_STATE state = 0; + if ((busyModes & kSYSTEM) != 0) { + state |= ES_SYSTEM_REQUIRED; + } + if ((busyModes & kDISPLAY) != 0) { + state |= ES_DISPLAY_REQUIRED; + } + if (state != 0) { + state |= ES_CONTINUOUS; + } + + // do it + s_stes(state); +} + +DWORD +CArchMiscWindows::dummySetThreadExecutionState(DWORD) +{ + // do nothing + return 0; +} diff --git a/lib/arch/CArchMiscWindows.h b/lib/arch/CArchMiscWindows.h new file mode 100644 index 00000000..95a1d136 --- /dev/null +++ b/lib/arch/CArchMiscWindows.h @@ -0,0 +1,191 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHMISCWINDOWS_H +#define CARCHMISCWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "common.h" +#include "stdstring.h" +#include "stdset.h" +#include + +//! Miscellaneous win32 functions. +class CArchMiscWindows { +public: + enum EValueType { + kUNKNOWN, + kNO_VALUE, + kUINT, + kSTRING, + kBINARY + }; + enum EBusyModes { + kIDLE = 0x0000, + kSYSTEM = 0x0001, + kDISPLAY = 0x0002 + }; + + typedef int (*RunFunc)(void); + + //! Initialize + static void init(); + + //! Test if windows 95, et al. + /*! + Returns true iff the platform is win95/98/me. + */ + static bool isWindows95Family(); + + //! Test if windows 95, et al. + /*! + Returns true iff the platform is win98 or win2k or higher (i.e. + not windows 95 or windows NT). + */ + static bool isWindowsModern(); + + //! Set the application icons + /*! + Set the application icons. + */ + static void setIcons(HICON largeIcon, HICON smallIcon); + + //! Get the application icons + /*! + Get the application icons. + */ + static void getIcons(HICON& largeIcon, HICON& smallIcon); + + //! Run the daemon + /*! + Delegates to CArchDaemonWindows. + */ + static int runDaemon(RunFunc runFunc); + + //! Indicate daemon is in main loop + /*! + Delegates to CArchDaemonWindows. + */ + static void daemonRunning(bool running); + + //! Indicate failure of running daemon + /*! + Delegates to CArchDaemonWindows. + */ + static void daemonFailed(int result); + + //! Get daemon quit message + /*! + Delegates to CArchDaemonWindows. + */ + static UINT getDaemonQuitMessage(); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* child); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* const* keyPath); + + //! Open/create and return a registry key, closing the parent key + static HKEY addKey(HKEY parent, const TCHAR* child); + + //! Open/create and return a registry key, closing the parent key + static HKEY addKey(HKEY parent, const TCHAR* const* keyPath); + + //! Close a key + static void closeKey(HKEY); + + //! Delete a key (which should have no subkeys) + static void deleteKey(HKEY parent, const TCHAR* name); + + //! Delete a value + static void deleteValue(HKEY parent, const TCHAR* name); + + //! Test if a value exists + static bool hasValue(HKEY key, const TCHAR* name); + + //! Get type of value + static EValueType typeOfValue(HKEY key, const TCHAR* name); + + //! Set a string value in the registry + static void setValue(HKEY key, const TCHAR* name, + const std::string& value); + + //! Set a DWORD value in the registry + static void setValue(HKEY key, const TCHAR* name, DWORD value); + + //! Set a BINARY value in the registry + /*! + Sets the \p name value of \p key to \p value.data(). + */ + static void setValueBinary(HKEY key, const TCHAR* name, + const std::string& value); + + //! Read a string value from the registry + static std::string readValueString(HKEY, const TCHAR* name); + + //! Read a DWORD value from the registry + static DWORD readValueInt(HKEY, const TCHAR* name); + + //! Read a BINARY value from the registry + static std::string readValueBinary(HKEY, const TCHAR* name); + + //! Add a dialog + static void addDialog(HWND); + + //! Remove a dialog + static void removeDialog(HWND); + + //! Process dialog message + /*! + Checks if the message is destined for a dialog. If so the message + is passed to the dialog and returns true, otherwise returns false. + */ + static bool processDialog(MSG*); + + //! Disable power saving + static void addBusyState(DWORD busyModes); + + //! Enable power saving + static void removeBusyState(DWORD busyModes); + +private: + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* child, bool create); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* const* keyPath, + bool create); + + //! Read a string value from the registry + static std::string readBinaryOrString(HKEY, const TCHAR* name, DWORD type); + + //! Set thread busy state + static void setThreadExecutionState(DWORD); + + static DWORD WINAPI dummySetThreadExecutionState(DWORD); + +private: + typedef std::set CDialogs; + typedef DWORD (WINAPI *STES_t)(DWORD); + + static CDialogs* s_dialogs; + static DWORD s_busyState; + static STES_t s_stes; + static HICON s_largeIcon; + static HICON s_smallIcon; +}; + +#endif diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp new file mode 100644 index 00000000..ec11fc50 --- /dev/null +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -0,0 +1,806 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchMultithreadPosix.h" +#include "CArch.h" +#include "XArch.h" +#include +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#include + +#define SIGWAKEUP SIGUSR1 + +#if !HAVE_PTHREAD_SIGNAL + // boy, is this platform broken. forget about pthread signal + // handling and let signals through to every process. synergy + // will not terminate cleanly when it gets SIGTERM or SIGINT. +# define pthread_sigmask sigprocmask +# define pthread_kill(tid_, sig_) kill(0, (sig_)) +# define sigwait(set_, sig_) +# undef HAVE_POSIX_SIGWAIT +# define HAVE_POSIX_SIGWAIT 1 +#endif + +static +void +setSignalSet(sigset_t* sigset) +{ + sigemptyset(sigset); + sigaddset(sigset, SIGHUP); + sigaddset(sigset, SIGINT); + sigaddset(sigset, SIGTERM); + sigaddset(sigset, SIGUSR2); +} + +// +// CArchThreadImpl +// + +class CArchThreadImpl { +public: + CArchThreadImpl(); + +public: + int m_refCount; + IArchMultithread::ThreadID m_id; + pthread_t m_thread; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + bool m_cancel; + bool m_cancelling; + bool m_exited; + void* m_result; + void* m_networkData; +}; + +CArchThreadImpl::CArchThreadImpl() : + m_refCount(1), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancel(false), + m_cancelling(false), + m_exited(false), + m_result(NULL), + m_networkData(NULL) +{ + // do nothing +} + + +// +// CArchMultithreadPosix +// + +CArchMultithreadPosix* CArchMultithreadPosix::s_instance = NULL; + +CArchMultithreadPosix::CArchMultithreadPosix() : + m_newThreadCalled(false), + m_nextID(0) +{ + assert(s_instance == NULL); + + s_instance = this; + + // no signal handlers + for (size_t i = 0; i < kNUM_SIGNALS; ++i) { + m_signalFunc[i] = NULL; + m_signalUserData[i] = NULL; + } + + // create mutex for thread list + m_threadMutex = newMutex(); + + // create thread for calling (main) thread and add it to our + // list. no need to lock the mutex since we're the only thread. + m_mainThread = new CArchThreadImpl; + m_mainThread->m_thread = pthread_self(); + insert(m_mainThread); + + // install SIGWAKEUP handler. this causes SIGWAKEUP to interrupt + // system calls. we use that when cancelling a thread to force it + // to wake up immediately if it's blocked in a system call. we + // won't need this until another thread is created but it's fine + // to install it now. + struct sigaction act; + sigemptyset(&act.sa_mask); +# if defined(SA_INTERRUPT) + act.sa_flags = SA_INTERRUPT; +# else + act.sa_flags = 0; +# endif + act.sa_handler = &threadCancel; + sigaction(SIGWAKEUP, &act, NULL); + + // set desired signal dispositions. let SIGWAKEUP through but + // ignore SIGPIPE (we'll handle EPIPE). + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGWAKEUP); + pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); + sigemptyset(&sigset); + sigaddset(&sigset, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &sigset, NULL); +} + +CArchMultithreadPosix::~CArchMultithreadPosix() +{ + assert(s_instance != NULL); + + closeMutex(m_threadMutex); + s_instance = NULL; +} + +void +CArchMultithreadPosix::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = find(pthread_self()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +CArchMultithreadPosix::getNetworkDataForThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +CArchMultithreadPosix* +CArchMultithreadPosix::getInstance() +{ + return s_instance; +} + +CArchCond +CArchMultithreadPosix::newCondVar() +{ + CArchCondImpl* cond = new CArchCondImpl; + int status = pthread_cond_init(&cond->m_cond, NULL); + (void)status; + assert(status == 0); + return cond; +} + +void +CArchMultithreadPosix::closeCondVar(CArchCond cond) +{ + int status = pthread_cond_destroy(&cond->m_cond); + (void)status; + assert(status == 0); + delete cond; +} + +void +CArchMultithreadPosix::signalCondVar(CArchCond cond) +{ + int status = pthread_cond_signal(&cond->m_cond); + (void)status; + assert(status == 0); +} + +void +CArchMultithreadPosix::broadcastCondVar(CArchCond cond) +{ + int status = pthread_cond_broadcast(&cond->m_cond); + (void)status; + assert(status == 0); +} + +bool +CArchMultithreadPosix::waitCondVar(CArchCond cond, + CArchMutex mutex, double timeout) +{ + // we can't wait on a condition variable and also wake it up for + // cancellation since we don't use posix cancellation. so we + // must wake up periodically to check for cancellation. we + // can't simply go back to waiting after the check since the + // condition may have changed and we'll have lost the signal. + // so we have to return to the caller. since the caller will + // always check for spurious wakeups the only drawback here is + // performance: we're waking up a lot more than desired. + static const double maxCancellationLatency = 0.1; + if (timeout < 0.0 || timeout > maxCancellationLatency) { + timeout = maxCancellationLatency; + } + + // see if we should cancel this thread + testCancelThread(); + + // get final time + struct timeval now; + gettimeofday(&now, NULL); + struct timespec finalTime; + finalTime.tv_sec = now.tv_sec; + finalTime.tv_nsec = now.tv_usec * 1000; + long timeout_sec = (long)timeout; + long timeout_nsec = (long)(1.0e+9 * (timeout - timeout_sec)); + finalTime.tv_sec += timeout_sec; + finalTime.tv_nsec += timeout_nsec; + if (finalTime.tv_nsec >= 1000000000) { + finalTime.tv_nsec -= 1000000000; + finalTime.tv_sec += 1; + } + + // wait + int status = pthread_cond_timedwait(&cond->m_cond, + &mutex->m_mutex, &finalTime); + + // check for cancel again + testCancelThread(); + + switch (status) { + case 0: + // success + return true; + + case ETIMEDOUT: + return false; + + default: + assert(0 && "condition variable wait error"); + return false; + } +} + +CArchMutex +CArchMultithreadPosix::newMutex() +{ + pthread_mutexattr_t attr; + int status = pthread_mutexattr_init(&attr); + assert(status == 0); + CArchMutexImpl* mutex = new CArchMutexImpl; + status = pthread_mutex_init(&mutex->m_mutex, &attr); + assert(status == 0); + return mutex; +} + +void +CArchMultithreadPosix::closeMutex(CArchMutex mutex) +{ + int status = pthread_mutex_destroy(&mutex->m_mutex); + (void)status; + assert(status == 0); + delete mutex; +} + +void +CArchMultithreadPosix::lockMutex(CArchMutex mutex) +{ + int status = pthread_mutex_lock(&mutex->m_mutex); + + switch (status) { + case 0: + // success + return; + + case EDEADLK: + assert(0 && "lock already owned"); + break; + + case EAGAIN: + assert(0 && "too many recursive locks"); + break; + + default: + assert(0 && "unexpected error"); + break; + } +} + +void +CArchMultithreadPosix::unlockMutex(CArchMutex mutex) +{ + int status = pthread_mutex_unlock(&mutex->m_mutex); + + switch (status) { + case 0: + // success + return; + + case EPERM: + assert(0 && "thread doesn't own a lock"); + break; + + default: + assert(0 && "unexpected error"); + break; + } +} + +CArchThread +CArchMultithreadPosix::newThread(ThreadFunc func, void* data) +{ + assert(func != NULL); + + // initialize signal handler. we do this here instead of the + // constructor so we can avoid daemonizing (using fork()) + // when there are multiple threads. clients can safely + // use condition variables and mutexes before creating a + // new thread and they can safely use the only thread + // they have access to, the main thread, so they really + // can't tell the difference. + if (!m_newThreadCalled) { + m_newThreadCalled = true; +#if HAVE_PTHREAD_SIGNAL + startSignalHandler(); +#endif + } + + lockMutex(m_threadMutex); + + // create thread impl for new thread + CArchThreadImpl* thread = new CArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create the thread. pthread_create() on RedHat 7.2 smp fails + // if passed a NULL attr so use a default attr. + pthread_attr_t attr; + int status = pthread_attr_init(&attr); + if (status == 0) { + status = pthread_create(&thread->m_thread, &attr, + &CArchMultithreadPosix::threadFunc, thread); + pthread_attr_destroy(&attr); + } + + // check if thread was started + if (status != 0) { + // failed to start thread so clean up + delete thread; + thread = NULL; + } + else { + // add thread to list + insert(thread); + + // increment ref count to account for the thread itself + refThread(thread); + } + + // note that the child thread will wait until we release this mutex + unlockMutex(m_threadMutex); + + return thread; +} + +CArchThread +CArchMultithreadPosix::newCurrentThread() +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = find(pthread_self()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +CArchMultithreadPosix::closeThread(CArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // detach from thread (unless it's the main thread) + if (thread->m_func != NULL) { + pthread_detach(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRef(thread->m_thread) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +CArchThread +CArchMultithreadPosix::copyThread(CArchThread thread) +{ + refThread(thread); + return thread; +} + +void +CArchMultithreadPosix::cancelThread(CArchThread thread) +{ + assert(thread != NULL); + + // set cancel and wakeup flags if thread can be cancelled + bool wakeup = false; + lockMutex(m_threadMutex); + if (!thread->m_exited && !thread->m_cancelling) { + thread->m_cancel = true; + wakeup = true; + } + unlockMutex(m_threadMutex); + + // force thread to exit system calls if wakeup is true + if (wakeup) { + pthread_kill(thread->m_thread, SIGWAKEUP); + } +} + +void +CArchMultithreadPosix::setPriorityOfThread(CArchThread thread, int /*n*/) +{ + assert(thread != NULL); + + // FIXME +} + +void +CArchMultithreadPosix::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(pthread_self()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +CArchMultithreadPosix::wait(CArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + CArchThreadImpl* self = findNoRef(pthread_self()); + + // ignore wait if trying to wait on ourself + if (target == self) { + unlockMutex(m_threadMutex); + return false; + } + + // ref the target so it can't go away while we're watching it + refThread(target); + + unlockMutex(m_threadMutex); + + try { + // do first test regardless of timeout + testCancelThreadImpl(self); + if (isExitedThread(target)) { + closeThread(target); + return true; + } + + // wait and repeat test if there's a timeout + if (timeout != 0.0) { + const double start = ARCH->time(); + do { + // wait a little + ARCH->sleep(0.05); + + // repeat test + testCancelThreadImpl(self); + if (isExitedThread(target)) { + closeThread(target); + return true; + } + + // repeat wait and test until timed out + } while (timeout < 0.0 || (ARCH->time() - start) <= timeout); + } + + closeThread(target); + return false; + } + catch (...) { + closeThread(target); + throw; + } +} + +bool +CArchMultithreadPosix::isSameThread(CArchThread thread1, CArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +CArchMultithreadPosix::isExitedThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + bool exited = thread->m_exited; + unlockMutex(m_threadMutex); + return exited; +} + +void* +CArchMultithreadPosix::getResultOfThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +CArchMultithreadPosix::getIDOfThread(CArchThread thread) +{ + return thread->m_id; +} + +void +CArchMultithreadPosix::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadPosix::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + pthread_kill(m_mainThread->m_thread, SIGWAKEUP); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadPosix::startSignalHandler() +{ + // set signal mask. the main thread blocks these signals and + // the signal handler thread will listen for them. + sigset_t sigset, oldsigset; + setSignalSet(&sigset); + pthread_sigmask(SIG_BLOCK, &sigset, &oldsigset); + + // fire up the INT and TERM signal handler thread. we could + // instead arrange to catch and handle these signals but + // we'd be unable to cancel the main thread since no pthread + // calls are allowed in a signal handler. + pthread_attr_t attr; + int status = pthread_attr_init(&attr); + if (status == 0) { + status = pthread_create(&m_signalThread, &attr, + &CArchMultithreadPosix::threadSignalHandler, + NULL); + pthread_attr_destroy(&attr); + } + if (status != 0) { + // can't create thread to wait for signal so don't block + // the signals. + pthread_sigmask(SIG_UNBLOCK, &oldsigset, NULL); + } +} + +CArchThreadImpl* +CArchMultithreadPosix::find(pthread_t thread) +{ + CArchThreadImpl* impl = findNoRef(thread); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +CArchThreadImpl* +CArchMultithreadPosix::findNoRef(pthread_t thread) +{ + // linear search + for (CThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_thread == thread) { + return *index; + } + } + return NULL; +} + +void +CArchMultithreadPosix::insert(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRef(thread->m_thread) == NULL); + + // set thread id. note that we don't worry about m_nextID + // wrapping back to 0 and duplicating thread ID's since the + // likelihood of synergy running that long is vanishingly + // small. + thread->m_id = ++m_nextID; + + // append to list + m_threadList.push_back(thread); +} + +void +CArchMultithreadPosix::erase(CArchThreadImpl* thread) +{ + for (CThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +CArchMultithreadPosix::refThread(CArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRef(thread->m_thread) != NULL); + ++thread->m_refCount; +} + +void +CArchMultithreadPosix::testCancelThreadImpl(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = false; + if (thread->m_cancel && !thread->m_cancelling) { + thread->m_cancelling = true; + thread->m_cancel = false; + cancel = true; + } + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +void* +CArchMultithreadPosix::threadFunc(void* vrep) +{ + // get the thread + CArchThreadImpl* thread = reinterpret_cast(vrep); + + // setup pthreads + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return NULL; +} + +void +CArchMultithreadPosix::doThreadFunc(CArchThread thread) +{ + // default priority is slightly below normal + setPriorityOfThread(thread, 1); + + // wait for parent to initialize this object + lockMutex(m_threadMutex); + unlockMutex(m_threadMutex); + + void* result = NULL; + try { + // go + result = (*thread->m_func)(thread->m_userData); + } + + catch (XThreadCancel&) { + // client called cancel() + } + catch (...) { + // note -- don't catch (...) to avoid masking bugs + lockMutex(m_threadMutex); + thread->m_exited = true; + unlockMutex(m_threadMutex); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + thread->m_exited = true; + unlockMutex(m_threadMutex); + + // done with thread + closeThread(thread); +} + +void +CArchMultithreadPosix::threadCancel(int) +{ + // do nothing +} + +void* +CArchMultithreadPosix::threadSignalHandler(void*) +{ + // detach + pthread_detach(pthread_self()); + + // add signal to mask + sigset_t sigset; + setSignalSet(&sigset); + + // also wait on SIGABRT. on linux (others?) this thread (process) + // will persist after all the other threads evaporate due to an + // assert unless we wait on SIGABRT. that means our resources (like + // the socket we're listening on) are not released and never will be + // until the lingering thread is killed. i don't know why sigwait() + // should protect the thread from being killed. note that sigwait() + // doesn't actually return if we receive SIGABRT and, for some + // reason, we don't have to block SIGABRT. + sigaddset(&sigset, SIGABRT); + + // we exit the loop via thread cancellation in sigwait() + for (;;) { + // wait +#if HAVE_POSIX_SIGWAIT + int signal = 0; + sigwait(&sigset, &signal); +#else + sigwait(&sigset); +#endif + + // if we get here then the signal was raised + switch (signal) { + case SIGINT: + ARCH->raiseSignal(kINTERRUPT); + break; + + case SIGTERM: + ARCH->raiseSignal(kTERMINATE); + break; + + case SIGHUP: + ARCH->raiseSignal(kHANGUP); + break; + + case SIGUSR2: + ARCH->raiseSignal(kUSER); + break; + + default: + // ignore + break; + } + } + + return NULL; +} diff --git a/lib/arch/CArchMultithreadPosix.h b/lib/arch/CArchMultithreadPosix.h new file mode 100644 index 00000000..4e587cfc --- /dev/null +++ b/lib/arch/CArchMultithreadPosix.h @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHMULTITHREADPOSIX_H +#define CARCHMULTITHREADPOSIX_H + +#include "IArchMultithread.h" +#include "stdlist.h" +#include + +#define ARCH_MULTITHREAD CArchMultithreadPosix + +class CArchCondImpl { +public: + pthread_cond_t m_cond; +}; + +class CArchMutexImpl { +public: + pthread_mutex_t m_mutex; +}; + +//! Posix implementation of IArchMultithread +class CArchMultithreadPosix : public IArchMultithread { +public: + CArchMultithreadPosix(); + virtual ~CArchMultithreadPosix(); + + //! @name manipulators + //@{ + + void setNetworkDataForCurrentThread(void*); + + //@} + //! @name accessors + //@{ + + void* getNetworkDataForThread(CArchThread); + + static CArchMultithreadPosix* getInstance(); + + //@} + + // IArchMultithread overrides + virtual CArchCond newCondVar(); + virtual void closeCondVar(CArchCond); + virtual void signalCondVar(CArchCond); + virtual void broadcastCondVar(CArchCond); + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout); + virtual CArchMutex newMutex(); + virtual void closeMutex(CArchMutex); + virtual void lockMutex(CArchMutex); + virtual void unlockMutex(CArchMutex); + virtual CArchThread newThread(ThreadFunc, void*); + virtual CArchThread newCurrentThread(); + virtual CArchThread copyThread(CArchThread); + virtual void closeThread(CArchThread); + virtual void cancelThread(CArchThread); + virtual void setPriorityOfThread(CArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(CArchThread, double timeout); + virtual bool isSameThread(CArchThread, CArchThread); + virtual bool isExitedThread(CArchThread); + virtual void* getResultOfThread(CArchThread); + virtual ThreadID getIDOfThread(CArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + +private: + void startSignalHandler(); + + CArchThreadImpl* find(pthread_t thread); + CArchThreadImpl* findNoRef(pthread_t thread); + void insert(CArchThreadImpl* thread); + void erase(CArchThreadImpl* thread); + + void refThread(CArchThreadImpl* rep); + void testCancelThreadImpl(CArchThreadImpl* rep); + + void doThreadFunc(CArchThread thread); + static void* threadFunc(void* vrep); + static void threadCancel(int); + static void* threadSignalHandler(void* vrep); + +private: + typedef std::list CThreadList; + + static CArchMultithreadPosix* s_instance; + + bool m_newThreadCalled; + + CArchMutex m_threadMutex; + CArchThread m_mainThread; + CThreadList m_threadList; + ThreadID m_nextID; + + pthread_t m_signalThread; + SignalFunc m_signalFunc[kNUM_SIGNALS]; + void* m_signalUserData[kNUM_SIGNALS]; +}; + +#endif diff --git a/lib/arch/CArchMultithreadWindows.cpp b/lib/arch/CArchMultithreadWindows.cpp new file mode 100644 index 00000000..3adcd46a --- /dev/null +++ b/lib/arch/CArchMultithreadWindows.cpp @@ -0,0 +1,699 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#if defined(_MSC_VER) && !defined(_MT) +# error multithreading compile option is required +#endif + +#include "CArchMultithreadWindows.h" +#include "CArch.h" +#include "XArch.h" +#include + +// +// note -- implementation of condition variable taken from: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// titled "Strategies for Implementing POSIX Condition Variables +// on Win32." it also provides an implementation that doesn't +// suffer from the incorrectness problem described in our +// corresponding header but it is slower, still unfair, and +// can cause busy waiting. +// + +// +// CArchThreadImpl +// + +class CArchThreadImpl { +public: + CArchThreadImpl(); + ~CArchThreadImpl(); + +public: + int m_refCount; + HANDLE m_thread; + DWORD m_id; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + HANDLE m_cancel; + bool m_cancelling; + HANDLE m_exit; + void* m_result; + void* m_networkData; +}; + +CArchThreadImpl::CArchThreadImpl() : + m_refCount(1), + m_thread(NULL), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancelling(false), + m_result(NULL), + m_networkData(NULL) +{ + m_exit = CreateEvent(NULL, TRUE, FALSE, NULL); + m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +CArchThreadImpl::~CArchThreadImpl() +{ + CloseHandle(m_exit); + CloseHandle(m_cancel); +} + + +// +// CArchMultithreadWindows +// + +CArchMultithreadWindows* CArchMultithreadWindows::s_instance = NULL; + +CArchMultithreadWindows::CArchMultithreadWindows() +{ + assert(s_instance == NULL); + s_instance = this; + + // no signal handlers + for (size_t i = 0; i < kNUM_SIGNALS; ++i) { + m_signalFunc[i] = NULL; + m_signalUserData[i] = NULL; + } + + // create mutex for thread list + m_threadMutex = newMutex(); + + // create thread for calling (main) thread and add it to our + // list. no need to lock the mutex since we're the only thread. + m_mainThread = new CArchThreadImpl; + m_mainThread->m_thread = NULL; + m_mainThread->m_id = GetCurrentThreadId(); + insert(m_mainThread); +} + +CArchMultithreadWindows::~CArchMultithreadWindows() +{ + s_instance = NULL; + + // clean up thread list + for (CThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + delete *index; + } + + // done with mutex + delete m_threadMutex; +} + +void +CArchMultithreadWindows::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +CArchMultithreadWindows::getNetworkDataForThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +HANDLE +CArchMultithreadWindows::getCancelEventForCurrentThread() +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + return thread->m_cancel; +} + +CArchMultithreadWindows* +CArchMultithreadWindows::getInstance() +{ + return s_instance; +} + +CArchCond +CArchMultithreadWindows::newCondVar() +{ + CArchCondImpl* cond = new CArchCondImpl; + cond->m_events[CArchCondImpl::kSignal] = CreateEvent(NULL, + FALSE, FALSE, NULL); + cond->m_events[CArchCondImpl::kBroadcast] = CreateEvent(NULL, + TRUE, FALSE, NULL); + cond->m_waitCountMutex = newMutex(); + cond->m_waitCount = 0; + return cond; +} + +void +CArchMultithreadWindows::closeCondVar(CArchCond cond) +{ + CloseHandle(cond->m_events[CArchCondImpl::kSignal]); + CloseHandle(cond->m_events[CArchCondImpl::kBroadcast]); + closeMutex(cond->m_waitCountMutex); + delete cond; +} + +void +CArchMultithreadWindows::signalCondVar(CArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake one thread if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[CArchCondImpl::kSignal]); + } +} + +void +CArchMultithreadWindows::broadcastCondVar(CArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake all threads if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[CArchCondImpl::kBroadcast]); + } +} + +bool +CArchMultithreadWindows::waitCondVar(CArchCond cond, + CArchMutex mutex, double timeout) +{ + // prepare to wait + const DWORD winTimeout = (timeout < 0.0) ? INFINITE : + static_cast(1000.0 * timeout); + + // make a list of the condition variable events and the cancel event + // for the current thread. + HANDLE handles[4]; + handles[0] = cond->m_events[CArchCondImpl::kSignal]; + handles[1] = cond->m_events[CArchCondImpl::kBroadcast]; + handles[2] = getCancelEventForCurrentThread(); + + // update waiter count + lockMutex(cond->m_waitCountMutex); + ++cond->m_waitCount; + unlockMutex(cond->m_waitCountMutex); + + // release mutex. this should be atomic with the wait so that it's + // impossible for another thread to signal us between the unlock and + // the wait, which would lead to a lost signal on broadcasts. + // however, we're using a manual reset event for broadcasts which + // stays set until we reset it, so we don't lose the broadcast. + unlockMutex(mutex); + + // wait for a signal or broadcast + DWORD result = WaitForMultipleObjects(3, handles, FALSE, winTimeout); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 2 && + WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 2; + } + + // update the waiter count and check if we're the last waiter + lockMutex(cond->m_waitCountMutex); + --cond->m_waitCount; + const bool last = (result == WAIT_OBJECT_0 + 1 && cond->m_waitCount == 0); + unlockMutex(cond->m_waitCountMutex); + + // reset the broadcast event if we're the last waiter + if (last) { + ResetEvent(cond->m_events[CArchCondImpl::kBroadcast]); + } + + // reacquire the mutex + lockMutex(mutex); + + // cancel thread if necessary + if (result == WAIT_OBJECT_0 + 2) { + ARCH->testCancelThread(); + } + + // return success or failure + return (result == WAIT_OBJECT_0 + 0 || + result == WAIT_OBJECT_0 + 1); +} + +CArchMutex +CArchMultithreadWindows::newMutex() +{ + CArchMutexImpl* mutex = new CArchMutexImpl; + InitializeCriticalSection(&mutex->m_mutex); + return mutex; +} + +void +CArchMultithreadWindows::closeMutex(CArchMutex mutex) +{ + DeleteCriticalSection(&mutex->m_mutex); + delete mutex; +} + +void +CArchMultithreadWindows::lockMutex(CArchMutex mutex) +{ + EnterCriticalSection(&mutex->m_mutex); +} + +void +CArchMultithreadWindows::unlockMutex(CArchMutex mutex) +{ + LeaveCriticalSection(&mutex->m_mutex); +} + +CArchThread +CArchMultithreadWindows::newThread(ThreadFunc func, void* data) +{ + lockMutex(m_threadMutex); + + // create thread impl for new thread + CArchThreadImpl* thread = new CArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create thread + unsigned int id; + thread->m_thread = reinterpret_cast(_beginthreadex(NULL, 0, + threadFunc, (void*)thread, 0, &id)); + thread->m_id = static_cast(id); + + // check if thread was started + if (thread->m_thread == 0) { + // failed to start thread so clean up + delete thread; + thread = NULL; + } + else { + // add thread to list + insert(thread); + + // increment ref count to account for the thread itself + refThread(thread); + } + + // note that the child thread will wait until we release this mutex + unlockMutex(m_threadMutex); + + return thread; +} + +CArchThread +CArchMultithreadWindows::newCurrentThread() +{ + lockMutex(m_threadMutex); + CArchThreadImpl* thread = find(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +CArchMultithreadWindows::closeThread(CArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // close the handle (main thread has a NULL handle) + if (thread->m_thread != NULL) { + CloseHandle(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRefOrCreate(thread->m_id) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +CArchThread +CArchMultithreadWindows::copyThread(CArchThread thread) +{ + refThread(thread); + return thread; +} + +void +CArchMultithreadWindows::cancelThread(CArchThread thread) +{ + assert(thread != NULL); + + // set cancel flag + SetEvent(thread->m_cancel); +} + +void +CArchMultithreadWindows::setPriorityOfThread(CArchThread thread, int n) +{ + struct CPriorityInfo { + public: + DWORD m_class; + int m_level; + }; + static const CPriorityInfo s_pClass[] = { + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL} + }; +#if defined(_DEBUG) + // don't use really high priorities when debugging + static const size_t s_pMax = 13; +#else + static const size_t s_pMax = sizeof(s_pClass) / sizeof(s_pClass[0]) - 1; +#endif + static const size_t s_pBase = 8; // index of normal priority + + assert(thread != NULL); + + size_t index; + if (n > 0 && s_pBase < (size_t)n) { + // lowest priority + index = 0; + } + else { + index = (size_t)((int)s_pBase - n); + if (index > s_pMax) { + // highest priority + index = s_pMax; + } + } + SetPriorityClass(GetCurrentProcess(), s_pClass[index].m_class); + SetThreadPriority(thread->m_thread, s_pClass[index].m_level); +} + +void +CArchMultithreadWindows::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + CArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +CArchMultithreadWindows::wait(CArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + CArchThreadImpl* self = findNoRef(GetCurrentThreadId()); + + // ignore wait if trying to wait on ourself + if (target == self) { + unlockMutex(m_threadMutex); + return false; + } + + // ref the target so it can't go away while we're watching it + refThread(target); + + unlockMutex(m_threadMutex); + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for this thread to be cancelled or woken up or for the + // target thread to terminate. + HANDLE handles[2]; + handles[0] = target->m_exit; + handles[1] = self->m_cancel; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, t); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 1 && + WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 1; + } + + // release target + closeThread(target); + + // handle result + switch (result) { + case WAIT_OBJECT_0 + 0: + // target thread terminated + return true; + + case WAIT_OBJECT_0 + 1: + // this thread was cancelled. does not return. + testCancelThreadImpl(self); + + default: + // timeout or error + return false; + } +} + +bool +CArchMultithreadWindows::isSameThread(CArchThread thread1, CArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +CArchMultithreadWindows::isExitedThread(CArchThread thread) +{ + // poll exit event + return (WaitForSingleObject(thread->m_exit, 0) == WAIT_OBJECT_0); +} + +void* +CArchMultithreadWindows::getResultOfThread(CArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +CArchMultithreadWindows::getIDOfThread(CArchThread thread) +{ + return static_cast(thread->m_id); +} + +void +CArchMultithreadWindows::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +CArchMultithreadWindows::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + ARCH->unblockPollSocket(m_mainThread); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +CArchThreadImpl* +CArchMultithreadWindows::find(DWORD id) +{ + CArchThreadImpl* impl = findNoRef(id); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +CArchThreadImpl* +CArchMultithreadWindows::findNoRef(DWORD id) +{ + CArchThreadImpl* impl = findNoRefOrCreate(id); + if (impl == NULL) { + // create thread for calling thread which isn't in our list and + // add it to the list. this won't normally happen but it can if + // the system calls us under a new thread, like it does when we + // run as a service. + impl = new CArchThreadImpl; + impl->m_thread = NULL; + impl->m_id = GetCurrentThreadId(); + insert(impl); + } + return impl; +} + +CArchThreadImpl* +CArchMultithreadWindows::findNoRefOrCreate(DWORD id) +{ + // linear search + for (CThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_id == id) { + return *index; + } + } + return NULL; +} + +void +CArchMultithreadWindows::insert(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRefOrCreate(thread->m_id) == NULL); + + // append to list + m_threadList.push_back(thread); +} + +void +CArchMultithreadWindows::erase(CArchThreadImpl* thread) +{ + for (CThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +CArchMultithreadWindows::refThread(CArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRefOrCreate(thread->m_id) != NULL); + ++thread->m_refCount; +} + +void +CArchMultithreadWindows::testCancelThreadImpl(CArchThreadImpl* thread) +{ + assert(thread != NULL); + + // poll cancel event. return if not set. + const DWORD result = WaitForSingleObject(thread->m_cancel, 0); + if (result != WAIT_OBJECT_0) { + return; + } + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = !thread->m_cancelling; + thread->m_cancelling = true; + ResetEvent(thread->m_cancel); + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +unsigned int __stdcall +CArchMultithreadWindows::threadFunc(void* vrep) +{ + // get the thread + CArchThreadImpl* thread = reinterpret_cast(vrep); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return 0; +} + +void +CArchMultithreadWindows::doThreadFunc(CArchThread thread) +{ + // wait for parent to initialize this object + lockMutex(m_threadMutex); + unlockMutex(m_threadMutex); + + void* result = NULL; + try { + // go + result = (*thread->m_func)(thread->m_userData); + } + + catch (XThreadCancel&) { + // client called cancel() + } + catch (...) { + // note -- don't catch (...) to avoid masking bugs + SetEvent(thread->m_exit); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + unlockMutex(m_threadMutex); + SetEvent(thread->m_exit); + + // done with thread + closeThread(thread); +} diff --git a/lib/arch/CArchMultithreadWindows.h b/lib/arch/CArchMultithreadWindows.h new file mode 100644 index 00000000..d009c842 --- /dev/null +++ b/lib/arch/CArchMultithreadWindows.h @@ -0,0 +1,115 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHMULTITHREADWINDOWS_H +#define CARCHMULTITHREADWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchMultithread.h" +#include "stdlist.h" +#include + +#define ARCH_MULTITHREAD CArchMultithreadWindows + +class CArchCondImpl { +public: + enum { kSignal = 0, kBroadcast }; + + HANDLE m_events[2]; + mutable int m_waitCount; + CArchMutex m_waitCountMutex; +}; + +class CArchMutexImpl { +public: + CRITICAL_SECTION m_mutex; +}; + +//! Win32 implementation of IArchMultithread +class CArchMultithreadWindows : public IArchMultithread { +public: + CArchMultithreadWindows(); + virtual ~CArchMultithreadWindows(); + + //! @name manipulators + //@{ + + void setNetworkDataForCurrentThread(void*); + + //@} + //! @name accessors + //@{ + + HANDLE getCancelEventForCurrentThread(); + + void* getNetworkDataForThread(CArchThread); + + static CArchMultithreadWindows* getInstance(); + + //@} + + // IArchMultithread overrides + virtual CArchCond newCondVar(); + virtual void closeCondVar(CArchCond); + virtual void signalCondVar(CArchCond); + virtual void broadcastCondVar(CArchCond); + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout); + virtual CArchMutex newMutex(); + virtual void closeMutex(CArchMutex); + virtual void lockMutex(CArchMutex); + virtual void unlockMutex(CArchMutex); + virtual CArchThread newThread(ThreadFunc, void*); + virtual CArchThread newCurrentThread(); + virtual CArchThread copyThread(CArchThread); + virtual void closeThread(CArchThread); + virtual void cancelThread(CArchThread); + virtual void setPriorityOfThread(CArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(CArchThread, double timeout); + virtual bool isSameThread(CArchThread, CArchThread); + virtual bool isExitedThread(CArchThread); + virtual void* getResultOfThread(CArchThread); + virtual ThreadID getIDOfThread(CArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + +private: + CArchThreadImpl* find(DWORD id); + CArchThreadImpl* findNoRef(DWORD id); + CArchThreadImpl* findNoRefOrCreate(DWORD id); + void insert(CArchThreadImpl* thread); + void erase(CArchThreadImpl* thread); + + void refThread(CArchThreadImpl* rep); + void testCancelThreadImpl(CArchThreadImpl* rep); + + void doThreadFunc(CArchThread thread); + static unsigned int __stdcall threadFunc(void* vrep); + +private: + typedef std::list CThreadList; + + static CArchMultithreadWindows* s_instance; + + CArchMutex m_threadMutex; + + CThreadList m_threadList; + CArchThread m_mainThread; + + SignalFunc m_signalFunc[kNUM_SIGNALS]; + void* m_signalUserData[kNUM_SIGNALS]; +}; + +#endif diff --git a/lib/arch/CArchNetworkBSD.cpp b/lib/arch/CArchNetworkBSD.cpp new file mode 100644 index 00000000..af474e86 --- /dev/null +++ b/lib/arch/CArchNetworkBSD.cpp @@ -0,0 +1,974 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchNetworkBSD.h" +#include "CArch.h" +#include "CArchMultithreadPosix.h" +#include "XArchUnix.h" +#if HAVE_UNISTD_H +# include +#endif +#include +#include +#if !defined(TCP_NODELAY) +# include +#endif +#include +#include +#include +#include + +#if HAVE_POLL +# include +#else +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TIME_H +# include +# endif +#endif + +#if !HAVE_INET_ATON +# include +#endif + +static const int s_family[] = { + PF_UNSPEC, + PF_INET +}; +static const int s_type[] = { + SOCK_DGRAM, + SOCK_STREAM +}; + +#if !HAVE_INET_ATON +// parse dotted quad addresses. we don't bother with the weird BSD'ism +// of handling octal and hex and partial forms. +static +in_addr_t +inet_aton(const char* cp, struct in_addr* inp) +{ + unsigned int a, b, c, d; + if (sscanf(cp, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) { + return 0; + } + if (a >= 256 || b >= 256 || c >= 256 || d >= 256) { + return 0; + } + unsigned char* incp = (unsigned char*)inp; + incp[0] = (unsigned char)(a & 0xffu); + incp[1] = (unsigned char)(b & 0xffu); + incp[2] = (unsigned char)(c & 0xffu); + incp[3] = (unsigned char)(d & 0xffu); + return inp->s_addr; +} +#endif + +// +// CArchNetworkBSD +// + +CArchNetworkBSD::CArchNetworkBSD() +{ + // create mutex to make some calls thread safe + m_mutex = ARCH->newMutex(); +} + +CArchNetworkBSD::~CArchNetworkBSD() +{ + ARCH->closeMutex(m_mutex); +} + +CArchSocket +CArchNetworkBSD::newSocket(EAddressFamily family, ESocketType type) +{ + // create socket + int fd = socket(s_family[family], s_type[type], 0); + if (fd == -1) { + throwError(errno); + } + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close(fd); + throw; + } + + // allocate socket object + CArchSocketImpl* newSocket = new CArchSocketImpl; + newSocket->m_fd = fd; + newSocket->m_refCount = 1; + return newSocket; +} + +CArchSocket +CArchNetworkBSD::copySocket(CArchSocket s) +{ + assert(s != NULL); + + // ref the socket and return it + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + return s; +} + +void +CArchNetworkBSD::closeSocket(CArchSocket s) +{ + assert(s != NULL); + + // unref the socket and note if it should be released + ARCH->lockMutex(m_mutex); + const bool doClose = (--s->m_refCount == 0); + ARCH->unlockMutex(m_mutex); + + // close the socket if necessary + if (doClose) { + if (close(s->m_fd) == -1) { + // close failed. restore the last ref and throw. + int err = errno; + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + throwError(err); + } + delete s; + } +} + +void +CArchNetworkBSD::closeSocketForRead(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown(s->m_fd, 0) == -1) { + if (errno != ENOTCONN) { + throwError(errno); + } + } +} + +void +CArchNetworkBSD::closeSocketForWrite(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown(s->m_fd, 1) == -1) { + if (errno != ENOTCONN) { + throwError(errno); + } + } +} + +void +CArchNetworkBSD::bindSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (bind(s->m_fd, &addr->m_addr, addr->m_len) == -1) { + throwError(errno); + } +} + +void +CArchNetworkBSD::listenOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // hardcoding backlog + if (listen(s->m_fd, 3) == -1) { + throwError(errno); + } +} + +CArchSocket +CArchNetworkBSD::acceptSocket(CArchSocket s, CArchNetAddress* addr) +{ + assert(s != NULL); + + // if user passed NULL in addr then use scratch space + CArchNetAddress dummy; + if (addr == NULL) { + addr = &dummy; + } + + // create new socket and address + CArchSocketImpl* newSocket = new CArchSocketImpl; + *addr = new CArchNetAddressImpl; + + // accept on socket + ACCEPT_TYPE_ARG3 len = (ACCEPT_TYPE_ARG3)((*addr)->m_len); + int fd = accept(s->m_fd, &(*addr)->m_addr, &len); + (*addr)->m_len = (socklen_t)len; + if (fd == -1) { + int err = errno; + delete newSocket; + delete *addr; + *addr = NULL; + if (err == EAGAIN) { + return NULL; + } + throwError(err); + } + + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close(fd); + delete newSocket; + delete *addr; + *addr = NULL; + throw; + } + + // initialize socket + newSocket->m_fd = fd; + newSocket->m_refCount = 1; + + // discard address if not requested + if (addr == &dummy) { + ARCH->closeAddr(dummy); + } + + return newSocket; +} + +bool +CArchNetworkBSD::connectSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (connect(s->m_fd, &addr->m_addr, addr->m_len) == -1) { + if (errno == EISCONN) { + return true; + } + if (errno == EINPROGRESS) { + return false; + } + throwError(errno); + } + return true; +} + +#if HAVE_POLL + +int +CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) +{ + assert(pe != NULL || num == 0); + + // return if nothing to do + if (num == 0) { + if (timeout > 0.0) { + ARCH->sleep(timeout); + } + return 0; + } + + // allocate space for translated query + struct pollfd* pfd = new struct pollfd[1 + num]; + + // translate query + for (int i = 0; i < num; ++i) { + pfd[i].fd = (pe[i].m_socket == NULL) ? -1 : pe[i].m_socket->m_fd; + pfd[i].events = 0; + if ((pe[i].m_events & kPOLLIN) != 0) { + pfd[i].events |= POLLIN; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + pfd[i].events |= POLLOUT; + } + } + int n = num; + + // add the unblock pipe + const int* unblockPipe = getUnblockPipe(); + if (unblockPipe != NULL) { + pfd[n].fd = unblockPipe[0]; + pfd[n].events = POLLIN; + ++n; + } + + // prepare timeout + int t = (timeout < 0.0) ? -1 : static_cast(1000.0 * timeout); + + // do the poll + n = poll(pfd, n, t); + + // reset the unblock pipe + if (n > 0 && unblockPipe != NULL && (pfd[num].revents & POLLIN) != 0) { + // the unblock event was signalled. flush the pipe. + char dummy[100]; + do { + read(unblockPipe[0], dummy, sizeof(dummy)); + } while (errno != EAGAIN); + + // don't count this unblock pipe in return value + --n; + } + + // handle results + if (n == -1) { + if (errno == EINTR) { + // interrupted system call + ARCH->testCancelThread(); + delete[] pfd; + return 0; + } + delete[] pfd; + throwError(errno); + } + + // translate back + for (int i = 0; i < num; ++i) { + pe[i].m_revents = 0; + if ((pfd[i].revents & POLLIN) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((pfd[i].revents & POLLOUT) != 0) { + pe[i].m_revents |= kPOLLOUT; + } + if ((pfd[i].revents & POLLERR) != 0) { + pe[i].m_revents |= kPOLLERR; + } + if ((pfd[i].revents & POLLNVAL) != 0) { + pe[i].m_revents |= kPOLLNVAL; + } + } + + delete[] pfd; + return n; +} + +#else + +int +CArchNetworkBSD::pollSocket(CPollEntry pe[], int num, double timeout) +{ + int i, n; + + // prepare sets for select + n = 0; + fd_set readSet, writeSet, errSet; + fd_set* readSetP = NULL; + fd_set* writeSetP = NULL; + fd_set* errSetP = NULL; + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&errSet); + for (i = 0; i < num; ++i) { + // reset return flags + pe[i].m_revents = 0; + + // set invalid flag if socket is bogus then go to next socket + if (pe[i].m_socket == NULL) { + pe[i].m_revents |= kPOLLNVAL; + continue; + } + + int fdi = pe[i].m_socket->m_fd; + if (pe[i].m_events & kPOLLIN) { + FD_SET(pe[i].m_socket->m_fd, &readSet); + readSetP = &readSet; + if (fdi > n) { + n = fdi; + } + } + if (pe[i].m_events & kPOLLOUT) { + FD_SET(pe[i].m_socket->m_fd, &writeSet); + writeSetP = &writeSet; + if (fdi > n) { + n = fdi; + } + } + if (true) { + FD_SET(pe[i].m_socket->m_fd, &errSet); + errSetP = &errSet; + if (fdi > n) { + n = fdi; + } + } + } + + // add the unblock pipe + const int* unblockPipe = getUnblockPipe(); + if (unblockPipe != NULL) { + FD_SET(unblockPipe[0], &readSet); + readSetP = &readSet; + if (unblockPipe[0] > n) { + n = unblockPipe[0]; + } + } + + // if there are no sockets then don't block forever + if (n == 0 && timeout < 0.0) { + timeout = 0.0; + } + + // prepare timeout for select + struct timeval timeout2; + struct timeval* timeout2P; + if (timeout < 0.0) { + timeout2P = NULL; + } + else { + timeout2P = &timeout2; + timeout2.tv_sec = static_cast(timeout); + timeout2.tv_usec = static_cast(1.0e+6 * + (timeout - timeout2.tv_sec)); + } + + // do the select + n = select((SELECT_TYPE_ARG1) n + 1, + SELECT_TYPE_ARG234 readSetP, + SELECT_TYPE_ARG234 writeSetP, + SELECT_TYPE_ARG234 errSetP, + SELECT_TYPE_ARG5 timeout2P); + + // reset the unblock pipe + if (n > 0 && unblockPipe != NULL && FD_ISSET(unblockPipe[0], &readSet)) { + // the unblock event was signalled. flush the pipe. + char dummy[100]; + do { + read(unblockPipe[0], dummy, sizeof(dummy)); + } while (errno != EAGAIN); + } + + // handle results + if (n == -1) { + if (errno == EINTR) { + // interrupted system call + ARCH->testCancelThread(); + return 0; + } + throwError(errno); + } + n = 0; + for (i = 0; i < num; ++i) { + if (pe[i].m_socket != NULL) { + if (FD_ISSET(pe[i].m_socket->m_fd, &readSet)) { + pe[i].m_revents |= kPOLLIN; + } + if (FD_ISSET(pe[i].m_socket->m_fd, &writeSet)) { + pe[i].m_revents |= kPOLLOUT; + } + if (FD_ISSET(pe[i].m_socket->m_fd, &errSet)) { + pe[i].m_revents |= kPOLLERR; + } + } + if (pe[i].m_revents != 0) { + ++n; + } + } + + return n; +} + +#endif + +void +CArchNetworkBSD::unblockPollSocket(CArchThread thread) +{ + const int* unblockPipe = getUnblockPipeForThread(thread); + if (unblockPipe != NULL) { + char dummy = 0; + write(unblockPipe[1], &dummy, 1); + } +} + +size_t +CArchNetworkBSD::readSocket(CArchSocket s, void* buf, size_t len) +{ + assert(s != NULL); + + ssize_t n = read(s->m_fd, buf, len); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + return 0; + } + throwError(errno); + } + return n; +} + +size_t +CArchNetworkBSD::writeSocket(CArchSocket s, const void* buf, size_t len) +{ + assert(s != NULL); + + ssize_t n = write(s->m_fd, buf, len); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + return 0; + } + throwError(errno); + } + return n; +} + +void +CArchNetworkBSD::throwErrorOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // get the error from the socket layer + int err = 0; + socklen_t size = (socklen_t)sizeof(err); + if (getsockopt(s->m_fd, SOL_SOCKET, SO_ERROR, + (optval_t*)&err, &size) == -1) { + err = errno; + } + + // throw if there's an error + if (err != 0) { + throwError(err); + } +} + +void +CArchNetworkBSD::setBlockingOnSocket(int fd, bool blocking) +{ + assert(fd != -1); + + int mode = fcntl(fd, F_GETFL, 0); + if (mode == -1) { + throwError(errno); + } + if (blocking) { + mode &= ~O_NONBLOCK; + } + else { + mode |= O_NONBLOCK; + } + if (fcntl(fd, F_SETFL, mode) == -1) { + throwError(errno); + } +} + +bool +CArchNetworkBSD::setNoDelayOnSocket(CArchSocket s, bool noDelay) +{ + assert(s != NULL); + + // get old state + int oflag; + socklen_t size = (socklen_t)sizeof(oflag); + if (getsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, + (optval_t*)&oflag, &size) == -1) { + throwError(errno); + } + + int flag = noDelay ? 1 : 0; + size = (socklen_t)sizeof(flag); + if (setsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, + (optval_t*)&flag, size) == -1) { + throwError(errno); + } + + return (oflag != 0); +} + +bool +CArchNetworkBSD::setReuseAddrOnSocket(CArchSocket s, bool reuse) +{ + assert(s != NULL); + + // get old state + int oflag; + socklen_t size = (socklen_t)sizeof(oflag); + if (getsockopt(s->m_fd, SOL_SOCKET, SO_REUSEADDR, + (optval_t*)&oflag, &size) == -1) { + throwError(errno); + } + + int flag = reuse ? 1 : 0; + size = (socklen_t)sizeof(flag); + if (setsockopt(s->m_fd, SOL_SOCKET, SO_REUSEADDR, + (optval_t*)&flag, size) == -1) { + throwError(errno); + } + + return (oflag != 0); +} + +std::string +CArchNetworkBSD::getHostName() +{ + char name[256]; + if (gethostname(name, sizeof(name)) == -1) { + name[0] = '\0'; + } + else { + name[sizeof(name) - 1] = '\0'; + } + return name; +} + +CArchNetAddress +CArchNetworkBSD::newAnyAddr(EAddressFamily family) +{ + // allocate address + CArchNetAddressImpl* addr = new CArchNetAddressImpl; + + // fill it in + switch (family) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ipAddr->sin_family = AF_INET; + ipAddr->sin_port = 0; + ipAddr->sin_addr.s_addr = INADDR_ANY; + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + break; + } + + default: + delete addr; + assert(0 && "invalid family"); + } + + return addr; +} + +CArchNetAddress +CArchNetworkBSD::copyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + // allocate and copy address + return new CArchNetAddressImpl(*addr); +} + +CArchNetAddress +CArchNetworkBSD::nameToAddr(const std::string& name) +{ + // allocate address + CArchNetAddressImpl* addr = new CArchNetAddressImpl; + + // try to convert assuming an IPv4 dot notation address + struct sockaddr_in inaddr; + memset(&inaddr, 0, sizeof(inaddr)); + if (inet_aton(name.c_str(), &inaddr.sin_addr) != 0) { + // it's a dot notation address + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + inaddr.sin_family = AF_INET; + inaddr.sin_port = 0; + memcpy(&addr->m_addr, &inaddr, addr->m_len); + } + + else { + // mutexed address lookup (ugh) + ARCH->lockMutex(m_mutex); + struct hostent* info = gethostbyname(name.c_str()); + if (info == NULL) { + ARCH->unlockMutex(m_mutex); + delete addr; + throwNameError(h_errno); + } + + // copy over address (only IPv4 currently supported) + if (info->h_addrtype == AF_INET) { + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + inaddr.sin_family = info->h_addrtype; + inaddr.sin_port = 0; + memcpy(&inaddr.sin_addr, info->h_addr_list[0], + sizeof(inaddr.sin_addr)); + memcpy(&addr->m_addr, &inaddr, addr->m_len); + } + else { + ARCH->unlockMutex(m_mutex); + delete addr; + throw XArchNetworkNameUnsupported( + "The requested name is valid but " + "does not have a supported address family"); + } + + // done with static buffer + ARCH->unlockMutex(m_mutex); + } + + return addr; +} + +void +CArchNetworkBSD::closeAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + delete addr; +} + +std::string +CArchNetworkBSD::addrToName(CArchNetAddress addr) +{ + assert(addr != NULL); + + // mutexed name lookup (ugh) + ARCH->lockMutex(m_mutex); + struct hostent* info = gethostbyaddr( + reinterpret_cast(&addr->m_addr), + addr->m_len, addr->m_addr.sa_family); + if (info == NULL) { + ARCH->unlockMutex(m_mutex); + throwNameError(h_errno); + } + + // save (primary) name + std::string name = info->h_name; + + // done with static buffer + ARCH->unlockMutex(m_mutex); + + return name; +} + +std::string +CArchNetworkBSD::addrToString(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ARCH->lockMutex(m_mutex); + std::string s = inet_ntoa(ipAddr->sin_addr); + ARCH->unlockMutex(m_mutex); + return s; + } + + default: + assert(0 && "unknown address family"); + return ""; + } +} + +IArchNetwork::EAddressFamily +CArchNetworkBSD::getAddrFamily(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (addr->m_addr.sa_family) { + case AF_INET: + return kINET; + + default: + return kUNKNOWN; + } +} + +void +CArchNetworkBSD::setAddrPort(CArchNetAddress addr, int port) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ipAddr->sin_port = htons(port); + break; + } + + default: + assert(0 && "unknown address family"); + break; + } +} + +int +CArchNetworkBSD::getAddrPort(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return ntohs(ipAddr->sin_port); + } + + default: + assert(0 && "unknown address family"); + return 0; + } +} + +bool +CArchNetworkBSD::isAnyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return (ipAddr->sin_addr.s_addr == INADDR_ANY && + addr->m_len == (socklen_t)sizeof(struct sockaddr_in)); + } + + default: + assert(0 && "unknown address family"); + return true; + } +} + +bool +CArchNetworkBSD::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0); +} + +const int* +CArchNetworkBSD::getUnblockPipe() +{ + CArchMultithreadPosix* mt = CArchMultithreadPosix::getInstance(); + CArchThread thread = mt->newCurrentThread(); + const int* p = getUnblockPipeForThread(thread); + ARCH->closeThread(thread); + return p; +} + +const int* +CArchNetworkBSD::getUnblockPipeForThread(CArchThread thread) +{ + CArchMultithreadPosix* mt = CArchMultithreadPosix::getInstance(); + int* unblockPipe = (int*)mt->getNetworkDataForThread(thread); + if (unblockPipe == NULL) { + unblockPipe = new int[2]; + if (pipe(unblockPipe) != -1) { + try { + setBlockingOnSocket(unblockPipe[0], false); + mt->setNetworkDataForCurrentThread(unblockPipe); + } + catch (...) { + delete[] unblockPipe; + unblockPipe = NULL; + } + } + else { + delete[] unblockPipe; + unblockPipe = NULL; + } + } + return unblockPipe; +} + +void +CArchNetworkBSD::throwError(int err) +{ + switch (err) { + case EINTR: + ARCH->testCancelThread(); + throw XArchNetworkInterrupted(new XArchEvalUnix(err)); + + case EACCES: + case EPERM: + throw XArchNetworkAccess(new XArchEvalUnix(err)); + + case ENFILE: + case EMFILE: + case ENODEV: + case ENOBUFS: + case ENOMEM: + case ENETDOWN: +#if defined(ENOSR) + case ENOSR: +#endif + throw XArchNetworkResource(new XArchEvalUnix(err)); + + case EPROTOTYPE: + case EPROTONOSUPPORT: + case EAFNOSUPPORT: + case EPFNOSUPPORT: + case ESOCKTNOSUPPORT: + case EINVAL: + case ENOPROTOOPT: + case EOPNOTSUPP: + case ESHUTDOWN: +#if defined(ENOPKG) + case ENOPKG: +#endif + throw XArchNetworkSupport(new XArchEvalUnix(err)); + + case EIO: + throw XArchNetworkIO(new XArchEvalUnix(err)); + + case EADDRNOTAVAIL: + throw XArchNetworkNoAddress(new XArchEvalUnix(err)); + + case EADDRINUSE: + throw XArchNetworkAddressInUse(new XArchEvalUnix(err)); + + case EHOSTUNREACH: + case ENETUNREACH: + throw XArchNetworkNoRoute(new XArchEvalUnix(err)); + + case ENOTCONN: + throw XArchNetworkNotConnected(new XArchEvalUnix(err)); + + case EPIPE: + throw XArchNetworkShutdown(new XArchEvalUnix(err)); + + case ECONNABORTED: + case ECONNRESET: + throw XArchNetworkDisconnected(new XArchEvalUnix(err)); + + case ECONNREFUSED: + throw XArchNetworkConnectionRefused(new XArchEvalUnix(err)); + + case EHOSTDOWN: + case ETIMEDOUT: + throw XArchNetworkTimedOut(new XArchEvalUnix(err)); + + default: + throw XArchNetwork(new XArchEvalUnix(err)); + } +} + +void +CArchNetworkBSD::throwNameError(int err) +{ + static const char* s_msg[] = { + "The specified host is unknown", + "The requested name is valid but does not have an IP address", + "A non-recoverable name server error occurred", + "A temporary error occurred on an authoritative name server", + "An unknown name server error occurred" + }; + + switch (err) { + case HOST_NOT_FOUND: + throw XArchNetworkNameUnknown(s_msg[0]); + + case NO_DATA: + throw XArchNetworkNameNoAddress(s_msg[1]); + + case NO_RECOVERY: + throw XArchNetworkNameFailure(s_msg[2]); + + case TRY_AGAIN: + throw XArchNetworkNameUnavailable(s_msg[3]); + + default: + throw XArchNetworkName(s_msg[4]); + } +} diff --git a/lib/arch/CArchNetworkBSD.h b/lib/arch/CArchNetworkBSD.h new file mode 100644 index 00000000..bba60272 --- /dev/null +++ b/lib/arch/CArchNetworkBSD.h @@ -0,0 +1,101 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHNETWORKBSD_H +#define CARCHNETWORKBSD_H + +#include "IArchNetwork.h" +#include "IArchMultithread.h" +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_SYS_SOCKET_H +# include +#endif + +#if !HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +// old systems may use char* for [gs]etsockopt()'s optval argument. +// this should be void on modern systems but char is forwards +// compatible so we always use it. +typedef char optval_t; + +#define ARCH_NETWORK CArchNetworkBSD + +class CArchSocketImpl { +public: + int m_fd; + int m_refCount; +}; + +class CArchNetAddressImpl { +public: + CArchNetAddressImpl() : m_len(sizeof(m_addr)) { } + +public: + struct sockaddr m_addr; + socklen_t m_len; +}; + +//! Berkeley (BSD) sockets implementation of IArchNetwork +class CArchNetworkBSD : public IArchNetwork { +public: + CArchNetworkBSD(); + virtual ~CArchNetworkBSD(); + + // IArchNetwork overrides + virtual CArchSocket newSocket(EAddressFamily, ESocketType); + virtual CArchSocket copySocket(CArchSocket s); + virtual void closeSocket(CArchSocket s); + virtual void closeSocketForRead(CArchSocket s); + virtual void closeSocketForWrite(CArchSocket s); + virtual void bindSocket(CArchSocket s, CArchNetAddress addr); + virtual void listenOnSocket(CArchSocket s); + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr); + virtual bool connectSocket(CArchSocket s, CArchNetAddress name); + virtual int pollSocket(CPollEntry[], int num, double timeout); + virtual void unblockPollSocket(CArchThread thread); + virtual size_t readSocket(CArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(CArchSocket); + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse); + virtual std::string getHostName(); + virtual CArchNetAddress newAnyAddr(EAddressFamily); + virtual CArchNetAddress copyAddr(CArchNetAddress); + virtual CArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(CArchNetAddress); + virtual std::string addrToName(CArchNetAddress); + virtual std::string addrToString(CArchNetAddress); + virtual EAddressFamily getAddrFamily(CArchNetAddress); + virtual void setAddrPort(CArchNetAddress, int port); + virtual int getAddrPort(CArchNetAddress); + virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); + +private: + const int* getUnblockPipe(); + const int* getUnblockPipeForThread(CArchThread); + void setBlockingOnSocket(int fd, bool blocking); + void throwError(int); + void throwNameError(int); + +private: + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/arch/CArchNetworkWinsock.cpp b/lib/arch/CArchNetworkWinsock.cpp new file mode 100644 index 00000000..ac40596a --- /dev/null +++ b/lib/arch/CArchNetworkWinsock.cpp @@ -0,0 +1,934 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include "CArchNetworkWinsock.h" +#include "CArch.h" +#include "CArchMultithreadWindows.h" +#include "IArchMultithread.h" +#include "XArchWindows.h" +#include + +static const int s_family[] = { + PF_UNSPEC, + PF_INET +}; +static const int s_type[] = { + SOCK_DGRAM, + SOCK_STREAM +}; + +static SOCKET (PASCAL FAR *accept_winsock)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen); +static int (PASCAL FAR *bind_winsock)(SOCKET s, const struct sockaddr FAR *addr, int namelen); +static int (PASCAL FAR *close_winsock)(SOCKET s); +static int (PASCAL FAR *connect_winsock)(SOCKET s, const struct sockaddr FAR *name, int namelen); +static int (PASCAL FAR *gethostname_winsock)(char FAR * name, int namelen); +static int (PASCAL FAR *getsockerror_winsock)(void); +static int (PASCAL FAR *getsockopt_winsock)(SOCKET s, int level, int optname, void FAR * optval, int FAR *optlen); +static u_short (PASCAL FAR *htons_winsock)(u_short v); +static char FAR * (PASCAL FAR *inet_ntoa_winsock)(struct in_addr in); +static unsigned long (PASCAL FAR *inet_addr_winsock)(const char FAR * cp); +static int (PASCAL FAR *ioctl_winsock)(SOCKET s, int cmd, void FAR * data); +static int (PASCAL FAR *listen_winsock)(SOCKET s, int backlog); +static u_short (PASCAL FAR *ntohs_winsock)(u_short v); +static int (PASCAL FAR *recv_winsock)(SOCKET s, void FAR * buf, int len, int flags); +static int (PASCAL FAR *select_winsock)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout); +static int (PASCAL FAR *send_winsock)(SOCKET s, const void FAR * buf, int len, int flags); +static int (PASCAL FAR *setsockopt_winsock)(SOCKET s, int level, int optname, const void FAR * optval, int optlen); +static int (PASCAL FAR *shutdown_winsock)(SOCKET s, int how); +static SOCKET (PASCAL FAR *socket_winsock)(int af, int type, int protocol); +static struct hostent FAR * (PASCAL FAR *gethostbyaddr_winsock)(const char FAR * addr, int len, int type); +static struct hostent FAR * (PASCAL FAR *gethostbyname_winsock)(const char FAR * name); +static int (PASCAL FAR *WSACleanup_winsock)(void); +static int (PASCAL FAR *WSAFDIsSet_winsock)(SOCKET, fd_set FAR * fdset); +static WSAEVENT (PASCAL FAR *WSACreateEvent_winsock)(void); +static BOOL (PASCAL FAR *WSACloseEvent_winsock)(WSAEVENT); +static BOOL (PASCAL FAR *WSASetEvent_winsock)(WSAEVENT); +static BOOL (PASCAL FAR *WSAResetEvent_winsock)(WSAEVENT); +static int (PASCAL FAR *WSAEventSelect_winsock)(SOCKET, WSAEVENT, long); +static DWORD (PASCAL FAR *WSAWaitForMultipleEvents_winsock)(DWORD, const WSAEVENT FAR*, BOOL, DWORD, BOOL); +static int (PASCAL FAR *WSAEnumNetworkEvents_winsock)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS); + +#undef FD_ISSET +#define FD_ISSET(fd, set) WSAFDIsSet_winsock((SOCKET)(fd), (fd_set FAR *)(set)) + +#define setfunc(var, name, type) var = (type)netGetProcAddress(module, #name) + +static HMODULE s_networkModule = NULL; + +static +FARPROC +netGetProcAddress(HMODULE module, LPCSTR name) +{ + FARPROC func = ::GetProcAddress(module, name); + if (!func) { + throw XArchNetworkSupport(""); + } + return func; +} + +CArchNetAddressImpl* +CArchNetAddressImpl::alloc(size_t size) +{ + size_t totalSize = size + ADDR_HDR_SIZE; + CArchNetAddressImpl* addr = (CArchNetAddressImpl*)malloc(totalSize); + addr->m_len = size; + return addr; +} + + +// +// CArchNetworkWinsock +// + +CArchNetworkWinsock::CArchNetworkWinsock() +{ + static const char* s_library[] = { "ws2_32.dll" }; + + assert(WSACleanup_winsock == NULL); + assert(s_networkModule == NULL); + + // try each winsock library + for (size_t i = 0; i < sizeof(s_library) / sizeof(s_library[0]); ++i) { + try { + init((HMODULE)::LoadLibrary(s_library[i])); + m_mutex = ARCH->newMutex(); + return; + } + catch (XArchNetwork&) { + // ignore + } + } + + // can't initialize any library + throw XArchNetworkSupport("Cannot load winsock library"); +} + +CArchNetworkWinsock::~CArchNetworkWinsock() +{ + if (s_networkModule != NULL) { + WSACleanup_winsock(); + ::FreeLibrary(s_networkModule); + + WSACleanup_winsock = NULL; + s_networkModule = NULL; + } + ARCH->closeMutex(m_mutex); +} + +void +CArchNetworkWinsock::init(HMODULE module) +{ + if (module == NULL) { + throw XArchNetworkSupport(""); + } + + // get startup function address + int (PASCAL FAR *startup)(WORD, LPWSADATA); + setfunc(startup, WSAStartup, int(PASCAL FAR*)(WORD, LPWSADATA)); + + // startup network library + WORD version = MAKEWORD(2 /*major*/, 0 /*minor*/); + WSADATA data; + int err = startup(version, &data); + if (data.wVersion != version) { + throw XArchNetworkSupport(new XArchEvalWinsock(err)); + } + if (err != 0) { + // some other initialization error + throwError(err); + } + + // get function addresses + setfunc(accept_winsock, accept, SOCKET (PASCAL FAR *)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen)); + setfunc(bind_winsock, bind, int (PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *addr, int namelen)); + setfunc(close_winsock, closesocket, int (PASCAL FAR *)(SOCKET s)); + setfunc(connect_winsock, connect, int (PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *name, int namelen)); + setfunc(gethostname_winsock, gethostname, int (PASCAL FAR *)(char FAR * name, int namelen)); + setfunc(getsockerror_winsock, WSAGetLastError, int (PASCAL FAR *)(void)); + setfunc(getsockopt_winsock, getsockopt, int (PASCAL FAR *)(SOCKET s, int level, int optname, void FAR * optval, int FAR *optlen)); + setfunc(htons_winsock, htons, u_short (PASCAL FAR *)(u_short v)); + setfunc(inet_ntoa_winsock, inet_ntoa, char FAR * (PASCAL FAR *)(struct in_addr in)); + setfunc(inet_addr_winsock, inet_addr, unsigned long (PASCAL FAR *)(const char FAR * cp)); + setfunc(ioctl_winsock, ioctlsocket, int (PASCAL FAR *)(SOCKET s, int cmd, void FAR *)); + setfunc(listen_winsock, listen, int (PASCAL FAR *)(SOCKET s, int backlog)); + setfunc(ntohs_winsock, ntohs, u_short (PASCAL FAR *)(u_short v)); + setfunc(recv_winsock, recv, int (PASCAL FAR *)(SOCKET s, void FAR * buf, int len, int flags)); + setfunc(select_winsock, select, int (PASCAL FAR *)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout)); + setfunc(send_winsock, send, int (PASCAL FAR *)(SOCKET s, const void FAR * buf, int len, int flags)); + setfunc(setsockopt_winsock, setsockopt, int (PASCAL FAR *)(SOCKET s, int level, int optname, const void FAR * optval, int optlen)); + setfunc(shutdown_winsock, shutdown, int (PASCAL FAR *)(SOCKET s, int how)); + setfunc(socket_winsock, socket, SOCKET (PASCAL FAR *)(int af, int type, int protocol)); + setfunc(gethostbyaddr_winsock, gethostbyaddr, struct hostent FAR * (PASCAL FAR *)(const char FAR * addr, int len, int type)); + setfunc(gethostbyname_winsock, gethostbyname, struct hostent FAR * (PASCAL FAR *)(const char FAR * name)); + setfunc(WSACleanup_winsock, WSACleanup, int (PASCAL FAR *)(void)); + setfunc(WSAFDIsSet_winsock, __WSAFDIsSet, int (PASCAL FAR *)(SOCKET, fd_set FAR *)); + setfunc(WSACreateEvent_winsock, WSACreateEvent, WSAEVENT (PASCAL FAR *)(void)); + setfunc(WSACloseEvent_winsock, WSACloseEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSASetEvent_winsock, WSASetEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSAResetEvent_winsock, WSAResetEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSAEventSelect_winsock, WSAEventSelect, int (PASCAL FAR *)(SOCKET, WSAEVENT, long)); + setfunc(WSAWaitForMultipleEvents_winsock, WSAWaitForMultipleEvents, DWORD (PASCAL FAR *)(DWORD, const WSAEVENT FAR*, BOOL, DWORD, BOOL)); + setfunc(WSAEnumNetworkEvents_winsock, WSAEnumNetworkEvents, int (PASCAL FAR *)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); + + s_networkModule = module; +} + +CArchSocket +CArchNetworkWinsock::newSocket(EAddressFamily family, ESocketType type) +{ + // create socket + SOCKET fd = socket_winsock(s_family[family], s_type[type], 0); + if (fd == INVALID_SOCKET) { + throwError(getsockerror_winsock()); + } + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close_winsock(fd); + throw; + } + + // allocate socket object + CArchSocketImpl* socket = new CArchSocketImpl; + socket->m_socket = fd; + socket->m_refCount = 1; + socket->m_event = WSACreateEvent_winsock(); + socket->m_pollWrite = true; + return socket; +} + +CArchSocket +CArchNetworkWinsock::copySocket(CArchSocket s) +{ + assert(s != NULL); + + // ref the socket and return it + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + return s; +} + +void +CArchNetworkWinsock::closeSocket(CArchSocket s) +{ + assert(s != NULL); + + // unref the socket and note if it should be released + ARCH->lockMutex(m_mutex); + const bool doClose = (--s->m_refCount == 0); + ARCH->unlockMutex(m_mutex); + + // close the socket if necessary + if (doClose) { + if (close_winsock(s->m_socket) == SOCKET_ERROR) { + // close failed. restore the last ref and throw. + int err = getsockerror_winsock(); + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + throwError(err); + } + WSACloseEvent_winsock(s->m_event); + delete s; + } +} + +void +CArchNetworkWinsock::closeSocketForRead(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown_winsock(s->m_socket, SD_RECEIVE) == SOCKET_ERROR) { + if (getsockerror_winsock() != WSAENOTCONN) { + throwError(getsockerror_winsock()); + } + } +} + +void +CArchNetworkWinsock::closeSocketForWrite(CArchSocket s) +{ + assert(s != NULL); + + if (shutdown_winsock(s->m_socket, SD_SEND) == SOCKET_ERROR) { + if (getsockerror_winsock() != WSAENOTCONN) { + throwError(getsockerror_winsock()); + } + } +} + +void +CArchNetworkWinsock::bindSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (bind_winsock(s->m_socket, &addr->m_addr, addr->m_len) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +void +CArchNetworkWinsock::listenOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // hardcoding backlog + if (listen_winsock(s->m_socket, 3) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +CArchSocket +CArchNetworkWinsock::acceptSocket(CArchSocket s, CArchNetAddress* addr) +{ + assert(s != NULL); + + // create new socket and temporary address + CArchSocketImpl* socket = new CArchSocketImpl; + CArchNetAddress tmp = CArchNetAddressImpl::alloc(sizeof(struct sockaddr)); + + // accept on socket + SOCKET fd = accept_winsock(s->m_socket, &tmp->m_addr, &tmp->m_len); + if (fd == INVALID_SOCKET) { + int err = getsockerror_winsock(); + delete socket; + free(tmp); + *addr = NULL; + if (err == WSAEWOULDBLOCK) { + return NULL; + } + throwError(err); + } + + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close_winsock(fd); + delete socket; + free(tmp); + *addr = NULL; + throw; + } + + // initialize socket + socket->m_socket = fd; + socket->m_refCount = 1; + socket->m_event = WSACreateEvent_winsock(); + socket->m_pollWrite = true; + + // copy address if requested + if (addr != NULL) { + *addr = ARCH->copyAddr(tmp); + } + + free(tmp); + return socket; +} + +bool +CArchNetworkWinsock::connectSocket(CArchSocket s, CArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (connect_winsock(s->m_socket, &addr->m_addr, + addr->m_len) == SOCKET_ERROR) { + if (getsockerror_winsock() == WSAEISCONN) { + return true; + } + if (getsockerror_winsock() == WSAEWOULDBLOCK) { + return false; + } + throwError(getsockerror_winsock()); + } + return true; +} + +int +CArchNetworkWinsock::pollSocket(CPollEntry pe[], int num, double timeout) +{ + int i; + DWORD n; + + // prepare sockets and wait list + bool canWrite = false; + WSAEVENT* events = (WSAEVENT*)alloca((num + 1) * sizeof(WSAEVENT)); + for (i = 0, n = 0; i < num; ++i) { + // reset return flags + pe[i].m_revents = 0; + + // set invalid flag if socket is bogus then go to next socket + if (pe[i].m_socket == NULL) { + pe[i].m_revents |= kPOLLNVAL; + continue; + } + + // select desired events + long socketEvents = 0; + if ((pe[i].m_events & kPOLLIN) != 0) { + socketEvents |= FD_READ | FD_ACCEPT | FD_CLOSE; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + socketEvents |= FD_WRITE | FD_CONNECT | FD_CLOSE; + + // if m_pollWrite is false then we assume the socket is + // writable. winsock doesn't signal writability except + // when the state changes from unwritable. + if (!pe[i].m_socket->m_pollWrite) { + canWrite = true; + pe[i].m_revents |= kPOLLOUT; + } + } + + // if no events then ignore socket + if (socketEvents == 0) { + continue; + } + + // select socket for desired events + WSAEventSelect_winsock(pe[i].m_socket->m_socket, + pe[i].m_socket->m_event, socketEvents); + + // add socket event to wait list + events[n++] = pe[i].m_socket->m_event; + } + + // if no sockets then return immediately + if (n == 0) { + return 0; + } + + // add the unblock event + CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance(); + CArchThread thread = mt->newCurrentThread(); + WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread); + ARCH->closeThread(thread); + if (unblockEvent == NULL) { + unblockEvent = new WSAEVENT; + *unblockEvent = WSACreateEvent_winsock(); + mt->setNetworkDataForCurrentThread(unblockEvent); + } + events[n++] = *unblockEvent; + + // prepare timeout + DWORD t = (timeout < 0.0) ? INFINITE : (DWORD)(1000.0 * timeout); + if (canWrite) { + // if we know we can write then don't block + t = 0; + } + + // wait + DWORD result = WSAWaitForMultipleEvents_winsock(n, events, FALSE, t, FALSE); + + // reset the unblock event + WSAResetEvent_winsock(*unblockEvent); + + // handle results + if (result == WSA_WAIT_FAILED) { + if (getsockerror_winsock() == WSAEINTR) { + // interrupted system call + ARCH->testCancelThread(); + return 0; + } + throwError(getsockerror_winsock()); + } + if (result == WSA_WAIT_TIMEOUT && !canWrite) { + return 0; + } + if (result == WSA_WAIT_EVENT_0 + n - 1) { + // the unblock event was signalled + return 0; + } + for (i = 0, n = 0; i < num; ++i) { + // skip events we didn't check + if (pe[i].m_socket == NULL || + (pe[i].m_events & (kPOLLIN | kPOLLOUT)) == 0) { + continue; + } + + // get events + WSANETWORKEVENTS info; + if (WSAEnumNetworkEvents_winsock(pe[i].m_socket->m_socket, + pe[i].m_socket->m_event, &info) == SOCKET_ERROR) { + continue; + } + if ((info.lNetworkEvents & FD_READ) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((info.lNetworkEvents & FD_ACCEPT) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((info.lNetworkEvents & FD_WRITE) != 0) { + pe[i].m_revents |= kPOLLOUT; + + // socket is now writable so don't bothing polling for + // writable until it becomes unwritable. + pe[i].m_socket->m_pollWrite = false; + } + if ((info.lNetworkEvents & FD_CONNECT) != 0) { + if (info.iErrorCode[FD_CONNECT_BIT] != 0) { + pe[i].m_revents |= kPOLLERR; + } + else { + pe[i].m_revents |= kPOLLOUT; + pe[i].m_socket->m_pollWrite = false; + } + } + if ((info.lNetworkEvents & FD_CLOSE) != 0) { + if (info.iErrorCode[FD_CLOSE_BIT] != 0) { + pe[i].m_revents |= kPOLLERR; + } + else { + if ((pe[i].m_events & kPOLLIN) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + pe[i].m_revents |= kPOLLOUT; + } + } + } + if (pe[i].m_revents != 0) { + ++n; + } + } + + return (int)n; +} + +void +CArchNetworkWinsock::unblockPollSocket(CArchThread thread) +{ + // set the unblock event + CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance(); + WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread); + if (unblockEvent != NULL) { + WSASetEvent_winsock(*unblockEvent); + } +} + +size_t +CArchNetworkWinsock::readSocket(CArchSocket s, void* buf, size_t len) +{ + assert(s != NULL); + + int n = recv_winsock(s->m_socket, buf, len, 0); + if (n == SOCKET_ERROR) { + int err = getsockerror_winsock(); + if (err == WSAEINTR || err == WSAEWOULDBLOCK) { + return 0; + } + throwError(err); + } + return static_cast(n); +} + +size_t +CArchNetworkWinsock::writeSocket(CArchSocket s, const void* buf, size_t len) +{ + assert(s != NULL); + + int n = send_winsock(s->m_socket, buf, len, 0); + if (n == SOCKET_ERROR) { + int err = getsockerror_winsock(); + if (err == WSAEINTR) { + return 0; + } + if (err == WSAEWOULDBLOCK) { + s->m_pollWrite = true; + return 0; + } + throwError(err); + } + return static_cast(n); +} + +void +CArchNetworkWinsock::throwErrorOnSocket(CArchSocket s) +{ + assert(s != NULL); + + // get the error from the socket layer + int err = 0; + int size = sizeof(err); + if (getsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_ERROR, &err, &size) == SOCKET_ERROR) { + err = getsockerror_winsock(); + } + + // throw if there's an error + if (err != 0) { + throwError(err); + } +} + +void +CArchNetworkWinsock::setBlockingOnSocket(SOCKET s, bool blocking) +{ + assert(s != 0); + + int flag = blocking ? 0 : 1; + if (ioctl_winsock(s, FIONBIO, &flag) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +bool +CArchNetworkWinsock::setNoDelayOnSocket(CArchSocket s, bool noDelay) +{ + assert(s != NULL); + + // get old state + BOOL oflag; + int size = sizeof(oflag); + if (getsockopt_winsock(s->m_socket, IPPROTO_TCP, + TCP_NODELAY, &oflag, &size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + // set new state + BOOL flag = noDelay ? 1 : 0; + size = sizeof(flag); + if (setsockopt_winsock(s->m_socket, IPPROTO_TCP, + TCP_NODELAY, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + return (oflag != 0); +} + +bool +CArchNetworkWinsock::setReuseAddrOnSocket(CArchSocket s, bool reuse) +{ + assert(s != NULL); + + // get old state + BOOL oflag; + int size = sizeof(oflag); + if (getsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_REUSEADDR, &oflag, &size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + // set new state + BOOL flag = reuse ? 1 : 0; + size = sizeof(flag); + if (setsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_REUSEADDR, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + return (oflag != 0); +} + +std::string +CArchNetworkWinsock::getHostName() +{ + char name[256]; + if (gethostname_winsock(name, sizeof(name)) == -1) { + name[0] = '\0'; + } + else { + name[sizeof(name) - 1] = '\0'; + } + return name; +} + +CArchNetAddress +CArchNetworkWinsock::newAnyAddr(EAddressFamily family) +{ + CArchNetAddressImpl* addr = NULL; + switch (family) { + case kINET: { + addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + struct sockaddr_in* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + ipAddr->sin_family = AF_INET; + ipAddr->sin_port = 0; + ipAddr->sin_addr.s_addr = INADDR_ANY; + break; + } + + default: + assert(0 && "invalid family"); + } + return addr; +} + +CArchNetAddress +CArchNetworkWinsock::copyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + CArchNetAddressImpl* copy = CArchNetAddressImpl::alloc(addr->m_len); + memcpy(TYPED_ADDR(void, copy), TYPED_ADDR(void, addr), addr->m_len); + return copy; +} + +CArchNetAddress +CArchNetworkWinsock::nameToAddr(const std::string& name) +{ + // allocate address + CArchNetAddressImpl* addr = NULL; + + // try to convert assuming an IPv4 dot notation address + struct sockaddr_in inaddr; + memset(&inaddr, 0, sizeof(inaddr)); + inaddr.sin_family = AF_INET; + inaddr.sin_port = 0; + inaddr.sin_addr.s_addr = inet_addr_winsock(name.c_str()); + if (inaddr.sin_addr.s_addr != INADDR_NONE) { + // it's a dot notation address + addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + memcpy(TYPED_ADDR(void, addr), &inaddr, addr->m_len); + } + + else { + // address lookup + struct hostent* info = gethostbyname_winsock(name.c_str()); + if (info == NULL) { + throwNameError(getsockerror_winsock()); + } + + // copy over address (only IPv4 currently supported) + if (info->h_addrtype == AF_INET) { + addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + memcpy(&inaddr.sin_addr, info->h_addr_list[0], + sizeof(inaddr.sin_addr)); + memcpy(TYPED_ADDR(void, addr), &inaddr, addr->m_len); + } + else { + throw XArchNetworkNameUnsupported( + "The requested name is valid but " + "does not have a supported address family"); + } + } + + return addr; +} + +void +CArchNetworkWinsock::closeAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + free(addr); +} + +std::string +CArchNetworkWinsock::addrToName(CArchNetAddress addr) +{ + assert(addr != NULL); + + // name lookup + struct hostent* info = gethostbyaddr_winsock( + reinterpret_cast(&addr->m_addr), + addr->m_len, addr->m_addr.sa_family); + if (info == NULL) { + throwNameError(getsockerror_winsock()); + } + + // return (primary) name + return info->h_name; +} + +std::string +CArchNetworkWinsock::addrToString(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return inet_ntoa_winsock(ipAddr->sin_addr); + } + + default: + assert(0 && "unknown address family"); + return ""; + } +} + +IArchNetwork::EAddressFamily +CArchNetworkWinsock::getAddrFamily(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (addr->m_addr.sa_family) { + case AF_INET: + return kINET; + + default: + return kUNKNOWN; + } +} + +void +CArchNetworkWinsock::setAddrPort(CArchNetAddress addr, int port) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + ipAddr->sin_port = htons_winsock(static_cast(port)); + break; + } + + default: + assert(0 && "unknown address family"); + break; + } +} + +int +CArchNetworkWinsock::getAddrPort(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return ntohs_winsock(ipAddr->sin_port); + } + + default: + assert(0 && "unknown address family"); + return 0; + } +} + +bool +CArchNetworkWinsock::isAnyAddr(CArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + struct sockaddr_in* ipAddr = + reinterpret_cast(&addr->m_addr); + return (addr->m_len == sizeof(struct sockaddr_in) && + ipAddr->sin_addr.s_addr == INADDR_ANY); + } + + default: + assert(0 && "unknown address family"); + return true; + } +} + +bool +CArchNetworkWinsock::isEqualAddr(CArchNetAddress a, CArchNetAddress b) +{ + return (a == b || (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0)); +} + +void +CArchNetworkWinsock::throwError(int err) +{ + switch (err) { + case WSAEACCES: + throw XArchNetworkAccess(new XArchEvalWinsock(err)); + + case WSAEMFILE: + case WSAENOBUFS: + case WSAENETDOWN: + throw XArchNetworkResource(new XArchEvalWinsock(err)); + + case WSAEPROTOTYPE: + case WSAEPROTONOSUPPORT: + case WSAEAFNOSUPPORT: + case WSAEPFNOSUPPORT: + case WSAESOCKTNOSUPPORT: + case WSAEINVAL: + case WSAENOPROTOOPT: + case WSAEOPNOTSUPP: + case WSAESHUTDOWN: + case WSANOTINITIALISED: + case WSAVERNOTSUPPORTED: + case WSASYSNOTREADY: + throw XArchNetworkSupport(new XArchEvalWinsock(err)); + + case WSAEADDRNOTAVAIL: + throw XArchNetworkNoAddress(new XArchEvalWinsock(err)); + + case WSAEADDRINUSE: + throw XArchNetworkAddressInUse(new XArchEvalWinsock(err)); + + case WSAEHOSTUNREACH: + case WSAENETUNREACH: + throw XArchNetworkNoRoute(new XArchEvalWinsock(err)); + + case WSAENOTCONN: + throw XArchNetworkNotConnected(new XArchEvalWinsock(err)); + + case WSAEDISCON: + throw XArchNetworkShutdown(new XArchEvalWinsock(err)); + + case WSAENETRESET: + case WSAECONNABORTED: + case WSAECONNRESET: + throw XArchNetworkDisconnected(new XArchEvalWinsock(err)); + + case WSAECONNREFUSED: + throw XArchNetworkConnectionRefused(new XArchEvalWinsock(err)); + + case WSAEHOSTDOWN: + case WSAETIMEDOUT: + throw XArchNetworkTimedOut(new XArchEvalWinsock(err)); + + case WSAHOST_NOT_FOUND: + throw XArchNetworkNameUnknown(new XArchEvalWinsock(err)); + + case WSANO_DATA: + throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err)); + + case WSANO_RECOVERY: + throw XArchNetworkNameFailure(new XArchEvalWinsock(err)); + + case WSATRY_AGAIN: + throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err)); + + default: + throw XArchNetwork(new XArchEvalWinsock(err)); + } +} + +void +CArchNetworkWinsock::throwNameError(int err) +{ + switch (err) { + case WSAHOST_NOT_FOUND: + throw XArchNetworkNameUnknown(new XArchEvalWinsock(err)); + + case WSANO_DATA: + throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err)); + + case WSANO_RECOVERY: + throw XArchNetworkNameFailure(new XArchEvalWinsock(err)); + + case WSATRY_AGAIN: + throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err)); + + default: + throw XArchNetworkName(new XArchEvalWinsock(err)); + } +} diff --git a/lib/arch/CArchNetworkWinsock.h b/lib/arch/CArchNetworkWinsock.h new file mode 100644 index 00000000..3912ba5b --- /dev/null +++ b/lib/arch/CArchNetworkWinsock.h @@ -0,0 +1,99 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHNETWORKWINSOCK_H +#define CARCHNETWORKWINSOCK_H + +#define WIN32_LEAN_AND_MEAN + +// declare no functions in winsock2 +#define INCL_WINSOCK_API_PROTOTYPES 0 +#define INCL_WINSOCK_API_TYPEDEFS 0 + +#include "IArchNetwork.h" +#include "IArchMultithread.h" +#include +#include + +#define ARCH_NETWORK CArchNetworkWinsock + +class CArchSocketImpl { +public: + SOCKET m_socket; + int m_refCount; + WSAEVENT m_event; + bool m_pollWrite; +}; + +class CArchNetAddressImpl { +public: + static CArchNetAddressImpl* alloc(size_t); + +public: + int m_len; + struct sockaddr m_addr; +}; +#define ADDR_HDR_SIZE offsetof(CArchNetAddressImpl, m_addr) +#define TYPED_ADDR(type_, addr_) (reinterpret_cast(&addr_->m_addr)) + +//! Win32 implementation of IArchNetwork +class CArchNetworkWinsock : public IArchNetwork { +public: + CArchNetworkWinsock(); + virtual ~CArchNetworkWinsock(); + + // IArchNetwork overrides + virtual CArchSocket newSocket(EAddressFamily, ESocketType); + virtual CArchSocket copySocket(CArchSocket s); + virtual void closeSocket(CArchSocket s); + virtual void closeSocketForRead(CArchSocket s); + virtual void closeSocketForWrite(CArchSocket s); + virtual void bindSocket(CArchSocket s, CArchNetAddress addr); + virtual void listenOnSocket(CArchSocket s); + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr); + virtual bool connectSocket(CArchSocket s, CArchNetAddress name); + virtual int pollSocket(CPollEntry[], int num, double timeout); + virtual void unblockPollSocket(CArchThread thread); + virtual size_t readSocket(CArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(CArchSocket); + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse); + virtual std::string getHostName(); + virtual CArchNetAddress newAnyAddr(EAddressFamily); + virtual CArchNetAddress copyAddr(CArchNetAddress); + virtual CArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(CArchNetAddress); + virtual std::string addrToName(CArchNetAddress); + virtual std::string addrToString(CArchNetAddress); + virtual EAddressFamily getAddrFamily(CArchNetAddress); + virtual void setAddrPort(CArchNetAddress, int port); + virtual int getAddrPort(CArchNetAddress); + virtual bool isAnyAddr(CArchNetAddress); + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress); + +private: + void init(HMODULE); + + void setBlockingOnSocket(SOCKET, bool blocking); + + void throwError(int); + void throwNameError(int); + +private: + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/arch/CArchSleepUnix.cpp b/lib/arch/CArchSleepUnix.cpp new file mode 100644 index 00000000..35010721 --- /dev/null +++ b/lib/arch/CArchSleepUnix.cpp @@ -0,0 +1,88 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchSleepUnix.h" +#include "CArch.h" +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#if !HAVE_NANOSLEEP +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TYPES_H +# include +# endif +# if HAVE_UNISTD_H +# include +# endif +#endif + +// +// CArchSleepUnix +// + +CArchSleepUnix::CArchSleepUnix() +{ + // do nothing +} + +CArchSleepUnix::~CArchSleepUnix() +{ + // do nothing +} + +void +CArchSleepUnix::sleep(double timeout) +{ + ARCH->testCancelThread(); + if (timeout < 0.0) { + return; + } + +#if HAVE_NANOSLEEP + // prep timeout + struct timespec t; + t.tv_sec = (long)timeout; + t.tv_nsec = (long)(1.0e+9 * (timeout - (double)t.tv_sec)); + + // wait + while (nanosleep(&t, &t) < 0) + ARCH->testCancelThread(); +#else + /* emulate nanosleep() with select() */ + double startTime = ARCH->time(); + double timeLeft = timeout; + while (timeLeft > 0.0) { + struct timeval timeout2; + timeout2.tv_sec = static_cast(timeLeft); + timeout2.tv_usec = static_cast(1.0e+6 * (timeLeft - + timeout2.tv_sec)); + select((SELECT_TYPE_ARG1) 0, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 &timeout2); + ARCH->testCancelThread(); + timeLeft = timeout - (ARCH->time() - startTime); + } +#endif +} diff --git a/lib/arch/CArchSleepUnix.h b/lib/arch/CArchSleepUnix.h new file mode 100644 index 00000000..939ca401 --- /dev/null +++ b/lib/arch/CArchSleepUnix.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSLEEPUNIX_H +#define CARCHSLEEPUNIX_H + +#include "IArchSleep.h" + +#define ARCH_SLEEP CArchSleepUnix + +//! Unix implementation of IArchSleep +class CArchSleepUnix : public IArchSleep { +public: + CArchSleepUnix(); + virtual ~CArchSleepUnix(); + + // IArchSleep overrides + virtual void sleep(double timeout); +}; + +#endif diff --git a/lib/arch/CArchSleepWindows.cpp b/lib/arch/CArchSleepWindows.cpp new file mode 100644 index 00000000..f6c8bed8 --- /dev/null +++ b/lib/arch/CArchSleepWindows.cpp @@ -0,0 +1,57 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchSleepWindows.h" +#include "CArch.h" +#include "CArchMultithreadWindows.h" + +// +// CArchSleepWindows +// + +CArchSleepWindows::CArchSleepWindows() +{ + // do nothing +} + +CArchSleepWindows::~CArchSleepWindows() +{ + // do nothing +} + +void +CArchSleepWindows::sleep(double timeout) +{ + ARCH->testCancelThread(); + if (timeout < 0.0) { + return; + } + + // get the cancel event from the current thread. this only + // works if we're using the windows multithread object but + // this is windows so that's pretty certain; we'll get a + // link error if we're not, though. + CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance(); + if (mt != NULL) { + HANDLE cancelEvent = mt->getCancelEventForCurrentThread(); + WaitForSingleObject(cancelEvent, (DWORD)(1000.0 * timeout)); + if (timeout == 0.0) { + Sleep(0); + } + } + else { + Sleep((DWORD)(1000.0 * timeout)); + } + ARCH->testCancelThread(); +} diff --git a/lib/arch/CArchSleepWindows.h b/lib/arch/CArchSleepWindows.h new file mode 100644 index 00000000..a5a5fa90 --- /dev/null +++ b/lib/arch/CArchSleepWindows.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSLEEPWINDOWS_H +#define CARCHSLEEPWINDOWS_H + +#include "IArchSleep.h" + +#define ARCH_SLEEP CArchSleepWindows + +//! Win32 implementation of IArchSleep +class CArchSleepWindows : public IArchSleep { +public: + CArchSleepWindows(); + virtual ~CArchSleepWindows(); + + // IArchSleep overrides + virtual void sleep(double timeout); +}; + +#endif diff --git a/lib/arch/CArchStringUnix.cpp b/lib/arch/CArchStringUnix.cpp new file mode 100644 index 00000000..e0ad3457 --- /dev/null +++ b/lib/arch/CArchStringUnix.cpp @@ -0,0 +1,29 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchStringUnix.h" +#include + +// +// CArchStringUnix +// + +#include "CMultibyte.cpp" +#include "vsnprintf.cpp" + +IArchString::EWideCharEncoding +CArchStringUnix::getWideCharEncoding() +{ + return kUCS4; +} diff --git a/lib/arch/CArchStringUnix.h b/lib/arch/CArchStringUnix.h new file mode 100644 index 00000000..20e5486b --- /dev/null +++ b/lib/arch/CArchStringUnix.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSTRINGUNIX_H +#define CARCHSTRINGUNIX_H + +#include "IArchString.h" + +#define ARCH_STRING CArchStringUnix + +//! Unix implementation of IArchString +class CArchStringUnix : public IArchString { +public: + CArchStringUnix(); + virtual ~CArchStringUnix(); + + // IArchString overrides + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + virtual EWideCharEncoding + getWideCharEncoding(); +}; + +#endif diff --git a/lib/arch/CArchStringWindows.cpp b/lib/arch/CArchStringWindows.cpp new file mode 100644 index 00000000..a3b90765 --- /dev/null +++ b/lib/arch/CArchStringWindows.cpp @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define WIN32_LEAN_AND_MEAN + +#include "CArchStringWindows.h" +#include +#include + +// +// CArchStringWindows +// + +#include "CMultibyte.cpp" +#define HAVE_VSNPRINTF 1 +#define ARCH_VSNPRINTF _vsnprintf +#include "vsnprintf.cpp" + +IArchString::EWideCharEncoding +CArchStringWindows::getWideCharEncoding() +{ + return kUTF16; +} diff --git a/lib/arch/CArchStringWindows.h b/lib/arch/CArchStringWindows.h new file mode 100644 index 00000000..a67d8431 --- /dev/null +++ b/lib/arch/CArchStringWindows.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSTRINGWINDOWS_H +#define CARCHSTRINGWINDOWS_H + +#include "IArchString.h" + +#define ARCH_STRING CArchStringWindows + +//! Win32 implementation of IArchString +class CArchStringWindows : public IArchString { +public: + CArchStringWindows(); + virtual ~CArchStringWindows(); + + // IArchString overrides + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + virtual EWideCharEncoding + getWideCharEncoding(); +}; + +#endif diff --git a/lib/arch/CArchSystemUnix.cpp b/lib/arch/CArchSystemUnix.cpp new file mode 100644 index 00000000..541038db --- /dev/null +++ b/lib/arch/CArchSystemUnix.cpp @@ -0,0 +1,50 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchSystemUnix.h" +#include + +// +// CArchSystemUnix +// + +CArchSystemUnix::CArchSystemUnix() +{ + // do nothing +} + +CArchSystemUnix::~CArchSystemUnix() +{ + // do nothing +} + +std::string +CArchSystemUnix::getOSName() const +{ +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname info; + if (uname(&info) == 0) { + std::string msg; + msg += info.sysname; + msg += " "; + msg += info.release; + msg += " "; + msg += info.version; + msg += " "; + msg += info.machine; + return msg; + } +#endif + return "Unix "; +} diff --git a/lib/arch/CArchSystemUnix.h b/lib/arch/CArchSystemUnix.h new file mode 100644 index 00000000..525aed1c --- /dev/null +++ b/lib/arch/CArchSystemUnix.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSYSTEMUNIX_H +#define CARCHSYSTEMUNIX_H + +#include "IArchSystem.h" + +#define ARCH_SYSTEM CArchSystemUnix + +//! Unix implementation of IArchString +class CArchSystemUnix : public IArchSystem { +public: + CArchSystemUnix(); + virtual ~CArchSystemUnix(); + + // IArchSystem overrides + virtual std::string getOSName() const; +}; + +#endif diff --git a/lib/arch/CArchSystemWindows.cpp b/lib/arch/CArchSystemWindows.cpp new file mode 100644 index 00000000..b634b4bc --- /dev/null +++ b/lib/arch/CArchSystemWindows.cpp @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define WIN32_LEAN_AND_MEAN + +#include "CArchSystemWindows.h" +#include + +// +// CArchSystemWindows +// + +CArchSystemWindows::CArchSystemWindows() +{ + // do nothing +} + +CArchSystemWindows::~CArchSystemWindows() +{ + // do nothing +} + +std::string +CArchSystemWindows::getOSName() const +{ + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + switch (info.dwPlatformId) { + case VER_PLATFORM_WIN32_NT: + if (info.dwMajorVersion == 5 && info.dwMinorVersion == 2) { + return "Microsoft Windows Server 2003"; + } + if (info.dwMajorVersion == 5 && info.dwMinorVersion == 1) { + return "Microsoft Windows Server XP"; + } + if (info.dwMajorVersion == 5 && info.dwMinorVersion == 0) { + return "Microsoft Windows Server 2000"; + } + if (info.dwMajorVersion <= 4) { + return "Microsoft Windows NT"; + } + char buffer[100]; + sprintf(buffer, "Microsoft Windows %d.%d", + info.dwMajorVersion, info.dwMinorVersion); + return buffer; + + case VER_PLATFORM_WIN32_WINDOWS: + if (info.dwMajorVersion == 4 && info.dwMinorVersion == 0) { + if (info.szCSDVersion[1] == 'C' || + info.szCSDVersion[1] == 'B') { + return "Microsoft Windows 95 OSR2"; + } + return "Microsoft Windows 95"; + } + if (info.dwMajorVersion == 4 && info.dwMinorVersion == 10) { + if (info.szCSDVersion[1] == 'A') { + return "Microsoft Windows 98 SE"; + } + return "Microsoft Windows 98"; + } + if (info.dwMajorVersion == 4 && info.dwMinorVersion == 90) { + return "Microsoft Windows ME"; + } + if (info.dwMajorVersion == 4) { + return "Microsoft Windows unknown 95 family"; + } + break; + + default: + break; + } + } + return "Microsoft Windows "; +} diff --git a/lib/arch/CArchSystemWindows.h b/lib/arch/CArchSystemWindows.h new file mode 100644 index 00000000..e23913d0 --- /dev/null +++ b/lib/arch/CArchSystemWindows.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHSYSTEMWINDOWS_H +#define CARCHSYSTEMWINDOWS_H + +#include "IArchSystem.h" + +#define ARCH_SYSTEM CArchSystemWindows + +//! Win32 implementation of IArchString +class CArchSystemWindows : public IArchSystem { +public: + CArchSystemWindows(); + virtual ~CArchSystemWindows(); + + // IArchSystem overrides + virtual std::string getOSName() const; +}; + +#endif diff --git a/lib/arch/CArchTaskBarWindows.cpp b/lib/arch/CArchTaskBarWindows.cpp new file mode 100644 index 00000000..29c57b66 --- /dev/null +++ b/lib/arch/CArchTaskBarWindows.cpp @@ -0,0 +1,492 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchTaskBarWindows.h" +#include "CArchMiscWindows.h" +#include "IArchTaskBarReceiver.h" +#include "CArch.h" +#include "XArch.h" +#include +#include + +static const UINT kAddReceiver = WM_USER + 10; +static const UINT kRemoveReceiver = WM_USER + 11; +static const UINT kUpdateReceiver = WM_USER + 12; +static const UINT kNotifyReceiver = WM_USER + 13; +static const UINT kFirstReceiverID = WM_USER + 14; + +// +// CArchTaskBarWindows +// + +CArchTaskBarWindows* CArchTaskBarWindows::s_instance = NULL; +HINSTANCE CArchTaskBarWindows::s_appInstance = NULL; + +CArchTaskBarWindows::CArchTaskBarWindows(void* appInstance) : + m_nextID(kFirstReceiverID) +{ + // save the singleton instance + s_instance = this; + + // save app instance + s_appInstance = reinterpret_cast(appInstance); + + // we need a mutex + m_mutex = ARCH->newMutex(); + + // and a condition variable which uses the above mutex + m_ready = false; + m_condVar = ARCH->newCondVar(); + + // we're going to want to get a result from the thread we're + // about to create to know if it initialized successfully. + // so we lock the condition variable. + ARCH->lockMutex(m_mutex); + + // open a window and run an event loop in a separate thread. + // this has to happen in a separate thread because if we + // create a window on the current desktop with the current + // thread then the current thread won't be able to switch + // desktops if it needs to. + m_thread = ARCH->newThread(&CArchTaskBarWindows::threadEntry, this); + + // wait for child thread + while (!m_ready) { + ARCH->waitCondVar(m_condVar, m_mutex, -1.0); + } + + // ready + ARCH->unlockMutex(m_mutex); +} + +CArchTaskBarWindows::~CArchTaskBarWindows() +{ + if (m_thread != NULL) { + PostMessage(m_hwnd, WM_QUIT, 0, 0); + ARCH->wait(m_thread, -1.0); + ARCH->closeThread(m_thread); + } + ARCH->closeCondVar(m_condVar); + ARCH->closeMutex(m_mutex); + s_instance = NULL; +} + +void +CArchTaskBarWindows::addDialog(HWND hwnd) +{ + CArchMiscWindows::addDialog(hwnd); +} + +void +CArchTaskBarWindows::removeDialog(HWND hwnd) +{ + CArchMiscWindows::removeDialog(hwnd); +} + +void +CArchTaskBarWindows::addReceiver(IArchTaskBarReceiver* receiver) +{ + // ignore bogus receiver + if (receiver == NULL) { + return; + } + + // add receiver if necessary + CReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + // add it, creating a new message ID for it + CReceiverInfo info; + info.m_id = getNextID(); + index = m_receivers.insert(std::make_pair(receiver, info)).first; + + // add ID to receiver mapping + m_idTable.insert(std::make_pair(info.m_id, index)); + } + + // add receiver + PostMessage(m_hwnd, kAddReceiver, index->second.m_id, 0); +} + +void +CArchTaskBarWindows::removeReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + CReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + return; + } + + // remove icon. wait for this to finish before returning. + SendMessage(m_hwnd, kRemoveReceiver, index->second.m_id, 0); + + // recycle the ID + recycleID(index->second.m_id); + + // discard + m_idTable.erase(index->second.m_id); + m_receivers.erase(index); +} + +void +CArchTaskBarWindows::updateReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + CReceiverToInfoMap::const_iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + return; + } + + // update icon and tool tip + PostMessage(m_hwnd, kUpdateReceiver, index->second.m_id, 0); +} + +UINT +CArchTaskBarWindows::getNextID() +{ + if (m_oldIDs.empty()) { + return m_nextID++; + } + UINT id = m_oldIDs.back(); + m_oldIDs.pop_back(); + return id; +} + +void +CArchTaskBarWindows::recycleID(UINT id) +{ + m_oldIDs.push_back(id); +} + +void +CArchTaskBarWindows::addIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + CIDToReceiverMap::const_iterator index = m_idTable.find(id); + if (index != m_idTable.end()) { + modifyIconNoLock(index->second, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::removeIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + removeIconNoLock(id); + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::updateIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + CIDToReceiverMap::const_iterator index = m_idTable.find(id); + if (index != m_idTable.end()) { + modifyIconNoLock(index->second, NIM_MODIFY); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::addAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + modifyIconNoLock(index, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::removeAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + removeIconNoLock(index->second.m_id); + } + ARCH->unlockMutex(m_mutex); +} + +void +CArchTaskBarWindows::modifyIconNoLock( + CReceiverToInfoMap::const_iterator index, DWORD taskBarMessage) +{ + // get receiver + UINT id = index->second.m_id; + IArchTaskBarReceiver* receiver = index->first; + + // lock receiver so icon and tool tip are guaranteed to be consistent + receiver->lock(); + + // get icon data + HICON icon = reinterpret_cast( + const_cast(receiver->getIcon())); + + // get tool tip + std::string toolTip = receiver->getToolTip(); + + // done querying + receiver->unlock(); + + // prepare to add icon + NOTIFYICONDATA data; + data.cbSize = sizeof(NOTIFYICONDATA); + data.hWnd = m_hwnd; + data.uID = id; + data.uFlags = NIF_MESSAGE; + data.uCallbackMessage = kNotifyReceiver; + data.hIcon = icon; + if (icon != NULL) { + data.uFlags |= NIF_ICON; + } + if (!toolTip.empty()) { + strncpy(data.szTip, toolTip.c_str(), sizeof(data.szTip)); + data.szTip[sizeof(data.szTip) - 1] = '\0'; + data.uFlags |= NIF_TIP; + } + else { + data.szTip[0] = '\0'; + } + + // add icon + if (Shell_NotifyIcon(taskBarMessage, &data) == 0) { + // failed + } +} + +void +CArchTaskBarWindows::removeIconNoLock(UINT id) +{ + NOTIFYICONDATA data; + data.cbSize = sizeof(NOTIFYICONDATA); + data.hWnd = m_hwnd; + data.uID = id; + if (Shell_NotifyIcon(NIM_DELETE, &data) == 0) { + // failed + } +} + +void +CArchTaskBarWindows::handleIconMessage( + IArchTaskBarReceiver* receiver, LPARAM lParam) +{ + // process message + switch (lParam) { + case WM_LBUTTONDOWN: + receiver->showStatus(); + break; + + case WM_LBUTTONDBLCLK: + receiver->primaryAction(); + break; + + case WM_RBUTTONUP: { + POINT p; + GetCursorPos(&p); + receiver->runMenu(p.x, p.y); + break; + } + + case WM_MOUSEMOVE: + // currently unused + break; + + default: + // unused + break; + } +} + +bool +CArchTaskBarWindows::processDialogs(MSG* msg) +{ + // only one thread can be in this method on any particular object + // at any given time. that's not a problem since only our event + // loop calls this method and there's just one of those. + + ARCH->lockMutex(m_mutex); + + // remove removed dialogs + m_dialogs.erase(false); + + // merge added dialogs into the dialog list + for (CDialogs::const_iterator index = m_addedDialogs.begin(); + index != m_addedDialogs.end(); ++index) { + m_dialogs.insert(std::make_pair(index->first, index->second)); + } + m_addedDialogs.clear(); + + ARCH->unlockMutex(m_mutex); + + // check message against all dialogs until one handles it. + // note that we don't hold a lock while checking because + // the message is processed and may make calls to this + // object. that's okay because addDialog() and + // removeDialog() don't change the map itself (just the + // values of some elements). + ARCH->lockMutex(m_mutex); + for (CDialogs::const_iterator index = m_dialogs.begin(); + index != m_dialogs.end(); ++index) { + if (index->second) { + ARCH->unlockMutex(m_mutex); + if (IsDialogMessage(index->first, msg)) { + return true; + } + ARCH->lockMutex(m_mutex); + } + } + ARCH->unlockMutex(m_mutex); + + return false; +} + +LRESULT +CArchTaskBarWindows::wndProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case kNotifyReceiver: { + // lookup receiver + CIDToReceiverMap::const_iterator index = m_idTable.find(wParam); + if (index != m_idTable.end()) { + IArchTaskBarReceiver* receiver = index->second->first; + handleIconMessage(receiver, lParam); + return 0; + } + break; + } + + case kAddReceiver: + addIcon(wParam); + break; + + case kRemoveReceiver: + removeIcon(wParam); + break; + + case kUpdateReceiver: + updateIcon(wParam); + break; + + default: + if (msg == m_taskBarRestart) { + // task bar was recreated so re-add our icons + addAllIcons(); + } + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CArchTaskBarWindows::staticWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_NCCREATE, extract the CArchTaskBarWindows* and put + // it in the extra window data then forward the call. + CArchTaskBarWindows* self = NULL; + if (msg == WM_NCCREATE) { + CREATESTRUCT* createInfo; + createInfo = reinterpret_cast(lParam); + self = reinterpret_cast( + createInfo->lpCreateParams); + SetWindowLong(hwnd, 0, reinterpret_cast(self)); + } + else { + // get the extra window data and forward the call + LONG data = GetWindowLong(hwnd, 0); + if (data != 0) { + self = reinterpret_cast( + reinterpret_cast(data)); + } + } + + // forward the message + if (self != NULL) { + return self->wndProc(hwnd, msg, wParam, lParam); + } + else { + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} + +void +CArchTaskBarWindows::threadMainLoop() +{ + // register the task bar restart message + m_taskBarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); + + // register a window class + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_NOCLOSE; + classInfo.lpfnWndProc = &CArchTaskBarWindows::staticWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = sizeof(CArchTaskBarWindows*); + classInfo.hInstance = s_appInstance; + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = TEXT("SynergyTaskBar"); + classInfo.hIconSm = NULL; + ATOM windowClass = RegisterClassEx(&classInfo); + + // create window + m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + TEXT("Synergy Task Bar"), + WS_POPUP, + 0, 0, 1, 1, + NULL, + NULL, + s_appInstance, + reinterpret_cast(this)); + + // signal ready + ARCH->lockMutex(m_mutex); + m_ready = true; + ARCH->broadcastCondVar(m_condVar); + ARCH->unlockMutex(m_mutex); + + // handle failure + if (m_hwnd == NULL) { + UnregisterClass(reinterpret_cast(windowClass), s_appInstance); + return; + } + + // main loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + if (!processDialogs(&msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + // clean up + removeAllIcons(); + DestroyWindow(m_hwnd); + UnregisterClass(reinterpret_cast(windowClass), s_appInstance); +} + +void* +CArchTaskBarWindows::threadEntry(void* self) +{ + reinterpret_cast(self)->threadMainLoop(); + return NULL; +} diff --git a/lib/arch/CArchTaskBarWindows.h b/lib/arch/CArchTaskBarWindows.h new file mode 100644 index 00000000..67e9af17 --- /dev/null +++ b/lib/arch/CArchTaskBarWindows.h @@ -0,0 +1,110 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTASKBARWINDOWS_H +#define CARCHTASKBARWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "IArchTaskBar.h" +#include "IArchMultithread.h" +#include "stdmap.h" +#include "stdvector.h" +#include + +#define ARCH_TASKBAR CArchTaskBarWindows + +//! Win32 implementation of IArchTaskBar +class CArchTaskBarWindows : public IArchTaskBar { +public: + CArchTaskBarWindows(void*); + virtual ~CArchTaskBarWindows(); + + //! Add a dialog window + /*! + Tell the task bar event loop about a dialog. Win32 annoyingly + requires messages destined for modeless dialog boxes to be + dispatched differently than other messages. + */ + static void addDialog(HWND); + + //! Remove a dialog window + /*! + Remove a dialog window added via \c addDialog(). + */ + static void removeDialog(HWND); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + +private: + class CReceiverInfo { + public: + UINT m_id; + }; + + typedef std::map CReceiverToInfoMap; + typedef std::map CIDToReceiverMap; + typedef std::vector CIDStack; + typedef std::map CDialogs; + + UINT getNextID(); + void recycleID(UINT); + + void addIcon(UINT); + void removeIcon(UINT); + void updateIcon(UINT); + void addAllIcons(); + void removeAllIcons(); + void modifyIconNoLock(CReceiverToInfoMap::const_iterator, + DWORD taskBarMessage); + void removeIconNoLock(UINT id); + void handleIconMessage(IArchTaskBarReceiver*, LPARAM); + + bool processDialogs(MSG*); + LRESULT wndProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK + staticWndProc(HWND, UINT, WPARAM, LPARAM); + void threadMainLoop(); + static void* threadEntry(void*); + +private: + static CArchTaskBarWindows* s_instance; + static HINSTANCE s_appInstance; + + // multithread data + CArchMutex m_mutex; + CArchCond m_condVar; + bool m_ready; + int m_result; + CArchThread m_thread; + + // child thread data + HWND m_hwnd; + UINT m_taskBarRestart; + + // shared data + CReceiverToInfoMap m_receivers; + CIDToReceiverMap m_idTable; + CIDStack m_oldIDs; + UINT m_nextID; + + // dialogs + CDialogs m_dialogs; + CDialogs m_addedDialogs; +}; + +#endif diff --git a/lib/arch/CArchTaskBarXWindows.cpp b/lib/arch/CArchTaskBarXWindows.cpp new file mode 100644 index 00000000..6934f271 --- /dev/null +++ b/lib/arch/CArchTaskBarXWindows.cpp @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchTaskBarXWindows.h" + +// +// CArchTaskBarXWindows +// + +CArchTaskBarXWindows::CArchTaskBarXWindows(void*) +{ + // do nothing +} + +CArchTaskBarXWindows::~CArchTaskBarXWindows() +{ + // do nothing +} + +void +CArchTaskBarXWindows::addReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +CArchTaskBarXWindows::removeReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +CArchTaskBarXWindows::updateReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} diff --git a/lib/arch/CArchTaskBarXWindows.h b/lib/arch/CArchTaskBarXWindows.h new file mode 100644 index 00000000..abf28012 --- /dev/null +++ b/lib/arch/CArchTaskBarXWindows.h @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTASKBARXWINDOWS_H +#define CARCHTASKBARXWINDOWS_H + +#include "IArchTaskBar.h" + +#define ARCH_TASKBAR CArchTaskBarXWindows + +//! X11 implementation of IArchTaskBar +class CArchTaskBarXWindows : public IArchTaskBar { +public: + CArchTaskBarXWindows(void*); + virtual ~CArchTaskBarXWindows(); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); +}; + +#endif diff --git a/lib/arch/CArchTimeUnix.cpp b/lib/arch/CArchTimeUnix.cpp new file mode 100644 index 00000000..49506bad --- /dev/null +++ b/lib/arch/CArchTimeUnix.cpp @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CArchTimeUnix.h" +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +// +// CArchTimeUnix +// + +CArchTimeUnix::CArchTimeUnix() +{ + // do nothing +} + +CArchTimeUnix::~CArchTimeUnix() +{ + // do nothing +} + +double +CArchTimeUnix::time() +{ + struct timeval t; + gettimeofday(&t, NULL); + return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec; +} diff --git a/lib/arch/CArchTimeUnix.h b/lib/arch/CArchTimeUnix.h new file mode 100644 index 00000000..78c6ff6f --- /dev/null +++ b/lib/arch/CArchTimeUnix.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTIMEUNIX_H +#define CARCHTIMEUNIX_H + +#include "IArchTime.h" + +#define ARCH_TIME CArchTimeUnix + +//! Generic Unix implementation of IArchTime +class CArchTimeUnix : public IArchTime { +public: + CArchTimeUnix(); + virtual ~CArchTimeUnix(); + + // IArchTime overrides + virtual double time(); +}; + +#endif diff --git a/lib/arch/CArchTimeWindows.cpp b/lib/arch/CArchTimeWindows.cpp new file mode 100644 index 00000000..57aee290 --- /dev/null +++ b/lib/arch/CArchTimeWindows.cpp @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +// avoid getting a lot a crap from mmsystem.h that we don't need +#define MMNODRV // Installable driver support +#define MMNOSOUND // Sound support +#define MMNOWAVE // Waveform support +#define MMNOMIDI // MIDI support +#define MMNOAUX // Auxiliary audio support +#define MMNOMIXER // Mixer support +#define MMNOJOY // Joystick support +#define MMNOMCI // MCI support +#define MMNOMMIO // Multimedia file I/O support +#define MMNOMMSYSTEM // General MMSYSTEM functions + +#define WIN32_LEAN_AND_MEAN + +#include "CArchTimeWindows.h" +#include +#include + +typedef WINMMAPI DWORD (WINAPI *PTimeGetTime)(void); + +static double s_freq = 0.0; +static HINSTANCE s_mmInstance = NULL; +static PTimeGetTime s_tgt = NULL; + + +// +// CArchTimeWindows +// + +CArchTimeWindows::CArchTimeWindows() +{ + assert(s_freq == 0.0 || s_mmInstance == NULL); + + LARGE_INTEGER freq; + if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0) { + s_freq = 1.0 / static_cast(freq.QuadPart); + } + else { + // load winmm.dll and get timeGetTime + s_mmInstance = LoadLibrary("winmm"); + if (s_mmInstance != NULL) { + s_tgt = (PTimeGetTime)GetProcAddress(s_mmInstance, "timeGetTime"); + } + } +} + +CArchTimeWindows::~CArchTimeWindows() +{ + s_freq = 0.0; + if (s_mmInstance == NULL) { + FreeLibrary(reinterpret_cast(s_mmInstance)); + s_tgt = NULL; + s_mmInstance = NULL; + } +} + +double +CArchTimeWindows::time() +{ + // get time. we try three ways, in order of descending precision + if (s_freq != 0.0) { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + return s_freq * static_cast(c.QuadPart); + } + else if (s_tgt != NULL) { + return 0.001 * static_cast(s_tgt()); + } + else { + return 0.001 * static_cast(GetTickCount()); + } +} diff --git a/lib/arch/CArchTimeWindows.h b/lib/arch/CArchTimeWindows.h new file mode 100644 index 00000000..fb9b1e9f --- /dev/null +++ b/lib/arch/CArchTimeWindows.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CARCHTIMEWINDOWS_H +#define CARCHTIMEWINDOWS_H + +#include "IArchTime.h" + +#define ARCH_TIME CArchTimeWindows + +//! Win32 implementation of IArchTime +class CArchTimeWindows : public IArchTime { +public: + CArchTimeWindows(); + virtual ~CArchTimeWindows(); + + // IArchTime overrides + virtual double time(); +}; + +#endif diff --git a/lib/arch/CMultibyte.cpp b/lib/arch/CMultibyte.cpp new file mode 100644 index 00000000..517d72d6 --- /dev/null +++ b/lib/arch/CMultibyte.cpp @@ -0,0 +1,219 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMULTIBYTE_H +#define CMULTIBYTE_H + +#include "common.h" +#include "CArch.h" +#include +#include +#if HAVE_LOCALE_H +# include +#endif +#if HAVE_WCHAR_H || defined(_MSC_VER) +# include +#elif __APPLE__ + // wtf? Darwin puts mbtowc() et al. in stdlib +# include +#else + // platform apparently has no wchar_t support. provide dummy + // implementations. hopefully at least the C++ compiler has + // a built-in wchar_t type. + +static inline +int +mbtowc(wchar_t* dst, const char* src, int n) +{ + *dst = static_cast(*src); + return 1; +} + +static inline +int +wctomb(char* dst, wchar_t src) +{ + *dst = static_cast(src); + return 1; +} + +#endif + +// +// use C library non-reentrant multibyte conversion with mutex +// + +static CArchMutex s_mutex = NULL; + +ARCH_STRING::ARCH_STRING() +{ + s_mutex = ARCH->newMutex(); + +#if HAVE_LOCALE_H + // see if we can convert a Latin-1 character + char mb[MB_LEN_MAX]; + if (wctomb(mb, 0xe3) == -1) { + // can't convert. try another locale so we can convert latin-1. + setlocale(LC_CTYPE, "en_US"); + } +#endif +} + +ARCH_STRING::~ARCH_STRING() +{ + ARCH->closeMutex(s_mutex); + s_mutex = NULL; +} + +int +ARCH_STRING::convStringWCToMB(char* dst, + const wchar_t* src, UInt32 n, bool* errors) +{ + int len = 0; + + bool dummyErrors; + if (errors == NULL) { + errors = &dummyErrors; + } + + ARCH->lockMutex(s_mutex); + if (dst == NULL) { + char dummy[MB_LEN_MAX]; + for (const wchar_t* scan = src; n > 0; ++scan, --n) { + int mblen = wctomb(dummy, *scan); + if (mblen == -1) { + *errors = true; + mblen = 1; + } + len += mblen; + } + int mblen = wctomb(dummy, L'\0'); + if (mblen != -1) { + len += mblen - 1; + } + } + else { + char* dst0 = dst; + for (const wchar_t* scan = src; n > 0; ++scan, --n) { + int mblen = wctomb(dst, *scan); + if (mblen == -1) { + *errors = true; + *dst++ = '?'; + } + else { + dst += mblen; + } + } + int mblen = wctomb(dst, L'\0'); + if (mblen != -1) { + // don't include nul terminator + dst += mblen - 1; + } + len = (int)(dst - dst0); + } + ARCH->unlockMutex(s_mutex); + + return len; +} + +int +ARCH_STRING::convStringMBToWC(wchar_t* dst, + const char* src, UInt32 n, bool* errors) +{ + int len = 0; + wchar_t dummy; + + bool dummyErrors; + if (errors == NULL) { + errors = &dummyErrors; + } + + ARCH->lockMutex(s_mutex); + if (dst == NULL) { + for (const char* scan = src; n > 0; ) { + int mblen = mbtowc(&dummy, scan, n); + switch (mblen) { + case -2: + // incomplete last character. convert to unknown character. + *errors = true; + len += 1; + n = 0; + break; + + case -1: + // invalid character. count one unknown character and + // start at the next byte. + *errors = true; + len += 1; + scan += 1; + n -= 1; + break; + + case 0: + len += 1; + scan += 1; + n -= 1; + break; + + default: + // normal character + len += 1; + scan += mblen; + n -= mblen; + break; + } + } + } + else { + wchar_t* dst0 = dst; + for (const char* scan = src; n > 0; ++dst) { + int mblen = mbtowc(dst, scan, n); + switch (mblen) { + case -2: + // incomplete character. convert to unknown character. + *errors = true; + *dst = (wchar_t)0xfffd; + n = 0; + break; + + case -1: + // invalid character. count one unknown character and + // start at the next byte. + *errors = true; + *dst = (wchar_t)0xfffd; + scan += 1; + n -= 1; + break; + + case 0: + *dst = (wchar_t)0x0000; + scan += 1; + n -= 1; + break; + + default: + // normal character + scan += mblen; + n -= mblen; + break; + } + } + len = (int)(dst - dst0); + } + ARCH->unlockMutex(s_mutex); + + return len; +} + +#endif diff --git a/lib/arch/IArchConsole.h b/lib/arch/IArchConsole.h new file mode 100644 index 00000000..2befb196 --- /dev/null +++ b/lib/arch/IArchConsole.h @@ -0,0 +1,71 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHCONSOLE_H +#define IARCHCONSOLE_H + +#include "IInterface.h" + +//! Interface for architecture dependent console output +/*! +This interface defines the console operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchConsole : public IInterface { +public: + //! @name manipulators + //@{ + + //! Open the console + /*! + Opens the console for writing. The console is opened automatically + on the first write so calling this method is optional. Uses \c title + for the console's title if appropriate for the architecture. Calling + this method on an already open console must have no effect. + */ + virtual void openConsole(const char* title) = 0; + + //! Close the console + /*! + Close the console. Calling this method on an already closed console + must have no effect. + */ + virtual void closeConsole() = 0; + + //! Show the console + /*! + Causes the console to become visible. This generally only makes sense + for a console in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the console if it's not empty. + */ + virtual void showConsole(bool showIfEmpty) = 0; + + //! Write to the console + /*! + Writes the given string to the console, opening it if necessary. + */ + virtual void writeConsole(const char*) = 0; + + //! Returns the newline sequence for the console + /*! + Different consoles use different character sequences for newlines. + This method returns the appropriate newline sequence for the console. + */ + virtual const char* getNewlineForConsole() = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchDaemon.h b/lib/arch/IArchDaemon.h new file mode 100644 index 00000000..ba6b049b --- /dev/null +++ b/lib/arch/IArchDaemon.h @@ -0,0 +1,107 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHDAEMON_H +#define IARCHDAEMON_H + +#include "IInterface.h" + +//! Interface for architecture dependent daemonizing +/*! +This interface defines the operations required by synergy for installing +uninstalling daeamons and daemonizing a process. Each architecture must +implement this interface. +*/ +class IArchDaemon : public IInterface { +public: + typedef int (*DaemonFunc)(int argc, const char** argv); + + //! @name manipulators + //@{ + + //! Install daemon + /*! + Install a daemon. \c name is the name of the daemon passed to the + system and \c description is a short human readable description of + the daemon. \c pathname is the path to the daemon executable. + \c commandLine should \b not include the name of program as the + first argument. If \c allUsers is true then the daemon will be + installed to start at boot time, otherwise it will be installed to + start when the current user logs in. If \p dependencies is not NULL + then it's a concatenation of NUL terminated other daemon names + followed by a NUL; the daemon will be configured to startup after + the listed daemons. Throws an \c XArchDaemon exception on failure. + */ + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies, + bool allUsers) = 0; + + //! Uninstall daemon + /*! + Uninstall a daemon. Throws an \c XArchDaemon on failure. + */ + virtual void uninstallDaemon(const char* name, bool allUsers) = 0; + + //! Daemonize the process + /*! + Daemonize. Throw XArchDaemonFailed on error. \c name is the name + of the daemon. Once daemonized, \c func is invoked and daemonize + returns when and what it does. + + Exactly what happens when daemonizing depends on the platform. +
      +
    • unix: + Detaches from terminal. \c func gets passed one argument, the + name passed to daemonize(). +
    • win32: + Becomes a service. Argument 0 is the name of the service + and the rest are the arguments passed to StartService(). + \c func is only called when the service is actually started. + \c func must call \c CArchMiscWindows::runDaemon() to finally + becoming a service. The \c runFunc function passed to \c runDaemon() + must call \c CArchMiscWindows::daemonRunning(true) when it + enters the main loop (i.e. after initialization) and + \c CArchMiscWindows::daemonRunning(false) when it leaves + the main loop. The \c stopFunc function passed to \c runDaemon() + is called when the daemon must exit the main loop and it must cause + \c runFunc to return. \c func should return what \c runDaemon() + returns. \c func or \c runFunc can call + \c CArchMiscWindows::daemonFailed() to indicate startup failure. +
    + */ + virtual int daemonize(const char* name, DaemonFunc func) = 0; + + //! Check if user has permission to install the daemon + /*! + Returns true iff the caller has permission to install or + uninstall the daemon. Note that even if this method returns + true it's possible that installing/uninstalling the service + may still fail. This method ignores whether or not the + service is already installed. + */ + virtual bool canInstallDaemon(const char* name, bool allUsers) = 0; + + //! Check if the daemon is installed + /*! + Returns true iff the daemon is installed. + */ + virtual bool isDaemonInstalled(const char* name, bool allUsers) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchFile.h b/lib/arch/IArchFile.h new file mode 100644 index 00000000..8594053d --- /dev/null +++ b/lib/arch/IArchFile.h @@ -0,0 +1,64 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHFILE_H +#define IARCHFILE_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent file system operations +/*! +This interface defines the file system operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchFile : public IInterface { +public: + //! @name manipulators + //@{ + + //! Extract base name + /*! + Find the base name in the given \c pathname. + */ + virtual const char* getBasename(const char* pathname) = 0; + + //! Get user's home directory + /*! + Returns the user's home directory. Returns the empty string if + this cannot be determined. + */ + virtual std::string getUserDirectory() = 0; + + //! Get system directory + /*! + Returns the ussystem configuration file directory. + */ + virtual std::string getSystemDirectory() = 0; + + //! Concatenate path components + /*! + Concatenate pathname components with a directory separator + between them. This should not check if the resulting path + is longer than allowed by the system; we'll rely on the + system calls to tell us that. + */ + virtual std::string concatPath( + const std::string& prefix, + const std::string& suffix) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchLog.h b/lib/arch/IArchLog.h new file mode 100644 index 00000000..7655ff95 --- /dev/null +++ b/lib/arch/IArchLog.h @@ -0,0 +1,73 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHLOG_H +#define IARCHLOG_H + +#include "IInterface.h" + +//! Interface for architecture dependent logging +/*! +This interface defines the logging operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchLog : public IInterface { +public: + //! Log levels + /*! + The logging priority levels in order of highest to lowest priority. + */ + enum ELevel { + kERROR, //!< For serious or fatal errors + kWARNING, //!< For minor errors and warnings + kNOTE, //!< For messages about notable events + kINFO, //!< For informational messages + kDEBUG //!< For debugging messages + }; + + //! @name manipulators + //@{ + + //! Open the log + /*! + Opens the log for writing. The log must be opened before being + written to. + */ + virtual void openLog(const char* name) = 0; + + //! Close the log + /*! + Close the log. + */ + virtual void closeLog() = 0; + + //! Show the log + /*! + Causes the log to become visible. This generally only makes sense + for a log in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the log if it's not empty. + */ + virtual void showLog(bool showIfEmpty) = 0; + + //! Write to the log + /*! + Writes the given string to the log with the given level. + */ + virtual void writeLog(ELevel, const char*) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchMultithread.h b/lib/arch/IArchMultithread.h new file mode 100644 index 00000000..b7b72293 --- /dev/null +++ b/lib/arch/IArchMultithread.h @@ -0,0 +1,272 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHMULTITHREAD_H +#define IARCHMULTITHREAD_H + +#include "IInterface.h" + +/*! +\class CArchCondImpl +\brief Internal condition variable data. +An architecture dependent type holding the necessary data for a +condition variable. +*/ +class CArchCondImpl; + +/*! +\var CArchCond +\brief Opaque condition variable type. +An opaque type representing a condition variable. +*/ +typedef CArchCondImpl* CArchCond; + +/*! +\class CArchMutexImpl +\brief Internal mutex data. +An architecture dependent type holding the necessary data for a mutex. +*/ +class CArchMutexImpl; + +/*! +\var CArchMutex +\brief Opaque mutex type. +An opaque type representing a mutex. +*/ +typedef CArchMutexImpl* CArchMutex; + +/*! +\class CArchThreadImpl +\brief Internal thread data. +An architecture dependent type holding the necessary data for a thread. +*/ +class CArchThreadImpl; + +/*! +\var CArchThread +\brief Opaque thread type. +An opaque type representing a thread. +*/ +typedef CArchThreadImpl* CArchThread; + +//! Interface for architecture dependent multithreading +/*! +This interface defines the multithreading operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchMultithread : public IInterface { +public: + //! Type of thread entry point + typedef void* (*ThreadFunc)(void*); + //! Type of thread identifier + typedef unsigned int ThreadID; + //! Types of signals + /*! + Not all platforms support all signals. Unsupported signals are + ignored. + */ + enum ESignal { + kINTERRUPT, //!< Interrupt (e.g. Ctrl+C) + kTERMINATE, //!< Terminate (e.g. Ctrl+Break) + kHANGUP, //!< Hangup (SIGHUP) + kUSER, //!< User (SIGUSR2) + kNUM_SIGNALS + }; + //! Type of signal handler function + typedef void (*SignalFunc)(ESignal, void* userData); + + //! @name manipulators + //@{ + + // + // condition variable methods + // + + //! Create a condition variable + /*! + The condition variable is an opaque data type. + */ + virtual CArchCond newCondVar() = 0; + + //! Destroy a condition variable + virtual void closeCondVar(CArchCond) = 0; + + //! Signal a condition variable + /*! + Signalling a condition variable releases one waiting thread. + */ + virtual void signalCondVar(CArchCond) = 0; + + //! Broadcast a condition variable + /*! + Broadcasting a condition variable releases all waiting threads. + */ + virtual void broadcastCondVar(CArchCond) = 0; + + //! Wait on a condition variable + /*! + Wait on a conditation variable for up to \c timeout seconds. + If \c timeout is < 0 then there is no timeout. The mutex must + be locked when this method is called. The mutex is unlocked + during the wait and locked again before returning. Returns + true if the condition variable was signalled and false on + timeout. + + (Cancellation point) + */ + virtual bool waitCondVar(CArchCond, CArchMutex, double timeout) = 0; + + // + // mutex methods + // + + //! Create a recursive mutex + /*! + Creates a recursive mutex. A thread may lock a recursive mutex + when it already holds a lock on that mutex. The mutex is an + opaque data type. + */ + virtual CArchMutex newMutex() = 0; + + //! Destroy a mutex + virtual void closeMutex(CArchMutex) = 0; + + //! Lock a mutex + virtual void lockMutex(CArchMutex) = 0; + + //! Unlock a mutex + virtual void unlockMutex(CArchMutex) = 0; + + // + // thread methods + // + + //! Start a new thread + /*! + Creates and starts a new thread, using \c func as the entry point + and passing it \c userData. The thread is an opaque data type. + */ + virtual CArchThread newThread(ThreadFunc func, void* userData) = 0; + + //! Get a reference to the calling thread + /*! + Returns a thread representing the current (i.e. calling) thread. + */ + virtual CArchThread newCurrentThread() = 0; + + //! Copy a thread object + /*! + Returns a reference to to thread referred to by \c thread. + */ + virtual CArchThread copyThread(CArchThread thread) = 0; + + //! Release a thread reference + /*! + Deletes the given thread object. This does not destroy the thread + the object referred to, even if there are no remaining references. + Use cancelThread() and waitThread() to stop a thread and wait for + it to exit. + */ + virtual void closeThread(CArchThread) = 0; + + //! Force a thread to exit + /*! + Causes \c thread to exit when it next calls a cancellation point. + A thread avoids cancellation as long as it nevers calls a + cancellation point. Once it begins the cancellation process it + must always let cancellation go to completion but may take as + long as necessary to clean up. + */ + virtual void cancelThread(CArchThread thread) = 0; + + //! Change thread priority + /*! + Changes the priority of \c thread by \c n. If \c n is positive + the thread has a lower priority and if negative a higher priority. + Some architectures may not support either or both directions. + */ + virtual void setPriorityOfThread(CArchThread, int n) = 0; + + //! Cancellation point + /*! + This method does nothing but is a cancellation point. Clients + can make their own functions cancellation points by calling this + method at appropriate times. + + (Cancellation point) + */ + virtual void testCancelThread() = 0; + + //! Wait for a thread to exit + /*! + Waits for up to \c timeout seconds for \c thread to exit (normally + or by cancellation). Waits forever if \c timeout < 0. Returns + true if the thread exited, false otherwise. Waiting on the current + thread returns immediately with false. + + (Cancellation point) + */ + virtual bool wait(CArchThread thread, double timeout) = 0; + + //! Compare threads + /*! + Returns true iff two thread objects refer to the same thread. + Note that comparing thread objects directly is meaningless. + */ + virtual bool isSameThread(CArchThread, CArchThread) = 0; + + //! Test if thread exited + /*! + Returns true iff \c thread has exited. + */ + virtual bool isExitedThread(CArchThread thread) = 0; + + //! Returns the exit code of a thread + /*! + Waits indefinitely for \c thread to exit (if it hasn't yet) then + returns the thread's exit code. + + (Cancellation point) + */ + virtual void* getResultOfThread(CArchThread thread) = 0; + + //! Returns an ID for a thread + /*! + Returns some ID number for \c thread. This is for logging purposes. + All thread objects referring to the same thread return the same ID. + However, clients should us isSameThread() to compare thread objects + instead of comparing IDs. + */ + virtual ThreadID getIDOfThread(CArchThread thread) = 0; + + //! Set the interrupt handler + /*! + Sets the function to call on receipt of an external interrupt. + By default and when \p func is NULL, the main thread is cancelled. + */ + virtual void setSignalHandler(ESignal, SignalFunc func, + void* userData) = 0; + + //! Invoke the signal handler + /*! + Invokes the signal handler for \p signal, if any. If no handler + cancels the main thread for \c kINTERRUPT and \c kTERMINATE and + ignores the call otherwise. + */ + virtual void raiseSignal(ESignal signal) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchNetwork.h b/lib/arch/IArchNetwork.h new file mode 100644 index 00000000..007fb442 --- /dev/null +++ b/lib/arch/IArchNetwork.h @@ -0,0 +1,279 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHNETWORK_H +#define IARCHNETWORK_H + +#include "IInterface.h" +#include "stdstring.h" + +class CArchThreadImpl; +typedef CArchThreadImpl* CArchThread; + +/*! +\class CArchSocketImpl +\brief Internal socket data. +An architecture dependent type holding the necessary data for a socket. +*/ +class CArchSocketImpl; + +/*! +\var CArchSocket +\brief Opaque socket type. +An opaque type representing a socket. +*/ +typedef CArchSocketImpl* CArchSocket; + +/*! +\class CArchNetAddressImpl +\brief Internal network address data. +An architecture dependent type holding the necessary data for a network +address. +*/ +class CArchNetAddressImpl; + +/*! +\var CArchNetAddress +\brief Opaque network address type. +An opaque type representing a network address. +*/ +typedef CArchNetAddressImpl* CArchNetAddress; + +//! Interface for architecture dependent networking +/*! +This interface defines the networking operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchNetwork : public IInterface { +public: + //! Supported address families + enum EAddressFamily { + kUNKNOWN, + kINET, + }; + + //! Supported socket types + enum ESocketType { + kDGRAM, + kSTREAM + }; + + //! Events for \c poll() + /*! + Events for \c poll() are bitmasks and can be combined using the + bitwise operators. + */ + enum { + kPOLLIN = 1, //!< Socket is readable + kPOLLOUT = 2, //!< Socket is writable + kPOLLERR = 4, //!< The socket is in an error state + kPOLLNVAL = 8 //!< The socket is invalid + }; + + //! A socket query for \c poll() + class CPollEntry { + public: + //! The socket to query + CArchSocket m_socket; + + //! The events to query for + /*! + The events to query for can be any combination of kPOLLIN and + kPOLLOUT. + */ + unsigned short m_events; + + //! The result events + unsigned short m_revents; + }; + + //! @name manipulators + //@{ + + //! Create a new socket + /*! + The socket is an opaque data type. + */ + virtual CArchSocket newSocket(EAddressFamily, ESocketType) = 0; + + //! Copy a socket object + /*! + Returns a reference to to socket referred to by \c s. + */ + virtual CArchSocket copySocket(CArchSocket s) = 0; + + //! Release a socket reference + /*! + Deletes the given socket object. This does not destroy the socket + the object referred to until there are no remaining references for + the socket. + */ + virtual void closeSocket(CArchSocket s) = 0; + + //! Close socket for further reads + /*! + Calling this disallows future reads on socket \c s. + */ + virtual void closeSocketForRead(CArchSocket s) = 0; + + //! Close socket for further writes + /*! + Calling this disallows future writes on socket \c s. + */ + virtual void closeSocketForWrite(CArchSocket s) = 0; + + //! Bind socket to address + /*! + Binds socket \c s to the address \c addr. + */ + virtual void bindSocket(CArchSocket s, CArchNetAddress addr) = 0; + + //! Listen for connections on socket + /*! + Causes the socket \c s to begin listening for incoming connections. + */ + virtual void listenOnSocket(CArchSocket s) = 0; + + //! Accept connection on socket + /*! + Accepts a connection on socket \c s, returning a new socket for the + connection and filling in \c addr with the address of the remote + end. \c addr may be NULL if the remote address isn't required. + The original socket \c s is unaffected and remains in the listening + state. The new socket shares most of the properties of \c s except + it's not in the listening state and it's connected. Returns NULL + if there are no pending connection requests. + */ + virtual CArchSocket acceptSocket(CArchSocket s, CArchNetAddress* addr) = 0; + + //! Connect socket + /*! + Connects the socket \c s to the remote address \c addr. Returns + true if the connection succeed immediately, false if the connection + is in progress, and throws if the connection failed immediately. + If it returns false, \c pollSocket() can be used to wait on the + socket for writing to detect when the connection finally succeeds + or fails. + */ + virtual bool connectSocket(CArchSocket s, CArchNetAddress addr) = 0; + + //! Check socket state + /*! + Tests the state of \c num sockets for readability and/or writability. + Waits up to \c timeout seconds for some socket to become readable + and/or writable (or indefinitely if \c timeout < 0). Returns the + number of sockets that were readable (if readability was being + queried) or writable (if writablility was being queried) and sets + the \c m_revents members of the entries. \c kPOLLERR and \c kPOLLNVAL + are set in \c m_revents as appropriate. If a socket indicates + \c kPOLLERR then \c throwErrorOnSocket() can be used to determine + the type of error. Returns 0 immediately regardless of the \c timeout + if no valid sockets are selected for testing. + + (Cancellation point) + */ + virtual int pollSocket(CPollEntry[], int num, double timeout) = 0; + + //! Unblock thread in pollSocket() + /*! + Cause a thread that's in a pollSocket() call to return. This + call may return before the thread is unblocked. If the thread is + not in a pollSocket() call this call has no effect. + */ + virtual void unblockPollSocket(CArchThread thread) = 0; + + //! Read data from socket + /*! + Read up to \c len bytes from socket \c s in \c buf and return the + number of bytes read. The number of bytes can be less than \c len + if not enough data is available. Returns 0 if the remote end has + disconnected and/or there is no more queued received data. + */ + virtual size_t readSocket(CArchSocket s, void* buf, size_t len) = 0; + + //! Write data from socket + /*! + Write up to \c len bytes to socket \c s from \c buf and return the + number of bytes written. The number of bytes can be less than + \c len if the remote end disconnected or the internal buffers fill + up. + */ + virtual size_t writeSocket(CArchSocket s, + const void* buf, size_t len) = 0; + + //! Check error on socket + /*! + If the socket \c s is in an error state then throws an appropriate + XArchNetwork exception. + */ + virtual void throwErrorOnSocket(CArchSocket s) = 0; + + //! Turn Nagle algorithm on or off on socket + /*! + Set socket to send messages immediately (true) or to collect small + messages into one packet (false). Returns the previous state. + */ + virtual bool setNoDelayOnSocket(CArchSocket, bool noDelay) = 0; + + //! Turn address reuse on or off on socket + /*! + Allows the address this socket is bound to to be reused while in the + TIME_WAIT state. Returns the previous state. + */ + virtual bool setReuseAddrOnSocket(CArchSocket, bool reuse) = 0; + + //! Return local host's name + virtual std::string getHostName() = 0; + + //! Create an "any" network address + virtual CArchNetAddress newAnyAddr(EAddressFamily) = 0; + + //! Copy a network address + virtual CArchNetAddress copyAddr(CArchNetAddress) = 0; + + //! Convert a name to a network address + virtual CArchNetAddress nameToAddr(const std::string&) = 0; + + //! Destroy a network address + virtual void closeAddr(CArchNetAddress) = 0; + + //! Convert an address to a host name + virtual std::string addrToName(CArchNetAddress) = 0; + + //! Convert an address to a string + virtual std::string addrToString(CArchNetAddress) = 0; + + //! Get an address's family + virtual EAddressFamily getAddrFamily(CArchNetAddress) = 0; + + //! Set the port of an address + virtual void setAddrPort(CArchNetAddress, int port) = 0; + + //! Get the port of an address + virtual int getAddrPort(CArchNetAddress) = 0; + + //! Test addresses for equality + virtual bool isEqualAddr(CArchNetAddress, CArchNetAddress) = 0; + + //! Test for the "any" address + /*! + Returns true if \c addr is the "any" address. \c newAnyAddr() + returns an "any" address. + */ + virtual bool isAnyAddr(CArchNetAddress addr) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchSleep.h b/lib/arch/IArchSleep.h new file mode 100644 index 00000000..95a2852c --- /dev/null +++ b/lib/arch/IArchSleep.h @@ -0,0 +1,43 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHSLEEP_H +#define IARCHSLEEP_H + +#include "IInterface.h" + +//! Interface for architecture dependent sleeping +/*! +This interface defines the sleep operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchSleep : public IInterface { +public: + //! @name manipulators + //@{ + + //! Sleep + /*! + Blocks the calling thread for \c timeout seconds. If + \c timeout < 0.0 then the call returns immediately. If \c timeout + == 0.0 then the calling thread yields the CPU. + + (cancellation point) + */ + virtual void sleep(double timeout) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchString.h b/lib/arch/IArchString.h new file mode 100644 index 00000000..703d64b1 --- /dev/null +++ b/lib/arch/IArchString.h @@ -0,0 +1,68 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHSTRING_H +#define IARCHSTRING_H + +#include "IInterface.h" +#include "BasicTypes.h" +#include + +//! Interface for architecture dependent string operations +/*! +This interface defines the string operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchString : public IInterface { +public: + //! Wide character encodings + /*! + The known wide character encodings + */ + enum EWideCharEncoding { + kUCS2, //!< The UCS-2 encoding + kUCS4, //!< The UCS-4 encoding + kUTF16, //!< The UTF-16 encoding + kUTF32 //!< The UTF-32 encoding + }; + + //! @name manipulators + //@{ + + //! printf() to limited size buffer with va_list + /*! + This method is equivalent to vsprintf() except it will not write + more than \c n bytes to the buffer, returning -1 if the output + was truncated and the number of bytes written not including the + trailing NUL otherwise. + */ + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap) = 0; + + //! Convert multibyte string to wide character string + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors) = 0; + + //! Convert wide character string to multibyte string + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors) = 0; + + //! Return the architecture's native wide character encoding + virtual EWideCharEncoding + getWideCharEncoding() = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchSystem.h b/lib/arch/IArchSystem.h new file mode 100644 index 00000000..7a6c941b --- /dev/null +++ b/lib/arch/IArchSystem.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHSYSTEM_H +#define IARCHSYSTEM_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent system queries +/*! +This interface defines operations for querying system info. +*/ +class IArchSystem : public IInterface { +public: + //! @name accessors + //@{ + + //! Identify the OS + /*! + Returns a string identifying the operating system. + */ + virtual std::string getOSName() const = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTaskBar.h b/lib/arch/IArchTaskBar.h new file mode 100644 index 00000000..e9471566 --- /dev/null +++ b/lib/arch/IArchTaskBar.h @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHTASKBAR_H +#define IARCHTASKBAR_H + +#include "IInterface.h" + +class IArchTaskBarReceiver; + +//! Interface for architecture dependent task bar control +/*! +This interface defines the task bar icon operations required +by synergy. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBar : public IInterface { +public: + //! @name manipulators + //@{ + + //! Add a receiver + /*! + Add a receiver object to be notified of user and application + events. This should be called before other methods. When + the receiver is added to the task bar, its icon appears on + the task bar. + */ + virtual void addReceiver(IArchTaskBarReceiver*) = 0; + + //! Remove a receiver + /*! + Remove a receiver object from the task bar. This removes the + icon from the task bar. + */ + virtual void removeReceiver(IArchTaskBarReceiver*) = 0; + + //! Update a receiver + /*! + Updates the display of the receiver on the task bar. This + should be called when the receiver appearance may have changed + (e.g. it's icon or tool tip has changed). + */ + virtual void updateReceiver(IArchTaskBarReceiver*) = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTaskBarReceiver.h b/lib/arch/IArchTaskBarReceiver.h new file mode 100644 index 00000000..917f2fbf --- /dev/null +++ b/lib/arch/IArchTaskBarReceiver.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHTASKBARRECEIVER_H +#define IARCHTASKBARRECEIVER_H + +#include "IInterface.h" +#include "stdstring.h" + +//! Interface for architecture dependent task bar event handling +/*! +This interface defines the task bar icon event handlers required +by synergy. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBarReceiver : public IInterface { +public: + // Icon data is architecture dependent + typedef void* Icon; + + //! @name manipulators + //@{ + + //! Show status window + /*! + Open a window displaying current status. This should return + immediately without waiting for the window to be closed. + */ + virtual void showStatus() = 0; + + //! Popup menu + /*! + Popup a menu of operations at or around \c x,y and perform the + chosen operation. + */ + virtual void runMenu(int x, int y) = 0; + + //! Perform primary action + /*! + Perform the primary (default) action. + */ + virtual void primaryAction() = 0; + + //@} + //! @name accessors + //@{ + + //! Lock receiver + /*! + Locks the receiver from changing state. The receiver should be + locked when querying it's state to ensure consistent results. + Each call to \c lock() must have a matching \c unlock() and + locks cannot be nested. + */ + virtual void lock() const = 0; + + //! Unlock receiver + virtual void unlock() const = 0; + + //! Get icon + /*! + Returns the icon to display in the task bar. The interface + to set the icon is left to subclasses. Getting and setting + the icon must be thread safe. + */ + virtual const Icon getIcon() const = 0; + + //! Get tooltip + /*! + Returns the tool tip to display in the task bar. The interface + to set the tooltip is left to sublclasses. Getting and setting + the icon must be thread safe. + */ + virtual std::string getToolTip() const = 0; + + //@} +}; + +#endif diff --git a/lib/arch/IArchTime.h b/lib/arch/IArchTime.h new file mode 100644 index 00000000..dade64bb --- /dev/null +++ b/lib/arch/IArchTime.h @@ -0,0 +1,40 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IARCHTIME_H +#define IARCHTIME_H + +#include "IInterface.h" + +//! Interface for architecture dependent time operations +/*! +This interface defines the time operations required by +synergy. Each architecture must implement this interface. +*/ +class IArchTime : public IInterface { +public: + //! @name manipulators + //@{ + + //! Get the current time + /*! + Returns the number of seconds since some arbitrary starting time. + This should return as high a precision as reasonable. + */ + virtual double time() = 0; + + //@} +}; + +#endif diff --git a/lib/arch/Makefile.am b/lib/arch/Makefile.am new file mode 100644 index 00000000..8a1749ab --- /dev/null +++ b/lib/arch/Makefile.am @@ -0,0 +1,118 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +COMMON_SOURCE_FILES = \ + CArch.cpp \ + CArchDaemonNone.cpp \ + CArchDaemonNone.h \ + XArch.cpp \ + CArch.h \ + IArchConsole.h \ + IArchDaemon.h \ + IArchFile.h \ + IArchLog.h \ + IArchMultithread.h \ + IArchNetwork.h \ + IArchSleep.h \ + IArchString.h \ + IArchSystem.h \ + IArchTaskBar.h \ + IArchTaskBarReceiver.h \ + IArchTime.h \ + XArch.h \ + $(NULL) +UNIX_SOURCE_FILES = \ + CArchConsoleUnix.cpp \ + CArchDaemonUnix.cpp \ + CArchFileUnix.cpp \ + CArchLogUnix.cpp \ + CArchMultithreadPosix.cpp \ + CArchNetworkBSD.cpp \ + CArchSleepUnix.cpp \ + CArchStringUnix.cpp \ + CArchSystemUnix.cpp \ + CArchTaskBarXWindows.cpp \ + CArchTimeUnix.cpp \ + XArchUnix.cpp \ + CArchConsoleUnix.h \ + CArchDaemonUnix.h \ + CArchFileUnix.h \ + CArchLogUnix.h \ + CArchMultithreadPosix.h \ + CArchNetworkBSD.h \ + CArchSleepUnix.h \ + CArchStringUnix.h \ + CArchSystemUnix.h \ + CArchTaskBarXWindows.h \ + CArchTimeUnix.h \ + XArchUnix.h \ + $(NULL) +WIN32_SOURCE_FILES = \ + CArchConsoleWindows.cpp \ + CArchDaemonWindows.cpp \ + CArchFileWindows.cpp \ + CArchLogWindows.cpp \ + CArchMiscWindows.cpp \ + CArchMultithreadWindows.cpp \ + CArchNetworkWinsock.cpp \ + CArchSleepWindows.cpp \ + CArchStringWindows.cpp \ + CArchSystemWindows.cpp \ + CArchTaskBarWindows.cpp \ + CArchTimeWindows.cpp \ + XArchWindows.cpp \ + CArchConsoleWindows.h \ + CArchDaemonWindows.h \ + CArchFileWindows.h \ + CArchLogWindows.h \ + CArchMiscWindows.h \ + CArchMultithreadWindows.h \ + CArchNetworkWinsock.h \ + CArchSleepWindows.h \ + CArchStringWindows.h \ + CArchSystemWindows.h \ + CArchTaskBarWindows.h \ + CArchTimeWindows.h \ + XArchWindows.h \ + $(NULL) + +EXTRA_DIST = \ + CMultibyte.cpp \ + Makefile.win \ + vsnprintf.cpp \ + $(UNIX_SOURCE_FILES) \ + $(WIN32_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libarch.a +if UNIX +libarch_a_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(UNIX_SOURCE_FILES) \ + $(NULL) +endif +if WIN32 +libarch_a_SOURCES = \ + $(COMMON_SOURCE_FILES) \ + $(WIN32_SOURCE_FILES) \ + $(NULL) +endif +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + $(NULL) diff --git a/lib/arch/Makefile.win b/lib/arch/Makefile.win new file mode 100644 index 00000000..4e151976 --- /dev/null +++ b/lib/arch/Makefile.win @@ -0,0 +1,84 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_ARCH_SRC = lib\arch +LIB_ARCH_DST = $(BUILD_DST)\$(LIB_ARCH_SRC) +LIB_ARCH_LIB = "$(LIB_ARCH_DST)\arch.lib" +LIB_ARCH_CPP = \ + "CArch.cpp" \ + "CArchDaemonNone.cpp" \ + "XArch.cpp" \ + "CArchConsoleWindows.cpp" \ + "CArchDaemonWindows.cpp" \ + "CArchFileWindows.cpp" \ + "CArchLogWindows.cpp" \ + "CArchMiscWindows.cpp" \ + "CArchMultithreadWindows.cpp" \ + "CArchNetworkWinsock.cpp" \ + "CArchSleepWindows.cpp" \ + "CArchStringWindows.cpp" \ + "CArchSystemWindows.cpp" \ + "CArchTaskBarWindows.cpp" \ + "CArchTimeWindows.cpp" \ + "XArchWindows.cpp" \ + $(NULL) +LIB_ARCH_OBJ = \ + "$(LIB_ARCH_DST)\CArch.obj" \ + "$(LIB_ARCH_DST)\CArchDaemonNone.obj" \ + "$(LIB_ARCH_DST)\XArch.obj" \ + "$(LIB_ARCH_DST)\CArchConsoleWindows.obj" \ + "$(LIB_ARCH_DST)\CArchDaemonWindows.obj" \ + "$(LIB_ARCH_DST)\CArchFileWindows.obj" \ + "$(LIB_ARCH_DST)\CArchLogWindows.obj" \ + "$(LIB_ARCH_DST)\CArchMiscWindows.obj" \ + "$(LIB_ARCH_DST)\CArchMultithreadWindows.obj" \ + "$(LIB_ARCH_DST)\CArchNetworkWinsock.obj" \ + "$(LIB_ARCH_DST)\CArchSleepWindows.obj" \ + "$(LIB_ARCH_DST)\CArchStringWindows.obj" \ + "$(LIB_ARCH_DST)\CArchSystemWindows.obj" \ + "$(LIB_ARCH_DST)\CArchTaskBarWindows.obj" \ + "$(LIB_ARCH_DST)\CArchTimeWindows.obj" \ + "$(LIB_ARCH_DST)\XArchWindows.obj" \ + $(NULL) +LIB_ARCH_INC = \ + /I"lib\common" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_ARCH_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_ARCH_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_ARCH_LIB) + +# Dependency rules +$(LIB_ARCH_OBJ): $(AUTODEP) +!if EXIST($(LIB_ARCH_DST)\deps.mak) +!include $(LIB_ARCH_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_ARCH_SRC)\}.cpp{$(LIB_ARCH_DST)\}.obj:: +!else +{$(LIB_ARCH_SRC)\}.cpp{$(LIB_ARCH_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_ARCH_SRC) + -@$(MKDIR) $(LIB_ARCH_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_ARCH_INC) \ + /Fo$(LIB_ARCH_DST)\ \ + /Fd$(LIB_ARCH_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_ARCH_SRC) $(LIB_ARCH_DST) +$(LIB_ARCH_LIB): $(LIB_ARCH_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_ARCH_SRC) $(LIB_ARCH_DST) $(**:.obj=.d) diff --git a/lib/arch/XArch.cpp b/lib/arch/XArch.cpp new file mode 100644 index 00000000..9dce5283 --- /dev/null +++ b/lib/arch/XArch.cpp @@ -0,0 +1,33 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XArch.h" + +// +// XArch +// + +std::string +XArch::what() const throw() +{ + try { + if (m_what.empty() && m_eval != NULL) { + m_what = m_eval->eval(); + } + } + catch (...) { + // ignore + } + return m_what; +} diff --git a/lib/arch/XArch.h b/lib/arch/XArch.h new file mode 100644 index 00000000..75083649 --- /dev/null +++ b/lib/arch/XArch.h @@ -0,0 +1,170 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XARCH_H +#define XARCH_H + +#include "common.h" +#include "stdstring.h" + +//! Generic thread exception +/*! +Exceptions derived from this class are used by the multithreading +library to perform stack unwinding when a thread terminates. These +exceptions must always be rethrown by clients when caught. +*/ +class XThread { }; + +//! Thread exception to cancel +/*! +Thrown to cancel a thread. Clients must not throw this type, but +must rethrow it if caught (by XThreadCancel, XThread, or ...). +*/ +class XThreadCancel : public XThread { }; + +/*! +\def RETHROW_XTHREAD +Convenience macro to rethrow an XThread exception but ignore other +exceptions. Put this in your catch (...) handler after necessary +cleanup but before leaving or returning from the handler. +*/ +#define RETHROW_XTHREAD \ + try { throw; } catch (XThread&) { throw; } catch (...) { } + +//! Lazy error message string evaluation +/*! +This class encapsulates platform dependent error string lookup. +Platforms subclass this type, taking an appropriate error code +type in the c'tor and overriding eval() to return the error +string for that error code. +*/ +class XArchEval { +public: + XArchEval() { } + virtual ~XArchEval() { } + + virtual XArchEval* clone() const throw() = 0; + + virtual std::string eval() const throw() = 0; +}; + +//! Generic exception architecture dependent library +class XArch { +public: + XArch(XArchEval* adoptedEvaluator) : m_eval(adoptedEvaluator) { } + XArch(const std::string& msg) : m_eval(NULL), m_what(msg) { } + XArch(const XArch& e) : m_eval(e.m_eval != NULL ? e.m_eval->clone() : NULL), + m_what(e.m_what) { } + ~XArch() { delete m_eval; } + + std::string what() const throw(); + +private: + XArchEval* m_eval; + mutable std::string m_what; +}; + +// Macro to declare XArch derived types +#define XARCH_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_(XArchEval* adoptedEvaluator) : super_(adoptedEvaluator) { } \ + name_(const std::string& msg) : super_(msg) { } \ +} + +//! Generic network exception +/*! +Exceptions derived from this class are used by the networking +library to indicate various errors. +*/ +XARCH_SUBCLASS(XArchNetwork, XArch); + +//! Operation was interrupted +XARCH_SUBCLASS(XArchNetworkInterrupted, XArchNetwork); + +//! Network insufficient permission +XARCH_SUBCLASS(XArchNetworkAccess, XArchNetwork); + +//! Network insufficient resources +XARCH_SUBCLASS(XArchNetworkResource, XArchNetwork); + +//! No support for requested network resource/service +XARCH_SUBCLASS(XArchNetworkSupport, XArchNetwork); + +//! Network I/O error +XARCH_SUBCLASS(XArchNetworkIO, XArchNetwork); + +//! Network address is unavailable or not local +XARCH_SUBCLASS(XArchNetworkNoAddress, XArchNetwork); + +//! Network address in use +XARCH_SUBCLASS(XArchNetworkAddressInUse, XArchNetwork); + +//! No route to address +XARCH_SUBCLASS(XArchNetworkNoRoute, XArchNetwork); + +//! Socket not connected +XARCH_SUBCLASS(XArchNetworkNotConnected, XArchNetwork); + +//! Remote read end of socket has closed +XARCH_SUBCLASS(XArchNetworkShutdown, XArchNetwork); + +//! Remote end of socket has disconnected +XARCH_SUBCLASS(XArchNetworkDisconnected, XArchNetwork); + +//! Remote end of socket refused connection +XARCH_SUBCLASS(XArchNetworkConnectionRefused, XArchNetwork); + +//! Remote end of socket is not responding +XARCH_SUBCLASS(XArchNetworkTimedOut, XArchNetwork); + +//! Generic network name lookup erros +XARCH_SUBCLASS(XArchNetworkName, XArchNetwork); + +//! The named host is unknown +XARCH_SUBCLASS(XArchNetworkNameUnknown, XArchNetworkName); + +//! The named host is known but has no address +XARCH_SUBCLASS(XArchNetworkNameNoAddress, XArchNetworkName); + +//! Non-recoverable name server error +XARCH_SUBCLASS(XArchNetworkNameFailure, XArchNetworkName); + +//! Temporary name server error +XARCH_SUBCLASS(XArchNetworkNameUnavailable, XArchNetworkName); + +//! The named host is known but no supported address +XARCH_SUBCLASS(XArchNetworkNameUnsupported, XArchNetworkName); + +//! Generic daemon exception +/*! +Exceptions derived from this class are used by the daemon +library to indicate various errors. +*/ +XARCH_SUBCLASS(XArchDaemon, XArch); + +//! Could not daemonize +XARCH_SUBCLASS(XArchDaemonFailed, XArchDaemon); + +//! Could not install daemon +XARCH_SUBCLASS(XArchDaemonInstallFailed, XArchDaemon); + +//! Could not uninstall daemon +XARCH_SUBCLASS(XArchDaemonUninstallFailed, XArchDaemon); + +//! Attempted to uninstall a daemon that was not installed +XARCH_SUBCLASS(XArchDaemonUninstallNotInstalled, XArchDaemonUninstallFailed); + + +#endif diff --git a/lib/arch/XArchUnix.cpp b/lib/arch/XArchUnix.cpp new file mode 100644 index 00000000..6f4047d5 --- /dev/null +++ b/lib/arch/XArchUnix.cpp @@ -0,0 +1,33 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XArchUnix.h" +#include + +// +// XArchEvalUnix +// + +XArchEval* +XArchEvalUnix::clone() const throw() +{ + return new XArchEvalUnix(m_errno); +} + +std::string +XArchEvalUnix::eval() const throw() +{ + // FIXME -- not thread safe + return strerror(m_errno); +} diff --git a/lib/arch/XArchUnix.h b/lib/arch/XArchUnix.h new file mode 100644 index 00000000..19e0df4c --- /dev/null +++ b/lib/arch/XArchUnix.h @@ -0,0 +1,34 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XARCHUNIX_H +#define XARCHUNIX_H + +#include "XArch.h" + +//! Lazy error message string evaluation for unix +class XArchEvalUnix : public XArchEval { +public: + XArchEvalUnix(int err) : m_errno(err) { } + virtual ~XArchEvalUnix() { } + + // XArchEval overrides + virtual XArchEval* clone() const throw(); + virtual std::string eval() const throw(); + +private: + int m_errno; +}; + +#endif diff --git a/lib/arch/XArchWindows.cpp b/lib/arch/XArchWindows.cpp new file mode 100644 index 00000000..eebd6449 --- /dev/null +++ b/lib/arch/XArchWindows.cpp @@ -0,0 +1,127 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XArchWindows.h" +#include "CArchNetworkWinsock.h" + +// +// XArchEvalWindows +// + +XArchEval* +XArchEvalWindows::clone() const throw() +{ + return new XArchEvalWindows(m_errno); +} + +std::string +XArchEvalWindows::eval() const throw() +{ + char* cmsg; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, + m_errno, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&cmsg, + 0, + NULL) == 0) { + cmsg = NULL; + return "Unknown error"; + } + std::string smsg(cmsg); + LocalFree(cmsg); + return smsg; +} + + +// +// XArchEvalWinsock +// + +XArchEval* +XArchEvalWinsock::clone() const throw() +{ + return new XArchEvalWinsock(m_errno); +} + +std::string +XArchEvalWinsock::eval() const throw() +{ + // built-in windows function for looking up error message strings + // may not look up network error messages correctly. we'll have + // to do it ourself. + static const struct { int m_code; const char* m_msg; } s_netErrorCodes[] = { + /* 10004 */{WSAEINTR, "The (blocking) call was canceled via WSACancelBlockingCall"}, + /* 10009 */{WSAEBADF, "Bad file handle"}, + /* 10013 */{WSAEACCES, "The requested address is a broadcast address, but the appropriate flag was not set"}, + /* 10014 */{WSAEFAULT, "WSAEFAULT"}, + /* 10022 */{WSAEINVAL, "WSAEINVAL"}, + /* 10024 */{WSAEMFILE, "No more file descriptors available"}, + /* 10035 */{WSAEWOULDBLOCK, "Socket is marked as non-blocking and no connections are present or the receive operation would block"}, + /* 10036 */{WSAEINPROGRESS, "A blocking Windows Sockets operation is in progress"}, + /* 10037 */{WSAEALREADY, "The asynchronous routine being canceled has already completed"}, + /* 10038 */{WSAENOTSOCK, "At least on descriptor is not a socket"}, + /* 10039 */{WSAEDESTADDRREQ, "A destination address is required"}, + /* 10040 */{WSAEMSGSIZE, "The datagram was too large to fit into the specified buffer and was truncated"}, + /* 10041 */{WSAEPROTOTYPE, "The specified protocol is the wrong type for this socket"}, + /* 10042 */{WSAENOPROTOOPT, "The option is unknown or unsupported"}, + /* 10043 */{WSAEPROTONOSUPPORT,"The specified protocol is not supported"}, + /* 10044 */{WSAESOCKTNOSUPPORT,"The specified socket type is not supported by this address family"}, + /* 10045 */{WSAEOPNOTSUPP, "The referenced socket is not a type that supports that operation"}, + /* 10046 */{WSAEPFNOSUPPORT, "BSD: Protocol family not supported"}, + /* 10047 */{WSAEAFNOSUPPORT, "The specified address family is not supported"}, + /* 10048 */{WSAEADDRINUSE, "The specified address is already in use"}, + /* 10049 */{WSAEADDRNOTAVAIL, "The specified address is not available from the local machine"}, + /* 10050 */{WSAENETDOWN, "The Windows Sockets implementation has detected that the network subsystem has failed"}, + /* 10051 */{WSAENETUNREACH, "The network can't be reached from this hos at this time"}, + /* 10052 */{WSAENETRESET, "The connection must be reset because the Windows Sockets implementation dropped it"}, + /* 10053 */{WSAECONNABORTED, "The virtual circuit was aborted due to timeout or other failure"}, + /* 10054 */{WSAECONNRESET, "The virtual circuit was reset by the remote side"}, + /* 10055 */{WSAENOBUFS, "No buffer space is available or a buffer deadlock has occured. The socket cannot be created"}, + /* 10056 */{WSAEISCONN, "The socket is already connected"}, + /* 10057 */{WSAENOTCONN, "The socket is not connected"}, + /* 10058 */{WSAESHUTDOWN, "The socket has been shutdown"}, + /* 10059 */{WSAETOOMANYREFS, "BSD: Too many references"}, + /* 10060 */{WSAETIMEDOUT, "Attempt to connect timed out without establishing a connection"}, + /* 10061 */{WSAECONNREFUSED, "The attempt to connect was forcefully rejected"}, + /* 10062 */{WSAELOOP, "Undocumented WinSock error code used in BSD"}, + /* 10063 */{WSAENAMETOOLONG, "Undocumented WinSock error code used in BSD"}, + /* 10064 */{WSAEHOSTDOWN, "Undocumented WinSock error code used in BSD"}, + /* 10065 */{WSAEHOSTUNREACH, "No route to host"}, + /* 10066 */{WSAENOTEMPTY, "Undocumented WinSock error code"}, + /* 10067 */{WSAEPROCLIM, "Undocumented WinSock error code"}, + /* 10068 */{WSAEUSERS, "Undocumented WinSock error code"}, + /* 10069 */{WSAEDQUOT, "Undocumented WinSock error code"}, + /* 10070 */{WSAESTALE, "Undocumented WinSock error code"}, + /* 10071 */{WSAEREMOTE, "Undocumented WinSock error code"}, + /* 10091 */{WSASYSNOTREADY, "Underlying network subsytem is not ready for network communication"}, + /* 10092 */{WSAVERNOTSUPPORTED, "The version of WinSock API support requested is not provided in this implementation"}, + /* 10093 */{WSANOTINITIALISED, "WinSock subsystem not properly initialized"}, + /* 10101 */{WSAEDISCON, "Virtual circuit has gracefully terminated connection"}, + /* 11001 */{WSAHOST_NOT_FOUND, "The specified host is unknown"}, + /* 11002 */{WSATRY_AGAIN, "A temporary error occurred on an authoritative name server"}, + /* 11003 */{WSANO_RECOVERY, "A non-recoverable name server error occurred"}, + /* 11004 */{WSANO_DATA, "The requested name is valid but does not have an IP address"}, + /* end */{0, NULL} + }; + + for (unsigned int i = 0; s_netErrorCodes[i].m_code != 0; ++i) { + if (s_netErrorCodes[i].m_code == m_errno) { + return s_netErrorCodes[i].m_msg; + } + } + return "Unknown error"; +} diff --git a/lib/arch/XArchWindows.h b/lib/arch/XArchWindows.h new file mode 100644 index 00000000..56c24007 --- /dev/null +++ b/lib/arch/XArchWindows.h @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XARCHWINDOWS_H +#define XARCHWINDOWS_H + +#define WIN32_LEAN_AND_MEAN + +#include "XArch.h" +#include + +//! Lazy error message string evaluation for windows +class XArchEvalWindows : public XArchEval { +public: + XArchEvalWindows() : m_errno(GetLastError()) { } + XArchEvalWindows(DWORD err) : m_errno(err) { } + virtual ~XArchEvalWindows() { } + + // XArchEval overrides + virtual XArchEval* clone() const throw(); + virtual std::string eval() const throw(); + +private: + DWORD m_errno; +}; + +//! Lazy error message string evaluation for winsock +class XArchEvalWinsock : public XArchEval { +public: + XArchEvalWinsock(int err) : m_errno(err) { } + virtual ~XArchEvalWinsock() { } + + // XArchEval overrides + virtual XArchEval* clone() const throw(); + virtual std::string eval() const throw(); + +private: + int m_errno; +}; + +#endif diff --git a/lib/arch/vsnprintf.cpp b/lib/arch/vsnprintf.cpp new file mode 100644 index 00000000..10800ec7 --- /dev/null +++ b/lib/arch/vsnprintf.cpp @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#if HAVE_VSNPRINTF + +#if !defined(ARCH_VSNPRINTF) +# define ARCH_VSNPRINTF vsnprintf +#endif + +int +ARCH_STRING::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + int n = ::ARCH_VSNPRINTF(str, size, fmt, ap); + if (n > size) { + n = -1; + } + return n; +} + +#elif SYSAPI_UNIX // !HAVE_VSNPRINTF + +#include + +int +ARCH_STRING::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + static FILE* bitbucket = fopen("/dev/null", "w"); + if (bitbucket == NULL) { + // uh oh + if (size > 0) { + str[0] = '\0'; + } + return 0; + } + else { + // count the characters using the bitbucket + int n = vfprintf(bitbucket, fmt, ap); + if (n + 1 <= size) { + // it'll fit so print it into str + vsprintf(str, fmt, ap); + } + return n; + } +} + +#else // !HAVE_VSNPRINTF && !SYSAPI_UNIX + +#error vsnprintf not implemented + +#endif // !HAVE_VSNPRINTF diff --git a/lib/base/CEvent.cpp b/lib/base/CEvent.cpp new file mode 100644 index 00000000..bfdf88ed --- /dev/null +++ b/lib/base/CEvent.cpp @@ -0,0 +1,98 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CEvent.h" +#include "CEventQueue.h" + +// +// CEvent +// + +CEvent::CEvent() : + m_type(kUnknown), + m_target(NULL), + m_data(NULL), + m_flags(0) +{ + // do nothing +} + +CEvent::CEvent(Type type, void* target, void* data, Flags flags) : + m_type(type), + m_target(target), + m_data(data), + m_flags(flags) +{ + // do nothing +} + +CEvent::Type +CEvent::getType() const +{ + return m_type; +} + +void* +CEvent::getTarget() const +{ + return m_target; +} + +void* +CEvent::getData() const +{ + return m_data; +} + +CEvent::Flags +CEvent::getFlags() const +{ + return m_flags; +} + +CEvent::Type +CEvent::registerType(const char* name) +{ + return EVENTQUEUE->registerType(name); +} + +CEvent::Type +CEvent::registerTypeOnce(Type& type, const char* name) +{ + return EVENTQUEUE->registerTypeOnce(type, name); +} + +const char* +CEvent::getTypeName(Type type) +{ + return EVENTQUEUE->getTypeName(type); +} + +void +CEvent::deleteData(const CEvent& event) +{ + switch (event.getType()) { + case kUnknown: + case kQuit: + case kSystem: + case kTimer: + break; + + default: + if ((event.getFlags() & kDontFreeData) == 0) { + free(event.getData()); + } + break; + } +} diff --git a/lib/base/CEvent.h b/lib/base/CEvent.h new file mode 100644 index 00000000..8b637cef --- /dev/null +++ b/lib/base/CEvent.h @@ -0,0 +1,123 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CEVENT_H +#define CEVENT_H + +#include "BasicTypes.h" +#include "stdmap.h" + +//! Event +/*! +A \c CEvent holds an event type and a pointer to event data. +*/ +class CEvent { +public: + typedef UInt32 Type; + enum { + kUnknown, //!< The event type is unknown + kQuit, //!< The quit event + kSystem, //!< The data points to a system event type + kTimer, //!< The data points to timer info + kLast //!< Must be last + }; + + typedef UInt32 Flags; + enum { + kNone = 0x00, //!< No flags + kDeliverImmediately = 0x01, //!< Dispatch and free event immediately + kDontFreeData = 0x02 //!< Don't free data in deleteData + }; + + CEvent(); + + //! Create \c CEvent with data + /*! + The \p type must have been registered using \c registerType(). + The \p data must be POD (plain old data) allocated by malloc(), + which means it cannot have a constructor, destructor or be + composed of any types that do. \p target is the intended + recipient of the event. \p flags is any combination of \c Flags. + */ + CEvent(Type type, void* target = NULL, void* data = NULL, + UInt32 flags = kNone); + + //! @name manipulators + //@{ + + //! Creates a new event type + /*! + Returns a unique event type id. + */ + static Type registerType(const char* name); + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + static Type registerTypeOnce(Type& type, const char* name); + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + static const char* getTypeName(Type type); + + //! Release event data + /*! + Deletes event data for the given event (using free()). + */ + static void deleteData(const CEvent&); + + //@} + //! @name accessors + //@{ + + //! Get event type + /*! + Returns the event type. + */ + Type getType() const; + + //! Get the event target + /*! + Returns the event target. + */ + void* getTarget() const; + + //! Get the event data + /*! + Returns the event data. + */ + void* getData() const; + + //! Get event flags + /*! + Returns the event flags. + */ + Flags getFlags() const; + + //@} + +private: + Type m_type; + void* m_target; + void* m_data; + Flags m_flags; +}; + +#endif diff --git a/lib/base/CEventQueue.cpp b/lib/base/CEventQueue.cpp new file mode 100644 index 00000000..d0a93391 --- /dev/null +++ b/lib/base/CEventQueue.cpp @@ -0,0 +1,526 @@ +;/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CEventQueue.h" +#include "CLog.h" +#include "CSimpleEventQueueBuffer.h" +#include "CStopwatch.h" +#include "IEventJob.h" +#include "CArch.h" + +// interrupt handler. this just adds a quit event to the queue. +static +void +interrupt(CArch::ESignal, void*) +{ + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); +} + + +// +// CEventQueue +// + +CEventQueue::CEventQueue() : + m_nextType(CEvent::kLast) +{ + setInstance(this); + m_mutex = ARCH->newMutex(); + ARCH->setSignalHandler(CArch::kINTERRUPT, &interrupt, NULL); + ARCH->setSignalHandler(CArch::kTERMINATE, &interrupt, NULL); + m_buffer = new CSimpleEventQueueBuffer; +} + +CEventQueue::~CEventQueue() +{ + delete m_buffer; + ARCH->setSignalHandler(CArch::kINTERRUPT, NULL, NULL); + ARCH->setSignalHandler(CArch::kTERMINATE, NULL, NULL); + ARCH->closeMutex(m_mutex); + setInstance(NULL); +} + +CEvent::Type +CEventQueue::registerType(const char* name) +{ + CArchMutexLock lock(m_mutex); + m_typeMap.insert(std::make_pair(m_nextType, name)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + return m_nextType++; +} + +CEvent::Type +CEventQueue::registerTypeOnce(CEvent::Type& type, const char* name) +{ + CArchMutexLock lock(m_mutex); + if (type == CEvent::kUnknown) { + m_typeMap.insert(std::make_pair(m_nextType, name)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + type = m_nextType++; + } + return type; +} + +const char* +CEventQueue::getTypeName(CEvent::Type type) +{ + switch (type) { + case CEvent::kUnknown: + return "nil"; + + case CEvent::kQuit: + return "quit"; + + case CEvent::kSystem: + return "system"; + + case CEvent::kTimer: + return "timer"; + + default: + CTypeMap::const_iterator i = m_typeMap.find(type); + if (i == m_typeMap.end()) { + return ""; + } + else { + return i->second; + } + } +} + +void +CEventQueue::adoptBuffer(IEventQueueBuffer* buffer) +{ + CArchMutexLock lock(m_mutex); + + // discard old buffer and old events + delete m_buffer; + for (CEventTable::iterator i = m_events.begin(); i != m_events.end(); ++i) { + CEvent::deleteData(i->second); + } + m_events.clear(); + m_oldEventIDs.clear(); + + // use new buffer + m_buffer = buffer; + if (m_buffer == NULL) { + m_buffer = new CSimpleEventQueueBuffer; + } +} + +bool +CEventQueue::getEvent(CEvent& event, double timeout) +{ + CStopwatch timer(true); +retry: + // if no events are waiting then handle timers and then wait + while (m_buffer->isEmpty()) { + // handle timers first + if (hasTimerExpired(event)) { + return true; + } + + // get time remaining in timeout + double timeLeft = timeout - timer.getTime(); + if (timeout >= 0.0 && timeLeft <= 0.0) { + return false; + } + + // get time until next timer expires. if there is a timer + // and it'll expire before the client's timeout then use + // that duration for our timeout instead. + double timerTimeout = getNextTimerTimeout(); + if (timeout < 0.0 || (timerTimeout >= 0.0 && timerTimeout < timeLeft)) { + timeLeft = timerTimeout; + } + + // wait for an event + m_buffer->waitForEvent(timeLeft); + } + + // get the event + UInt32 dataID; + IEventQueueBuffer::Type type = m_buffer->getEvent(event, dataID); + switch (type) { + case IEventQueueBuffer::kNone: + if (timeout < 0.0 || timeout <= timer.getTime()) { + // don't want to fail if client isn't expecting that + // so if getEvent() fails with an infinite timeout + // then just try getting another event. + goto retry; + } + return false; + + case IEventQueueBuffer::kSystem: + return true; + + case IEventQueueBuffer::kUser: + { + CArchMutexLock lock(m_mutex); + event = removeEvent(dataID); + return true; + } + + default: + assert(0 && "invalid event type"); + return false; + } +} + +bool +CEventQueue::dispatchEvent(const CEvent& event) +{ + void* target = event.getTarget(); + IEventJob* job = getHandler(event.getType(), target); + if (job == NULL) { + job = getHandler(CEvent::kUnknown, target); + } + if (job != NULL) { + job->run(event); + return true; + } + return false; +} + +void +CEventQueue::addEvent(const CEvent& event) +{ + // discard bogus event types + switch (event.getType()) { + case CEvent::kUnknown: + case CEvent::kSystem: + case CEvent::kTimer: + return; + + default: + break; + } + + if ((event.getFlags() & CEvent::kDeliverImmediately) != 0) { + dispatchEvent(event); + CEvent::deleteData(event); + } + else { + CArchMutexLock lock(m_mutex); + + // store the event's data locally + UInt32 eventID = saveEvent(event); + + // add it + if (!m_buffer->addEvent(eventID)) { + // failed to send event + removeEvent(eventID); + CEvent::deleteData(event); + } + } +} + +CEventQueueTimer* +CEventQueue::newTimer(double duration, void* target) +{ + assert(duration > 0.0); + + CEventQueueTimer* timer = m_buffer->newTimer(duration, false); + if (target == NULL) { + target = timer; + } + CArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(CTimer(timer, duration, + duration + m_time.getTime(), target, false)); + return timer; +} + +CEventQueueTimer* +CEventQueue::newOneShotTimer(double duration, void* target) +{ + assert(duration > 0.0); + + CEventQueueTimer* timer = m_buffer->newTimer(duration, true); + if (target == NULL) { + target = timer; + } + CArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(CTimer(timer, duration, + duration + m_time.getTime(), target, true)); + return timer; +} + +void +CEventQueue::deleteTimer(CEventQueueTimer* timer) +{ + CArchMutexLock lock(m_mutex); + for (CTimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + if (index->getTimer() == timer) { + m_timerQueue.erase(index); + break; + } + } + CTimers::iterator index = m_timers.find(timer); + if (index != m_timers.end()) { + m_timers.erase(index); + } + m_buffer->deleteTimer(timer); +} + +void +CEventQueue::adoptHandler(CEvent::Type type, void* target, IEventJob* handler) +{ + CArchMutexLock lock(m_mutex); + IEventJob*& job = m_handlers[target][type]; + delete job; + job = handler; +} + +void +CEventQueue::removeHandler(CEvent::Type type, void* target) +{ + IEventJob* handler = NULL; + { + CArchMutexLock lock(m_mutex); + CHandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + CTypeHandlerTable& typeHandlers = index->second; + CTypeHandlerTable::iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + handler = index2->second; + typeHandlers.erase(index2); + } + } + } + delete handler; +} + +void +CEventQueue::removeHandlers(void* target) +{ + std::vector handlers; + { + CArchMutexLock lock(m_mutex); + CHandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + // copy to handlers array and clear table for target + CTypeHandlerTable& typeHandlers = index->second; + for (CTypeHandlerTable::iterator index2 = typeHandlers.begin(); + index2 != typeHandlers.end(); ++index2) { + handlers.push_back(index2->second); + } + typeHandlers.clear(); + } + } + + // delete handlers + for (std::vector::iterator index = handlers.begin(); + index != handlers.end(); ++index) { + delete *index; + } +} + +bool +CEventQueue::isEmpty() const +{ + return (m_buffer->isEmpty() && getNextTimerTimeout() != 0.0); +} + +IEventJob* +CEventQueue::getHandler(CEvent::Type type, void* target) const +{ + CArchMutexLock lock(m_mutex); + CHandlerTable::const_iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + const CTypeHandlerTable& typeHandlers = index->second; + CTypeHandlerTable::const_iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + return index2->second; + } + } + return NULL; +} + +UInt32 +CEventQueue::saveEvent(const CEvent& event) +{ + // choose id + UInt32 id; + if (!m_oldEventIDs.empty()) { + // reuse an id + id = m_oldEventIDs.back(); + m_oldEventIDs.pop_back(); + } + else { + // make a new id + id = static_cast(m_events.size()); + } + + // save data + m_events[id] = event; + return id; +} + +CEvent +CEventQueue::removeEvent(UInt32 eventID) +{ + // look up id + CEventTable::iterator index = m_events.find(eventID); + if (index == m_events.end()) { + return CEvent(); + } + + // get data + CEvent event = index->second; + m_events.erase(index); + + // save old id for reuse + m_oldEventIDs.push_back(eventID); + + return event; +} + +bool +CEventQueue::hasTimerExpired(CEvent& event) +{ + // return true if there's a timer in the timer priority queue that + // has expired. if returning true then fill in event appropriately + // and reset and reinsert the timer. + if (m_timerQueue.empty()) { + return false; + } + + // get time elapsed since last check + const double time = m_time.getTime(); + m_time.reset(); + + // countdown elapsed time + for (CTimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + (*index) -= time; + } + + // done if no timers are expired + if (m_timerQueue.top() > 0.0) { + return false; + } + + // remove timer from queue + CTimer timer = m_timerQueue.top(); + m_timerQueue.pop(); + + // prepare event and reset the timer's clock + timer.fillEvent(m_timerEvent); + event = CEvent(CEvent::kTimer, timer.getTarget(), &m_timerEvent); + timer.reset(); + + // reinsert timer into queue if it's not a one-shot + if (!timer.isOneShot()) { + m_timerQueue.push(timer); + } + + return true; +} + +double +CEventQueue::getNextTimerTimeout() const +{ + // return -1 if no timers, 0 if the top timer has expired, otherwise + // the time until the top timer in the timer priority queue will + // expire. + if (m_timerQueue.empty()) { + return -1.0; + } + if (m_timerQueue.top() <= 0.0) { + return 0.0; + } + return m_timerQueue.top(); +} + + +// +// CEventQueue::CTimer +// + +CEventQueue::CTimer::CTimer(CEventQueueTimer* timer, double timeout, + double initialTime, void* target, bool oneShot) : + m_timer(timer), + m_timeout(timeout), + m_target(target), + m_oneShot(oneShot), + m_time(initialTime) +{ + assert(m_timeout > 0.0); +} + +CEventQueue::CTimer::~CTimer() +{ + // do nothing +} + +void +CEventQueue::CTimer::reset() +{ + m_time = m_timeout; +} + +CEventQueue::CTimer& +CEventQueue::CTimer::operator-=(double dt) +{ + m_time -= dt; + return *this; +} + +CEventQueue::CTimer::operator double() const +{ + return m_time; +} + +bool +CEventQueue::CTimer::isOneShot() const +{ + return m_oneShot; +} + +CEventQueueTimer* +CEventQueue::CTimer::getTimer() const +{ + return m_timer; +} + +void* +CEventQueue::CTimer::getTarget() const +{ + return m_target; +} + +void +CEventQueue::CTimer::fillEvent(CTimerEvent& event) const +{ + event.m_timer = m_timer; + event.m_count = 0; + if (m_time <= 0.0) { + event.m_count = static_cast((m_timeout - m_time) / m_timeout); + } +} + +bool +CEventQueue::CTimer::operator<(const CTimer& t) const +{ + return m_time < t.m_time; +} diff --git a/lib/base/CEventQueue.h b/lib/base/CEventQueue.h new file mode 100644 index 00000000..a63c7b16 --- /dev/null +++ b/lib/base/CEventQueue.h @@ -0,0 +1,123 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CEVENTQUEUE_H +#define CEVENTQUEUE_H + +#include "IEventQueue.h" +#include "CEvent.h" +#include "CPriorityQueue.h" +#include "CStopwatch.h" +#include "IArchMultithread.h" +#include "stdmap.h" +#include "stdset.h" + +//! Event queue +/*! +An event queue that implements the platform independent parts and +delegates the platform dependent parts to a subclass. +*/ +class CEventQueue : public IEventQueue { +public: + CEventQueue(); + virtual ~CEventQueue(); + + // IEventQueue overrides + virtual void adoptBuffer(IEventQueueBuffer*); + virtual bool getEvent(CEvent& event, double timeout = -1.0); + virtual bool dispatchEvent(const CEvent& event); + virtual void addEvent(const CEvent& event); + virtual CEventQueueTimer* + newTimer(double duration, void* target); + virtual CEventQueueTimer* + newOneShotTimer(double duration, void* target); + virtual void deleteTimer(CEventQueueTimer*); + virtual void adoptHandler(CEvent::Type type, + void* target, IEventJob* handler); + virtual void removeHandler(CEvent::Type type, void* target); + virtual void removeHandlers(void* target); + virtual CEvent::Type + registerType(const char* name); + virtual CEvent::Type + registerTypeOnce(CEvent::Type& type, const char* name); + virtual bool isEmpty() const; + virtual IEventJob* getHandler(CEvent::Type type, void* target) const; + virtual const char* getTypeName(CEvent::Type type); + +private: + UInt32 saveEvent(const CEvent& event); + CEvent removeEvent(UInt32 eventID); + bool hasTimerExpired(CEvent& event); + double getNextTimerTimeout() const; + +private: + class CTimer { + public: + CTimer(CEventQueueTimer*, double timeout, double initialTime, + void* target, bool oneShot); + ~CTimer(); + + void reset(); + + CTimer& operator-=(double); + + operator double() const; + + bool isOneShot() const; + CEventQueueTimer* + getTimer() const; + void* getTarget() const; + void fillEvent(CTimerEvent&) const; + + bool operator<(const CTimer&) const; + + private: + CEventQueueTimer* m_timer; + double m_timeout; + void* m_target; + bool m_oneShot; + double m_time; + }; + typedef std::set CTimers; + typedef CPriorityQueue CTimerQueue; + typedef std::map CEventTable; + typedef std::vector CEventIDList; + typedef std::map CTypeMap; + typedef std::map CTypeHandlerTable; + typedef std::map CHandlerTable; + + CArchMutex m_mutex; + + // registered events + CEvent::Type m_nextType; + CTypeMap m_typeMap; + + // buffer of events + IEventQueueBuffer* m_buffer; + + // saved events + CEventTable m_events; + CEventIDList m_oldEventIDs; + + // timers + CStopwatch m_time; + CTimers m_timers; + CTimerQueue m_timerQueue; + CTimerEvent m_timerEvent; + + // event handlers + CHandlerTable m_handlers; +}; + +#endif diff --git a/lib/base/CFunctionEventJob.cpp b/lib/base/CFunctionEventJob.cpp new file mode 100644 index 00000000..5afdc988 --- /dev/null +++ b/lib/base/CFunctionEventJob.cpp @@ -0,0 +1,40 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CFunctionEventJob.h" + +// +// CFunctionEventJob +// + +CFunctionEventJob::CFunctionEventJob( + void (*func)(const CEvent&, void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +CFunctionEventJob::~CFunctionEventJob() +{ + // do nothing +} + +void +CFunctionEventJob::run(const CEvent& event) +{ + if (m_func != NULL) { + m_func(event, m_arg); + } +} diff --git a/lib/base/CFunctionEventJob.h b/lib/base/CFunctionEventJob.h new file mode 100644 index 00000000..517b9c45 --- /dev/null +++ b/lib/base/CFunctionEventJob.h @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CFUNCTIONEVENTJOB_H +#define CFUNCTIONEVENTJOB_H + +#include "IEventJob.h" + +//! Use a function as an event job +/*! +An event job class that invokes a function. +*/ +class CFunctionEventJob : public IEventJob { +public: + //! run() invokes \c func(arg) + CFunctionEventJob(void (*func)(const CEvent&, void*), void* arg = NULL); + virtual ~CFunctionEventJob(); + + // IEventJob overrides + virtual void run(const CEvent&); + +private: + void (*m_func)(const CEvent&, void*); + void* m_arg; +}; + +#endif diff --git a/lib/base/CFunctionJob.cpp b/lib/base/CFunctionJob.cpp new file mode 100644 index 00000000..bc16c509 --- /dev/null +++ b/lib/base/CFunctionJob.cpp @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CFunctionJob.h" + +// +// CFunctionJob +// + +CFunctionJob::CFunctionJob(void (*func)(void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +CFunctionJob::~CFunctionJob() +{ + // do nothing +} + +void +CFunctionJob::run() +{ + if (m_func != NULL) { + m_func(m_arg); + } +} diff --git a/lib/base/CFunctionJob.h b/lib/base/CFunctionJob.h new file mode 100644 index 00000000..e30bbfa2 --- /dev/null +++ b/lib/base/CFunctionJob.h @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CFUNCTIONJOB_H +#define CFUNCTIONJOB_H + +#include "IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a function. +*/ +class CFunctionJob : public IJob { +public: + //! run() invokes \c func(arg) + CFunctionJob(void (*func)(void*), void* arg = NULL); + virtual ~CFunctionJob(); + + // IJob overrides + virtual void run(); + +private: + void (*m_func)(void*); + void* m_arg; +}; + +#endif diff --git a/lib/base/CLog.cpp b/lib/base/CLog.cpp new file mode 100644 index 00000000..7c73ac87 --- /dev/null +++ b/lib/base/CLog.cpp @@ -0,0 +1,293 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CLog.h" +#include "CString.h" +#include "CStringUtil.h" +#include "LogOutputters.h" +#include "CArch.h" +#include "Version.h" +#include +#include + +// names of priorities +static const char* g_priority[] = { + "FATAL", + "ERROR", + "WARNING", + "NOTE", + "INFO", + "DEBUG", + "DEBUG1", + "DEBUG2" + }; + +// number of priorities +static const int g_numPriority = (int)(sizeof(g_priority) / + sizeof(g_priority[0])); + +// the default priority +#if defined(NDEBUG) +static const int g_defaultMaxPriority = 4; +#else +static const int g_defaultMaxPriority = 5; +#endif + +// length of longest string in g_priority +static const int g_maxPriorityLength = 7; + +// length of suffix string (": ") +static const int g_prioritySuffixLength = 2; + +// amount of padded required to fill in the priority prefix +static const int g_priorityPad = g_maxPriorityLength + + g_prioritySuffixLength; + + +// +// CLog +// + +CLog* CLog::s_log = NULL; + +CLog::CLog() +{ + assert(s_log == NULL); + + // create mutex for multithread safe operation + m_mutex = ARCH->newMutex(); + + // other initalization + m_maxPriority = g_defaultMaxPriority; + m_maxNewlineLength = 0; + insert(new CConsoleLogOutputter); +} + +CLog::~CLog() +{ + // clean up + for (COutputterList::iterator index = m_outputters.begin(); + index != m_outputters.end(); ++index) { + delete *index; + } + for (COutputterList::iterator index = m_alwaysOutputters.begin(); + index != m_alwaysOutputters.end(); ++index) { + delete *index; + } + ARCH->closeMutex(m_mutex); + s_log = NULL; +} + +CLog* +CLog::getInstance() +{ + // note -- not thread safe; client must initialize log safely + if (s_log == NULL) { + s_log = new CLog; + } + return s_log; +} + +void +CLog::print(const char* file, int line, const char* fmt, ...) const +{ + // check if fmt begins with a priority argument + int priority = 4; + if (fmt[0] == '%' && fmt[1] == 'z') { + priority = fmt[2] - '\060'; + fmt += 3; + } + + // done if below priority threshold + if (priority > getFilter()) { + return; + } + + // compute prefix padding length + char stack[1024]; + int pPad = g_priorityPad; + if (file != NULL) { + sprintf(stack, "%d", line); + pPad += strlen(file) + 1 /* comma */ + + strlen(stack) + 1 /* colon */ + 1 /* space */; + } + + // compute suffix padding length + int sPad = m_maxNewlineLength; + + // print to buffer, leaving space for a newline at the end and prefix + // at the beginning. + char* buffer = stack; + int len = (int)(sizeof(stack) / sizeof(stack[0])); + while (true) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer + pPad, len - pPad - sPad, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > (int)len) { + if (buffer != stack) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if the buffer was big enough then continue + else { + break; + } + } + + // print the prefix to the buffer. leave space for priority label. + char* message = buffer; + if (file != NULL) { + sprintf(buffer + g_priorityPad, "%s,%d:", file, line); + buffer[pPad - 1] = ' '; + + // discard file and line if priority < 0 + if (priority < 0) { + message += pPad - g_priorityPad; + } + } + + // output buffer + output(priority, message); + + // clean up + if (buffer != stack) { + delete[] buffer; + } +} + +void +CLog::insert(ILogOutputter* outputter, bool alwaysAtHead) +{ + assert(outputter != NULL); + assert(outputter->getNewline() != NULL); + + CArchMutexLock lock(m_mutex); + if (alwaysAtHead) { + m_alwaysOutputters.push_front(outputter); + } + else { + m_outputters.push_front(outputter); + } + int newlineLength = strlen(outputter->getNewline()); + if (newlineLength > m_maxNewlineLength) { + m_maxNewlineLength = newlineLength; + } + outputter->open(kAppVersion); + outputter->show(false); +} + +void +CLog::remove(ILogOutputter* outputter) +{ + CArchMutexLock lock(m_mutex); + m_outputters.remove(outputter); + m_alwaysOutputters.remove(outputter); +} + +void +CLog::pop_front(bool alwaysAtHead) +{ + CArchMutexLock lock(m_mutex); + COutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters; + if (!list->empty()) { + delete list->front(); + list->pop_front(); + } +} + +bool +CLog::setFilter(const char* maxPriority) +{ + if (maxPriority != NULL) { + for (int i = 0; i < g_numPriority; ++i) { + if (strcmp(maxPriority, g_priority[i]) == 0) { + setFilter(i); + return true; + } + } + return false; + } + return true; +} + +void +CLog::setFilter(int maxPriority) +{ + CArchMutexLock lock(m_mutex); + m_maxPriority = maxPriority; +} + +int +CLog::getFilter() const +{ + CArchMutexLock lock(m_mutex); + return m_maxPriority; +} + +void +CLog::output(int priority, char* msg) const +{ + assert(priority >= -1 && priority < g_numPriority); + assert(msg != NULL); + + // insert priority label + int n = -g_prioritySuffixLength; + if (priority >= 0) { + n = strlen(g_priority[priority]); + strcpy(msg + g_maxPriorityLength - n, g_priority[priority]); + msg[g_maxPriorityLength + 0] = ':'; + msg[g_maxPriorityLength + 1] = ' '; + msg[g_maxPriorityLength + 1] = ' '; + } + + // find end of message + char* end = msg + g_priorityPad + strlen(msg + g_priorityPad); + + // write to each outputter + CArchMutexLock lock(m_mutex); + for (COutputterList::const_iterator index = m_alwaysOutputters.begin(); + index != m_alwaysOutputters.end(); + ++index) { + // get outputter + ILogOutputter* outputter = *index; + + // put an appropriate newline at the end + strcpy(end, outputter->getNewline()); + + // write message + outputter->write(static_cast(priority), + msg + g_maxPriorityLength - n); + } + for (COutputterList::const_iterator index = m_outputters.begin(); + index != m_outputters.end(); ++index) { + // get outputter + ILogOutputter* outputter = *index; + + // put an appropriate newline at the end + strcpy(end, outputter->getNewline()); + + // write message and break out of loop if it returns false + if (!outputter->write(static_cast(priority), + msg + g_maxPriorityLength - n)) { + break; + } + } +} diff --git a/lib/base/CLog.h b/lib/base/CLog.h new file mode 100644 index 00000000..391480e2 --- /dev/null +++ b/lib/base/CLog.h @@ -0,0 +1,202 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CLOG_H +#define CLOG_H + +#include "common.h" +#include "IArchMultithread.h" +#include "stdlist.h" +#include + +#define CLOG (CLog::getInstance()) + +class ILogOutputter; + +//! Logging facility +/*! +The logging class; all console output should go through this class. +It supports multithread safe operation, several message priority levels, +filtering by priority, and output redirection. The macros LOG() and +LOGC() provide convenient access. +*/ +class CLog { +public: + //! Log levels + /*! + The logging priority levels in order of highest to lowest priority. + */ + enum ELevel { + kFATAL, //!< For fatal errors + kERROR, //!< For serious errors + kWARNING, //!< For minor errors and warnings + kNOTE, //!< For messages about notable events + kINFO, //!< For informational messages + kDEBUG, //!< For important debugging messages + kDEBUG1, //!< For more detailed debugging messages + kDEBUG2 //!< For even more detailed debugging messages + }; + + ~CLog(); + + //! @name manipulators + //@{ + + //! Add an outputter to the head of the list + /*! + Inserts an outputter to the head of the outputter list. When the + logger writes a message, it goes to the outputter at the head of + the outputter list. If that outputter's \c write() method returns + true then it also goes to the next outputter, as so on until an + outputter returns false or there are no more outputters. Outputters + still in the outputter list when the log is destroyed will be + deleted. If \c alwaysAtHead is true then the outputter is always + called before all outputters with \c alwaysAtHead false and the + return value of the outputter is ignored. + + By default, the logger has one outputter installed which writes to + the console. + */ + void insert(ILogOutputter* adopted, + bool alwaysAtHead = false); + + //! Remove an outputter from the list + /*! + Removes the first occurrence of the given outputter from the + outputter list. It does nothing if the outputter is not in the + list. The outputter is not deleted. + */ + void remove(ILogOutputter* orphaned); + + //! Remove the outputter from the head of the list + /*! + Removes and deletes the outputter at the head of the outputter list. + This does nothing if the outputter list is empty. Only removes + outputters that were inserted with the matching \c alwaysAtHead. + */ + void pop_front(bool alwaysAtHead = false); + + //! Set the minimum priority filter. + /*! + Set the filter. Messages below this priority are discarded. + The default priority is 4 (INFO) (unless built without NDEBUG + in which case it's 5 (DEBUG)). setFilter(const char*) returns + true if the priority \c name was recognized; if \c name is NULL + then it simply returns true. + */ + bool setFilter(const char* name); + void setFilter(int); + + //@} + //! @name accessors + //@{ + + //! Print a log message + /*! + Print a log message using the printf-like \c format and arguments + preceded by the filename and line number. If \c file is NULL then + neither the file nor the line are printed. + */ + void print(const char* file, int line, + const char* format, ...) const; + + //! Get the minimum priority level. + int getFilter() const; + + //! Get the singleton instance of the log + static CLog* getInstance(); + + //@} + +private: + CLog(); + + void output(int priority, char* msg) const; + +private: + typedef std::list COutputterList; + + static CLog* s_log; + + CArchMutex m_mutex; + COutputterList m_outputters; + COutputterList m_alwaysOutputters; + int m_maxNewlineLength; + int m_maxPriority; +}; + +/*! +\def LOG(arg) +Write to the log. Because macros cannot accept variable arguments, this +should be invoked like so: +\code +LOG((CLOG_XXX "%d and %d are %s", x, y, x == y ? "equal" : "not equal")); +\endcode +In particular, notice the double open and close parentheses. Also note +that there is no comma after the \c CLOG_XXX. The \c XXX should be +replaced by one of enumerants in \c CLog::ELevel without the leading +\c k. For example, \c CLOG_INFO. The special \c CLOG_PRINT level will +not be filtered and is never prefixed by the filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is defined during the build then it expands to a +call to CLog::print. Otherwise it expands to a call to CLog::printt, +which includes the filename and line number. +*/ + +/*! +\def LOGC(expr, arg) +Write to the log if and only if expr is true. Because macros cannot accept +variable arguments, this should be invoked like so: +\code +LOGC(x == y, (CLOG_XXX "%d and %d are equal", x, y)); +\endcode +In particular, notice the parentheses around everything after the boolean +expression. Also note that there is no comma after the \c CLOG_XXX. +The \c XXX should be replaced by one of enumerants in \c CLog::ELevel +without the leading \c k. For example, \c CLOG_INFO. The special +\c CLOG_PRINT level will not be filtered and is never prefixed by the +filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is not defined during the build then it expands +to a call to CLog::print that prints the filename and line number, +otherwise it expands to a call that doesn't. +*/ + +#if defined(NOLOGGING) +#define LOG(_a1) +#define LOGC(_a1, _a2) +#define CLOG_TRACE +#elif defined(NDEBUG) +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE NULL, 0, +#else +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE __FILE__, __LINE__, +#endif + +#define CLOG_PRINT CLOG_TRACE "%z\057" +#define CLOG_CRIT CLOG_TRACE "%z\060" +#define CLOG_ERR CLOG_TRACE "%z\061" +#define CLOG_WARN CLOG_TRACE "%z\062" +#define CLOG_NOTE CLOG_TRACE "%z\063" +#define CLOG_INFO CLOG_TRACE "%z\064" +#define CLOG_DEBUG CLOG_TRACE "%z\065" +#define CLOG_DEBUG1 CLOG_TRACE "%z\066" +#define CLOG_DEBUG2 CLOG_TRACE "%z\067" + +#endif diff --git a/lib/base/CPriorityQueue.h b/lib/base/CPriorityQueue.h new file mode 100644 index 00000000..29129e31 --- /dev/null +++ b/lib/base/CPriorityQueue.h @@ -0,0 +1,136 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPRIORITYQUEUE_H +#define CPRIORITYQUEUE_H + +#include "stdvector.h" +#include +#include + +//! A priority queue with an iterator +/*! +This priority queue is the same as a standard priority queue except: +it sorts by std::greater, it has a forward iterator through the elements +(which can appear in any order), and its contents can be swapped. +*/ +template , +#if defined(_MSC_VER) + class Compare = std::greater > +#else + class Compare = std::greater > +#endif +class CPriorityQueue { +public: + typedef typename Container::value_type value_type; + typedef typename Container::size_type size_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef Container container_type; + + CPriorityQueue() { } + CPriorityQueue(Container& swappedIn) { swap(swappedIn); } + ~CPriorityQueue() { } + + //! @name manipulators + //@{ + + //! Add element + void push(const value_type& v) + { + c.push_back(v); + std::push_heap(c.begin(), c.end(), comp); + } + + //! Remove head element + void pop() + { + std::pop_heap(c.begin(), c.end(), comp); + c.pop_back(); + } + + //! Erase element + void erase(iterator i) + { + c.erase(i); + std::make_heap(c.begin(), c.end(), comp); + } + + //! Get start iterator + iterator begin() + { + return c.begin(); + } + + //! Get end iterator + iterator end() + { + return c.end(); + } + + //! Swap contents with another priority queue + void swap(CPriorityQueue& q) + { + c.swap(q.c); + } + + //! Swap contents with another container + void swap(Container& c2) + { + c.swap(c2); + std::make_heap(c.begin(), c.end(), comp); + } + + //@} + //! @name accessors + //@{ + + //! Returns true if there are no elements + bool empty() const + { + return c.empty(); + } + + //! Returns the number of elements + size_type size() const + { + return c.size(); + } + + //! Returns the head element + const value_type& top() const + { + return c.front(); + } + + //! Get start iterator + const_iterator begin() const + { + return c.begin(); + } + + //! Get end iterator + const_iterator end() const + { + return c.end(); + } + + //@} + +private: + Container c; + Compare comp; +}; + +#endif diff --git a/lib/base/CSimpleEventQueueBuffer.cpp b/lib/base/CSimpleEventQueueBuffer.cpp new file mode 100644 index 00000000..8f2dbd14 --- /dev/null +++ b/lib/base/CSimpleEventQueueBuffer.cpp @@ -0,0 +1,97 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CSimpleEventQueueBuffer.h" +#include "CStopwatch.h" +#include "CArch.h" + +class CEventQueueTimer { }; + +// +// CSimpleEventQueueBuffer +// + +CSimpleEventQueueBuffer::CSimpleEventQueueBuffer() +{ + m_queueMutex = ARCH->newMutex(); + m_queueReadyCond = ARCH->newCondVar(); + m_queueReady = false; +} + +CSimpleEventQueueBuffer::~CSimpleEventQueueBuffer() +{ + ARCH->closeCondVar(m_queueReadyCond); + ARCH->closeMutex(m_queueMutex); +} + +void +CSimpleEventQueueBuffer::waitForEvent(double timeout) +{ + CArchMutexLock lock(m_queueMutex); + CStopwatch timer(true); + while (!m_queueReady) { + double timeLeft = timeout; + if (timeLeft >= 0.0) { + timeLeft -= timer.getTime(); + if (timeLeft < 0.0) { + return; + } + } + ARCH->waitCondVar(m_queueReadyCond, m_queueMutex, timeLeft); + } +} + +IEventQueueBuffer::Type +CSimpleEventQueueBuffer::getEvent(CEvent&, UInt32& dataID) +{ + CArchMutexLock lock(m_queueMutex); + if (!m_queueReady) { + return kNone; + } + dataID = m_queue.back(); + m_queue.pop_back(); + m_queueReady = !m_queue.empty(); + return kUser; +} + +bool +CSimpleEventQueueBuffer::addEvent(UInt32 dataID) +{ + CArchMutexLock lock(m_queueMutex); + m_queue.push_front(dataID); + if (!m_queueReady) { + m_queueReady = true; + ARCH->broadcastCondVar(m_queueReadyCond); + } + return true; +} + +bool +CSimpleEventQueueBuffer::isEmpty() const +{ + CArchMutexLock lock(m_queueMutex); + return !m_queueReady; +} + +CEventQueueTimer* +CSimpleEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CSimpleEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/base/CSimpleEventQueueBuffer.h b/lib/base/CSimpleEventQueueBuffer.h new file mode 100644 index 00000000..c395fabd --- /dev/null +++ b/lib/base/CSimpleEventQueueBuffer.h @@ -0,0 +1,49 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSIMPLEEVENTQUEUEBUFFER_H +#define CSIMPLEEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include "IArchMultithread.h" +#include "stddeque.h" + +//! In-memory event queue buffer +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class CSimpleEventQueueBuffer : public IEventQueueBuffer { +public: + CSimpleEventQueueBuffer(); + ~CSimpleEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + typedef std::deque CEventDeque; + + CArchMutex m_queueMutex; + CArchCond m_queueReadyCond; + bool m_queueReady; + CEventDeque m_queue; +}; + +#endif diff --git a/lib/base/CStopwatch.cpp b/lib/base/CStopwatch.cpp new file mode 100644 index 00000000..89edce2b --- /dev/null +++ b/lib/base/CStopwatch.cpp @@ -0,0 +1,126 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CStopwatch.h" +#include "CArch.h" + +// +// CStopwatch +// + +CStopwatch::CStopwatch(bool triggered) : + m_mark(0.0), + m_triggered(triggered), + m_stopped(triggered) +{ + if (!triggered) { + m_mark = ARCH->time(); + } +} + +CStopwatch::~CStopwatch() +{ + // do nothing +} + +double +CStopwatch::reset() +{ + if (m_stopped) { + const double dt = m_mark; + m_mark = 0.0; + return dt; + } + else { + const double t = ARCH->time(); + const double dt = t - m_mark; + m_mark = t; + return dt; + } +} + +void +CStopwatch::stop() +{ + if (m_stopped) { + return; + } + + // save the elapsed time + m_mark = ARCH->time() - m_mark; + m_stopped = true; +} + +void +CStopwatch::start() +{ + m_triggered = false; + if (!m_stopped) { + return; + } + + // set the mark such that it reports the time elapsed at stop() + m_mark = ARCH->time() - m_mark; + m_stopped = false; +} + +void +CStopwatch::setTrigger() +{ + stop(); + m_triggered = true; +} + +double +CStopwatch::getTime() +{ + if (m_triggered) { + const double dt = m_mark; + start(); + return dt; + } + else if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +CStopwatch::operator double() +{ + return getTime(); +} + +bool +CStopwatch::isStopped() const +{ + return m_stopped; +} + +double +CStopwatch::getTime() const +{ + if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +CStopwatch::operator double() const +{ + return getTime(); +} diff --git a/lib/base/CStopwatch.h b/lib/base/CStopwatch.h new file mode 100644 index 00000000..e6a34719 --- /dev/null +++ b/lib/base/CStopwatch.h @@ -0,0 +1,108 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTOPWATCH_H +#define CSTOPWATCH_H + +#include "common.h" + +//! A timer class +/*! +This class measures time intervals. All time interval measurement +should use this class. +*/ +class CStopwatch { +public: + /*! + The default constructor does an implicit reset() or setTrigger(). + If triggered == false then the clock starts ticking. + */ + CStopwatch(bool triggered = false); + ~CStopwatch(); + + //! @name manipulators + //@{ + + //! Reset the timer to zero + /*! + Set the start time to the current time, returning the time since + the last reset. This does not remove the trigger if it's set nor + does it start a stopped clock. If the clock is stopped then + subsequent reset()'s will return 0. + */ + double reset(); + + //! Stop the timer + /*! + Stop the stopwatch. The time interval while stopped is not + counted by the stopwatch. stop() does not remove the trigger. + Has no effect if already stopped. + */ + void stop(); + + //! Start the timer + /*! + Start the stopwatch. start() removes the trigger, even if the + stopwatch was already started. + */ + void start(); + + //! Stop the timer and set the trigger + /*! + setTrigger() stops the clock like stop() except there's an + implicit start() the next time (non-const) getTime() is called. + This is useful when you want the clock to start the first time + you check it. + */ + void setTrigger(); + + //! Get elapsed time + /*! + Returns the time since the last reset() (or calls reset() and + returns zero if the trigger is set). + */ + double getTime(); + //! Same as getTime() + operator double(); + //@} + //! @name accessors + //@{ + + //! Check if timer is stopped + /*! + Returns true if the stopwatch is stopped. + */ + bool isStopped() const; + + // return the time since the last reset(). + //! Get elapsed time + /*! + Returns the time since the last reset(). This cannot trigger the + stopwatch to start and will not clear the trigger. + */ + double getTime() const; + //! Same as getTime() const + operator double() const; + //@} + +private: + double getClock() const; + +private: + double m_mark; + bool m_triggered; + bool m_stopped; +}; + +#endif diff --git a/lib/base/CString.h b/lib/base/CString.h new file mode 100644 index 00000000..bc905009 --- /dev/null +++ b/lib/base/CString.h @@ -0,0 +1,25 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTRING_H +#define CSTRING_H + +#include "common.h" +#include "stdstring.h" + +// use standard C++ string class for our string class +typedef std::string CString; + +#endif + diff --git a/lib/base/CStringUtil.cpp b/lib/base/CStringUtil.cpp new file mode 100644 index 00000000..46361932 --- /dev/null +++ b/lib/base/CStringUtil.cpp @@ -0,0 +1,194 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CStringUtil.h" +#include "CArch.h" +#include "common.h" +#include "stdvector.h" +#include +#include +#include +#include + +// +// CStringUtil +// + +CString +CStringUtil::format(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + CString result = vformat(fmt, args); + va_end(args); + return result; +} + +CString +CStringUtil::vformat(const char* fmt, va_list args) +{ + // find highest indexed substitution and the locations of substitutions + std::vector pos; + std::vector width; + std::vector index; + int maxIndex = 0; + for (const char* scan = fmt; *scan != '\0'; ++scan) { + if (*scan == '%') { + ++scan; + if (*scan == '\0') { + break; + } + else if (*scan == '%') { + // literal + index.push_back(0); + pos.push_back(static_cast(scan - 1 - fmt)); + width.push_back(2); + } + else if (*scan == '{') { + // get argument index + char* end; + int i = static_cast(strtol(scan + 1, &end, 10)); + if (*end != '}') { + // invalid index -- ignore + scan = end - 1; + } + else { + index.push_back(i); + pos.push_back(static_cast(scan - 1 - fmt)); + width.push_back(static_cast(end - scan + 2)); + if (i > maxIndex) { + maxIndex = i; + } + scan = end; + } + } + else { + // improper escape -- ignore + } + } + } + + // get args + std::vector value; + std::vector length; + value.push_back("%"); + length.push_back(1); + for (int i = 0; i < maxIndex; ++i) { + const char* arg = va_arg(args, const char*); + size_t len = strlen(arg); + value.push_back(arg); + length.push_back(len); + } + + // compute final length + size_t resultLength = strlen(fmt); + const int n = static_cast(pos.size()); + for (int i = 0; i < n; ++i) { + resultLength -= width[i]; + resultLength += length[index[i]]; + } + + // substitute + CString result; + result.reserve(resultLength); + size_t src = 0; + for (int i = 0; i < n; ++i) { + result.append(fmt + src, pos[i] - src); + result.append(value[index[i]]); + src = pos[i] + width[i]; + } + result.append(fmt + src); + + return result; +} + +CString +CStringUtil::print(const char* fmt, ...) +{ + char tmp[1024]; + char* buffer = tmp; + int len = (int)(sizeof(tmp) / sizeof(tmp[0])); + CString result; + while (buffer != NULL) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > len) { + if (buffer != tmp) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if it was big enough then save the string and don't try again + else { + result = buffer; + if (buffer != tmp) { + delete[] buffer; + } + buffer = NULL; + } + } + + return result; +} + + +// +// CStringUtil::CaselessCmp +// + +bool +CStringUtil::CaselessCmp::cmpEqual( + const CString::value_type& a, + const CString::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) == tolower(b); +} + +bool +CStringUtil::CaselessCmp::cmpLess( + const CString::value_type& a, + const CString::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) < tolower(b); +} + +bool +CStringUtil::CaselessCmp::less(const CString& a, const CString& b) +{ + return std::lexicographical_compare( + a.begin(), a.end(), + b.begin(), b.end(), + &CStringUtil::CaselessCmp::cmpLess); +} + +bool +CStringUtil::CaselessCmp::equal(const CString& a, const CString& b) +{ + return !(less(a, b) || less(b, a)); +} + +bool +CStringUtil::CaselessCmp::operator()(const CString& a, const CString& b) const +{ + return less(a, b); +} diff --git a/lib/base/CStringUtil.h b/lib/base/CStringUtil.h new file mode 100644 index 00000000..8ee86647 --- /dev/null +++ b/lib/base/CStringUtil.h @@ -0,0 +1,77 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTRINGUTIL_H +#define CSTRINGUTIL_H + +#include "CString.h" +#include + +//! String utilities +/*! +This class provides various functions for string manipulation. +*/ +class CStringUtil { +public: + //! Format positional arguments + /*! + Format a string using positional arguments. fmt has literal + characters and conversion specifications introduced by `\%': + - \c\%\% -- literal `\%' + - \c\%{n} -- positional element n, n a positive integer, {} are literal + + All arguments in the variable list are const char*. Positional + elements are indexed from 1. + */ + static CString format(const char* fmt, ...); + + //! Format positional arguments + /*! + Same as format() except takes va_list. + */ + static CString vformat(const char* fmt, va_list); + + //! Print a string using printf-style formatting + /*! + Equivalent to printf() except the result is returned as a CString. + */ + static CString print(const char* fmt, ...); + + //! Case-insensitive comparisons + /*! + This class provides case-insensitve comparison functions. + */ + class CaselessCmp { + public: + //! Same as less() + bool operator()(const CString& a, const CString& b) const; + + //! Returns true iff \c a is lexicographically less than \c b + static bool less(const CString& a, const CString& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool equal(const CString& a, const CString& b); + + //! Returns true iff \c a is lexicographically less than \c b + static bool cmpLess(const CString::value_type& a, + const CString::value_type& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool cmpEqual(const CString::value_type& a, + const CString::value_type& b); + }; +}; + +#endif + diff --git a/lib/base/CUnicode.cpp b/lib/base/CUnicode.cpp new file mode 100644 index 00000000..4dcfcd6f --- /dev/null +++ b/lib/base/CUnicode.cpp @@ -0,0 +1,779 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CUnicode.h" +#include "CArch.h" +#include + +// +// local utility functions +// + +inline +static +UInt16 +decode16(const UInt8* n, bool byteSwapped) +{ + union x16 { + UInt8 n8[2]; + UInt16 n16; + } c; + if (byteSwapped) { + c.n8[0] = n[1]; + c.n8[1] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + } + return c.n16; +} + +inline +static +UInt32 +decode32(const UInt8* n, bool byteSwapped) +{ + union x32 { + UInt8 n8[4]; + UInt32 n32; + } c; + if (byteSwapped) { + c.n8[0] = n[3]; + c.n8[1] = n[2]; + c.n8[2] = n[1]; + c.n8[3] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + c.n8[2] = n[2]; + c.n8[3] = n[3]; + } + return c.n32; +} + +inline +static +void +resetError(bool* errors) +{ + if (errors != NULL) { + *errors = false; + } +} + +inline +static +void +setError(bool* errors) +{ + if (errors != NULL) { + *errors = true; + } +} + + +// +// CUnicode +// + +UInt32 CUnicode::s_invalid = 0x0000ffff; +UInt32 CUnicode::s_replacement = 0x0000fffd; + +bool +CUnicode::isUTF8(const CString& src) +{ + // convert and test each character + const UInt8* data = reinterpret_cast(src.c_str()); + for (UInt32 n = src.size(); n > 0; ) { + if (fromUTF8(data, n) == s_invalid) { + return false; + } + } + return true; +} + +CString +CUnicode::UTF8ToUCS2(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00010000) { + setError(errors); + c = s_replacement; + } + UInt16 ucs2 = static_cast(c); + dst.append(reinterpret_cast(&ucs2), 2); + } + + return dst; +} + +CString +CUnicode::UTF8ToUCS4(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + dst.append(reinterpret_cast(&c), 4); + } + + return dst; +} + +CString +CUnicode::UTF8ToUTF16(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + if (c < 0x00010000) { + UInt16 ucs2 = static_cast(c); + dst.append(reinterpret_cast(&ucs2), 2); + } + else { + c -= 0x00010000; + UInt16 utf16h = static_cast((c >> 10) + 0xd800); + UInt16 utf16l = static_cast((c & 0x03ff) + 0xdc00); + dst.append(reinterpret_cast(&utf16h), 2); + dst.append(reinterpret_cast(&utf16l), 2); + } + } + + return dst; +} + +CString +CUnicode::UTF8ToUTF32(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = src.size(); + CString dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + dst.append(reinterpret_cast(&c), 4); + } + + return dst; +} + +CString +CUnicode::UTF8ToText(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert to wide char + UInt32 size; + wchar_t* tmp = UTF8ToWideChar(src, size, errors); + + // convert string to multibyte + int len = ARCH->convStringWCToMB(NULL, tmp, size, errors); + char* mbs = new char[len + 1]; + ARCH->convStringWCToMB(mbs, tmp, size, errors); + CString text(mbs, len); + + // clean up + delete[] mbs; + delete[] tmp; + + return text; +} + +CString +CUnicode::UCS2ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 1; + return doUCS2ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::UCS4ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 2; + return doUCS4ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::UTF16ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 1; + return doUTF16ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::UTF32ToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = src.size() >> 2; + return doUTF32ToUTF8(reinterpret_cast(src.data()), n, errors); +} + +CString +CUnicode::textToUTF8(const CString& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert string to wide characters + UInt32 n = src.size(); + int len = ARCH->convStringMBToWC(NULL, src.c_str(), n, errors); + wchar_t* wcs = new wchar_t[len + 1]; + ARCH->convStringMBToWC(wcs, src.c_str(), n, errors); + + // convert to UTF8 + CString utf8 = wideCharToUTF8(wcs, len, errors); + + // clean up + delete[] wcs; + + return utf8; +} + +wchar_t* +CUnicode::UTF8ToWideChar(const CString& src, UInt32& size, bool* errors) +{ + // convert to platform's wide character encoding + CString tmp; + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + tmp = UTF8ToUCS2(src, errors); + size = tmp.size() >> 1; + break; + + case IArchString::kUCS4: + tmp = UTF8ToUCS4(src, errors); + size = tmp.size() >> 2; + break; + + case IArchString::kUTF16: + tmp = UTF8ToUTF16(src, errors); + size = tmp.size() >> 1; + break; + + case IArchString::kUTF32: + tmp = UTF8ToUTF32(src, errors); + size = tmp.size() >> 2; + break; + + default: + assert(0 && "unknown wide character encoding"); + } + + // copy to a wchar_t array + wchar_t* dst = new wchar_t[size]; + ::memcpy(dst, tmp.data(), sizeof(wchar_t) * size); + return dst; +} + +CString +CUnicode::wideCharToUTF8(const wchar_t* src, UInt32 size, bool* errors) +{ + // convert from platform's wide character encoding. + // note -- this must include a wide nul character (independent of + // the CString's nul character). + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + return doUCS2ToUTF8(reinterpret_cast(src), size, errors); + + case IArchString::kUCS4: + return doUCS4ToUTF8(reinterpret_cast(src), size, errors); + + case IArchString::kUTF16: + return doUTF16ToUTF8(reinterpret_cast(src), size, errors); + + case IArchString::kUTF32: + return doUTF32ToUTF8(reinterpret_cast(src), size, errors); + + default: + assert(0 && "unknown wide character encoding"); + return CString(); + } +} + +CString +CUnicode::doUCS2ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +CString +CUnicode::doUCS4ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +CString +CUnicode::doUTF16ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + if (c < 0x0000d800 || c > 0x0000dfff) { + toUTF8(dst, c, errors); + } + else if (n == 1) { + // error -- missing second word + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else if (c >= 0x0000d800 && c <= 0x0000dbff) { + UInt32 c2 = decode16(data, byteSwapped); + data += 2; + --n; + if (c2 < 0x0000dc00 || c2 > 0x0000dfff) { + // error -- [d800,dbff] not followed by [dc00,dfff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else { + c = (((c - 0x0000d800) << 10) | (c2 - 0x0000dc00)) + 0x00010000; + toUTF8(dst, c, errors); + } + } + else { + // error -- [dc00,dfff] without leading [d800,dbff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + } + + return dst; +} + +CString +CUnicode::doUTF32ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + CString dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + toUTF8(dst, c, errors); + } + + return dst; +} + +UInt32 +CUnicode::fromUTF8(const UInt8*& data, UInt32& n) +{ + assert(data != NULL); + assert(n != 0); + + // compute character encoding length, checking for overlong + // sequences (i.e. characters that don't use the shortest + // possible encoding). + UInt32 size; + if (data[0] < 0x80) { + // 0xxxxxxx + size = 1; + } + else if (data[0] < 0xc0) { + // 10xxxxxx -- in the middle of a multibyte character. counts + // as one invalid character. + --n; + ++data; + return s_invalid; + } + else if (data[0] < 0xe0) { + // 110xxxxx + size = 2; + } + else if (data[0] < 0xf0) { + // 1110xxxx + size = 3; + } + else if (data[0] < 0xf8) { + // 11110xxx + size = 4; + } + else if (data[0] < 0xfc) { + // 111110xx + size = 5; + } + else if (data[0] < 0xfe) { + // 1111110x + size = 6; + } + else { + // invalid sequence. dunno how many bytes to skip so skip one. + --n; + ++data; + return s_invalid; + } + + // make sure we have enough data + if (size > n) { + data += n; + n = 0; + return s_invalid; + } + + // extract character + UInt32 c; + switch (size) { + case 1: + c = static_cast(data[0]); + break; + + case 2: + c = ((static_cast(data[0]) & 0x1f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + case 3: + c = ((static_cast(data[0]) & 0x0f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[2]) & 0x3f) ); + break; + + case 4: + c = ((static_cast(data[0]) & 0x07) << 18) | + ((static_cast(data[1]) & 0x3f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + case 5: + c = ((static_cast(data[0]) & 0x03) << 24) | + ((static_cast(data[1]) & 0x3f) << 18) | + ((static_cast(data[1]) & 0x3f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + case 6: + c = ((static_cast(data[0]) & 0x01) << 30) | + ((static_cast(data[1]) & 0x3f) << 24) | + ((static_cast(data[1]) & 0x3f) << 18) | + ((static_cast(data[1]) & 0x3f) << 12) | + ((static_cast(data[1]) & 0x3f) << 6) | + ((static_cast(data[1]) & 0x3f) ); + break; + + default: + assert(0 && "invalid size"); + return s_invalid; + } + + // check that all bytes after the first have the pattern 10xxxxxx. + // truncated sequences are treated as a single malformed character. + bool truncated = false; + switch (size) { + case 6: + if ((data[5] & 0xc0) != 0x80) { + truncated = true; + size = 5; + } + // fall through + + case 5: + if ((data[4] & 0xc0) != 0x80) { + truncated = true; + size = 4; + } + // fall through + + case 4: + if ((data[3] & 0xc0) != 0x80) { + truncated = true; + size = 3; + } + // fall through + + case 3: + if ((data[2] & 0xc0) != 0x80) { + truncated = true; + size = 2; + } + // fall through + + case 2: + if ((data[1] & 0xc0) != 0x80) { + truncated = true; + size = 1; + } + } + + // update parameters + data += size; + n -= size; + + // invalid if sequence was truncated + if (truncated) { + return s_invalid; + } + + // check for characters that didn't use the smallest possible encoding + static UInt32 s_minChar[] = { + 0, + 0x00000000, + 0x00000080, + 0x00000800, + 0x00010000, + 0x00200000, + 0x04000000 + }; + if (c < s_minChar[size]) { + return s_invalid; + } + + // check for characters not in ISO-10646 + if (c >= 0x0000d800 && c <= 0x0000dfff) { + return s_invalid; + } + if (c >= 0x0000fffe && c <= 0x0000ffff) { + return s_invalid; + } + + return c; +} + +void +CUnicode::toUTF8(CString& dst, UInt32 c, bool* errors) +{ + UInt8 data[6]; + + // handle characters outside the valid range + if ((c >= 0x0000d800 && c <= 0x0000dfff) || c >= 0x80000000) { + setError(errors); + c = s_replacement; + } + + // convert to UTF-8 + if (c < 0x00000080) { + data[0] = static_cast(c); + dst.append(reinterpret_cast(data), 1); + } + else if (c < 0x00000800) { + data[0] = static_cast(((c >> 6) & 0x0000001f) + 0xc0); + data[1] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 2); + } + else if (c < 0x00010000) { + data[0] = static_cast(((c >> 12) & 0x0000000f) + 0xe0); + data[1] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[2] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 3); + } + else if (c < 0x00200000) { + data[0] = static_cast(((c >> 18) & 0x00000007) + 0xf0); + data[1] = static_cast(((c >> 12) & 0x0000003f) + 0x80); + data[2] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[3] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 4); + } + else if (c < 0x04000000) { + data[0] = static_cast(((c >> 24) & 0x00000003) + 0xf8); + data[1] = static_cast(((c >> 18) & 0x0000003f) + 0x80); + data[2] = static_cast(((c >> 12) & 0x0000003f) + 0x80); + data[3] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[4] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 5); + } + else if (c < 0x80000000) { + data[0] = static_cast(((c >> 30) & 0x00000001) + 0xfc); + data[1] = static_cast(((c >> 24) & 0x0000003f) + 0x80); + data[2] = static_cast(((c >> 18) & 0x0000003f) + 0x80); + data[3] = static_cast(((c >> 12) & 0x0000003f) + 0x80); + data[4] = static_cast(((c >> 6) & 0x0000003f) + 0x80); + data[5] = static_cast((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast(data), 6); + } + else { + assert(0 && "character out of range"); + } +} diff --git a/lib/base/CUnicode.h b/lib/base/CUnicode.h new file mode 100644 index 00000000..85afbfd9 --- /dev/null +++ b/lib/base/CUnicode.h @@ -0,0 +1,143 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CUNICODE_H +#define CUNICODE_H + +#include "CString.h" +#include "BasicTypes.h" + +//! Unicode utility functions +/*! +This class provides functions for converting between various Unicode +encodings and the current locale encoding. +*/ +class CUnicode { +public: + //! @name accessors + //@{ + + //! Test UTF-8 string for validity + /*! + Returns true iff the string contains a valid sequence of UTF-8 + encoded characters. + */ + static bool isUTF8(const CString&); + + //! Convert from UTF-8 to UCS-2 encoding + /*! + Convert from UTF-8 to UCS-2. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-2. + Decoding errors do not set *errors. + */ + static CString UTF8ToUCS2(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to UCS-4 encoding + /*! + Convert from UTF-8 to UCS-4. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-4. + Decoding errors do not set *errors. + */ + static CString UTF8ToUCS4(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-16 encoding + /*! + Convert from UTF-8 to UTF-16. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-16. + Decoding errors do not set *errors. + */ + static CString UTF8ToUTF16(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-32 encoding + /*! + Convert from UTF-8 to UTF-32. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-32. + Decoding errors do not set *errors. + */ + static CString UTF8ToUTF32(const CString&, bool* errors = NULL); + + //! Convert from UTF-8 to the current locale encoding + /*! + Convert from UTF-8 to the current locale encoding. If errors is not + NULL then *errors is set to true iff any character could not be encoded. + Decoding errors do not set *errors. + */ + static CString UTF8ToText(const CString&, bool* errors = NULL); + + //! Convert from UCS-2 to UTF-8 + /*! + Convert from UCS-2 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UCS2ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from UCS-4 to UTF-8 + /*! + Convert from UCS-4 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UCS4ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from UTF-16 to UTF-8 + /*! + Convert from UTF-16 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UTF16ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from UTF-32 to UTF-8 + /*! + Convert from UTF-32 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static CString UTF32ToUTF8(const CString&, bool* errors = NULL); + + //! Convert from the current locale encoding to UTF-8 + /*! + Convert from the current locale encoding to UTF-8. If errors is not + NULL then *errors is set to true iff any character could not be decoded. + */ + static CString textToUTF8(const CString&, bool* errors = NULL); + + //@} + +private: + // convert UTF8 to wchar_t string (using whatever encoding is native + // to the platform). caller must delete[] the returned string. the + // string is *not* nul terminated; the length (in characters) is + // returned in size. + static wchar_t* UTF8ToWideChar(const CString&, + UInt32& size, bool* errors); + + // convert nul terminated wchar_t string (in platform's native + // encoding) to UTF8. + static CString wideCharToUTF8(const wchar_t*, + UInt32 size, bool* errors); + + // internal conversion to UTF8 + static CString doUCS2ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static CString doUCS4ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static CString doUTF16ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static CString doUTF32ToUTF8(const UInt8* src, UInt32 n, bool* errors); + + // convert characters to/from UTF8 + static UInt32 fromUTF8(const UInt8*& src, UInt32& size); + static void toUTF8(CString& dst, UInt32 c, bool* errors); + +private: + static UInt32 s_invalid; + static UInt32 s_replacement; +}; + +#endif diff --git a/lib/base/IEventJob.h b/lib/base/IEventJob.h new file mode 100644 index 00000000..01ef9a96 --- /dev/null +++ b/lib/base/IEventJob.h @@ -0,0 +1,32 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IEVENTJOB_H +#define IEVENTJOB_H + +#include "IInterface.h" + +class CEvent; + +//! Event handler interface +/*! +An event job is an interface for executing a event handler. +*/ +class IEventJob : public IInterface { +public: + //! Run the job + virtual void run(const CEvent&) = 0; +}; + +#endif diff --git a/lib/base/IEventQueue.cpp b/lib/base/IEventQueue.cpp new file mode 100644 index 00000000..a10621f2 --- /dev/null +++ b/lib/base/IEventQueue.cpp @@ -0,0 +1,43 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IEventQueue.h" + +// +// IEventQueue +// + +static int g_systemTarget = 0; +IEventQueue* IEventQueue::s_instance = NULL; + +void* +IEventQueue::getSystemTarget() +{ + // any unique arbitrary pointer will do + return &g_systemTarget; +} + +IEventQueue* +IEventQueue::getInstance() +{ + assert(s_instance != NULL); + return s_instance; +} + +void +IEventQueue::setInstance(IEventQueue* instance) +{ + assert(s_instance == NULL || instance == NULL); + s_instance = instance; +} diff --git a/lib/base/IEventQueue.h b/lib/base/IEventQueue.h new file mode 100644 index 00000000..6f48f25c --- /dev/null +++ b/lib/base/IEventQueue.h @@ -0,0 +1,213 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IEVENTQUEUE_H +#define IEVENTQUEUE_H + +#include "IInterface.h" +#include "CEvent.h" + +#define EVENTQUEUE IEventQueue::getInstance() + +class IEventJob; +class IEventQueueBuffer; + +// Opaque type for timer info. This is defined by subclasses of +// IEventQueueBuffer. +class CEventQueueTimer; + +//! Event queue interface +/*! +An event queue provides a queue of CEvents. Clients can block waiting +on any event becoming available at the head of the queue and can place +new events at the end of the queue. Clients can also add and remove +timers which generate events periodically. +*/ +class IEventQueue : public IInterface { +public: + class CTimerEvent { + public: + CEventQueueTimer* m_timer; //!< The timer + UInt32 m_count; //!< Number of repeats + }; + + //! @name manipulators + //@{ + + //! Set the buffer + /*! + Replace the current event queue buffer. Any queued events are + discarded. The queue takes ownership of the buffer. + */ + virtual void adoptBuffer(IEventQueueBuffer*) = 0; + + //! Remove event from queue + /*! + Returns the next event on the queue into \p event. If no event is + available then blocks for up to \p timeout seconds, or forever if + \p timeout is negative. Returns true iff an event was available. + */ + virtual bool getEvent(CEvent& event, double timeout = -1.0) = 0; + + //! Dispatch an event + /*! + Looks up the dispatcher for the event's target and invokes it. + Returns true iff a dispatcher exists for the target. + */ + virtual bool dispatchEvent(const CEvent& event) = 0; + + //! Add event to queue + /*! + Adds \p event to the end of the queue. + */ + virtual void addEvent(const CEvent& event) = 0; + + //! Create a recurring timer + /*! + Creates and returns a timer. An event is returned after \p duration + seconds and the timer is reset to countdown again. When a timer event + is returned the data points to a \c CTimerEvent. The client must pass + the returned timer to \c deleteTimer() (whether or not the timer has + expired) to release the timer. The returned timer event uses the + given \p target. If \p target is NULL it uses the returned timer as + the target. + + Events for a single timer don't accumulate in the queue, even if the + client reading events can't keep up. Instead, the \c m_count member + of the \c CTimerEvent indicates how many events for the timer would + have been put on the queue since the last event for the timer was + removed (or since the timer was added). + */ + virtual CEventQueueTimer* + newTimer(double duration, void* target) = 0; + + //! Create a one-shot timer + /*! + Creates and returns a one-shot timer. An event is returned when + the timer expires and the timer is removed from further handling. + When a timer event is returned the data points to a \c CTimerEvent. + The \m c_count member of the \c CTimerEvent is always 1. The client + must pass the returned timer to \c deleteTimer() (whether or not the + timer has expired) to release the timer. The returned timer event + uses the given \p target. If \p target is NULL it uses the returned + timer as the target. + */ + virtual CEventQueueTimer* + newOneShotTimer(double duration, + void* target) = 0; + + //! Destroy a timer + /*! + Destroys a previously created timer. The timer is removed from the + queue and will not generate event, even if the timer has expired. + */ + virtual void deleteTimer(CEventQueueTimer*) = 0; + + //! Register an event handler for an event type + /*! + Registers an event handler for \p type and \p target. The \p handler + is adopted. Any existing handler for the type,target pair is deleted. + \c dispatchEvent() will invoke \p handler for any event for \p target + of type \p type. If no such handler exists it will use the handler + for \p target and type \p kUnknown if it exists. + */ + virtual void adoptHandler(CEvent::Type type, + void* target, IEventJob* handler) = 0; + + //! Unregister an event handler for an event type + /*! + Unregisters an event handler for the \p type, \p target pair and + deletes it. + */ + virtual void removeHandler(CEvent::Type type, void* target) = 0; + + //! Unregister all event handlers for an event target + /*! + Unregisters all event handlers for the \p target and deletes them. + */ + virtual void removeHandlers(void* target) = 0; + + //! Creates a new event type + /*! + Returns a unique event type id. + */ + virtual CEvent::Type + registerType(const char* name) = 0; + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + virtual CEvent::Type + registerTypeOnce(CEvent::Type& type, + const char* name) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if queue is empty + /*! + Returns true iff the queue has no events in it, including timer + events. + */ + virtual bool isEmpty() const = 0; + + //! Get an event handler + /*! + Finds and returns the event handler for the \p type, \p target pair + if it exists, otherwise it returns NULL. + */ + virtual IEventJob* getHandler(CEvent::Type type, void* target) const = 0; + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + virtual const char* getTypeName(CEvent::Type type) = 0; + + //! Get the system event type target + /*! + Returns the target to use for dispatching \c CEvent::kSystem events. + */ + static void* getSystemTarget(); + + //! Get the singleton instance + /*! + Returns the singleton instance of the event queue + */ + static IEventQueue* getInstance(); + + //@} + +protected: + //! @name manipulators + //@{ + + //! Set the singleton instance + /*! + Sets the singleton instance of the event queue + */ + static void setInstance(IEventQueue*); + + //@} + +private: + static IEventQueue* s_instance; +}; + +#endif diff --git a/lib/base/IEventQueueBuffer.h b/lib/base/IEventQueueBuffer.h new file mode 100644 index 00000000..1aff51a6 --- /dev/null +++ b/lib/base/IEventQueueBuffer.h @@ -0,0 +1,94 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IEVENTQUEUEBUFFER_H +#define IEVENTQUEUEBUFFER_H + +#include "IInterface.h" +#include "BasicTypes.h" + +class CEvent; +class CEventQueueTimer; + +//! Event queue buffer interface +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class IEventQueueBuffer : public IInterface { +public: + enum Type { + kNone, //!< No event is available + kSystem, //!< Event is a system event + kUser //!< Event is a user event + }; + + //! @name manipulators + //@{ + + //! Block waiting for an event + /*! + Wait for an event in the event queue buffer for up to \p timeout + seconds. + */ + virtual void waitForEvent(double timeout) = 0; + + //! Get the next event + /*! + Get the next event from the buffer. Return kNone if no event is + available. If a system event is next, return kSystem and fill in + event. The event data in a system event can point to a static + buffer (because CEvent::deleteData() will not attempt to delete + data in a kSystem event). Otherwise, return kUser and fill in + \p dataID with the value passed to \c addEvent(). + */ + virtual Type getEvent(CEvent& event, UInt32& dataID) = 0; + + //! Post an event + /*! + Add the given event to the end of the queue buffer. This is a user + event and \c getEvent() must be able to identify it as such and + return \p dataID. This method must cause \c waitForEvent() to + return at some future time if it's blocked waiting on an event. + */ + virtual bool addEvent(UInt32 dataID) = 0; + + //@} + //! @name accessors + //@{ + + //! Check if event queue buffer is empty + /*! + Return true iff the event queue buffer is empty. + */ + virtual bool isEmpty() const = 0; + + //! Create a timer object + /*! + Create and return a timer object. The object is opaque and is + used only by the buffer but it must be a valid object (i.e. + not NULL). + */ + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const = 0; + + //! Destroy a timer object + /*! + Destroy a timer object previously returned by \c newTimer(). + */ + virtual void deleteTimer(CEventQueueTimer*) const = 0; + + //@} +}; + +#endif diff --git a/lib/base/IJob.h b/lib/base/IJob.h new file mode 100644 index 00000000..30ef5f5a --- /dev/null +++ b/lib/base/IJob.h @@ -0,0 +1,30 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IJOB_H +#define IJOB_H + +#include "IInterface.h" + +//! Job interface +/*! +A job is an interface for executing some function. +*/ +class IJob : public IInterface { +public: + //! Run the job + virtual void run() = 0; +}; + +#endif diff --git a/lib/base/ILogOutputter.h b/lib/base/ILogOutputter.h new file mode 100644 index 00000000..2be4dcc9 --- /dev/null +++ b/lib/base/ILogOutputter.h @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ILOGOUTPUTTER_H +#define ILOGOUTPUTTER_H + +#include "IInterface.h" +#include "CLog.h" + +//! Outputter interface +/*! +Type of outputter interface. The logger performs all output through +outputters. ILogOutputter overrides must not call any log functions +directly or indirectly. +*/ +class ILogOutputter : public IInterface { +public: + typedef CLog::ELevel ELevel; + + //! @name manipulators + //@{ + + //! Open the outputter + /*! + Opens the outputter for writing. Calling this method on an + already open outputter must have no effect. + */ + virtual void open(const char* title) = 0; + + //! Close the outputter + /*! + Close the outputter. Calling this method on an already closed + outputter must have no effect. + */ + virtual void close() = 0; + + //! Show the outputter + /*! + Causes the output to become visible. This generally only makes sense + for a logger in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the log if it's not empty. + */ + virtual void show(bool showIfEmpty) = 0; + + //! Write a message with level + /*! + Writes \c message, which has the given \c level, to a log. + If this method returns true then CLog will stop passing the + message to all outputters in the outputter chain, otherwise + it continues. Most implementations should return true. + */ + virtual bool write(ELevel level, const char* message) = 0; + + //@} + //! @name accessors + //@{ + + //! Returns the newline sequence for the outputter + /*! + Different outputters use different character sequences for newlines. + This method returns the appropriate newline sequence for this + outputter. + */ + virtual const char* getNewline() const = 0; + + //@} +}; + +#endif diff --git a/lib/base/LogOutputters.cpp b/lib/base/LogOutputters.cpp new file mode 100644 index 00000000..f556d53f --- /dev/null +++ b/lib/base/LogOutputters.cpp @@ -0,0 +1,267 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "LogOutputters.h" +#include "CArch.h" + +// +// CStopLogOutputter +// + +CStopLogOutputter::CStopLogOutputter() +{ + // do nothing +} + +CStopLogOutputter::~CStopLogOutputter() +{ + // do nothing +} + +void +CStopLogOutputter::open(const char*) +{ + // do nothing +} + +void +CStopLogOutputter::close() +{ + // do nothing +} + +void +CStopLogOutputter::show(bool) +{ + // do nothing +} + +bool +CStopLogOutputter::write(ELevel, const char*) +{ + return false; +} + +const char* +CStopLogOutputter::getNewline() const +{ + return ""; +} + + +// +// CConsoleLogOutputter +// + +CConsoleLogOutputter::CConsoleLogOutputter() +{ + // do nothing +} + +CConsoleLogOutputter::~CConsoleLogOutputter() +{ + // do nothing +} + +void +CConsoleLogOutputter::open(const char* title) +{ + ARCH->openConsole(title); +} + +void +CConsoleLogOutputter::close() +{ + ARCH->closeConsole(); +} + +void +CConsoleLogOutputter::show(bool showIfEmpty) +{ + ARCH->showConsole(showIfEmpty); +} + +bool +CConsoleLogOutputter::write(ELevel, const char* msg) +{ + ARCH->writeConsole(msg); + return true; +} + +const char* +CConsoleLogOutputter::getNewline() const +{ + return ARCH->getNewlineForConsole(); +} + + +// +// CSystemLogOutputter +// + +CSystemLogOutputter::CSystemLogOutputter() +{ + // do nothing +} + +CSystemLogOutputter::~CSystemLogOutputter() +{ + // do nothing +} + +void +CSystemLogOutputter::open(const char* title) +{ + ARCH->openLog(title); +} + +void +CSystemLogOutputter::close() +{ + ARCH->closeLog(); +} + +void +CSystemLogOutputter::show(bool showIfEmpty) +{ + ARCH->showLog(showIfEmpty); +} + +bool +CSystemLogOutputter::write(ELevel level, const char* msg) +{ + IArchLog::ELevel archLogLevel; + switch (level) { + case CLog::kFATAL: + case CLog::kERROR: + archLogLevel = IArchLog::kERROR; + break; + + case CLog::kWARNING: + archLogLevel = IArchLog::kWARNING; + break; + + case CLog::kNOTE: + archLogLevel = IArchLog::kNOTE; + break; + + case CLog::kINFO: + archLogLevel = IArchLog::kINFO; + break; + + default: + archLogLevel = IArchLog::kDEBUG; + break; + + }; + ARCH->writeLog(archLogLevel, msg); + return true; +} + +const char* +CSystemLogOutputter::getNewline() const +{ + return ""; +} + + +// +// CSystemLogger +// + +CSystemLogger::CSystemLogger(const char* title, bool blockConsole) : + m_stop(NULL) +{ + // redirect log messages + if (blockConsole) { + m_stop = new CStopLogOutputter; + CLOG->insert(m_stop); + } + m_syslog = new CSystemLogOutputter; + m_syslog->open(title); + CLOG->insert(m_syslog); +} + +CSystemLogger::~CSystemLogger() +{ + CLOG->remove(m_syslog); + delete m_syslog; + if (m_stop != NULL) { + CLOG->remove(m_stop); + delete m_stop; + } +} + + +// +// CBufferedLogOutputter +// + +CBufferedLogOutputter::CBufferedLogOutputter(UInt32 maxBufferSize) : + m_maxBufferSize(maxBufferSize) +{ + // do nothing +} + +CBufferedLogOutputter::~CBufferedLogOutputter() +{ + // do nothing +} + +CBufferedLogOutputter::const_iterator +CBufferedLogOutputter::begin() const +{ + return m_buffer.begin(); +} + +CBufferedLogOutputter::const_iterator +CBufferedLogOutputter::end() const +{ + return m_buffer.end(); +} + +void +CBufferedLogOutputter::open(const char*) +{ + // do nothing +} + +void +CBufferedLogOutputter::close() +{ + // remove all elements from the buffer + m_buffer.clear(); +} + +void +CBufferedLogOutputter::show(bool) +{ + // do nothing +} + +bool +CBufferedLogOutputter::write(ELevel, const char* message) +{ + while (m_buffer.size() >= m_maxBufferSize) { + m_buffer.pop_front(); + } + m_buffer.push_back(CString(message)); + return true; +} + +const char* +CBufferedLogOutputter::getNewline() const +{ + return ""; +} diff --git a/lib/base/LogOutputters.h b/lib/base/LogOutputters.h new file mode 100644 index 00000000..a8087593 --- /dev/null +++ b/lib/base/LogOutputters.h @@ -0,0 +1,132 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LOGOUTPUTTERS_H +#define LOGOUTPUTTERS_H + +#include "BasicTypes.h" +#include "ILogOutputter.h" +#include "CString.h" +#include "stddeque.h" + +//! Stop traversing log chain outputter +/*! +This outputter performs no output and returns false from \c write(), +causing the logger to stop traversing the outputter chain. Insert +this to prevent already inserted outputters from writing. +*/ +class CStopLogOutputter : public ILogOutputter { +public: + CStopLogOutputter(); + virtual ~CStopLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const; +}; + +//! Write log to console +/*! +This outputter writes output to the console. The level for each +message is ignored. +*/ +class CConsoleLogOutputter : public ILogOutputter { +public: + CConsoleLogOutputter(); + virtual ~CConsoleLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const; +}; + +//! Write log to system log +/*! +This outputter writes output to the system log. +*/ +class CSystemLogOutputter : public ILogOutputter { +public: + CSystemLogOutputter(); + virtual ~CSystemLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const; +}; + +//! Write log to system log only +/*! +Creating an object of this type inserts a CStopLogOutputter followed +by a CSystemLogOutputter into CLog. The destructor removes those +outputters. Add one of these to any scope that needs to write to +the system log (only) and restore the old outputters when exiting +the scope. +*/ +class CSystemLogger { +public: + CSystemLogger(const char* title, bool blockConsole); + ~CSystemLogger(); + +private: + ILogOutputter* m_syslog; + ILogOutputter* m_stop; +}; + +//! Save log history +/*! +This outputter records the last N log messages. +*/ +class CBufferedLogOutputter : public ILogOutputter { +private: + typedef std::deque CBuffer; + +public: + typedef CBuffer::const_iterator const_iterator; + + CBufferedLogOutputter(UInt32 maxBufferSize); + virtual ~CBufferedLogOutputter(); + + //! @name accessors + //@{ + + //! Get start of buffer + const_iterator begin() const; + + //! Get end of buffer + const_iterator end() const; + + //@} + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual const char* getNewline() const; + +private: + UInt32 m_maxBufferSize; + CBuffer m_buffer; +}; + +#endif diff --git a/lib/base/Makefile.am b/lib/base/Makefile.am new file mode 100644 index 00000000..a53c8b33 --- /dev/null +++ b/lib/base/Makefile.am @@ -0,0 +1,62 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libbase.a +libbase_a_SOURCES = \ + CEvent.cpp \ + CEventQueue.cpp \ + CFunctionEventJob.cpp \ + CFunctionJob.cpp \ + CLog.cpp \ + CSimpleEventQueueBuffer.cpp \ + CStopwatch.cpp \ + CStringUtil.cpp \ + CUnicode.cpp \ + IEventQueue.cpp \ + LogOutputters.cpp \ + XBase.cpp \ + CEvent.h \ + CEventQueue.h \ + CFunctionEventJob.h \ + CFunctionJob.h \ + CLog.h \ + CPriorityQueue.h \ + CSimpleEventQueueBuffer.h \ + CStopwatch.h \ + CString.h \ + CStringUtil.h \ + CUnicode.h \ + IEventJob.h \ + IEventQueue.h \ + IEventQueueBuffer.h \ + IJob.h \ + ILogOutputter.h \ + LogOutputters.h \ + TMethodEventJob.h \ + TMethodJob.h \ + XBase.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + $(NULL) diff --git a/lib/base/Makefile.win b/lib/base/Makefile.win new file mode 100644 index 00000000..a386da17 --- /dev/null +++ b/lib/base/Makefile.win @@ -0,0 +1,77 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_BASE_SRC = lib\base +LIB_BASE_DST = $(BUILD_DST)\$(LIB_BASE_SRC) +LIB_BASE_LIB = "$(LIB_BASE_DST)\base.lib" +LIB_BASE_CPP = \ + "CEvent.cpp" \ + "CEventQueue.cpp" \ + "CFunctionEventJob.cpp" \ + "CFunctionJob.cpp" \ + "CLog.cpp" \ + "CSimpleEventQueueBuffer.cpp" \ + "CStopwatch.cpp" \ + "CStringUtil.cpp" \ + "CUnicode.cpp" \ + "IEventQueue.cpp" \ + "LogOutputters.cpp" \ + "XBase.cpp" \ + $(NULL) +LIB_BASE_OBJ = \ + "$(LIB_BASE_DST)\CEvent.obj" \ + "$(LIB_BASE_DST)\CEventQueue.obj" \ + "$(LIB_BASE_DST)\CFunctionEventJob.obj" \ + "$(LIB_BASE_DST)\CFunctionJob.obj" \ + "$(LIB_BASE_DST)\CLog.obj" \ + "$(LIB_BASE_DST)\CSimpleEventQueueBuffer.obj" \ + "$(LIB_BASE_DST)\CStopwatch.obj" \ + "$(LIB_BASE_DST)\CStringUtil.obj" \ + "$(LIB_BASE_DST)\CUnicode.obj" \ + "$(LIB_BASE_DST)\IEventQueue.obj" \ + "$(LIB_BASE_DST)\LogOutputters.obj" \ + "$(LIB_BASE_DST)\XBase.obj" \ + $(NULL) +LIB_BASE_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_BASE_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_BASE_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_BASE_LIB) + +# Dependency rules +$(LIB_BASE_OBJ): $(AUTODEP) +!if EXIST($(LIB_BASE_DST)\deps.mak) +!include $(LIB_BASE_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_BASE_SRC)\}.cpp{$(LIB_BASE_DST)\}.obj:: +!else +{$(LIB_BASE_SRC)\}.cpp{$(LIB_BASE_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_BASE_SRC) + -@$(MKDIR) $(LIB_BASE_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_BASE_INC) \ + /Fo$(LIB_BASE_DST)\ \ + /Fd$(LIB_BASE_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_BASE_SRC) $(LIB_BASE_DST) +$(LIB_BASE_LIB): $(LIB_BASE_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_BASE_SRC) $(LIB_BASE_DST) $(**:.obj=.d) diff --git a/lib/base/TMethodEventJob.h b/lib/base/TMethodEventJob.h new file mode 100644 index 00000000..15826be0 --- /dev/null +++ b/lib/base/TMethodEventJob.h @@ -0,0 +1,70 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMETHODEVENTJOB_H +#define CMETHODEVENTJOB_H + +#include "IEventJob.h" + +//! Use a member function as an event job +/*! +An event job class that invokes a member function. +*/ +template +class TMethodEventJob : public IEventJob { +public: + //! run(event) invokes \c object->method(event, arg) + TMethodEventJob(T* object, + void (T::*method)(const CEvent&, void*), + void* arg = NULL); + virtual ~TMethodEventJob(); + + // IJob overrides + virtual void run(const CEvent&); + +private: + T* m_object; + void (T::*m_method)(const CEvent&, void*); + void* m_arg; +}; + +template +inline +TMethodEventJob::TMethodEventJob(T* object, + void (T::*method)(const CEvent&, void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template +inline +TMethodEventJob::~TMethodEventJob() +{ + // do nothing +} + +template +inline +void +TMethodEventJob::run(const CEvent& event) +{ + if (m_object != NULL) { + (m_object->*m_method)(event, m_arg); + } +} + +#endif diff --git a/lib/base/TMethodJob.h b/lib/base/TMethodJob.h new file mode 100644 index 00000000..54cd936b --- /dev/null +++ b/lib/base/TMethodJob.h @@ -0,0 +1,67 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMETHODJOB_H +#define CMETHODJOB_H + +#include "IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a member function. +*/ +template +class TMethodJob : public IJob { +public: + //! run() invokes \c object->method(arg) + TMethodJob(T* object, void (T::*method)(void*), void* arg = NULL); + virtual ~TMethodJob(); + + // IJob overrides + virtual void run(); + +private: + T* m_object; + void (T::*m_method)(void*); + void* m_arg; +}; + +template +inline +TMethodJob::TMethodJob(T* object, void (T::*method)(void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template +inline +TMethodJob::~TMethodJob() +{ + // do nothing +} + +template +inline +void +TMethodJob::run() +{ + if (m_object != NULL) { + (m_object->*m_method)(m_arg); + } +} + +#endif diff --git a/lib/base/XBase.cpp b/lib/base/XBase.cpp new file mode 100644 index 00000000..022be306 --- /dev/null +++ b/lib/base/XBase.cpp @@ -0,0 +1,69 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XBase.h" +#include "CStringUtil.h" +#include +#include + +// +// XBase +// + +XBase::XBase() : + m_what() +{ + // do nothing +} + +XBase::XBase(const CString& msg) : + m_what(msg) +{ + // do nothing +} + +XBase::~XBase() +{ + // do nothing +} + +const char* +XBase::what() const +{ + if (m_what.empty()) { + m_what = getWhat(); + } + return m_what.c_str(); +} + +CString +XBase::format(const char* /*id*/, const char* fmt, ...) const throw() +{ + // FIXME -- lookup message string using id as an index. set + // fmt to that string if it exists. + + // format + CString result; + va_list args; + va_start(args, fmt); + try { + result = CStringUtil::vformat(fmt, args); + } + catch (...) { + // ignore + } + va_end(args); + + return result; +} diff --git a/lib/base/XBase.h b/lib/base/XBase.h new file mode 100644 index 00000000..e9161d70 --- /dev/null +++ b/lib/base/XBase.h @@ -0,0 +1,121 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XBASE_H +#define XBASE_H + +#include "CString.h" + +//! Exception base class +/*! +This is the base class of most exception types. +*/ +class XBase { +public: + //! Use getWhat() as the result of what() + XBase(); + //! Use \c msg as the result of what() + XBase(const CString& msg); + virtual ~XBase(); + + //! Reason for exception + virtual const char* what() const; + +protected: + //! Get a human readable string describing the exception + virtual CString getWhat() const throw() = 0; + + //! Format a string + /*! + Looks up a message format using \c id, using \c defaultFormat if + no format can be found, then replaces positional parameters in + the format string and returns the result. + */ + virtual CString format(const char* id, + const char* defaultFormat, ...) const throw(); + +private: + mutable CString m_what; +}; + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const CString&. getWhat() is not +declared. +*/ +#define XBASE_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const CString& msg) : super_(msg) { } \ +} + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const CString&. getWhat() must be +implemented. +*/ +#define XBASE_SUBCLASS_WHAT(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const CString& msg) : super_(msg) { } \ + \ +protected: \ + virtual CString getWhat() const throw(); \ +} + +/*! +\def XBASE_SUBCLASS_FORMAT +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const CString&. what() is overridden +to call getWhat() when first called; getWhat() can format the +error message and can call what() to get the message passed to the +c'tor. +*/ +#define XBASE_SUBCLASS_FORMAT(name_, super_) \ +class name_ : public super_ { \ +private: \ + enum EState { kFirst, kFormat, kDone }; \ + \ +public: \ + name_() : super_(), m_state(kDone) { } \ + name_(const CString& msg) : super_(msg), m_state(kFirst) { } \ + \ + virtual const char* what() const \ + { \ + if (m_state == kFirst) { \ + m_state = kFormat; \ + m_formatted = getWhat(); \ + m_state = kDone; \ + } \ + if (m_state == kDone) { \ + return m_formatted.c_str(); \ + } \ + else { \ + return super_::what(); \ + } \ + } \ + \ +protected: \ + virtual CString getWhat() const throw(); \ + \ +private: \ + mutable EState m_state; \ + mutable std::string m_formatted; \ +} + +#endif diff --git a/lib/client/CClient.cpp b/lib/client/CClient.cpp new file mode 100644 index 00000000..1e450a17 --- /dev/null +++ b/lib/client/CClient.cpp @@ -0,0 +1,662 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClient.h" +#include "CServerProxy.h" +#include "CScreen.h" +#include "CClipboard.h" +#include "CPacketStreamFilter.h" +#include "CProtocolUtil.h" +#include "ProtocolTypes.h" +#include "XSynergy.h" +#include "IDataSocket.h" +#include "ISocketFactory.h" +#include "IStreamFilterFactory.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClient +// + +CEvent::Type CClient::s_connectedEvent = CEvent::kUnknown; +CEvent::Type CClient::s_connectionFailedEvent = CEvent::kUnknown; +CEvent::Type CClient::s_disconnectedEvent = CEvent::kUnknown; + +CClient::CClient(const CString& name, const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory, + CScreen* screen) : + m_name(name), + m_serverAddress(address), + m_socketFactory(socketFactory), + m_streamFilterFactory(streamFilterFactory), + m_screen(screen), + m_stream(NULL), + m_timer(NULL), + m_server(NULL), + m_ready(false), + m_active(false), + m_suspended(false), + m_connectOnResume(false) +{ + assert(m_socketFactory != NULL); + assert(m_screen != NULL); + + // register suspend/resume event handlers + EVENTQUEUE->adoptHandler(IScreen::getSuspendEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleSuspend)); + EVENTQUEUE->adoptHandler(IScreen::getResumeEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleResume)); +} + +CClient::~CClient() +{ + EVENTQUEUE->removeHandler(IScreen::getSuspendEvent(), + getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getResumeEvent(), + getEventTarget()); + + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + delete m_socketFactory; + delete m_streamFilterFactory; +} + +void +CClient::connect() +{ + if (m_stream != NULL) { + return; + } + if (m_suspended) { + m_connectOnResume = true; + return; + } + + try { + // resolve the server hostname. do this every time we connect + // in case we couldn't resolve the address earlier or the address + // has changed (which can happen frequently if this is a laptop + // being shuttled between various networks). patch by Brent + // Priddy. + m_serverAddress.resolve(); + + // create the socket + IDataSocket* socket = m_socketFactory->create(); + + // filter socket messages, including a packetizing filter + m_stream = socket; + if (m_streamFilterFactory != NULL) { + m_stream = m_streamFilterFactory->create(m_stream, true); + } + m_stream = new CPacketStreamFilter(m_stream, true); + + // connect + LOG((CLOG_DEBUG1 "connecting to server")); + setupConnecting(); + setupTimer(); + socket->connect(m_serverAddress); + } + catch (XBase& e) { + cleanupTimer(); + cleanupConnecting(); + delete m_stream; + m_stream = NULL; + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(e.what()); + return; + } +} + +void +CClient::disconnect(const char* msg) +{ + m_connectOnResume = false; + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + if (msg != NULL) { + sendConnectionFailedEvent(msg); + } + else { + sendEvent(getDisconnectedEvent(), NULL); + } +} + +void +CClient::handshakeComplete() +{ + m_ready = true; + m_screen->enable(); + sendEvent(getConnectedEvent(), NULL); +} + +bool +CClient::isConnected() const +{ + return (m_server != NULL); +} + +bool +CClient::isConnecting() const +{ + return (m_timer != NULL); +} + +CNetworkAddress +CClient::getServerAddress() const +{ + return m_serverAddress; +} + +CEvent::Type +CClient::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CClient::connected"); +} + +CEvent::Type +CClient::getConnectionFailedEvent() +{ + return CEvent::registerTypeOnce(s_connectionFailedEvent, + "CClient::failed"); +} + +CEvent::Type +CClient::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CClient::disconnected"); +} + +void* +CClient::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +CClient::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +CClient::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +CClient::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +CClient::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool) +{ + m_active = true; + m_screen->mouseMove(xAbs, yAbs); + m_screen->enter(mask); +} + +bool +CClient::leave() +{ + m_screen->leave(); + + m_active = false; + + // send clipboards that we own and that have changed + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_ownClipboard[id]) { + sendClipboard(id); + } + } + + return true; +} + +void +CClient::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +CClient::grabClipboard(ClipboardID id) +{ + m_screen->grabClipboard(id); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +CClient::setClipboardDirty(ClipboardID, bool) +{ + assert(0 && "shouldn't be called"); +} + +void +CClient::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyDown(id, mask, button); +} + +void +CClient::keyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + m_screen->keyRepeat(id, mask, count, button); +} + +void +CClient::keyUp(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyUp(id, mask, button); +} + +void +CClient::mouseDown(ButtonID id) +{ + m_screen->mouseDown(id); +} + +void +CClient::mouseUp(ButtonID id) +{ + m_screen->mouseUp(id); +} + +void +CClient::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->mouseMove(x, y); +} + +void +CClient::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + m_screen->mouseRelativeMove(dx, dy); +} + +void +CClient::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + m_screen->mouseWheel(xDelta, yDelta); +} + +void +CClient::screensaver(bool activate) +{ + m_screen->screensaver(activate); +} + +void +CClient::resetOptions() +{ + m_screen->resetOptions(); +} + +void +CClient::setOptions(const COptionsList& options) +{ + m_screen->setOptions(options); +} + +CString +CClient::getName() const +{ + return m_name; +} + +void +CClient::sendClipboard(ClipboardID id) +{ + // note -- m_mutex must be locked on entry + assert(m_screen != NULL); + assert(m_server != NULL); + + // get clipboard data. set the clipboard time to the last + // clipboard time before getting the data from the screen + // as the screen may detect an unchanged clipboard and + // avoid copying the data. + CClipboard clipboard; + if (clipboard.open(m_timeClipboard[id])) { + clipboard.close(); + } + m_screen->getClipboard(id, &clipboard); + + // check time + if (m_timeClipboard[id] == 0 || + clipboard.getTime() != m_timeClipboard[id]) { + // save new time + m_timeClipboard[id] = clipboard.getTime(); + + // marshall the data + CString data = clipboard.marshall(); + + // save and send data if different or not yet sent + if (!m_sentClipboard[id] || data != m_dataClipboard[id]) { + m_sentClipboard[id] = true; + m_dataClipboard[id] = data; + m_server->onClipboardChanged(id, &clipboard); + } + } +} + +void +CClient::sendEvent(CEvent::Type type, void* data) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +CClient::sendConnectionFailedEvent(const char* msg) +{ + CFailInfo* info = (CFailInfo*)malloc(sizeof(CFailInfo) + strlen(msg)); + info->m_retry = true; + strcpy(info->m_what, msg); + sendEvent(getConnectionFailedEvent(), info); +} + +void +CClient::setupConnecting() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(IDataSocket::getConnectedEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleConnected)); + EVENTQUEUE->adoptHandler(IDataSocket::getConnectionFailedEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleConnectionFailed)); +} + +void +CClient::setupConnection() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(ISocket::getDisconnectedEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleDisconnected)); + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleHello)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleOutputError)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleDisconnected)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClient::handleDisconnected)); +} + +void +CClient::setupScreen() +{ + assert(m_server == NULL); + + m_ready = false; + m_server = new CServerProxy(this, m_stream); + EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleShapeChanged)); + EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(), + getEventTarget(), + new TMethodEventJob(this, + &CClient::handleClipboardGrabbed)); +} + +void +CClient::setupTimer() +{ + assert(m_timer == NULL); + + m_timer = EVENTQUEUE->newOneShotTimer(15.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, + new TMethodEventJob(this, + &CClient::handleConnectTimeout)); +} + +void +CClient::cleanupConnecting() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IDataSocket::getConnectedEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IDataSocket::getConnectionFailedEvent(), + m_stream->getEventTarget()); + } +} + +void +CClient::cleanupConnection() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(ISocket::getDisconnectedEvent(), + m_stream->getEventTarget()); + delete m_stream; + m_stream = NULL; + } +} + +void +CClient::cleanupScreen() +{ + if (m_server != NULL) { + if (m_ready) { + m_screen->disable(); + m_ready = false; + } + EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(), + getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(), + getEventTarget()); + delete m_server; + m_server = NULL; + } +} + +void +CClient::cleanupTimer() +{ + if (m_timer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); + EVENTQUEUE->deleteTimer(m_timer); + m_timer = NULL; + } +} + +void +CClient::handleConnected(const CEvent&, void*) +{ + LOG((CLOG_DEBUG1 "connected; wait for hello")); + cleanupConnecting(); + setupConnection(); + + // reset clipboard state + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; + m_timeClipboard[id] = 0; + } +} + +void +CClient::handleConnectionFailed(const CEvent& event, void*) +{ + IDataSocket::CConnectionFailedInfo* info = + reinterpret_cast(event.getData()); + + cleanupTimer(); + cleanupConnecting(); + delete m_stream; + m_stream = NULL; + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(info->m_what); +} + +void +CClient::handleConnectTimeout(const CEvent&, void*) +{ + cleanupTimer(); + cleanupConnecting(); + cleanupConnection(); + delete m_stream; + m_stream = NULL; + LOG((CLOG_DEBUG1 "connection timed out")); + sendConnectionFailedEvent("Timed out"); +} + +void +CClient::handleOutputError(const CEvent&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_WARN "error sending to server")); + sendEvent(getDisconnectedEvent(), NULL); +} + +void +CClient::handleDisconnected(const CEvent&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_DEBUG1 "disconnected")); + sendEvent(getDisconnectedEvent(), NULL); +} + +void +CClient::handleShapeChanged(const CEvent&, void*) +{ + LOG((CLOG_DEBUG "resolution changed")); + m_server->onInfoChanged(); +} + +void +CClient::handleClipboardGrabbed(const CEvent& event, void*) +{ + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + + // grab ownership + m_server->onGrabClipboard(info->m_id); + + // we now own the clipboard and it has not been sent to the server + m_ownClipboard[info->m_id] = true; + m_sentClipboard[info->m_id] = false; + m_timeClipboard[info->m_id] = 0; + + // if we're not the active screen then send the clipboard now, + // otherwise we'll wait until we leave. + if (!m_active) { + sendClipboard(info->m_id); + } +} + +void +CClient::handleHello(const CEvent&, void*) +{ + SInt16 major, minor; + if (!CProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) { + sendConnectionFailedEvent("Protocol error from server"); + cleanupTimer(); + cleanupConnection(); + return; + } + + // check versions + LOG((CLOG_DEBUG1 "got hello version %d.%d", major, minor)); + if (major < kProtocolMajorVersion || + (major == kProtocolMajorVersion && minor < kProtocolMinorVersion)) { + sendConnectionFailedEvent(XIncompatibleClient(major, minor).what()); + cleanupTimer(); + cleanupConnection(); + return; + } + + // say hello back + LOG((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion)); + CProtocolUtil::writef(m_stream, kMsgHelloBack, + kProtocolMajorVersion, + kProtocolMinorVersion, &m_name); + + // now connected but waiting to complete handshake + setupScreen(); + cleanupTimer(); + + // make sure we process any remaining messages later. we won't + // receive another event for already pending messages so we fake + // one. + if (m_stream->isReady()) { + EVENTQUEUE->addEvent(CEvent(IStream::getInputReadyEvent(), + m_stream->getEventTarget())); + } +} + +void +CClient::handleSuspend(const CEvent&, void*) +{ + LOG((CLOG_INFO "suspend")); + m_suspended = true; + bool wasConnected = isConnected(); + disconnect(NULL); + m_connectOnResume = wasConnected; +} + +void +CClient::handleResume(const CEvent&, void*) +{ + LOG((CLOG_INFO "resume")); + m_suspended = false; + if (m_connectOnResume) { + m_connectOnResume = false; + connect(); + } +} diff --git a/lib/client/CClient.h b/lib/client/CClient.h new file mode 100644 index 00000000..4bd11c66 --- /dev/null +++ b/lib/client/CClient.h @@ -0,0 +1,198 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENT_H +#define CCLIENT_H + +#include "IClient.h" +#include "IClipboard.h" +#include "CNetworkAddress.h" + +class CEventQueueTimer; +class CScreen; +class CServerProxy; +class IDataSocket; +class ISocketFactory; +class IStream; +class IStreamFilterFactory; + +//! Synergy client +/*! +This class implements the top-level client algorithms for synergy. +*/ +class CClient : public IClient { +public: + class CFailInfo { + public: + bool m_retry; + char m_what[1]; + }; + + /*! + This client will attempt to connect to the server using \p name + as its name and \p address as the server's address and \p factory + to create the socket. \p screen is the local screen. + */ + CClient(const CString& name, const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory, + CScreen* screen); + ~CClient(); + + //! @name manipulators + //@{ + + //! Connect to server + /*! + Starts an attempt to connect to the server. This is ignored if + the client is trying to connect or is already connected. + */ + void connect(); + + //! Disconnect + /*! + Disconnects from the server with an optional error message. + */ + void disconnect(const char* msg); + + //! Notify of handshake complete + /*! + Notifies the client that the connection handshake has completed. + */ + void handshakeComplete(); + + //@} + //! @name accessors + //@{ + + //! Test if connected + /*! + Returns true iff the client is successfully connected to the server. + */ + bool isConnected() const; + + //! Test if connecting + /*! + Returns true iff the client is currently attempting to connect to + the server. + */ + bool isConnecting() const; + + //! Get address of server + /*! + Returns the address of the server the client is connected (or wants + to connect) to. + */ + CNetworkAddress getServerAddress() const; + + //! Get connected event type + /*! + Returns the connected event type. This is sent when the client has + successfully connected to the server. + */ + static CEvent::Type getConnectedEvent(); + + //! Get connection failed event type + /*! + Returns the connection failed event type. This is sent when the + server fails for some reason. The event data is a CFailInfo*. + */ + static CEvent::Type getConnectionFailedEvent(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when the client + has disconnected from the server (and only after having successfully + connected). + */ + static CEvent::Type getDisconnectedEvent(); + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual CString getName() const; + +private: + void sendClipboard(ClipboardID); + void sendEvent(CEvent::Type, void*); + void sendConnectionFailedEvent(const char* msg); + void setupConnecting(); + void setupConnection(); + void setupScreen(); + void setupTimer(); + void cleanupConnecting(); + void cleanupConnection(); + void cleanupScreen(); + void cleanupTimer(); + void handleConnected(const CEvent&, void*); + void handleConnectionFailed(const CEvent&, void*); + void handleConnectTimeout(const CEvent&, void*); + void handleOutputError(const CEvent&, void*); + void handleDisconnected(const CEvent&, void*); + void handleShapeChanged(const CEvent&, void*); + void handleClipboardGrabbed(const CEvent&, void*); + void handleHello(const CEvent&, void*); + void handleSuspend(const CEvent& event, void*); + void handleResume(const CEvent& event, void*); + +private: + CString m_name; + CNetworkAddress m_serverAddress; + ISocketFactory* m_socketFactory; + IStreamFilterFactory* m_streamFilterFactory; + CScreen* m_screen; + IStream* m_stream; + CEventQueueTimer* m_timer; + CServerProxy* m_server; + bool m_ready; + bool m_active; + bool m_suspended; + bool m_connectOnResume; + bool m_ownClipboard[kClipboardEnd]; + bool m_sentClipboard[kClipboardEnd]; + IClipboard::Time m_timeClipboard[kClipboardEnd]; + CString m_dataClipboard[kClipboardEnd]; + + static CEvent::Type s_connectedEvent; + static CEvent::Type s_connectionFailedEvent; + static CEvent::Type s_disconnectedEvent; +}; + +#endif diff --git a/lib/client/CServerProxy.cpp b/lib/client/CServerProxy.cpp new file mode 100644 index 00000000..acef43b2 --- /dev/null +++ b/lib/client/CServerProxy.cpp @@ -0,0 +1,816 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CServerProxy.h" +#include "CClient.h" +#include "CClipboard.h" +#include "CProtocolUtil.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "IStream.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "XBase.h" +#include + +// +// CServerProxy +// + +CServerProxy::CServerProxy(CClient* client, IStream* stream) : + m_client(client), + m_stream(stream), + m_seqNum(0), + m_compressMouse(false), + m_compressMouseRelative(false), + m_xMouse(0), + m_yMouse(0), + m_dxMouse(0), + m_dyMouse(0), + m_ignoreMouse(false), + m_keepAliveAlarm(0.0), + m_keepAliveAlarmTimer(NULL), + m_parser(&CServerProxy::parseHandshakeMessage) +{ + assert(m_client != NULL); + assert(m_stream != NULL); + + // initialize modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) + m_modifierTranslationTable[id] = id; + + // handle data on stream + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CServerProxy::handleData)); + + // send heartbeat + setKeepAliveRate(kKeepAliveRate); +} + +CServerProxy::~CServerProxy() +{ + setKeepAliveRate(-1.0); + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); +} + +void +CServerProxy::resetKeepAliveAlarm() +{ + if (m_keepAliveAlarmTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_keepAliveAlarmTimer); + EVENTQUEUE->deleteTimer(m_keepAliveAlarmTimer); + m_keepAliveAlarmTimer = NULL; + } + if (m_keepAliveAlarm > 0.0) { + m_keepAliveAlarmTimer = + EVENTQUEUE->newOneShotTimer(m_keepAliveAlarm, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_keepAliveAlarmTimer, + new TMethodEventJob(this, + &CServerProxy::handleKeepAliveAlarm)); + } +} + +void +CServerProxy::setKeepAliveRate(double rate) +{ + m_keepAliveAlarm = rate * kKeepAlivesUntilDeath; + resetKeepAliveAlarm(); +} + +void +CServerProxy::handleData(const CEvent&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = m_stream->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from server: %d bytes", n)); + m_client->disconnect("incomplete message from server"); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + switch ((this->*m_parser)(code)) { + case kOkay: + break; + + case kUnknown: + LOG((CLOG_ERR "invalid message from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + m_client->disconnect("invalid message from server"); + return; + + case kDisconnect: + return; + } + + // next message + n = m_stream->read(code, 4); + } + + flushCompressedMouse(); +} + +CServerProxy::EResult +CServerProxy::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + + // handshake is complete + m_parser = &CServerProxy::parseMessage; + m_client->handshakeComplete(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + CProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + + else if (memcmp(code, kMsgEIncompatible, 4) == 0) { + SInt32 major, minor; + CProtocolUtil::readf(m_stream, + kMsgEIncompatible + 4, &major, &minor); + LOG((CLOG_ERR "server has incompatible version %d.%d", major, minor)); + m_client->disconnect("server has incompatible version"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBusy, 4) == 0) { + LOG((CLOG_ERR "server already has a connected client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server already has a connected client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEUnknown, 4) == 0) { + LOG((CLOG_ERR "server refused client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server refused client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + return kOkay; +} + +CServerProxy::EResult +CServerProxy::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDMouseMove, 4) == 0) { + mouseMove(); + } + + else if (memcmp(code, kMsgDMouseRelMove, 4) == 0) { + mouseRelativeMove(); + } + + else if (memcmp(code, kMsgDMouseWheel, 4) == 0) { + mouseWheel(); + } + + else if (memcmp(code, kMsgDKeyDown, 4) == 0) { + keyDown(); + } + + else if (memcmp(code, kMsgDKeyUp, 4) == 0) { + keyUp(); + } + + else if (memcmp(code, kMsgDMouseDown, 4) == 0) { + mouseDown(); + } + + else if (memcmp(code, kMsgDMouseUp, 4) == 0) { + mouseUp(); + } + + else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { + keyRepeat(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + CProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCEnter, 4) == 0) { + enter(); + } + + else if (memcmp(code, kMsgCLeave, 4) == 0) { + leave(); + } + + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + grabClipboard(); + } + + else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { + screensaver(); + } + + else if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + setClipboard(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + // send a reply. this is intended to work around a delay when + // running a linux server and an OS X (any BSD?) client. the + // client waits to send an ACK (if the system control flag + // net.inet.tcp.delayed_ack is 1) in hopes of piggybacking it + // on a data packet. we provide that packet here. i don't + // know why a delayed ACK should cause the server to wait since + // TCP_NODELAY is enabled. + CProtocolUtil::writef(m_stream, kMsgCNoop); + + return kOkay; +} + +void +CServerProxy::handleKeepAliveAlarm(const CEvent&, void*) +{ + LOG((CLOG_NOTE "server is dead")); + m_client->disconnect("server is not responding"); +} + +void +CServerProxy::onInfoChanged() +{ + // ignore mouse motion until we receive acknowledgment of our info + // change message. + m_ignoreMouse = true; + + // send info update + queryInfo(); +} + +bool +CServerProxy::onGrabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG1 "sending clipboard %d changed", id)); + CProtocolUtil::writef(m_stream, kMsgCClipboard, id, m_seqNum); + return true; +} + +void +CServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) +{ + CString data = IClipboard::marshall(clipboard); + LOG((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size())); + CProtocolUtil::writef(m_stream, kMsgDClipboard, id, m_seqNum, &data); +} + +void +CServerProxy::flushCompressedMouse() +{ + if (m_compressMouse) { + m_compressMouse = false; + m_client->mouseMove(m_xMouse, m_yMouse); + } + if (m_compressMouseRelative) { + m_compressMouseRelative = false; + m_client->mouseRelativeMove(m_dxMouse, m_dyMouse); + m_dxMouse = 0; + m_dyMouse = 0; + } +} + +void +CServerProxy::sendInfo(const CClientInfo& info) +{ + LOG((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d", info.m_x, info.m_y, info.m_w, info.m_h)); + CProtocolUtil::writef(m_stream, kMsgDInfo, + info.m_x, info.m_y, + info.m_w, info.m_h, 0, + info.m_mx, info.m_my); +} + +KeyID +CServerProxy::translateKey(KeyID id) const +{ + static const KeyID s_translationTable[kKeyModifierIDLast][2] = { + { kKeyNone, kKeyNone }, + { kKeyShift_L, kKeyShift_R }, + { kKeyControl_L, kKeyControl_R }, + { kKeyAlt_L, kKeyAlt_R }, + { kKeyMeta_L, kKeyMeta_R }, + { kKeySuper_L, kKeySuper_R } + }; + + KeyModifierID id2 = kKeyModifierIDNull; + UInt32 side = 0; + switch (id) { + case kKeyShift_L: + id2 = kKeyModifierIDShift; + side = 0; + break; + + case kKeyShift_R: + id2 = kKeyModifierIDShift; + side = 1; + break; + + case kKeyControl_L: + id2 = kKeyModifierIDControl; + side = 0; + break; + + case kKeyControl_R: + id2 = kKeyModifierIDControl; + side = 1; + break; + + case kKeyAlt_L: + id2 = kKeyModifierIDAlt; + side = 0; + break; + + case kKeyAlt_R: + id2 = kKeyModifierIDAlt; + side = 1; + break; + + case kKeyMeta_L: + id2 = kKeyModifierIDMeta; + side = 0; + break; + + case kKeyMeta_R: + id2 = kKeyModifierIDMeta; + side = 1; + break; + + case kKeySuper_L: + id2 = kKeyModifierIDSuper; + side = 0; + break; + + case kKeySuper_R: + id2 = kKeyModifierIDSuper; + side = 1; + break; + } + + if (id2 != kKeyModifierIDNull) { + return s_translationTable[m_modifierTranslationTable[id2]][side]; + } + else { + return id; + } +} + +KeyModifierMask +CServerProxy::translateModifierMask(KeyModifierMask mask) const +{ + static const KeyModifierMask s_masks[kKeyModifierIDLast] = { + 0x0000, + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper + }; + + KeyModifierMask newMask = mask & ~(KeyModifierShift | + KeyModifierControl | + KeyModifierAlt | + KeyModifierMeta | + KeyModifierSuper); + if ((mask & KeyModifierShift) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDShift]]; + } + if ((mask & KeyModifierControl) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDControl]]; + } + if ((mask & KeyModifierAlt) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAlt]]; + } + if ((mask & KeyModifierMeta) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]]; + } + if ((mask & KeyModifierSuper) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]]; + } + return newMask; +} + +void +CServerProxy::enter() +{ + // parse + SInt16 x, y; + UInt16 mask; + UInt32 seqNum; + CProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask); + LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask)); + + // discard old compressed mouse motion, if any + m_compressMouse = false; + m_compressMouseRelative = false; + m_dxMouse = 0; + m_dyMouse = 0; + m_seqNum = seqNum; + + // forward + m_client->enter(x, y, seqNum, static_cast(mask), false); +} + +void +CServerProxy::leave() +{ + // parse + LOG((CLOG_DEBUG1 "recv leave")); + + // send last mouse motion + flushCompressedMouse(); + + // forward + m_client->leave(); +} + +void +CServerProxy::setClipboard() +{ + // parse + ClipboardID id; + UInt32 seqNum; + CString data; + CProtocolUtil::readf(m_stream, kMsgDClipboard + 4, &id, &seqNum, &data); + LOG((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size())); + + // validate + if (id >= kClipboardEnd) { + return; + } + + // forward + CClipboard clipboard; + clipboard.unmarshall(data, 0); + m_client->setClipboard(id, &clipboard); +} + +void +CServerProxy::grabClipboard() +{ + // parse + ClipboardID id; + UInt32 seqNum; + CProtocolUtil::readf(m_stream, kMsgCClipboard + 4, &id, &seqNum); + LOG((CLOG_DEBUG "recv grab clipboard %d", id)); + + // validate + if (id >= kClipboardEnd) { + return; + } + + // forward + m_client->grabClipboard(id); +} + +void +CServerProxy::keyDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + CProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast(mask)); + if (id2 != static_cast(id) || + mask2 != static_cast(mask)) + LOG((CLOG_DEBUG1 "key down translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyDown(id2, mask2, button); +} + +void +CServerProxy::keyRepeat() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, count, button; + CProtocolUtil::readf(m_stream, kMsgDKeyRepeat + 4, + &id, &mask, &count, &button); + LOG((CLOG_DEBUG1 "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button)); + + // translate + KeyID id2 = translateKey(static_cast(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast(mask)); + if (id2 != static_cast(id) || + mask2 != static_cast(mask)) + LOG((CLOG_DEBUG1 "key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyRepeat(id2, mask2, count, button); +} + +void +CServerProxy::keyUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + CProtocolUtil::readf(m_stream, kMsgDKeyUp + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key up id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast(mask)); + if (id2 != static_cast(id) || + mask2 != static_cast(mask)) + LOG((CLOG_DEBUG1 "key up translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyUp(id2, mask2, button); +} + +void +CServerProxy::mouseDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + CProtocolUtil::readf(m_stream, kMsgDMouseDown + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse down id=%d", id)); + + // forward + m_client->mouseDown(static_cast(id)); +} + +void +CServerProxy::mouseUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + CProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse up id=%d", id)); + + // forward + m_client->mouseUp(static_cast(id)); +} + +void +CServerProxy::mouseMove() +{ + // parse + bool ignore; + SInt16 x, y; + CProtocolUtil::readf(m_stream, kMsgDMouseMove + 4, &x, &y); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouse && m_stream->isReady()) { + m_compressMouse = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouse) { + m_compressMouseRelative = false; + ignore = true; + m_xMouse = x; + m_yMouse = y; + m_dxMouse = 0; + m_dyMouse = 0; + } + LOG((CLOG_DEBUG2 "recv mouse move %d,%d", x, y)); + + // forward + if (!ignore) { + m_client->mouseMove(x, y); + } +} + +void +CServerProxy::mouseRelativeMove() +{ + // parse + bool ignore; + SInt16 dx, dy; + CProtocolUtil::readf(m_stream, kMsgDMouseRelMove + 4, &dx, &dy); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouseRelative && m_stream->isReady()) { + m_compressMouseRelative = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouseRelative) { + ignore = true; + m_dxMouse += dx; + m_dyMouse += dy; + } + LOG((CLOG_DEBUG2 "recv mouse relative move %d,%d", dx, dy)); + + // forward + if (!ignore) { + m_client->mouseRelativeMove(dx, dy); + } +} + +void +CServerProxy::mouseWheel() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt16 xDelta, yDelta; + CProtocolUtil::readf(m_stream, kMsgDMouseWheel + 4, &xDelta, &yDelta); + LOG((CLOG_DEBUG2 "recv mouse wheel %+d,%+d", xDelta, yDelta)); + + // forward + m_client->mouseWheel(xDelta, yDelta); +} + +void +CServerProxy::screensaver() +{ + // parse + SInt8 on; + CProtocolUtil::readf(m_stream, kMsgCScreenSaver + 4, &on); + LOG((CLOG_DEBUG1 "recv screen saver on=%d", on)); + + // forward + m_client->screensaver(on != 0); +} + +void +CServerProxy::resetOptions() +{ + // parse + LOG((CLOG_DEBUG1 "recv reset options")); + + // forward + m_client->resetOptions(); + + // reset keep alive + setKeepAliveRate(kKeepAliveRate); + + // reset modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) { + m_modifierTranslationTable[id] = id; + } +} + +void +CServerProxy::setOptions() +{ + // parse + COptionsList options; + CProtocolUtil::readf(m_stream, kMsgDSetOptions + 4, &options); + LOG((CLOG_DEBUG1 "recv set options size=%d", options.size())); + + // forward + m_client->setOptions(options); + + // update modifier table + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + KeyModifierID id = kKeyModifierIDNull; + if (options[i] == kOptionModifierMapForShift) { + id = kKeyModifierIDShift; + } + else if (options[i] == kOptionModifierMapForControl) { + id = kKeyModifierIDControl; + } + else if (options[i] == kOptionModifierMapForAlt) { + id = kKeyModifierIDAlt; + } + else if (options[i] == kOptionModifierMapForMeta) { + id = kKeyModifierIDMeta; + } + else if (options[i] == kOptionModifierMapForSuper) { + id = kKeyModifierIDSuper; + } + else if (options[i] == kOptionHeartbeat) { + // update keep alive + setKeepAliveRate(1.0e-3 * static_cast(options[i + 1])); + } + if (id != kKeyModifierIDNull) { + m_modifierTranslationTable[id] = + static_cast(options[i + 1]); + LOG((CLOG_DEBUG1 "modifier %d mapped to %d", id, m_modifierTranslationTable[id])); + } + } +} + +void +CServerProxy::queryInfo() +{ + CClientInfo info; + m_client->getShape(info.m_x, info.m_y, info.m_w, info.m_h); + m_client->getCursorPos(info.m_mx, info.m_my); + sendInfo(info); +} + +void +CServerProxy::infoAcknowledgment() +{ + LOG((CLOG_DEBUG1 "recv info acknowledgment")); + m_ignoreMouse = false; +} diff --git a/lib/client/CServerProxy.h b/lib/client/CServerProxy.h new file mode 100644 index 00000000..f6642696 --- /dev/null +++ b/lib/client/CServerProxy.h @@ -0,0 +1,115 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSERVERPROXY_H +#define CSERVERPROXY_H + +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "CEvent.h" + +class CClient; +class CClientInfo; +class CEventQueueTimer; +class IClipboard; +class IStream; + +//! Proxy for server +/*! +This class acts a proxy for the server, converting calls into messages +to the server and messages from the server to calls on the client. +*/ +class CServerProxy { +public: + /*! + Process messages from the server on \p stream and forward to + \p client. + */ + CServerProxy(CClient* client, IStream* stream); + ~CServerProxy(); + + //! @name manipulators + //@{ + + void onInfoChanged(); + bool onGrabClipboard(ClipboardID); + void onClipboardChanged(ClipboardID, const IClipboard*); + + //@} + +protected: + enum EResult { kOkay, kUnknown, kDisconnect }; + EResult parseHandshakeMessage(const UInt8* code); + EResult parseMessage(const UInt8* code); + +private: + // if compressing mouse motion then send the last motion now + void flushCompressedMouse(); + + void sendInfo(const CClientInfo&); + + void resetKeepAliveAlarm(); + void setKeepAliveRate(double); + + // modifier key translation + KeyID translateKey(KeyID) const; + KeyModifierMask translateModifierMask(KeyModifierMask) const; + + // event handlers + void handleData(const CEvent&, void*); + void handleKeepAliveAlarm(const CEvent&, void*); + + // message handlers + void enter(); + void leave(); + void setClipboard(); + void grabClipboard(); + void keyDown(); + void keyRepeat(); + void keyUp(); + void mouseDown(); + void mouseUp(); + void mouseMove(); + void mouseRelativeMove(); + void mouseWheel(); + void screensaver(); + void resetOptions(); + void setOptions(); + void queryInfo(); + void infoAcknowledgment(); + +private: + typedef EResult (CServerProxy::*MessageParser)(const UInt8*); + + CClient* m_client; + IStream* m_stream; + + UInt32 m_seqNum; + + bool m_compressMouse; + bool m_compressMouseRelative; + SInt32 m_xMouse, m_yMouse; + SInt32 m_dxMouse, m_dyMouse; + + bool m_ignoreMouse; + + KeyModifierID m_modifierTranslationTable[kKeyModifierIDLast]; + + double m_keepAliveAlarm; + CEventQueueTimer* m_keepAliveAlarmTimer; + + MessageParser m_parser; +}; + +#endif diff --git a/lib/client/Makefile.am b/lib/client/Makefile.am new file mode 100644 index 00000000..4ea5a230 --- /dev/null +++ b/lib/client/Makefile.am @@ -0,0 +1,40 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libclient.a +libclient_a_SOURCES = \ + CClient.cpp \ + CServerProxy.cpp \ + CClient.h \ + CServerProxy.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + $(NULL) diff --git a/lib/client/Makefile.win b/lib/client/Makefile.win new file mode 100644 index 00000000..3da2c73c --- /dev/null +++ b/lib/client/Makefile.win @@ -0,0 +1,63 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_CLIENT_SRC = lib\client +LIB_CLIENT_DST = $(BUILD_DST)\$(LIB_CLIENT_SRC) +LIB_CLIENT_LIB = "$(LIB_CLIENT_DST)\client.lib" +LIB_CLIENT_CPP = \ + "CClient.cpp" \ + "CServerProxy.cpp" \ + $(NULL) +LIB_CLIENT_OBJ = \ + "$(LIB_CLIENT_DST)\CClient.obj" \ + "$(LIB_CLIENT_DST)\CServerProxy.obj" \ + $(NULL) +LIB_CLIENT_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_CLIENT_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_CLIENT_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_CLIENT_LIB) + +# Dependency rules +$(LIB_CLIENT_OBJ): $(AUTODEP) +!if EXIST($(LIB_CLIENT_DST)\deps.mak) +!include $(LIB_CLIENT_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_CLIENT_SRC)\}.cpp{$(LIB_CLIENT_DST)\}.obj:: +!else +{$(LIB_CLIENT_SRC)\}.cpp{$(LIB_CLIENT_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_CLIENT_SRC) + -@$(MKDIR) $(LIB_CLIENT_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_CLIENT_INC) \ + /Fo$(LIB_CLIENT_DST)\ \ + /Fd$(LIB_CLIENT_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_CLIENT_SRC) $(LIB_CLIENT_DST) +$(LIB_CLIENT_LIB): $(LIB_CLIENT_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_CLIENT_SRC) $(LIB_CLIENT_DST) $(**:.obj=.d) diff --git a/lib/common/BasicTypes.h b/lib/common/BasicTypes.h new file mode 100644 index 00000000..b9d8293e --- /dev/null +++ b/lib/common/BasicTypes.h @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef BASICTYPES_H +#define BASICTYPES_H + +#include "common.h" + +// +// pick types of particular sizes +// + +#if !defined(TYPE_OF_SIZE_1) +# if SIZEOF_CHAR == 1 +# define TYPE_OF_SIZE_1 char +# endif +#endif + +#if !defined(TYPE_OF_SIZE_2) +# if SIZEOF_INT == 2 +# define TYPE_OF_SIZE_2 int +# else +# define TYPE_OF_SIZE_2 short +# endif +#endif + +#if !defined(TYPE_OF_SIZE_4) + // Carbon defines SInt32 and UInt32 in terms of long +# if SIZEOF_INT == 4 && !defined(__APPLE__) +# define TYPE_OF_SIZE_4 int +# else +# define TYPE_OF_SIZE_4 long +# endif +#endif + +// +// verify existence of required types +// + +#if !defined(TYPE_OF_SIZE_1) +# error No 1 byte integer type +#endif +#if !defined(TYPE_OF_SIZE_2) +# error No 2 byte integer type +#endif +#if !defined(TYPE_OF_SIZE_4) +# error No 4 byte integer type +#endif + + +// +// make typedefs +// +// except for SInt8 and UInt8 these types are only guaranteed to be +// at least as big as indicated (in bits). that is, they may be +// larger than indicated. +// + +typedef signed TYPE_OF_SIZE_1 SInt8; +typedef signed TYPE_OF_SIZE_2 SInt16; +typedef signed TYPE_OF_SIZE_4 SInt32; + +typedef unsigned TYPE_OF_SIZE_1 UInt8; +typedef unsigned TYPE_OF_SIZE_2 UInt16; +typedef unsigned TYPE_OF_SIZE_4 UInt32; + +// +// clean up +// + +#undef TYPE_OF_SIZE_1 +#undef TYPE_OF_SIZE_2 +#undef TYPE_OF_SIZE_4 + +#endif diff --git a/lib/common/IInterface.h b/lib/common/IInterface.h new file mode 100644 index 00000000..97df31c2 --- /dev/null +++ b/lib/common/IInterface.h @@ -0,0 +1,31 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IINTERFACE_H +#define IINTERFACE_H + +#include "common.h" + +//! Base class of interfaces +/*! +This is the base class of all interface classes. An interface class has +only pure virtual methods. +*/ +class IInterface { +public: + //! Interface destructor does nothing + virtual ~IInterface() { } +}; + +#endif diff --git a/lib/common/MacOSXPrecomp.h b/lib/common/MacOSXPrecomp.h new file mode 100644 index 00000000..64e7c90a --- /dev/null +++ b/lib/common/MacOSXPrecomp.h @@ -0,0 +1,8 @@ +// +// Prefix header for all source files of the 'deleteme' target in the 'deleteme' project. +// + +#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_2 + + +#include diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am new file mode 100644 index 00000000..74804748 --- /dev/null +++ b/lib/common/Makefile.am @@ -0,0 +1,48 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + BasicTypes.h \ + IInterface.h \ + MacOSXPrecomp.h \ + common.h \ + stdbitset.h \ + stddeque.h \ + stdfstream.h \ + stdistream.h \ + stdlist.h \ + stdmap.h \ + stdostream.h \ + stdpost.h \ + stdpre.h \ + stdset.h \ + stdsstream.h \ + stdstring.h \ + stdvector.h \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libcommon.a +libcommon_a_SOURCES = \ + Version.cpp \ + Version.h \ + $(NULL) + +INCLUDES = \ + $(NULL) diff --git a/lib/common/Makefile.win b/lib/common/Makefile.win new file mode 100644 index 00000000..9b63a046 --- /dev/null +++ b/lib/common/Makefile.win @@ -0,0 +1,53 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_COMMON_SRC = lib\common +LIB_COMMON_DST = $(BUILD_DST)\$(LIB_COMMON_SRC) +LIB_COMMON_LIB = "$(LIB_COMMON_DST)\common.lib" +LIB_COMMON_CPP = \ + Version.cpp \ + $(NULL) +LIB_COMMON_OBJ = \ + "$(LIB_COMMON_DST)\Version.obj" \ + $(NULL) +LIB_COMMON_INC = \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_COMMON_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_COMMON_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_COMMON_LIB) + +# Dependency rules +$(LIB_COMMON_OBJ): $(AUTODEP) +!if EXIST($(LIB_COMMON_DST)\deps.mak) +!include $(LIB_COMMON_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_COMMON_SRC)\}.cpp{$(LIB_COMMON_DST)\}.obj:: +!else +{$(LIB_COMMON_SRC)\}.cpp{$(LIB_COMMON_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_COMMON_SRC) + -@$(MKDIR) $(LIB_COMMON_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_COMMON_INC) \ + /Fo$(LIB_COMMON_DST)\ \ + /Fd$(LIB_COMMON_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_COMMON_SRC) $(LIB_COMMON_DST) +$(LIB_COMMON_LIB): $(LIB_COMMON_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_COMMON_SRC) $(LIB_COMMON_DST) $(**:.obj=.d) diff --git a/lib/common/Version.cpp b/lib/common/Version.cpp new file mode 100644 index 00000000..86fcdef7 --- /dev/null +++ b/lib/common/Version.cpp @@ -0,0 +1,22 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "Version.h" + +const char* kApplication = "Synergy"; +const char* kCopyright = "Copyright (C) 2002 Chris Schoeneman"; +const char* kContact = "Chris Schoeneman, crs23@bigfoot.com"; +const char* kWebsite = "http://synergy2.sourceforge.net/"; +const char* kVersion = VERSION; +const char* kAppVersion = "Synergy " VERSION; diff --git a/lib/common/Version.h b/lib/common/Version.h new file mode 100644 index 00000000..43783c8c --- /dev/null +++ b/lib/common/Version.h @@ -0,0 +1,45 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef VERSION_H +#define VERSION_H + +#include "common.h" + +// set version macro if not set yet +#if !defined(VERSION) +# define VERSION "1.3.2" +#endif + +// important strings +extern const char* kApplication; +extern const char* kCopyright; +extern const char* kContact; +extern const char* kWebsite; + +// build version. follows linux kernel style: an even minor number implies +// a release version, odd implies development version. +extern const char* kVersion; + +// application version +extern const char* kAppVersion; + +// exit codes +static const int kExitSuccess = 0; // successful completion +static const int kExitFailed = 1; // general failure +static const int kExitTerminated = 2; // killed by signal +static const int kExitArgs = 3; // bad arguments +static const int kExitConfig = 4; // cannot read configuration + +#endif diff --git a/lib/common/common.h b/lib/common/common.h new file mode 100644 index 00000000..e539a7d6 --- /dev/null +++ b/lib/common/common.h @@ -0,0 +1,135 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COMMON_H +#define COMMON_H + +// this file should be included, directly or indirectly by every other. + +#if HAVE_CONFIG_H +# include "config.h" + + // don't use poll() on mac +# if defined(__APPLE__) +# undef HAVE_POLL +# endif +#else + // we may not have run configure on win32 +# if defined(_WIN32) +# define SYSAPI_WIN32 1 +# define WINAPI_MSWINDOWS 1 +# endif + + // we may not have run configure on OS X +# if defined(__APPLE__) +# define SYSAPI_UNIX 1 +# define WINAPI_CARBON 1 + +# define HAVE_CXX_BOOL 1 +# define HAVE_CXX_CASTS 1 +# define HAVE_CXX_EXCEPTIONS 1 +# define HAVE_CXX_MUTABLE 1 +# define HAVE_CXX_STDLIB 1 +# define HAVE_GETPWUID_R 1 +# define HAVE_GMTIME_R 1 +# define HAVE_INET_ATON 1 +# define HAVE_INTTYPES_H 1 +# define HAVE_ISTREAM 1 +# define HAVE_MEMORY_H 1 +# define HAVE_NANOSLEEP 1 +# define HAVE_OSTREAM 1 +# define HAVE_POSIX_SIGWAIT 1 +# define HAVE_PTHREAD 1 +# define HAVE_PTHREAD_SIGNAL 1 +# include +# include +# if defined(_SOCKLEN_T) +# define HAVE_SOCKLEN_T 1 +# endif +# define HAVE_SSTREAM 1 +# define HAVE_STDINT_H 1 +# define HAVE_STDLIB_H 1 +# define HAVE_STRINGS_H 1 +# define HAVE_STRING_H 1 +# define HAVE_SYS_SELECT_H 1 +# define HAVE_SYS_SOCKET_H 1 +# define HAVE_SYS_STAT_H 1 +# define HAVE_SYS_TIME_H 1 +# define HAVE_SYS_TYPES_H 1 +# define HAVE_SYS_UTSNAME_H 1 +# define HAVE_UNISTD_H 1 +# define HAVE_VSNPRINTF 1 +/* disable this so we can build with the 10.2.8 SDK */ +/*# define HAVE_WCHAR_H 1*/ + +# define SELECT_TYPE_ARG1 int +# define SELECT_TYPE_ARG234 (fd_set *) +# define SELECT_TYPE_ARG5 (struct timeval *) +# define SIZEOF_CHAR 1 +# define SIZEOF_INT 4 +# define SIZEOF_LONG 4 +# define SIZEOF_SHORT 2 +# define STDC_HEADERS 1 +# define TIME_WITH_SYS_TIME 1 +# define X_DISPLAY_MISSING 1 +# endif +#endif + +// VC++ specific +#if (_MSC_VER >= 1200) + // work around for statement scoping bug +# define for if (false) { } else for + + // turn off bonehead warnings +# pragma warning(disable: 4786) // identifier truncated in debug info +# pragma warning(disable: 4514) // unreferenced inline function removed + + // this one's a little too aggressive +# pragma warning(disable: 4127) // conditional expression is constant + + // emitted incorrectly under release build in some circumstances +# if defined(NDEBUG) +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4701) // variable maybe used uninitialized +# endif +#endif // (_MSC_VER >= 1200) + +// VC++ has built-in sized types +#if defined(_MSC_VER) +# include +# define TYPE_OF_SIZE_1 __int8 +# define TYPE_OF_SIZE_2 __int16 +# define TYPE_OF_SIZE_4 __int32 +#else +# define SIZE_OF_CHAR 1 +# define SIZE_OF_SHORT 2 +# define SIZE_OF_INT 4 +# define SIZE_OF_LONG 4 +#endif + +// FIXME -- including fp.h from Carbon.h causes a undefined symbol error +// on my build system. the symbol is scalb. since we don't need any +// math functions we define __FP__, the include guard macro for fp.h, to +// prevent fp.h from being included. +#if defined(__APPLE__) +#define __FP__ +#endif + +// define NULL +#include + +// make assert available since we use it a lot +#include + +#endif diff --git a/lib/common/stdbitset.h b/lib/common/stdbitset.h new file mode 100644 index 00000000..529772ca --- /dev/null +++ b/lib/common/stdbitset.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stddeque.h b/lib/common/stddeque.h new file mode 100644 index 00000000..06bdafd1 --- /dev/null +++ b/lib/common/stddeque.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdfstream.h b/lib/common/stdfstream.h new file mode 100644 index 00000000..9d1f5aa6 --- /dev/null +++ b/lib/common/stdfstream.h @@ -0,0 +1,18 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" +#include "stdistream.h" diff --git a/lib/common/stdistream.h b/lib/common/stdistream.h new file mode 100644 index 00000000..33667398 --- /dev/null +++ b/lib/common/stdistream.h @@ -0,0 +1,43 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#if HAVE_ISTREAM +#include +#else +#include +#endif +#include "stdpost.h" + +#if defined(_MSC_VER) && _MSC_VER <= 1200 +// VC++6 istream has no overloads for __int* types, .NET does +inline +std::istream& operator>>(std::istream& s, SInt8& i) +{ return s >> (signed char&)i; } +inline +std::istream& operator>>(std::istream& s, SInt16& i) +{ return s >> (short&)i; } +inline +std::istream& operator>>(std::istream& s, SInt32& i) +{ return s >> (int&)i; } +inline +std::istream& operator>>(std::istream& s, UInt8& i) +{ return s >> (unsigned char&)i; } +inline +std::istream& operator>>(std::istream& s, UInt16& i) +{ return s >> (unsigned short&)i; } +inline +std::istream& operator>>(std::istream& s, UInt32& i) +{ return s >> (unsigned int&)i; } +#endif diff --git a/lib/common/stdlist.h b/lib/common/stdlist.h new file mode 100644 index 00000000..6f0df8e8 --- /dev/null +++ b/lib/common/stdlist.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdmap.h b/lib/common/stdmap.h new file mode 100644 index 00000000..da501615 --- /dev/null +++ b/lib/common/stdmap.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdostream.h b/lib/common/stdostream.h new file mode 100644 index 00000000..48e89862 --- /dev/null +++ b/lib/common/stdostream.h @@ -0,0 +1,21 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#if HAVE_OSTREAM +#include +#else +#include +#endif +#include "stdpost.h" diff --git a/lib/common/stdpost.h b/lib/common/stdpost.h new file mode 100644 index 00000000..0f06b74a --- /dev/null +++ b/lib/common/stdpost.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/lib/common/stdpre.h b/lib/common/stdpre.h new file mode 100644 index 00000000..10fa7d5d --- /dev/null +++ b/lib/common/stdpre.h @@ -0,0 +1,27 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#if defined(_MSC_VER) +#pragma warning(disable: 4786) // identifier truncated +#pragma warning(disable: 4514) // unreferenced inline +#pragma warning(disable: 4710) // not inlined +#pragma warning(disable: 4663) // C++ change, template specialization +#pragma warning(disable: 4503) // decorated name length too long +#pragma warning(push, 3) +#pragma warning(disable: 4018) // signed/unsigned mismatch +#pragma warning(disable: 4284) +#pragma warning(disable: 4146) // unary minus on unsigned value +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4701) // variable possibly used uninitialized +#endif diff --git a/lib/common/stdset.h b/lib/common/stdset.h new file mode 100644 index 00000000..aeb491b6 --- /dev/null +++ b/lib/common/stdset.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdsstream.h b/lib/common/stdsstream.h new file mode 100644 index 00000000..45b28124 --- /dev/null +++ b/lib/common/stdsstream.h @@ -0,0 +1,371 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" + +#if HAVE_SSTREAM || !defined(__GNUC__) || (__GNUC__ >= 3) + +#include + +#elif defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 95) +// g++ 2.95 didn't ship with sstream. the following is a backport +// by Magnus Fromreide of the sstream in g++ 3.0. + +/* This is part of libio/iostream, providing -*- C++ -*- input/output. +Copyright (C) 2000 Free Software Foundation + +This file is part of the GNU IO Library. This library is free +software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) +any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this library; see the file COPYING. If not, write to the Free +Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +As a special exception, if you link this library with files +compiled with a GNU compiler to produce an executable, this does not cause +the resulting executable to be covered by the GNU General Public License. +This exception does not however invalidate any other reasons why +the executable file might be covered by the GNU General Public License. */ + +/* Written by Magnus Fromreide (magfr@lysator.liu.se). */ +/* seekoff and ideas for overflow is largely borrowed from libstdc++-v3 */ + +#include +#include +#include + +namespace std +{ + class stringbuf : public streambuf + { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + stringbuf(int which=ios::in|ios::out) + : streambuf(), mode(static_cast(which)), + stream(NULL), stream_len(0) + { + stringbuf_init(); + } + + explicit + stringbuf(const string &str, int which=ios::in|ios::out) + : streambuf(), mode(static_cast(which)), + stream(NULL), stream_len(0) + { + if (mode & (ios::in|ios::out)) + { + stream_len = str.size(); + stream = new char_type[stream_len]; + str.copy(stream, stream_len); + } + stringbuf_init(); + } + + virtual + ~stringbuf() + { + delete[] stream; + } + + string + str() const + { + if (pbase() != 0) + return string(stream, pptr()-pbase()); + else + return string(); + } + + void + str(const string& str) + { + delete[] stream; + stream_len = str.size(); + stream = new char_type[stream_len]; + str.copy(stream, stream_len); + stringbuf_init(); + } + + protected: + // The buffer is already in gptr, so if it ends then it is out of data. + virtual int + underflow() + { + return EOF; + } + + virtual int + overflow(int c = EOF) + { + int res; + if (mode & ios::out) + { + if (c != EOF) + { + streamsize old_stream_len = stream_len; + stream_len += 1; + char_type* new_stream = new char_type[stream_len]; + memcpy(new_stream, stream, old_stream_len); + delete[] stream; + stream = new_stream; + stringbuf_sync(gptr()-eback(), pptr()-pbase()); + sputc(c); + res = c; + } + else + res = EOF; + } + else + res = 0; + return res; + } + + virtual streambuf* + setbuf(char_type* s, streamsize n) + { + if (n != 0) + { + delete[] stream; + stream = new char_type[n]; + memcpy(stream, s, n); + stream_len = n; + stringbuf_sync(0, 0); + } + return this; + } + + virtual pos_type + seekoff(off_type off, ios::seek_dir way, int which = ios::in | ios::out) + { + pos_type ret = pos_type(off_type(-1)); + bool testin = which & ios::in && mode & ios::in; + bool testout = which & ios::out && mode & ios::out; + bool testboth = testin && testout && way != ios::cur; + + if (stream_len && ((testin != testout) || testboth)) + { + char_type* beg = stream; + char_type* curi = NULL; + char_type* curo = NULL; + char_type* endi = NULL; + char_type* endo = NULL; + + if (testin) + { + curi = gptr(); + endi = egptr(); + } + if (testout) + { + curo = pptr(); + endo = epptr(); + } + + off_type newoffi = 0; + off_type newoffo = 0; + if (way == ios::beg) + { + newoffi = beg - curi; + newoffo = beg - curo; + } + else if (way == ios::end) + { + newoffi = endi - curi; + newoffo = endo - curo; + } + + if (testin && newoffi + off + curi - beg >= 0 && + endi - beg >= newoffi + off + curi - beg) + { + gbump(newoffi + off); + ret = pos_type(newoffi + off + curi); + } + if (testout && newoffo + off + curo - beg >= 0 && + endo - beg >= newoffo + off + curo - beg) + { + pbump(newoffo + off); + ret = pos_type(newoffo + off + curo); + } + } + return ret; + } + + virtual pos_type + seekpos(pos_type sp, int which = ios::in | ios::out) + { + pos_type ret = seekoff(sp, ios::beg, which); + return ret; + } + + private: + void + stringbuf_sync(streamsize i, streamsize o) + { + if (mode & ios::in) + setg(stream, stream + i, stream + stream_len); + if (mode & ios::out) + { + setp(stream, stream + stream_len); + pbump(o); + } + } + void + stringbuf_init() + { + if (mode & ios::ate) + stringbuf_sync(0, stream_len); + else + stringbuf_sync(0, 0); + } + + private: + ios::open_mode mode; + char_type* stream; + streamsize stream_len; + }; + + class istringstream : public istream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + istringstream(int which=ios::in) + : istream(&sb), sb(which | ios::in) + { } + + explicit + istringstream(const string& str, int which=ios::in) + : istream(&sb), sb(str, which | ios::in) + { } + + stringbuf* + rdbuf() const + { + return const_cast(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + void + str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; + + class ostringstream : public ostream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + ostringstream(int which=ios::out) + : ostream(&sb), sb(which | ios::out) + { } + + explicit + ostringstream(const string& str, int which=ios::out) + : ostream(&sb), sb(str, which | ios::out) + { } + + stringbuf* + rdbuf() const + { + return const_cast(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + + void str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; + + class stringstream : public iostream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + stringstream(int which=ios::out|ios::in) + : iostream(&sb), sb(which) + { } + + explicit + stringstream(const string& str, int which=ios::out|ios::in) + : iostream(&sb), sb(str, which) + { } + + stringbuf* + rdbuf() const + { + return const_cast(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + + void + str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; +}; + +#else /* not g++ 2.95 and no */ + +#error "Standard C++ library is missing required sstream header." + +#endif /* not g++ 2.95 and no */ + +#include "stdpost.h" +#include "stdistream.h" diff --git a/lib/common/stdstring.h b/lib/common/stdstring.h new file mode 100644 index 00000000..3d83c03c --- /dev/null +++ b/lib/common/stdstring.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/common/stdvector.h b/lib/common/stdvector.h new file mode 100644 index 00000000..1056bb46 --- /dev/null +++ b/lib/common/stdvector.h @@ -0,0 +1,17 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "stdpre.h" +#include +#include "stdpost.h" diff --git a/lib/io/CStreamBuffer.cpp b/lib/io/CStreamBuffer.cpp new file mode 100644 index 00000000..d11494b4 --- /dev/null +++ b/lib/io/CStreamBuffer.cpp @@ -0,0 +1,142 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CStreamBuffer.h" + +// +// CStreamBuffer +// + +const UInt32 CStreamBuffer::kChunkSize = 4096; + +CStreamBuffer::CStreamBuffer() : + m_size(0), + m_headUsed(0) +{ + // do nothing +} + +CStreamBuffer::~CStreamBuffer() +{ + // do nothing +} + +const void* +CStreamBuffer::peek(UInt32 n) +{ + assert(n <= m_size); + + // if requesting no data then return NULL so we don't try to access + // an empty list. + if (n == 0) { + return NULL; + } + + // reserve space in first chunk + ChunkList::iterator head = m_chunks.begin(); + head->reserve(n + m_headUsed); + + // consolidate chunks into the first chunk until it has n bytes + ChunkList::iterator scan = head; + ++scan; + while (head->size() - m_headUsed < n && scan != m_chunks.end()) { + head->insert(head->end(), scan->begin(), scan->end()); + scan = m_chunks.erase(scan); + } + + return reinterpret_cast(&(head->begin()[m_headUsed])); +} + +void +CStreamBuffer::pop(UInt32 n) +{ + // discard all chunks if n is greater than or equal to m_size + if (n >= m_size) { + m_size = 0; + m_headUsed = 0; + m_chunks.clear(); + return; + } + + // update size + m_size -= n; + + // discard chunks until more than n bytes would've been discarded + ChunkList::iterator scan = m_chunks.begin(); + assert(scan != m_chunks.end()); + while (scan->size() - m_headUsed <= n) { + n -= scan->size() - m_headUsed; + m_headUsed = 0; + scan = m_chunks.erase(scan); + assert(scan != m_chunks.end()); + } + + // remove left over bytes from the head chunk + if (n > 0) { + m_headUsed += n; + } +} + +void +CStreamBuffer::write(const void* vdata, UInt32 n) +{ + assert(vdata != NULL); + + // ignore if no data, otherwise update size + if (n == 0) { + return; + } + m_size += n; + + // cast data to bytes + const UInt8* data = reinterpret_cast(vdata); + + // point to last chunk if it has space, otherwise append an empty chunk + ChunkList::iterator scan = m_chunks.end(); + if (scan != m_chunks.begin()) { + --scan; + if (scan->size() >= kChunkSize) { + ++scan; + } + } + if (scan == m_chunks.end()) { + scan = m_chunks.insert(scan, Chunk()); + } + + // append data in chunks + while (n > 0) { + // choose number of bytes for next chunk + assert(scan->size() <= kChunkSize); + UInt32 count = kChunkSize - scan->size(); + if (count > n) + count = n; + + // transfer data + scan->insert(scan->end(), data, data + count); + n -= count; + data += count; + + // append another empty chunk if we're not done yet + if (n > 0) { + ++scan; + scan = m_chunks.insert(scan, Chunk()); + } + } +} + +UInt32 +CStreamBuffer::getSize() const +{ + return m_size; +} diff --git a/lib/io/CStreamBuffer.h b/lib/io/CStreamBuffer.h new file mode 100644 index 00000000..fafcc72b --- /dev/null +++ b/lib/io/CStreamBuffer.h @@ -0,0 +1,78 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTREAMBUFFER_H +#define CSTREAMBUFFER_H + +#include "BasicTypes.h" +#include "stdlist.h" +#include "stdvector.h" + +//! FIFO of bytes +/*! +This class maintains a FIFO (first-in, last-out) buffer of bytes. +*/ +class CStreamBuffer { +public: + CStreamBuffer(); + ~CStreamBuffer(); + + //! @name manipulators + //@{ + + //! Read data without removing from buffer + /*! + Return a pointer to memory with the next \c n bytes in the buffer + (which must be <= getSize()). The caller must not modify the returned + memory nor delete it. + */ + const void* peek(UInt32 n); + + //! Discard data + /*! + Discards the next \c n bytes. If \c n >= getSize() then the buffer + is cleared. + */ + void pop(UInt32 n); + + //! Write data to buffer + /*! + Appends \c n bytes from \c data to the buffer. + */ + void write(const void* data, UInt32 n); + + //@} + //! @name accessors + //@{ + + //! Get size of buffer + /*! + Returns the number of bytes in the buffer. + */ + UInt32 getSize() const; + + //@} + +private: + static const UInt32 kChunkSize; + + typedef std::vector Chunk; + typedef std::list ChunkList; + + ChunkList m_chunks; + UInt32 m_size; + UInt32 m_headUsed; +}; + +#endif diff --git a/lib/io/CStreamFilter.cpp b/lib/io/CStreamFilter.cpp new file mode 100644 index 00000000..312b0dfd --- /dev/null +++ b/lib/io/CStreamFilter.cpp @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CStreamFilter.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CStreamFilter +// + +CStreamFilter::CStreamFilter(IStream* stream, bool adoptStream) : + m_stream(stream), + m_adopted(adoptStream) +{ + // replace handlers for m_stream + EVENTQUEUE->removeHandlers(m_stream->getEventTarget()); + EVENTQUEUE->adoptHandler(CEvent::kUnknown, m_stream->getEventTarget(), + new TMethodEventJob(this, + &CStreamFilter::handleUpstreamEvent)); +} + +CStreamFilter::~CStreamFilter() +{ + EVENTQUEUE->removeHandler(CEvent::kUnknown, m_stream->getEventTarget()); + if (m_adopted) { + delete m_stream; + } +} + +void +CStreamFilter::close() +{ + getStream()->close(); +} + +UInt32 +CStreamFilter::read(void* buffer, UInt32 n) +{ + return getStream()->read(buffer, n); +} + +void +CStreamFilter::write(const void* buffer, UInt32 n) +{ + getStream()->write(buffer, n); +} + +void +CStreamFilter::flush() +{ + getStream()->flush(); +} + +void +CStreamFilter::shutdownInput() +{ + getStream()->shutdownInput(); +} + +void +CStreamFilter::shutdownOutput() +{ + getStream()->shutdownOutput(); +} + +void* +CStreamFilter::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +bool +CStreamFilter::isReady() const +{ + return getStream()->isReady(); +} + +UInt32 +CStreamFilter::getSize() const +{ + return getStream()->getSize(); +} + +IStream* +CStreamFilter::getStream() const +{ + return m_stream; +} + +void +CStreamFilter::filterEvent(const CEvent& event) +{ + EVENTQUEUE->dispatchEvent(CEvent(event.getType(), + getEventTarget(), event.getData())); +} + +void +CStreamFilter::handleUpstreamEvent(const CEvent& event, void*) +{ + filterEvent(event); +} diff --git a/lib/io/CStreamFilter.h b/lib/io/CStreamFilter.h new file mode 100644 index 00000000..4dc87094 --- /dev/null +++ b/lib/io/CStreamFilter.h @@ -0,0 +1,70 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSTREAMFILTER_H +#define CSTREAMFILTER_H + +#include "IStream.h" + +//! A stream filter +/*! +This class wraps a stream. Subclasses provide indirect access +to the wrapped stream, typically performing some filtering. +*/ +class CStreamFilter : public IStream { +public: + /*! + Create a wrapper around \c stream. Iff \c adoptStream is true then + this object takes ownership of the stream and will delete it in the + d'tor. + */ + CStreamFilter(IStream* stream, bool adoptStream = true); + ~CStreamFilter(); + + // IStream overrides + // These all just forward to the underlying stream except getEventTarget. + // Override as necessary. getEventTarget returns a pointer to this. + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual void* getEventTarget() const; + virtual bool isReady() const; + virtual UInt32 getSize() const; + +protected: + //! Get the stream + /*! + Returns the stream passed to the c'tor. + */ + IStream* getStream() const; + + //! Handle events from source stream + /*! + Does the event filtering. The default simply dispatches an event + identical except using this object as the event target. + */ + virtual void filterEvent(const CEvent&); + +private: + void handleUpstreamEvent(const CEvent&, void*); + +private: + IStream* m_stream; + bool m_adopted; +}; + +#endif diff --git a/lib/io/IStream.cpp b/lib/io/IStream.cpp new file mode 100644 index 00000000..aec65b61 --- /dev/null +++ b/lib/io/IStream.cpp @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IStream.h" + +// +// IStream +// + +CEvent::Type IStream::s_inputReadyEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputFlushedEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputErrorEvent = CEvent::kUnknown; +CEvent::Type IStream::s_inputShutdownEvent = CEvent::kUnknown; +CEvent::Type IStream::s_outputShutdownEvent = CEvent::kUnknown; + +CEvent::Type +IStream::getInputReadyEvent() +{ + return CEvent::registerTypeOnce(s_inputReadyEvent, + "IStream::inputReady"); +} + +CEvent::Type +IStream::getOutputFlushedEvent() +{ + return CEvent::registerTypeOnce(s_outputFlushedEvent, + "IStream::outputFlushed"); +} + +CEvent::Type +IStream::getOutputErrorEvent() +{ + return CEvent::registerTypeOnce(s_outputErrorEvent, + "IStream::outputError"); +} + +CEvent::Type +IStream::getInputShutdownEvent() +{ + return CEvent::registerTypeOnce(s_inputShutdownEvent, + "IStream::inputShutdown"); +} + +CEvent::Type +IStream::getOutputShutdownEvent() +{ + return CEvent::registerTypeOnce(s_outputShutdownEvent, + "IStream::outputShutdown"); +} diff --git a/lib/io/IStream.h b/lib/io/IStream.h new file mode 100644 index 00000000..cb5b54c9 --- /dev/null +++ b/lib/io/IStream.h @@ -0,0 +1,157 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISTREAM_H +#define ISTREAM_H + +#include "IInterface.h" +#include "CEvent.h" + +//! Bidirectional stream interface +/*! +Defines the interface for all streams. +*/ +class IStream : public IInterface { +public: + //! @name manipulators + //@{ + + //! Close the stream + /*! + Closes the stream. Pending input data and buffered output data + are discarded. Use \c flush() before \c close() to send buffered + output data. Attempts to \c read() after a close return 0, + attempts to \c write() generate output error events, and attempts + to \c flush() return immediately. + */ + virtual void close() = 0; + + //! Read from stream + /*! + Read up to \p n bytes into \p buffer, returning the number read + (zero if no data is available or input is shutdown). \p buffer + may be NULL in which case the data is discarded. + */ + virtual UInt32 read(void* buffer, UInt32 n) = 0; + + //! Write to stream + /*! + Write \c n bytes from \c buffer to the stream. If this can't + complete immediately it will block. Data may be buffered in + order to return more quickly. A output error event is generated + when writing fails. + */ + virtual void write(const void* buffer, UInt32 n) = 0; + + //! Flush the stream + /*! + Waits until all buffered data has been written to the stream. + */ + virtual void flush() = 0; + + //! Shutdown input + /*! + Shutdown the input side of the stream. Any pending input data is + discarded and further reads immediately return 0. + */ + virtual void shutdownInput() = 0; + + //! Shutdown output + /*! + Shutdown the output side of the stream. Any buffered output data + is discarded and further writes generate output error events. Use + \c flush() before \c shutdownOutput() to send buffered output data. + */ + virtual void shutdownOutput() = 0; + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this stream. It + should be the source stream in a chain of stream filters. + */ + virtual void* getEventTarget() const = 0; + + //! Test if \c read() will succeed + /*! + Returns true iff an immediate \c read() will return data. This + may or may not be the same as \c getSize() > 0, depending on the + stream type. + */ + virtual bool isReady() const = 0; + + //! Get bytes available to read + /*! + Returns a conservative estimate of the available bytes to read + (i.e. a number not greater than the actual number of bytes). + Some streams may not be able to determine this and will always + return zero. + */ + virtual UInt32 getSize() const = 0; + + //! Get input ready event type + /*! + Returns the input ready event type. A stream sends this event + when \c read() will return with data. + */ + static CEvent::Type getInputReadyEvent(); + + //! Get output flushed event type + /*! + Returns the output flushed event type. A stream sends this event + when the output buffer has been flushed. If there have been no + writes since the event was posted, calling \c shutdownOutput() or + \c close() will not discard any data and \c flush() will return + immediately. + */ + static CEvent::Type getOutputFlushedEvent(); + + //! Get output error event type + /*! + Returns the output error event type. A stream sends this event + when a write has failed. + */ + static CEvent::Type getOutputErrorEvent(); + + //! Get input shutdown event type + /*! + Returns the input shutdown event type. This is sent when the + input side of the stream has shutdown. When the input has + shutdown, no more data will ever be available to read. + */ + static CEvent::Type getInputShutdownEvent(); + + //! Get output shutdown event type + /*! + Returns the output shutdown event type. This is sent when the + output side of the stream has shutdown. When the output has + shutdown, no more data can ever be written to the stream. Any + attempt to do so will generate a output error event. + */ + static CEvent::Type getOutputShutdownEvent(); + + //@} + +private: + static CEvent::Type s_inputReadyEvent; + static CEvent::Type s_outputFlushedEvent; + static CEvent::Type s_outputErrorEvent; + static CEvent::Type s_inputShutdownEvent; + static CEvent::Type s_outputShutdownEvent; +}; + +#endif diff --git a/lib/io/IStreamFilterFactory.h b/lib/io/IStreamFilterFactory.h new file mode 100644 index 00000000..6e6c86ef --- /dev/null +++ b/lib/io/IStreamFilterFactory.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISTREAMFILTERFACTORY_H +#define ISTREAMFILTERFACTORY_H + +#include "IInterface.h" + +class IStream; + +//! Stream filter factory interface +/*! +This interface provides factory methods to create stream filters. +*/ +class IStreamFilterFactory : public IInterface { +public: + //! Create filter + /*! + Create and return a stream filter on \p stream. The caller must + delete the returned object. + */ + virtual IStream* create(IStream* stream, bool adoptStream) = 0; +}; + +#endif diff --git a/lib/io/Makefile.am b/lib/io/Makefile.am new file mode 100644 index 00000000..fec1dd31 --- /dev/null +++ b/lib/io/Makefile.am @@ -0,0 +1,41 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libio.a +libio_a_SOURCES = \ + CStreamBuffer.cpp \ + CStreamFilter.cpp \ + IStream.cpp \ + XIO.cpp \ + CStreamBuffer.h \ + CStreamFilter.h \ + IStream.h \ + IStreamFilterFactory.h \ + XIO.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + $(NULL) diff --git a/lib/io/Makefile.win b/lib/io/Makefile.win new file mode 100644 index 00000000..765c6909 --- /dev/null +++ b/lib/io/Makefile.win @@ -0,0 +1,63 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_IO_SRC = lib\io +LIB_IO_DST = $(BUILD_DST)\$(LIB_IO_SRC) +LIB_IO_LIB = "$(LIB_IO_DST)\io.lib" +LIB_IO_CPP = \ + "CStreamBuffer.cpp" \ + "CStreamFilter.cpp" \ + "IStream.cpp" \ + "XIO.cpp" \ + $(NULL) +LIB_IO_OBJ = \ + "$(LIB_IO_DST)\CStreamBuffer.obj" \ + "$(LIB_IO_DST)\CStreamFilter.obj" \ + "$(LIB_IO_DST)\IStream.obj" \ + "$(LIB_IO_DST)\XIO.obj" \ + $(NULL) +LIB_IO_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_IO_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_IO_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_IO_LIB) + +# Dependency rules +$(LIB_IO_OBJ): $(AUTODEP) +!if EXIST($(LIB_IO_DST)\deps.mak) +!include $(LIB_IO_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_IO_SRC)\}.cpp{$(LIB_IO_DST)\}.obj:: +!else +{$(LIB_IO_SRC)\}.cpp{$(LIB_IO_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_IO_SRC) + -@$(MKDIR) $(LIB_IO_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_IO_INC) \ + /Fo$(LIB_IO_DST)\ \ + /Fd$(LIB_IO_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_IO_SRC) $(LIB_IO_DST) +$(LIB_IO_LIB): $(LIB_IO_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_IO_SRC) $(LIB_IO_DST) $(**:.obj=.d) diff --git a/lib/io/XIO.cpp b/lib/io/XIO.cpp new file mode 100644 index 00000000..b7101a4d --- /dev/null +++ b/lib/io/XIO.cpp @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XIO.h" + +// +// XIOClosed +// + +CString +XIOClosed::getWhat() const throw() +{ + return format("XIOClosed", "already closed"); +} + + +// +// XIOEndOfStream +// + +CString +XIOEndOfStream::getWhat() const throw() +{ + return format("XIOEndOfStream", "reached end of stream"); +} + + +// +// XIOWouldBlock +// + +CString +XIOWouldBlock::getWhat() const throw() +{ + return format("XIOWouldBlock", "stream operation would block"); +} diff --git a/lib/io/XIO.h b/lib/io/XIO.h new file mode 100644 index 00000000..cc41ef40 --- /dev/null +++ b/lib/io/XIO.h @@ -0,0 +1,48 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XIO_H +#define XIO_H + +#include "XBase.h" + +//! Generic I/O exception +XBASE_SUBCLASS(XIO, XBase); + +//! I/O closing exception +/*! +Thrown if a stream cannot be closed. +*/ +XBASE_SUBCLASS(XIOClose, XIO); + +//! I/O already closed exception +/*! +Thrown when attempting to close or perform I/O on an already closed. +stream. +*/ +XBASE_SUBCLASS_WHAT(XIOClosed, XIO); + +//! I/O end of stream exception +/*! +Thrown when attempting to read beyond the end of a stream. +*/ +XBASE_SUBCLASS_WHAT(XIOEndOfStream, XIO); + +//! I/O would block exception +/*! +Thrown if an operation on a stream would block. +*/ +XBASE_SUBCLASS_WHAT(XIOWouldBlock, XIO); + +#endif diff --git a/lib/mt/CCondVar.cpp b/lib/mt/CCondVar.cpp new file mode 100644 index 00000000..d13aedfd --- /dev/null +++ b/lib/mt/CCondVar.cpp @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CCondVar.h" +#include "CStopwatch.h" +#include "CArch.h" + +// +// CCondVarBase +// + +CCondVarBase::CCondVarBase(CMutex* mutex) : + m_mutex(mutex) +{ + assert(m_mutex != NULL); + m_cond = ARCH->newCondVar(); +} + +CCondVarBase::~CCondVarBase() +{ + ARCH->closeCondVar(m_cond); +} + +void +CCondVarBase::lock() const +{ + m_mutex->lock(); +} + +void +CCondVarBase::unlock() const +{ + m_mutex->unlock(); +} + +void +CCondVarBase::signal() +{ + ARCH->signalCondVar(m_cond); +} + +void +CCondVarBase::broadcast() +{ + ARCH->broadcastCondVar(m_cond); +} + +bool +CCondVarBase::wait(CStopwatch& timer, double timeout) const +{ + // check timeout against timer + if (timeout >= 0.0) { + timeout -= timer.getTime(); + if (timeout < 0.0) + return false; + } + return wait(timeout); +} + +bool +CCondVarBase::wait(double timeout) const +{ + return ARCH->waitCondVar(m_cond, m_mutex->m_mutex, timeout); +} + +CMutex* +CCondVarBase::getMutex() const +{ + return m_mutex; +} diff --git a/lib/mt/CCondVar.h b/lib/mt/CCondVar.h new file mode 100644 index 00000000..a04102c4 --- /dev/null +++ b/lib/mt/CCondVar.h @@ -0,0 +1,224 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCONDVAR_H +#define CCONDVAR_H + +#include "CMutex.h" +#include "BasicTypes.h" + +class CStopwatch; + +//! Generic condition variable +/*! +This class provides functionality common to all condition variables +but doesn't provide the actual variable storage. A condition variable +is a multiprocessing primitive that can be waited on. Every condition +variable has an associated mutex. +*/ +class CCondVarBase { +public: + /*! + \c mutex must not be NULL. All condition variables have an + associated mutex. The mutex needn't be unique to one condition + variable. + */ + CCondVarBase(CMutex* mutex); + ~CCondVarBase(); + + //! @name manipulators + //@{ + + //! Lock the condition variable's mutex + /*! + Lock the condition variable's mutex. The condition variable should + be locked before reading or writing it. It must be locked for a + call to wait(). Locks are not recursive; locking a locked mutex + will deadlock the thread. + */ + void lock() const; + + //! Unlock the condition variable's mutex + void unlock() const; + + //! Signal the condition variable + /*! + Wake up one waiting thread, if there are any. Which thread gets + woken is undefined. + */ + void signal(); + + //! Signal the condition variable + /*! + Wake up all waiting threads, if any. + */ + void broadcast(); + + //@} + //! @name accessors + //@{ + + //! Wait on the condition variable + /*! + Wait on the condition variable. If \c timeout < 0 then wait until + signalled, otherwise up to \c timeout seconds or until signalled, + whichever comes first. Returns true if the object was signalled + during the wait, false otherwise. + + The proper way to wait for a condition is: + \code + cv.lock(); + while (cv-expr) { + cv.wait(); + } + cv.unlock(); + \endcode + where \c cv-expr involves the value of \c cv and is false when the + condition is satisfied. + + (cancellation point) + */ + bool wait(double timeout = -1.0) const; + + //! Wait on the condition variable + /*! + Same as \c wait(double) but use \c timer to compare against \timeout. + Since clients normally wait on condition variables in a loop, clients + can use this to avoid recalculating \c timeout on each iteration. + Passing a stopwatch with a negative \c timeout is pointless (it will + never time out) but permitted. + + (cancellation point) + */ + bool wait(CStopwatch& timer, double timeout) const; + + //! Get the mutex + /*! + Get the mutex passed to the c'tor. + */ + CMutex* getMutex() const; + + //@} + +private: + // not implemented + CCondVarBase(const CCondVarBase&); + CCondVarBase& operator=(const CCondVarBase&); + +private: + CMutex* m_mutex; + CArchCond m_cond; +}; + +//! Condition variable +/*! +A condition variable with storage for type \c T. +*/ +template +class CCondVar : public CCondVarBase { +public: + //! Initialize using \c value + CCondVar(CMutex* mutex, const T& value); + //! Initialize using another condition variable's value + CCondVar(const CCondVar&); + ~CCondVar(); + + //! @name manipulators + //@{ + + //! Assigns the value of \c cv to this + /*! + Set the variable's value. The condition variable should be locked + before calling this method. + */ + CCondVar& operator=(const CCondVar& cv); + + //! Assigns \c value to this + /*! + Set the variable's value. The condition variable should be locked + before calling this method. + */ + CCondVar& operator=(const T& v); + + //@} + //! @name accessors + //@{ + + //! Get the variable's value + /*! + Get the variable's value. The condition variable should be locked + before calling this method. + */ + operator const volatile T&() const; + + //@} + +private: + volatile T m_data; +}; + +template +inline +CCondVar::CCondVar( + CMutex* mutex, + const T& data) : + CCondVarBase(mutex), + m_data(data) +{ + // do nothing +} + +template +inline +CCondVar::CCondVar( + const CCondVar& cv) : + CCondVarBase(cv.getMutex()), + m_data(cv.m_data) +{ + // do nothing +} + +template +inline +CCondVar::~CCondVar() +{ + // do nothing +} + +template +inline +CCondVar& +CCondVar::operator=(const CCondVar& cv) +{ + m_data = cv.m_data; + return *this; +} + +template +inline +CCondVar& +CCondVar::operator=(const T& data) +{ + m_data = data; + return *this; +} + +template +inline +CCondVar::operator const volatile T&() const +{ + return m_data; +} + +#endif diff --git a/lib/mt/CLock.cpp b/lib/mt/CLock.cpp new file mode 100644 index 00000000..c943395b --- /dev/null +++ b/lib/mt/CLock.cpp @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CLock.h" +#include "CCondVar.h" +#include "CMutex.h" + +// +// CLock +// + +CLock::CLock(const CMutex* mutex) : + m_mutex(mutex) +{ + m_mutex->lock(); +} + +CLock::CLock(const CCondVarBase* cv) : + m_mutex(cv->getMutex()) +{ + m_mutex->lock(); +} + +CLock::~CLock() +{ + m_mutex->unlock(); +} diff --git a/lib/mt/CLock.h b/lib/mt/CLock.h new file mode 100644 index 00000000..f00e0cd1 --- /dev/null +++ b/lib/mt/CLock.h @@ -0,0 +1,48 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CLOCK_H +#define CLOCK_H + +#include "common.h" + +class CMutex; +class CCondVarBase; + +//! Mutual exclusion lock utility +/*! +This class locks a mutex or condition variable in the c'tor and unlocks +it in the d'tor. It's easier and safer than manually locking and +unlocking since unlocking must usually be done no matter how a function +exits (including by unwinding due to an exception). +*/ +class CLock { +public: + //! Lock the mutex \c mutex + CLock(const CMutex* mutex); + //! Lock the condition variable \c cv + CLock(const CCondVarBase* cv); + //! Unlock the mutex or condition variable + ~CLock(); + +private: + // not implemented + CLock(const CLock&); + CLock& operator=(const CLock&); + +private: + const CMutex* m_mutex; +}; + +#endif diff --git a/lib/mt/CMutex.cpp b/lib/mt/CMutex.cpp new file mode 100644 index 00000000..0d2bab3c --- /dev/null +++ b/lib/mt/CMutex.cpp @@ -0,0 +1,53 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMutex.h" +#include "CArch.h" + +// +// CMutex +// + +CMutex::CMutex() +{ + m_mutex = ARCH->newMutex(); +} + +CMutex::CMutex(const CMutex&) +{ + m_mutex = ARCH->newMutex(); +} + +CMutex::~CMutex() +{ + ARCH->closeMutex(m_mutex); +} + +CMutex& +CMutex::operator=(const CMutex&) +{ + return *this; +} + +void +CMutex::lock() const +{ + ARCH->lockMutex(m_mutex); +} + +void +CMutex::unlock() const +{ + ARCH->unlockMutex(m_mutex); +} diff --git a/lib/mt/CMutex.h b/lib/mt/CMutex.h new file mode 100644 index 00000000..c11eeee3 --- /dev/null +++ b/lib/mt/CMutex.h @@ -0,0 +1,78 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMUTEX_H +#define CMUTEX_H + +#include "IArchMultithread.h" + +//! Mutual exclusion +/*! +A non-recursive mutual exclusion object. Only one thread at a time can +hold a lock on a mutex. Any thread that attempts to lock a locked mutex +will block until the mutex is unlocked. At that time, if any threads are +blocked, exactly one waiting thread will acquire the lock and continue +running. A thread may not lock a mutex it already owns the lock on; if +it tries it will deadlock itself. +*/ +class CMutex { +public: + CMutex(); + //! Equivalent to default c'tor + /*! + Copy c'tor doesn't copy anything. It just makes it possible to + copy objects that contain a mutex. + */ + CMutex(const CMutex&); + ~CMutex(); + + //! @name manipulators + //@{ + + //! Does nothing + /*! + This does nothing. It just makes it possible to assign objects + that contain a mutex. + */ + CMutex& operator=(const CMutex&); + + //@} + //! @name accessors + //@{ + + //! Lock the mutex + /*! + Locks the mutex, which must not have been previously locked by the + calling thread. This blocks if the mutex is already locked by another + thread. + + (cancellation point) + */ + void lock() const; + + //! Unlock the mutex + /*! + Unlocks the mutex, which must have been previously locked by the + calling thread. + */ + void unlock() const; + + //@} + +private: + friend class CCondVarBase; + CArchMutex m_mutex; +}; + +#endif diff --git a/lib/mt/CThread.cpp b/lib/mt/CThread.cpp new file mode 100644 index 00000000..195b7b67 --- /dev/null +++ b/lib/mt/CThread.cpp @@ -0,0 +1,183 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CThread.h" +#include "XMT.h" +#include "XThread.h" +#include "CLog.h" +#include "IJob.h" +#include "CArch.h" + +// +// CThread +// + +CThread::CThread(IJob* job) +{ + m_thread = ARCH->newThread(&CThread::threadFunc, job); + if (m_thread == NULL) { + // couldn't create thread + delete job; + throw XMTThreadUnavailable(); + } +} + +CThread::CThread(const CThread& thread) +{ + m_thread = ARCH->copyThread(thread.m_thread); +} + +CThread::CThread(CArchThread adoptedThread) +{ + m_thread = adoptedThread; +} + +CThread::~CThread() +{ + ARCH->closeThread(m_thread); +} + +CThread& +CThread::operator=(const CThread& thread) +{ + // copy given thread and release ours + CArchThread copy = ARCH->copyThread(thread.m_thread); + ARCH->closeThread(m_thread); + + // cut over + m_thread = copy; + + return *this; +} + +void +CThread::exit(void* result) +{ + throw XThreadExit(result); +} + +void +CThread::cancel() +{ + ARCH->cancelThread(m_thread); +} + +void +CThread::setPriority(int n) +{ + ARCH->setPriorityOfThread(m_thread, n); +} + +void +CThread::unblockPollSocket() +{ + ARCH->unblockPollSocket(m_thread); +} + +CThread +CThread::getCurrentThread() +{ + return CThread(ARCH->newCurrentThread()); +} + +void +CThread::testCancel() +{ + ARCH->testCancelThread(); +} + +bool +CThread::wait(double timeout) const +{ + return ARCH->wait(m_thread, timeout); +} + +void* +CThread::getResult() const +{ + if (wait()) + return ARCH->getResultOfThread(m_thread); + else + return NULL; +} + +IArchMultithread::ThreadID +CThread::getID() const +{ + return ARCH->getIDOfThread(m_thread); +} + +bool +CThread::operator==(const CThread& thread) const +{ + return ARCH->isSameThread(m_thread, thread.m_thread); +} + +bool +CThread::operator!=(const CThread& thread) const +{ + return !ARCH->isSameThread(m_thread, thread.m_thread); +} + +void* +CThread::threadFunc(void* vjob) +{ + // get this thread's id for logging + IArchMultithread::ThreadID id; + { + CArchThread thread = ARCH->newCurrentThread(); + id = ARCH->getIDOfThread(thread); + ARCH->closeThread(thread); + } + + // get job + IJob* job = reinterpret_cast(vjob); + + // run job + void* result = NULL; + try { + // go + LOG((CLOG_DEBUG1 "thread 0x%08x entry", id)); + job->run(); + LOG((CLOG_DEBUG1 "thread 0x%08x exit", id)); + } + + catch (XThreadCancel&) { + // client called cancel() + LOG((CLOG_DEBUG1 "caught cancel on thread 0x%08x", id)); + delete job; + throw; + } + catch (XThreadExit& e) { + // client called exit() + result = e.m_result; + LOG((CLOG_DEBUG1 "caught exit on thread 0x%08x, result %p", id, result)); + } + catch (XBase& e) { + LOG((CLOG_ERR "exception on thread 0x%08x: %s", id, e.what())); + delete job; + throw; + } + catch (...) { + LOG((CLOG_ERR "exception on thread 0x%08x: ", id)); + delete job; + throw; + } + + // done with job + delete job; + + // return exit result + return result; +} diff --git a/lib/mt/CThread.h b/lib/mt/CThread.h new file mode 100644 index 00000000..896d3778 --- /dev/null +++ b/lib/mt/CThread.h @@ -0,0 +1,209 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTHREAD_H +#define CTHREAD_H + +#include "IArchMultithread.h" + +class IJob; + +//! Thread handle +/*! +Creating a CThread creates a new context of execution (i.e. thread) that +runs simulatenously with the calling thread. A CThread is only a handle +to a thread; deleting a CThread does not cancel or destroy the thread it +refers to and multiple CThread objects can refer to the same thread. + +Threads can terminate themselves but cannot be forced to terminate by +other threads. However, other threads can signal a thread to terminate +itself by cancelling it. And a thread can wait (block) on another thread +to terminate. + +Most functions that can block for an arbitrary time are cancellation +points. A cancellation point is a function that can be interrupted by +a request to cancel the thread. Cancellation points are noted in the +documentation. +*/ +// note -- do not derive from this class +class CThread { +public: + //! Run \c adoptedJob in a new thread + /*! + Create and start a new thread executing the \c adoptedJob. The + new thread takes ownership of \c adoptedJob and will delete it. + */ + CThread(IJob* adoptedJob); + + //! Duplicate a thread handle + /*! + Make a new thread object that refers to an existing thread. + This does \b not start a new thread. + */ + CThread(const CThread&); + + //! Release a thread handle + /*! + Release a thread handle. This does not terminate the thread. A thread + will keep running until the job completes or calls exit() or allows + itself to be cancelled. + */ + ~CThread(); + + //! @name manipulators + //@{ + + //! Assign thread handle + /*! + Assign a thread handle. This has no effect on the threads, it simply + makes this thread object refer to another thread. It does \b not + start a new thread. + */ + CThread& operator=(const CThread&); + + //! Terminate the calling thread + /*! + Terminate the calling thread. This function does not return but + the stack is unwound and automatic objects are destroyed, as if + exit() threw an exception (which is, in fact, what it does). The + argument is saved as the result returned by getResult(). If you + have \c catch(...) blocks then you should add the following before + each to avoid catching the exit: + \code + catch(CThreadExit&) { throw; } + \endcode + or add the \c RETHROW_XTHREAD macro to the \c catch(...) block. + */ + static void exit(void*); + + //! Cancel thread + /*! + Cancel the thread. cancel() never waits for the thread to + terminate; it just posts the cancel and returns. A thread will + terminate when it enters a cancellation point with cancellation + enabled. If cancellation is disabled then the cancel is + remembered but not acted on until the first call to a + cancellation point after cancellation is enabled. + + A cancellation point is a function that can act on cancellation. + A cancellation point does not return if there's a cancel pending. + Instead, it unwinds the stack and destroys automatic objects, as + if cancel() threw an exception (which is, in fact, what it does). + Threads must take care to unlock and clean up any resources they + may have, especially mutexes. They can \c catch(XThreadCancel) to + do that then rethrow the exception or they can let it happen + automatically by doing clean up in the d'tors of automatic + objects (like CLock). Clients are strongly encouraged to do the latter. + During cancellation, further cancel() calls are ignored (i.e. + a thread cannot be interrupted by a cancel during cancellation). + + Clients that \c catch(XThreadCancel) must always rethrow the + exception. Clients that \c catch(...) must either rethrow the + exception or include a \c catch(XThreadCancel) handler that + rethrows. The \c RETHROW_XTHREAD macro may be useful for that. + */ + void cancel(); + + //! Change thread priority + /*! + Change the priority of the thread. Normal priority is 0, 1 is + the next lower, etc. -1 is the next higher, etc. but boosting + the priority may not be permitted and will be silenty ignored. + */ + void setPriority(int n); + + //! Force pollSocket() to return + /*! + Forces a currently blocked pollSocket() in the thread to return + immediately. + */ + void unblockPollSocket(); + + //@} + //! @name accessors + //@{ + + //! Get current thread's handle + /*! + Return a CThread object representing the calling thread. + */ + static CThread getCurrentThread(); + + //! Test for cancellation + /*! + testCancel() does nothing but is a cancellation point. Call + this to make a function itself a cancellation point. If the + thread was cancelled and cancellation is enabled this will + cause the thread to unwind the stack and terminate. + + (cancellation point) + */ + static void testCancel(); + + //! Wait for thread to terminate + /*! + Waits for the thread to terminate (by exit() or cancel() or + by returning from the thread job) for up to \c timeout seconds, + returning true if the thread terminated and false otherwise. + This returns immediately with false if called by a thread on + itself and immediately with true if the thread has already + terminated. This will wait forever if \c timeout < 0.0. + + (cancellation point) + */ + bool wait(double timeout = -1.0) const; + + //! Get the exit result + /*! + Returns the exit result. This does an implicit wait(). It returns + NULL immediately if called by a thread on itself or on a thread that + was cancelled. + + (cancellation point) + */ + void* getResult() const; + + //! Get the thread id + /*! + Returns an integer id for this thread. This id must not be used to + check if two CThread objects refer to the same thread. Use + operator==() for that. + */ + IArchMultithread::ThreadID + getID() const; + + //! Compare thread handles + /*! + Returns true if two CThread objects refer to the same thread. + */ + bool operator==(const CThread&) const; + + //! Compare thread handles + /*! + Returns true if two CThread objects do not refer to the same thread. + */ + bool operator!=(const CThread&) const; + + //@} + +private: + CThread(CArchThread); + + static void* threadFunc(void*); + +private: + CArchThread m_thread; +}; + +#endif diff --git a/lib/mt/Makefile.am b/lib/mt/Makefile.am new file mode 100644 index 00000000..fbf01c03 --- /dev/null +++ b/lib/mt/Makefile.am @@ -0,0 +1,42 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libmt.a +libmt_a_SOURCES = \ + CCondVar.cpp \ + CLock.cpp \ + CMutex.cpp \ + CThread.cpp \ + XMT.cpp \ + CCondVar.h \ + CLock.h \ + CMutex.h \ + CThread.h \ + XMT.h \ + XThread.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + $(NULL) diff --git a/lib/mt/Makefile.win b/lib/mt/Makefile.win new file mode 100644 index 00000000..4ae79130 --- /dev/null +++ b/lib/mt/Makefile.win @@ -0,0 +1,64 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_MT_SRC = lib\mt +LIB_MT_DST = $(BUILD_DST)\$(LIB_MT_SRC) +LIB_MT_LIB = "$(LIB_MT_DST)\mt.lib" +LIB_MT_CPP = \ + "CCondVar.cpp" \ + "CLock.cpp" \ + "CMutex.cpp" \ + "CThread.cpp" \ + "XMT.cpp" \ + $(NULL) +LIB_MT_OBJ = \ + "$(LIB_MT_DST)\CCondVar.obj" \ + "$(LIB_MT_DST)\CLock.obj" \ + "$(LIB_MT_DST)\CMutex.obj" \ + "$(LIB_MT_DST)\CThread.obj" \ + "$(LIB_MT_DST)\XMT.obj" \ + $(NULL) +LIB_MT_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_MT_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_MT_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_MT_LIB) + +# Dependency rules +$(LIB_MT_OBJ): $(AUTODEP) +!if EXIST($(LIB_MT_DST)\deps.mak) +!include $(LIB_MT_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_MT_SRC)\}.cpp{$(LIB_MT_DST)\}.obj:: +!else +{$(LIB_MT_SRC)\}.cpp{$(LIB_MT_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_MT_SRC) + -@$(MKDIR) $(LIB_MT_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_MT_INC) \ + /Fo$(LIB_MT_DST)\ \ + /Fd$(LIB_MT_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_MT_SRC) $(LIB_MT_DST) +$(LIB_MT_LIB): $(LIB_MT_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_MT_SRC) $(LIB_MT_DST) $(**:.obj=.d) diff --git a/lib/mt/XMT.cpp b/lib/mt/XMT.cpp new file mode 100644 index 00000000..b10d2d79 --- /dev/null +++ b/lib/mt/XMT.cpp @@ -0,0 +1,25 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XMT.h" + +// +// XMTThreadUnavailable +// + +CString +XMTThreadUnavailable::getWhat() const throw() +{ + return format("XMTThreadUnavailable", "cannot create thread"); +} diff --git a/lib/mt/XMT.h b/lib/mt/XMT.h new file mode 100644 index 00000000..f66428ea --- /dev/null +++ b/lib/mt/XMT.h @@ -0,0 +1,29 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XMT_H +#define XMT_H + +#include "XBase.h" + +//! Generic multithreading exception +XBASE_SUBCLASS(XMT, XBase); + +//! Thread creation exception +/*! +Thrown when a thread cannot be created. +*/ +XBASE_SUBCLASS_WHAT(XMTThreadUnavailable, XMT); + +#endif diff --git a/lib/mt/XThread.h b/lib/mt/XThread.h new file mode 100644 index 00000000..30dff1da --- /dev/null +++ b/lib/mt/XThread.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XTHREAD_H +#define XTHREAD_H + +#include "XArch.h" + +//! Thread exception to exit +/*! +Thrown by CThread::exit() to exit a thread. Clients of CThread +must not throw this type but must rethrow it if caught (by +XThreadExit, XThread, or ...). +*/ +class XThreadExit : public XThread { +public: + //! \c result is the result of the thread + XThreadExit(void* result) : m_result(result) { } + ~XThreadExit() { } + +public: + void* m_result; +}; + +#endif diff --git a/lib/net/CNetworkAddress.cpp b/lib/net/CNetworkAddress.cpp new file mode 100644 index 00000000..7daeed55 --- /dev/null +++ b/lib/net/CNetworkAddress.cpp @@ -0,0 +1,208 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CNetworkAddress.h" +#include "XSocket.h" +#include "CArch.h" +#include "XArch.h" +#include + +// +// CNetworkAddress +// + +// name re-resolution adapted from a patch by Brent Priddy. + +CNetworkAddress::CNetworkAddress() : + m_address(NULL), + m_hostname(), + m_port(0) +{ + // note -- make no calls to CNetwork socket interface here; + // we're often called prior to CNetwork::init(). +} + +CNetworkAddress::CNetworkAddress(int port) : + m_address(NULL), + m_hostname(), + m_port(port) +{ + checkPort(); + m_address = ARCH->newAnyAddr(IArchNetwork::kINET); + ARCH->setAddrPort(m_address, m_port); +} + +CNetworkAddress::CNetworkAddress(const CNetworkAddress& addr) : + m_address(addr.m_address != NULL ? ARCH->copyAddr(addr.m_address) : NULL), + m_hostname(addr.m_hostname), + m_port(addr.m_port) +{ + // do nothing +} + +CNetworkAddress::CNetworkAddress(const CString& hostname, int port) : + m_address(NULL), + m_hostname(hostname), + m_port(port) +{ + // check for port suffix + CString::size_type i = m_hostname.rfind(':'); + if (i != CString::npos && i + 1 < m_hostname.size()) { + // found a colon. see if it looks like an IPv6 address. + bool colonNotation = false; + bool dotNotation = false; + bool doubleColon = false; + for (CString::size_type j = 0; j < i; ++j) { + if (m_hostname[j] == ':') { + colonNotation = true; + dotNotation = false; + if (m_hostname[j + 1] == ':') { + doubleColon = true; + } + } + else if (m_hostname[j] == '.' && colonNotation) { + dotNotation = true; + } + } + + // port suffix is ambiguous with IPv6 notation if there's + // a double colon and the end of the address is not in dot + // notation. in that case we assume it's not a port suffix. + // the user can replace the double colon with zeros to + // disambiguate. + if ((!doubleColon || dotNotation) || !colonNotation) { + // parse port from hostname + char* end; + const char* chostname = m_hostname.c_str(); + long suffixPort = strtol(chostname + i + 1, &end, 10); + if (end == chostname + i + 1 || *end != '\0') { + throw XSocketAddress(XSocketAddress::kBadPort, + m_hostname, m_port); + } + + // trim port from hostname + m_hostname.erase(i); + + // save port + m_port = static_cast(suffixPort); + } + } + + // check port number + checkPort(); +} + +CNetworkAddress::~CNetworkAddress() +{ + if (m_address != NULL) { + ARCH->closeAddr(m_address); + } +} + +CNetworkAddress& +CNetworkAddress::operator=(const CNetworkAddress& addr) +{ + CArchNetAddress newAddr = NULL; + if (addr.m_address != NULL) { + newAddr = ARCH->copyAddr(addr.m_address); + } + if (m_address != NULL) { + ARCH->closeAddr(m_address); + } + m_address = newAddr; + m_hostname = addr.m_hostname; + m_port = addr.m_port; + return *this; +} + +void +CNetworkAddress::resolve() +{ + // discard previous address + if (m_address != NULL) { + ARCH->closeAddr(m_address); + m_address = NULL; + } + + try { + // if hostname is empty then use wildcard address otherwise look + // up the name. + if (m_hostname.empty()) { + m_address = ARCH->newAnyAddr(IArchNetwork::kINET); + } + else { + m_address = ARCH->nameToAddr(m_hostname); + } + } + catch (XArchNetworkNameUnknown&) { + throw XSocketAddress(XSocketAddress::kNotFound, m_hostname, m_port); + } + catch (XArchNetworkNameNoAddress&) { + throw XSocketAddress(XSocketAddress::kNoAddress, m_hostname, m_port); + } + catch (XArchNetworkNameUnsupported&) { + throw XSocketAddress(XSocketAddress::kUnsupported, m_hostname, m_port); + } + catch (XArchNetworkName&) { + throw XSocketAddress(XSocketAddress::kUnknown, m_hostname, m_port); + } + + // set port in address + ARCH->setAddrPort(m_address, m_port); +} + +bool +CNetworkAddress::operator==(const CNetworkAddress& addr) const +{ + return ARCH->isEqualAddr(m_address, addr.m_address); +} + +bool +CNetworkAddress::operator!=(const CNetworkAddress& addr) const +{ + return !operator==(addr); +} + +bool +CNetworkAddress::isValid() const +{ + return (m_address != NULL); +} + +const CArchNetAddress& +CNetworkAddress::getAddress() const +{ + return m_address; +} + +int +CNetworkAddress::getPort() const +{ + return m_port; +} + +CString +CNetworkAddress::getHostname() const +{ + return m_hostname; +} + +void +CNetworkAddress::checkPort() +{ + // check port number + if (m_port <= 0 || m_port > 65535) { + throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port); + } +} diff --git a/lib/net/CNetworkAddress.h b/lib/net/CNetworkAddress.h new file mode 100644 index 00000000..d29b93b1 --- /dev/null +++ b/lib/net/CNetworkAddress.h @@ -0,0 +1,122 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CNETWORKADDRESS_H +#define CNETWORKADDRESS_H + +#include "CString.h" +#include "BasicTypes.h" +#include "IArchNetwork.h" + +//! Network address type +/*! +This class represents a network address. +*/ +class CNetworkAddress { +public: + /*! + Constructs the invalid address + */ + CNetworkAddress(); + + /*! + Construct the wildcard address with the given port. \c port must + not be zero. + */ + CNetworkAddress(int port); + + /*! + Construct the network address for the given \c hostname and \c port. + If \c hostname can be parsed as a numerical address then that's how + it's used, otherwise it's used as a host name. If \c hostname ends + in ":[0-9]+" then that suffix is extracted and used as the port, + overridding the port parameter. The resulting port must be a valid + port number (zero is not a valid port number) otherwise \c XSocketAddress + is thrown with an error of \c XSocketAddress::kBadPort. The hostname + is not resolved by the c'tor; use \c resolve to do that. + */ + CNetworkAddress(const CString& hostname, int port); + + CNetworkAddress(const CNetworkAddress&); + + ~CNetworkAddress(); + + CNetworkAddress& operator=(const CNetworkAddress&); + + //! @name manipulators + //@{ + + //! Resolve address + /*! + Resolves the hostname to an address. This can be done any number of + times and is done automatically by the c'tor taking a hostname. + Throws XSocketAddress if resolution is unsuccessful, after which + \c isValid returns false until the next call to this method. + */ + void resolve(); + + //@} + //! @name accessors + //@{ + + //! Check address equality + /*! + Returns true if this address is equal to \p address. + */ + bool operator==(const CNetworkAddress& address) const; + + //! Check address inequality + /*! + Returns true if this address is not equal to \p address. + */ + bool operator!=(const CNetworkAddress& address) const; + + //! Check address validity + /*! + Returns true if this is not the invalid address. + */ + bool isValid() const; + + //! Get address + /*! + Returns the address in the platform's native network address + structure. + */ + const CArchNetAddress& getAddress() const; + + //! Get port + /*! + Returns the port passed to the c'tor as a suffix to the hostname, + if that existed, otherwise as passed directly to the c'tor. + */ + int getPort() const; + + //! Get hostname + /*! + Returns the hostname passed to the c'tor sans any port suffix. + */ + CString getHostname() const; + + //@} + +private: + void checkPort(); + +private: + CArchNetAddress m_address; + CString m_hostname; + int m_port; +}; + +#endif diff --git a/lib/net/CSocketMultiplexer.cpp b/lib/net/CSocketMultiplexer.cpp new file mode 100644 index 00000000..2082730b --- /dev/null +++ b/lib/net/CSocketMultiplexer.cpp @@ -0,0 +1,359 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CSocketMultiplexer.h" +#include "ISocketMultiplexerJob.h" +#include "CCondVar.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "CLog.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "XArch.h" +#include "stdvector.h" + +// +// CSocketMultiplexer +// + +CSocketMultiplexer* CSocketMultiplexer::s_instance = NULL; + +CSocketMultiplexer::CSocketMultiplexer() : + m_mutex(new CMutex), + m_thread(NULL), + m_update(false), + m_jobsReady(new CCondVar(m_mutex, false)), + m_jobListLock(new CCondVar(m_mutex, false)), + m_jobListLockLocked(new CCondVar(m_mutex, false)), + m_jobListLocker(NULL), + m_jobListLockLocker(NULL) +{ + assert(s_instance == NULL); + + // this pointer just has to be unique and not NULL. it will + // never be dereferenced. it's used to identify cursor nodes + // in the jobs list. + m_cursorMark = reinterpret_cast(this); + + // start thread + m_thread = new CThread(new TMethodJob( + this, &CSocketMultiplexer::serviceThread)); + + s_instance = this; +} + +CSocketMultiplexer::~CSocketMultiplexer() +{ + m_thread->cancel(); + m_thread->unblockPollSocket(); + m_thread->wait(); + delete m_thread; + delete m_jobsReady; + delete m_jobListLock; + delete m_jobListLockLocked; + delete m_jobListLocker; + delete m_jobListLockLocker; + delete m_mutex; + + // clean up jobs + for (CSocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end(); ++i) { + delete *(i->second); + } + + s_instance = NULL; +} + +CSocketMultiplexer* +CSocketMultiplexer::getInstance() +{ + return s_instance; +} + +void +CSocketMultiplexer::addSocket(ISocket* socket, ISocketMultiplexerJob* job) +{ + assert(socket != NULL); + assert(job != NULL); + + // prevent other threads from locking the job list + lockJobListLock(); + + // break thread out of poll + m_thread->unblockPollSocket(); + + // lock the job list + lockJobList(); + + // insert/replace job + CSocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i == m_socketJobMap.end()) { + // we *must* put the job at the end so the order of jobs in + // the list continue to match the order of jobs in pfds in + // serviceThread(). + CJobCursor j = m_socketJobs.insert(m_socketJobs.end(), job); + m_update = true; + m_socketJobMap.insert(std::make_pair(socket, j)); + } + else { + CJobCursor j = i->second; + if (*j != job) { + delete *j; + *j = job; + } + m_update = true; + } + + // unlock the job list + unlockJobList(); +} + +void +CSocketMultiplexer::removeSocket(ISocket* socket) +{ + assert(socket != NULL); + + // prevent other threads from locking the job list + lockJobListLock(); + + // break thread out of poll + m_thread->unblockPollSocket(); + + // lock the job list + lockJobList(); + + // remove job. rather than removing it from the map we put NULL + // in the list instead so the order of jobs in the list continues + // to match the order of jobs in pfds in serviceThread(). + CSocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i != m_socketJobMap.end()) { + if (*(i->second) != NULL) { + delete *(i->second); + *(i->second) = NULL; + m_update = true; + } + } + + // unlock the job list + unlockJobList(); +} + +void +CSocketMultiplexer::serviceThread(void*) +{ + std::vector pfds; + IArchNetwork::CPollEntry pfd; + + // service the connections + for (;;) { + CThread::testCancel(); + + // wait until there are jobs to handle + { + CLock lock(m_mutex); + while (!(bool)*m_jobsReady) { + m_jobsReady->wait(); + } + } + + // lock the job list + lockJobListLock(); + lockJobList(); + + // collect poll entries + if (m_update) { + m_update = false; + pfds.clear(); + pfds.reserve(m_socketJobMap.size()); + + CJobCursor cursor = newCursor(); + CJobCursor jobCursor = nextCursor(cursor); + while (jobCursor != m_socketJobs.end()) { + ISocketMultiplexerJob* job = *jobCursor; + if (job != NULL) { + pfd.m_socket = job->getSocket(); + pfd.m_events = 0; + if (job->isReadable()) { + pfd.m_events |= IArchNetwork::kPOLLIN; + } + if (job->isWritable()) { + pfd.m_events |= IArchNetwork::kPOLLOUT; + } + pfds.push_back(pfd); + } + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + int status; + try { + // check for status + if (!pfds.empty()) { + status = ARCH->pollSocket(&pfds[0], pfds.size(), -1); + } + else { + status = 0; + } + } + catch (XArchNetwork& e) { + LOG((CLOG_WARN "error in socket multiplexer: %s", e.what().c_str())); + status = 0; + } + + if (status != 0) { + // iterate over socket jobs, invoking each and saving the + // new job. + UInt32 i = 0; + CJobCursor cursor = newCursor(); + CJobCursor jobCursor = nextCursor(cursor); + while (i < pfds.size() && jobCursor != m_socketJobs.end()) { + if (*jobCursor != NULL) { + // get poll state + unsigned short revents = pfds[i].m_revents; + bool read = ((revents & IArchNetwork::kPOLLIN) != 0); + bool write = ((revents & IArchNetwork::kPOLLOUT) != 0); + bool error = ((revents & (IArchNetwork::kPOLLERR | + IArchNetwork::kPOLLNVAL)) != 0); + + // run job + ISocketMultiplexerJob* job = *jobCursor; + ISocketMultiplexerJob* newJob = job->run(read, write, error); + + // save job, if different + if (newJob != job) { + CLock lock(m_mutex); + delete job; + *jobCursor = newJob; + m_update = true; + } + ++i; + } + + // next job + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + // delete any removed socket jobs + for (CSocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end();) { + if (*(i->second) == NULL) { + m_socketJobMap.erase(i++); + m_update = true; + } + else { + ++i; + } + } + + // unlock the job list + unlockJobList(); + } +} + +CSocketMultiplexer::CJobCursor +CSocketMultiplexer::newCursor() +{ + CLock lock(m_mutex); + return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark); +} + +CSocketMultiplexer::CJobCursor +CSocketMultiplexer::nextCursor(CJobCursor cursor) +{ + CLock lock(m_mutex); + CJobCursor j = m_socketJobs.end(); + CJobCursor i = cursor; + while (++i != m_socketJobs.end()) { + if (*i != m_cursorMark) { + // found a real job (as opposed to a cursor) + j = i; + + // move our cursor just past the job + m_socketJobs.splice(++i, m_socketJobs, cursor); + break; + } + } + return j; +} + +void +CSocketMultiplexer::deleteCursor(CJobCursor cursor) +{ + CLock lock(m_mutex); + m_socketJobs.erase(cursor); +} + +void +CSocketMultiplexer::lockJobListLock() +{ + CLock lock(m_mutex); + + // wait for the lock on the lock + while (*m_jobListLockLocked) { + m_jobListLockLocked->wait(); + } + + // take ownership of the lock on the lock + *m_jobListLockLocked = true; + m_jobListLockLocker = new CThread(CThread::getCurrentThread()); +} + +void +CSocketMultiplexer::lockJobList() +{ + CLock lock(m_mutex); + + // make sure we're the one that called lockJobListLock() + assert(*m_jobListLockLocker == CThread::getCurrentThread()); + + // wait for the job list lock + while (*m_jobListLock) { + m_jobListLock->wait(); + } + + // take ownership of the lock + *m_jobListLock = true; + m_jobListLocker = m_jobListLockLocker; + m_jobListLockLocker = NULL; + + // release the lock on the lock + *m_jobListLockLocked = false; + m_jobListLockLocked->broadcast(); +} + +void +CSocketMultiplexer::unlockJobList() +{ + CLock lock(m_mutex); + + // make sure we're the one that called lockJobList() + assert(*m_jobListLocker == CThread::getCurrentThread()); + + // release the lock + delete m_jobListLocker; + m_jobListLocker = NULL; + *m_jobListLock = false; + m_jobListLock->signal(); + + // set new jobs ready state + bool isReady = !m_socketJobMap.empty(); + if (*m_jobsReady != isReady) { + *m_jobsReady = isReady; + m_jobsReady->signal(); + } +} diff --git a/lib/net/CSocketMultiplexer.h b/lib/net/CSocketMultiplexer.h new file mode 100644 index 00000000..bb96ca70 --- /dev/null +++ b/lib/net/CSocketMultiplexer.h @@ -0,0 +1,111 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSOCKETMULTIPLEXER_H +#define CSOCKETMULTIPLEXER_H + +#include "IArchNetwork.h" +#include "stdlist.h" +#include "stdmap.h" + +template +class CCondVar; +class CMutex; +class CThread; +class ISocket; +class ISocketMultiplexerJob; + +//! Socket multiplexer +/*! +A socket multiplexer services multiple sockets simultaneously. +*/ +class CSocketMultiplexer { +public: + CSocketMultiplexer(); + ~CSocketMultiplexer(); + + //! @name manipulators + //@{ + + void addSocket(ISocket*, ISocketMultiplexerJob*); + + void removeSocket(ISocket*); + + //@} + //! @name accessors + //@{ + + // maybe belongs on ISocketMultiplexer + static CSocketMultiplexer* + getInstance(); + + //@} + +private: + // list of jobs. we use a list so we can safely iterate over it + // while other threads modify it. + typedef std::list CSocketJobs; + typedef CSocketJobs::iterator CJobCursor; + typedef std::map CSocketJobMap; + + // service sockets. the service thread will only access m_sockets + // and m_update while m_pollable and m_polling are true. all other + // threads must only modify these when m_pollable and m_polling are + // false. only the service thread sets m_polling. + void serviceThread(void*); + + // create, iterate, and destroy a cursor. a cursor is used to + // safely iterate through the job list while other threads modify + // the list. it works by inserting a dummy item in the list and + // moving that item through the list. the dummy item will never + // be removed by other edits so an iterator pointing at the item + // remains valid until we remove the dummy item in deleteCursor(). + // nextCursor() finds the next non-dummy item, moves our dummy + // item just past it, and returns an iterator for the non-dummy + // item. all cursor calls lock the mutex for their duration. + CJobCursor newCursor(); + CJobCursor nextCursor(CJobCursor); + void deleteCursor(CJobCursor); + + // lock out locking the job list. this blocks if another thread + // has already locked out locking. once it returns, only the + // calling thread will be able to lock the job list after any + // current lock is released. + void lockJobListLock(); + + // lock the job list. this blocks if the job list is already + // locked. the calling thread must have called requestJobLock. + void lockJobList(); + + // unlock the job list and the lock out on locking. + void unlockJobList(); + +private: + CMutex* m_mutex; + CThread* m_thread; + bool m_update; + CCondVar* m_jobsReady; + CCondVar* m_jobListLock; + CCondVar* m_jobListLockLocked; + CThread* m_jobListLocker; + CThread* m_jobListLockLocker; + + CSocketJobs m_socketJobs; + CSocketJobMap m_socketJobMap; + ISocketMultiplexerJob* m_cursorMark; + + static CSocketMultiplexer* s_instance; +}; + +#endif diff --git a/lib/net/CTCPListenSocket.cpp b/lib/net/CTCPListenSocket.cpp new file mode 100644 index 00000000..938bf95a --- /dev/null +++ b/lib/net/CTCPListenSocket.cpp @@ -0,0 +1,134 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CTCPListenSocket.h" +#include "CNetworkAddress.h" +#include "CSocketMultiplexer.h" +#include "CTCPSocket.h" +#include "TSocketMultiplexerMethodJob.h" +#include "XSocket.h" +#include "XIO.h" +#include "CLock.h" +#include "CMutex.h" +#include "IEventQueue.h" +#include "CArch.h" +#include "XArch.h" + +// +// CTCPListenSocket +// + +CTCPListenSocket::CTCPListenSocket() +{ + m_mutex = new CMutex; + try { + m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM); + } + catch (XArchNetwork& e) { + throw XSocketCreate(e.what()); + } +} + +CTCPListenSocket::~CTCPListenSocket() +{ + try { + if (m_socket != NULL) { + CSocketMultiplexer::getInstance()->removeSocket(this); + ARCH->closeSocket(m_socket); + } + } + catch (...) { + // ignore + } + delete m_mutex; +} + +void +CTCPListenSocket::bind(const CNetworkAddress& addr) +{ + try { + CLock lock(m_mutex); + ARCH->setReuseAddrOnSocket(m_socket, true); + ARCH->bindSocket(m_socket, addr.getAddress()); + ARCH->listenOnSocket(m_socket); + CSocketMultiplexer::getInstance()->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &CTCPListenSocket::serviceListening, + m_socket, true, false)); + } + catch (XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void +CTCPListenSocket::close() +{ + CLock lock(m_mutex); + if (m_socket == NULL) { + throw XIOClosed(); + } + try { + CSocketMultiplexer::getInstance()->removeSocket(this); + ARCH->closeSocket(m_socket); + m_socket = NULL; + } + catch (XArchNetwork& e) { + throw XSocketIOClose(e.what()); + } +} + +void* +CTCPListenSocket::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +IDataSocket* +CTCPListenSocket::accept() +{ + try { + IDataSocket* socket = + new CTCPSocket(ARCH->acceptSocket(m_socket, NULL)); + if (socket != NULL) { + CSocketMultiplexer::getInstance()->addSocket(this, + new TSocketMultiplexerMethodJob( + this, &CTCPListenSocket::serviceListening, + m_socket, true, false)); + } + return socket; + } + catch (XArchNetwork&) { + return NULL; + } +} + +ISocketMultiplexerJob* +CTCPListenSocket::serviceListening(ISocketMultiplexerJob* job, + bool read, bool, bool error) +{ + if (error) { + close(); + return NULL; + } + if (read) { + EVENTQUEUE->addEvent(CEvent(getConnectingEvent(), this, NULL)); + // stop polling on this socket until the client accepts + return NULL; + } + return job; +} diff --git a/lib/net/CTCPListenSocket.h b/lib/net/CTCPListenSocket.h new file mode 100644 index 00000000..5321b8db --- /dev/null +++ b/lib/net/CTCPListenSocket.h @@ -0,0 +1,51 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTCPLISTENSOCKET_H +#define CTCPLISTENSOCKET_H + +#include "IListenSocket.h" +#include "IArchNetwork.h" + +class CMutex; +class ISocketMultiplexerJob; + +//! TCP listen socket +/*! +A listen socket using TCP. +*/ +class CTCPListenSocket : public IListenSocket { +public: + CTCPListenSocket(); + ~CTCPListenSocket(); + + // ISocket overrides + virtual void bind(const CNetworkAddress&); + virtual void close(); + virtual void* getEventTarget() const; + + // IListenSocket overrides + virtual IDataSocket* accept(); + +private: + ISocketMultiplexerJob* + serviceListening(ISocketMultiplexerJob*, + bool, bool, bool); + +private: + CArchSocket m_socket; + CMutex* m_mutex; +}; + +#endif diff --git a/lib/net/CTCPSocket.cpp b/lib/net/CTCPSocket.cpp new file mode 100644 index 00000000..c44b41ea --- /dev/null +++ b/lib/net/CTCPSocket.cpp @@ -0,0 +1,542 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CTCPSocket.h" +#include "CNetworkAddress.h" +#include "CSocketMultiplexer.h" +#include "TSocketMultiplexerMethodJob.h" +#include "XSocket.h" +#include "CLock.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "IEventJob.h" +#include "CArch.h" +#include "XArch.h" +#include + +// +// CTCPSocket +// + +CTCPSocket::CTCPSocket() : + m_mutex(), + m_flushed(&m_mutex, true) +{ + try { + m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM); + } + catch (XArchNetwork& e) { + throw XSocketCreate(e.what()); + } + + init(); +} + +CTCPSocket::CTCPSocket(CArchSocket socket) : + m_mutex(), + m_socket(socket), + m_flushed(&m_mutex, true) +{ + assert(m_socket != NULL); + + // socket starts in connected state + init(); + onConnected(); + setJob(newJob()); +} + +CTCPSocket::~CTCPSocket() +{ + try { + close(); + } + catch (...) { + // ignore + } +} + +void +CTCPSocket::bind(const CNetworkAddress& addr) +{ + try { + ARCH->bindSocket(m_socket, addr.getAddress()); + } + catch (XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void +CTCPSocket::close() +{ + // remove ourself from the multiplexer + setJob(NULL); + + CLock lock(&m_mutex); + + // clear buffers and enter disconnected state + if (m_connected) { + sendEvent(getDisconnectedEvent()); + } + onDisconnected(); + + // close the socket + if (m_socket != NULL) { + CArchSocket socket = m_socket; + m_socket = NULL; + try { + ARCH->closeSocket(socket); + } + catch (XArchNetwork& e) { + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what().c_str())); + } + } +} + +void* +CTCPSocket::getEventTarget() const +{ + return const_cast(reinterpret_cast(this)); +} + +UInt32 +CTCPSocket::read(void* buffer, UInt32 n) +{ + // copy data directly from our input buffer + CLock lock(&m_mutex); + UInt32 size = m_inputBuffer.getSize(); + if (n > size) { + n = size; + } + if (buffer != NULL && n != 0) { + memcpy(buffer, m_inputBuffer.peek(n), n); + } + m_inputBuffer.pop(n); + + // if no more data and we cannot read or write then send disconnected + if (n > 0 && m_inputBuffer.getSize() == 0 && !m_readable && !m_writable) { + sendEvent(getDisconnectedEvent()); + m_connected = false; + } + + return n; +} + +void +CTCPSocket::write(const void* buffer, UInt32 n) +{ + bool wasEmpty; + { + CLock lock(&m_mutex); + + // must not have shutdown output + if (!m_writable) { + sendEvent(getOutputErrorEvent()); + return; + } + + // ignore empty writes + if (n == 0) { + return; + } + + // copy data to the output buffer + wasEmpty = (m_outputBuffer.getSize() == 0); + m_outputBuffer.write(buffer, n); + + // there's data to write + m_flushed = false; + } + + // make sure we're waiting to write + if (wasEmpty) { + setJob(newJob()); + } +} + +void +CTCPSocket::flush() +{ + CLock lock(&m_mutex); + while (m_flushed == false) { + m_flushed.wait(); + } +} + +void +CTCPSocket::shutdownInput() +{ + bool useNewJob = false; + { + CLock lock(&m_mutex); + + // shutdown socket for reading + try { + ARCH->closeSocketForRead(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for reading + if (m_readable) { + sendEvent(getInputShutdownEvent()); + onInputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob()); + } +} + +void +CTCPSocket::shutdownOutput() +{ + bool useNewJob = false; + { + CLock lock(&m_mutex); + + // shutdown socket for writing + try { + ARCH->closeSocketForWrite(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for writing + if (m_writable) { + sendEvent(getOutputShutdownEvent()); + onOutputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob()); + } +} + +bool +CTCPSocket::isReady() const +{ + CLock lock(&m_mutex); + return (m_inputBuffer.getSize() > 0); +} + +UInt32 +CTCPSocket::getSize() const +{ + CLock lock(&m_mutex); + return m_inputBuffer.getSize(); +} + +void +CTCPSocket::connect(const CNetworkAddress& addr) +{ + { + CLock lock(&m_mutex); + + // fail on attempts to reconnect + if (m_socket == NULL || m_connected) { + sendConnectionFailedEvent("busy"); + return; + } + + try { + if (ARCH->connectSocket(m_socket, addr.getAddress())) { + sendEvent(getConnectedEvent()); + onConnected(); + } + else { + // connection is in progress + m_writable = true; + } + } + catch (XArchNetwork& e) { + throw XSocketConnect(e.what()); + } + } + setJob(newJob()); +} + +void +CTCPSocket::init() +{ + // default state + m_connected = false; + m_readable = false; + m_writable = false; + + try { + // turn off Nagle algorithm. we send lots of very short messages + // that should be sent without (much) delay. for example, the + // mouse motion messages are much less useful if they're delayed. + ARCH->setNoDelayOnSocket(m_socket, true); + } + catch (XArchNetwork& e) { + try { + ARCH->closeSocket(m_socket); + m_socket = NULL; + } + catch (XArchNetwork&) { + // ignore + } + throw XSocketCreate(e.what()); + } +} + +void +CTCPSocket::setJob(ISocketMultiplexerJob* job) +{ + // multiplexer will delete the old job + if (job == NULL) { + CSocketMultiplexer::getInstance()->removeSocket(this); + } + else { + CSocketMultiplexer::getInstance()->addSocket(this, job); + } +} + +ISocketMultiplexerJob* +CTCPSocket::newJob() +{ + // note -- must have m_mutex locked on entry + + if (m_socket == NULL) { + return NULL; + } + else if (!m_connected) { + assert(!m_readable); + if (!(m_readable || m_writable)) { + return NULL; + } + return new TSocketMultiplexerMethodJob( + this, &CTCPSocket::serviceConnecting, + m_socket, m_readable, m_writable); + } + else { + if (!(m_readable || (m_writable && (m_outputBuffer.getSize() > 0)))) { + return NULL; + } + return new TSocketMultiplexerMethodJob( + this, &CTCPSocket::serviceConnected, + m_socket, m_readable, + m_writable && (m_outputBuffer.getSize() > 0)); + } +} + +void +CTCPSocket::sendConnectionFailedEvent(const char* msg) +{ + CConnectionFailedInfo* info = (CConnectionFailedInfo*)malloc( + sizeof(CConnectionFailedInfo) + strlen(msg)); + strcpy(info->m_what, msg); + EVENTQUEUE->addEvent(CEvent(getConnectionFailedEvent(), + getEventTarget(), info)); +} + +void +CTCPSocket::sendEvent(CEvent::Type type) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), NULL)); +} + +void +CTCPSocket::onConnected() +{ + m_connected = true; + m_readable = true; + m_writable = true; +} + +void +CTCPSocket::onInputShutdown() +{ + m_inputBuffer.pop(m_inputBuffer.getSize()); + m_readable = false; +} + +void +CTCPSocket::onOutputShutdown() +{ + m_outputBuffer.pop(m_outputBuffer.getSize()); + m_writable = false; + + // we're now flushed + m_flushed = true; + m_flushed.broadcast(); +} + +void +CTCPSocket::onDisconnected() +{ + // disconnected + onInputShutdown(); + onOutputShutdown(); + m_connected = false; +} + +ISocketMultiplexerJob* +CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + CLock lock(&m_mutex); + + // should only check for errors if error is true but checking a new + // socket (and a socket that's connecting should be new) for errors + // should be safe and Mac OS X appears to have a bug where a + // non-blocking stream socket that fails to connect immediately is + // reported by select as being writable (i.e. connected) even when + // the connection has failed. this is easily demonstrated on OS X + // 10.3.4 by starting a synergy client and telling to connect to + // another system that's not running a synergy server. it will + // claim to have connected then quickly disconnect (i guess because + // read returns 0 bytes). unfortunately, synergy attempts to + // reconnect immediately, the process repeats and we end up + // spinning the CPU. luckily, OS X does set SO_ERROR on the + // socket correctly when the connection has failed so checking for + // errors works. (curiously, sometimes OS X doesn't report + // connection refused. when that happens it at least doesn't + // report the socket as being writable so synergy is able to time + // out the attempt.) + if (error || true) { + try { + // connection may have failed or succeeded + ARCH->throwErrorOnSocket(m_socket); + } + catch (XArchNetwork& e) { + sendConnectionFailedEvent(e.what().c_str()); + onDisconnected(); + return newJob(); + } + } + + if (write) { + sendEvent(getConnectedEvent()); + onConnected(); + return newJob(); + } + + return job; +} + +ISocketMultiplexerJob* +CTCPSocket::serviceConnected(ISocketMultiplexerJob* job, + bool read, bool write, bool error) +{ + CLock lock(&m_mutex); + + if (error) { + sendEvent(getDisconnectedEvent()); + onDisconnected(); + return newJob(); + } + + bool needNewJob = false; + + if (write) { + try { + // write data + UInt32 n = m_outputBuffer.getSize(); + const void* buffer = m_outputBuffer.peek(n); + n = (UInt32)ARCH->writeSocket(m_socket, buffer, n); + + // discard written data + if (n > 0) { + m_outputBuffer.pop(n); + if (m_outputBuffer.getSize() == 0) { + sendEvent(getOutputFlushedEvent()); + m_flushed = true; + m_flushed.broadcast(); + needNewJob = true; + } + } + } + catch (XArchNetworkShutdown&) { + // remote read end of stream hungup. our output side + // has therefore shutdown. + onOutputShutdown(); + sendEvent(getOutputShutdownEvent()); + if (!m_readable && m_inputBuffer.getSize() == 0) { + sendEvent(getDisconnectedEvent()); + m_connected = false; + } + needNewJob = true; + } + catch (XArchNetworkDisconnected&) { + // stream hungup + onDisconnected(); + sendEvent(getDisconnectedEvent()); + needNewJob = true; + } + catch (XArchNetwork& e) { + // other write error + LOG((CLOG_WARN "error writing socket: %s", e.what().c_str())); + onDisconnected(); + sendEvent(getOutputErrorEvent()); + sendEvent(getDisconnectedEvent()); + needNewJob = true; + } + } + + if (read && m_readable) { + try { + UInt8 buffer[4096]; + size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + if (n > 0) { + bool wasEmpty = (m_inputBuffer.getSize() == 0); + + // slurp up as much as possible + do { + m_inputBuffer.write(buffer, n); + n = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + } while (n > 0); + + // send input ready if input buffer was empty + if (wasEmpty) { + sendEvent(getInputReadyEvent()); + } + } + else { + // remote write end of stream hungup. our input side + // has therefore shutdown but don't flush our buffer + // since there's still data to be read. + sendEvent(getInputShutdownEvent()); + if (!m_writable && m_inputBuffer.getSize() == 0) { + sendEvent(getDisconnectedEvent()); + m_connected = false; + } + m_readable = false; + needNewJob = true; + } + } + catch (XArchNetworkDisconnected&) { + // stream hungup + sendEvent(getDisconnectedEvent()); + onDisconnected(); + needNewJob = true; + } + catch (XArchNetwork& e) { + // ignore other read error + LOG((CLOG_WARN "error reading socket: %s", e.what().c_str())); + } + } + + return needNewJob ? newJob() : job; +} diff --git a/lib/net/CTCPSocket.h b/lib/net/CTCPSocket.h new file mode 100644 index 00000000..aa1df8c1 --- /dev/null +++ b/lib/net/CTCPSocket.h @@ -0,0 +1,86 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTCPSOCKET_H +#define CTCPSOCKET_H + +#include "IDataSocket.h" +#include "CStreamBuffer.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "IArchNetwork.h" + +class CMutex; +class CThread; +class ISocketMultiplexerJob; + +//! TCP data socket +/*! +A data socket using TCP. +*/ +class CTCPSocket : public IDataSocket { +public: + CTCPSocket(); + CTCPSocket(CArchSocket); + ~CTCPSocket(); + + // ISocket overrides + virtual void bind(const CNetworkAddress&); + virtual void close(); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual bool isReady() const; + virtual UInt32 getSize() const; + + // IDataSocket overrides + virtual void connect(const CNetworkAddress&); + +private: + void init(); + + void setJob(ISocketMultiplexerJob*); + ISocketMultiplexerJob* newJob(); + void sendConnectionFailedEvent(const char*); + void sendEvent(CEvent::Type); + + void onConnected(); + void onInputShutdown(); + void onOutputShutdown(); + void onDisconnected(); + + ISocketMultiplexerJob* + serviceConnecting(ISocketMultiplexerJob*, + bool, bool, bool); + ISocketMultiplexerJob* + serviceConnected(ISocketMultiplexerJob*, + bool, bool, bool); + +private: + CMutex m_mutex; + CArchSocket m_socket; + CStreamBuffer m_inputBuffer; + CStreamBuffer m_outputBuffer; + CCondVar m_flushed; + bool m_connected; + bool m_readable; + bool m_writable; +}; + +#endif diff --git a/lib/net/CTCPSocketFactory.cpp b/lib/net/CTCPSocketFactory.cpp new file mode 100644 index 00000000..f590efa0 --- /dev/null +++ b/lib/net/CTCPSocketFactory.cpp @@ -0,0 +1,43 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CTCPSocketFactory.h" +#include "CTCPSocket.h" +#include "CTCPListenSocket.h" + +// +// CTCPSocketFactory +// + +CTCPSocketFactory::CTCPSocketFactory() +{ + // do nothing +} + +CTCPSocketFactory::~CTCPSocketFactory() +{ + // do nothing +} + +IDataSocket* +CTCPSocketFactory::create() const +{ + return new CTCPSocket; +} + +IListenSocket* +CTCPSocketFactory::createListen() const +{ + return new CTCPListenSocket; +} diff --git a/lib/net/CTCPSocketFactory.h b/lib/net/CTCPSocketFactory.h new file mode 100644 index 00000000..2b946d19 --- /dev/null +++ b/lib/net/CTCPSocketFactory.h @@ -0,0 +1,31 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CTCPSOCKETFACTORY_H +#define CTCPSOCKETFACTORY_H + +#include "ISocketFactory.h" + +//! Socket factory for TCP sockets +class CTCPSocketFactory : public ISocketFactory { +public: + CTCPSocketFactory(); + virtual ~CTCPSocketFactory(); + + // ISocketFactory overrides + virtual IDataSocket* create() const; + virtual IListenSocket* createListen() const; +}; + +#endif diff --git a/lib/net/IDataSocket.cpp b/lib/net/IDataSocket.cpp new file mode 100644 index 00000000..dc4b08ed --- /dev/null +++ b/lib/net/IDataSocket.cpp @@ -0,0 +1,51 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IDataSocket.h" + +// +// IDataSocket +// + +CEvent::Type IDataSocket::s_connectedEvent = CEvent::kUnknown; +CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown; + +CEvent::Type +IDataSocket::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "IDataSocket::connected"); +} + +CEvent::Type +IDataSocket::getConnectionFailedEvent() +{ + return CEvent::registerTypeOnce(s_failedEvent, + "IDataSocket::failed"); +} + +void +IDataSocket::close() +{ + // this is here to work around a VC++6 bug. see the header file. + assert(0 && "bad call"); +} + +void* +IDataSocket::getEventTarget() const +{ + // this is here to work around a VC++6 bug. see the header file. + assert(0 && "bad call"); + return NULL; +} diff --git a/lib/net/IDataSocket.h b/lib/net/IDataSocket.h new file mode 100644 index 00000000..d760d4ab --- /dev/null +++ b/lib/net/IDataSocket.h @@ -0,0 +1,90 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IDATASOCKET_H +#define IDATASOCKET_H + +#include "ISocket.h" +#include "IStream.h" + +//! Data stream socket interface +/*! +This interface defines the methods common to all network sockets that +represent a full-duplex data stream. +*/ +class IDataSocket : public ISocket, public IStream { +public: + class CConnectionFailedInfo { + public: + // pointer to a string describing the failure + char m_what[1]; + }; + + //! @name manipulators + //@{ + + //! Connect socket + /*! + Attempt to connect to a remote endpoint. This returns immediately + and sends a connected event when successful or a connection failed + event when it fails. The stream acts as if shutdown for input and + output until the stream connects. + */ + virtual void connect(const CNetworkAddress&) = 0; + + //@} + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the socket connected event type. A socket sends this + event when a remote connection has been established. + */ + static CEvent::Type getConnectedEvent(); + + //! Get connection failed event type + /*! + Returns the socket connection failed event type. A socket sends + this event when an attempt to connect to a remote port has failed. + The data is a pointer to a CConnectionFailedInfo. + */ + static CEvent::Type getConnectionFailedEvent(); + + //@} + + // ISocket overrides + // close() and getEventTarget() aren't pure to work around a bug + // in VC++6. it claims the methods are unused locals and warns + // that it's removing them. it's presumably tickled by inheriting + // methods with identical signatures from both superclasses. + virtual void bind(const CNetworkAddress&) = 0; + virtual void close(); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n) = 0; + virtual void write(const void* buffer, UInt32 n) = 0; + virtual void flush() = 0; + virtual void shutdownInput() = 0; + virtual void shutdownOutput() = 0; + virtual bool isReady() const = 0; + virtual UInt32 getSize() const = 0; + +private: + static CEvent::Type s_connectedEvent; + static CEvent::Type s_failedEvent; +}; + +#endif diff --git a/lib/net/IListenSocket.cpp b/lib/net/IListenSocket.cpp new file mode 100644 index 00000000..20dbc9a4 --- /dev/null +++ b/lib/net/IListenSocket.cpp @@ -0,0 +1,28 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IListenSocket.h" + +// +// IListenSocket +// + +CEvent::Type IListenSocket::s_connectingEvent = CEvent::kUnknown; + +CEvent::Type +IListenSocket::getConnectingEvent() +{ + return CEvent::registerTypeOnce(s_connectingEvent, + "IListenSocket::connecting"); +} diff --git a/lib/net/IListenSocket.h b/lib/net/IListenSocket.h new file mode 100644 index 00000000..894234f4 --- /dev/null +++ b/lib/net/IListenSocket.h @@ -0,0 +1,62 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ILISTENSOCKET_H +#define ILISTENSOCKET_H + +#include "ISocket.h" + +class IDataSocket; + +//! Listen socket interface +/*! +This interface defines the methods common to all network sockets that +listen for incoming connections. +*/ +class IListenSocket : public ISocket { +public: + //! @name manipulators + //@{ + + //! Accept connection + /*! + Accept a connection, returning a socket representing the full-duplex + data stream. Returns NULL if no socket is waiting to be accepted. + This is only valid after a call to \c bind(). + */ + virtual IDataSocket* accept() = 0; + + //@} + //! @name accessors + //@{ + + //! Get connecting event type + /*! + Returns the socket connecting event type. A socket sends this + event when a remote connection is waiting to be accepted. + */ + static CEvent::Type getConnectingEvent(); + + //@} + + // ISocket overrides + virtual void bind(const CNetworkAddress&) = 0; + virtual void close() = 0; + virtual void* getEventTarget() const = 0; + +private: + static CEvent::Type s_connectingEvent; +}; + +#endif diff --git a/lib/net/ISocket.cpp b/lib/net/ISocket.cpp new file mode 100644 index 00000000..a9aaf1ac --- /dev/null +++ b/lib/net/ISocket.cpp @@ -0,0 +1,28 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ISocket.h" + +// +// ISocket +// + +CEvent::Type ISocket::s_disconnectedEvent = CEvent::kUnknown; + +CEvent::Type +ISocket::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "ISocket::disconnected"); +} diff --git a/lib/net/ISocket.h b/lib/net/ISocket.h new file mode 100644 index 00000000..4452e023 --- /dev/null +++ b/lib/net/ISocket.h @@ -0,0 +1,69 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISOCKET_H +#define ISOCKET_H + +#include "IInterface.h" +#include "CEvent.h" + +class CNetworkAddress; + +//! Generic socket interface +/*! +This interface defines the methods common to all network sockets. +Generated events use \c this as the target. +*/ +class ISocket : public IInterface { +public: + //! @name manipulators + //@{ + + //! Bind socket to address + /*! + Binds the socket to a particular address. + */ + virtual void bind(const CNetworkAddress&) = 0; + + //! Close socket + /*! + Closes the socket. This should flush the output stream. + */ + virtual void close() = 0; + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this socket. + */ + virtual void* getEventTarget() const = 0; + + //! Get disconnected event type + /*! + Returns the socket disconnected event type. A socket sends this + event when the remote side of the socket has disconnected or + shutdown both input and output. + */ + static CEvent::Type getDisconnectedEvent(); + + //@} + +private: + static CEvent::Type s_disconnectedEvent; +}; + +#endif diff --git a/lib/net/ISocketFactory.h b/lib/net/ISocketFactory.h new file mode 100644 index 00000000..dbf9ae26 --- /dev/null +++ b/lib/net/ISocketFactory.h @@ -0,0 +1,42 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISOCKETFACTORY_H +#define ISOCKETFACTORY_H + +#include "IInterface.h" + +class IDataSocket; +class IListenSocket; + +//! Socket factory +/*! +This interface defines the methods common to all factories used to +create sockets. +*/ +class ISocketFactory : public IInterface { +public: + //! @name accessors + //@{ + + //! Create data socket + virtual IDataSocket* create() const = 0; + + //! Create listen socket + virtual IListenSocket* createListen() const = 0; + + //@} +}; + +#endif diff --git a/lib/net/ISocketMultiplexerJob.h b/lib/net/ISocketMultiplexerJob.h new file mode 100644 index 00000000..ac722326 --- /dev/null +++ b/lib/net/ISocketMultiplexerJob.h @@ -0,0 +1,75 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISOCKETMULTIPLEXERJOB_H +#define ISOCKETMULTIPLEXERJOB_H + +#include "IArchNetwork.h" +#include "IInterface.h" + +//! Socket multiplexer job +/*! +A socket multiplexer job handles events on a socket. +*/ +class ISocketMultiplexerJob : public IInterface { +public: + //! @name manipulators + //@{ + + //! Handle socket event + /*! + Called by a socket multiplexer when the socket becomes readable, + writable, or has an error. It should return itself if the same + job can continue to service events, a new job if the socket must + be serviced differently, or NULL if the socket should no longer + be serviced. The socket is readable if \p readable is true, + writable if \p writable is true, and in error if \p error is + true. + + This call must not attempt to directly change the job for this + socket by calling \c addSocket() or \c removeSocket() on the + multiplexer. It must instead return the new job. It can, + however, add or remove jobs for other sockets. + */ + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error) = 0; + + //@} + //! @name accessors + //@{ + + //! Get the socket + /*! + Return the socket to multiplex + */ + virtual CArchSocket getSocket() const = 0; + + //! Check for interest in readability + /*! + Return true if the job is interested in being run if the socket + becomes readable. + */ + virtual bool isReadable() const = 0; + + //! Check for interest in writability + /*! + Return true if the job is interested in being run if the socket + becomes writable. + */ + virtual bool isWritable() const = 0; + + //@} +}; + +#endif diff --git a/lib/net/Makefile.am b/lib/net/Makefile.am new file mode 100644 index 00000000..b49fe363 --- /dev/null +++ b/lib/net/Makefile.am @@ -0,0 +1,54 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libnet.a +libnet_a_SOURCES = \ + CNetworkAddress.cpp \ + CSocketMultiplexer.cpp \ + CTCPListenSocket.cpp \ + CTCPSocket.cpp \ + CTCPSocketFactory.cpp \ + IDataSocket.cpp \ + IListenSocket.cpp \ + ISocket.cpp \ + XSocket.cpp \ + CNetworkAddress.h \ + CSocketMultiplexer.h \ + CTCPListenSocket.h \ + CTCPSocket.h \ + CTCPSocketFactory.h \ + IDataSocket.h \ + IListenSocket.h \ + ISocket.h \ + ISocketFactory.h \ + ISocketMultiplexerJob.h \ + TSocketMultiplexerMethodJob.h \ + XSocket.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + $(NULL) diff --git a/lib/net/Makefile.win b/lib/net/Makefile.win new file mode 100644 index 00000000..d4cd357c --- /dev/null +++ b/lib/net/Makefile.win @@ -0,0 +1,74 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_NET_SRC = lib\net +LIB_NET_DST = $(BUILD_DST)\$(LIB_NET_SRC) +LIB_NET_LIB = "$(LIB_NET_DST)\net.lib" +LIB_NET_CPP = \ + "CNetworkAddress.cpp" \ + "CSocketMultiplexer.cpp" \ + "CTCPListenSocket.cpp" \ + "CTCPSocket.cpp" \ + "CTCPSocketFactory.cpp" \ + "IDataSocket.cpp" \ + "IListenSocket.cpp" \ + "ISocket.cpp" \ + "XSocket.cpp" \ + $(NULL) +LIB_NET_OBJ = \ + "$(LIB_NET_DST)\CNetworkAddress.obj" \ + "$(LIB_NET_DST)\CSocketMultiplexer.obj" \ + "$(LIB_NET_DST)\CTCPListenSocket.obj" \ + "$(LIB_NET_DST)\CTCPSocket.obj" \ + "$(LIB_NET_DST)\CTCPSocketFactory.obj" \ + "$(LIB_NET_DST)\IDataSocket.obj" \ + "$(LIB_NET_DST)\IListenSocket.obj" \ + "$(LIB_NET_DST)\ISocket.obj" \ + "$(LIB_NET_DST)\XSocket.obj" \ + $(NULL) +LIB_NET_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_NET_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_NET_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_NET_LIB) + +# Dependency rules +$(LIB_NET_OBJ): $(AUTODEP) +!if EXIST($(LIB_NET_DST)\deps.mak) +!include $(LIB_NET_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_NET_SRC)\}.cpp{$(LIB_NET_DST)\}.obj:: +!else +{$(LIB_NET_SRC)\}.cpp{$(LIB_NET_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_NET_SRC) + -@$(MKDIR) $(LIB_NET_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_NET_INC) \ + /Fo$(LIB_NET_DST)\ \ + /Fd$(LIB_NET_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_NET_SRC) $(LIB_NET_DST) +$(LIB_NET_LIB): $(LIB_NET_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_NET_SRC) $(LIB_NET_DST) $(**:.obj=.d) diff --git a/lib/net/TSocketMultiplexerMethodJob.h b/lib/net/TSocketMultiplexerMethodJob.h new file mode 100644 index 00000000..992885c4 --- /dev/null +++ b/lib/net/TSocketMultiplexerMethodJob.h @@ -0,0 +1,108 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TSOCKERMULTIPLEXERMETHODJOB_H +#define TSOCKERMULTIPLEXERMETHODJOB_H + +#include "ISocketMultiplexerJob.h" +#include "CArch.h" + +//! Use a method as a socket multiplexer job +/*! +A socket multiplexer job class that invokes a member function. +*/ +template +class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob { +public: + typedef ISocketMultiplexerJob* + (T::*Method)(ISocketMultiplexerJob*, bool, bool, bool); + + //! run() invokes \c object->method(arg) + TSocketMultiplexerMethodJob(T* object, Method method, + CArchSocket socket, bool readable, bool writeable); + virtual ~TSocketMultiplexerMethodJob(); + + // IJob overrides + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error); + virtual CArchSocket getSocket() const; + virtual bool isReadable() const; + virtual bool isWritable() const; + +private: + T* m_object; + Method m_method; + CArchSocket m_socket; + bool m_readable; + bool m_writable; + void* m_arg; +}; + +template +inline +TSocketMultiplexerMethodJob::TSocketMultiplexerMethodJob(T* object, + Method method, CArchSocket socket, + bool readable, bool writable) : + m_object(object), + m_method(method), + m_socket(ARCH->copySocket(socket)), + m_readable(readable), + m_writable(writable) +{ + // do nothing +} + +template +inline +TSocketMultiplexerMethodJob::~TSocketMultiplexerMethodJob() +{ + ARCH->closeSocket(m_socket); +} + +template +inline +ISocketMultiplexerJob* +TSocketMultiplexerMethodJob::run(bool read, bool write, bool error) +{ + if (m_object != NULL) { + return (m_object->*m_method)(this, read, write, error); + } + return NULL; +} + +template +inline +CArchSocket +TSocketMultiplexerMethodJob::getSocket() const +{ + return m_socket; +} + +template +inline +bool +TSocketMultiplexerMethodJob::isReadable() const +{ + return m_readable; +} + +template +inline +bool +TSocketMultiplexerMethodJob::isWritable() const +{ + return m_writable; +} + +#endif diff --git a/lib/net/XSocket.cpp b/lib/net/XSocket.cpp new file mode 100644 index 00000000..8471538a --- /dev/null +++ b/lib/net/XSocket.cpp @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XSocket.h" +#include "CStringUtil.h" + +// +// XSocketAddress +// + +XSocketAddress::XSocketAddress(EError error, + const CString& hostname, int port) throw() : + m_error(error), + m_hostname(hostname), + m_port(port) +{ + // do nothing +} + +XSocketAddress::EError +XSocketAddress::getError() const throw() +{ + return m_error; +} + +CString +XSocketAddress::getHostname() const throw() +{ + return m_hostname; +} + +int +XSocketAddress::getPort() const throw() +{ + return m_port; +} + +CString +XSocketAddress::getWhat() const throw() +{ + static const char* s_errorID[] = { + "XSocketAddressUnknown", + "XSocketAddressNotFound", + "XSocketAddressNoAddress", + "XSocketAddressUnsupported", + "XSocketAddressBadPort" + }; + static const char* s_errorMsg[] = { + "unknown error for: %{1}:%{2}", + "address not found for: %{1}", + "no address for: %{1}", + "unsupported address for: %{1}", + "invalid port" // m_port may not be set to the bad port + }; + return format(s_errorID[m_error], s_errorMsg[m_error], + m_hostname.c_str(), + CStringUtil::print("%d", m_port).c_str()); +} + + +// +// XSocketIOClose +// + +CString +XSocketIOClose::getWhat() const throw() +{ + return format("XSocketIOClose", "close: %{1}", what()); +} + + +// +// XSocketBind +// + +CString +XSocketBind::getWhat() const throw() +{ + return format("XSocketBind", "cannot bind address: %{1}", what()); +} + + +// +// XSocketConnect +// + +CString +XSocketConnect::getWhat() const throw() +{ + return format("XSocketConnect", "cannot connect socket: %{1}", what()); +} + + +// +// XSocketCreate +// + +CString +XSocketCreate::getWhat() const throw() +{ + return format("XSocketCreate", "cannot create socket: %{1}", what()); +} diff --git a/lib/net/XSocket.h b/lib/net/XSocket.h new file mode 100644 index 00000000..f84285b9 --- /dev/null +++ b/lib/net/XSocket.h @@ -0,0 +1,96 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XSOCKET_H +#define XSOCKET_H + +#include "XIO.h" +#include "XBase.h" +#include "CString.h" +#include "BasicTypes.h" + +//! Generic socket exception +XBASE_SUBCLASS(XSocket, XBase); + +//! Socket bad address exception +/*! +Thrown when attempting to create an invalid network address. +*/ +class XSocketAddress : public XSocket { +public: + //! Failure codes + enum EError { + kUnknown, //!< Unknown error + kNotFound, //!< The hostname is unknown + kNoAddress, //!< The hostname is valid but has no IP address + kUnsupported, //!< The hostname is valid but has no supported address + kBadPort //!< The port is invalid + }; + + XSocketAddress(EError, const CString& hostname, int port) throw(); + + //! @name accessors + //@{ + + //! Get the error code + EError getError() const throw(); + //! Get the hostname + CString getHostname() const throw(); + //! Get the port + int getPort() const throw(); + + //@} + +protected: + // XBase overrides + virtual CString getWhat() const throw(); + +private: + EError m_error; + CString m_hostname; + int m_port; +}; + +//! I/O closing exception +/*! +Thrown if a stream cannot be closed. +*/ +XBASE_SUBCLASS_FORMAT(XSocketIOClose, XIOClose); + +//! Socket cannot bind address exception +/*! +Thrown when a socket cannot be bound to an address. +*/ +XBASE_SUBCLASS_FORMAT(XSocketBind, XSocket); + +//! Socket address in use exception +/*! +Thrown when a socket cannot be bound to an address because the address +is already in use. +*/ +XBASE_SUBCLASS(XSocketAddressInUse, XSocketBind); + +//! Cannot connect socket exception +/*! +Thrown when a socket cannot connect to a remote endpoint. +*/ +XBASE_SUBCLASS_FORMAT(XSocketConnect, XSocket); + +//! Cannot create socket exception +/*! +Thrown when a socket cannot be created (by the operating system). +*/ +XBASE_SUBCLASS_FORMAT(XSocketCreate, XSocket); + +#endif diff --git a/lib/platform/CMSWindowsClipboard.cpp b/lib/platform/CMSWindowsClipboard.cpp new file mode 100644 index 00000000..7df9db9f --- /dev/null +++ b/lib/platform/CMSWindowsClipboard.cpp @@ -0,0 +1,209 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClipboard.h" +#include "CMSWindowsClipboardTextConverter.h" +#include "CMSWindowsClipboardUTF16Converter.h" +#include "CMSWindowsClipboardBitmapConverter.h" +#include "CMSWindowsClipboardHTMLConverter.h" +#include "CLog.h" +#include "CArchMiscWindows.h" + +// +// CMSWindowsClipboard +// + +UINT CMSWindowsClipboard::s_ownershipFormat = 0; + +CMSWindowsClipboard::CMSWindowsClipboard(HWND window) : + m_window(window), + m_time(0) +{ + // add converters, most desired first + m_converters.push_back(new CMSWindowsClipboardUTF16Converter); + if (CArchMiscWindows::isWindows95Family()) { + // windows nt family converts to/from unicode automatically. + // let it do so to avoid text encoding issues. + m_converters.push_back(new CMSWindowsClipboardTextConverter); + } + m_converters.push_back(new CMSWindowsClipboardBitmapConverter); + m_converters.push_back(new CMSWindowsClipboardHTMLConverter); +} + +CMSWindowsClipboard::~CMSWindowsClipboard() +{ + clearConverters(); +} + +bool +CMSWindowsClipboard::emptyUnowned() +{ + LOG((CLOG_DEBUG "empty clipboard")); + + // empty the clipboard (and take ownership) + if (!EmptyClipboard()) { + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + return true; +} + +bool +CMSWindowsClipboard::empty() +{ + if (!emptyUnowned()) { + return false; + } + + // mark clipboard as being owned by synergy + HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, 1); + SetClipboardData(getOwnershipFormat(), data); + + return true; +} + +void +CMSWindowsClipboard::add(EFormat format, const CString& data) +{ + LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format)); + + // convert data to win32 form + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IMSWindowsClipboardConverter* converter = *index; + + // skip converters for other formats + if (converter->getFormat() == format) { + HANDLE win32Data = converter->fromIClipboard(data); + if (win32Data != NULL) { + UINT win32Format = converter->getWin32Format(); + if (SetClipboardData(win32Format, win32Data) == NULL) { + // free converted data if we couldn't put it on + // the clipboard + GlobalFree(win32Data); + } + } + } + } +} + +bool +CMSWindowsClipboard::open(Time time) const +{ + LOG((CLOG_DEBUG "open clipboard")); + + if (!OpenClipboard(m_window)) { + LOG((CLOG_WARN "failed to open clipboard")); + return false; + } + + m_time = time; + + return true; +} + +void +CMSWindowsClipboard::close() const +{ + LOG((CLOG_DEBUG "close clipboard")); + CloseClipboard(); +} + +IClipboard::Time +CMSWindowsClipboard::getTime() const +{ + return m_time; +} + +bool +CMSWindowsClipboard::has(EFormat format) const +{ + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IMSWindowsClipboardConverter* converter = *index; + if (converter->getFormat() == format) { + if (IsClipboardFormatAvailable(converter->getWin32Format())) { + return true; + } + } + } + return false; +} + +CString +CMSWindowsClipboard::get(EFormat format) const +{ + // find the converter for the first clipboard format we can handle + IMSWindowsClipboardConverter* converter = NULL; + UINT win32Format = EnumClipboardFormats(0); + while (converter == NULL && win32Format != 0) { + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + if (converter->getWin32Format() == win32Format && + converter->getFormat() == format) { + break; + } + converter = NULL; + } + win32Format = EnumClipboardFormats(win32Format); + } + + // if no converter then we don't recognize any formats + if (converter == NULL) { + return CString(); + } + + // get a handle to the clipboard data + HANDLE win32Data = GetClipboardData(converter->getWin32Format()); + if (win32Data == NULL) { + return CString(); + } + + // convert + return converter->toIClipboard(win32Data); +} + +void +CMSWindowsClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +bool +CMSWindowsClipboard::isOwnedBySynergy() +{ + // create ownership format if we haven't yet + if (s_ownershipFormat == 0) { + s_ownershipFormat = RegisterClipboardFormat(TEXT("SynergyOwnership")); + } + return (IsClipboardFormatAvailable(getOwnershipFormat()) != 0); +} + +UINT +CMSWindowsClipboard::getOwnershipFormat() +{ + // create ownership format if we haven't yet + if (s_ownershipFormat == 0) { + s_ownershipFormat = RegisterClipboardFormat(TEXT("SynergyOwnership")); + } + + // return the format + return s_ownershipFormat; +} diff --git a/lib/platform/CMSWindowsClipboard.h b/lib/platform/CMSWindowsClipboard.h new file mode 100644 index 00000000..e9b59fb9 --- /dev/null +++ b/lib/platform/CMSWindowsClipboard.h @@ -0,0 +1,104 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARD_H +#define CMSWINDOWSCLIPBOARD_H + +#include "IClipboard.h" +#include "stdvector.h" +#define WIN32_LEAN_AND_MEAN +#include + +class IMSWindowsClipboardConverter; + +//! Microsoft windows clipboard implementation +class CMSWindowsClipboard : public IClipboard { +public: + CMSWindowsClipboard(HWND window); + virtual ~CMSWindowsClipboard(); + + //! Empty clipboard without ownership + /*! + Take ownership of the clipboard and clear all data from it. + This must be called between a successful open() and close(). + Return false if the clipboard ownership could not be taken; + the clipboard should not be emptied in this case. Unlike + empty(), isOwnedBySynergy() will return false when emptied + this way. This is useful when synergy wants to put data on + clipboard but pretend (to itself) that some other app did it. + When using empty(), synergy assumes the data came from the + server and doesn't need to be sent back. emptyUnowned() + makes synergy send the data to the server. + */ + bool emptyUnowned(); + + //! Test if clipboard is owned by synergy + static bool isOwnedBySynergy(); + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + void clearConverters(); + + UINT convertFormatToWin32(EFormat) const; + HANDLE convertTextToWin32(const CString& data) const; + CString convertTextFromWin32(HANDLE) const; + + static UINT getOwnershipFormat(); + +private: + typedef std::vector ConverterList; + + HWND m_window; + mutable Time m_time; + ConverterList m_converters; + static UINT s_ownershipFormat; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all win32 clipboard format +converters. +*/ +class IMSWindowsClipboardConverter : public IInterface { +public: + // accessors + + // return the clipboard format this object converts from/to + virtual IClipboard::EFormat + getFormat() const = 0; + + // return the atom representing the win32 clipboard format that + // this object converts from/to + virtual UINT getWin32Format() const = 0; + + // convert from the IClipboard format to the win32 clipboard format. + // the input data must be in the IClipboard format returned by + // getFormat(). the return data will be in the win32 clipboard + // format returned by getWin32Format(), allocated by GlobalAlloc(). + virtual HANDLE fromIClipboard(const CString&) const = 0; + + // convert from the win32 clipboard format to the IClipboard format + // (i.e., the reverse of fromIClipboard()). + virtual CString toIClipboard(HANDLE data) const = 0; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardAnyTextConverter.cpp b/lib/platform/CMSWindowsClipboardAnyTextConverter.cpp new file mode 100644 index 00000000..730f3424 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardAnyTextConverter.cpp @@ -0,0 +1,145 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClipboardAnyTextConverter.h" + +// +// CMSWindowsClipboardAnyTextConverter +// + +CMSWindowsClipboardAnyTextConverter::CMSWindowsClipboardAnyTextConverter() +{ + // do nothing +} + +CMSWindowsClipboardAnyTextConverter::~CMSWindowsClipboardAnyTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +CMSWindowsClipboardAnyTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +HANDLE +CMSWindowsClipboardAnyTextConverter::fromIClipboard(const CString& data) const +{ + // convert linefeeds and then convert to desired encoding + CString text = doFromIClipboard(convertLinefeedToWin32(data)); + UInt32 size = text.size(); + + // copy to memory handle + HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size); + if (gData != NULL) { + // get a pointer to the allocated memory + char* dst = (char*)GlobalLock(gData); + if (dst != NULL) { + memcpy(dst, text.data(), size); + GlobalUnlock(gData); + } + else { + GlobalFree(gData); + gData = NULL; + } + } + + return gData; +} + +CString +CMSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const +{ + // get datator + const char* src = (const char*)GlobalLock(data); + UInt32 srcSize = (UInt32)GlobalSize(data); + if (src == NULL || srcSize <= 1) { + return CString(); + } + + // convert text + CString text = doToIClipboard(CString(src, srcSize)); + + // release handle + GlobalUnlock(data); + + // convert newlines + return convertLinefeedToUnix(text); +} + +CString +CMSWindowsClipboardAnyTextConverter::convertLinefeedToWin32( + const CString& src) const +{ + // note -- we assume src is a valid UTF-8 string + + // count newlines in string + UInt32 numNewlines = 0; + UInt32 n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (*scan == '\n') { + ++numNewlines; + } + } + if (numNewlines == 0) { + return src; + } + + // allocate new string + CString dst; + dst.reserve(src.size() + numNewlines); + + // copy string, converting newlines + n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] == '\n') { + dst += '\r'; + } + dst += scan[0]; + } + + return dst; +} + +CString +CMSWindowsClipboardAnyTextConverter::convertLinefeedToUnix( + const CString& src) const +{ + // count newlines in string + UInt32 numNewlines = 0; + UInt32 n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] == '\r' && scan[1] == '\n') { + ++numNewlines; + } + } + if (numNewlines == 0) { + return src; + } + + // allocate new string + CString dst; + dst.reserve(src.size()); + + // copy string, converting newlines + n = src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] != '\r' || scan[1] != '\n') { + dst += scan[0]; + } + } + + return dst; +} diff --git a/lib/platform/CMSWindowsClipboardAnyTextConverter.h b/lib/platform/CMSWindowsClipboardAnyTextConverter.h new file mode 100644 index 00000000..254099f2 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardAnyTextConverter.h @@ -0,0 +1,56 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDANYTEXTCONVERTER_H +#define CMSWINDOWSCLIPBOARDANYTEXTCONVERTER_H + +#include "CMSWindowsClipboard.h" + +//! Convert to/from some text encoding +class CMSWindowsClipboardAnyTextConverter : + public IMSWindowsClipboardConverter { +public: + CMSWindowsClipboardAnyTextConverter(); + virtual ~CMSWindowsClipboardAnyTextConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const = 0; + virtual HANDLE fromIClipboard(const CString&) const; + virtual CString toIClipboard(HANDLE) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion only. Memory handle allocation and + linefeed conversion is done by this class. doFromIClipboard() + must include the nul terminator in the returned string (not + including the CString's nul terminator). + */ + virtual CString doFromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion only. Memory handle allocation and + linefeed conversion is done by this class. + */ + virtual CString doToIClipboard(const CString&) const = 0; + +private: + CString convertLinefeedToWin32(const CString&) const; + CString convertLinefeedToUnix(const CString&) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardBitmapConverter.cpp b/lib/platform/CMSWindowsClipboardBitmapConverter.cpp new file mode 100644 index 00000000..a17c66cf --- /dev/null +++ b/lib/platform/CMSWindowsClipboardBitmapConverter.cpp @@ -0,0 +1,146 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClipboardBitmapConverter.h" +#include "CLog.h" + +// +// CMSWindowsClipboardBitmapConverter +// + +CMSWindowsClipboardBitmapConverter::CMSWindowsClipboardBitmapConverter() +{ + // do nothing +} + +CMSWindowsClipboardBitmapConverter::~CMSWindowsClipboardBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +CMSWindowsClipboardBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +UINT +CMSWindowsClipboardBitmapConverter::getWin32Format() const +{ + return CF_DIB; +} + +HANDLE +CMSWindowsClipboardBitmapConverter::fromIClipboard(const CString& data) const +{ + // copy to memory handle + HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, data.size()); + if (gData != NULL) { + // get a pointer to the allocated memory + char* dst = (char*)GlobalLock(gData); + if (dst != NULL) { + memcpy(dst, data.data(), data.size()); + GlobalUnlock(gData); + } + else { + GlobalFree(gData); + gData = NULL; + } + } + + return gData; +} + +CString +CMSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const +{ + // get datator + const char* src = (const char*)GlobalLock(data); + if (src == NULL) { + return CString(); + } + UInt32 srcSize = (UInt32)GlobalSize(data); + + // check image type + const BITMAPINFO* bitmap = reinterpret_cast(src); + LOG((CLOG_INFO "bitmap: %dx%d %d", bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, (int)bitmap->bmiHeader.biBitCount)); + if (bitmap->bmiHeader.biPlanes == 1 && + (bitmap->bmiHeader.biBitCount == 24 || + bitmap->bmiHeader.biBitCount == 32) && + bitmap->bmiHeader.biCompression == BI_RGB) { + // already in canonical form + CString image(src, srcSize); + GlobalUnlock(data); + return image; + } + + // create a destination DIB section + LOG((CLOG_INFO "convert image from: depth=%d comp=%d", bitmap->bmiHeader.biBitCount, bitmap->bmiHeader.biCompression)); + void* raw; + BITMAPINFOHEADER info; + LONG w = bitmap->bmiHeader.biWidth; + LONG h = bitmap->bmiHeader.biHeight; + info.biSize = sizeof(BITMAPINFOHEADER); + info.biWidth = w; + info.biHeight = h; + info.biPlanes = 1; + info.biBitCount = 32; + info.biCompression = BI_RGB; + info.biSizeImage = 0; + info.biXPelsPerMeter = 1000; + info.biYPelsPerMeter = 1000; + info.biClrUsed = 0; + info.biClrImportant = 0; + HDC dc = GetDC(NULL); + HBITMAP dst = CreateDIBSection(dc, (BITMAPINFO*)&info, + DIB_RGB_COLORS, &raw, NULL, 0); + + // find the start of the pixel data + const char* srcBits = (const char*)bitmap + bitmap->bmiHeader.biSize; + if (bitmap->bmiHeader.biBitCount >= 16) { + if (bitmap->bmiHeader.biCompression == BI_BITFIELDS && + (bitmap->bmiHeader.biBitCount == 16 || + bitmap->bmiHeader.biBitCount == 32)) { + srcBits += 3 * sizeof(DWORD); + } + } + else if (bitmap->bmiHeader.biClrUsed != 0) { + srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD); + } + else { + srcBits += (1 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD); + } + + // copy source image to destination image + HDC dstDC = CreateCompatibleDC(dc); + HGDIOBJ oldBitmap = SelectObject(dstDC, dst); + SetDIBitsToDevice(dstDC, 0, 0, w, h, 0, 0, 0, h, + srcBits, bitmap, DIB_RGB_COLORS); + SelectObject(dstDC, oldBitmap); + DeleteDC(dstDC); + GdiFlush(); + + // extract data + CString image((const char*)&info, info.biSize); + image.append((const char*)raw, 4 * w * h); + + // clean up GDI + DeleteObject(dst); + ReleaseDC(NULL, dc); + + // release handle + GlobalUnlock(data); + + return image; +} diff --git a/lib/platform/CMSWindowsClipboardBitmapConverter.h b/lib/platform/CMSWindowsClipboardBitmapConverter.h new file mode 100644 index 00000000..6ddd7ce8 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardBitmapConverter.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDBITMAPCONVERTER_H +#define CMSWINDOWSCLIPBOARDBITMAPCONVERTER_H + +#include "CMSWindowsClipboard.h" + +//! Convert to/from some text encoding +class CMSWindowsClipboardBitmapConverter : + public IMSWindowsClipboardConverter { +public: + CMSWindowsClipboardBitmapConverter(); + virtual ~CMSWindowsClipboardBitmapConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const; + virtual HANDLE fromIClipboard(const CString&) const; + virtual CString toIClipboard(HANDLE) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardHTMLConverter.cpp b/lib/platform/CMSWindowsClipboardHTMLConverter.cpp new file mode 100644 index 00000000..a64a0f78 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardHTMLConverter.cpp @@ -0,0 +1,107 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClipboardHTMLConverter.h" +#include "CStringUtil.h" + +// +// CMSWindowsClipboardHTMLConverter +// + +CMSWindowsClipboardHTMLConverter::CMSWindowsClipboardHTMLConverter() +{ + m_format = RegisterClipboardFormat("HTML Format"); +} + +CMSWindowsClipboardHTMLConverter::~CMSWindowsClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +CMSWindowsClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +UINT +CMSWindowsClipboardHTMLConverter::getWin32Format() const +{ + return m_format; +} + +CString +CMSWindowsClipboardHTMLConverter::doFromIClipboard(const CString& data) const +{ + // prepare to CF_HTML format prefix and suffix + CString prefix("Version:0.9\nStartHTML:-1\nEndHTML:-1\n" + "StartFragment:XXXXXXXXXX\nEndFragment:YYYYYYYYYY\n" + ""); + CString suffix("\n"); + UInt32 start = prefix.size(); + UInt32 end = start + data.size(); + prefix.replace(prefix.find("XXXXXXXXXX"), 10, + CStringUtil::print("%010u", start)); + prefix.replace(prefix.find("YYYYYYYYYY"), 10, + CStringUtil::print("%010u", end)); + + // concatenate + prefix += data; + prefix += suffix; + return prefix; +} + +CString +CMSWindowsClipboardHTMLConverter::doToIClipboard(const CString& data) const +{ + // get fragment start/end args + CString startArg = findArg(data, "StartFragment"); + CString endArg = findArg(data, "EndFragment"); + if (startArg.empty() || endArg.empty()) { + return CString(); + } + + // convert args to integers + SInt32 start = (SInt32)atoi(startArg.c_str()); + SInt32 end = (SInt32)atoi(endArg.c_str()); + if (start <= 0 || end <= 0 || start >= end) { + return CString(); + } + + // extract the fragment + return data.substr(start, end - start); +} + +CString +CMSWindowsClipboardHTMLConverter::findArg( + const CString& data, const CString& name) const +{ + CString::size_type i = data.find(name); + if (i == CString::npos) { + return CString(); + } + i = data.find_first_of(":\r\n", i); + if (i == CString::npos || data[i] != ':') { + return CString(); + } + i = data.find_first_of("0123456789\r\n", i + 1); + if (i == CString::npos || data[i] == '\r' || data[i] == '\n') { + return CString(); + } + CString::size_type j = data.find_first_not_of("0123456789", i); + if (j == CString::npos) { + j = data.size(); + } + return data.substr(i, j - i); +} diff --git a/lib/platform/CMSWindowsClipboardHTMLConverter.h b/lib/platform/CMSWindowsClipboardHTMLConverter.h new file mode 100644 index 00000000..02cd8f88 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardHTMLConverter.h @@ -0,0 +1,44 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDHTMLCONVERTER_H +#define CMSWINDOWSCLIPBOARDHTMLCONVERTER_H + +#include "CMSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from HTML encoding +class CMSWindowsClipboardHTMLConverter : + public CMSWindowsClipboardAnyTextConverter { +public: + CMSWindowsClipboardHTMLConverter(); + virtual ~CMSWindowsClipboardHTMLConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const; + +protected: + // CMSWindowsClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; + +private: + CString findArg(const CString& data, const CString& name) const; + +private: + UINT m_format; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardTextConverter.cpp b/lib/platform/CMSWindowsClipboardTextConverter.cpp new file mode 100644 index 00000000..a735094a --- /dev/null +++ b/lib/platform/CMSWindowsClipboardTextConverter.cpp @@ -0,0 +1,55 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClipboardTextConverter.h" +#include "CUnicode.h" + +// +// CMSWindowsClipboardTextConverter +// + +CMSWindowsClipboardTextConverter::CMSWindowsClipboardTextConverter() +{ + // do nothing +} + +CMSWindowsClipboardTextConverter::~CMSWindowsClipboardTextConverter() +{ + // do nothing +} + +UINT +CMSWindowsClipboardTextConverter::getWin32Format() const +{ + return CF_TEXT; +} + +CString +CMSWindowsClipboardTextConverter::doFromIClipboard(const CString& data) const +{ + // convert and add nul terminator + return CUnicode::UTF8ToText(data) += '\0'; +} + +CString +CMSWindowsClipboardTextConverter::doToIClipboard(const CString& data) const +{ + // convert and truncate at first nul terminator + CString dst = CUnicode::textToUTF8(data); + CString::size_type n = dst.find('\0'); + if (n != CString::npos) { + dst.erase(n); + } + return dst; +} diff --git a/lib/platform/CMSWindowsClipboardTextConverter.h b/lib/platform/CMSWindowsClipboardTextConverter.h new file mode 100644 index 00000000..6f00d475 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardTextConverter.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDTEXTCONVERTER_H +#define CMSWINDOWSCLIPBOARDTEXTCONVERTER_H + +#include "CMSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from locale text encoding +class CMSWindowsClipboardTextConverter : + public CMSWindowsClipboardAnyTextConverter { +public: + CMSWindowsClipboardTextConverter(); + virtual ~CMSWindowsClipboardTextConverter(); + + // IMSWindowsClipboardConverter overrides + virtual UINT getWin32Format() const; + +protected: + // CMSWindowsClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsClipboardUTF16Converter.cpp b/lib/platform/CMSWindowsClipboardUTF16Converter.cpp new file mode 100644 index 00000000..81b85c60 --- /dev/null +++ b/lib/platform/CMSWindowsClipboardUTF16Converter.cpp @@ -0,0 +1,55 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsClipboardUTF16Converter.h" +#include "CUnicode.h" + +// +// CMSWindowsClipboardUTF16Converter +// + +CMSWindowsClipboardUTF16Converter::CMSWindowsClipboardUTF16Converter() +{ + // do nothing +} + +CMSWindowsClipboardUTF16Converter::~CMSWindowsClipboardUTF16Converter() +{ + // do nothing +} + +UINT +CMSWindowsClipboardUTF16Converter::getWin32Format() const +{ + return CF_UNICODETEXT; +} + +CString +CMSWindowsClipboardUTF16Converter::doFromIClipboard(const CString& data) const +{ + // convert and add nul terminator + return CUnicode::UTF8ToUTF16(data).append(sizeof(wchar_t), 0); +} + +CString +CMSWindowsClipboardUTF16Converter::doToIClipboard(const CString& data) const +{ + // convert and strip nul terminator + CString dst = CUnicode::UTF16ToUTF8(data); + CString::size_type n = dst.find('\0'); + if (n != CString::npos) { + dst.erase(n); + } + return dst; +} diff --git a/lib/platform/CMSWindowsClipboardUTF16Converter.h b/lib/platform/CMSWindowsClipboardUTF16Converter.h new file mode 100644 index 00000000..51f477fa --- /dev/null +++ b/lib/platform/CMSWindowsClipboardUTF16Converter.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSCLIPBOARDUTF16CONVERTER_H +#define CMSWINDOWSCLIPBOARDUTF16CONVERTER_H + +#include "CMSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from UTF-16 encoding +class CMSWindowsClipboardUTF16Converter : + public CMSWindowsClipboardAnyTextConverter { +public: + CMSWindowsClipboardUTF16Converter(); + virtual ~CMSWindowsClipboardUTF16Converter(); + + // IMSWindowsClipboardConverter overrides + virtual UINT getWin32Format() const; + +protected: + // CMSWindowsClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; +}; + +#endif diff --git a/lib/platform/CMSWindowsDesks.cpp b/lib/platform/CMSWindowsDesks.cpp new file mode 100644 index 00000000..4a1c4474 --- /dev/null +++ b/lib/platform/CMSWindowsDesks.cpp @@ -0,0 +1,1033 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsDesks.h" +#include "CMSWindowsScreen.h" +#include "CSynergyHook.h" +#include "IScreenSaver.h" +#include "XScreen.h" +#include "CLock.h" +#include "CThread.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "IJob.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "CArchMiscWindows.h" +#include + +// these are only defined when WINVER >= 0x0500 +#if !defined(SPI_GETMOUSESPEED) +#define SPI_GETMOUSESPEED 112 +#endif +#if !defined(SPI_SETMOUSESPEED) +#define SPI_SETMOUSESPEED 113 +#endif +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// ; +#define SYNERGY_MSG_SWITCH SYNERGY_HOOK_LAST_MSG + 1 +// ; +#define SYNERGY_MSG_ENTER SYNERGY_HOOK_LAST_MSG + 2 +// ; +#define SYNERGY_MSG_LEAVE SYNERGY_HOOK_LAST_MSG + 3 +// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code +#define SYNERGY_MSG_FAKE_KEY SYNERGY_HOOK_LAST_MSG + 4 + // flags, XBUTTON id +#define SYNERGY_MSG_FAKE_BUTTON SYNERGY_HOOK_LAST_MSG + 5 +// x; y +#define SYNERGY_MSG_FAKE_MOVE SYNERGY_HOOK_LAST_MSG + 6 +// xDelta; yDelta +#define SYNERGY_MSG_FAKE_WHEEL SYNERGY_HOOK_LAST_MSG + 7 +// POINT*; +#define SYNERGY_MSG_CURSOR_POS SYNERGY_HOOK_LAST_MSG + 8 +// IKeyState*; +#define SYNERGY_MSG_SYNC_KEYS SYNERGY_HOOK_LAST_MSG + 9 +// install; +#define SYNERGY_MSG_SCREENSAVER SYNERGY_HOOK_LAST_MSG + 10 +// dx; dy +#define SYNERGY_MSG_FAKE_REL_MOVE SYNERGY_HOOK_LAST_MSG + 11 +// enable; +#define SYNERGY_MSG_FAKE_INPUT SYNERGY_HOOK_LAST_MSG + 12 + +// +// CMSWindowsDesks +// + +CMSWindowsDesks::CMSWindowsDesks( + bool isPrimary, HINSTANCE hookLibrary, + const IScreenSaver* screensaver, IJob* updateKeys) : + m_isPrimary(isPrimary), + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_isModernFamily(CArchMiscWindows::isWindowsModern()), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_timer(NULL), + m_screensaver(screensaver), + m_screensaverNotify(false), + m_activeDesk(NULL), + m_activeDeskName(), + m_mutex(), + m_deskReady(&m_mutex, false), + m_updateKeys(updateKeys) +{ + queryHookLibrary(hookLibrary); + m_cursor = createBlankCursor(); + m_deskClass = createDeskWindowClass(m_isPrimary); + m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); + resetOptions(); +} + +CMSWindowsDesks::~CMSWindowsDesks() +{ + disable(); + destroyClass(m_deskClass); + destroyCursor(m_cursor); + delete m_updateKeys; +} + +void +CMSWindowsDesks::enable() +{ + m_threadID = GetCurrentThreadId(); + + // set the active desk and (re)install the hooks + checkDesk(); + + // install the desk timer. this timer periodically checks + // which desk is active and reinstalls the hooks as necessary. + // we wouldn't need this if windows notified us of a desktop + // change but as far as i can tell it doesn't. + m_timer = EVENTQUEUE->newTimer(0.2, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, + new TMethodEventJob( + this, &CMSWindowsDesks::handleCheckDesk)); + + updateKeys(); +} + +void +CMSWindowsDesks::disable() +{ + // remove timer + if (m_timer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); + EVENTQUEUE->deleteTimer(m_timer); + m_timer = NULL; + } + + // destroy desks + removeDesks(); + + m_isOnScreen = m_isPrimary; +} + +void +CMSWindowsDesks::enter() +{ + sendMessage(SYNERGY_MSG_ENTER, 0, 0); +} + +void +CMSWindowsDesks::leave(HKL keyLayout) +{ + sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)keyLayout, 0); +} + +void +CMSWindowsDesks::resetOptions() +{ + m_leaveForegroundOption = false; +} + +void +CMSWindowsDesks::setOptions(const COptionsList& options) +{ + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionWin32KeepForeground) { + m_leaveForegroundOption = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "Don\'t grab" : "Grab")); + } + } +} + +void +CMSWindowsDesks::updateKeys() +{ + sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); +} + +void +CMSWindowsDesks::setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon) +{ + m_x = x; + m_y = y; + m_w = width; + m_h = height; + m_xCenter = xCenter; + m_yCenter = yCenter; + m_multimon = isMultimon; +} + +void +CMSWindowsDesks::installScreensaverHooks(bool install) +{ + if (m_isPrimary && m_screensaverNotify != install) { + m_screensaverNotify = install; + sendMessage(SYNERGY_MSG_SCREENSAVER, install, 0); + } +} + +void +CMSWindowsDesks::fakeInputBegin() +{ + sendMessage(SYNERGY_MSG_FAKE_INPUT, 1, 0); +} + +void +CMSWindowsDesks::fakeInputEnd() +{ + sendMessage(SYNERGY_MSG_FAKE_INPUT, 0, 0); +} + +void +CMSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const +{ + POINT pos; + sendMessage(SYNERGY_MSG_CURSOR_POS, reinterpret_cast(&pos), 0); + x = pos.x; + y = pos.y; +} + +void +CMSWindowsDesks::fakeKeyEvent( + KeyButton button, UINT virtualKey, + bool press, bool /*isAutoRepeat*/) const +{ + // win 95 family doesn't understand handed modifier virtual keys + if (m_is95Family) { + switch (virtualKey) { + case VK_LSHIFT: + case VK_RSHIFT: + virtualKey = VK_SHIFT; + break; + + case VK_LCONTROL: + case VK_RCONTROL: + virtualKey = VK_CONTROL; + break; + + case VK_LMENU: + case VK_RMENU: + virtualKey = VK_MENU; + break; + } + } + + // synthesize event + DWORD flags = 0; + if (((button & 0x100u) != 0)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (!press) { + flags |= KEYEVENTF_KEYUP; + } + sendMessage(SYNERGY_MSG_FAKE_KEY, flags, + MAKEWORD(static_cast(button & 0xffu), + static_cast(virtualKey & 0xffu))); +} + +void +CMSWindowsDesks::fakeMouseButton(ButtonID button, bool press) const +{ + // the system will swap the meaning of left/right for us if + // the user has configured a left-handed mouse but we don't + // want it to swap since we want the handedness of the + // server's mouse. so pre-swap for a left-handed mouse. + if (GetSystemMetrics(SM_SWAPBUTTON)) { + switch (button) { + case kButtonLeft: + button = kButtonRight; + break; + + case kButtonRight: + button = kButtonLeft; + break; + } + } + + // map button id to button flag and button data + DWORD data = 0; + DWORD flags; + switch (button) { + case kButtonLeft: + flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + break; + + case kButtonMiddle: + flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + break; + + case kButtonRight: + flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + break; + + case kButtonExtra0 + 0: + data = XBUTTON1; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + case kButtonExtra0 + 1: + data = XBUTTON2; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + default: + return; + } + + // do it + sendMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data); +} + +void +CMSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const +{ + sendMessage(SYNERGY_MSG_FAKE_MOVE, + static_cast(x), + static_cast(y)); +} + +void +CMSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + sendMessage(SYNERGY_MSG_FAKE_REL_MOVE, + static_cast(dx), + static_cast(dy)); +} + +void +CMSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + sendMessage(SYNERGY_MSG_FAKE_WHEEL, xDelta, yDelta); +} + +void +CMSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const +{ + if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { + PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); + waitForDesk(); + } +} + +void +CMSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary) +{ + // look up functions + if (m_isPrimary) { + m_install = (InstallFunc)GetProcAddress(hookLibrary, "install"); + m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall"); + m_installScreensaver = + (InstallScreenSaverFunc)GetProcAddress( + hookLibrary, "installScreenSaver"); + m_uninstallScreensaver = + (UninstallScreenSaverFunc)GetProcAddress( + hookLibrary, "uninstallScreenSaver"); + if (m_install == NULL || + m_uninstall == NULL || + m_installScreensaver == NULL || + m_uninstallScreensaver == NULL) { + LOG((CLOG_ERR "Invalid hook library")); + throw XScreenOpenFailure(); + } + } + else { + m_install = NULL; + m_uninstall = NULL; + m_installScreensaver = NULL; + m_uninstallScreensaver = NULL; + } +} + +HCURSOR +CMSWindowsDesks::createBlankCursor() const +{ + // create a transparent cursor + int cw = GetSystemMetrics(SM_CXCURSOR); + int ch = GetSystemMetrics(SM_CYCURSOR); + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; + UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; + memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(CMSWindowsScreen::getInstance(), + 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +CMSWindowsDesks::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +CMSWindowsDesks::createDeskWindowClass(bool isPrimary) const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = isPrimary ? + &CMSWindowsDesks::primaryDeskProc : + &CMSWindowsDesks::secondaryDeskProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = CMSWindowsScreen::getInstance(); + classInfo.hIcon = NULL; + classInfo.hCursor = m_cursor; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "SynergyDesk"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +CMSWindowsDesks::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(reinterpret_cast(windowClass), + CMSWindowsScreen::getInstance()); + } +} + +HWND +CMSWindowsDesks::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + CMSWindowsScreen::getInstance(), + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +CMSWindowsDesks::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +LRESULT CALLBACK +CMSWindowsDesks::primaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CMSWindowsDesks::secondaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // would like to detect any local user input and hide the hider + // window but for now we just detect mouse motion. + bool hide = false; + switch (msg) { + case WM_MOUSEMOVE: + if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { + hide = true; + } + break; + } + + if (hide && IsWindowVisible(hwnd)) { + ReleaseCapture(); + SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +void +CMSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const +{ + // motion is simple (i.e. it's on the primary monitor) if there + // is only one monitor. it's also simple if we're not on the + // windows 95 family since those platforms don't have a broken + // mouse_event() function (see the comment below). + bool simple = (!m_multimon || !m_is95Family); + if (!simple) { + // also simple if motion is within the primary monitor + simple = (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) && + y >= 0 && y < GetSystemMetrics(SM_CYSCREEN)); + } + + // move the mouse directly to target position if motion is simple + if (simple) { + // when using absolute positioning with mouse_event(), + // the normalized device coordinates range over only + // the primary screen. + SInt32 w = GetSystemMetrics(SM_CXSCREEN); + SInt32 h = GetSystemMetrics(SM_CYSCREEN); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.0f * x) / (w - 1) + 0.5f), + (DWORD)((65535.0f * y) / (h - 1) + 0.5f), + 0, 0); + } + + // windows 98 and Me are broken. you cannot set the absolute + // position of the mouse except on the primary monitor but you + // can do relative moves onto any monitor. this is, in microsoft's + // words, "by design." apparently the designers of windows 2000 + // we're a little less lazy and did it right. + // + // microsoft recommends in Q193003 to absolute position the cursor + // somewhere on the primary monitor then relative move to the + // desired location. this doesn't work for us because when the + // user drags a scrollbar, a window, etc. it causes the dragged + // item to jump back and forth between the position on the primary + // monitor and the desired position. while it always ends up in + // the right place, the effect is disconcerting. + // + // instead we'll get the cursor's current position and do just a + // relative move from there to the desired position. + else { + POINT pos; + GetCursorPos(&pos); + deskMouseRelativeMove(x - pos.x, y - pos.y); + } +} + +void +CMSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // relative moves are subject to cursor acceleration which we don't + // want.so we disable acceleration, do the relative move, then + // restore acceleration. there's a slight chance we'll end up in + // the wrong place if the user moves the cursor using this system's + // mouse while simultaneously moving the mouse on the server + // system. that defeats the purpose of synergy so we'll assume + // that won't happen. even if it does, the next mouse move will + // correct the position. + + // save mouse speed & acceleration + int oldSpeed[4]; + bool accelChanged = + SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) && + SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); + + // use 1:1 motion + if (accelChanged) { + int newSpeed[4] = { 0, 0, 0, 1 }; + accelChanged = + SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || + SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); + } + + // move relative to mouse position + mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } +} + +void +CMSWindowsDesks::deskEnter(CDesk* desk) +{ + if (!m_isPrimary) { + ReleaseCapture(); + } + ShowCursor(TRUE); + SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + + // restore the foreground window + // XXX -- this raises the window to the top of the Z-order. we + // want it to stay wherever it was to properly support X-mouse + // (mouse over activation) but i've no idea how to do that. + // the obvious workaround of using SetWindowPos() to move it back + // after being raised doesn't work. + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_foregroundWindow); + AttachThreadInput(thatThread, thisThread, FALSE); + EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); + desk->m_foregroundWindow = NULL; +} + +void +CMSWindowsDesks::deskLeave(CDesk* desk, HKL keyLayout) +{ + ShowCursor(FALSE); + if (m_isPrimary) { + // map a window to hide the cursor and to use whatever keyboard + // layout we choose rather than the keyboard layout of the last + // active window. + int x, y, w, h; + if (desk->m_lowLevel) { + // with a low level hook the cursor will never budge so + // just a 1x1 window is sufficient. + x = m_xCenter; + y = m_yCenter; + w = 1; + h = 1; + } + else { + // with regular hooks the cursor will jitter as it's moved + // by the user then back to the center by us. to be sure + // we never lose it, cover all the monitors with the window. + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // if not using low-level hooks we have to also activate the + // window to ensure we don't lose keyboard focus. + // FIXME -- see if this can be avoided. if so then always + // disable the window (see handling of SYNERGY_MSG_SWITCH). + if (!desk->m_lowLevel) { + SetActiveWindow(desk->m_window); + } + + // if using low-level hooks then disable the foreground window + // so it can't mess up any of our keyboard events. the console + // program, for example, will cause characters to be reported as + // unshifted, regardless of the shift key state. interestingly + // we do see the shift key go down and up. + // + // note that we must enable the window to activate it and we + // need to disable the window on deskEnter. + else { + desk->m_foregroundWindow = getForegroundWindow(); + if (desk->m_foregroundWindow != NULL) { + EnableWindow(desk->m_window, TRUE); + SetActiveWindow(desk->m_window); + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_window); + AttachThreadInput(thatThread, thisThread, FALSE); + } + } + + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); + } + else { + // move hider window under the cursor center, raise, and show it + SetWindowPos(desk->m_window, HWND_TOPMOST, + m_xCenter, m_yCenter, 1, 1, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // watch for mouse motion. if we see any then we hide the + // hider window so the user can use the physically attached + // mouse if desired. we'd rather not capture the mouse but + // we aren't notified when the mouse leaves our window. + SetCapture(desk->m_window); + + // warp the mouse to the cursor center + deskMouseMove(m_xCenter, m_yCenter); + } +} + +void +CMSWindowsDesks::deskThread(void* vdesk) +{ + MSG msg; + + // use given desktop for this thread + CDesk* desk = reinterpret_cast(vdesk); + desk->m_threadID = GetCurrentThreadId(); + desk->m_window = NULL; + desk->m_foregroundWindow = NULL; + if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { + // create a message queue + PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); + + // create a window. we use this window to hide the cursor. + try { + desk->m_window = createWindow(m_deskClass, "SynergyDesk"); + LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); + } + catch (...) { + // ignore + LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); + } + } + + // tell main thread that we're ready + { + CLock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + while (GetMessage(&msg, NULL, 0, 0)) { + switch (msg.message) { + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + + case SYNERGY_MSG_SWITCH: + if (m_isPrimary) { + m_uninstall(); + if (m_screensaverNotify) { + m_uninstallScreensaver(); + m_installScreensaver(); + } + switch (m_install()) { + case kHOOK_FAILED: + // we won't work on this desk + desk->m_lowLevel = false; + break; + + case kHOOK_OKAY: + desk->m_lowLevel = false; + break; + + case kHOOK_OKAY_LL: + desk->m_lowLevel = true; + break; + } + + // a window on the primary screen with low-level hooks + // should never activate. + EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); + } + break; + + case SYNERGY_MSG_ENTER: + m_isOnScreen = true; + deskEnter(desk); + break; + + case SYNERGY_MSG_LEAVE: + m_isOnScreen = false; + m_keyLayout = (HKL)msg.wParam; + deskLeave(desk, m_keyLayout); + break; + + case SYNERGY_MSG_FAKE_KEY: + keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), msg.wParam, 0); + break; + + case SYNERGY_MSG_FAKE_BUTTON: + if (msg.wParam != 0) { + mouse_event(msg.wParam, 0, 0, msg.lParam, 0); + } + break; + + case SYNERGY_MSG_FAKE_MOVE: + deskMouseMove(static_cast(msg.wParam), + static_cast(msg.lParam)); + break; + + case SYNERGY_MSG_FAKE_REL_MOVE: + deskMouseRelativeMove(static_cast(msg.wParam), + static_cast(msg.lParam)); + break; + + case SYNERGY_MSG_FAKE_WHEEL: + // XXX -- add support for x-axis scrolling + if (msg.lParam != 0) { + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, msg.lParam, 0); + } + break; + + case SYNERGY_MSG_CURSOR_POS: { + POINT* pos = reinterpret_cast(msg.wParam); + if (!GetCursorPos(pos)) { + pos->x = m_xCenter; + pos->y = m_yCenter; + } + break; + } + + case SYNERGY_MSG_SYNC_KEYS: + m_updateKeys->run(); + break; + + case SYNERGY_MSG_SCREENSAVER: + if (msg.wParam != 0) { + m_installScreensaver(); + } + else { + m_uninstallScreensaver(); + } + break; + + case SYNERGY_MSG_FAKE_INPUT: + keybd_event(SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY, + SYNERGY_HOOK_FAKE_INPUT_SCANCODE, + msg.wParam ? 0 : KEYEVENTF_KEYUP, 0); + break; + } + + // notify that message was processed + CLock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + // clean up + deskEnter(desk); + if (desk->m_window != NULL) { + DestroyWindow(desk->m_window); + } + if (desk->m_desk != NULL) { + closeDesktop(desk->m_desk); + } +} + +CMSWindowsDesks::CDesk* +CMSWindowsDesks::addDesk(const CString& name, HDESK hdesk) +{ + CDesk* desk = new CDesk; + desk->m_name = name; + desk->m_desk = hdesk; + desk->m_targetID = GetCurrentThreadId(); + desk->m_thread = new CThread(new TMethodJob( + this, &CMSWindowsDesks::deskThread, desk)); + waitForDesk(); + m_desks.insert(std::make_pair(name, desk)); + return desk; +} + +void +CMSWindowsDesks::removeDesks() +{ + for (CDesks::iterator index = m_desks.begin(); + index != m_desks.end(); ++index) { + CDesk* desk = index->second; + PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); + desk->m_thread->wait(); + delete desk->m_thread; + delete desk; + } + m_desks.clear(); + m_activeDesk = NULL; + m_activeDeskName = ""; +} + +void +CMSWindowsDesks::checkDesk() +{ + // get current desktop. if we already know about it then return. + CDesk* desk; + HDESK hdesk = openInputDesktop(); + CString name = getDesktopName(hdesk); + CDesks::const_iterator index = m_desks.find(name); + if (index == m_desks.end()) { + desk = addDesk(name, hdesk); + // hold on to hdesk until thread exits so the desk can't + // be removed by the system + } + else { + closeDesktop(hdesk); + desk = index->second; + } + + // if active desktop changed then tell the old and new desk threads + // about the change. don't switch desktops when the screensaver is + // active becaue we'd most likely switch to the screensaver desktop + // which would have the side effect of forcing the screensaver to + // stop. + if (name != m_activeDeskName && !m_screensaver->isActive()) { + // show cursor on previous desk + bool wasOnScreen = m_isOnScreen; + if (!wasOnScreen) { + sendMessage(SYNERGY_MSG_ENTER, 0, 0); + } + + // check for desk accessibility change. we don't get events + // from an inaccessible desktop so when we switch from an + // inaccessible desktop to an accessible one we have to + // update the keyboard state. + LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); + bool syncKeys = false; + bool isAccessible = isDeskAccessible(desk); + if (isDeskAccessible(m_activeDesk) != isAccessible) { + if (isAccessible) { + LOG((CLOG_DEBUG "desktop is now accessible")); + syncKeys = true; + } + else { + LOG((CLOG_DEBUG "desktop is now inaccessible")); + } + } + + // switch desk + m_activeDesk = desk; + m_activeDeskName = name; + sendMessage(SYNERGY_MSG_SWITCH, 0, 0); + + // hide cursor on new desk + if (!wasOnScreen) { + sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); + } + + // update keys if necessary + if (syncKeys) { + updateKeys(); + } + } + else if (name != m_activeDeskName) { + // screen saver might have started + PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); + } +} + +bool +CMSWindowsDesks::isDeskAccessible(const CDesk* desk) const +{ + return (desk != NULL && desk->m_desk != NULL); +} + +void +CMSWindowsDesks::waitForDesk() const +{ + CMSWindowsDesks* self = const_cast(this); + + CLock lock(&m_mutex); + while (!(bool)m_deskReady) { + m_deskReady.wait(); + } + self->m_deskReady = false; +} + +void +CMSWindowsDesks::handleCheckDesk(const CEvent&, void*) +{ + checkDesk(); + + // also check if screen saver is running if on a modern OS and + // this is the primary screen. + if (m_isPrimary && m_isModernFamily) { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE); + PostThreadMessage(m_threadID, SYNERGY_MSG_SCREEN_SAVER, running, 0); + } +} + +HDESK +CMSWindowsDesks::openInputDesktop() +{ + if (m_is95Family) { + // there's only one desktop on windows 95 et al. + return GetThreadDesktop(GetCurrentThreadId()); + } + else { + return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, + DESKTOP_CREATEWINDOW | + DESKTOP_HOOKCONTROL | + GENERIC_WRITE); + } +} + +void +CMSWindowsDesks::closeDesktop(HDESK desk) +{ + // on 95/98/me we don't need to close the desktop returned by + // openInputDesktop(). + if (desk != NULL && !m_is95Family) { + CloseDesktop(desk); + } +} + +CString +CMSWindowsDesks::getDesktopName(HDESK desk) +{ + if (desk == NULL) { + return CString(); + } + else if (m_is95Family) { + return "desktop"; + } + else { + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + CString result(name); + return result; + } +} + +HWND +CMSWindowsDesks::getForegroundWindow() const +{ + // Ideally we'd return NULL as much as possible, only returning + // the actual foreground window when we know it's going to mess + // up our keyboard input. For now we'll just let the user + // decide. + if (m_leaveForegroundOption) { + return NULL; + } + return GetForegroundWindow(); +} diff --git a/lib/platform/CMSWindowsDesks.h b/lib/platform/CMSWindowsDesks.h new file mode 100644 index 00000000..f0388f98 --- /dev/null +++ b/lib/platform/CMSWindowsDesks.h @@ -0,0 +1,296 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSDESKS_H +#define CMSWINDOWSDESKS_H + +#include "CSynergyHook.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "CString.h" +#include "stdmap.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEvent; +class CEventQueueTimer; +class CThread; +class IJob; +class IScreenSaver; + +//! Microsoft Windows desk handling +/*! +Desks in Microsoft Windows are only remotely like desktops on X11 +systems. A desk is another virtual surface for windows but desks +impose serious restrictions: a thread can interact with only one +desk at a time, you can't switch desks if the thread has any hooks +installed or owns any windows, windows cannot exist on multiple +desks at once, etc. Basically, they're useless except for running +the login window or the screensaver, which is what they're used +for. Synergy must deal with them mainly because of the login +window and screensaver but users can create their own desks and +synergy should work on those too. + +This class encapsulates all the desk nastiness. Clients of this +object don't have to know anything about desks. +*/ +class CMSWindowsDesks { +public: + //! Constructor + /*! + \p isPrimary is true iff the desk is for a primary screen. + \p screensaver points to a screensaver object and it's used + only to check if the screensaver is active. The \p updateKeys + job is adopted and is called when the key state should be + updated in a thread attached to the current desk. + \p hookLibrary must be a handle to the hook library. + */ + CMSWindowsDesks(bool isPrimary, HINSTANCE hookLibrary, + const IScreenSaver* screensaver, IJob* updateKeys); + ~CMSWindowsDesks(); + + //! @name manipulators + //@{ + + //! Enable desk tracking + /*! + Enables desk tracking. While enabled, this object checks to see + if the desk has changed and ensures that the hooks are installed + on the new desk. \c setShape should be called at least once + before calling \c enable. + */ + void enable(); + + //! Disable desk tracking + /*! + Disables desk tracking. \sa enable. + */ + void disable(); + + //! Notify of entering a desk + /*! + Prepares a desk for when the cursor enters it. + */ + void enter(); + + //! Notify of leaving a desk + /*! + Prepares a desk for when the cursor leaves it. + */ + void leave(HKL keyLayout); + + //! Notify of options changes + /*! + Resets all options to their default values. + */ + void resetOptions(); + + //! Notify of options changes + /*! + Set options to given values. Ignores unknown options and doesn't + modify options that aren't given in \c options. + */ + void setOptions(const COptionsList& options); + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state and current keyboard mapping. + */ + void updateKeys(); + + //! Tell desk about new size + /*! + This tells the desks that the display size has changed. + */ + void setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon); + + //! Install/uninstall screensaver hooks + /*! + If \p install is true then the screensaver hooks are installed and, + if desk tracking is enabled, updated whenever the desk changes. If + \p install is false then the screensaver hooks are uninstalled. + */ + void installScreensaverHooks(bool install); + + //! Start ignoring user input + /*! + Starts ignoring user input so we don't pick up our own synthesized events. + */ + void fakeInputBegin(); + + //! Stop ignoring user input + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + void getCursorPos(SInt32& x, SInt32& y) const; + + //! Fake key press/release + /*! + Synthesize a press or release of key \c button. + */ + void fakeKeyEvent(KeyButton button, UINT virtualKey, + bool press, bool isAutoRepeat) const; + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + void fakeMouseButton(ButtonID id, bool press) const; + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + void fakeMouseMove(SInt32 x, SInt32 y) const; + + //! Fake mouse move + /*! + Synthesize a mouse move to the relative coordinates \c dx,dy. + */ + void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c delta in direction \c axis. + */ + void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + //@} + +private: + class CDesk { + public: + CString m_name; + CThread* m_thread; + DWORD m_threadID; + DWORD m_targetID; + HDESK m_desk; + HWND m_window; + HWND m_foregroundWindow; + bool m_lowLevel; + }; + typedef std::map CDesks; + + // initialization and shutdown operations + void queryHookLibrary(HINSTANCE hookLibrary); + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // message handlers + void deskMouseMove(SInt32 x, SInt32 y) const; + void deskMouseRelativeMove(SInt32 dx, SInt32 dy) const; + void deskEnter(CDesk* desk); + void deskLeave(CDesk* desk, HKL keyLayout); + void deskThread(void* vdesk); + + // desk switch checking and handling + CDesk* addDesk(const CString& name, HDESK hdesk); + void removeDesks(); + void checkDesk(); + bool isDeskAccessible(const CDesk* desk) const; + void handleCheckDesk(const CEvent& event, void*); + + // communication with desk threads + void waitForDesk() const; + void sendMessage(UINT, WPARAM, LPARAM) const; + + // work around for messed up keyboard events from low-level hooks + HWND getForegroundWindow() const; + + // desk API wrappers + HDESK openInputDesktop(); + void closeDesktop(HDESK); + CString getDesktopName(HDESK); + + // our desk window procs + static LRESULT CALLBACK primaryDeskProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); + +private: + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if windows 95/98/me + bool m_is95Family; + + // true if windows 98/2k or higher (i.e. not 95/nt) + bool m_isModernFamily; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_deskClass; + HCURSOR m_cursor; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // the timer used to check for desktop switching + CEventQueueTimer* m_timer; + + // screen saver stuff + DWORD m_threadID; + const IScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // the current desk and it's name + CDesk* m_activeDesk; + CString m_activeDeskName; + + // one desk per desktop and a cond var to communicate with it + CMutex m_mutex; + CCondVar m_deskReady; + CDesks m_desks; + + // hook library stuff + InstallFunc m_install; + UninstallFunc m_uninstall; + InstallScreenSaverFunc m_installScreensaver; + UninstallScreenSaverFunc m_uninstallScreensaver; + + // keyboard stuff + IJob* m_updateKeys; + HKL m_keyLayout; + + // options + bool m_leaveForegroundOption; +}; + +#endif diff --git a/lib/platform/CMSWindowsEventQueueBuffer.cpp b/lib/platform/CMSWindowsEventQueueBuffer.cpp new file mode 100644 index 00000000..5bab2855 --- /dev/null +++ b/lib/platform/CMSWindowsEventQueueBuffer.cpp @@ -0,0 +1,138 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsEventQueueBuffer.h" +#include "CThread.h" +#include "IEventQueue.h" +#include "CArchMiscWindows.h" + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + + +// +// CMSWindowsEventQueueBuffer +// + +CMSWindowsEventQueueBuffer::CMSWindowsEventQueueBuffer() +{ + // remember thread. we'll be posting messages to it. + m_thread = GetCurrentThreadId(); + + // create a message type for custom events + m_userEvent = RegisterWindowMessage("SYNERGY_USER_EVENT"); + + // get message type for daemon quit + m_daemonQuit = CArchMiscWindows::getDaemonQuitMessage(); + + // make sure this thread has a message queue + MSG dummy; + PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); +} + +CMSWindowsEventQueueBuffer::~CMSWindowsEventQueueBuffer() +{ + // do nothing +} + +void +CMSWindowsEventQueueBuffer::waitForEvent(double timeout) +{ + // check if messages are available first. if we don't do this then + // MsgWaitForMultipleObjects() will block even if the queue isn't + // empty if the messages in the queue were there before the last + // call to GetMessage()/PeekMessage(). + if (HIWORD(GetQueueStatus(QS_ALLINPUT)) != 0) { + return; + } + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for a message. we cannot be interrupted by thread + // cancellation but that's okay because we're run in the main + // thread and we never cancel that thread. + HANDLE dummy[1]; + MsgWaitForMultipleObjects(0, dummy, FALSE, t, QS_ALLINPUT); +} + +IEventQueueBuffer::Type +CMSWindowsEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + // peek at messages first. waiting for QS_ALLINPUT will return + // if a message has been sent to our window but GetMessage will + // dispatch that message behind our backs and block. PeekMessage + // will also dispatch behind our backs but won't block. + if (!PeekMessage(&m_event, NULL, 0, 0, PM_NOREMOVE) && + !PeekMessage(&m_event, (HWND)-1, 0, 0, PM_NOREMOVE)) { + return kNone; + } + + // BOOL. yeah, right. + BOOL result = GetMessage(&m_event, NULL, 0, 0); + if (result == -1) { + return kNone; + } + else if (result == 0) { + event = CEvent(CEvent::kQuit); + return kSystem; + } + else if (m_daemonQuit != 0 && m_event.message == m_daemonQuit) { + event = CEvent(CEvent::kQuit); + return kSystem; + } + else if (m_event.message == m_userEvent) { + dataID = static_cast(m_event.wParam); + return kUser; + } + else { + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; + } +} + +bool +CMSWindowsEventQueueBuffer::addEvent(UInt32 dataID) +{ + return (PostThreadMessage(m_thread, m_userEvent, + static_cast(dataID), 0) != 0); +} + +bool +CMSWindowsEventQueueBuffer::isEmpty() const +{ + return (HIWORD(GetQueueStatus(QS_ALLINPUT)) == 0); +} + +CEventQueueTimer* +CMSWindowsEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CMSWindowsEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/platform/CMSWindowsEventQueueBuffer.h b/lib/platform/CMSWindowsEventQueueBuffer.h new file mode 100644 index 00000000..28d8a2f6 --- /dev/null +++ b/lib/platform/CMSWindowsEventQueueBuffer.h @@ -0,0 +1,44 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSEVENTQUEUEBUFFER_H +#define CMSWINDOWSEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#define WIN32_LEAN_AND_MEAN +#include + +//! Event queue buffer for Win32 +class CMSWindowsEventQueueBuffer : public IEventQueueBuffer { +public: + CMSWindowsEventQueueBuffer(); + virtual ~CMSWindowsEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + DWORD m_thread; + UINT m_userEvent; + MSG m_event; + UINT m_daemonQuit; +}; + +#endif diff --git a/lib/platform/CMSWindowsKeyState.cpp b/lib/platform/CMSWindowsKeyState.cpp new file mode 100644 index 00000000..ba105530 --- /dev/null +++ b/lib/platform/CMSWindowsKeyState.cpp @@ -0,0 +1,1420 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsKeyState.h" +#include "CMSWindowsDesks.h" +#include "CThread.h" +#include "CFunctionJob.h" +#include "CLog.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "CArchMiscWindows.h" + +// extended mouse buttons +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// +// CMSWindowsKeyState +// + +// map virtual keys to synergy key enumeration +const KeyID CMSWindowsKeyState::s_virtualKey[] = +{ + /* 0x000 */ { kKeyNone }, // reserved + /* 0x001 */ { kKeyNone }, // VK_LBUTTON + /* 0x002 */ { kKeyNone }, // VK_RBUTTON + /* 0x003 */ { kKeyNone }, // VK_CANCEL + /* 0x004 */ { kKeyNone }, // VK_MBUTTON + /* 0x005 */ { kKeyNone }, // VK_XBUTTON1 + /* 0x006 */ { kKeyNone }, // VK_XBUTTON2 + /* 0x007 */ { kKeyNone }, // undefined + /* 0x008 */ { kKeyBackSpace }, // VK_BACK + /* 0x009 */ { kKeyTab }, // VK_TAB + /* 0x00a */ { kKeyNone }, // undefined + /* 0x00b */ { kKeyNone }, // undefined + /* 0x00c */ { kKeyClear }, // VK_CLEAR + /* 0x00d */ { kKeyReturn }, // VK_RETURN + /* 0x00e */ { kKeyNone }, // undefined + /* 0x00f */ { kKeyNone }, // undefined + /* 0x010 */ { kKeyShift_L }, // VK_SHIFT + /* 0x011 */ { kKeyControl_L }, // VK_CONTROL + /* 0x012 */ { kKeyAlt_L }, // VK_MENU + /* 0x013 */ { kKeyPause }, // VK_PAUSE + /* 0x014 */ { kKeyCapsLock }, // VK_CAPITAL + /* 0x015 */ { kKeyHangulKana }, // VK_HANGUL, VK_KANA + /* 0x016 */ { kKeyNone }, // undefined + /* 0x017 */ { kKeyNone }, // VK_JUNJA + /* 0x018 */ { kKeyNone }, // VK_FINAL + /* 0x019 */ { kKeyHanjaKanzi }, // VK_KANJI + /* 0x01a */ { kKeyNone }, // undefined + /* 0x01b */ { kKeyEscape }, // VK_ESCAPE + /* 0x01c */ { kKeyHenkan }, // VK_CONVERT + /* 0x01d */ { kKeyNone }, // VK_NONCONVERT + /* 0x01e */ { kKeyNone }, // VK_ACCEPT + /* 0x01f */ { kKeyNone }, // VK_MODECHANGE + /* 0x020 */ { kKeyNone }, // VK_SPACE + /* 0x021 */ { kKeyKP_PageUp }, // VK_PRIOR + /* 0x022 */ { kKeyKP_PageDown },// VK_NEXT + /* 0x023 */ { kKeyKP_End }, // VK_END + /* 0x024 */ { kKeyKP_Home }, // VK_HOME + /* 0x025 */ { kKeyKP_Left }, // VK_LEFT + /* 0x026 */ { kKeyKP_Up }, // VK_UP + /* 0x027 */ { kKeyKP_Right }, // VK_RIGHT + /* 0x028 */ { kKeyKP_Down }, // VK_DOWN + /* 0x029 */ { kKeySelect }, // VK_SELECT + /* 0x02a */ { kKeyNone }, // VK_PRINT + /* 0x02b */ { kKeyExecute }, // VK_EXECUTE + /* 0x02c */ { kKeyPrint }, // VK_SNAPSHOT + /* 0x02d */ { kKeyKP_Insert }, // VK_INSERT + /* 0x02e */ { kKeyKP_Delete }, // VK_DELETE + /* 0x02f */ { kKeyHelp }, // VK_HELP + /* 0x030 */ { kKeyNone }, // VK_0 + /* 0x031 */ { kKeyNone }, // VK_1 + /* 0x032 */ { kKeyNone }, // VK_2 + /* 0x033 */ { kKeyNone }, // VK_3 + /* 0x034 */ { kKeyNone }, // VK_4 + /* 0x035 */ { kKeyNone }, // VK_5 + /* 0x036 */ { kKeyNone }, // VK_6 + /* 0x037 */ { kKeyNone }, // VK_7 + /* 0x038 */ { kKeyNone }, // VK_8 + /* 0x039 */ { kKeyNone }, // VK_9 + /* 0x03a */ { kKeyNone }, // undefined + /* 0x03b */ { kKeyNone }, // undefined + /* 0x03c */ { kKeyNone }, // undefined + /* 0x03d */ { kKeyNone }, // undefined + /* 0x03e */ { kKeyNone }, // undefined + /* 0x03f */ { kKeyNone }, // undefined + /* 0x040 */ { kKeyNone }, // undefined + /* 0x041 */ { kKeyNone }, // VK_A + /* 0x042 */ { kKeyNone }, // VK_B + /* 0x043 */ { kKeyNone }, // VK_C + /* 0x044 */ { kKeyNone }, // VK_D + /* 0x045 */ { kKeyNone }, // VK_E + /* 0x046 */ { kKeyNone }, // VK_F + /* 0x047 */ { kKeyNone }, // VK_G + /* 0x048 */ { kKeyNone }, // VK_H + /* 0x049 */ { kKeyNone }, // VK_I + /* 0x04a */ { kKeyNone }, // VK_J + /* 0x04b */ { kKeyNone }, // VK_K + /* 0x04c */ { kKeyNone }, // VK_L + /* 0x04d */ { kKeyNone }, // VK_M + /* 0x04e */ { kKeyNone }, // VK_N + /* 0x04f */ { kKeyNone }, // VK_O + /* 0x050 */ { kKeyNone }, // VK_P + /* 0x051 */ { kKeyNone }, // VK_Q + /* 0x052 */ { kKeyNone }, // VK_R + /* 0x053 */ { kKeyNone }, // VK_S + /* 0x054 */ { kKeyNone }, // VK_T + /* 0x055 */ { kKeyNone }, // VK_U + /* 0x056 */ { kKeyNone }, // VK_V + /* 0x057 */ { kKeyNone }, // VK_W + /* 0x058 */ { kKeyNone }, // VK_X + /* 0x059 */ { kKeyNone }, // VK_Y + /* 0x05a */ { kKeyNone }, // VK_Z + /* 0x05b */ { kKeySuper_L }, // VK_LWIN + /* 0x05c */ { kKeySuper_R }, // VK_RWIN + /* 0x05d */ { kKeyMenu }, // VK_APPS + /* 0x05e */ { kKeyNone }, // undefined + /* 0x05f */ { kKeySleep }, // VK_SLEEP + /* 0x060 */ { kKeyKP_0 }, // VK_NUMPAD0 + /* 0x061 */ { kKeyKP_1 }, // VK_NUMPAD1 + /* 0x062 */ { kKeyKP_2 }, // VK_NUMPAD2 + /* 0x063 */ { kKeyKP_3 }, // VK_NUMPAD3 + /* 0x064 */ { kKeyKP_4 }, // VK_NUMPAD4 + /* 0x065 */ { kKeyKP_5 }, // VK_NUMPAD5 + /* 0x066 */ { kKeyKP_6 }, // VK_NUMPAD6 + /* 0x067 */ { kKeyKP_7 }, // VK_NUMPAD7 + /* 0x068 */ { kKeyKP_8 }, // VK_NUMPAD8 + /* 0x069 */ { kKeyKP_9 }, // VK_NUMPAD9 + /* 0x06a */ { kKeyKP_Multiply },// VK_MULTIPLY + /* 0x06b */ { kKeyKP_Add }, // VK_ADD + /* 0x06c */ { kKeyKP_Separator },// VK_SEPARATOR + /* 0x06d */ { kKeyKP_Subtract },// VK_SUBTRACT + /* 0x06e */ { kKeyKP_Decimal }, // VK_DECIMAL + /* 0x06f */ { kKeyNone }, // VK_DIVIDE + /* 0x070 */ { kKeyF1 }, // VK_F1 + /* 0x071 */ { kKeyF2 }, // VK_F2 + /* 0x072 */ { kKeyF3 }, // VK_F3 + /* 0x073 */ { kKeyF4 }, // VK_F4 + /* 0x074 */ { kKeyF5 }, // VK_F5 + /* 0x075 */ { kKeyF6 }, // VK_F6 + /* 0x076 */ { kKeyF7 }, // VK_F7 + /* 0x077 */ { kKeyF8 }, // VK_F8 + /* 0x078 */ { kKeyF9 }, // VK_F9 + /* 0x079 */ { kKeyF10 }, // VK_F10 + /* 0x07a */ { kKeyF11 }, // VK_F11 + /* 0x07b */ { kKeyF12 }, // VK_F12 + /* 0x07c */ { kKeyF13 }, // VK_F13 + /* 0x07d */ { kKeyF14 }, // VK_F14 + /* 0x07e */ { kKeyF15 }, // VK_F15 + /* 0x07f */ { kKeyF16 }, // VK_F16 + /* 0x080 */ { kKeyF17 }, // VK_F17 + /* 0x081 */ { kKeyF18 }, // VK_F18 + /* 0x082 */ { kKeyF19 }, // VK_F19 + /* 0x083 */ { kKeyF20 }, // VK_F20 + /* 0x084 */ { kKeyF21 }, // VK_F21 + /* 0x085 */ { kKeyF22 }, // VK_F22 + /* 0x086 */ { kKeyF23 }, // VK_F23 + /* 0x087 */ { kKeyF24 }, // VK_F24 + /* 0x088 */ { kKeyNone }, // unassigned + /* 0x089 */ { kKeyNone }, // unassigned + /* 0x08a */ { kKeyNone }, // unassigned + /* 0x08b */ { kKeyNone }, // unassigned + /* 0x08c */ { kKeyNone }, // unassigned + /* 0x08d */ { kKeyNone }, // unassigned + /* 0x08e */ { kKeyNone }, // unassigned + /* 0x08f */ { kKeyNone }, // unassigned + /* 0x090 */ { kKeyNumLock }, // VK_NUMLOCK + /* 0x091 */ { kKeyScrollLock }, // VK_SCROLL + /* 0x092 */ { kKeyNone }, // unassigned + /* 0x093 */ { kKeyNone }, // unassigned + /* 0x094 */ { kKeyNone }, // unassigned + /* 0x095 */ { kKeyNone }, // unassigned + /* 0x096 */ { kKeyNone }, // unassigned + /* 0x097 */ { kKeyNone }, // unassigned + /* 0x098 */ { kKeyNone }, // unassigned + /* 0x099 */ { kKeyNone }, // unassigned + /* 0x09a */ { kKeyNone }, // unassigned + /* 0x09b */ { kKeyNone }, // unassigned + /* 0x09c */ { kKeyNone }, // unassigned + /* 0x09d */ { kKeyNone }, // unassigned + /* 0x09e */ { kKeyNone }, // unassigned + /* 0x09f */ { kKeyNone }, // unassigned + /* 0x0a0 */ { kKeyShift_L }, // VK_LSHIFT + /* 0x0a1 */ { kKeyShift_R }, // VK_RSHIFT + /* 0x0a2 */ { kKeyControl_L }, // VK_LCONTROL + /* 0x0a3 */ { kKeyControl_R }, // VK_RCONTROL + /* 0x0a4 */ { kKeyAlt_L }, // VK_LMENU + /* 0x0a5 */ { kKeyAlt_R }, // VK_RMENU + /* 0x0a6 */ { kKeyNone }, // VK_BROWSER_BACK + /* 0x0a7 */ { kKeyNone }, // VK_BROWSER_FORWARD + /* 0x0a8 */ { kKeyNone }, // VK_BROWSER_REFRESH + /* 0x0a9 */ { kKeyNone }, // VK_BROWSER_STOP + /* 0x0aa */ { kKeyNone }, // VK_BROWSER_SEARCH + /* 0x0ab */ { kKeyNone }, // VK_BROWSER_FAVORITES + /* 0x0ac */ { kKeyNone }, // VK_BROWSER_HOME + /* 0x0ad */ { kKeyNone }, // VK_VOLUME_MUTE + /* 0x0ae */ { kKeyNone }, // VK_VOLUME_DOWN + /* 0x0af */ { kKeyNone }, // VK_VOLUME_UP + /* 0x0b0 */ { kKeyNone }, // VK_MEDIA_NEXT_TRACK + /* 0x0b1 */ { kKeyNone }, // VK_MEDIA_PREV_TRACK + /* 0x0b2 */ { kKeyNone }, // VK_MEDIA_STOP + /* 0x0b3 */ { kKeyNone }, // VK_MEDIA_PLAY_PAUSE + /* 0x0b4 */ { kKeyNone }, // VK_LAUNCH_MAIL + /* 0x0b5 */ { kKeyNone }, // VK_LAUNCH_MEDIA_SELECT + /* 0x0b6 */ { kKeyNone }, // VK_LAUNCH_APP1 + /* 0x0b7 */ { kKeyNone }, // VK_LAUNCH_APP2 + /* 0x0b8 */ { kKeyNone }, // unassigned + /* 0x0b9 */ { kKeyNone }, // unassigned + /* 0x0ba */ { kKeyNone }, // OEM specific + /* 0x0bb */ { kKeyNone }, // OEM specific + /* 0x0bc */ { kKeyNone }, // OEM specific + /* 0x0bd */ { kKeyNone }, // OEM specific + /* 0x0be */ { kKeyNone }, // OEM specific + /* 0x0bf */ { kKeyNone }, // OEM specific + /* 0x0c0 */ { kKeyNone }, // OEM specific + /* 0x0c1 */ { kKeyNone }, // unassigned + /* 0x0c2 */ { kKeyNone }, // unassigned + /* 0x0c3 */ { kKeyNone }, // unassigned + /* 0x0c4 */ { kKeyNone }, // unassigned + /* 0x0c5 */ { kKeyNone }, // unassigned + /* 0x0c6 */ { kKeyNone }, // unassigned + /* 0x0c7 */ { kKeyNone }, // unassigned + /* 0x0c8 */ { kKeyNone }, // unassigned + /* 0x0c9 */ { kKeyNone }, // unassigned + /* 0x0ca */ { kKeyNone }, // unassigned + /* 0x0cb */ { kKeyNone }, // unassigned + /* 0x0cc */ { kKeyNone }, // unassigned + /* 0x0cd */ { kKeyNone }, // unassigned + /* 0x0ce */ { kKeyNone }, // unassigned + /* 0x0cf */ { kKeyNone }, // unassigned + /* 0x0d0 */ { kKeyNone }, // unassigned + /* 0x0d1 */ { kKeyNone }, // unassigned + /* 0x0d2 */ { kKeyNone }, // unassigned + /* 0x0d3 */ { kKeyNone }, // unassigned + /* 0x0d4 */ { kKeyNone }, // unassigned + /* 0x0d5 */ { kKeyNone }, // unassigned + /* 0x0d6 */ { kKeyNone }, // unassigned + /* 0x0d7 */ { kKeyNone }, // unassigned + /* 0x0d8 */ { kKeyNone }, // unassigned + /* 0x0d9 */ { kKeyNone }, // unassigned + /* 0x0da */ { kKeyNone }, // unassigned + /* 0x0db */ { kKeyNone }, // OEM specific + /* 0x0dc */ { kKeyNone }, // OEM specific + /* 0x0dd */ { kKeyNone }, // OEM specific + /* 0x0de */ { kKeyNone }, // OEM specific + /* 0x0df */ { kKeyNone }, // OEM specific + /* 0x0e0 */ { kKeyNone }, // OEM specific + /* 0x0e1 */ { kKeyNone }, // OEM specific + /* 0x0e2 */ { kKeyNone }, // OEM specific + /* 0x0e3 */ { kKeyNone }, // OEM specific + /* 0x0e4 */ { kKeyNone }, // OEM specific + /* 0x0e5 */ { kKeyNone }, // unassigned + /* 0x0e6 */ { kKeyNone }, // OEM specific + /* 0x0e7 */ { kKeyNone }, // unassigned + /* 0x0e8 */ { kKeyNone }, // unassigned + /* 0x0e9 */ { kKeyNone }, // OEM specific + /* 0x0ea */ { kKeyNone }, // OEM specific + /* 0x0eb */ { kKeyNone }, // OEM specific + /* 0x0ec */ { kKeyNone }, // OEM specific + /* 0x0ed */ { kKeyNone }, // OEM specific + /* 0x0ee */ { kKeyNone }, // OEM specific + /* 0x0ef */ { kKeyNone }, // OEM specific + /* 0x0f0 */ { kKeyNone }, // OEM specific + /* 0x0f1 */ { kKeyNone }, // OEM specific + /* 0x0f2 */ { kKeyHiraganaKatakana }, // VK_OEM_COPY + /* 0x0f3 */ { kKeyZenkaku }, // VK_OEM_AUTO + /* 0x0f4 */ { kKeyZenkaku }, // VK_OEM_ENLW + /* 0x0f5 */ { kKeyNone }, // OEM specific + /* 0x0f6 */ { kKeyNone }, // VK_ATTN + /* 0x0f7 */ { kKeyNone }, // VK_CRSEL + /* 0x0f8 */ { kKeyNone }, // VK_EXSEL + /* 0x0f9 */ { kKeyNone }, // VK_EREOF + /* 0x0fa */ { kKeyNone }, // VK_PLAY + /* 0x0fb */ { kKeyNone }, // VK_ZOOM + /* 0x0fc */ { kKeyNone }, // reserved + /* 0x0fd */ { kKeyNone }, // VK_PA1 + /* 0x0fe */ { kKeyNone }, // VK_OEM_CLEAR + /* 0x0ff */ { kKeyNone }, // reserved + + /* 0x100 */ { kKeyNone }, // reserved + /* 0x101 */ { kKeyNone }, // VK_LBUTTON + /* 0x102 */ { kKeyNone }, // VK_RBUTTON + /* 0x103 */ { kKeyBreak }, // VK_CANCEL + /* 0x104 */ { kKeyNone }, // VK_MBUTTON + /* 0x105 */ { kKeyNone }, // VK_XBUTTON1 + /* 0x106 */ { kKeyNone }, // VK_XBUTTON2 + /* 0x107 */ { kKeyNone }, // undefined + /* 0x108 */ { kKeyNone }, // VK_BACK + /* 0x109 */ { kKeyNone }, // VK_TAB + /* 0x10a */ { kKeyNone }, // undefined + /* 0x10b */ { kKeyNone }, // undefined + /* 0x10c */ { kKeyClear }, // VK_CLEAR + /* 0x10d */ { kKeyKP_Enter }, // VK_RETURN + /* 0x10e */ { kKeyNone }, // undefined + /* 0x10f */ { kKeyNone }, // undefined + /* 0x110 */ { kKeyShift_R }, // VK_SHIFT + /* 0x111 */ { kKeyControl_R }, // VK_CONTROL + /* 0x112 */ { kKeyAlt_R }, // VK_MENU + /* 0x113 */ { kKeyNone }, // VK_PAUSE + /* 0x114 */ { kKeyNone }, // VK_CAPITAL + /* 0x115 */ { kKeyNone }, // VK_KANA + /* 0x116 */ { kKeyNone }, // VK_HANGUL + /* 0x117 */ { kKeyNone }, // VK_JUNJA + /* 0x118 */ { kKeyNone }, // VK_FINAL + /* 0x119 */ { kKeyNone }, // VK_KANJI + /* 0x11a */ { kKeyNone }, // undefined + /* 0x11b */ { kKeyNone }, // VK_ESCAPE + /* 0x11c */ { kKeyNone }, // VK_CONVERT + /* 0x11d */ { kKeyNone }, // VK_NONCONVERT + /* 0x11e */ { kKeyNone }, // VK_ACCEPT + /* 0x11f */ { kKeyNone }, // VK_MODECHANGE + /* 0x120 */ { kKeyNone }, // VK_SPACE + /* 0x121 */ { kKeyPageUp }, // VK_PRIOR + /* 0x122 */ { kKeyPageDown }, // VK_NEXT + /* 0x123 */ { kKeyEnd }, // VK_END + /* 0x124 */ { kKeyHome }, // VK_HOME + /* 0x125 */ { kKeyLeft }, // VK_LEFT + /* 0x126 */ { kKeyUp }, // VK_UP + /* 0x127 */ { kKeyRight }, // VK_RIGHT + /* 0x128 */ { kKeyDown }, // VK_DOWN + /* 0x129 */ { kKeySelect }, // VK_SELECT + /* 0x12a */ { kKeyNone }, // VK_PRINT + /* 0x12b */ { kKeyExecute }, // VK_EXECUTE + /* 0x12c */ { kKeyPrint }, // VK_SNAPSHOT + /* 0x12d */ { kKeyInsert }, // VK_INSERT + /* 0x12e */ { kKeyDelete }, // VK_DELETE + /* 0x12f */ { kKeyHelp }, // VK_HELP + /* 0x130 */ { kKeyNone }, // VK_0 + /* 0x131 */ { kKeyNone }, // VK_1 + /* 0x132 */ { kKeyNone }, // VK_2 + /* 0x133 */ { kKeyNone }, // VK_3 + /* 0x134 */ { kKeyNone }, // VK_4 + /* 0x135 */ { kKeyNone }, // VK_5 + /* 0x136 */ { kKeyNone }, // VK_6 + /* 0x137 */ { kKeyNone }, // VK_7 + /* 0x138 */ { kKeyNone }, // VK_8 + /* 0x139 */ { kKeyNone }, // VK_9 + /* 0x13a */ { kKeyNone }, // undefined + /* 0x13b */ { kKeyNone }, // undefined + /* 0x13c */ { kKeyNone }, // undefined + /* 0x13d */ { kKeyNone }, // undefined + /* 0x13e */ { kKeyNone }, // undefined + /* 0x13f */ { kKeyNone }, // undefined + /* 0x140 */ { kKeyNone }, // undefined + /* 0x141 */ { kKeyNone }, // VK_A + /* 0x142 */ { kKeyNone }, // VK_B + /* 0x143 */ { kKeyNone }, // VK_C + /* 0x144 */ { kKeyNone }, // VK_D + /* 0x145 */ { kKeyNone }, // VK_E + /* 0x146 */ { kKeyNone }, // VK_F + /* 0x147 */ { kKeyNone }, // VK_G + /* 0x148 */ { kKeyNone }, // VK_H + /* 0x149 */ { kKeyNone }, // VK_I + /* 0x14a */ { kKeyNone }, // VK_J + /* 0x14b */ { kKeyNone }, // VK_K + /* 0x14c */ { kKeyNone }, // VK_L + /* 0x14d */ { kKeyNone }, // VK_M + /* 0x14e */ { kKeyNone }, // VK_N + /* 0x14f */ { kKeyNone }, // VK_O + /* 0x150 */ { kKeyNone }, // VK_P + /* 0x151 */ { kKeyNone }, // VK_Q + /* 0x152 */ { kKeyNone }, // VK_R + /* 0x153 */ { kKeyNone }, // VK_S + /* 0x154 */ { kKeyNone }, // VK_T + /* 0x155 */ { kKeyNone }, // VK_U + /* 0x156 */ { kKeyNone }, // VK_V + /* 0x157 */ { kKeyNone }, // VK_W + /* 0x158 */ { kKeyNone }, // VK_X + /* 0x159 */ { kKeyNone }, // VK_Y + /* 0x15a */ { kKeyNone }, // VK_Z + /* 0x15b */ { kKeySuper_L }, // VK_LWIN + /* 0x15c */ { kKeySuper_R }, // VK_RWIN + /* 0x15d */ { kKeyMenu }, // VK_APPS + /* 0x15e */ { kKeyNone }, // undefined + /* 0x15f */ { kKeyNone }, // VK_SLEEP + /* 0x160 */ { kKeyNone }, // VK_NUMPAD0 + /* 0x161 */ { kKeyNone }, // VK_NUMPAD1 + /* 0x162 */ { kKeyNone }, // VK_NUMPAD2 + /* 0x163 */ { kKeyNone }, // VK_NUMPAD3 + /* 0x164 */ { kKeyNone }, // VK_NUMPAD4 + /* 0x165 */ { kKeyNone }, // VK_NUMPAD5 + /* 0x166 */ { kKeyNone }, // VK_NUMPAD6 + /* 0x167 */ { kKeyNone }, // VK_NUMPAD7 + /* 0x168 */ { kKeyNone }, // VK_NUMPAD8 + /* 0x169 */ { kKeyNone }, // VK_NUMPAD9 + /* 0x16a */ { kKeyNone }, // VK_MULTIPLY + /* 0x16b */ { kKeyNone }, // VK_ADD + /* 0x16c */ { kKeyKP_Separator },// VK_SEPARATOR + /* 0x16d */ { kKeyNone }, // VK_SUBTRACT + /* 0x16e */ { kKeyNone }, // VK_DECIMAL + /* 0x16f */ { kKeyKP_Divide }, // VK_DIVIDE + /* 0x170 */ { kKeyNone }, // VK_F1 + /* 0x171 */ { kKeyNone }, // VK_F2 + /* 0x172 */ { kKeyNone }, // VK_F3 + /* 0x173 */ { kKeyNone }, // VK_F4 + /* 0x174 */ { kKeyNone }, // VK_F5 + /* 0x175 */ { kKeyNone }, // VK_F6 + /* 0x176 */ { kKeyNone }, // VK_F7 + /* 0x177 */ { kKeyNone }, // VK_F8 + /* 0x178 */ { kKeyNone }, // VK_F9 + /* 0x179 */ { kKeyNone }, // VK_F10 + /* 0x17a */ { kKeyNone }, // VK_F11 + /* 0x17b */ { kKeyNone }, // VK_F12 + /* 0x17c */ { kKeyF13 }, // VK_F13 + /* 0x17d */ { kKeyF14 }, // VK_F14 + /* 0x17e */ { kKeyF15 }, // VK_F15 + /* 0x17f */ { kKeyF16 }, // VK_F16 + /* 0x180 */ { kKeyF17 }, // VK_F17 + /* 0x181 */ { kKeyF18 }, // VK_F18 + /* 0x182 */ { kKeyF19 }, // VK_F19 + /* 0x183 */ { kKeyF20 }, // VK_F20 + /* 0x184 */ { kKeyF21 }, // VK_F21 + /* 0x185 */ { kKeyF22 }, // VK_F22 + /* 0x186 */ { kKeyF23 }, // VK_F23 + /* 0x187 */ { kKeyF24 }, // VK_F24 + /* 0x188 */ { kKeyNone }, // unassigned + /* 0x189 */ { kKeyNone }, // unassigned + /* 0x18a */ { kKeyNone }, // unassigned + /* 0x18b */ { kKeyNone }, // unassigned + /* 0x18c */ { kKeyNone }, // unassigned + /* 0x18d */ { kKeyNone }, // unassigned + /* 0x18e */ { kKeyNone }, // unassigned + /* 0x18f */ { kKeyNone }, // unassigned + /* 0x190 */ { kKeyNumLock }, // VK_NUMLOCK + /* 0x191 */ { kKeyNone }, // VK_SCROLL + /* 0x192 */ { kKeyNone }, // unassigned + /* 0x193 */ { kKeyNone }, // unassigned + /* 0x194 */ { kKeyNone }, // unassigned + /* 0x195 */ { kKeyNone }, // unassigned + /* 0x196 */ { kKeyNone }, // unassigned + /* 0x197 */ { kKeyNone }, // unassigned + /* 0x198 */ { kKeyNone }, // unassigned + /* 0x199 */ { kKeyNone }, // unassigned + /* 0x19a */ { kKeyNone }, // unassigned + /* 0x19b */ { kKeyNone }, // unassigned + /* 0x19c */ { kKeyNone }, // unassigned + /* 0x19d */ { kKeyNone }, // unassigned + /* 0x19e */ { kKeyNone }, // unassigned + /* 0x19f */ { kKeyNone }, // unassigned + /* 0x1a0 */ { kKeyShift_L }, // VK_LSHIFT + /* 0x1a1 */ { kKeyShift_R }, // VK_RSHIFT + /* 0x1a2 */ { kKeyControl_L }, // VK_LCONTROL + /* 0x1a3 */ { kKeyControl_R }, // VK_RCONTROL + /* 0x1a4 */ { kKeyAlt_L }, // VK_LMENU + /* 0x1a5 */ { kKeyAlt_R }, // VK_RMENU + /* 0x1a6 */ { kKeyWWWBack }, // VK_BROWSER_BACK + /* 0x1a7 */ { kKeyWWWForward }, // VK_BROWSER_FORWARD + /* 0x1a8 */ { kKeyWWWRefresh }, // VK_BROWSER_REFRESH + /* 0x1a9 */ { kKeyWWWStop }, // VK_BROWSER_STOP + /* 0x1aa */ { kKeyWWWSearch }, // VK_BROWSER_SEARCH + /* 0x1ab */ { kKeyWWWFavorites },// VK_BROWSER_FAVORITES + /* 0x1ac */ { kKeyWWWHome }, // VK_BROWSER_HOME + /* 0x1ad */ { kKeyAudioMute }, // VK_VOLUME_MUTE + /* 0x1ae */ { kKeyAudioDown }, // VK_VOLUME_DOWN + /* 0x1af */ { kKeyAudioUp }, // VK_VOLUME_UP + /* 0x1b0 */ { kKeyAudioNext }, // VK_MEDIA_NEXT_TRACK + /* 0x1b1 */ { kKeyAudioPrev }, // VK_MEDIA_PREV_TRACK + /* 0x1b2 */ { kKeyAudioStop }, // VK_MEDIA_STOP + /* 0x1b3 */ { kKeyAudioPlay }, // VK_MEDIA_PLAY_PAUSE + /* 0x1b4 */ { kKeyAppMail }, // VK_LAUNCH_MAIL + /* 0x1b5 */ { kKeyAppMedia }, // VK_LAUNCH_MEDIA_SELECT + /* 0x1b6 */ { kKeyAppUser1 }, // VK_LAUNCH_APP1 + /* 0x1b7 */ { kKeyAppUser2 }, // VK_LAUNCH_APP2 + /* 0x1b8 */ { kKeyNone }, // unassigned + /* 0x1b9 */ { kKeyNone }, // unassigned + /* 0x1ba */ { kKeyNone }, // OEM specific + /* 0x1bb */ { kKeyNone }, // OEM specific + /* 0x1bc */ { kKeyNone }, // OEM specific + /* 0x1bd */ { kKeyNone }, // OEM specific + /* 0x1be */ { kKeyNone }, // OEM specific + /* 0x1bf */ { kKeyNone }, // OEM specific + /* 0x1c0 */ { kKeyNone }, // OEM specific + /* 0x1c1 */ { kKeyNone }, // unassigned + /* 0x1c2 */ { kKeyNone }, // unassigned + /* 0x1c3 */ { kKeyNone }, // unassigned + /* 0x1c4 */ { kKeyNone }, // unassigned + /* 0x1c5 */ { kKeyNone }, // unassigned + /* 0x1c6 */ { kKeyNone }, // unassigned + /* 0x1c7 */ { kKeyNone }, // unassigned + /* 0x1c8 */ { kKeyNone }, // unassigned + /* 0x1c9 */ { kKeyNone }, // unassigned + /* 0x1ca */ { kKeyNone }, // unassigned + /* 0x1cb */ { kKeyNone }, // unassigned + /* 0x1cc */ { kKeyNone }, // unassigned + /* 0x1cd */ { kKeyNone }, // unassigned + /* 0x1ce */ { kKeyNone }, // unassigned + /* 0x1cf */ { kKeyNone }, // unassigned + /* 0x1d0 */ { kKeyNone }, // unassigned + /* 0x1d1 */ { kKeyNone }, // unassigned + /* 0x1d2 */ { kKeyNone }, // unassigned + /* 0x1d3 */ { kKeyNone }, // unassigned + /* 0x1d4 */ { kKeyNone }, // unassigned + /* 0x1d5 */ { kKeyNone }, // unassigned + /* 0x1d6 */ { kKeyNone }, // unassigned + /* 0x1d7 */ { kKeyNone }, // unassigned + /* 0x1d8 */ { kKeyNone }, // unassigned + /* 0x1d9 */ { kKeyNone }, // unassigned + /* 0x1da */ { kKeyNone }, // unassigned + /* 0x1db */ { kKeyNone }, // OEM specific + /* 0x1dc */ { kKeyNone }, // OEM specific + /* 0x1dd */ { kKeyNone }, // OEM specific + /* 0x1de */ { kKeyNone }, // OEM specific + /* 0x1df */ { kKeyNone }, // OEM specific + /* 0x1e0 */ { kKeyNone }, // OEM specific + /* 0x1e1 */ { kKeyNone }, // OEM specific + /* 0x1e2 */ { kKeyNone }, // OEM specific + /* 0x1e3 */ { kKeyNone }, // OEM specific + /* 0x1e4 */ { kKeyNone }, // OEM specific + /* 0x1e5 */ { kKeyNone }, // unassigned + /* 0x1e6 */ { kKeyNone }, // OEM specific + /* 0x1e7 */ { kKeyNone }, // unassigned + /* 0x1e8 */ { kKeyNone }, // unassigned + /* 0x1e9 */ { kKeyNone }, // OEM specific + /* 0x1ea */ { kKeyNone }, // OEM specific + /* 0x1eb */ { kKeyNone }, // OEM specific + /* 0x1ec */ { kKeyNone }, // OEM specific + /* 0x1ed */ { kKeyNone }, // OEM specific + /* 0x1ee */ { kKeyNone }, // OEM specific + /* 0x1ef */ { kKeyNone }, // OEM specific + /* 0x1f0 */ { kKeyNone }, // OEM specific + /* 0x1f1 */ { kKeyNone }, // OEM specific + /* 0x1f2 */ { kKeyNone }, // VK_OEM_COPY + /* 0x1f3 */ { kKeyNone }, // VK_OEM_AUTO + /* 0x1f4 */ { kKeyNone }, // VK_OEM_ENLW + /* 0x1f5 */ { kKeyNone }, // OEM specific + /* 0x1f6 */ { kKeyNone }, // VK_ATTN + /* 0x1f7 */ { kKeyNone }, // VK_CRSEL + /* 0x1f8 */ { kKeyNone }, // VK_EXSEL + /* 0x1f9 */ { kKeyNone }, // VK_EREOF + /* 0x1fa */ { kKeyNone }, // VK_PLAY + /* 0x1fb */ { kKeyNone }, // VK_ZOOM + /* 0x1fc */ { kKeyNone }, // reserved + /* 0x1fd */ { kKeyNone }, // VK_PA1 + /* 0x1fe */ { kKeyNone }, // VK_OEM_CLEAR + /* 0x1ff */ { kKeyNone } // reserved +}; + +struct CWin32Modifiers { +public: + UINT m_vk; + KeyModifierMask m_mask; +}; + +static const CWin32Modifiers s_modifiers[] = +{ + { VK_SHIFT, KeyModifierShift }, + { VK_LSHIFT, KeyModifierShift }, + { VK_RSHIFT, KeyModifierShift }, + { VK_CONTROL, KeyModifierControl }, + { VK_LCONTROL, KeyModifierControl }, + { VK_RCONTROL, KeyModifierControl }, + { VK_MENU, KeyModifierAlt }, + { VK_LMENU, KeyModifierAlt }, + { VK_RMENU, KeyModifierAlt }, + { VK_LWIN, KeyModifierSuper }, + { VK_RWIN, KeyModifierSuper } +}; + +CMSWindowsKeyState::CMSWindowsKeyState(CMSWindowsDesks* desks, + void* eventTarget) : + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_eventTarget(eventTarget), + m_desks(desks), + m_keyLayout(GetKeyboardLayout(0)), + m_fixTimer(NULL), + m_lastDown(0), + m_useSavedModifiers(false), + m_savedModifiers(0), + m_originalSavedModifiers(0) +{ + // look up symbol that's available on winNT family but not win95 + HMODULE userModule = GetModuleHandle("user32.dll"); + m_ToUnicodeEx = (ToUnicodeEx_t)GetProcAddress(userModule, "ToUnicodeEx"); +} + +CMSWindowsKeyState::~CMSWindowsKeyState() +{ + disable(); +} + +void +CMSWindowsKeyState::disable() +{ + if (m_fixTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + m_lastDown = 0; +} + +KeyButton +CMSWindowsKeyState::virtualKeyToButton(UINT virtualKey) const +{ + return m_virtualKeyToButton[virtualKey & 0xffu]; +} + +void +CMSWindowsKeyState::setKeyLayout(HKL keyLayout) +{ + m_keyLayout = keyLayout; +} + +bool +CMSWindowsKeyState::testAutoRepeat(bool press, bool isRepeat, KeyButton button) +{ + if (!isRepeat) { + isRepeat = (press && m_lastDown != 0 && button == m_lastDown); + } + if (press) { + m_lastDown = button; + } + else { + m_lastDown = 0; + } + return isRepeat; +} + +void +CMSWindowsKeyState::saveModifiers() +{ + m_savedModifiers = getActiveModifiers(); + m_originalSavedModifiers = m_savedModifiers; +} + +void +CMSWindowsKeyState::useSavedModifiers(bool enable) +{ + if (enable != m_useSavedModifiers) { + m_useSavedModifiers = enable; + if (!m_useSavedModifiers) { + // transfer any modifier state changes to CKeyState's state + KeyModifierMask mask = m_originalSavedModifiers ^ m_savedModifiers; + getActiveModifiersRValue() = + (getActiveModifiers() & ~mask) | (m_savedModifiers & mask); + } + } +} + +KeyID +CMSWindowsKeyState::mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const +{ + static const KeyModifierMask s_controlAlt = + KeyModifierControl | KeyModifierAlt; + + // extract character, virtual key, and if we didn't use AltGr + char c = (char)((charAndVirtKey & 0xff00u) >> 8); + UINT vkCode = (charAndVirtKey & 0xffu); + bool noAltGr = ((charAndVirtKey & 0xff0000u) != 0); + + // handle some keys via table lookup + KeyID id = getKeyID(vkCode, (KeyButton)((info >> 16) & 0x1ffu)); + + // check if not in table; map character to key id + if (id == kKeyNone && c != 0) { + if ((c & 0x80u) == 0) { + // ASCII + id = static_cast(c) & 0xffu; + } + else { + // character is not really ASCII. instead it's some + // character in the current ANSI code page. try to + // convert that to a Unicode character. if we fail + // then use the single byte character as is. + char src = c; + wchar_t unicode; + if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, + &src, 1, &unicode, 1) > 0) { + id = static_cast(unicode); + } + else { + id = static_cast(c) & 0xffu; + } + } + } + + // set modifier mask + if (maskOut != NULL) { + KeyModifierMask active = getActiveModifiers(); + if (!noAltGr && (active & s_controlAlt) == s_controlAlt) { + // if !noAltGr then we're only interested in matching the + // key, not the AltGr. AltGr is down (i.e. control and alt + // are down) but we don't want the client to have to match + // that so we clear it. + active &= ~s_controlAlt; + } + *maskOut = active; + } + + return id; +} + +bool +CMSWindowsKeyState::didGroupsChange() const +{ + GroupList groups; + return (getGroups(groups) && groups != m_groups); +} + +UINT +CMSWindowsKeyState::mapKeyToVirtualKey(KeyID key) const +{ + if (key == kKeyNone) { + return 0; + } + KeyToVKMap::const_iterator i = m_keyToVKMap.find(key); + if (i == m_keyToVKMap.end()) { + return 0; + } + else { + return i->second; + } +} + +void +CMSWindowsKeyState::onKey(KeyButton button, bool down, KeyModifierMask newState) +{ + // handle win32 brokenness and forward to superclass + fixKeys(); + CKeyState::onKey(button, down, newState); + fixKeys(); +} + +void +CMSWindowsKeyState::sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (press || isAutoRepeat) { + // send key + if (press && !isAutoRepeat) { + CKeyState::sendKeyEvent(target, true, false, + key, mask, 1, button); + if (count > 0) { + --count; + } + } + if (count >= 1) { + CKeyState::sendKeyEvent(target, true, true, + key, mask, count, button); + } + } + else { + // do key up + CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button); + } +} + +void +CMSWindowsKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + CKeyState::fakeKeyDown(id, mask, button); +} + +void +CMSWindowsKeyState::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + CKeyState::fakeKeyRepeat(id, mask, count, button); +} + +bool +CMSWindowsKeyState::fakeCtrlAltDel() +{ + if (!m_is95Family) { + // to fake ctrl+alt+del on the NT family we broadcast a suitable + // hotkey to all windows on the winlogon desktop. however, the + // current thread must be on that desktop to do the broadcast + // and we can't switch just any thread because some own windows + // or hooks. so start a new thread to do the real work. + CThread cad(new CFunctionJob(&CMSWindowsKeyState::ctrlAltDelThread)); + cad.wait(); + } + else { + // simulate ctrl+alt+del + fakeKeyDown(kKeyDelete, KeyModifierControl | KeyModifierAlt, + virtualKeyToButton(VK_DELETE)); + } + return true; +} + +void +CMSWindowsKeyState::ctrlAltDelThread(void*) +{ + // get the Winlogon desktop at whatever privilege we can + HDESK desk = OpenDesktop("Winlogon", 0, FALSE, MAXIMUM_ALLOWED); + if (desk != NULL) { + if (SetThreadDesktop(desk)) { + PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, + MAKELPARAM(MOD_CONTROL | MOD_ALT, VK_DELETE)); + } + else { + LOG((CLOG_DEBUG "can't switch to Winlogon desk: %d", GetLastError())); + } + CloseDesktop(desk); + } + else { + LOG((CLOG_DEBUG "can't open Winlogon desk: %d", GetLastError())); + } +} + +KeyModifierMask +CMSWindowsKeyState::pollActiveModifiers() const +{ + KeyModifierMask state = 0; + + // get non-toggle modifiers from our own shadow key state + for (size_t i = 0; i < sizeof(s_modifiers) / sizeof(s_modifiers[0]); ++i) { + KeyButton button = virtualKeyToButton(s_modifiers[i].m_vk); + if (button != 0 && isKeyDown(button)) { + state |= s_modifiers[i].m_mask; + } + } + + // we can get toggle modifiers from the system + if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) { + state |= KeyModifierCapsLock; + } + if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) { + state |= KeyModifierNumLock; + } + if ((GetKeyState(VK_SCROLL) & 0x01) != 0) { + state |= KeyModifierScrollLock; + } + + return state; +} + +SInt32 +CMSWindowsKeyState::pollActiveGroup() const +{ + // determine the thread that'll receive this event + HWND targetWindow = GetForegroundWindow(); + DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL); + + // get keyboard layout for the thread + HKL hkl = GetKeyboardLayout(targetThread); + + // get group + GroupMap::const_iterator i = m_groupMap.find(hkl); + if (i == m_groupMap.end()) { + LOG((CLOG_DEBUG1 "can't find keyboard layout %08x", hkl)); + return 0; + } + + return i->second; +} + +void +CMSWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + BYTE keyState[256]; + GetKeyboardState(keyState); + for (KeyButton i = 1; i < 256; ++i) { + if ((keyState[i] & 0x80) != 0) { + pressedKeys.insert(i); + } + } +} + +void +CMSWindowsKeyState::getKeyMap(CKeyMap& keyMap) +{ + // update keyboard groups + if (getGroups(m_groups)) { + m_groupMap.clear(); + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + m_groupMap[m_groups[g]] = g; + } + } + HKL activeLayout = GetKeyboardLayout(0); + + // clear table + memset(m_virtualKeyToButton, 0, sizeof(m_virtualKeyToButton)); + m_keyToVKMap.clear(); + + CKeyMap::KeyItem item; + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + item.m_group = g; + ActivateKeyboardLayout(m_groups[g], 0); + + // clear tables + memset(m_buttonToVK, 0, sizeof(m_buttonToVK)); + memset(m_buttonToNumpadVK, 0, sizeof(m_buttonToNumpadVK)); + + // map buttons (scancodes) to virtual keys + for (KeyButton i = 1; i < 256; ++i) { + UINT vk = MapVirtualKey(i, 1); + if (vk == 0) { + // unmapped + continue; + } + + // deal with certain virtual keys specially + switch (vk) { + case VK_SHIFT: + if (MapVirtualKey(VK_RSHIFT, 0) == i) { + vk = VK_RSHIFT; + } + else { + vk = VK_LSHIFT; + } + break; + + case VK_CONTROL: + vk = VK_LCONTROL; + break; + + case VK_MENU: + vk = VK_LMENU; + break; + + case VK_NUMLOCK: + vk = VK_PAUSE; + break; + + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + // numpad keys are saved in their own table + m_buttonToNumpadVK[i] = vk; + continue; + + case VK_LWIN: + case VK_RWIN: + // add extended key only for these on 95 family + if (m_is95Family) { + m_buttonToVK[i | 0x100u] = vk; + continue; + } + break; + + case VK_RETURN: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_INSERT: + case VK_DELETE: + // also add extended key for these + m_buttonToVK[i | 0x100u] = vk; + break; + } + + if (m_buttonToVK[i] == 0) { + m_buttonToVK[i] = vk; + } + } + + // now map virtual keys to buttons. multiple virtual keys may map + // to a single button. if the virtual key matches the one in + // m_buttonToVK then we use the button as is. if not then it's + // either a numpad key and we use the button as is or it's an + // extended button. + for (UINT i = 1; i < 255; ++i) { + // skip virtual keys we don't want + switch (i) { + case VK_LBUTTON: + case VK_RBUTTON: + case VK_MBUTTON: + case VK_XBUTTON1: + case VK_XBUTTON2: + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + continue; + } + + // get the button + KeyButton button = static_cast(MapVirtualKey(i, 0)); + if (button == 0) { + continue; + } + + // deal with certain virtual keys specially + switch (i) { + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + m_buttonToNumpadVK[button] = i; + break; + + default: + // add extended key if virtual keys don't match + if (m_buttonToVK[button] != i) { + m_buttonToVK[button | 0x100u] = i; + } + break; + } + } + + // add alt+printscreen + if (m_buttonToVK[0x54u] == 0) { + m_buttonToVK[0x54u] = VK_SNAPSHOT; + } + + // set virtual key to button table + if (GetKeyboardLayout(0) == m_groups[g]) { + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToVK[i] != 0) { + if (m_virtualKeyToButton[m_buttonToVK[i]] == 0) { + m_virtualKeyToButton[m_buttonToVK[i]] = i; + } + } + if (m_buttonToNumpadVK[i] != 0) { + if (m_virtualKeyToButton[m_buttonToNumpadVK[i]] == 0) { + m_virtualKeyToButton[m_buttonToNumpadVK[i]] = i; + } + } + } + } + + // add numpad keys + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToNumpadVK[i] != 0) { + item.m_id = getKeyID(m_buttonToNumpadVK[i], i); + item.m_button = i; + item.m_required = KeyModifierNumLock; + item.m_sensitive = KeyModifierNumLock | KeyModifierShift; + item.m_generates = 0; + item.m_client = m_buttonToNumpadVK[i]; + addKeyEntry(keyMap, item); + } + } + + // add other keys + BYTE keys[256]; + memset(keys, 0, sizeof(keys)); + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToVK[i] != 0) { + // initialize item + item.m_id = getKeyID(m_buttonToVK[i], i); + item.m_button = i; + item.m_required = 0; + item.m_sensitive = 0; + item.m_client = m_buttonToVK[i]; + + // get flags for modifier keys + CKeyMap::initModifierKey(item); + + if (item.m_id == 0) { + // translate virtual key to a character with and without + // shift, caps lock, and AltGr. + struct Modifier { + UINT m_vk1; + UINT m_vk2; + BYTE m_state; + KeyModifierMask m_mask; + }; + static const Modifier modifiers[] = { + { VK_SHIFT, VK_SHIFT, 0x80u, KeyModifierShift }, + { VK_CAPITAL, VK_CAPITAL, 0x01u, KeyModifierCapsLock }, + { VK_CONTROL, VK_MENU, 0x80u, KeyModifierControl | + KeyModifierAlt } + }; + static const size_t s_numModifiers = + sizeof(modifiers) / sizeof(modifiers[0]); + static const size_t s_numCombinations = 1 << s_numModifiers; + KeyID id[s_numCombinations]; + + bool anyFound = false; + KeyButton button = static_cast(i & 0xffu); + for (size_t j = 0; j < s_numCombinations; ++j) { + for (size_t k = 0; k < s_numModifiers; ++k) { + if ((j & (1 << k)) != 0) { + keys[modifiers[k].m_vk1] = modifiers[k].m_state; + keys[modifiers[k].m_vk2] = modifiers[k].m_state; + } + else { + keys[modifiers[k].m_vk1] = 0; + keys[modifiers[k].m_vk2] = 0; + } + } + id[j] = getIDForKey(item, button, + m_buttonToVK[i], keys, m_groups[g]); + if (id[j] != 0) { + anyFound = true; + } + } + + if (anyFound) { + // determine what modifiers we're sensitive to. + // we're sensitive if the KeyID changes when the + // modifier does. + item.m_sensitive = 0; + for (size_t k = 0; k < s_numModifiers; ++k) { + for (size_t j = 0; j < s_numCombinations; ++j) { + if (id[j] != id[j ^ (1u << k)]) { + item.m_sensitive |= modifiers[k].m_mask; + break; + } + } + } + + // save each key. the map will automatically discard + // duplicates, like an unshift and shifted version of + // a key that's insensitive to shift. + for (size_t j = 0; j < s_numCombinations; ++j) { + item.m_id = id[j]; + item.m_required = 0; + for (size_t k = 0; k < s_numModifiers; ++k) { + if ((j & (1 << k)) != 0) { + item.m_required |= modifiers[k].m_mask; + } + } + addKeyEntry(keyMap, item); + } + } + } + else { + // found in table + switch (m_buttonToVK[i]) { + case VK_TAB: + // add kKeyLeftTab, too + item.m_id = kKeyLeftTab; + item.m_required |= KeyModifierShift; + item.m_sensitive |= KeyModifierShift; + addKeyEntry(keyMap, item); + item.m_id = kKeyTab; + item.m_required &= ~KeyModifierShift; + break; + + case VK_CANCEL: + item.m_required |= KeyModifierControl; + item.m_sensitive |= KeyModifierControl; + break; + + case VK_SNAPSHOT: + item.m_sensitive |= KeyModifierAlt; + if ((i & 0x100u) == 0) { + // non-extended snapshot key requires alt + item.m_required |= KeyModifierAlt; + } + break; + } + addKeyEntry(keyMap, item); + } + } + } + } + + // restore keyboard layout + ActivateKeyboardLayout(activeLayout, 0); +} + +void +CMSWindowsKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: { + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + KeyButton button = keystroke.m_data.m_button.m_button; + + // windows doesn't send key ups for key repeats + if (keystroke.m_data.m_button.m_repeat && + !keystroke.m_data.m_button.m_press) { + LOG((CLOG_DEBUG1 " discard key repeat release")); + break; + } + + // get the virtual key for the button + UINT vk = keystroke.m_data.m_button.m_client; + + // special handling of VK_SNAPSHOT + if (vk == VK_SNAPSHOT) { + if ((getActiveModifiers() & KeyModifierAlt) != 0) { + // snapshot active window + button = 1; + } + else { + // snapshot full screen + button = 0; + } + } + + // synthesize event + m_desks->fakeKeyEvent(button, vk, + keystroke.m_data.m_button.m_press, + keystroke.m_data.m_button.m_repeat); + break; + } + + case Keystroke::kGroup: + // we don't restore the group. we'd like to but we can't be + // sure the restoring group change will be processed after the + // key events. + if (!keystroke.m_data.m_group.m_restore) { + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); + setWindowGroup(keystroke.m_data.m_group.m_group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); + setWindowGroup(getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + } + break; + } +} + +KeyModifierMask& +CMSWindowsKeyState::getActiveModifiersRValue() +{ + if (m_useSavedModifiers) { + return m_savedModifiers; + } + else { + return CKeyState::getActiveModifiersRValue(); + } +} + +bool +CMSWindowsKeyState::getGroups(GroupList& groups) const +{ + // get keyboard layouts + UInt32 newNumLayouts = GetKeyboardLayoutList(0, NULL); + if (newNumLayouts == 0) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + return false; + } + HKL* newLayouts = new HKL[newNumLayouts]; + newNumLayouts = GetKeyboardLayoutList(newNumLayouts, newLayouts); + if (newNumLayouts == 0) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + delete[] newLayouts; + return false; + } + + groups.clear(); + groups.insert(groups.end(), newLayouts, newLayouts + newNumLayouts); + delete[] newLayouts; + return true; +} + +void +CMSWindowsKeyState::setWindowGroup(SInt32 group) +{ + HWND targetWindow = GetForegroundWindow(); + + bool sysCharSet = true; + // XXX -- determine if m_groups[group] can be used with the system + // character set. + + PostMessage(targetWindow, WM_INPUTLANGCHANGEREQUEST, + sysCharSet ? 1 : 0, (LPARAM)m_groups[group]); + + // XXX -- use a short delay to let the target window process the message + // before it sees the keyboard events. i'm not sure why this is + // necessary since the messages should arrive in order. if we don't + // delay, though, some of our keyboard events may disappear. + Sleep(100); +} + +void +CMSWindowsKeyState::fixKeys() +{ + // fake key releases for the windows keys if we think they're + // down but they're really up. we have to do this because if the + // user presses and releases a windows key without pressing any + // other key while it's down then the system will eat the key + // release. if we don't detect that and synthesize the release + // then the client won't take the usual windows key release action + // (which on windows is to show the start menu). + // + // only check on the windows 95 family since the NT family reports + // the key releases as usual. + if (!m_is95Family) { + return; + } + + KeyButton leftButton = virtualKeyToButton(VK_LWIN); + KeyButton rightButton = virtualKeyToButton(VK_RWIN); + bool leftDown = isKeyDown(leftButton); + bool rightDown = isKeyDown(rightButton); + bool fix = (leftDown || rightDown); + if (fix) { + // check if either button is not really down + bool leftAsyncDown = ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0); + bool rightAsyncDown = ((GetAsyncKeyState(VK_RWIN) & 0x8000) != 0); + + if (leftAsyncDown != leftDown || rightAsyncDown != rightDown) { + KeyModifierMask state = getActiveModifiers(); + if (!leftAsyncDown && !rightAsyncDown) { + // no win keys are down so remove super modifier + state &= ~KeyModifierSuper; + } + + // report up events + if (leftDown && !leftAsyncDown) { + LOG((CLOG_DEBUG1 "event: fake key release left windows key (0x%03x)", leftButton)); + CKeyState::onKey(leftButton, false, state); + CKeyState::sendKeyEvent(m_eventTarget, false, false, + kKeySuper_L, state, 1, leftButton); + } + if (rightDown && !rightAsyncDown) { + LOG((CLOG_DEBUG1 "event: fake key release right windows key (0x%03x)", rightButton)); + CKeyState::onKey(rightButton, false, state); + CKeyState::sendKeyEvent(m_eventTarget, false, false, + kKeySuper_R, state, 1, rightButton); + } + } + } + + if (fix && m_fixTimer == NULL) { + // schedule check + m_fixTimer = EVENTQUEUE->newTimer(0.1, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_fixTimer, + new TMethodEventJob( + this, &CMSWindowsKeyState::handleFixKeys)); + } + else if (!fix && m_fixTimer != NULL) { + // remove scheduled check + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } +} + +void +CMSWindowsKeyState::handleFixKeys(const CEvent&, void*) +{ + fixKeys(); +} + +KeyID +CMSWindowsKeyState::getKeyID(UINT virtualKey, KeyButton button) +{ + if ((button & 0x100u) != 0) { + virtualKey += 0x100u; + } + return s_virtualKey[virtualKey]; +} + +KeyID +CMSWindowsKeyState::getIDForKey(CKeyMap::KeyItem& item, + KeyButton button, UINT virtualKey, + PBYTE keyState, HKL hkl) const +{ + int n; + KeyID id; + if (m_is95Family) { + // XXX -- how do we get characters not in Latin-1? + WORD ascii; + n = ToAsciiEx(virtualKey, button, keyState, &ascii, 0, hkl); + id = static_cast(ascii & 0xffu); + } + else { + WCHAR unicode[2]; + n = m_ToUnicodeEx(virtualKey, button, keyState, + unicode, sizeof(unicode) / sizeof(unicode[0]), + 0, hkl); + id = static_cast(unicode[0]); + } + switch (n) { + case -1: + return CKeyMap::getDeadKey(id); + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + + case 2: + // left over dead key in buffer; oops. + return getIDForKey(item, button, virtualKey, keyState, hkl); + } +} + +void +CMSWindowsKeyState::addKeyEntry(CKeyMap& keyMap, CKeyMap::KeyItem& item) +{ + keyMap.addKeyEntry(item); + if (item.m_group == 0) { + m_keyToVKMap[item.m_id] = static_cast(item.m_client); + } +} diff --git a/lib/platform/CMSWindowsKeyState.h b/lib/platform/CMSWindowsKeyState.h new file mode 100644 index 00000000..d8320a58 --- /dev/null +++ b/lib/platform/CMSWindowsKeyState.h @@ -0,0 +1,216 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSKEYSTATE_H +#define CMSWINDOWSKEYSTATE_H + +#include "CKeyState.h" +#include "CString.h" +#include "stdvector.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEvent; +class CEventQueueTimer; +class CMSWindowsDesks; + +//! Microsoft Windows key mapper +/*! +This class maps KeyIDs to keystrokes. +*/ +class CMSWindowsKeyState : public CKeyState { +public: + CMSWindowsKeyState(CMSWindowsDesks* desks, void* eventTarget); + virtual ~CMSWindowsKeyState(); + + //! @name manipulators + //@{ + + //! Handle screen disabling + /*! + Called when screen is disabled. This is needed to deal with platform + brokenness. + */ + void disable(); + + //! Set the active keyboard layout + /*! + Uses \p keyLayout when querying the keyboard. + */ + void setKeyLayout(HKL keyLayout); + + //! Test and set autorepeat state + /*! + Returns true if the given button is autorepeating and updates internal + state. + */ + bool testAutoRepeat(bool press, bool isRepeat, KeyButton); + + //! Remember modifier state + /*! + Records the current non-toggle modifier state. + */ + void saveModifiers(); + + //! Set effective modifier state + /*! + Temporarily sets the non-toggle modifier state to those saved by the + last call to \c saveModifiers if \p enable is \c true. Restores the + modifier state to the current modifier state if \p enable is \c false. + This is for synthesizing keystrokes on the primary screen when the + cursor is on a secondary screen. When on a secondary screen we capture + all non-toggle modifier state, track the state internally and do not + pass it on. So if Alt+F1 synthesizes Alt+X we need to synthesize + not just X but also Alt, despite the fact that our internal modifier + state indicates Alt is down, because local apps never saw the Alt down + event. + */ + void useSavedModifiers(bool enable); + + //@} + //! @name accessors + //@{ + + //! Map a virtual key to a button + /*! + Returns the button for the \p virtualKey. + */ + KeyButton virtualKeyToButton(UINT virtualKey) const; + + //! Map key event to a key + /*! + Converts a key event into a KeyID and the shadow modifier state + to a modifier mask. + */ + KeyID mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const; + + //! Check if keyboard groups have changed + /*! + Returns true iff the number or order of the keyboard groups have + changed since the last call to updateKeys(). + */ + bool didGroupsChange() const; + + //! Map key to virtual key + /*! + Returns the virtual key for key \p key or 0 if there's no such virtual + key. + */ + UINT mapKeyToVirtualKey(KeyID key) const; + + //! Map virtual key and button to KeyID + /*! + Returns the KeyID for virtual key \p virtualKey and button \p button + (button should include the extended key bit), or kKeyNone if there is + no such key. + */ + static KeyID getKeyID(UINT virtualKey, KeyButton button); + + //@} + + // IKeyState overrides + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + + // CKeyState overrides + virtual void onKey(KeyButton button, bool down, + KeyModifierMask newState); + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + +protected: + // CKeyState overrides + virtual void getKeyMap(CKeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + virtual KeyModifierMask& + getActiveModifiersRValue(); + +private: + typedef std::vector GroupList; + + // send ctrl+alt+del hotkey event on NT family + static void ctrlAltDelThread(void*); + + bool getGroups(GroupList&) const; + void setWindowGroup(SInt32 group); + + void fixKeys(); + void handleFixKeys(const CEvent&, void*); + + KeyID getIDForKey(CKeyMap::KeyItem& item, + KeyButton button, UINT virtualKey, + PBYTE keyState, HKL hkl) const; + + void addKeyEntry(CKeyMap& keyMap, CKeyMap::KeyItem& item); + +private: + // not implemented + CMSWindowsKeyState(const CMSWindowsKeyState&); + CMSWindowsKeyState& operator=(const CMSWindowsKeyState&); + +private: + typedef std::map GroupMap; + typedef std::map KeyToVKMap; + + bool m_is95Family; + void* m_eventTarget; + CMSWindowsDesks* m_desks; + HKL m_keyLayout; + UINT m_buttonToVK[512]; + UINT m_buttonToNumpadVK[512]; + KeyButton m_virtualKeyToButton[256]; + KeyToVKMap m_keyToVKMap; + + // the timer used to check for fixing key state + CEventQueueTimer* m_fixTimer; + + // the groups (keyboard layouts) + GroupList m_groups; + GroupMap m_groupMap; + + // the last button that we generated a key down event for. this + // is zero if the last key event was a key up. we use this to + // synthesize key repeats since the low level keyboard hook can't + // tell us if an event is a key repeat. + KeyButton m_lastDown; + + // modifier tracking + bool m_useSavedModifiers; + KeyModifierMask m_savedModifiers; + KeyModifierMask m_originalSavedModifiers; + + // pointer to ToUnicodeEx. on win95 family this will be NULL. + typedef int (WINAPI *ToUnicodeEx_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags, + HKL dwhkl); + ToUnicodeEx_t m_ToUnicodeEx; + + static const KeyID s_virtualKey[]; +}; + +#endif diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp new file mode 100644 index 00000000..b000b477 --- /dev/null +++ b/lib/platform/CMSWindowsScreen.cpp @@ -0,0 +1,1738 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsScreen.h" +#include "CMSWindowsClipboard.h" +#include "CMSWindowsDesks.h" +#include "CMSWindowsEventQueueBuffer.h" +#include "CMSWindowsKeyState.h" +#include "CMSWindowsScreenSaver.h" +#include "CClipboard.h" +#include "CKeyMap.h" +#include "XScreen.h" +#include "CLock.h" +#include "CThread.h" +#include "CFunctionJob.h" +#include "CLog.h" +#include "CString.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include +#include + +// +// add backwards compatible multihead support (and suppress bogus warning). +// this isn't supported on MinGW yet AFAICT. +// +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional +#define COMPILE_MULTIMON_STUBS +#include +#pragma warning(pop) +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// WM_POWERBROADCAST stuff +#if !defined(PBT_APMRESUMEAUTOMATIC) +#define PBT_APMRESUMEAUTOMATIC 0x0012 +#endif + +// +// CMSWindowsScreen +// + +HINSTANCE CMSWindowsScreen::s_instance = NULL; +CMSWindowsScreen* CMSWindowsScreen::s_screen = NULL; + +CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) : + m_isPrimary(isPrimary), + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_isOnScreen(m_isPrimary), + m_class(0), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_xCursor(0), m_yCursor(0), + m_sequenceNumber(0), + m_mark(0), + m_markReceived(0), + m_fixTimer(NULL), + m_keyLayout(NULL), + m_screensaver(NULL), + m_screensaverNotify(false), + m_screensaverActive(false), + m_window(NULL), + m_nextClipboardWindow(NULL), + m_ownClipboard(false), + m_desks(NULL), + m_hookLibrary(NULL), + m_init(NULL), + m_cleanup(NULL), + m_setSides(NULL), + m_setZone(NULL), + m_setMode(NULL), + m_keyState(NULL), + m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0), + m_showingMouse(false) +{ + assert(s_instance != NULL); + assert(s_screen == NULL); + + s_screen = this; + try { + if (m_isPrimary) { + m_hookLibrary = openHookLibrary("synrgyhk"); + } + m_screensaver = new CMSWindowsScreenSaver(); + m_desks = new CMSWindowsDesks(m_isPrimary, + m_hookLibrary, m_screensaver, + new TMethodJob(this, + &CMSWindowsScreen::updateKeysCB)); + m_keyState = new CMSWindowsKeyState(m_desks, getEventTarget()); + updateScreenShape(); + m_class = createWindowClass(); + m_window = createWindow(m_class, "Synergy"); + forceShowCursor(); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + } + catch (...) { + delete m_keyState; + delete m_desks; + delete m_screensaver; + destroyWindow(m_window); + destroyClass(m_class); + closeHookLibrary(m_hookLibrary); + s_screen = NULL; + throw; + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CMSWindowsScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new CMSWindowsEventQueueBuffer); +} + +CMSWindowsScreen::~CMSWindowsScreen() +{ + assert(s_screen != NULL); + + disable(); + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + delete m_keyState; + delete m_desks; + delete m_screensaver; + destroyWindow(m_window); + destroyClass(m_class); + closeHookLibrary(m_hookLibrary); + s_screen = NULL; +} + +void +CMSWindowsScreen::init(HINSTANCE instance) +{ + assert(s_instance == NULL); + assert(instance != NULL); + + s_instance = instance; +} + +HINSTANCE +CMSWindowsScreen::getInstance() +{ + return s_instance; +} + +void +CMSWindowsScreen::enable() +{ + assert(m_isOnScreen == m_isPrimary); + + // we need to poll some things to fix them + m_fixTimer = EVENTQUEUE->newTimer(1.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_fixTimer, + new TMethodEventJob(this, + &CMSWindowsScreen::handleFixes)); + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + // track the active desk and (re)install the hooks + m_desks->enable(); + + if (m_isPrimary) { + // set jump zones + m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + + // watch jump zones + m_setMode(kHOOK_WATCH_JUMP_ZONE); + } + else { + // prevent the system from entering power saving modes. if + // it did we'd be forced to disconnect from the server and + // the server would not be able to wake us up. + CArchMiscWindows::addBusyState(CArchMiscWindows::kSYSTEM); + } +} + +void +CMSWindowsScreen::disable() +{ + // stop tracking the active desk + m_desks->disable(); + + if (m_isPrimary) { + // disable hooks + m_setMode(kHOOK_DISABLE); + + // enable special key sequences on win95 family + enableSpecialKeys(true); + } + else { + // allow the system to enter power saving mode + CArchMiscWindows::removeBusyState(CArchMiscWindows::kSYSTEM | + CArchMiscWindows::kDISPLAY); + } + + // tell key state + m_keyState->disable(); + + // stop snooping the clipboard + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // uninstall fix timer + if (m_fixTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + + m_isOnScreen = m_isPrimary; + forceShowCursor(); +} + +void +CMSWindowsScreen::enter() +{ + m_desks->enter(); + if (m_isPrimary) { + // enable special key sequences on win95 family + enableSpecialKeys(true); + + // watch jump zones + m_setMode(kHOOK_WATCH_JUMP_ZONE); + + // all messages prior to now are invalid + nextMark(); + } + + // now on screen + m_isOnScreen = true; + forceShowCursor(); +} + +bool +CMSWindowsScreen::leave() +{ + // get keyboard layout of foreground window. we'll use this + // keyboard layout for translating keys sent to clients. + HWND window = GetForegroundWindow(); + DWORD thread = GetWindowThreadProcessId(window, NULL); + m_keyLayout = GetKeyboardLayout(thread); + + // tell the key mapper about the keyboard layout + m_keyState->setKeyLayout(m_keyLayout); + + // tell desk that we're leaving and tell it the keyboard layout + m_desks->leave(m_keyLayout); + + if (m_isPrimary) { + // warp to center + warpCursor(m_xCenter, m_yCenter); + + // disable special key sequences on win95 family + enableSpecialKeys(false); + + // all messages prior to now are invalid + nextMark(); + + // remember the modifier state. this is the modifier state + // reflected in the internal keyboard state. + m_keyState->saveModifiers(); + + // capture events + m_setMode(kHOOK_RELAY_EVENTS); + } + + // now off screen + m_isOnScreen = false; + forceShowCursor(); + + return true; +} + +bool +CMSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src) +{ + CMSWindowsClipboard dst(m_window); + if (src != NULL) { + // save clipboard data + return CClipboard::copy(&dst, src); + } + else { + // assert clipboard ownership + if (!dst.open(0)) { + return false; + } + dst.empty(); + dst.close(); + return true; + } +} + +void +CMSWindowsScreen::checkClipboards() +{ + // if we think we own the clipboard but we don't then somebody + // grabbed the clipboard on this screen without us knowing. + // tell the server that this screen grabbed the clipboard. + // + // this works around bugs in the clipboard viewer chain. + // sometimes NT will simply never send WM_DRAWCLIPBOARD + // messages for no apparent reason and rebooting fixes the + // problem. since we don't want a broken clipboard until the + // next reboot we do this double check. clipboard ownership + // won't be reflected on other screens until we leave but at + // least the clipboard itself will work. + if (m_ownClipboard && !CMSWindowsClipboard::isOwnedBySynergy()) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); + m_ownClipboard = false; + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); + } +} + +void +CMSWindowsScreen::openScreensaver(bool notify) +{ + assert(m_screensaver != NULL); + + m_screensaverNotify = notify; + if (m_screensaverNotify) { + m_desks->installScreensaverHooks(true); + } + else { + m_screensaver->disable(); + } +} + +void +CMSWindowsScreen::closeScreensaver() +{ + if (m_screensaver != NULL) { + if (m_screensaverNotify) { + m_desks->installScreensaverHooks(false); + } + else { + m_screensaver->enable(); + } + } + m_screensaverNotify = false; +} + +void +CMSWindowsScreen::screensaver(bool activate) +{ + assert(m_screensaver != NULL); + + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +CMSWindowsScreen::resetOptions() +{ + m_desks->resetOptions(); +} + +void +CMSWindowsScreen::setOptions(const COptionsList& options) +{ + m_desks->setOptions(options); +} + +void +CMSWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +CMSWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +CMSWindowsScreen::getEventTarget() const +{ + return const_cast(this); +} + +bool +CMSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const +{ + CMSWindowsClipboard src(m_window); + CClipboard::copy(dst, &src); + return true; +} + +void +CMSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + assert(m_class != 0); + + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + m_desks->getCursorPos(x, y); +} + +void +CMSWindowsScreen::reconfigure(UInt32 activeSides) +{ + assert(m_isPrimary); + + LOG((CLOG_DEBUG "active sides: %x", activeSides)); + m_setSides(activeSides); +} + +void +CMSWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + MSG msg; + while (PeekMessage(&msg, NULL, SYNERGY_MSG_INPUT_FIRST, + SYNERGY_MSG_INPUT_LAST, PM_REMOVE)) { + // do nothing + } + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +CMSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to win32 + UINT modifiers = 0; + if ((mask & KeyModifierShift) != 0) { + modifiers |= MOD_SHIFT; + } + if ((mask & KeyModifierControl) != 0) { + modifiers |= MOD_CONTROL; + } + if ((mask & KeyModifierAlt) != 0) { + modifiers |= MOD_ALT; + } + if ((mask & KeyModifierSuper) != 0) { + modifiers |= MOD_WIN; + } + UINT vk = m_keyState->mapKeyToVirtualKey(key); + if (key != kKeyNone && vk == 0) { + // can't map key + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + + // if this hot key has modifiers only then we'll handle it specially + bool err; + if (key == kKeyNone) { + // check if already registered + err = (m_hotKeyToIDMap.count(CHotKeyItem(vk, modifiers)) > 0); + } + else { + // register with OS + err = (RegisterHotKey(NULL, id, modifiers, vk) == 0); + } + + if (!err) { + m_hotKeys.insert(std::make_pair(id, CHotKeyItem(vk, modifiers))); + m_hotKeyToIDMap[CHotKeyItem(vk, modifiers)] = id; + } + else { + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +CMSWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err; + if (i->second.getVirtualKey() != 0) { + err = !UnregisterHotKey(NULL, id); + } + else { + err = false; + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeyToIDMap.erase(i->second); + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +CMSWindowsScreen::fakeInputBegin() +{ + assert(m_isPrimary); + + if (!m_isOnScreen) { + m_keyState->useSavedModifiers(true); + } + m_desks->fakeInputBegin(); +} + +void +CMSWindowsScreen::fakeInputEnd() +{ + assert(m_isPrimary); + + m_desks->fakeInputEnd(); + if (!m_isOnScreen) { + m_keyState->useSavedModifiers(false); + } +} + +SInt32 +CMSWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +CMSWindowsScreen::isAnyMouseButtonDown() const +{ + static const char* buttonToName[] = { + "", + "Left Button", + "Middle Button", + "Right Button", + "X Button 1", + "X Button 2" + }; + + for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + if (m_buttons[i]) { + LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i])); + return true; + } + } + + return false; +} + +void +CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +CMSWindowsScreen::fakeMouseButton(ButtonID id, bool press) const +{ + m_desks->fakeMouseButton(id, press); +} + +void +CMSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + m_desks->fakeMouseMove(x, y); +} + +void +CMSWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + m_desks->fakeMouseRelativeMove(dx, dy); +} + +void +CMSWindowsScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + m_desks->fakeMouseWheel(xDelta, yDelta); +} + +void +CMSWindowsScreen::updateKeys() +{ + m_desks->updateKeys(); +} + +void +CMSWindowsScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + CPlatformScreen::fakeKeyDown(id, mask, button); + updateForceShowCursor(); +} + +void +CMSWindowsScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + CPlatformScreen::fakeKeyRepeat(id, mask, count, button); + updateForceShowCursor(); +} + +void +CMSWindowsScreen::fakeKeyUp(KeyButton button) +{ + CPlatformScreen::fakeKeyUp(button); + updateForceShowCursor(); +} + +void +CMSWindowsScreen::fakeAllKeysUp() +{ + CPlatformScreen::fakeAllKeysUp(); + updateForceShowCursor(); +} + +HINSTANCE +CMSWindowsScreen::openHookLibrary(const char* name) +{ + // load the hook library + HINSTANCE hookLibrary = LoadLibrary(name); + if (hookLibrary == NULL) { + LOG((CLOG_ERR "Failed to load hook library; %s.dll is missing", name)); + throw XScreenOpenFailure(); + } + + // look up functions + m_setSides = (SetSidesFunc)GetProcAddress(hookLibrary, "setSides"); + m_setZone = (SetZoneFunc)GetProcAddress(hookLibrary, "setZone"); + m_setMode = (SetModeFunc)GetProcAddress(hookLibrary, "setMode"); + m_init = (InitFunc)GetProcAddress(hookLibrary, "init"); + m_cleanup = (CleanupFunc)GetProcAddress(hookLibrary, "cleanup"); + if (m_setSides == NULL || + m_setZone == NULL || + m_setMode == NULL || + m_init == NULL || + m_cleanup == NULL) { + LOG((CLOG_ERR "Invalid hook library; use a newer %s.dll", name)); + throw XScreenOpenFailure(); + } + + // initialize hook library + if (m_init(GetCurrentThreadId()) == 0) { + LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?")); + throw XScreenOpenFailure(); + } + + return hookLibrary; +} + +void +CMSWindowsScreen::closeHookLibrary(HINSTANCE hookLibrary) const +{ + if (hookLibrary != NULL) { + m_cleanup(); + FreeLibrary(hookLibrary); + } +} + +HCURSOR +CMSWindowsScreen::createBlankCursor() const +{ + // create a transparent cursor + int cw = GetSystemMetrics(SM_CXCURSOR); + int ch = GetSystemMetrics(SM_CYCURSOR); + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; + UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; + memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(s_instance, 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +CMSWindowsScreen::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +CMSWindowsScreen::createWindowClass() const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = &CMSWindowsScreen::wndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = s_instance; + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "Synergy"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +CMSWindowsScreen::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(reinterpret_cast(windowClass), s_instance); + } +} + +HWND +CMSWindowsScreen::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + reinterpret_cast(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + s_instance, + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +CMSWindowsScreen::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +void +CMSWindowsScreen::sendEvent(CEvent::Type type, void* data) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +CMSWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) +{ + CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +void +CMSWindowsScreen::handleSystemEvent(const CEvent& event, void*) +{ + MSG* msg = reinterpret_cast(event.getData()); + assert(msg != NULL); + + if (CArchMiscWindows::processDialog(msg)) { + return; + } + if (onPreDispatch(msg->hwnd, msg->message, msg->wParam, msg->lParam)) { + return; + } + TranslateMessage(msg); + DispatchMessage(msg); +} + +void +CMSWindowsScreen::updateButtons() +{ + int numButtons = GetSystemMetrics(SM_CMOUSEBUTTONS); + m_buttons[kButtonNone] = false; + m_buttons[kButtonLeft] = (GetKeyState(VK_LBUTTON) < 0); + m_buttons[kButtonRight] = (GetKeyState(VK_RBUTTON) < 0); + m_buttons[kButtonMiddle] = (GetKeyState(VK_MBUTTON) < 0); + m_buttons[kButtonExtra0 + 0] = (numButtons >= 4) && + (GetKeyState(VK_XBUTTON1) < 0); + m_buttons[kButtonExtra0 + 1] = (numButtons >= 5) && + (GetKeyState(VK_XBUTTON2) < 0); +} + +IKeyState* +CMSWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +bool +CMSWindowsScreen::onPreDispatch(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + // handle event + switch (message) { + case SYNERGY_MSG_SCREEN_SAVER: + return onScreensaver(wParam != 0); + + case SYNERGY_MSG_DEBUG: + LOG((CLOG_DEBUG1 "hook: 0x%08x 0x%08x", wParam, lParam)); + return true; + } + + if (m_isPrimary) { + return onPreDispatchPrimary(hwnd, message, wParam, lParam); + } + + return false; +} + +bool +CMSWindowsScreen::onPreDispatchPrimary(HWND, + UINT message, WPARAM wParam, LPARAM lParam) +{ + // handle event + switch (message) { + case SYNERGY_MSG_MARK: + return onMark(static_cast(wParam)); + + case SYNERGY_MSG_KEY: + return onKey(wParam, lParam); + + case SYNERGY_MSG_MOUSE_BUTTON: + return onMouseButton(wParam, lParam); + + case SYNERGY_MSG_MOUSE_MOVE: + return onMouseMove(static_cast(wParam), + static_cast(lParam)); + + case SYNERGY_MSG_MOUSE_WHEEL: + // XXX -- support x-axis scrolling + return onMouseWheel(0, static_cast(wParam)); + + case SYNERGY_MSG_PRE_WARP: + { + // save position to compute delta of next motion + m_xCursor = static_cast(wParam); + m_yCursor = static_cast(lParam); + + // we warped the mouse. discard events until we find the + // matching post warp event. see warpCursorNoFlush() for + // where the events are sent. we discard the matching + // post warp event and can be sure we've skipped the warp + // event. + MSG msg; + do { + GetMessage(&msg, NULL, SYNERGY_MSG_MOUSE_MOVE, + SYNERGY_MSG_POST_WARP); + } while (msg.message != SYNERGY_MSG_POST_WARP); + } + return true; + + case SYNERGY_MSG_POST_WARP: + LOG((CLOG_WARN "unmatched post warp")); + return true; + + case WM_HOTKEY: + // we discard these messages. we'll catch the hot key in the + // regular key event handling, where we can detect both key + // press and release. we only register the hot key so no other + // app will act on the key combination. + break; + } + + return false; +} + +bool +CMSWindowsScreen::onEvent(HWND, UINT msg, + WPARAM wParam, LPARAM lParam, LRESULT* result) +{ + switch (msg) { + case WM_QUERYENDSESSION: + if (m_is95Family) { + *result = TRUE; + return true; + } + break; + + case WM_ENDSESSION: + if (m_is95Family) { + if (wParam == TRUE && lParam == 0) { + EVENTQUEUE->addEvent(CEvent(CEvent::kQuit)); + } + return true; + } + break; + + case WM_DRAWCLIPBOARD: + // first pass on the message + if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } + + // now handle the message + return onClipboardChange(); + + case WM_CHANGECBCHAIN: + if (m_nextClipboardWindow == (HWND)wParam) { + m_nextClipboardWindow = (HWND)lParam; + LOG((CLOG_DEBUG "clipboard chain: new next: 0x%08x", m_nextClipboardWindow)); + } + else if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } + return true; + + case WM_DISPLAYCHANGE: + return onDisplayChange(); + + case WM_POWERBROADCAST: + switch (wParam) { + case PBT_APMRESUMEAUTOMATIC: + case PBT_APMRESUMECRITICAL: + case PBT_APMRESUMESUSPEND: + EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(), + getEventTarget(), NULL, + CEvent::kDeliverImmediately)); + break; + + case PBT_APMSUSPEND: + EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(), + getEventTarget(), NULL, + CEvent::kDeliverImmediately)); + break; + } + *result = TRUE; + return true; + + case WM_DEVICECHANGE: + forceShowCursor(); + break; + + case WM_SETTINGCHANGE: + if (wParam == SPI_SETMOUSEKEYS) { + forceShowCursor(); + } + break; + } + + return false; +} + +bool +CMSWindowsScreen::onMark(UInt32 mark) +{ + m_markReceived = mark; + return true; +} + +bool +CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) +{ + static const KeyModifierMask s_ctrlAlt = + KeyModifierControl | KeyModifierAlt; + + LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, (wParam & 0x10000u) ? 1 : 0, lParam)); + + // get event info + KeyButton button = (KeyButton)((lParam & 0x01ff0000) >> 16); + bool down = ((lParam & 0x80000000u) == 0x00000000u); + bool wasDown = isKeyDown(button); + KeyModifierMask oldState = pollActiveModifiers(); + + // check for autorepeat + if (m_keyState->testAutoRepeat(down, (lParam & 0x40000000u) == 1, button)) { + lParam |= 0x40000000u; + } + + // if the button is zero then guess what the button should be. + // these are badly synthesized key events and logitech software + // that maps mouse buttons to keys is known to do this. + // alternatively, we could just throw these events out. + if (button == 0) { + button = m_keyState->virtualKeyToButton(wParam & 0xffu); + if (button == 0) { + return true; + } + wasDown = isKeyDown(button); + } + + // record keyboard state + m_keyState->onKey(button, down, oldState); + + // windows doesn't tell us the modifier key state on mouse or key + // events so we have to figure it out. most apps would use + // GetKeyState() or even GetAsyncKeyState() for that but we can't + // because our hook doesn't pass on key events for several modifiers. + // it can't otherwise the system would interpret them normally on + // the primary screen even when on a secondary screen. so tapping + // alt would activate menus and tapping the windows key would open + // the start menu. if you don't pass those events on in the hook + // then GetKeyState() understandably doesn't reflect the effect of + // the event. curiously, neither does GetAsyncKeyState(), which is + // surprising. + // + // so anyway, we have to track the modifier state ourselves for + // at least those modifiers we don't pass on. pollActiveModifiers() + // does that but we have to update the keyboard state before calling + // pollActiveModifiers() to get the right answer. but the only way + // to set the modifier state or to set the up/down state of a key + // is via onKey(). so we have to call onKey() twice. + KeyModifierMask state = pollActiveModifiers(); + m_keyState->onKey(button, down, state); + + // check for hot keys + if (oldState != state) { + // modifier key was pressed/released + if (onHotKey(0, lParam)) { + return true; + } + } + else { + // non-modifier was pressed/released + if (onHotKey(wParam, lParam)) { + return true; + } + } + + // ignore message if posted prior to last mark change + if (!ignore()) { + // check for ctrl+alt+del. we do not want to pass that to the + // client. the user can use ctrl+alt+pause to emulate it. + UINT virtKey = (wParam & 0xffu); + if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) { + LOG((CLOG_DEBUG "discard ctrl+alt+del")); + return true; + } + + // check for ctrl+alt+del emulation + if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && + (state & s_ctrlAlt) == s_ctrlAlt) { + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + // switch wParam and lParam to be as if VK_DELETE was + // pressed or released. when mapping the key we require that + // we not use AltGr (the 0x10000 flag in wParam) and we not + // use the keypad delete key (the 0x01000000 flag in lParam). + wParam = VK_DELETE | 0x00010000u; + lParam &= 0xfe000000; + lParam |= m_keyState->virtualKeyToButton(wParam & 0xffu) << 16; + lParam |= 0x01000001; + } + + // process key + KeyModifierMask mask; + KeyID key = m_keyState->mapKeyFromEvent(wParam, lParam, &mask); + button = static_cast((lParam & 0x01ff0000u) >> 16); + if (key != kKeyNone) { + // fix key up. if the key isn't down according to + // our table then we never got the key press event + // for it. if it's not a modifier key then we'll + // synthesize the press first. only do this on + // the windows 95 family, which eats certain special + // keys like alt+tab, ctrl+esc, etc. + if (m_is95Family && !wasDown && !down) { + switch (virtKey) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + break; + + default: + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, button); + break; + } + } + + // do it + m_keyState->sendKeyEvent(getEventTarget(), + ((lParam & 0x80000000u) == 0), + ((lParam & 0x40000000u) != 0), + key, mask, (SInt32)(lParam & 0xffff), button); + } + else { + LOG((CLOG_DEBUG1 "cannot map key")); + } + } + + return true; +} + +bool +CMSWindowsScreen::onHotKey(WPARAM wParam, LPARAM lParam) +{ + // get the key info + KeyModifierMask state = getActiveModifiers(); + UINT virtKey = (wParam & 0xffu); + UINT modifiers = 0; + if ((state & KeyModifierShift) != 0) { + modifiers |= MOD_SHIFT; + } + if ((state & KeyModifierControl) != 0) { + modifiers |= MOD_CONTROL; + } + if ((state & KeyModifierAlt) != 0) { + modifiers |= MOD_ALT; + } + if ((state & KeyModifierSuper) != 0) { + modifiers |= MOD_WIN; + } + + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(CHotKeyItem(virtKey, modifiers)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + CEvent::Type type; + if ((lParam & 0x80000000u) == 0u) { + if ((lParam & 0x40000000u) != 0u) { + // ignore key repeats but it counts as a hot key + return true; + } + type = getHotKeyDownEvent(); + } + else { + type = getHotKeyUpEvent(); + } + + // generate event + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(i->second))); + + return true; +} + +bool +CMSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam) +{ + // get which button + bool pressed = mapPressFromEvent(wParam, lParam); + ButtonID button = mapButtonFromEvent(wParam, lParam); + + // keep our shadow key state up to date + if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { + if (pressed) { + m_buttons[button] = true; + } + else { + m_buttons[button] = false; + } + } + + // ignore message if posted prior to last mark change + if (!ignore()) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + if (pressed) { + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + sendEvent(getButtonDownEvent(), + CButtonInfo::alloc(button, mask)); + } + } + else { + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + sendEvent(getButtonUpEvent(), + CButtonInfo::alloc(button, mask)); + } + } + } + + return true; +} + +bool +CMSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my) +{ + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = mx - m_xCursor; + SInt32 y = my - m_yCursor; + + // ignore if the mouse didn't move or if message posted prior + // to last mark change. + if (ignore() || (x == 0 && y == 0)) { + return true; + } + + // save position to compute delta of next motion + m_xCursor = mx; + m_yCursor = my; + + if (m_isOnScreen) { + // motion on primary screen + sendEvent(getMotionOnPrimaryEvent(), + CMotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + warpCursorNoFlush(m_xCenter, m_yCenter); + + // examine the motion. if it's about the distance + // from the center of the screen to an edge then + // it's probably a bogus motion that we want to + // ignore (see warpCursorNoFlush() for a further + // description). + static SInt32 bogusZoneSize = 10; + if (-x + bogusZoneSize > m_xCenter - m_x || + x + bogusZoneSize > m_x + m_w - m_xCenter || + -y + bogusZoneSize > m_yCenter - m_y || + y + bogusZoneSize > m_y + m_h - m_yCenter) { + LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); + } + else { + // send motion + sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); + } + } + + return true; +} + +bool +CMSWindowsScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + // ignore message if posted prior to last mark change + if (!ignore()) { + LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + sendEvent(getWheelEvent(), CWheelInfo::alloc(xDelta, yDelta)); + } + return true; +} + +bool +CMSWindowsScreen::onScreensaver(bool activated) +{ + // ignore this message if there are any other screen saver + // messages already in the queue. this is important because + // our checkStarted() function has a deliberate delay, so it + // can't respond to events at full CPU speed and will fall + // behind if a lot of screen saver events are generated. + // that can easily happen because windows will continually + // send SC_SCREENSAVE until the screen saver starts, even if + // the screen saver is disabled! + MSG msg; + if (PeekMessage(&msg, NULL, SYNERGY_MSG_SCREEN_SAVER, + SYNERGY_MSG_SCREEN_SAVER, PM_NOREMOVE)) { + return true; + } + + if (activated) { + if (!m_screensaverActive && + m_screensaver->checkStarted(SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) { + m_screensaverActive = true; + sendEvent(getScreensaverActivatedEvent()); + + // enable display power down + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); + } + } + else { + if (m_screensaverActive) { + m_screensaverActive = false; + sendEvent(getScreensaverDeactivatedEvent()); + + // disable display power down + CArchMiscWindows::addBusyState(CArchMiscWindows::kDISPLAY); + } + } + + return true; +} + +bool +CMSWindowsScreen::onDisplayChange() +{ + // screen resolution may have changed. save old shape. + SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h; + + // update shape + updateScreenShape(); + + // do nothing if resolution hasn't changed + if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) { + if (m_isPrimary) { + // warp mouse to center if off screen + if (!m_isOnScreen) { + warpCursor(m_xCenter, m_yCenter); + } + + // tell hook about resize if on screen + else { + m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + } + } + + // send new screen info + sendEvent(getShapeChangedEvent()); + + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + } + + return true; +} + +bool +CMSWindowsScreen::onClipboardChange() +{ + // now notify client that somebody changed the clipboard (unless + // we're the owner). + if (!CMSWindowsClipboard::isOwnedBySynergy()) { + if (m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership")); + m_ownClipboard = false; + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); + } + } + else if (!m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: synergy owned")); + m_ownClipboard = true; + } + + return true; +} + +void +CMSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + // send an event that we can recognize before the mouse warp + PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_PRE_WARP, x, y); + + // warp mouse. hopefully this inserts a mouse motion event + // between the previous message and the following message. + SetCursorPos(x, y); + + // yield the CPU. there's a race condition when warping: + // a hardware mouse event occurs + // the mouse hook is not called because that process doesn't have the CPU + // we send PRE_WARP, SetCursorPos(), send POST_WARP + // we process all of those events and update m_x, m_y + // we finish our time slice + // the hook is called + // the hook sends us a mouse event from the pre-warp position + // we get the CPU + // we compute a bogus warp + // we need the hook to process all mouse events that occur + // before we warp before we do the warp but i'm not sure how + // to guarantee that. yielding the CPU here may reduce the + // chance of undesired behavior. we'll also check for very + // large motions that look suspiciously like about half width + // or height of the screen. + ARCH->sleep(0.0); + + // send an event that we can recognize after the mouse warp + PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_POST_WARP, 0, 0); +} + +void +CMSWindowsScreen::nextMark() +{ + // next mark + ++m_mark; + + // mark point in message queue where the mark was changed + PostThreadMessage(GetCurrentThreadId(), SYNERGY_MSG_MARK, m_mark, 0); +} + +bool +CMSWindowsScreen::ignore() const +{ + return (m_mark != m_markReceived); +} + +void +CMSWindowsScreen::updateScreenShape() +{ + // get shape + m_x = GetSystemMetrics(SM_XVIRTUALSCREEN); + m_y = GetSystemMetrics(SM_YVIRTUALSCREEN); + m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + // get center for cursor + m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1; + m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1; + + // check for multiple monitors + m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) || + m_h != GetSystemMetrics(SM_CYSCREEN)); + + // tell the desks + m_desks->setShape(m_x, m_y, m_w, m_h, m_xCenter, m_yCenter, m_multimon); +} + +void +CMSWindowsScreen::handleFixes(const CEvent&, void*) +{ + // fix clipboard chain + fixClipboardViewer(); + + // update keys if keyboard layouts have changed + if (m_keyState->didGroupsChange()) { + updateKeys(); + } +} + +void +CMSWindowsScreen::fixClipboardViewer() +{ + // XXX -- disable this code for now. somehow it can cause an infinite + // recursion in the WM_DRAWCLIPBOARD handler. either we're sending + // the message to our own window or some window farther down the chain + // forwards the message to our window or a window farther up the chain. + // i'm not sure how that could happen. the m_nextClipboardWindow = NULL + // was not in the code that infinite loops and may fix the bug but i + // doubt it. +/* + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + m_nextClipboardWindow = SetClipboardViewer(m_window); +*/ +} + +void +CMSWindowsScreen::enableSpecialKeys(bool enable) const +{ + // enable/disable ctrl+alt+del, alt+tab, etc on win95 family. + // since the win95 family doesn't support low-level hooks, we + // use this undocumented feature to suppress normal handling + // of certain key combinations. + if (m_is95Family) { + DWORD dummy = 0; + SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, + enable ? FALSE : TRUE, &dummy, 0); + } +} + +ButtonID +CMSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const +{ + switch (msg) { + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCLBUTTONUP: + return kButtonLeft; + + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + case WM_MBUTTONUP: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONDBLCLK: + case WM_NCMBUTTONUP: + return kButtonMiddle; + + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_RBUTTONUP: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONUP: + return kButtonRight; + + case WM_XBUTTONDOWN: + case WM_XBUTTONDBLCLK: + case WM_XBUTTONUP: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONDBLCLK: + case WM_NCXBUTTONUP: + switch (button) { + case XBUTTON1: + if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 4) { + return kButtonExtra0 + 0; + } + break; + + case XBUTTON2: + if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 5) { + return kButtonExtra0 + 1; + } + break; + } + return kButtonNone; + + default: + return kButtonNone; + } +} + +bool +CMSWindowsScreen::mapPressFromEvent(WPARAM msg, LPARAM) const +{ + switch (msg) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + return true; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + return false; + + default: + return false; + } +} + +void +CMSWindowsScreen::updateKeysCB(void*) +{ + // record which keys we think are down + bool down[IKeyState::kNumButtons]; + bool sendFixes = (isPrimary() && !m_isOnScreen); + if (sendFixes) { + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + down[i] = m_keyState->isKeyDown(i); + } + } + + // update layouts if necessary + if (m_keyState->didGroupsChange()) { + CPlatformScreen::updateKeyMap(); + } + + // now update the keyboard state + CPlatformScreen::updateKeyState(); + + // now see which keys we thought were down but now think are up. + // send key releases for these keys to the active client. + if (sendFixes) { + KeyModifierMask mask = pollActiveModifiers(); + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (down[i] && !m_keyState->isKeyDown(i)) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, kKeyNone, mask, 1, i); + } + } + } +} + +void +CMSWindowsScreen::forceShowCursor() +{ + // check for mouse + m_hasMouse = (GetSystemMetrics(SM_MOUSEPRESENT) != 0); + + // decide if we should show the mouse + bool showMouse = (!m_hasMouse && !m_isPrimary && m_isOnScreen); + + // show/hide the mouse + if (showMouse != m_showingMouse) { + if (showMouse) { + m_oldMouseKeys.cbSize = sizeof(m_oldMouseKeys); + m_gotOldMouseKeys = + (SystemParametersInfo(SPI_GETMOUSEKEYS, + m_oldMouseKeys.cbSize, &m_oldMouseKeys, 0) != 0); + if (m_gotOldMouseKeys) { + m_mouseKeys = m_oldMouseKeys; + m_showingMouse = true; + updateForceShowCursor(); + } + } + else { + if (m_gotOldMouseKeys) { + SystemParametersInfo(SPI_SETMOUSEKEYS, + m_oldMouseKeys.cbSize, + &m_oldMouseKeys, SPIF_SENDCHANGE); + m_showingMouse = false; + } + } + } +} + +void +CMSWindowsScreen::updateForceShowCursor() +{ + DWORD oldFlags = m_mouseKeys.dwFlags; + + // turn on MouseKeys + m_mouseKeys.dwFlags = MKF_AVAILABLE | MKF_MOUSEKEYSON; + + // make sure MouseKeys is active in whatever state the NumLock is + // not currently in. + if ((m_keyState->getActiveModifiers() & KeyModifierNumLock) != 0) { + m_mouseKeys.dwFlags |= MKF_REPLACENUMBERS; + } + + // update MouseKeys + if (oldFlags != m_mouseKeys.dwFlags) { + SystemParametersInfo(SPI_SETMOUSEKEYS, + m_mouseKeys.cbSize, &m_mouseKeys, SPIF_SENDCHANGE); + } +} + +LRESULT CALLBACK +CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + assert(s_screen != NULL); + + LRESULT result = 0; + if (!s_screen->onEvent(hwnd, msg, wParam, lParam, &result)) { + result = DefWindowProc(hwnd, msg, wParam, lParam); + } + + return result; +} + + +// +// CMSWindowsScreen::CHotKeyItem +// + +CMSWindowsScreen::CHotKeyItem::CHotKeyItem(UINT keycode, UINT mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +UINT +CMSWindowsScreen::CHotKeyItem::getVirtualKey() const +{ + return m_keycode; +} + +bool +CMSWindowsScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h new file mode 100644 index 00000000..eda3f554 --- /dev/null +++ b/lib/platform/CMSWindowsScreen.h @@ -0,0 +1,308 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSSCREEN_H +#define CMSWINDOWSSCREEN_H + +#include "CPlatformScreen.h" +#include "CSynergyHook.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "CString.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEventQueueTimer; +class CMSWindowsDesks; +class CMSWindowsKeyState; +class CMSWindowsScreenSaver; +class CThread; + +//! Implementation of IPlatformScreen for Microsoft Windows +class CMSWindowsScreen : public CPlatformScreen { +public: + CMSWindowsScreen(bool isPrimary); + virtual ~CMSWindowsScreen(); + + //! @name manipulators + //@{ + + //! Initialize + /*! + Saves the application's HINSTANCE. This \b must be called by + WinMain with the HINSTANCE it was passed. + */ + static void init(HINSTANCE); + + //@} + //! @name accessors + //@{ + + //! Get instance + /*! + Returns the application instance handle passed to init(). + */ + static HINSTANCE getInstance(); + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, + KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IKeyState overrides + virtual void updateKeys(); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual void fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + // initialization and shutdown operations + HINSTANCE openHookLibrary(const char* name); + void closeHookLibrary(HINSTANCE hookLibrary) const; + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createWindowClass() const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // convenience function to send events + void sendEvent(CEvent::Type type, void* = NULL); + void sendClipboardEvent(CEvent::Type type, ClipboardID id); + + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatch(HWND, UINT, WPARAM, LPARAM); + + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatchPrimary(HWND, UINT, WPARAM, LPARAM); + + // handle message. returns true iff handled and optionally sets + // \c *result (which defaults to 0). + bool onEvent(HWND, UINT, WPARAM, LPARAM, LRESULT* result); + + // message handlers + bool onMark(UInt32 mark); + bool onKey(WPARAM, LPARAM); + bool onHotKey(WPARAM, LPARAM); + bool onMouseButton(WPARAM, LPARAM); + bool onMouseMove(SInt32 x, SInt32 y); + bool onMouseWheel(SInt32 xDelta, SInt32 yDelta); + bool onScreensaver(bool activated); + bool onDisplayChange(); + bool onClipboardChange(); + + // warp cursor without discarding queued events + void warpCursorNoFlush(SInt32 x, SInt32 y); + + // discard posted messages + void nextMark(); + + // test if event should be ignored + bool ignore() const; + + // update screen size cache + void updateScreenShape(); + + // fix timer callback + void handleFixes(const CEvent&, void*); + + // fix the clipboard viewer chain + void fixClipboardViewer(); + + // enable/disable special key combinations so we can catch/pass them + void enableSpecialKeys(bool) const; + + // map a button event to a button ID + ButtonID mapButtonFromEvent(WPARAM msg, LPARAM button) const; + + // map a button event to a press (true) or release (false) + bool mapPressFromEvent(WPARAM msg, LPARAM button) const; + + // job to update the key state + void updateKeysCB(void*); + + // determine whether the mouse is hidden by the system and force + // it to be displayed if user has entered this secondary screen. + void forceShowCursor(); + + // forceShowCursor uses MouseKeys to show the cursor. since we + // don't actually want MouseKeys behavior we have to make sure + // it applies when NumLock is in whatever state it's not in now. + // this method does that. + void updateForceShowCursor(); + + // our window proc + static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); + +private: + struct CHotKeyItem { + public: + CHotKeyItem(UINT vk, UINT modifiers); + + UINT getVirtualKey() const; + + bool operator<(const CHotKeyItem&) const; + + private: + UINT m_keycode; + UINT m_mask; + }; + typedef std::map HotKeyMap; + typedef std::vector HotKeyIDList; + typedef std::map HotKeyToIDMap; + + static HINSTANCE s_instance; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if windows 95/98/me + bool m_is95Family; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_class; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // last mouse position + SInt32 m_xCursor, m_yCursor; + + // last clipboard + UInt32 m_sequenceNumber; + + // used to discard queued messages that are no longer needed + UInt32 m_mark; + UInt32 m_markReceived; + + // the main loop's thread id + DWORD m_threadID; + + // timer for periodically checking stuff that requires polling + CEventQueueTimer* m_fixTimer; + + // the keyboard layout to use when off primary screen + HKL m_keyLayout; + + // screen saver stuff + CMSWindowsScreenSaver* m_screensaver; + bool m_screensaverNotify; + bool m_screensaverActive; + + // clipboard stuff. our window is used mainly as a clipboard + // owner and as a link in the clipboard viewer chain. + HWND m_window; + HWND m_nextClipboardWindow; + bool m_ownClipboard; + + // one desk per desktop and a cond var to communicate with it + CMSWindowsDesks* m_desks; + + // hook library stuff + HINSTANCE m_hookLibrary; + InitFunc m_init; + CleanupFunc m_cleanup; + SetSidesFunc m_setSides; + SetZoneFunc m_setZone; + SetModeFunc m_setMode; + + // keyboard stuff + CMSWindowsKeyState* m_keyState; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + HotKeyToIDMap m_hotKeyToIDMap; + + // map of button state + bool m_buttons[1 + kButtonExtra0 + 1]; + + // the system shows the mouse cursor when an internal display count + // is >= 0. this count is maintained per application but there's + // apparently a system wide count added to the application's count. + // this system count is 0 if there's a mouse attached to the system + // and -1 otherwise. the MouseKeys accessibility feature can modify + // this system count by making the system appear to have a mouse. + // + // m_hasMouse is true iff there's a mouse attached to the system or + // MouseKeys is simulating one. we track this so we can force the + // cursor to be displayed when the user has entered this screen. + // m_showingMouse is true when we're doing that. + bool m_hasMouse; + bool m_showingMouse; + bool m_gotOldMouseKeys; + MOUSEKEYS m_mouseKeys; + MOUSEKEYS m_oldMouseKeys; + + static CMSWindowsScreen* s_screen; +}; + +#endif diff --git a/lib/platform/CMSWindowsScreenSaver.cpp b/lib/platform/CMSWindowsScreenSaver.cpp new file mode 100644 index 00000000..7217338f --- /dev/null +++ b/lib/platform/CMSWindowsScreenSaver.cpp @@ -0,0 +1,498 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsScreenSaver.h" +#include "CMSWindowsScreen.h" +#include "CThread.h" +#include "CLog.h" +#include "TMethodJob.h" +#include "CArch.h" +#include "CArchMiscWindows.h" +#include +#include + +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +static const TCHAR* g_isSecureNT = "ScreenSaverIsSecure"; +static const TCHAR* g_isSecure9x = "ScreenSaveUsePassword"; +static const TCHAR* const g_pathScreenSaverIsSecure[] = { + "Control Panel", + "Desktop", + NULL +}; + +// +// CMSWindowsScreenSaver +// + +CMSWindowsScreenSaver::CMSWindowsScreenSaver() : + m_wasSecure(false), + m_wasSecureAnInt(false), + m_process(NULL), + m_watch(NULL), + m_threadID(0), + m_active(false) +{ + // detect OS + m_is95Family = false; + m_is95 = false; + m_isNT = false; + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + m_is95Family = (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + if (info.dwPlatformId == VER_PLATFORM_WIN32_NT && + info.dwMajorVersion <= 4) { + m_isNT = true; + } + else if (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && + info.dwMajorVersion == 4 && + info.dwMinorVersion == 0) { + m_is95 = true; + } + } + + // check if screen saver is enabled + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); +} + +CMSWindowsScreenSaver::~CMSWindowsScreenSaver() +{ + unwatchProcess(); +} + +bool +CMSWindowsScreenSaver::checkStarted(UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if already started then say it didn't just start + if (m_active) { + return false; + } + + // screen saver may have started. look for it and get + // the process. if we can't find it then assume it + // didn't really start. we wait a moment before + // looking to give the screen saver a chance to start. + // this shouldn't be a problem since we only get here + // if the screen saver wants to kick in, meaning that + // the system is idle or the user deliberately started + // the screen saver. + Sleep(250); + + // set parameters common to all screen saver handling + m_threadID = GetCurrentThreadId(); + m_msg = msg; + m_wParam = wParam; + m_lParam = lParam; + + // we handle the screen saver differently for the windows + // 95 and nt families. + if (m_is95Family) { + // on windows 95 we wait for the screen saver process + // to terminate. get the process. + DWORD processID = findScreenSaver(); + HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, processID); + if (process == NULL) { + // didn't start + LOG((CLOG_DEBUG2 "can't open screen saver process")); + return false; + } + + // watch for the process to exit + watchProcess(process); + } + else { + // on the windows nt family we wait for the desktop to + // change until it's neither the Screen-Saver desktop + // nor a desktop we can't open (the login desktop). + // since windows will send the request-to-start-screen- + // saver message even when the screen saver is disabled + // we first check that the screen saver is indeed active + // before watching for it to stop. + if (!isActive()) { + LOG((CLOG_DEBUG2 "can't open screen saver desktop")); + return false; + } + + watchDesktop(); + } + + return true; +} + +void +CMSWindowsScreenSaver::enable() +{ + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, m_wasEnabled, 0, 0); + + // restore password protection + if (m_wasSecure) { + setSecure(true, m_wasSecureAnInt); + } + + // restore display power down + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); +} + +void +CMSWindowsScreenSaver::disable() +{ + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, 0); + + // disable password protected screensaver + m_wasSecure = isSecure(&m_wasSecureAnInt); + if (m_wasSecure) { + setSecure(false, m_wasSecureAnInt); + } + + // disable display power down + CArchMiscWindows::addBusyState(CArchMiscWindows::kDISPLAY); +} + +void +CMSWindowsScreenSaver::activate() +{ + // don't activate if already active + if (!isActive()) { + // activate + HWND hwnd = GetForegroundWindow(); + if (hwnd != NULL) { + PostMessage(hwnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + else { + // no foreground window. pretend we got the event instead. + DefWindowProc(NULL, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + + // restore power save when screen saver activates + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); + } +} + +void +CMSWindowsScreenSaver::deactivate() +{ + bool killed = false; + if (!m_is95Family) { + // NT runs screen saver in another desktop + HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE, + DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS); + if (desktop != NULL) { + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::killScreenSaverFunc, + reinterpret_cast(&killed)); + CloseDesktop(desktop); + } + } + + // if above failed or wasn't tried, try the windows 95 way + if (!killed) { + // find screen saver window and close it + HWND hwnd = FindWindow("WindowsScreenSaverClass", NULL); + if (hwnd == NULL) { + // win2k may use a different class + hwnd = FindWindow("Default Screen Saver", NULL); + } + if (hwnd != NULL) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + } + + // force timer to restart + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + !m_wasEnabled, 0, SPIF_SENDWININICHANGE); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + m_wasEnabled, 0, SPIF_SENDWININICHANGE); + + // disable display power down + CArchMiscWindows::removeBusyState(CArchMiscWindows::kDISPLAY); +} + +bool +CMSWindowsScreenSaver::isActive() const +{ + if (m_is95) { + return (FindWindow("WindowsScreenSaverClass", NULL) != NULL); + } + else if (m_isNT) { + // screen saver runs on a separate desktop + HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE, MAXIMUM_ALLOWED); + if (desktop == NULL && GetLastError() != ERROR_ACCESS_DENIED) { + // desktop doesn't exist so screen saver is not running + return false; + } + + // desktop exists. this should indicate that the screen saver + // is running but an OS bug can cause a valid handle to be + // returned even if the screen saver isn't running (Q230117). + // we'll try to enumerate the windows on the desktop and, if + // there are any, we assume the screen saver is running. (note + // that if we don't have permission to enumerate then we'll + // assume that the screen saver is not running.) that'd be + // easy enough except there's another OS bug (Q198590) that can + // cause EnumDesktopWindows() to enumerate the windows of + // another desktop if the requested desktop has no windows. to + // work around that we have to verify that the enumerated + // windows are, in fact, on the expected desktop. + CFindScreenSaverInfo info; + info.m_desktop = desktop; + info.m_window = NULL; + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::findScreenSaverFunc, + reinterpret_cast(&info)); + + // done with desktop + CloseDesktop(desktop); + + // screen saver is running if a window was found + return (info.m_window != NULL); + } + else { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + return (running != FALSE); + } +} + +BOOL CALLBACK +CMSWindowsScreenSaver::findScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + CFindScreenSaverInfo* info = reinterpret_cast(arg); + + if (info->m_desktop != NULL) { + DWORD threadID = GetWindowThreadProcessId(hwnd, NULL); + HDESK desktop = GetThreadDesktop(threadID); + if (desktop != NULL && desktop != info->m_desktop) { + // stop enumerating -- wrong desktop + return FALSE; + } + } + + // found a window + info->m_window = hwnd; + + // don't need to enumerate further + return FALSE; +} + +BOOL CALLBACK +CMSWindowsScreenSaver::killScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + if (IsWindowVisible(hwnd)) { + HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + if (instance != CMSWindowsScreen::getInstance()) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + *reinterpret_cast(arg) = true; + } + } + return TRUE; +} + +DWORD +CMSWindowsScreenSaver::findScreenSaver() +{ + // try windows 95 way + HWND hwnd = FindWindow("WindowsScreenSaverClass", NULL); + + // get process ID of process that owns the window, if found + if (hwnd != NULL) { + DWORD processID; + GetWindowThreadProcessId(hwnd, &processID); + return processID; + } + + // not found + return 0; +} + +void +CMSWindowsScreenSaver::watchDesktop() +{ + // stop watching previous process/desktop + unwatchProcess(); + + // watch desktop in another thread + LOG((CLOG_DEBUG "watching screen saver desktop")); + m_active = true; + m_watch = new CThread(new TMethodJob(this, + &CMSWindowsScreenSaver::watchDesktopThread)); +} + +void +CMSWindowsScreenSaver::watchProcess(HANDLE process) +{ + // stop watching previous process/desktop + unwatchProcess(); + + // watch new process in another thread + if (process != NULL) { + LOG((CLOG_DEBUG "watching screen saver process")); + m_process = process; + m_active = true; + m_watch = new CThread(new TMethodJob(this, + &CMSWindowsScreenSaver::watchProcessThread)); + } +} + +void +CMSWindowsScreenSaver::unwatchProcess() +{ + if (m_watch != NULL) { + LOG((CLOG_DEBUG "stopped watching screen saver process/desktop")); + m_watch->cancel(); + m_watch->wait(); + delete m_watch; + m_watch = NULL; + m_active = false; + } + if (m_process != NULL) { + CloseHandle(m_process); + m_process = NULL; + } +} + +void +CMSWindowsScreenSaver::watchDesktopThread(void*) +{ + DWORD reserved = 0; + TCHAR* name = NULL; + + for (;;) { + // wait a bit + ARCH->sleep(0.2); + + if (m_isNT) { + // get current desktop + HDESK desk = OpenInputDesktop(0, FALSE, GENERIC_READ); + if (desk == NULL) { + // can't open desktop so keep waiting + continue; + } + + // get current desktop name length + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + + // allocate more space for the name, if necessary + if (size > reserved) { + reserved = size; + name = (TCHAR*)alloca(reserved + sizeof(TCHAR)); + } + + // get current desktop name + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + CloseDesktop(desk); + + // compare name to screen saver desktop name + if (_tcsicmp(name, TEXT("Screen-saver")) == 0) { + // still the screen saver desktop so keep waiting + continue; + } + } + else { + // 2000/XP have a sane way to detect a runnin screensaver. + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + if (running) { + continue; + } + } + + // send screen saver deactivation message + m_active = false; + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } +} + +void +CMSWindowsScreenSaver::watchProcessThread(void*) +{ + for (;;) { + CThread::testCancel(); + if (WaitForSingleObject(m_process, 50) == WAIT_OBJECT_0) { + // process terminated + LOG((CLOG_DEBUG "screen saver died")); + + // send screen saver deactivation message + m_active = false; + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } + } +} + +void +CMSWindowsScreenSaver::setSecure(bool secure, bool saveSecureAsInt) +{ + HKEY hkey = + CArchMiscWindows::addKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure); + if (hkey == NULL) { + return; + } + + const TCHAR* isSecure = m_is95Family ? g_isSecure9x : g_isSecureNT; + if (saveSecureAsInt) { + CArchMiscWindows::setValue(hkey, isSecure, secure ? 1 : 0); + } + else { + CArchMiscWindows::setValue(hkey, isSecure, secure ? "1" : "0"); + } + + CArchMiscWindows::closeKey(hkey); +} + +bool +CMSWindowsScreenSaver::isSecure(bool* wasSecureFlagAnInt) const +{ + // get the password protection setting key + HKEY hkey = + CArchMiscWindows::openKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure); + if (hkey == NULL) { + return false; + } + + // get the value. the value may be an int or a string, depending + // on the version of windows. + bool result; + const TCHAR* isSecure = m_is95Family ? g_isSecure9x : g_isSecureNT; + switch (CArchMiscWindows::typeOfValue(hkey, isSecure)) { + default: + result = false; + break; + + case CArchMiscWindows::kUINT: { + DWORD value = + CArchMiscWindows::readValueInt(hkey, isSecure); + *wasSecureFlagAnInt = true; + result = (value != 0); + break; + } + + case CArchMiscWindows::kSTRING: { + std::string value = + CArchMiscWindows::readValueString(hkey, isSecure); + *wasSecureFlagAnInt = false; + result = (value != "0"); + break; + } + } + + CArchMiscWindows::closeKey(hkey); + return result; +} diff --git a/lib/platform/CMSWindowsScreenSaver.h b/lib/platform/CMSWindowsScreenSaver.h new file mode 100644 index 00000000..bb43c703 --- /dev/null +++ b/lib/platform/CMSWindowsScreenSaver.h @@ -0,0 +1,92 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSSCREENSAVER_H +#define CMSWINDOWSSCREENSAVER_H + +#include "IScreenSaver.h" +#include "CString.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CThread; + +//! Microsoft windows screen saver implementation +class CMSWindowsScreenSaver : public IScreenSaver { +public: + CMSWindowsScreenSaver(); + virtual ~CMSWindowsScreenSaver(); + + //! @name manipulators + //@{ + + //! Check if screen saver started + /*! + Check if the screen saver really started. Returns false if it + hasn't, true otherwise. When the screen saver stops, \c msg will + be posted to the current thread's message queue with the given + parameters. + */ + bool checkStarted(UINT msg, WPARAM, LPARAM); + + //@} + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + class CFindScreenSaverInfo { + public: + HDESK m_desktop; + HWND m_window; + }; + + static BOOL CALLBACK findScreenSaverFunc(HWND hwnd, LPARAM lParam); + static BOOL CALLBACK killScreenSaverFunc(HWND hwnd, LPARAM lParam); + + DWORD findScreenSaver(); + void watchDesktop(); + void watchProcess(HANDLE process); + void unwatchProcess(); + void watchDesktopThread(void*); + void watchProcessThread(void*); + + void setSecure(bool secure, bool saveSecureAsInt); + bool isSecure(bool* wasSecureAnInt) const; + +private: + bool m_is95Family; + bool m_is95; + bool m_isNT; + BOOL m_wasEnabled; + bool m_wasSecure; + bool m_wasSecureAnInt; + + HANDLE m_process; + CThread* m_watch; + DWORD m_threadID; + UINT m_msg; + WPARAM m_wParam; + LPARAM m_lParam; + + // checkActive state. true if the screen saver is being watched + // for deactivation (and is therefore active). + bool m_active; +}; + +#endif diff --git a/lib/platform/CMSWindowsUtil.cpp b/lib/platform/CMSWindowsUtil.cpp new file mode 100644 index 00000000..4b3e3f4c --- /dev/null +++ b/lib/platform/CMSWindowsUtil.cpp @@ -0,0 +1,75 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsUtil.h" +#include "CStringUtil.h" +#include + +// +// CMSWindowsUtil +// + +CString +CMSWindowsUtil::getString(HINSTANCE instance, DWORD id) +{ + char buffer[1024]; + int size = static_cast(sizeof(buffer) / sizeof(buffer[0])); + char* msg = buffer; + + // load string + int n = LoadString(instance, id, msg, size); + msg[n] = '\0'; + if (n < size) { + return msg; + } + + // not enough buffer space. keep trying larger buffers until + // we get the whole string. + msg = NULL; + do { + size <<= 1; + delete[] msg; + char* msg = new char[size]; + n = LoadString(instance, id, msg, size); + } while (n == size); + msg[n] = '\0'; + + CString result(msg); + delete[] msg; + return result; +} + +CString +CMSWindowsUtil::getErrorString(HINSTANCE hinstance, DWORD error, DWORD id) +{ + char* buffer; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&buffer, + 0, + NULL) == 0) { + CString errorString = CStringUtil::print("%d", error); + return CStringUtil::format(getString(hinstance, id).c_str(), + errorString.c_str()); + } + else { + CString result(buffer); + LocalFree(buffer); + return result; + } +} diff --git a/lib/platform/CMSWindowsUtil.h b/lib/platform/CMSWindowsUtil.h new file mode 100644 index 00000000..5c4d14f5 --- /dev/null +++ b/lib/platform/CMSWindowsUtil.h @@ -0,0 +1,38 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSUTIL_H +#define CMSWINDOWSUTIL_H + +#include "CString.h" +#define WINDOWS_LEAN_AND_MEAN +#include + +class CMSWindowsUtil { +public: + //! Get message string + /*! + Gets a string for \p id from the string table of \p instance. + */ + static CString getString(HINSTANCE instance, DWORD id); + + //! Get error string + /*! + Gets a system error message for \p error. If the error cannot be + found return the string for \p id, replacing ${1} with \p error. + */ + static CString getErrorString(HINSTANCE, DWORD error, DWORD id); +}; + +#endif diff --git a/lib/platform/COSXClipboard.cpp b/lib/platform/COSXClipboard.cpp new file mode 100644 index 00000000..7d39eaad --- /dev/null +++ b/lib/platform/COSXClipboard.cpp @@ -0,0 +1,220 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXClipboard.h" +#include "COSXClipboardUTF16Converter.h" +#include "COSXClipboardTextConverter.h" +#include "CLog.h" +#include "XArch.h" + +// +// COSXClipboard +// + +COSXClipboard::COSXClipboard() : + m_time(0), + m_scrap(NULL) +{ + m_converters.push_back(new COSXClipboardUTF16Converter); + m_converters.push_back(new COSXClipboardTextConverter); +} + +COSXClipboard::~COSXClipboard() +{ + clearConverters(); +} + +bool +COSXClipboard::empty() +{ + LOG((CLOG_DEBUG "empty clipboard")); + assert(m_scrap != NULL); + + OSStatus err = ClearScrap(&m_scrap); + if (err != noErr) { + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + // we own the clipboard + err = PutScrapFlavor( + m_scrap, + getOwnershipFlavor(), + kScrapFlavorMaskNone, + 0, + 0); + if (err != noErr) { + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + return true; +} + +void +COSXClipboard::add(EFormat format, const CString & data) +{ + LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format)); + + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + + IOSXClipboardConverter* converter = *index; + + // skip converters for other formats + if (converter->getFormat() == format) { + CString osXData = converter->fromIClipboard(data); + ScrapFlavorType flavorType = converter->getOSXFormat(); + + PutScrapFlavor( + m_scrap, + flavorType, + kScrapFlavorMaskNone, + osXData.size(), + osXData.data()); + } + } +} + +bool +COSXClipboard::open(Time time) const +{ + LOG((CLOG_DEBUG "open clipboard")); + m_time = time; + OSStatus err = GetCurrentScrap(&m_scrap); + return (err == noErr); +} + +void +COSXClipboard::close() const +{ + LOG((CLOG_DEBUG "close clipboard")); + m_scrap = NULL; +} + +IClipboard::Time +COSXClipboard::getTime() const +{ + return m_time; +} + +bool +COSXClipboard::has(EFormat format) const +{ + assert(m_scrap != NULL); + + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IOSXClipboardConverter* converter = *index; + if (converter->getFormat() == format) { + ScrapFlavorFlags flags; + ScrapFlavorType type = converter->getOSXFormat(); + + if (GetScrapFlavorFlags(m_scrap, type, &flags) == noErr) { + return true; + } + } + } + + return false; +} + +CString +COSXClipboard::get(EFormat format) const +{ + CString result; + + // find the converter for the first clipboard format we can handle + IOSXClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + + ScrapFlavorFlags flags; + ScrapFlavorType type = converter->getOSXFormat(); + + if (converter->getFormat() == format && + GetScrapFlavorFlags(m_scrap, type, &flags) == noErr) { + break; + } + converter = NULL; + } + + // if no converter then we don't recognize any formats + if (converter == NULL) { + return result; + } + + // get the clipboard data. + char* buffer = NULL; + try { + Size flavorSize; + OSStatus err = GetScrapFlavorSize(m_scrap, + converter->getOSXFormat(), &flavorSize); + if (err != noErr) { + throw err; + } + + buffer = new char[flavorSize]; + if (buffer == NULL) { + throw memFullErr; + } + + err = GetScrapFlavorData(m_scrap, + converter->getOSXFormat(), &flavorSize, buffer); + if (err != noErr) { + throw err; + } + + result = CString(buffer, flavorSize); + } + catch (OSStatus err) { + LOG((CLOG_DEBUG "exception thrown in COSXClipboard::get MacError (%d)", err)); + } + catch (...) { + LOG((CLOG_DEBUG "unknown exception in COSXClipboard::get")); + RETHROW_XTHREAD + } + delete[] buffer; + + return converter->toIClipboard(result); +} + +void +COSXClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +bool +COSXClipboard::isOwnedBySynergy() +{ + ScrapFlavorFlags flags; + ScrapRef scrap; + OSStatus err = GetCurrentScrap(&scrap); + if (err == noErr) { + err = GetScrapFlavorFlags(scrap, getOwnershipFlavor() , &flags); + } + return (err == noErr); +} + +ScrapFlavorType +COSXClipboard::getOwnershipFlavor() +{ + return 'Syne'; +} diff --git a/lib/platform/COSXClipboard.h b/lib/platform/COSXClipboard.h new file mode 100644 index 00000000..19f95ec7 --- /dev/null +++ b/lib/platform/COSXClipboard.h @@ -0,0 +1,94 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARD_H +#define COSXCLIPBOARD_H + +#include "IClipboard.h" +#include +#include + +class IOSXClipboardConverter; + +//! OS X clipboard implementation +class COSXClipboard : public IClipboard { +public: + COSXClipboard(); + virtual ~COSXClipboard(); + + //! Test if clipboard is owned by synergy + static bool isOwnedBySynergy(); + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + void clearConverters(); + static ScrapFlavorType + getOwnershipFlavor(); + +private: + typedef std::vector ConverterList; + + mutable Time m_time; + ConverterList m_converters; + mutable ScrapRef m_scrap; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all Scrap book format +*/ +class IOSXClipboardConverter : public IInterface { +public: + //! @name accessors + //@{ + + //! Get clipboard format + /*! + Return the clipboard format this object converts from/to. + */ + virtual IClipboard::EFormat + getFormat() const = 0; + + //! returns the scrap flavor type that this object converts from/to + virtual ScrapFlavorType + getOSXFormat() const = 0; + + //! Convert from IClipboard format + /*! + Convert from the IClipboard format to the Carbon scrap format. + The input data must be in the IClipboard format returned by + getFormat(). The return data will be in the scrap + format returned by getOSXFormat(). + */ + virtual CString fromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Convert from the carbon scrap format to the IClipboard format + (i.e., the reverse of fromIClipboard()). + */ + virtual CString toIClipboard(const CString&) const = 0; + + //@} +}; + +#endif diff --git a/lib/platform/COSXClipboardAnyTextConverter.cpp b/lib/platform/COSXClipboardAnyTextConverter.cpp new file mode 100644 index 00000000..67fc3029 --- /dev/null +++ b/lib/platform/COSXClipboardAnyTextConverter.cpp @@ -0,0 +1,85 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXClipboardAnyTextConverter.h" +#include + +// +// COSXClipboardAnyTextConverter +// + +COSXClipboardAnyTextConverter::COSXClipboardAnyTextConverter() +{ + // do nothing +} + +COSXClipboardAnyTextConverter::~COSXClipboardAnyTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +COSXClipboardAnyTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +CString +COSXClipboardAnyTextConverter::fromIClipboard(const CString& data) const +{ + // convert linefeeds and then convert to desired encoding + return doFromIClipboard(convertLinefeedToMacOS(data)); +} + +CString +COSXClipboardAnyTextConverter::toIClipboard(const CString& data) const +{ + // convert text then newlines + return convertLinefeedToUnix(doToIClipboard(data)); +} + +static +bool +isLF(char ch) +{ + return (ch == '\n'); +} + +static +bool +isCR(char ch) +{ + return (ch == '\r'); +} + +CString +COSXClipboardAnyTextConverter::convertLinefeedToMacOS(const CString& src) +{ + // note -- we assume src is a valid UTF-8 string + CString copy = src; + + std::replace_if(copy.begin(), copy.end(), isLF, '\r'); + + return copy; +} + +CString +COSXClipboardAnyTextConverter::convertLinefeedToUnix(const CString& src) +{ + CString copy = src; + + std::replace_if(copy.begin(), copy.end(), isCR, '\n'); + + return copy; +} diff --git a/lib/platform/COSXClipboardAnyTextConverter.h b/lib/platform/COSXClipboardAnyTextConverter.h new file mode 100644 index 00000000..5d0b79f3 --- /dev/null +++ b/lib/platform/COSXClipboardAnyTextConverter.h @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARDANYTEXTCONVERTER_H +#define COSXCLIPBOARDANYTEXTCONVERTER_H + +#include "COSXClipboard.h" + +//! Convert to/from some text encoding +class COSXClipboardAnyTextConverter : public IOSXClipboardConverter { +public: + COSXClipboardAnyTextConverter(); + virtual ~COSXClipboardAnyTextConverter(); + + // IOSXClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual ScrapFlavorType + getOSXFormat() const = 0; + virtual CString fromIClipboard(const CString &) const; + virtual CString toIClipboard(const CString &) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion and linefeed conversion. + */ + virtual CString doFromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion and Linefeed conversion. + */ + virtual CString doToIClipboard(const CString&) const = 0; + +private: + static CString convertLinefeedToMacOS(const CString&); + static CString convertLinefeedToUnix(const CString&); +}; + +#endif diff --git a/lib/platform/COSXClipboardTextConverter.cpp b/lib/platform/COSXClipboardTextConverter.cpp new file mode 100644 index 00000000..bcbc228e --- /dev/null +++ b/lib/platform/COSXClipboardTextConverter.cpp @@ -0,0 +1,88 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXClipboardTextConverter.h" +#include "CUnicode.h" + +// +// COSXClipboardTextConverter +// + +COSXClipboardTextConverter::COSXClipboardTextConverter() +{ + // do nothing +} + +COSXClipboardTextConverter::~COSXClipboardTextConverter() +{ + // do nothing +} + +ScrapFlavorType +COSXClipboardTextConverter::getOSXFormat() const +{ + return kScrapFlavorTypeText; +} + +CString +COSXClipboardTextConverter::convertString( + const CString& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding) +{ + CFStringRef stringRef = + CFStringCreateWithCString(kCFAllocatorDefault, + data.c_str(), fromEncoding); + + if (stringRef == NULL) { + return CString(); + } + + CFIndex buffSize; + CFRange entireString = CFRangeMake(0, CFStringGetLength(stringRef)); + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, NULL, 0, &buffSize); + + char* buffer = new char[buffSize]; + + if (buffer == NULL) { + CFRelease(stringRef); + return CString(); + } + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, (UInt8*)buffer, buffSize, NULL); + + CString result(buffer, buffSize); + + delete[] buffer; + CFRelease(stringRef); + + return result; +} + +CString +COSXClipboardTextConverter::doFromIClipboard(const CString& data) const +{ + return convertString(data, kCFStringEncodingUTF8, + CFStringGetSystemEncoding()); +} + +CString +COSXClipboardTextConverter::doToIClipboard(const CString& data) const +{ + return convertString(data, CFStringGetSystemEncoding(), + kCFStringEncodingUTF8); +} diff --git a/lib/platform/COSXClipboardTextConverter.h b/lib/platform/COSXClipboardTextConverter.h new file mode 100644 index 00000000..2a75f4f0 --- /dev/null +++ b/lib/platform/COSXClipboardTextConverter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARDTEXTCONVERTER_H +#define COSXCLIPBOARDTEXTCONVERTER_H + +#include "COSXClipboardAnyTextConverter.h" + +//! Convert to/from locale text encoding +class COSXClipboardTextConverter : public COSXClipboardAnyTextConverter { +public: + COSXClipboardTextConverter(); + virtual ~COSXClipboardTextConverter(); + + // IOSXClipboardAnyTextConverter overrides + virtual ScrapFlavorType + getOSXFormat() const; + +protected: + // COSXClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; + + // generic encoding converter + static CString convertString(const CString& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding); +}; + +#endif diff --git a/lib/platform/COSXClipboardUTF16Converter.cpp b/lib/platform/COSXClipboardUTF16Converter.cpp new file mode 100644 index 00000000..10693c83 --- /dev/null +++ b/lib/platform/COSXClipboardUTF16Converter.cpp @@ -0,0 +1,50 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXClipboardUTF16Converter.h" +#include "CUnicode.h" + +// +// COSXClipboardUTF16Converter +// + +COSXClipboardUTF16Converter::COSXClipboardUTF16Converter() +{ + // do nothing +} + +COSXClipboardUTF16Converter::~COSXClipboardUTF16Converter() +{ + // do nothing +} + +ScrapFlavorType +COSXClipboardUTF16Converter::getOSXFormat() const +{ + return kScrapFlavorTypeUnicode; +} + +CString +COSXClipboardUTF16Converter::doFromIClipboard(const CString& data) const +{ + // convert and add nul terminator + return CUnicode::UTF8ToUTF16(data); +} + +CString +COSXClipboardUTF16Converter::doToIClipboard(const CString& data) const +{ + // convert and strip nul terminator + return CUnicode::UTF16ToUTF8(data); +} diff --git a/lib/platform/COSXClipboardUTF16Converter.h b/lib/platform/COSXClipboardUTF16Converter.h new file mode 100644 index 00000000..1499a7ed --- /dev/null +++ b/lib/platform/COSXClipboardUTF16Converter.h @@ -0,0 +1,36 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXCLIPBOARDUTF16CONVERTER_H +#define COSXCLIPBOARDUTF16CONVERTER_H + +#include "COSXClipboardAnyTextConverter.h" + +//! Convert to/from UTF-16 encoding +class COSXClipboardUTF16Converter : public COSXClipboardAnyTextConverter { +public: + COSXClipboardUTF16Converter(); + virtual ~COSXClipboardUTF16Converter(); + + // IOSXClipboardAnyTextConverter overrides + virtual ScrapFlavorType + getOSXFormat() const; + +protected: + // COSXClipboardAnyTextConverter overrides + virtual CString doFromIClipboard(const CString&) const; + virtual CString doToIClipboard(const CString&) const; +}; + +#endif diff --git a/lib/platform/COSXEventQueueBuffer.cpp b/lib/platform/COSXEventQueueBuffer.cpp new file mode 100644 index 00000000..5bd0d747 --- /dev/null +++ b/lib/platform/COSXEventQueueBuffer.cpp @@ -0,0 +1,124 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXEventQueueBuffer.h" +#include "CEvent.h" +#include "IEventQueue.h" + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + +// +// COSXEventQueueBuffer +// + +COSXEventQueueBuffer::COSXEventQueueBuffer() : + m_event(NULL) +{ + // do nothing +} + +COSXEventQueueBuffer::~COSXEventQueueBuffer() +{ + // release the last event + if (m_event != NULL) { + ReleaseEvent(m_event); + } +} + +void +COSXEventQueueBuffer::waitForEvent(double timeout) +{ + EventRef event; + ReceiveNextEvent(0, NULL, timeout, false, &event); +} + +IEventQueueBuffer::Type +COSXEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + // release the previous event + if (m_event != NULL) { + ReleaseEvent(m_event); + m_event = NULL; + } + + // get the next event + OSStatus error = ReceiveNextEvent(0, NULL, 0.0, true, &m_event); + + // handle the event + if (error == eventLoopQuitErr) { + event = CEvent(CEvent::kQuit); + return kSystem; + } + else if (error != noErr) { + return kNone; + } + else { + UInt32 eventClass = GetEventClass(m_event); + switch (eventClass) { + case 'Syne': + dataID = GetEventKind(m_event); + return kUser; + + default: + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; + } + } +} + +bool +COSXEventQueueBuffer::addEvent(UInt32 dataID) +{ + EventRef event; + OSStatus error = CreateEvent( + kCFAllocatorDefault, + 'Syne', + dataID, + 0, + kEventAttributeNone, + &event); + + if (error == noErr) { + error = PostEventToQueue(GetMainEventQueue(), event, + kEventPriorityStandard); + ReleaseEvent(event); + } + + return (error == noErr); +} + +bool +COSXEventQueueBuffer::isEmpty() const +{ + EventRef event; + OSStatus status = ReceiveNextEvent(0, NULL, 0.0, false, &event); + return (status == eventLoopTimedOutErr); +} + +CEventQueueTimer* +COSXEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +COSXEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} diff --git a/lib/platform/COSXEventQueueBuffer.h b/lib/platform/COSXEventQueueBuffer.h new file mode 100644 index 00000000..659b5940 --- /dev/null +++ b/lib/platform/COSXEventQueueBuffer.h @@ -0,0 +1,40 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXEVENTQUEUEBUFFER_H +#define COSXEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include + +//! Event queue buffer for OS X +class COSXEventQueueBuffer : public IEventQueueBuffer { +public: + COSXEventQueueBuffer(); + virtual ~COSXEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + EventRef m_event; +}; + +#endif diff --git a/lib/platform/COSXKeyState.cpp b/lib/platform/COSXKeyState.cpp new file mode 100644 index 00000000..e904d5e4 --- /dev/null +++ b/lib/platform/COSXKeyState.cpp @@ -0,0 +1,1237 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXKeyState.h" +#include "CLog.h" +#include "CArch.h" + +// Hardcoded virtual key table. Oddly, Apple doesn't document the +// meaning of virtual key codes. The whole point of *virtual* key +// codes is to make them hardware independent so these codes should +// be constant across OS versions and hardware. Yet they don't +// tell us what codes map to what keys so we have to figure it out +// for ourselves. +// +// Note that some virtual keys codes appear more than once. The +// first instance of a virtual key code maps to the KeyID that we +// want to generate for that code. The others are for mapping +// different KeyIDs to a single key code. +static const UInt32 s_shiftVK = 56; +static const UInt32 s_controlVK = 59; +static const UInt32 s_altVK = 55; +static const UInt32 s_superVK = 58; +static const UInt32 s_capsLockVK = 57; +static const UInt32 s_numLockVK = 71; +static const UInt32 s_osxNumLock = 1 << 16; +struct CKeyEntry { +public: + KeyID m_keyID; + UInt32 m_virtualKey; +}; +static const CKeyEntry s_controlKeys[] = { + // cursor keys. if we don't do this we'll may still get these from + // the keyboard resource but they may not correspond to the arrow + // keys. + { kKeyLeft, 123 }, + { kKeyRight, 124 }, + { kKeyUp, 126 }, + { kKeyDown, 125 }, + { kKeyHome, 115 }, + { kKeyEnd, 119 }, + { kKeyPageUp, 116 }, + { kKeyPageDown, 121 }, + + // function keys + { kKeyF1, 122 }, + { kKeyF2, 120 }, + { kKeyF3, 99 }, + { kKeyF4, 118 }, + { kKeyF5, 96 }, + { kKeyF6, 97 }, + { kKeyF7, 98 }, + { kKeyF8, 100 }, + { kKeyF9, 101 }, + { kKeyF10, 109 }, + { kKeyF11, 103 }, + { kKeyF12, 111 }, + { kKeyF13, 105 }, + { kKeyF14, 107 }, + { kKeyF15, 113 }, + { kKeyF16, 106 }, + + { kKeyKP_0, 82 }, + { kKeyKP_1, 83 }, + { kKeyKP_2, 84 }, + { kKeyKP_3, 85 }, + { kKeyKP_4, 86 }, + { kKeyKP_5, 87 }, + { kKeyKP_6, 88 }, + { kKeyKP_7, 89 }, + { kKeyKP_8, 91 }, + { kKeyKP_9, 92 }, + { kKeyKP_Decimal, 65 }, + { kKeyKP_Equal, 81 }, + { kKeyKP_Multiply, 67 }, + { kKeyKP_Add, 69 }, + { kKeyKP_Divide, 75 }, + { kKeyKP_Subtract, 79 }, + { kKeyKP_Enter, 76 }, + + // virtual key 110 is fn+enter and i have no idea what that's supposed + // to map to. also the enter key with numlock on is a modifier but i + // don't know which. + + // modifier keys. OS X doesn't seem to support right handed versions + // of modifier keys so we map them to the left handed versions. + { kKeyShift_L, s_shiftVK }, + { kKeyShift_R, s_shiftVK }, // 60 + { kKeyControl_L, s_controlVK }, + { kKeyControl_R, s_controlVK }, // 62 + { kKeyAlt_L, s_altVK }, + { kKeyAlt_R, s_altVK }, + { kKeySuper_L, s_superVK }, + { kKeySuper_R, s_superVK }, // 61 + { kKeyMeta_L, s_superVK }, + { kKeyMeta_R, s_superVK }, // 61 + + // toggle modifiers + { kKeyNumLock, s_numLockVK }, + { kKeyCapsLock, s_capsLockVK } +}; + + +// +// COSXKeyState +// + +COSXKeyState::COSXKeyState() : + m_deadKeyState(0) +{ + // enable input in scripts other that roman + KeyScript(smKeyEnableKybds); + + // build virtual key map + for (size_t i = 0; i < sizeof(s_controlKeys) / + sizeof(s_controlKeys[0]); ++i) { + m_virtualKeyMap[s_controlKeys[i].m_virtualKey] = + s_controlKeys[i].m_keyID; + } +} + +COSXKeyState::~COSXKeyState() +{ + // do nothing +} + +KeyModifierMask +COSXKeyState::mapModifiersFromOSX(UInt32 mask) const +{ +LOG((CLOG_DEBUG1 "mask: %04x", mask)); + // convert + KeyModifierMask outMask = 0; + if ((mask & shiftKey) != 0) { + outMask |= KeyModifierShift; + } + if ((mask & rightShiftKey) != 0) { + outMask |= KeyModifierShift; + } + if ((mask & controlKey) != 0) { + outMask |= KeyModifierControl; + } + if ((mask & rightControlKey) != 0) { + outMask |= KeyModifierControl; + } + if ((mask & cmdKey) != 0) { + outMask |= KeyModifierAlt; + } + if ((mask & optionKey) != 0) { + outMask |= KeyModifierSuper; + } + if ((mask & rightOptionKey) != 0) { + outMask |= KeyModifierSuper; + } + if ((mask & alphaLock) != 0) { + outMask |= KeyModifierCapsLock; + } + if ((mask & s_osxNumLock) != 0) { + outMask |= KeyModifierNumLock; + } + + return outMask; +} + +KeyButton +COSXKeyState::mapKeyFromEvent(CKeyIDs& ids, + KeyModifierMask* maskOut, EventRef event) const +{ + ids.clear(); + + // map modifier key + if (maskOut != NULL) { + KeyModifierMask activeMask = getActiveModifiers(); + activeMask &= ~KeyModifierAltGr; + *maskOut = activeMask; + } + + // get virtual key + UInt32 vkCode; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, + NULL, sizeof(vkCode), NULL, &vkCode); + + // handle up events + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventRawKeyUp) { + // the id isn't used. we just need the same button we used on + // the key press. note that we don't use or reset the dead key + // state; up events should not affect the dead key state. + ids.push_back(kKeyNone); + return mapVirtualKeyToKeyButton(vkCode); + } + + // check for special keys + CVirtualKeyMap::const_iterator i = m_virtualKeyMap.find(vkCode); + if (i != m_virtualKeyMap.end()) { + m_deadKeyState = 0; + ids.push_back(i->second); + return mapVirtualKeyToKeyButton(vkCode); + } + + // get keyboard info + KeyboardLayoutRef keyboardLayout; + OSStatus status = KLGetCurrentKeyboardLayout(&keyboardLayout); + if (status != noErr) { + return kKeyNone; + } + + // get the event modifiers and remove the command and control + // keys. note if we used them though. + UInt32 modifiers; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, + NULL, sizeof(modifiers), NULL, &modifiers); + static const UInt32 s_commandModifiers = + cmdKey | controlKey | rightControlKey; + bool isCommand = ((modifiers & s_commandModifiers) != 0); + modifiers &= ~s_commandModifiers; + + // if we've used a command key then we want the glyph produced without + // the option key (i.e. the base glyph). + if (isCommand) { + modifiers &= ~optionKey; + } + + // translate via uchr resource + const void* resource; + if (KLGetKeyboardLayoutProperty(keyboardLayout, + kKLuchrData, &resource) == noErr) { + // choose action + UInt16 action; + switch (eventKind) { + case kEventRawKeyDown: + action = kUCKeyActionDown; + break; + + case kEventRawKeyRepeat: + action = kUCKeyActionAutoKey; + break; + + default: + return 0; + } + + // translate key + UniCharCount count; + UniChar chars[2]; + OSStatus status = UCKeyTranslate((const UCKeyboardLayout*)resource, + vkCode & 0xffu, action, + (modifiers >> 8) & 0xffu, + LMGetKbdType(), 0, &m_deadKeyState, + sizeof(chars) / sizeof(chars[0]), &count, chars); + + // get the characters + if (status == 0) { + if (count != 0 || m_deadKeyState == 0) { + m_deadKeyState = 0; + for (UniCharCount i = 0; i < count; ++i) { + ids.push_back(CKeyResource::unicharToKeyID(chars[i])); + } + adjustAltGrModifier(ids, maskOut, isCommand); + return mapVirtualKeyToKeyButton(vkCode); + } + return 0; + } + } + + // translate via KCHR resource + if (KLGetKeyboardLayoutProperty(keyboardLayout, + kKLKCHRData, &resource) == noErr) { + // build keycode + UInt16 keycode = + static_cast((modifiers & 0xff00u) | (vkCode & 0x00ffu)); + + // translate key + UInt32 result = KeyTranslate(resource, keycode, &m_deadKeyState); + + // get the characters + UInt8 c1 = static_cast((result >> 16) & 0xffu); + UInt8 c2 = static_cast( result & 0xffu); + if (c2 != 0) { + m_deadKeyState = 0; + if (c1 != 0) { + ids.push_back(CKeyResource::getKeyID(c1)); + } + ids.push_back(CKeyResource::getKeyID(c2)); + adjustAltGrModifier(ids, maskOut, isCommand); + return mapVirtualKeyToKeyButton(vkCode); + } + } + + return 0; +} + +bool +COSXKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + +KeyModifierMask +COSXKeyState::pollActiveModifiers() const +{ + return mapModifiersFromOSX(GetCurrentKeyModifiers()); +} + +SInt32 +COSXKeyState::pollActiveGroup() const +{ + KeyboardLayoutRef keyboardLayout; + OSStatus status = KLGetCurrentKeyboardLayout(&keyboardLayout); + if (status == noErr) { + GroupMap::const_iterator i = m_groupMap.find(keyboardLayout); + if (i != m_groupMap.end()) { + return i->second; + } + } + return 0; +} + +void +COSXKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + KeyMap km; + GetKeys(km); + const UInt8* m = reinterpret_cast(km); + for (UInt32 i = 0; i < 16; ++i) { + for (UInt32 j = 0; j < 8; ++j) { + if ((m[i] & (1u << j)) != 0) { + pressedKeys.insert(mapVirtualKeyToKeyButton(8 * i + j)); + } + } + } +} + +void +COSXKeyState::getKeyMap(CKeyMap& keyMap) +{ + // update keyboard groups + if (getGroups(m_groups)) { + m_groupMap.clear(); + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + m_groupMap[m_groups[g]] = g; + } + } + + UInt32 keyboardType = LMGetKbdType(); + for (SInt32 g = 0, n = (SInt32)m_groups.size(); g < n; ++g) { + // add special keys + getKeyMapForSpecialKeys(keyMap, g); + + // add regular keys + + // try uchr resource first + const void* resource; + if (KLGetKeyboardLayoutProperty(m_groups[g], + kKLuchrData, &resource) == noErr) { + CUCHRKeyResource uchr(resource, keyboardType); + if (uchr.isValid()) { + LOG((CLOG_DEBUG1 "using uchr resource for group %d", g)); + getKeyMap(keyMap, g, uchr); + continue; + } + } + + // try KCHR resource + if (KLGetKeyboardLayoutProperty(m_groups[g], + kKLKCHRData, &resource) == noErr) { + CKCHRKeyResource kchr(resource); + if (kchr.isValid()) { + LOG((CLOG_DEBUG1 "using KCHR resource for group %d", g)); + getKeyMap(keyMap, g, kchr); + continue; + } + } + + LOG((CLOG_DEBUG1 "no keyboard resource for group %d", g)); + } +} + +void +COSXKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + + // let system figure out character for us + CGPostKeyboardEvent(0, mapKeyButtonToVirtualKey( + keystroke.m_data.m_button.m_button), + keystroke.m_data.m_button.m_press); + + // add a delay if client data isn't zero + if (keystroke.m_data.m_button.m_client) { + ARCH->sleep(0.01); + } + break; + + case Keystroke::kGroup: + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); + setGroup(keystroke.m_data.m_group.m_group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); + setGroup(getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + break; + } +} + +void +COSXKeyState::getKeyMapForSpecialKeys(CKeyMap& keyMap, SInt32 group) const +{ + // special keys are insensitive to modifers and none are dead keys + CKeyMap::KeyItem item; + for (size_t i = 0; i < sizeof(s_controlKeys) / + sizeof(s_controlKeys[0]); ++i) { + const CKeyEntry& entry = s_controlKeys[i]; + item.m_id = entry.m_keyID; + item.m_group = group; + item.m_button = mapVirtualKeyToKeyButton(entry.m_virtualKey); + item.m_required = 0; + item.m_sensitive = 0; + item.m_dead = false; + item.m_client = 0; + CKeyMap::initModifierKey(item); + keyMap.addKeyEntry(item); + + if (item.m_lock) { + // all locking keys are half duplex on OS X + keyMap.addHalfDuplexButton(item.m_button); + } + } + + // note: we don't special case the number pad keys. querying the + // mac keyboard returns the non-keypad version of those keys but + // a CKeyState always provides a mapping from keypad keys to + // non-keypad keys so we'll be able to generate the characters + // anyway. +} + +bool +COSXKeyState::getKeyMap(CKeyMap& keyMap, + SInt32 group, const CKeyResource& r) const +{ + if (!r.isValid()) { + return false; + } + + // space for all possible modifier combinations + std::vector modifiers(r.getNumModifierCombinations()); + + // make space for the keys that any single button can synthesize + std::vector > buttonKeys(r.getNumTables()); + + // iterate over each button + CKeyMap::KeyItem item; + for (UInt32 i = 0; i < r.getNumButtons(); ++i) { + item.m_button = mapVirtualKeyToKeyButton(i); + + // the KeyIDs we've already handled + std::set keys; + + // convert the entry in each table for this button to a KeyID + for (UInt32 j = 0; j < r.getNumTables(); ++j) { + buttonKeys[j].first = r.getKey(j, i); + buttonKeys[j].second = CKeyMap::isDeadKey(buttonKeys[j].first); + } + + // iterate over each character table + for (UInt32 j = 0; j < r.getNumTables(); ++j) { + // get the KeyID for the button/table + KeyID id = buttonKeys[j].first; + if (id == kKeyNone) { + continue; + } + + // if we've already handled the KeyID in the table then + // move on to the next table + if (keys.count(id) > 0) { + continue; + } + keys.insert(id); + + // prepare item. the client state is 1 for dead keys. + item.m_id = id; + item.m_group = group; + item.m_dead = buttonKeys[j].second; + item.m_client = buttonKeys[j].second ? 1 : 0; + CKeyMap::initModifierKey(item); + if (item.m_lock) { + // all locking keys are half duplex on OS X + keyMap.addHalfDuplexButton(i); + } + + // collect the tables that map to the same KeyID. we know it + // can't be any earlier tables because of the check above. + std::set tables; + tables.insert(static_cast(j)); + for (UInt32 k = j + 1; k < r.getNumTables(); ++k) { + if (buttonKeys[k].first == id) { + tables.insert(static_cast(k)); + } + } + + // collect the modifier combinations that map to any of the + // tables we just collected + for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) { + modifiers[k] = (tables.count(r.getTableForModifier(k)) > 0); + } + + // figure out which modifiers the key is sensitive to. the + // key is insensitive to a modifier if for every modifier mask + // with the modifier bit unset in the modifiers we also find + // the same mask with the bit set. + // + // we ignore a few modifiers that we know aren't important + // for generating characters. in fact, we want to ignore any + // characters generated by the control key. we don't map + // those and instead expect the control modifier plus a key. + UInt32 sensitive = 0; + for (UInt32 k = 0; (1u << k) < + r.getNumModifierCombinations(); ++k) { + UInt32 bit = (1u << k); + if ((bit << 8) == cmdKey || + (bit << 8) == controlKey || + (bit << 8) == rightControlKey) { + continue; + } + for (UInt32 m = 0; m < r.getNumModifierCombinations(); ++m) { + if (modifiers[m] != modifiers[m ^ bit]) { + sensitive |= bit; + break; + } + } + } + + // find each required modifier mask. the key can be synthesized + // using any of the masks. + std::set required; + for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) { + if ((k & sensitive) == k && modifiers[k & sensitive]) { + required.insert(k); + } + } + + // now add a key entry for each key/required modifier pair. + item.m_sensitive = mapModifiersFromOSX(sensitive << 8); + for (std::set::iterator k = required.begin(); + k != required.end(); ++k) { + item.m_required = mapModifiersFromOSX(*k << 8); + keyMap.addKeyEntry(item); + } + } + } + + return true; +} + +bool +COSXKeyState::mapSynergyHotKeyToMac(KeyID key, KeyModifierMask mask, + UInt32 &macVirtualKey, UInt32 &macModifierMask) const +{ + // look up button for key + KeyButton button = getButton(key, pollActiveGroup()); + if (button == 0 && key != kKeyNone) { + return false; + } + macVirtualKey = mapKeyButtonToVirtualKey(button); + + // calculate modifier mask + macModifierMask = 0; + if ((mask & KeyModifierShift) != 0) { + macModifierMask |= shiftKey; + } + if ((mask & KeyModifierControl) != 0) { + macModifierMask |= controlKey; + } + if ((mask & KeyModifierAlt) != 0) { + macModifierMask |= cmdKey; + } + if ((mask & KeyModifierSuper) != 0) { + macModifierMask |= optionKey; + } + if ((mask & KeyModifierCapsLock) != 0) { + macModifierMask |= alphaLock; + } + if ((mask & KeyModifierNumLock) != 0) { + macModifierMask |= s_osxNumLock; + } + + return true; +} + +void +COSXKeyState::handleModifierKeys(void* target, + KeyModifierMask oldMask, KeyModifierMask newMask) +{ + // compute changed modifiers + KeyModifierMask changed = (oldMask ^ newMask); + + // synthesize changed modifier keys + if ((changed & KeyModifierShift) != 0) { + handleModifierKey(target, s_shiftVK, kKeyShift_L, + (newMask & KeyModifierShift) != 0, newMask); + } + if ((changed & KeyModifierControl) != 0) { + handleModifierKey(target, s_controlVK, kKeyControl_L, + (newMask & KeyModifierControl) != 0, newMask); + } + if ((changed & KeyModifierAlt) != 0) { + handleModifierKey(target, s_altVK, kKeyAlt_L, + (newMask & KeyModifierAlt) != 0, newMask); + } + if ((changed & KeyModifierSuper) != 0) { + handleModifierKey(target, s_superVK, kKeySuper_L, + (newMask & KeyModifierSuper) != 0, newMask); + } + if ((changed & KeyModifierCapsLock) != 0) { + handleModifierKey(target, s_capsLockVK, kKeyCapsLock, + (newMask & KeyModifierCapsLock) != 0, newMask); + } + if ((changed & KeyModifierNumLock) != 0) { + handleModifierKey(target, s_numLockVK, kKeyNumLock, + (newMask & KeyModifierNumLock) != 0, newMask); + } +} + +void +COSXKeyState::handleModifierKey(void* target, + UInt32 virtualKey, KeyID id, + bool down, KeyModifierMask newMask) +{ + KeyButton button = mapVirtualKeyToKeyButton(virtualKey); + onKey(button, down, newMask); + sendKeyEvent(target, down, false, id, newMask, 0, button); +} + +bool +COSXKeyState::getGroups(GroupList& groups) const +{ + // get number of layouts + CFIndex n; + OSStatus status = KLGetKeyboardLayoutCount(&n); + if (status != noErr) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + return false; + } + + // get each layout + groups.clear(); + for (CFIndex i = 0; i < n; ++i) { + KeyboardLayoutRef keyboardLayout; + status = KLGetKeyboardLayoutAtIndex(i, &keyboardLayout); + if (status == noErr) { + groups.push_back(keyboardLayout); + } + } + return true; +} + +void +COSXKeyState::setGroup(SInt32 group) +{ + KLSetCurrentKeyboardLayout(m_groups[group]); +} + +void +COSXKeyState::checkKeyboardLayout() +{ + // XXX -- should call this when notified that groups have changed. + // if no notification for that then we should poll. + GroupList groups; + if (getGroups(groups) && groups != m_groups) { + updateKeyMap(); + updateKeyState(); + } +} + +void +COSXKeyState::adjustAltGrModifier(const CKeyIDs& ids, + KeyModifierMask* mask, bool isCommand) const +{ + if (!isCommand) { + for (CKeyIDs::const_iterator i = ids.begin(); i != ids.end(); ++i) { + KeyID id = *i; + if (id != kKeyNone && + ((id < 0xe000u || id > 0xefffu) || + (id >= kKeyKP_Equal && id <= kKeyKP_9))) { + *mask |= KeyModifierAltGr; + return; + } + } + } +} + +KeyButton +COSXKeyState::mapVirtualKeyToKeyButton(UInt32 keyCode) +{ + // 'A' maps to 0 so shift every id + return static_cast(keyCode + KeyButtonOffset); +} + +UInt32 +COSXKeyState::mapKeyButtonToVirtualKey(KeyButton keyButton) +{ + return static_cast(keyButton - KeyButtonOffset); +} + + +// +// COSXKeyState::CKeyResource +// + +KeyID +COSXKeyState::CKeyResource::getKeyID(UInt8 c) +{ + if (c == 0) { + return kKeyNone; + } + else if (c >= 32 && c < 127) { + // ASCII + return static_cast(c); + } + else { + // handle special keys + switch (c) { + case 0x01: + return kKeyHome; + + case 0x02: + return kKeyKP_Enter; + + case 0x03: + return kKeyKP_Enter; + + case 0x04: + return kKeyEnd; + + case 0x05: + return kKeyHelp; + + case 0x08: + return kKeyBackSpace; + + case 0x09: + return kKeyTab; + + case 0x0b: + return kKeyPageUp; + + case 0x0c: + return kKeyPageDown; + + case 0x0d: + return kKeyReturn; + + case 0x10: + // OS X maps all the function keys (F1, etc) to this one key. + // we can't determine the right key here so we have to do it + // some other way. + return kKeyNone; + + case 0x1b: + return kKeyEscape; + + case 0x1c: + return kKeyLeft; + + case 0x1d: + return kKeyRight; + + case 0x1e: + return kKeyUp; + + case 0x1f: + return kKeyDown; + + case 0x7f: + return kKeyDelete; + + case 0x06: + case 0x07: + case 0x0a: + case 0x0e: + case 0x0f: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + // discard other control characters + return kKeyNone; + + default: + // not special or unknown + break; + } + + // create string with character + char str[2]; + str[0] = static_cast(c); + str[1] = 0; + + // convert to unicode + CFStringRef cfString = + CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, + str, GetScriptManagerVariable(smKeyScript), + kCFAllocatorNull); + + // sometimes CFStringCreate...() returns NULL (e.g. Apple Korean + // encoding with char value 214). if it did then make no key, + // otherwise CFStringCreateMutableCopy() will crash. + if (cfString == NULL) { + return kKeyNone; + } + + // convert to precomposed + CFMutableStringRef mcfString = + CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfString); + CFRelease(cfString); + CFStringNormalize(mcfString, kCFStringNormalizationFormC); + + // check result + int unicodeLength = CFStringGetLength(mcfString); + if (unicodeLength == 0) { + CFRelease(mcfString); + return kKeyNone; + } + if (unicodeLength > 1) { + // FIXME -- more than one character, we should handle this + CFRelease(mcfString); + return kKeyNone; + } + + // get unicode character + UniChar uc = CFStringGetCharacterAtIndex(mcfString, 0); + CFRelease(mcfString); + + // convert to KeyID + return static_cast(uc); + } +} + +KeyID +COSXKeyState::CKeyResource::unicharToKeyID(UniChar c) +{ + switch (c) { + case 3: + return kKeyKP_Enter; + + case 8: + return kKeyBackSpace; + + case 9: + return kKeyTab; + + case 13: + return kKeyReturn; + + case 27: + return kKeyEscape; + + case 127: + return kKeyDelete; + + default: + if (c < 32) { + return kKeyNone; + } + return static_cast(c); + } +} + + +// +// COSXKeyState::CKCHRKeyResource +// + +COSXKeyState::CKCHRKeyResource::CKCHRKeyResource(const void* resource) +{ + m_resource = reinterpret_cast(resource); +} + +bool +COSXKeyState::CKCHRKeyResource::isValid() const +{ + return (m_resource != NULL); +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getNumModifierCombinations() const +{ + // only 32 (not 256) because the righthanded modifier bits are ignored + return 32; +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getNumTables() const +{ + return m_resource->m_numTables; +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getNumButtons() const +{ + return 128; +} + +UInt32 +COSXKeyState::CKCHRKeyResource::getTableForModifier(UInt32 mask) const +{ + assert(mask < getNumModifierCombinations()); + + return m_resource->m_tableSelectionIndex[mask]; +} + +KeyID +COSXKeyState::CKCHRKeyResource::getKey(UInt32 table, UInt32 button) const +{ + assert(table < getNumTables()); + assert(button < getNumButtons()); + + UInt8 c = m_resource->m_characterTables[table][button]; + if (c == 0) { + // could be a dead key + const CKCHRDeadKeys* dkp = + reinterpret_cast( + m_resource->m_characterTables[getNumTables()]); + const CKCHRDeadKeyRecord* dkr = dkp->m_records; + for (SInt16 i = 0; i < dkp->m_numRecords; ++i) { + if (dkr->m_tableIndex == table && dkr->m_virtualKey == button) { + // get the no completion entry + c = dkr->m_completion[dkr->m_numCompletions][1]; + return CKeyMap::getDeadKey(getKeyID(c)); + } + + // next table. skip all the completions and the no match + // pair to get the next table. + dkr = reinterpret_cast( + dkr->m_completion[dkr->m_numCompletions + 1]); + } + } + + return getKeyID(c); +} + + +// +// COSXKeyState::CUCHRKeyResource +// + +COSXKeyState::CUCHRKeyResource::CUCHRKeyResource(const void* resource, + UInt32 keyboardType) : + m_m(NULL), + m_cti(NULL), + m_sdi(NULL), + m_sri(NULL), + m_st(NULL) +{ + m_resource = reinterpret_cast(resource); + if (m_resource == NULL) { + return; + } + + // find the keyboard info for the current keyboard type + const UCKeyboardTypeHeader* th = NULL; + const UCKeyboardLayout* r = m_resource; + for (ItemCount i = 0; i < r->keyboardTypeCount; ++i) { + if (keyboardType >= r->keyboardTypeList[i].keyboardTypeFirst && + keyboardType <= r->keyboardTypeList[i].keyboardTypeLast) { + th = r->keyboardTypeList + i; + break; + } + if (r->keyboardTypeList[i].keyboardTypeFirst == 0) { + // found the default. use it unless we find a match. + th = r->keyboardTypeList + i; + } + } + if (th == NULL) { + // cannot find a suitable keyboard type + return; + } + + // get tables for keyboard type + const UInt8* base = reinterpret_cast(m_resource); + m_m = reinterpret_cast(base + + th->keyModifiersToTableNumOffset); + m_cti = reinterpret_cast(base + + th->keyToCharTableIndexOffset); + m_sdi = reinterpret_cast(base + + th->keySequenceDataIndexOffset); + if (th->keyStateRecordsIndexOffset != 0) { + m_sri = reinterpret_cast(base + + th->keyStateRecordsIndexOffset); + } + if (th->keyStateTerminatorsOffset != 0) { + m_st = reinterpret_cast(base + + th->keyStateTerminatorsOffset); + } + + // find the space key, but only if it can combine with dead keys. + // a dead key followed by a space yields the non-dead version of + // the dead key. + m_spaceOutput = 0xffffu; + UInt32 table = getTableForModifier(0); + for (UInt32 button = 0, n = getNumButtons(); button < n; ++button) { + KeyID id = getKey(table, button); + if (id == 0x20) { + UCKeyOutput c = + reinterpret_cast(base + + m_cti->keyToCharTableOffsets[table])[button]; + if ((c & kUCKeyOutputTestForIndexMask) == + kUCKeyOutputStateIndexMask) { + m_spaceOutput = (c & kUCKeyOutputGetIndexMask); + break; + } + } + } +} + +bool +COSXKeyState::CUCHRKeyResource::isValid() const +{ + return (m_m != NULL); +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getNumModifierCombinations() const +{ + // only 32 (not 256) because the righthanded modifier bits are ignored + return 32; +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getNumTables() const +{ + return m_cti->keyToCharTableCount; +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getNumButtons() const +{ + return m_cti->keyToCharTableSize; +} + +UInt32 +COSXKeyState::CUCHRKeyResource::getTableForModifier(UInt32 mask) const +{ + if (mask >= m_m->modifiersCount) { + return m_m->defaultTableNum; + } + else { + return m_m->tableNum[mask]; + } +} + +KeyID +COSXKeyState::CUCHRKeyResource::getKey(UInt32 table, UInt32 button) const +{ + assert(table < getNumTables()); + assert(button < getNumButtons()); + + const UInt8* base = reinterpret_cast(m_resource); + const UCKeyOutput c = reinterpret_cast(base + + m_cti->keyToCharTableOffsets[table])[button]; + + KeySequence keys; + switch (c & kUCKeyOutputTestForIndexMask) { + case kUCKeyOutputStateIndexMask: + if (!getDeadKey(keys, c & kUCKeyOutputGetIndexMask)) { + return kKeyNone; + } + break; + + case kUCKeyOutputSequenceIndexMask: + default: + if (!addSequence(keys, c)) { + return kKeyNone; + } + break; + } + + // XXX -- no support for multiple characters + if (keys.size() != 1) { + return kKeyNone; + } + + return keys.front(); +} + +bool +COSXKeyState::CUCHRKeyResource::getDeadKey( + KeySequence& keys, UInt16 index) const +{ + if (m_sri == NULL || index >= m_sri->keyStateRecordCount) { + // XXX -- should we be using some other fallback? + return false; + } + + UInt16 state = 0; + if (!getKeyRecord(keys, index, state)) { + return false; + } + if (state == 0) { + // not a dead key + return true; + } + + // no dead keys if we couldn't find the space key + if (m_spaceOutput == 0xffffu) { + return false; + } + + // the dead key should not have put anything in the key list + if (!keys.empty()) { + return false; + } + + // get the character generated by pressing the space key after the + // dead key. if we're still in a compose state afterwards then we're + // confused so we bail. + if (!getKeyRecord(keys, m_spaceOutput, state) || state != 0) { + return false; + } + + // convert keys to their dead counterparts + for (KeySequence::iterator i = keys.begin(); i != keys.end(); ++i) { + *i = CKeyMap::getDeadKey(*i); + } + + return true; +} + +bool +COSXKeyState::CUCHRKeyResource::getKeyRecord( + KeySequence& keys, UInt16 index, UInt16& state) const +{ + const UInt8* base = reinterpret_cast(m_resource); + const UCKeyStateRecord* sr = + reinterpret_cast(base + + m_sri->keyStateRecordOffsets[index]); + const UCKeyStateEntryTerminal* kset = + reinterpret_cast(sr->stateEntryData); + + UInt16 nextState = 0; + bool found = false; + if (state == 0) { + found = true; + nextState = sr->stateZeroNextState; + if (!addSequence(keys, sr->stateZeroCharData)) { + return false; + } + } + else { + // we have a next entry + switch (sr->stateEntryFormat) { + case kUCKeyStateEntryTerminalFormat: + for (UInt16 j = 0; j < sr->stateEntryCount; ++j) { + if (kset[j].curState == state) { + if (!addSequence(keys, kset[j].charData)) { + return false; + } + nextState = 0; + found = true; + break; + } + } + break; + + case kUCKeyStateEntryRangeFormat: + // XXX -- not supported yet + break; + + default: + // XXX -- unknown format + return false; + } + } + if (!found) { + // use a terminator + if (m_st != NULL && state < m_st->keyStateTerminatorCount) { + if (!addSequence(keys, m_st->keyStateTerminators[state - 1])) { + return false; + } + } + nextState = sr->stateZeroNextState; + if (!addSequence(keys, sr->stateZeroCharData)) { + return false; + } + } + + // next + state = nextState; + + return true; +} + +bool +COSXKeyState::CUCHRKeyResource::addSequence( + KeySequence& keys, UCKeyCharSeq c) const +{ + if ((c & kUCKeyOutputTestForIndexMask) == kUCKeyOutputSequenceIndexMask) { + UInt16 index = (c & kUCKeyOutputGetIndexMask); + if (index < m_sdi->charSequenceCount && + m_sdi->charSequenceOffsets[index] != + m_sdi->charSequenceOffsets[index + 1]) { + // XXX -- sequences not supported yet + return false; + } + } + + if (c != 0xfffe && c != 0xffff) { + KeyID id = unicharToKeyID(c); + if (id != kKeyNone) { + keys.push_back(id); + } + } + + return true; +} diff --git a/lib/platform/COSXKeyState.h b/lib/platform/COSXKeyState.h new file mode 100644 index 00000000..baf69713 --- /dev/null +++ b/lib/platform/COSXKeyState.h @@ -0,0 +1,235 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXKEYSTATE_H +#define COSXKEYSTATE_H + +#include "CKeyState.h" +#include "stdmap.h" +#include "stdset.h" +#include "stdvector.h" +#include + +//! OS X key state +/*! +A key state for OS X. +*/ +class COSXKeyState : public CKeyState { +public: + typedef std::vector CKeyIDs; + + COSXKeyState(); + virtual ~COSXKeyState(); + + //! @name modifiers + //@{ + + //! Handle modifier key change + /*! + Determines which modifier keys have changed and updates the modifier + state and sends key events as appropriate. + */ + void handleModifierKeys(void* target, + KeyModifierMask oldMask, KeyModifierMask newMask); + + //@} + //! @name accessors + //@{ + + //! Convert OS X modifier mask to synergy mask + /*! + Returns the synergy modifier mask corresponding to the OS X modifier + mask in \p mask. + */ + KeyModifierMask mapModifiersFromOSX(UInt32 mask) const; + + //! Map key event to keys + /*! + Converts a key event into a sequence of KeyIDs and the shadow modifier + state to a modifier mask. The KeyIDs list, in order, the characters + generated by the key press/release. It returns the id of the button + that was pressed or released, or 0 if the button doesn't map to a known + KeyID. + */ + KeyButton mapKeyFromEvent(CKeyIDs& ids, + KeyModifierMask* maskOut, EventRef event) const; + + //! Map key and mask to native values + /*! + Calculates mac virtual key and mask for a key \p key and modifiers + \p mask. Returns \c true if the key can be mapped, \c false otherwise. + */ + bool mapSynergyHotKeyToMac(KeyID key, KeyModifierMask mask, + UInt32& macVirtualKey, + UInt32& macModifierMask) const; + + //@} + + // IKeyState overrides + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + +protected: + // CKeyState overrides + virtual void getKeyMap(CKeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + +private: + class CKeyResource; + typedef std::vector GroupList; + + // Add hard coded special keys to a CKeyMap. + void getKeyMapForSpecialKeys( + CKeyMap& keyMap, SInt32 group) const; + + // Convert keyboard resource to a key map + bool getKeyMap(CKeyMap& keyMap, + SInt32 group, const CKeyResource& r) const; + + // Get the available keyboard groups + bool getGroups(GroupList&) const; + + // Change active keyboard group to group + void setGroup(SInt32 group); + + // Check if the keyboard layout has changed and update keyboard state + // if so. + void checkKeyboardLayout(); + + // Send an event for the given modifier key + void handleModifierKey(void* target, + UInt32 virtualKey, KeyID id, + bool down, KeyModifierMask newMask); + + // Checks if any in \p ids is a glyph key and if \p isCommand is false. + // If so it adds the AltGr modifier to \p mask. This allows OS X + // servers to use the option key both as AltGr and as a modifier. If + // option is acting as AltGr (i.e. it generates a glyph and there are + // no command modifiers active) then we don't send the super modifier + // to clients because they'd try to match it as a command modifier. + void adjustAltGrModifier(const CKeyIDs& ids, + KeyModifierMask* mask, bool isCommand) const; + + // Maps an OS X virtual key id to a KeyButton. This simply remaps + // the ids so we don't use KeyButton 0. + static KeyButton mapVirtualKeyToKeyButton(UInt32 keyCode); + + // Maps a KeyButton to an OS X key code. This is the inverse of + // mapVirtualKeyToKeyButton. + static UInt32 mapKeyButtonToVirtualKey(KeyButton keyButton); + +private: + class CKeyResource : public IInterface { + public: + virtual bool isValid() const = 0; + virtual UInt32 getNumModifierCombinations() const = 0; + virtual UInt32 getNumTables() const = 0; + virtual UInt32 getNumButtons() const = 0; + virtual UInt32 getTableForModifier(UInt32 mask) const = 0; + virtual KeyID getKey(UInt32 table, UInt32 button) const = 0; + + // Convert a character in the current script to the equivalent KeyID + static KeyID getKeyID(UInt8); + + // Convert a unicode character to the equivalent KeyID. + static KeyID unicharToKeyID(UniChar); + }; + + class CKCHRKeyResource : public CKeyResource { + public: + CKCHRKeyResource(const void*); + + // CKeyResource overrides + virtual bool isValid() const; + virtual UInt32 getNumModifierCombinations() const; + virtual UInt32 getNumTables() const; + virtual UInt32 getNumButtons() const; + virtual UInt32 getTableForModifier(UInt32 mask) const; + virtual KeyID getKey(UInt32 table, UInt32 button) const; + + private: + struct KCHRResource { + public: + SInt16 m_version; + UInt8 m_tableSelectionIndex[256]; + SInt16 m_numTables; + UInt8 m_characterTables[1][128]; + }; + struct CKCHRDeadKeyRecord { + public: + UInt8 m_tableIndex; + UInt8 m_virtualKey; + SInt16 m_numCompletions; + UInt8 m_completion[1][2]; + }; + struct CKCHRDeadKeys { + public: + SInt16 m_numRecords; + CKCHRDeadKeyRecord m_records[1]; + }; + + const KCHRResource* m_resource; + }; + + class CUCHRKeyResource : public CKeyResource { + public: + CUCHRKeyResource(const void*, UInt32 keyboardType); + + // CKeyResource overrides + virtual bool isValid() const; + virtual UInt32 getNumModifierCombinations() const; + virtual UInt32 getNumTables() const; + virtual UInt32 getNumButtons() const; + virtual UInt32 getTableForModifier(UInt32 mask) const; + virtual KeyID getKey(UInt32 table, UInt32 button) const; + + private: + typedef std::vector KeySequence; + + bool getDeadKey(KeySequence& keys, UInt16 index) const; + bool getKeyRecord(KeySequence& keys, + UInt16 index, UInt16& state) const; + bool addSequence(KeySequence& keys, UCKeyCharSeq c) const; + + private: + const UCKeyboardLayout* m_resource; + const UCKeyModifiersToTableNum* m_m; + const UCKeyToCharTableIndex* m_cti; + const UCKeySequenceDataIndex* m_sdi; + const UCKeyStateRecordsIndex* m_sri; + const UCKeyStateTerminators* m_st; + UInt16 m_spaceOutput; + }; + + // OS X uses a physical key if 0 for the 'A' key. synergy reserves + // KeyButton 0 so we offset all OS X physical key ids by this much + // when used as a KeyButton and by minus this much to map a KeyButton + // to a physical button. + enum { + KeyButtonOffset = 1 + }; + + typedef std::map GroupMap; + typedef std::map CVirtualKeyMap; + + CVirtualKeyMap m_virtualKeyMap; + mutable UInt32 m_deadKeyState; + GroupList m_groups; + GroupMap m_groupMap; +}; + +#endif diff --git a/lib/platform/COSXScreen.cpp b/lib/platform/COSXScreen.cpp new file mode 100644 index 00000000..82574cf4 --- /dev/null +++ b/lib/platform/COSXScreen.cpp @@ -0,0 +1,1699 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "COSXScreen.h" +#include "COSXClipboard.h" +#include "COSXEventQueueBuffer.h" +#include "COSXKeyState.h" +#include "COSXScreenSaver.h" +#include "CClipboard.h" +#include "CKeyMap.h" +#include "CCondVar.h" +#include "CLock.h" +#include "CMutex.h" +#include "CThread.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "XArch.h" + +#include +#include + +// Set some enums for fast user switching if we're building with an SDK +// from before such support was added. +#if !defined(MAC_OS_X_VERSION_10_3) || \ + (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_3) +enum { + kEventClassSystem = 'macs', + kEventSystemUserSessionActivated = 10, + kEventSystemUserSessionDeactivated = 11 +}; +#endif + +// This isn't in any Apple SDK that I know of as of yet. +enum { + kSynergyEventMouseScroll = 11, + kSynergyMouseScrollAxisX = 'saxx', + kSynergyMouseScrollAxisY = 'saxy' +}; + +// +// COSXScreen +// + +bool COSXScreen::s_testedForGHOM = false; +bool COSXScreen::s_hasGHOM = false; +CEvent::Type COSXScreen::s_confirmSleepEvent = CEvent::kUnknown; + +COSXScreen::COSXScreen(bool isPrimary) : + m_isPrimary(isPrimary), + m_isOnScreen(m_isPrimary), + m_cursorPosValid(false), + m_cursorHidden(false), + m_dragNumButtonsDown(0), + m_dragTimer(NULL), + m_keyState(NULL), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_ownClipboard(false), + m_clipboardTimer(NULL), + m_hiddenWindow(NULL), + m_userInputWindow(NULL), + m_displayManagerNotificationUPP(NULL), + m_switchEventHandlerRef(0), + m_pmMutex(new CMutex), + m_pmWatchThread(NULL), + m_pmThreadReady(new CCondVar(m_pmMutex, false)), + m_activeModifierHotKey(0), + m_activeModifierHotKeyMask(0) +{ + try { + m_displayID = CGMainDisplayID(); + updateScreenShape(); + m_screensaver = new COSXScreenSaver(getEventTarget()); + m_keyState = new COSXKeyState(); + + if (m_isPrimary) { + // 1x1 window (to minimze the back buffer allocated for this + // window. + Rect bounds = { 100, 100, 101, 101 }; + + // m_hiddenWindow is a window meant to let us get mouse moves + // when the focus is on another computer. If you get your event + // from the application event target you'll get every mouse + // moves. On the other hand the Window event target will only + // get events when the mouse moves over the window. + + // The ignoreClicks attributes makes it impossible for the + // user to click on our invisible window. + CreateNewWindow(kUtilityWindowClass, + kWindowNoShadowAttribute | + kWindowIgnoreClicksAttribute | + kWindowNoActivatesAttribute, + &bounds, &m_hiddenWindow); + + // Make it invisible + SetWindowAlpha(m_hiddenWindow, 0); + ShowWindow(m_hiddenWindow); + + // m_userInputWindow is a window meant to let us get mouse moves + // when the focus is on this computer. + Rect inputBounds = { 100, 100, 200, 200 }; + CreateNewWindow(kUtilityWindowClass, + kWindowNoShadowAttribute | + kWindowOpaqueForEventsAttribute | + kWindowStandardHandlerAttribute, + &inputBounds, &m_userInputWindow); + + SetWindowAlpha(m_userInputWindow, 0); + } + + // install display manager notification handler + m_displayManagerNotificationUPP = + NewDMExtendedNotificationUPP(displayManagerCallback); + OSStatus err = GetCurrentProcess(&m_PSN); + err = DMRegisterExtendedNotifyProc(m_displayManagerNotificationUPP, + this, 0, &m_PSN); + + // install fast user switching event handler + EventTypeSpec switchEventTypes[2]; + switchEventTypes[0].eventClass = kEventClassSystem; + switchEventTypes[0].eventKind = kEventSystemUserSessionDeactivated; + switchEventTypes[1].eventClass = kEventClassSystem; + switchEventTypes[1].eventKind = kEventSystemUserSessionActivated; + EventHandlerUPP switchEventHandler = + NewEventHandlerUPP(userSwitchCallback); + InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes, + this, &m_switchEventHandlerRef); + DisposeEventHandlerUPP(switchEventHandler); + + // watch for requests to sleep + EVENTQUEUE->adoptHandler(COSXScreen::getConfirmSleepEvent(), + getEventTarget(), + new TMethodEventJob(this, + &COSXScreen::handleConfirmSleep)); + + // create thread for monitoring system power state. + LOG((CLOG_DEBUG "starting watchSystemPowerThread")); + m_pmWatchThread = new CThread(new TMethodJob + (this, &COSXScreen::watchSystemPowerThread)); + } + catch (...) { + EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(), + getEventTarget()); + if (m_switchEventHandlerRef != 0) { + RemoveEventHandler(m_switchEventHandlerRef); + } + if (m_displayManagerNotificationUPP != NULL) { + DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP, + NULL, &m_PSN, 0); + } + + if (m_hiddenWindow) { + ReleaseWindow(m_hiddenWindow); + m_hiddenWindow = NULL; + } + + if (m_userInputWindow) { + ReleaseWindow(m_userInputWindow); + m_userInputWindow = NULL; + } + delete m_keyState; + delete m_screensaver; + throw; + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &COSXScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new COSXEventQueueBuffer); +} + +COSXScreen::~COSXScreen() +{ + disable(); + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + + if (m_pmWatchThread) { + // make sure the thread has setup the runloop. + { + CLock lock(m_pmMutex); + while (!(bool)*m_pmThreadReady) { + m_pmThreadReady->wait(); + } + } + + // now exit the thread's runloop and wait for it to exit + LOG((CLOG_DEBUG "stopping watchSystemPowerThread")); + CFRunLoopStop(m_pmRunloop); + m_pmWatchThread->wait(); + delete m_pmWatchThread; + m_pmWatchThread = NULL; + } + delete m_pmThreadReady; + delete m_pmMutex; + + EVENTQUEUE->removeHandler(COSXScreen::getConfirmSleepEvent(), + getEventTarget()); + + RemoveEventHandler(m_switchEventHandlerRef); + + DMRemoveExtendedNotifyProc(m_displayManagerNotificationUPP, + NULL, &m_PSN, 0); + + if (m_hiddenWindow) { + ReleaseWindow(m_hiddenWindow); + m_hiddenWindow = NULL; + } + + if (m_userInputWindow) { + ReleaseWindow(m_userInputWindow); + m_userInputWindow = NULL; + } + + delete m_keyState; + delete m_screensaver; +} + +void* +COSXScreen::getEventTarget() const +{ + return const_cast(this); +} + +bool +COSXScreen::getClipboard(ClipboardID, IClipboard* dst) const +{ + COSXClipboard src; + CClipboard::copy(dst, &src); + return true; +} + +void +COSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +COSXScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + Point mouse; + GetGlobalMouse(&mouse); + x = mouse.h; + y = mouse.v; + m_cursorPosValid = true; + m_xCursor = x; + m_yCursor = y; +} + +void +COSXScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +COSXScreen::warpCursor(SInt32 x, SInt32 y) +{ + // move cursor without generating events + CGPoint pos; + pos.x = x; + pos.y = y; + CGWarpMouseCursorPosition(pos); + + // save new cursor position + m_xCursor = x; + m_yCursor = y; + m_cursorPosValid = true; +} + +void +COSXScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +COSXScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +COSXScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +COSXScreen::isAnyMouseButtonDown() const +{ + return (GetCurrentButtonState() != 0); +} + +void +COSXScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +UInt32 +COSXScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // get mac virtual key and modifier mask matching synergy key and mask + UInt32 macKey, macMask; + if (!m_keyState->mapSynergyHotKeyToMac(key, mask, macKey, macMask)) { + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + + // if this hot key has modifiers only then we'll handle it specially + EventHotKeyRef ref = NULL; + bool okay; + if (key == kKeyNone) { + if (m_modifierHotKeys.count(mask) > 0) { + // already registered + okay = false; + } + else { + m_modifierHotKeys[mask] = id; + okay = true; + } + } + else { + EventHotKeyID hkid = { 'SNRG', (UInt32)id }; + OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, + GetApplicationEventTarget(), 0, + &ref); + okay = (status == noErr); + m_hotKeyToIDMap[CHotKeyItem(macKey, macMask)] = id; + } + + if (!okay) { + m_oldHotKeyIDs.push_back(id); + m_hotKeyToIDMap.erase(CHotKeyItem(macKey, macMask)); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + m_hotKeys.insert(std::make_pair(id, CHotKeyItem(ref, macKey, macMask))); + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +COSXScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool okay; + if (i->second.getRef() != NULL) { + okay = (UnregisterEventHotKey(i->second.getRef()) == noErr); + } + else { + okay = false; + // XXX -- this is inefficient + for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin(); + j != m_modifierHotKeys.end(); ++j) { + if (j->second == id) { + m_modifierHotKeys.erase(j); + okay = true; + break; + } + } + } + if (!okay) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeyToIDMap.erase(i->second); + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); + if (m_activeModifierHotKey == id) { + m_activeModifierHotKey = 0; + m_activeModifierHotKeyMask = 0; + } +} + +void +COSXScreen::postMouseEvent(CGPoint& pos) const +{ + // check if cursor position is valid on the client display configuration + // stkamp@users.sourceforge.net + CGDisplayCount displayCount = 0; + CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount); + if (displayCount == 0) { + // cursor position invalid - clamp to bounds of last valid display. + // find the last valid display using the last cursor position. + displayCount = 0; + CGDirectDisplayID displayID; + CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1, + &displayID, &displayCount); + if (displayCount != 0) { + CGRect displayRect = CGDisplayBounds(displayID); + if (pos.x < displayRect.origin.x) { + pos.x = displayRect.origin.x; + } + else if (pos.x > displayRect.origin.x + + displayRect.size.width - 1) { + pos.x = displayRect.origin.x + displayRect.size.width - 1; + } + if (pos.y < displayRect.origin.y) { + pos.y = displayRect.origin.y; + } + else if (pos.y > displayRect.origin.y + + displayRect.size.height - 1) { + pos.y = displayRect.origin.y + displayRect.size.height - 1; + } + } + } + + // synthesize event. CGPostMouseEvent is a particularly good + // example of a bad API. we have to shadow the mouse state to + // use this API and if we want to support more buttons we have + // to recompile. + // + // the order of buttons on the mac is: + // 1 - Left + // 2 - Right + // 3 - Middle + // Whatever the USB device defined. + // + // It is a bit weird that the behaviour of buttons over 3 are dependent + // on currently plugged in USB devices. + CGPostMouseEvent(pos, true, sizeof(m_buttons) / sizeof(m_buttons[0]), + m_buttons[0], + m_buttons[2], + m_buttons[1], + m_buttons[3], + m_buttons[4]); +} + + +void +COSXScreen::fakeMouseButton(ButtonID id, bool press) const +{ + // get button index + UInt32 index = id - kButtonLeft; + if (index >= sizeof(m_buttons) / sizeof(m_buttons[0])) { + return; + } + + // update state + m_buttons[index] = press; + + CGPoint pos; + if (!m_cursorPosValid) { + SInt32 x, y; + getCursorPos(x, y); + } + pos.x = m_xCursor; + pos.y = m_yCursor; + postMouseEvent(pos); +} + +void +COSXScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + // synthesize event + CGPoint pos; + pos.x = x; + pos.y = y; + postMouseEvent(pos); + + // save new cursor position + m_xCursor = static_cast(pos.x); + m_yCursor = static_cast(pos.y); + m_cursorPosValid = true; +} + +void +COSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // OS X does not appear to have a fake relative mouse move function. + // simulate it by getting the current mouse position and adding to + // that. this can yield the wrong answer but there's not much else + // we can do. + + // get current position + Point oldPos; + GetGlobalMouse(&oldPos); + + // synthesize event + CGPoint pos; + m_xCursor = static_cast(oldPos.h); + m_yCursor = static_cast(oldPos.v); + pos.x = oldPos.h + dx; + pos.y = oldPos.v + dy; + postMouseEvent(pos); + + // we now assume we don't know the current cursor position + m_cursorPosValid = false; +} + +void +COSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + if (xDelta != 0 || yDelta != 0) { + CGPostScrollWheelEvent(2, mapScrollWheelFromSynergy(yDelta), + -mapScrollWheelFromSynergy(xDelta)); + } +} + +void +COSXScreen::enable() +{ + // watch the clipboard + m_clipboardTimer = EVENTQUEUE->newTimer(1.0, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_clipboardTimer, + new TMethodEventJob(this, + &COSXScreen::handleClipboardCheck)); + + if (m_isPrimary) { + // FIXME -- start watching jump zones + } + else { + // FIXME -- prevent system from entering power save mode + + // hide cursor + if (!m_cursorHidden) { +// CGDisplayHideCursor(m_displayID); + m_cursorHidden = true; + } + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + + // FIXME -- prepare to show cursor if it moves + } +} + +void +COSXScreen::disable() +{ + if (m_isPrimary) { + // FIXME -- stop watching jump zones, stop capturing input + } + else { + // show cursor + if (m_cursorHidden) { +// CGDisplayShowCursor(m_displayID); + m_cursorHidden = false; + } + + // FIXME -- allow system to enter power saving mode + } + + // disable drag handling + m_dragNumButtonsDown = 0; + enableDragTimer(false); + + // uninstall clipboard timer + if (m_clipboardTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_clipboardTimer); + EVENTQUEUE->deleteTimer(m_clipboardTimer); + m_clipboardTimer = NULL; + } + + m_isOnScreen = m_isPrimary; +} + +void +COSXScreen::enter() +{ + if (m_isPrimary) { + // stop capturing input, watch jump zones + HideWindow( m_userInputWindow ); + ShowWindow( m_hiddenWindow ); + + SetMouseCoalescingEnabled(true, NULL); + + CGSetLocalEventsSuppressionInterval(0.0); + + // enable global hotkeys + setGlobalHotKeysEnabled(true); + } + else { + // show cursor + if (m_cursorHidden) { +// CGDisplayShowCursor(m_displayID); + m_cursorHidden = false; + } + + // reset buttons + for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + m_buttons[i] = false; + } + + // avoid suppression of local hardware events + // stkamp@users.sourceforge.net + CGSetLocalEventsFilterDuringSupressionState( + kCGEventFilterMaskPermitAllEvents, + kCGEventSupressionStateSupressionInterval); + CGSetLocalEventsFilterDuringSupressionState( + (kCGEventFilterMaskPermitLocalKeyboardEvents | + kCGEventFilterMaskPermitSystemDefinedEvents), + kCGEventSupressionStateRemoteMouseDrag); + } + + // now on screen + m_isOnScreen = true; +} + +bool +COSXScreen::leave() +{ + if (m_isPrimary) { + // warp to center + warpCursor(m_xCenter, m_yCenter); + + // capture events + HideWindow(m_hiddenWindow); + ShowWindow(m_userInputWindow); + RepositionWindow(m_userInputWindow, + m_userInputWindow, kWindowCenterOnMainScreen); + SetUserFocusWindow(m_userInputWindow); + + // The OS will coalesce some events if they are similar enough in a + // short period of time this is bad for us since we need every event + // to send it over to other machines. So disable it. + SetMouseCoalescingEnabled(false, NULL); + CGSetLocalEventsSuppressionInterval(0.0001); + + // disable global hotkeys + setGlobalHotKeysEnabled(false); + } + else { + // hide cursor + if (!m_cursorHidden) { +// CGDisplayHideCursor(m_displayID); + m_cursorHidden = true; + } + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + + // FIXME -- prepare to show cursor if it moves + + // take keyboard focus + // FIXME + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +COSXScreen::setClipboard(ClipboardID, const IClipboard* src) +{ + COSXClipboard dst; + if (src != NULL) { + // save clipboard data + if (!CClipboard::copy(&dst, src)) { + return false; + } + } + else { + // assert clipboard ownership + if (!dst.open(0)) { + return false; + } + dst.empty(); + dst.close(); + } + checkClipboards(); + return true; +} + +void +COSXScreen::checkClipboards() +{ + // check if clipboard ownership changed + if (!COSXClipboard::isOwnedBySynergy()) { + if (m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership")); + m_ownClipboard = false; + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); + sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); + } + } + else if (!m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: synergy owned")); + m_ownClipboard = true; + } +} + +void +COSXScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +COSXScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +COSXScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +COSXScreen::resetOptions() +{ + // no options +} + +void +COSXScreen::setOptions(const COptionsList&) +{ + // no options +} + +void +COSXScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +COSXScreen::isPrimary() const +{ + return m_isPrimary; +} + +void +COSXScreen::sendEvent(CEvent::Type type, void* data) const +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +COSXScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) const +{ + CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +void +COSXScreen::handleSystemEvent(const CEvent& event, void*) +{ + EventRef* carbonEvent = reinterpret_cast(event.getData()); + assert(carbonEvent != NULL); + + UInt32 eventClass = GetEventClass(*carbonEvent); + + switch (eventClass) { + case kEventClassMouse: + switch (GetEventKind(*carbonEvent)) { + case kEventMouseDown: + { + UInt16 myButton; + GetEventParameter(*carbonEvent, + kEventParamMouseButton, + typeMouseButton, + NULL, + sizeof(myButton), + NULL, + &myButton); + onMouseButton(true, myButton); + break; + } + + case kEventMouseUp: + { + UInt16 myButton; + GetEventParameter(*carbonEvent, + kEventParamMouseButton, + typeMouseButton, + NULL, + sizeof(myButton), + NULL, + &myButton); + onMouseButton(false, myButton); + break; + } + + case kEventMouseDragged: + case kEventMouseMoved: + { + HIPoint point; + GetEventParameter(*carbonEvent, + kEventParamMouseLocation, + typeHIPoint, + NULL, + sizeof(point), + NULL, + &point); + onMouseMove((SInt32)point.x, (SInt32)point.y); + break; + } + + case kEventMouseWheelMoved: + { + EventMouseWheelAxis axis; + SInt32 delta; + GetEventParameter(*carbonEvent, + kEventParamMouseWheelAxis, + typeMouseWheelAxis, + NULL, + sizeof(axis), + NULL, + &axis); + if (axis == kEventMouseWheelAxisX || + axis == kEventMouseWheelAxisY) { + GetEventParameter(*carbonEvent, + kEventParamMouseWheelDelta, + typeLongInteger, + NULL, + sizeof(delta), + NULL, + &delta); + if (axis == kEventMouseWheelAxisX) { + onMouseWheel(-mapScrollWheelToSynergy((SInt32)delta), 0); + } + else { + onMouseWheel(0, mapScrollWheelToSynergy((SInt32)delta)); + } + } + break; + } + + case kSynergyEventMouseScroll: + { + OSStatus r; + long xScroll; + long yScroll; + + // get scroll amount + r = GetEventParameter(*carbonEvent, + kSynergyMouseScrollAxisX, + typeLongInteger, + NULL, + sizeof(xScroll), + NULL, + &xScroll); + if (r != noErr) { + xScroll = 0; + } + r = GetEventParameter(*carbonEvent, + kSynergyMouseScrollAxisY, + typeLongInteger, + NULL, + sizeof(yScroll), + NULL, + &yScroll); + if (r != noErr) { + yScroll = 0; + } + + if (xScroll != 0 || yScroll != 0) { + onMouseWheel(-mapScrollWheelToSynergy(xScroll), + mapScrollWheelToSynergy(yScroll)); + } + } + } + break; + + case kEventClassKeyboard: + switch (GetEventKind(*carbonEvent)) { + case kEventRawKeyUp: + case kEventRawKeyDown: + case kEventRawKeyRepeat: + case kEventRawKeyModifiersChanged: + onKey(*carbonEvent); + break; + + case kEventHotKeyPressed: + case kEventHotKeyReleased: + onHotKey(*carbonEvent); + break; + } + + break; + + case kEventClassWindow: + SendEventToWindow(*carbonEvent, m_userInputWindow); + switch (GetEventKind(*carbonEvent)) { + case kEventWindowActivated: + LOG((CLOG_DEBUG1 "window activated")); + break; + + case kEventWindowDeactivated: + LOG((CLOG_DEBUG1 "window deactivated")); + break; + + case kEventWindowFocusAcquired: + LOG((CLOG_DEBUG1 "focus acquired")); + break; + + case kEventWindowFocusRelinquish: + LOG((CLOG_DEBUG1 "focus released")); + break; + } + break; + + default: + SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget()); + break; + } +} + +bool +COSXScreen::onMouseMove(SInt32 mx, SInt32 my) +{ + LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my)); + + SInt32 x = mx - m_xCursor; + SInt32 y = my - m_yCursor; + + if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) { + return true; + } + + // save position to compute delta of next motion + m_xCursor = mx; + m_yCursor = my; + + if (m_isOnScreen) { + // motion on primary screen + sendEvent(getMotionOnPrimaryEvent(), + CMotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + warpCursor(m_xCenter, m_yCenter); + + // examine the motion. if it's about the distance + // from the center of the screen to an edge then + // it's probably a bogus motion that we want to + // ignore (see warpCursorNoFlush() for a further + // description). + static SInt32 bogusZoneSize = 10; + if (-x + bogusZoneSize > m_xCenter - m_x || + x + bogusZoneSize > m_x + m_w - m_xCenter || + -y + bogusZoneSize > m_yCenter - m_y || + y + bogusZoneSize > m_y + m_h - m_yCenter) { + LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); + } + else { + // send motion + sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); + } + } + + return true; +} + +bool +COSXScreen::onMouseButton(bool pressed, UInt16 macButton) +{ + // Buttons 2 and 3 are inverted on the mac + ButtonID button = mapMacButtonToSynergy(macButton); + + if (pressed) { + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask)); + } + } + else { + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask)); + } + } + + // handle drags with any button other than button 1 or 2 + if (macButton > 2) { + if (pressed) { + // one more button + if (m_dragNumButtonsDown++ == 0) { + enableDragTimer(true); + } + } + else { + // one less button + if (--m_dragNumButtonsDown == 0) { + enableDragTimer(false); + } + } + } + + return true; +} + +bool +COSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + sendEvent(getWheelEvent(), CWheelInfo::alloc(xDelta, yDelta)); + return true; +} + +void +COSXScreen::handleClipboardCheck(const CEvent&, void*) +{ + checkClipboards(); +} + +pascal void +COSXScreen::displayManagerCallback(void* inUserData, SInt16 inMessage, void*) +{ + COSXScreen* screen = (COSXScreen*)inUserData; + + if (inMessage == kDMNotifyEvent) { + screen->onDisplayChange(); + } +} + +bool +COSXScreen::onDisplayChange() +{ + // screen resolution may have changed. save old shape. + SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h; + + // update shape + updateScreenShape(); + + // do nothing if resolution hasn't changed + if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) { + if (m_isPrimary) { + // warp mouse to center if off screen + if (!m_isOnScreen) { + warpCursor(m_xCenter, m_yCenter); + } + } + + // send new screen info + sendEvent(getShapeChangedEvent()); + } + + return true; +} + +bool +COSXScreen::onKey(EventRef event) +{ + UInt32 eventKind = GetEventKind(event); + + // get the key and active modifiers + UInt32 virtualKey, macMask; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, + NULL, sizeof(virtualKey), NULL, &virtualKey); + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, + NULL, sizeof(macMask), NULL, &macMask); + LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey)); + + // sadly, OS X doesn't report the virtualKey for modifier keys. + // virtualKey will be zero for modifier keys. since that's not good + // enough we'll have to figure out what the key was. + if (virtualKey == 0 && eventKind == kEventRawKeyModifiersChanged) { + // get old and new modifier state + KeyModifierMask oldMask = getActiveModifiers(); + KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask); + m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask); + + // if the current set of modifiers exactly matches a modifiers-only + // hot key then generate a hot key down event. + if (m_activeModifierHotKey == 0) { + if (m_modifierHotKeys.count(newMask) > 0) { + m_activeModifierHotKey = m_modifierHotKeys[newMask]; + m_activeModifierHotKeyMask = newMask; + EVENTQUEUE->addEvent(CEvent(getHotKeyDownEvent(), + getEventTarget(), + CHotKeyInfo::alloc(m_activeModifierHotKey))); + } + } + + // if a modifiers-only hot key is active and should no longer be + // then generate a hot key up event. + else if (m_activeModifierHotKey != 0) { + KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask); + if (mask != m_activeModifierHotKeyMask) { + EVENTQUEUE->addEvent(CEvent(getHotKeyUpEvent(), + getEventTarget(), + CHotKeyInfo::alloc(m_activeModifierHotKey))); + m_activeModifierHotKey = 0; + m_activeModifierHotKeyMask = 0; + } + } + + return true; + } + + // check for hot key. when we're on a secondary screen we disable + // all hotkeys so we can capture the OS defined hot keys as regular + // keystrokes but that means we don't get our own hot keys either. + // so we check for a key/modifier match in our hot key map. + if (!m_isOnScreen) { + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(CHotKeyItem(virtualKey, macMask & 0xff00u)); + if (i != m_hotKeyToIDMap.end()) { + UInt32 id = i->second; + + // determine event type + CEvent::Type type; + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventRawKeyDown) { + type = getHotKeyDownEvent(); + } + else if (eventKind == kEventRawKeyUp) { + type = getHotKeyUpEvent(); + } + else { + return false; + } + + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(id))); + + return true; + } + } + + // decode event type + bool down = (eventKind == kEventRawKeyDown); + bool up = (eventKind == kEventRawKeyUp); + bool isRepeat = (eventKind == kEventRawKeyRepeat); + + // map event to keys + KeyModifierMask mask; + COSXKeyState::CKeyIDs keys; + KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event); + if (button == 0) { + return false; + } + + // check for AltGr in mask. if set we send neither the AltGr nor + // the super modifiers to clients then remove AltGr before passing + // the modifiers to onKey. + KeyModifierMask sendMask = (mask & ~KeyModifierAltGr); + if ((mask & KeyModifierAltGr) != 0) { + sendMask &= ~KeyModifierSuper; + } + mask &= ~KeyModifierAltGr; + + // update button state + if (down) { + m_keyState->onKey(button, true, mask); + } + else if (up) { + if (!m_keyState->isKeyDown(button)) { + // up event for a dead key. throw it away. + return false; + } + m_keyState->onKey(button, false, mask); + } + + // send key events + for (COSXKeyState::CKeyIDs::const_iterator i = keys.begin(); + i != keys.end(); ++i) { + m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, + *i, sendMask, 1, button); + } + + return true; +} + +bool +COSXScreen::onHotKey(EventRef event) const +{ + // get the hotkey id + EventHotKeyID hkid; + GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, + NULL, sizeof(EventHotKeyID), NULL, &hkid); + UInt32 id = hkid.id; + + // determine event type + CEvent::Type type; + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventHotKeyPressed) { + type = getHotKeyDownEvent(); + } + else if (eventKind == kEventHotKeyReleased) { + type = getHotKeyUpEvent(); + } + else { + return false; + } + + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(id))); + + return true; +} + +ButtonID +COSXScreen::mapMacButtonToSynergy(UInt16 macButton) const +{ + switch (macButton) { + case 1: + return kButtonLeft; + + case 2: + return kButtonRight; + + case 3: + return kButtonMiddle; + } + + return static_cast(macButton); +} + +SInt32 +COSXScreen::mapScrollWheelToSynergy(SInt32 x) const +{ + // return accelerated scrolling but not exponentially scaled as it is + // on the mac. + double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor(); + return static_cast(120.0 * d); +} + +SInt32 +COSXScreen::mapScrollWheelFromSynergy(SInt32 x) const +{ + // use server's acceleration with a little boost since other platforms + // take one wheel step as a larger step than the mac does. + return static_cast(3.0 * x / 120.0); +} + +double +COSXScreen::getScrollSpeed() const +{ + double scaling = 0.0; + + CFPropertyListRef pref = ::CFPreferencesCopyValue( + CFSTR("com.apple.scrollwheel.scaling") , + kCFPreferencesAnyApplication, + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + if (pref != NULL) { + CFTypeID id = CFGetTypeID(pref); + if (id == CFNumberGetTypeID()) { + CFNumberRef value = static_cast(pref); + if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) { + if (scaling < 0.0) { + scaling = 0.0; + } + } + } + CFRelease(pref); + } + + return scaling; +} + +double +COSXScreen::getScrollSpeedFactor() const +{ + return pow(10.0, getScrollSpeed()); +} + +void +COSXScreen::enableDragTimer(bool enable) +{ + if (enable && m_dragTimer == NULL) { + m_dragTimer = EVENTQUEUE->newTimer(0.01, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_dragTimer, + new TMethodEventJob(this, + &COSXScreen::handleDrag)); + GetMouse(&m_dragLastPoint); + } + else if (!enable && m_dragTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_dragTimer); + EVENTQUEUE->deleteTimer(m_dragTimer); + m_dragTimer = NULL; + } +} + +void +COSXScreen::handleDrag(const CEvent&, void*) +{ + Point p; + GetMouse(&p); + if (p.h != m_dragLastPoint.h || p.v != m_dragLastPoint.v) { + m_dragLastPoint = p; + onMouseMove((SInt32)p.h, (SInt32)p.v); + } +} + +void +COSXScreen::updateButtons() +{ + UInt32 buttons = GetCurrentButtonState(); + for (size_t i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + m_buttons[i] = ((buttons & (1u << i)) != 0); + } +} + +IKeyState* +COSXScreen::getKeyState() const +{ + return m_keyState; +} + +void +COSXScreen::updateScreenShape() +{ + // get info for each display + CGDisplayCount displayCount = 0; + + if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) { + return; + } + + if (displayCount == 0) { + return; + } + + CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount]; + if (displays == NULL) { + return; + } + + if (CGGetActiveDisplayList(displayCount, + displays, &displayCount) != CGDisplayNoErr) { + delete[] displays; + return; + } + + // get smallest rect enclosing all display rects + CGRect totalBounds = CGRectZero; + for (CGDisplayCount i = 0; i < displayCount; ++i) { + CGRect bounds = CGDisplayBounds(displays[i]); + totalBounds = CGRectUnion(totalBounds, bounds); + } + + // get shape of default screen + m_x = (SInt32)totalBounds.origin.x; + m_y = (SInt32)totalBounds.origin.y; + m_w = (SInt32)totalBounds.size.width; + m_h = (SInt32)totalBounds.size.height; + + // get center of default screen + GDHandle mainScreen = GetMainDevice(); + if (mainScreen != NULL) { + const Rect& rect = (*mainScreen)->gdRect; + m_xCenter = (rect.left + rect.right) / 2; + m_yCenter = (rect.top + rect.bottom) / 2; + } + else { + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + } + + delete[] displays; + + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d on %u %s", m_x, m_y, m_w, m_h, displayCount, (displayCount == 1) ? "display" : "displays")); +} + +#pragma mark - + +// +// FAST USER SWITCH NOTIFICATION SUPPORT +// +// COSXScreen::userSwitchCallback(void*) +// +// gets called if a fast user switch occurs +// + +pascal OSStatus +COSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler, + EventRef theEvent, + void* inUserData) +{ + COSXScreen* screen = (COSXScreen*)inUserData; + UInt32 kind = GetEventKind(theEvent); + + if (kind == kEventSystemUserSessionDeactivated) { + LOG((CLOG_DEBUG "user session deactivated")); + EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(), + screen->getEventTarget())); + } + else if (kind == kEventSystemUserSessionActivated) { + LOG((CLOG_DEBUG "user session activated")); + EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(), + screen->getEventTarget())); + } + return (CallNextEventHandler(nextHandler, theEvent)); +} + +#pragma mark - + +// +// SLEEP/WAKEUP NOTIFICATION SUPPORT +// +// COSXScreen::watchSystemPowerThread(void*) +// +// main of thread monitoring system power (sleep/wakup) using a CFRunLoop +// + +void +COSXScreen::watchSystemPowerThread(void*) +{ + io_object_t notifier; + IONotificationPortRef notificationPortRef; + CFRunLoopSourceRef runloopSourceRef = 0; + + m_pmRunloop = CFRunLoopGetCurrent(); + + // install system power change callback + m_pmRootPort = IORegisterForSystemPower(this, ¬ificationPortRef, + powerChangeCallback, ¬ifier); + if (m_pmRootPort == 0) { + LOG((CLOG_WARN "IORegisterForSystemPower failed")); + } + else { + runloopSourceRef = + IONotificationPortGetRunLoopSource(notificationPortRef); + CFRunLoopAddSource(m_pmRunloop, runloopSourceRef, + kCFRunLoopCommonModes); + } + + // thread is ready + { + CLock lock(m_pmMutex); + *m_pmThreadReady = true; + m_pmThreadReady->signal(); + } + + // if we were unable to initialize then exit. we must do this after + // setting m_pmThreadReady to true otherwise the parent thread will + // block waiting for it. + if (m_pmRootPort == 0) { + return; + } + + // start the run loop + LOG((CLOG_DEBUG "started watchSystemPowerThread")); + CFRunLoopRun(); + + // cleanup + if (notificationPortRef) { + CFRunLoopRemoveSource(m_pmRunloop, + runloopSourceRef, kCFRunLoopDefaultMode); + CFRunLoopSourceInvalidate(runloopSourceRef); + CFRelease(runloopSourceRef); + } + + CLock lock(m_pmMutex); + IODeregisterForSystemPower(¬ifier); + m_pmRootPort = 0; + LOG((CLOG_DEBUG "stopped watchSystemPowerThread")); +} + +void +COSXScreen::powerChangeCallback(void* refcon, io_service_t service, + natural_t messageType, void* messageArg) +{ + ((COSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg); +} + +void +COSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg) +{ + // we've received a power change notification + switch (messageType) { + case kIOMessageSystemWillSleep: + // COSXScreen has to handle this in the main thread so we have to + // queue a confirm sleep event here. we actually don't allow the + // system to sleep until the event is handled. + EVENTQUEUE->addEvent(CEvent(COSXScreen::getConfirmSleepEvent(), + getEventTarget(), messageArg, + CEvent::kDontFreeData)); + return; + + case kIOMessageSystemHasPoweredOn: + LOG((CLOG_DEBUG "system wakeup")); + EVENTQUEUE->addEvent(CEvent(IScreen::getResumeEvent(), + getEventTarget())); + break; + + default: + break; + } + + CLock lock(m_pmMutex); + if (m_pmRootPort != 0) { + IOAllowPowerChange(m_pmRootPort, (long)messageArg); + } +} + +CEvent::Type +COSXScreen::getConfirmSleepEvent() +{ + return CEvent::registerTypeOnce(s_confirmSleepEvent, + "COSXScreen::confirmSleep"); +} + +void +COSXScreen::handleConfirmSleep(const CEvent& event, void*) +{ + long messageArg = (long)event.getData(); + if (messageArg != 0) { + CLock lock(m_pmMutex); + if (m_pmRootPort != 0) { + // deliver suspend event immediately. + EVENTQUEUE->addEvent(CEvent(IScreen::getSuspendEvent(), + getEventTarget(), NULL, + CEvent::kDeliverImmediately)); + + LOG((CLOG_DEBUG "system will sleep")); + IOAllowPowerChange(m_pmRootPort, messageArg); + } + } +} + +#pragma mark - + +// +// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3) +// +// CoreGraphics private API (OSX 10.3) +// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html +// +// We load the functions dynamically because they're not available in +// older SDKs. We don't use weak linking because we want users of +// older SDKs to build an app that works on newer systems and older +// SDKs will not provide the symbols. +// + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int CGSConnection; +typedef enum { + CGSGlobalHotKeyEnable = 0, + CGSGlobalHotKeyDisable = 1, +} CGSGlobalHotKeyOperatingMode; + +extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE; +extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE; +extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE; + +typedef CGSConnection (*_CGSDefaultConnection_t)(void); +typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode); +typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode); + +static _CGSDefaultConnection_t s__CGSDefaultConnection; +static CGSGetGlobalHotKeyOperatingMode_t s_CGSGetGlobalHotKeyOperatingMode; +static CGSSetGlobalHotKeyOperatingMode_t s_CGSSetGlobalHotKeyOperatingMode; + +#ifdef __cplusplus +} +#endif + +#define LOOKUP(name_) \ + s_ ## name_ = NULL; \ + if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) { \ + s_ ## name_ = (name_ ## _t)NSAddressOfSymbol( \ + NSLookupAndBindSymbolWithHint( \ + "_" #name_, "CoreGraphics")); \ + } + +bool +COSXScreen::isGlobalHotKeyOperatingModeAvailable() +{ + if (!s_testedForGHOM) { + s_testedForGHOM = true; + LOOKUP(_CGSDefaultConnection); + LOOKUP(CGSGetGlobalHotKeyOperatingMode); + LOOKUP(CGSSetGlobalHotKeyOperatingMode); + s_hasGHOM = (s__CGSDefaultConnection != NULL && + s_CGSGetGlobalHotKeyOperatingMode != NULL && + s_CGSSetGlobalHotKeyOperatingMode != NULL); + } + return s_hasGHOM; +} + +void +COSXScreen::setGlobalHotKeysEnabled(bool enabled) +{ + if (isGlobalHotKeyOperatingModeAvailable()) { + CGSConnection conn = s__CGSDefaultConnection(); + + CGSGlobalHotKeyOperatingMode mode; + s_CGSGetGlobalHotKeyOperatingMode(conn, &mode); + + if (enabled && mode == CGSGlobalHotKeyDisable) { + s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable); + } + else if (!enabled && mode == CGSGlobalHotKeyEnable) { + s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable); + } + } +} + +bool +COSXScreen::getGlobalHotKeysEnabled() +{ + CGSGlobalHotKeyOperatingMode mode; + if (isGlobalHotKeyOperatingModeAvailable()) { + CGSConnection conn = s__CGSDefaultConnection(); + s_CGSGetGlobalHotKeyOperatingMode(conn, &mode); + } + else { + mode = CGSGlobalHotKeyEnable; + } + return (mode == CGSGlobalHotKeyEnable); +} + + +// +// COSXScreen::CHotKeyItem +// + +COSXScreen::CHotKeyItem::CHotKeyItem(UInt32 keycode, UInt32 mask) : + m_ref(NULL), + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +COSXScreen::CHotKeyItem::CHotKeyItem(EventHotKeyRef ref, + UInt32 keycode, UInt32 mask) : + m_ref(ref), + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +EventHotKeyRef +COSXScreen::CHotKeyItem::getRef() const +{ + return m_ref; +} + +bool +COSXScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} diff --git a/lib/platform/COSXScreen.h b/lib/platform/COSXScreen.h new file mode 100644 index 00000000..f280d802 --- /dev/null +++ b/lib/platform/COSXScreen.h @@ -0,0 +1,252 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSCREEN_H +#define COSXSCREEN_H + +#include "CPlatformScreen.h" +#include "stdmap.h" +#include "stdvector.h" +#include + +#include +#include +#include +#include +#include + +template +class CCondVar; +class CEventQueueTimer; +class CMutex; +class CThread; +class COSXKeyState; +class COSXScreenSaver; + +//! Implementation of IPlatformScreen for OS X +class COSXScreen : public CPlatformScreen { +public: + COSXScreen(bool isPrimary); + virtual ~COSXScreen(); + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + void updateScreenShape(); + void postMouseEvent(CGPoint&) const; + + // convenience function to send events + void sendEvent(CEvent::Type type, void* = NULL) const; + void sendClipboardEvent(CEvent::Type type, ClipboardID id) const; + + // message handlers + bool onMouseMove(SInt32 x, SInt32 y); + // mouse button handler. pressed is true if this is a mousedown + // event, false if it is a mouseup event. macButton is the index + // of the button pressed using the mac button mapping. + bool onMouseButton(bool pressed, UInt16 macButton); + bool onMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + bool onDisplayChange(); + + bool onKey(EventRef event); + bool onHotKey(EventRef event) const; + + // map mac mouse button to synergy buttons + ButtonID mapMacButtonToSynergy(UInt16) const; + + // map mac scroll wheel value to a synergy scroll wheel value + SInt32 mapScrollWheelToSynergy(SInt32) const; + + // map synergy scroll wheel value to a mac scroll wheel value + SInt32 mapScrollWheelFromSynergy(SInt32) const; + + // get the current scroll wheel speed + double getScrollSpeed() const; + + // get the current scroll wheel speed + double getScrollSpeedFactor() const; + + // enable/disable drag handling for buttons 3 and up + void enableDragTimer(bool enable); + + // drag timer handler + void handleDrag(const CEvent&, void*); + + // clipboard check timer handler + void handleClipboardCheck(const CEvent&, void*); + + // Resolution switch callback + static pascal void displayManagerCallback(void* inUserData, + SInt16 inMessage, void* inNotifyData); + + // fast user switch callback + static pascal OSStatus + userSwitchCallback(EventHandlerCallRef nextHandler, + EventRef theEvent, void* inUserData); + + // sleep / wakeup support + void watchSystemPowerThread(void*); + static void testCanceled(CFRunLoopTimerRef timer, void*info); + static void powerChangeCallback(void* refcon, io_service_t service, + natural_t messageType, void* messageArgument); + void handlePowerChangeRequest(natural_t messageType, + void* messageArgument); + + static CEvent::Type getConfirmSleepEvent(); + void handleConfirmSleep(const CEvent& event, void*); + + // global hotkey operating mode + static bool isGlobalHotKeyOperatingModeAvailable(); + static void setGlobalHotKeysEnabled(bool enabled); + static bool getGlobalHotKeysEnabled(); + +private: + struct CHotKeyItem { + public: + CHotKeyItem(UInt32, UInt32); + CHotKeyItem(EventHotKeyRef, UInt32, UInt32); + + EventHotKeyRef getRef() const; + + bool operator<(const CHotKeyItem&) const; + + private: + EventHotKeyRef m_ref; + UInt32 m_keycode; + UInt32 m_mask; + }; + typedef std::map HotKeyMap; + typedef std::vector HotKeyIDList; + typedef std::map ModifierHotKeyMap; + typedef std::map HotKeyToIDMap; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // the display + CGDirectDisplayID m_displayID; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // mouse state + mutable SInt32 m_xCursor, m_yCursor; + mutable bool m_cursorPosValid; + mutable boolean_t m_buttons[5]; + bool m_cursorHidden; + SInt32 m_dragNumButtonsDown; + Point m_dragLastPoint; + CEventQueueTimer* m_dragTimer; + + // keyboard stuff + COSXKeyState* m_keyState; + + // clipboards + UInt32 m_sequenceNumber; + + // screen saver stuff + COSXScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // clipboard stuff + bool m_ownClipboard; + CEventQueueTimer* m_clipboardTimer; + + // window object that gets user input events when the server + // has focus. + WindowRef m_hiddenWindow; + // window object that gets user input events when the server + // does not have focus. + WindowRef m_userInputWindow; + + // display manager stuff (to get screen resolution switches). + DMExtendedNotificationUPP m_displayManagerNotificationUPP; + ProcessSerialNumber m_PSN; + + // fast user switching + EventHandlerRef m_switchEventHandlerRef; + + // sleep / wakeup + CMutex* m_pmMutex; + CThread* m_pmWatchThread; + CCondVar* m_pmThreadReady; + CFRunLoopRef m_pmRunloop; + io_connect_t m_pmRootPort; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + ModifierHotKeyMap m_modifierHotKeys; + UInt32 m_activeModifierHotKey; + KeyModifierMask m_activeModifierHotKeyMask; + HotKeyToIDMap m_hotKeyToIDMap; + + // global hotkey operating mode + static bool s_testedForGHOM; + static bool s_hasGHOM; + + // events + static CEvent::Type s_confirmSleepEvent; +}; + +#endif diff --git a/lib/platform/COSXScreenSaver.cpp b/lib/platform/COSXScreenSaver.cpp new file mode 100644 index 00000000..9cc6a678 --- /dev/null +++ b/lib/platform/COSXScreenSaver.cpp @@ -0,0 +1,175 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#import "COSXScreenSaver.h" +#import "COSXScreenSaverUtil.h" +#import "CLog.h" +#import "IEventQueue.h" +#import "IPrimaryScreen.h" +#import + +// +// COSXScreenSaver +// + +COSXScreenSaver::COSXScreenSaver(void* eventTarget) : + m_eventTarget(eventTarget), + m_enabled(true) +{ + m_autoReleasePool = screenSaverUtilCreatePool(); + m_screenSaverController = screenSaverUtilCreateController(); + + // install launch/termination event handlers + EventTypeSpec launchEventTypes[2]; + launchEventTypes[0].eventClass = kEventClassApplication; + launchEventTypes[0].eventKind = kEventAppLaunched; + launchEventTypes[1].eventClass = kEventClassApplication; + launchEventTypes[1].eventKind = kEventAppTerminated; + + EventHandlerUPP launchTerminationEventHandler = + NewEventHandlerUPP(launchTerminationCallback); + InstallApplicationEventHandler(launchTerminationEventHandler, 2, + launchEventTypes, this, + &m_launchTerminationEventHandlerRef); + DisposeEventHandlerUPP(launchTerminationEventHandler); + + m_screenSaverPSN.highLongOfPSN = 0; + m_screenSaverPSN.lowLongOfPSN = 0; + + // test if screensaver is running and find process number + if (isActive()) { + ProcessInfoRec procInfo; + Str31 procName; // pascal string. first byte holds length. + memset(&procInfo, 0, sizeof(procInfo)); + procInfo.processName = procName; + procInfo.processInfoLength = sizeof(ProcessInfoRec); + + ProcessSerialNumber psn; + OSErr err = GetNextProcess(&psn); + while (err == 0) { + memset(procName, 0, sizeof(procName)); + err = GetProcessInformation(&psn, &procInfo); + if (err != 0) { + break; + } + if (strcmp("ScreenSaverEngine", (const char*)&procName[1]) == 0) { + m_screenSaverPSN = psn; + break; + } + err = GetNextProcess(&psn); + } + } +} + +COSXScreenSaver::~COSXScreenSaver() +{ + RemoveEventHandler(m_launchTerminationEventHandlerRef); +// screenSaverUtilReleaseController(m_screenSaverController); + screenSaverUtilReleasePool(m_autoReleasePool); +} + +void +COSXScreenSaver::enable() +{ + m_enabled = true; + screenSaverUtilEnable(m_screenSaverController); +} + +void +COSXScreenSaver::disable() +{ + m_enabled = false; + screenSaverUtilDisable(m_screenSaverController); +} + +void +COSXScreenSaver::activate() +{ + screenSaverUtilActivate(m_screenSaverController); +} + +void +COSXScreenSaver::deactivate() +{ + screenSaverUtilDeactivate(m_screenSaverController, m_enabled); +} + +bool +COSXScreenSaver::isActive() const +{ + return (screenSaverUtilIsActive(m_screenSaverController) != 0); +} + +void +COSXScreenSaver::processLaunched(ProcessSerialNumber psn) +{ + CFStringRef processName; + OSStatus err = CopyProcessName(&psn, &processName); + + if (err == 0 && CFEqual(CFSTR("ScreenSaverEngine"), processName)) { + m_screenSaverPSN = psn; + LOG((CLOG_DEBUG1 "ScreenSaverEngine launched. Enabled=%d", m_enabled)); + if (m_enabled) { + EVENTQUEUE->addEvent( + CEvent(IPrimaryScreen::getScreensaverActivatedEvent(), + m_eventTarget)); + } + } +} + +void +COSXScreenSaver::processTerminated(ProcessSerialNumber psn) +{ + if (m_screenSaverPSN.highLongOfPSN == psn.highLongOfPSN && + m_screenSaverPSN.lowLongOfPSN == psn.lowLongOfPSN) { + LOG((CLOG_DEBUG1 "ScreenSaverEngine terminated. Enabled=%d", m_enabled)); + if (m_enabled) { + EVENTQUEUE->addEvent( + CEvent(IPrimaryScreen::getScreensaverDeactivatedEvent(), + m_eventTarget)); + } + + m_screenSaverPSN.highLongOfPSN = 0; + m_screenSaverPSN.lowLongOfPSN = 0; + } +} + +pascal OSStatus +COSXScreenSaver::launchTerminationCallback( + EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData) +{ + OSStatus result; + ProcessSerialNumber psn; + EventParamType actualType; + UInt32 actualSize; + + result = GetEventParameter(theEvent, kEventParamProcessID, + typeProcessSerialNumber, &actualType, + sizeof(psn), &actualSize, &psn); + + if ((result == noErr) && + (actualSize > 0) && + (actualType == typeProcessSerialNumber)) { + COSXScreenSaver* screenSaver = (COSXScreenSaver*)userData; + UInt32 eventKind = GetEventKind(theEvent); + if (eventKind == kEventAppLaunched) { + screenSaver->processLaunched(psn); + } + else if (eventKind == kEventAppTerminated) { + screenSaver->processTerminated(psn); + } + } + return (CallNextEventHandler(nextHandler, theEvent)); +} diff --git a/lib/platform/COSXScreenSaver.h b/lib/platform/COSXScreenSaver.h new file mode 100644 index 00000000..40c4e742 --- /dev/null +++ b/lib/platform/COSXScreenSaver.h @@ -0,0 +1,54 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSCREENSAVER_H +#define COSXSCREENSAVER_H + +#include "IScreenSaver.h" +#include + +//! OSX screen saver implementation +class COSXScreenSaver : public IScreenSaver { +public: + COSXScreenSaver(void* eventTarget); + virtual ~COSXScreenSaver(); + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + void processLaunched(ProcessSerialNumber psn); + void processTerminated(ProcessSerialNumber psn); + + static pascal OSStatus + launchTerminationCallback( + EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData); + +private: + // the target for the events we generate + void* m_eventTarget; + + bool m_enabled; + void* m_screenSaverController; + void* m_autoReleasePool; + EventHandlerRef m_launchTerminationEventHandlerRef; + ProcessSerialNumber m_screenSaverPSN; +}; + +#endif diff --git a/lib/platform/COSXScreenSaverUtil.h b/lib/platform/COSXScreenSaverUtil.h new file mode 100644 index 00000000..d4124ab3 --- /dev/null +++ b/lib/platform/COSXScreenSaverUtil.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef COSXSCREENSAVERUTIL_H +#define COSXSCREENSAVERUTIL_H + +#include "common.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +void* screenSaverUtilCreatePool(); +void screenSaverUtilReleasePool(void*); + +void* screenSaverUtilCreateController(); +void screenSaverUtilReleaseController(void*); +void screenSaverUtilEnable(void*); +void screenSaverUtilDisable(void*); +void screenSaverUtilActivate(void*); +void screenSaverUtilDeactivate(void*, int isEnabled); +int screenSaverUtilIsActive(void*); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/lib/platform/COSXScreenSaverUtil.m b/lib/platform/COSXScreenSaverUtil.m new file mode 100644 index 00000000..1376d1f2 --- /dev/null +++ b/lib/platform/COSXScreenSaverUtil.m @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#import "COSXScreenSaverUtil.h" +#import "OSXScreenSaverControl.h" +#import + +// +// screenSaverUtil functions +// +// Note: these helper functions exist only so we can avoid using ObjC++. +// autoconf/automake don't know about ObjC++ and I don't know how to +// teach them about it. +// + +void* +screenSaverUtilCreatePool() +{ + return [[NSAutoreleasePool alloc] init]; +} + +void +screenSaverUtilReleasePool(void* pool) +{ + [(NSAutoreleasePool*)pool release]; +} + +void* +screenSaverUtilCreateController() +{ + return [[ScreenSaverController controller] retain]; +} + +void +screenSaverUtilReleaseController(void* controller) +{ + [(ScreenSaverController*)controller release]; +} + +void +screenSaverUtilEnable(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:YES]; +} + +void +screenSaverUtilDisable(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:NO]; +} + +void +screenSaverUtilActivate(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:YES]; + [(ScreenSaverController*)controller screenSaverStartNow]; +} + +void +screenSaverUtilDeactivate(void* controller, int isEnabled) +{ + [(ScreenSaverController*)controller screenSaverStopNow]; + [(ScreenSaverController*)controller setScreenSaverCanRun:isEnabled]; +} + +int +screenSaverUtilIsActive(void* controller) +{ + return [(ScreenSaverController*)controller screenSaverIsRunning]; +} diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp new file mode 100644 index 00000000..f4deb476 --- /dev/null +++ b/lib/platform/CSynergyHook.cpp @@ -0,0 +1,1110 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CSynergyHook.h" +#include "ProtocolTypes.h" +#include +#include + +#if _MSC_VER >= 1400 +// VS2005 hack - we don't use assert here because we don't want to link with the CRT. +#undef assert +#if _DEBUG +#define assert(_X_) if (!(_X_)) __debugbreak() +#else +#define assert(_X_) __noop() +#endif +// VS2005 is a bit more smart than VC6 and optimize simple copy loop to +// intrinsic memcpy. +#pragma function(memcpy) +#endif + +// +// debugging compile flag. when not zero the server doesn't grab +// the keyboard when the mouse leaves the server screen. this +// makes it possible to use the debugger (via the keyboard) when +// all user input would normally be caught by the hook procedures. +// +#define NO_GRAB_KEYBOARD 0 + +// +// debugging compile flag. when not zero the server will not +// install low level hooks. +// +#define NO_LOWLEVEL_HOOKS 0 + +// +// extra mouse wheel stuff +// + +enum EWheelSupport { + kWheelNone, + kWheelOld, + kWheelWin2000, + kWheelModern +}; + +// declare extended mouse hook struct. useable on win2k +typedef struct tagMOUSEHOOKSTRUCTWin2000 { + MOUSEHOOKSTRUCT mhs; + DWORD mouseData; +} MOUSEHOOKSTRUCTWin2000; + +#if !defined(SM_MOUSEWHEELPRESENT) +#define SM_MOUSEWHEELPRESENT 75 +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif + + +// +// globals +// + +#if defined(_MSC_VER) +#pragma comment(linker, "-section:shared,rws") +#pragma data_seg("shared") +#endif +// all data in this shared section *must* be initialized + +static HINSTANCE g_hinstance = NULL; +static DWORD g_processID = 0; +static EWheelSupport g_wheelSupport = kWheelNone; +static UINT g_wmMouseWheel = 0; +static DWORD g_threadID = 0; +static HHOOK g_keyboard = NULL; +static HHOOK g_mouse = NULL; +static HHOOK g_getMessage = NULL; +static HHOOK g_keyboardLL = NULL; +static HHOOK g_mouseLL = NULL; +static bool g_screenSaver = false; +static EHookMode g_mode = kHOOK_DISABLE; +static UInt32 g_zoneSides = 0; +static SInt32 g_zoneSize = 0; +static SInt32 g_xScreen = 0; +static SInt32 g_yScreen = 0; +static SInt32 g_wScreen = 0; +static SInt32 g_hScreen = 0; +static WPARAM g_deadVirtKey = 0; +static LPARAM g_deadLParam = 0; +static BYTE g_deadKeyState[256] = { 0 }; +static DWORD g_hookThread = 0; +static DWORD g_attachedThread = 0; +static bool g_fakeInput = false; + +#if defined(_MSC_VER) +#pragma data_seg() +#endif + +// keep linker quiet about floating point stuff. we don't use any +// floating point operations but our includes may define some +// (unused) floating point values. +#ifndef _DEBUG +extern "C" { +int _fltused=0; +} +#endif + + +// +// internal functions +// + +static +void +detachThread() +{ + if (g_attachedThread != 0 && g_hookThread != g_attachedThread) { + AttachThreadInput(g_hookThread, g_attachedThread, FALSE); + g_attachedThread = 0; + } +} + +static +bool +attachThreadToForeground() +{ + // only attach threads if using low level hooks. a low level hook + // runs in the thread that installed the hook but we have to make + // changes that require being attached to the target thread (which + // should be the foreground window). a regular hook runs in the + // thread that just removed the event from its queue so we're + // already in the right thread. + if (g_hookThread != 0) { + HWND window = GetForegroundWindow(); + DWORD threadID = GetWindowThreadProcessId(window, NULL); + // skip if no change + if (g_attachedThread != threadID) { + // detach from previous thread + detachThread(); + + // attach to new thread + if (threadID != 0 && threadID != g_hookThread) { + AttachThreadInput(g_hookThread, threadID, TRUE); + g_attachedThread = threadID; + } + return true; + } + } + return false; +} + +#if !NO_GRAB_KEYBOARD +static +WPARAM +makeKeyMsg(UINT virtKey, char c, bool noAltGr) +{ + return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0); +} + +static +void +keyboardGetState(BYTE keys[256]) +{ + // we have to use GetAsyncKeyState() rather than GetKeyState() because + // we don't pass through most keys so the event synchronous state + // doesn't get updated. we do that because certain modifier keys have + // side effects, like alt and the windows key. + SHORT key; + for (int i = 0; i < 256; ++i) { + key = GetAsyncKeyState(i); + keys[i] = (BYTE)((key < 0) ? 0x80u : 0); + } + key = GetKeyState(VK_CAPITAL); + keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1)); +} + +static +bool +doKeyboardHookHandler(WPARAM wParam, LPARAM lParam) +{ + // check for special events indicating if we should start or stop + // passing events through and not report them to the server. this + // is used to allow the server to synthesize events locally but + // not pick them up as user events. + if (wParam == SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY && + ((lParam >> 16) & 0xffu) == SYNERGY_HOOK_FAKE_INPUT_SCANCODE) { + // update flag + g_fakeInput = ((lParam & 0x80000000u) == 0); + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + 0xff000000u | wParam, lParam); + + // discard event + return true; + } + + // if we're expecting fake input then just pass the event through + // and do not forward to the server + if (g_fakeInput) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + 0xfe000000u | wParam, lParam); + return false; + } + + // VK_RSHIFT may be sent with an extended scan code but right shift + // is not an extended key so we reset that bit. + if (wParam == VK_RSHIFT) { + lParam &= ~0x01000000u; + } + + // tell server about event + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam, lParam); + + // ignore dead key release + if (g_deadVirtKey == wParam && + (lParam & 0x80000000u) != 0) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x04000000, lParam); + return false; + } + + // we need the keyboard state for ToAscii() + BYTE keys[256]; + keyboardGetState(keys); + + // ToAscii() maps ctrl+letter to the corresponding control code + // and ctrl+backspace to delete. we don't want those translations + // so clear the control modifier state. however, if we want to + // simulate AltGr (which is ctrl+alt) then we must not clear it. + UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL]; + UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU]; + if ((control & 0x80) == 0 || (menu & 0x80) == 0) { + keys[VK_LCONTROL] = 0; + keys[VK_RCONTROL] = 0; + keys[VK_CONTROL] = 0; + } + else { + keys[VK_LCONTROL] = 0x80; + keys[VK_RCONTROL] = 0x80; + keys[VK_CONTROL] = 0x80; + keys[VK_LMENU] = 0x80; + keys[VK_RMENU] = 0x80; + keys[VK_MENU] = 0x80; + } + + // ToAscii() needs to know if a menu is active for some reason. + // we don't know and there doesn't appear to be any way to find + // out. so we'll just assume a menu is active if the menu key + // is down. + // FIXME -- figure out some way to check if a menu is active + UINT flags = 0; + if ((menu & 0x80) != 0) + flags |= 1; + + // if we're on the server screen then just pass numpad keys with alt + // key down as-is. we won't pick up the resulting character but the + // local app will. if on a client screen then grab keys as usual; + // if the client is a windows system it'll synthesize the expected + // character. if not then it'll probably just do nothing. + if (g_mode != kHOOK_RELAY_EVENTS) { + // we don't use virtual keys because we don't know what the + // state of the numlock key is. we'll hard code the scan codes + // instead. hopefully this works across all keyboards. + UINT sc = (lParam & 0x01ff0000u) >> 16; + if (menu && + (sc >= 0x47u && sc <= 0x52u && sc != 0x4au && sc != 0x4eu)) { + return false; + } + } + + // map the key event to a character. we have to put the dead + // key back first and this has the side effect of removing it. + if (g_deadVirtKey != 0) { + WORD c = 0; + ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + WORD c = 0; + UINT scanCode = ((lParam & 0x10ff0000u) >> 16); + int n = ToAscii(wParam, scanCode, keys, &c, flags); + + // if mapping failed and ctrl and alt are pressed then try again + // with both not pressed. this handles the case where ctrl and + // alt are being used as individual modifiers rather than AltGr. + // we note that's the case in the message sent back to synergy + // because there's no simple way to deduce it after the fact. + // we have to put the dead key back first, if there was one. + bool noAltGr = false; + if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) { + noAltGr = true; + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x05000000, lParam); + if (g_deadVirtKey != 0) { + ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + BYTE keys2[256]; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + keys2[i] = keys[i]; + } + keys2[VK_LCONTROL] = 0; + keys2[VK_RCONTROL] = 0; + keys2[VK_CONTROL] = 0; + keys2[VK_LMENU] = 0; + keys2[VK_RMENU] = 0; + keys2[VK_MENU] = 0; + n = ToAscii(wParam, scanCode, keys2, &c, flags); + } + + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | ((c & 0xff) << 8) | + ((n & 0xff) << 16) | 0x06000000, + lParam); + WPARAM charAndVirtKey = 0; + bool clearDeadKey = false; + switch (n) { + default: + // key is a dead key + g_deadVirtKey = wParam; + g_deadLParam = lParam; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + g_deadKeyState[i] = keys[i]; + } + break; + + case 0: + // key doesn't map to a character. this can happen if + // non-character keys are pressed after a dead key. + charAndVirtKey = makeKeyMsg(wParam, (char)0, noAltGr); + break; + + case 1: + // key maps to a character composed with dead key + charAndVirtKey = makeKeyMsg(wParam, (char)LOBYTE(c), noAltGr); + clearDeadKey = true; + break; + + case 2: { + // previous dead key not composed. send a fake key press + // and release for the dead key to our window. + WPARAM deadCharAndVirtKey = + makeKeyMsg(g_deadVirtKey, (char)LOBYTE(c), noAltGr); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + deadCharAndVirtKey, g_deadLParam & 0x7fffffffu); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + deadCharAndVirtKey, g_deadLParam | 0x80000000u); + + // use uncomposed character + charAndVirtKey = makeKeyMsg(wParam, (char)HIBYTE(c), noAltGr); + clearDeadKey = true; + break; + } + } + + // put back the dead key, if any, for the application to use + if (g_deadVirtKey != 0) { + ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + + // clear out old dead key state + if (clearDeadKey) { + g_deadVirtKey = 0; + g_deadLParam = 0; + } + + // forward message to our window. do this whether or not we're + // forwarding events to clients because this'll keep our thread's + // key state table up to date. that's important for querying + // the scroll lock toggle state. + // XXX -- with hot keys for actions we may only need to do this when + // forwarding. + if (charAndVirtKey != 0) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + charAndVirtKey | 0x07000000, lParam); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam); + } + + if (g_mode == kHOOK_RELAY_EVENTS) { + // let certain keys pass through + switch (wParam) { + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // pass event on. we want to let these through to + // the window proc because otherwise the keyboard + // lights may not stay synchronized. + break; + + case VK_HANGUL: + // pass these modifiers if using a low level hook, discard + // them if not. + if (g_hookThread == 0) { + return true; + } + break; + + default: + // discard + return true; + } + } + + return false; +} + +static +bool +keyboardHookHandler(WPARAM wParam, LPARAM lParam) +{ + attachThreadToForeground(); + return doKeyboardHookHandler(wParam, lParam); +} +#endif + +static +bool +doMouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) +{ + switch (wParam) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + // always relay the event. eat it if relaying. + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, data); + return (g_mode == kHOOK_RELAY_EVENTS); + + case WM_MOUSEWHEEL: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, data, 0); + } + return (g_mode == kHOOK_RELAY_EVENTS); + + case WM_NCMOUSEMOVE: + case WM_MOUSEMOVE: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay and eat event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + return true; + } + else if (g_mode == kHOOK_WATCH_JUMP_ZONE) { + // low level hooks can report bogus mouse positions that are + // outside of the screen. jeez. naturally we end up getting + // fake motion in the other direction to get the position back + // on the screen, which plays havoc with switch on double tap. + // CServer deals with that. we'll clamp positions onto the + // screen. also, if we discard events for positions outside + // of the screen then the mouse appears to get a bit jerky + // near the edge. we can either accept that or pass the bogus + // events. we'll try passing the events. + bool bogus = false; + if (x < g_xScreen) { + x = g_xScreen; + bogus = true; + } + else if (x >= g_xScreen + g_wScreen) { + x = g_xScreen + g_wScreen - 1; + bogus = true; + } + if (y < g_yScreen) { + y = g_yScreen; + bogus = true; + } + else if (y >= g_yScreen + g_hScreen) { + y = g_yScreen + g_hScreen - 1; + bogus = true; + } + + // check for mouse inside jump zone + bool inside = false; + if (!inside && (g_zoneSides & kLeftMask) != 0) { + inside = (x < g_xScreen + g_zoneSize); + } + if (!inside && (g_zoneSides & kRightMask) != 0) { + inside = (x >= g_xScreen + g_wScreen - g_zoneSize); + } + if (!inside && (g_zoneSides & kTopMask) != 0) { + inside = (y < g_yScreen + g_zoneSize); + } + if (!inside && (g_zoneSides & kBottomMask) != 0) { + inside = (y >= g_yScreen + g_hScreen - g_zoneSize); + } + + // relay the event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + + // if inside and not bogus then eat the event + return inside && !bogus; + } + } + + // pass the event + return false; +} + +static +bool +mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) +{ +// attachThreadToForeground(); + return doMouseHookHandler(wParam, x, y, data); +} + +#if !NO_GRAB_KEYBOARD +static +LRESULT CALLBACK +keyboardHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; + } + } + + return CallNextHookEx(g_keyboard, code, wParam, lParam); +} +#endif + +static +LRESULT CALLBACK +mouseHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode message + const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; + SInt32 x = (SInt32)info->pt.x; + SInt32 y = (SInt32)info->pt.y; + SInt32 w = 0; + if (wParam == WM_MOUSEWHEEL) { + // win2k and other systems supporting WM_MOUSEWHEEL in + // the mouse hook are gratuitously different (and poorly + // documented). if a low-level mouse hook is in place + // it should capture these events so we'll never see + // them. + switch (g_wheelSupport) { + case kWheelModern: + w = static_cast(LOWORD(info->dwExtraInfo)); + break; + + case kWheelWin2000: { + const MOUSEHOOKSTRUCTWin2000* info2k = + (const MOUSEHOOKSTRUCTWin2000*)lParam; + w = static_cast(HIWORD(info2k->mouseData)); + break; + } + + default: + break; + } + } + + // handle the message. note that we don't handle X buttons + // here. that's okay because they're only supported on + // win2k and winxp and up and on those platforms we'll get + // get the mouse events through the low level hook. + if (mouseHookHandler(wParam, x, y, w)) { + return 1; + } + } + + return CallNextHookEx(g_mouse, code, wParam, lParam); +} + +static +LRESULT CALLBACK +getMessageHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + if (g_screenSaver) { + MSG* msg = reinterpret_cast(lParam); + if (msg->message == WM_SYSCOMMAND && + msg->wParam == SC_SCREENSAVE) { + // broadcast screen saver started message + PostThreadMessage(g_threadID, + SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); + } + } + if (g_mode == kHOOK_RELAY_EVENTS) { + MSG* msg = reinterpret_cast(lParam); + if (g_wheelSupport == kWheelOld && msg->message == g_wmMouseWheel) { + // post message to our window + PostThreadMessage(g_threadID, + SYNERGY_MSG_MOUSE_WHEEL, + static_cast(msg->wParam & 0xffffu), 0); + + // zero out the delta in the message so it's (hopefully) + // ignored + msg->wParam = 0; + } + } + } + + return CallNextHookEx(g_getMessage, code, wParam, lParam); +} + +#if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS + +// +// low-level keyboard hook -- this allows us to capture and handle +// alt+tab, alt+esc, ctrl+esc, and windows key hot keys. on the down +// side, key repeats are not reported to us. +// + +#if !NO_GRAB_KEYBOARD +static +LRESULT CALLBACK +keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode the message + KBDLLHOOKSTRUCT* info = reinterpret_cast(lParam); + WPARAM wParam = info->vkCode; + LPARAM lParam = 1; // repeat code + lParam |= (info->scanCode << 16); // scan code + if (info->flags & LLKHF_EXTENDED) { + lParam |= (1lu << 24); // extended key + } + if (info->flags & LLKHF_ALTDOWN) { + lParam |= (1lu << 29); // context code + } + if (info->flags & LLKHF_UP) { + lParam |= (1lu << 31); // transition + } + // FIXME -- bit 30 should be set if key was already down but + // we don't know that info. as a result we'll never generate + // key repeat events. + + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; + } + } + + return CallNextHookEx(g_keyboardLL, code, wParam, lParam); +} +#endif + +// +// low-level mouse hook -- this allows us to capture and handle mouse +// events very early. the earlier the better. +// + +static +LRESULT CALLBACK +mouseLLHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode the message + MSLLHOOKSTRUCT* info = reinterpret_cast(lParam); + SInt32 x = static_cast(info->pt.x); + SInt32 y = static_cast(info->pt.y); + SInt32 w = static_cast(HIWORD(info->mouseData)); + + // handle the message + if (mouseHookHandler(wParam, x, y, w)) { + return 1; + } + } + + return CallNextHookEx(g_mouseLL, code, wParam, lParam); +} + +#endif + +static +EWheelSupport +getWheelSupport() +{ + // get operating system + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (!GetVersionEx(&info)) { + return kWheelNone; + } + + // see if modern wheel is present + if (GetSystemMetrics(SM_MOUSEWHEELPRESENT)) { + // note if running on win2k + if (info.dwPlatformId == VER_PLATFORM_WIN32_NT && + info.dwMajorVersion == 5 && + info.dwMinorVersion == 0) { + return kWheelWin2000; + } + return kWheelModern; + } + + // not modern. see if we've got old-style support. +#if defined(MSH_WHEELSUPPORT) + UINT wheelSupportMsg = RegisterWindowMessage(MSH_WHEELSUPPORT); + HWND wheelSupportWindow = FindWindow(MSH_WHEELMODULE_CLASS, + MSH_WHEELMODULE_TITLE); + if (wheelSupportWindow != NULL && wheelSupportMsg != 0) { + if (SendMessage(wheelSupportWindow, wheelSupportMsg, 0, 0) != 0) { + g_wmMouseWheel = RegisterWindowMessage(MSH_MOUSEWHEEL); + if (g_wmMouseWheel != 0) { + return kWheelOld; + } + } + } +#endif + + // assume modern. we don't do anything special in this case + // except respond to WM_MOUSEWHEEL messages. GetSystemMetrics() + // can apparently return FALSE even if a mouse wheel is present + // though i'm not sure exactly when it does that (WinME returns + // FALSE for my logitech USB trackball). + return kWheelModern; +} + + +// +// external functions +// + +BOOL WINAPI +DllMain(HINSTANCE instance, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) { + DisableThreadLibraryCalls(instance); + if (g_processID == 0) { + g_hinstance = instance; + g_processID = GetCurrentProcessId(); + } + } + else if (reason == DLL_PROCESS_DETACH) { + if (g_processID == GetCurrentProcessId()) { + uninstall(); + uninstallScreenSaver(); + g_processID = 0; + g_hinstance = NULL; + } + } + return TRUE; +} + +extern "C" { + +// VS2005 hack to not link with the CRT +#if _MSC_VER >= 1400 +BOOL WINAPI _DllMainCRTStartup( + HINSTANCE instance, DWORD reason, LPVOID lpreserved) +{ + return DllMain(instance, reason, lpreserved); +} + +// VS2005 is a bit more bright than VC6 and optimize simple copy loop to +// intrinsic memcpy. +void * __cdecl memcpy(void * _Dst, const void * _Src, size_t _MaxCount) +{ + void * _DstBackup = _Dst; + switch (_MaxCount & 3) { + case 3: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + --_MaxCount; + case 2: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + --_MaxCount; + case 1: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + --_MaxCount; + break; + case 0: + break; + + default: + __assume(0); + break; + } + + // I think it's faster on intel to deference than modify the pointer. + const size_t max = _MaxCount / sizeof(UINT_PTR); + for (size_t i = 0; i < max; ++i) { + ((UINT_PTR*)_Dst)[i] = ((UINT_PTR*)_Src)[i]; + } + + (UINT_PTR*&)_Dst += max; + (UINT_PTR*&)_Src += max; + + switch (_MaxCount & 3) { + case 3: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + case 2: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + case 1: + ((char*)_Dst)[0] = ((char*)_Src)[0]; + ++(char*&)_Dst; + ++(char*&)_Src; + break; + case 0: + break; + + default: + __assume(0); + break; + } + + return _DstBackup; +} +#endif + +int +init(DWORD threadID) +{ + assert(g_hinstance != NULL); + + // try to open process that last called init() to see if it's + // still running or if it died without cleaning up. + if (g_processID != 0 && g_processID != GetCurrentProcessId()) { + HANDLE process = OpenProcess(STANDARD_RIGHTS_REQUIRED, + FALSE, g_processID); + if (process != NULL) { + // old process (probably) still exists so refuse to + // reinitialize this DLL (and thus steal it from the + // old process). + CloseHandle(process); + return 0; + } + + // clean up after old process. the system should've already + // removed the hooks so we just need to reset our state. + g_hinstance = GetModuleHandle(_T("synrgyhk")); + g_processID = GetCurrentProcessId(); + g_wheelSupport = kWheelNone; + g_threadID = 0; + g_keyboard = NULL; + g_mouse = NULL; + g_getMessage = NULL; + g_keyboardLL = NULL; + g_mouseLL = NULL; + g_screenSaver = false; + } + + // save thread id. we'll post messages to this thread's + // message queue. + g_threadID = threadID; + + // set defaults + g_mode = kHOOK_DISABLE; + g_zoneSides = 0; + g_zoneSize = 0; + g_xScreen = 0; + g_yScreen = 0; + g_wScreen = 0; + g_hScreen = 0; + + return 1; +} + +int +cleanup(void) +{ + assert(g_hinstance != NULL); + + if (g_processID == GetCurrentProcessId()) { + g_threadID = 0; + } + + return 1; +} + +EHookResult +install() +{ + assert(g_hinstance != NULL); + assert(g_keyboard == NULL); + assert(g_mouse == NULL); + assert(g_getMessage == NULL || g_screenSaver); + + // must be initialized + if (g_threadID == 0) { + return kHOOK_FAILED; + } + + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + // reset fake input flag + g_fakeInput = false; + + // check for mouse wheel support + g_wheelSupport = getWheelSupport(); + + // install GetMessage hook (unless already installed) + if (g_wheelSupport == kWheelOld && g_getMessage == NULL) { + g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, + &getMessageHook, + g_hinstance, + 0); + } + + // install low-level hooks. we require that they both get installed. +#if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS + g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL, + &mouseLLHook, + g_hinstance, + 0); +#if !NO_GRAB_KEYBOARD + g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL, + &keyboardLLHook, + g_hinstance, + 0); + if (g_mouseLL == NULL || g_keyboardLL == NULL) { + if (g_keyboardLL != NULL) { + UnhookWindowsHookEx(g_keyboardLL); + g_keyboardLL = NULL; + } + if (g_mouseLL != NULL) { + UnhookWindowsHookEx(g_mouseLL); + g_mouseLL = NULL; + } + } +#endif +#endif + + // install regular hooks + if (g_mouseLL == NULL) { + g_mouse = SetWindowsHookEx(WH_MOUSE, + &mouseHook, + g_hinstance, + 0); + } +#if !NO_GRAB_KEYBOARD + if (g_keyboardLL == NULL) { + g_keyboard = SetWindowsHookEx(WH_KEYBOARD, + &keyboardHook, + g_hinstance, + 0); + } +#endif + + // check that we got all the hooks we wanted + if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) || +#if !NO_GRAB_KEYBOARD + (g_keyboardLL == NULL && g_keyboard == NULL) || +#endif + (g_mouseLL == NULL && g_mouse == NULL)) { + uninstall(); + return kHOOK_FAILED; + } + + if (g_keyboardLL != NULL || g_mouseLL != NULL) { + g_hookThread = GetCurrentThreadId(); + return kHOOK_OKAY_LL; + } + + return kHOOK_OKAY; +} + +int +uninstall(void) +{ + assert(g_hinstance != NULL); + + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + // detach from thread + detachThread(); + + // uninstall hooks + if (g_keyboardLL != NULL) { + UnhookWindowsHookEx(g_keyboardLL); + g_keyboardLL = NULL; + } + if (g_mouseLL != NULL) { + UnhookWindowsHookEx(g_mouseLL); + g_mouseLL = NULL; + } + if (g_keyboard != NULL) { + UnhookWindowsHookEx(g_keyboard); + g_keyboard = NULL; + } + if (g_mouse != NULL) { + UnhookWindowsHookEx(g_mouse); + g_mouse = NULL; + } + if (g_getMessage != NULL && !g_screenSaver) { + UnhookWindowsHookEx(g_getMessage); + g_getMessage = NULL; + } + g_wheelSupport = kWheelNone; + + return 1; +} + +int +installScreenSaver(void) +{ + assert(g_hinstance != NULL); + + // must be initialized + if (g_threadID == 0) { + return 0; + } + + // generate screen saver messages + g_screenSaver = true; + + // install hook unless it's already installed + if (g_getMessage == NULL) { + g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, + &getMessageHook, + g_hinstance, + 0); + } + + return (g_getMessage != NULL) ? 1 : 0; +} + +int +uninstallScreenSaver(void) +{ + assert(g_hinstance != NULL); + + // uninstall hook unless the mouse wheel hook is installed + if (g_getMessage != NULL && g_wheelSupport != kWheelOld) { + UnhookWindowsHookEx(g_getMessage); + g_getMessage = NULL; + } + + // screen saver hook is no longer installed + g_screenSaver = false; + + return 1; +} + +void +setSides(UInt32 sides) +{ + g_zoneSides = sides; +} + +void +setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize) +{ + g_zoneSize = jumpZoneSize; + g_xScreen = x; + g_yScreen = y; + g_wScreen = w; + g_hScreen = h; +} + +void +setMode(EHookMode mode) +{ + if (mode == g_mode) { + // no change + return; + } + g_mode = mode; +} + +} diff --git a/lib/platform/CSynergyHook.h b/lib/platform/CSynergyHook.h new file mode 100644 index 00000000..e8661815 --- /dev/null +++ b/lib/platform/CSynergyHook.h @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSYNERGYHOOK_H +#define CSYNERGYHOOK_H + +#include "BasicTypes.h" +#define WIN32_LEAN_AND_MEAN +#include + +#if defined(SYNRGYHK_EXPORTS) +#define CSYNERGYHOOK_API __declspec(dllexport) +#else +#define CSYNERGYHOOK_API __declspec(dllimport) +#endif + +#define SYNERGY_MSG_MARK WM_APP + 0x0011 // mark id; +#define SYNERGY_MSG_KEY WM_APP + 0x0012 // vk code; key data +#define SYNERGY_MSG_MOUSE_BUTTON WM_APP + 0x0013 // button msg; +#define SYNERGY_MSG_MOUSE_WHEEL WM_APP + 0x0014 // delta; +#define SYNERGY_MSG_MOUSE_MOVE WM_APP + 0x0015 // x; y +#define SYNERGY_MSG_POST_WARP WM_APP + 0x0016 // ; +#define SYNERGY_MSG_PRE_WARP WM_APP + 0x0017 // x; y +#define SYNERGY_MSG_SCREEN_SAVER WM_APP + 0x0018 // activated; +#define SYNERGY_MSG_DEBUG WM_APP + 0x0019 // data, data +#define SYNERGY_MSG_INPUT_FIRST SYNERGY_MSG_KEY +#define SYNERGY_MSG_INPUT_LAST SYNERGY_MSG_PRE_WARP +#define SYNERGY_HOOK_LAST_MSG SYNERGY_MSG_DEBUG + +#define SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY VK_CANCEL +#define SYNERGY_HOOK_FAKE_INPUT_SCANCODE 0 + +extern "C" { + +enum EHookResult { + kHOOK_FAILED, + kHOOK_OKAY, + kHOOK_OKAY_LL +}; + +enum EHookMode { + kHOOK_DISABLE, + kHOOK_WATCH_JUMP_ZONE, + kHOOK_RELAY_EVENTS +}; + +typedef int (*InitFunc)(DWORD targetQueueThreadID); +typedef int (*CleanupFunc)(void); +typedef EHookResult (*InstallFunc)(void); +typedef int (*UninstallFunc)(void); +typedef int (*InstallScreenSaverFunc)(void); +typedef int (*UninstallScreenSaverFunc)(void); +typedef void (*SetSidesFunc)(UInt32); +typedef void (*SetZoneFunc)(SInt32, SInt32, SInt32, SInt32, SInt32); +typedef void (*SetModeFunc)(int); + +CSYNERGYHOOK_API int init(DWORD); +CSYNERGYHOOK_API int cleanup(void); +CSYNERGYHOOK_API EHookResult install(void); +CSYNERGYHOOK_API int uninstall(void); +CSYNERGYHOOK_API int installScreenSaver(void); +CSYNERGYHOOK_API int uninstallScreenSaver(void); +CSYNERGYHOOK_API void setSides(UInt32 sides); +CSYNERGYHOOK_API void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, + SInt32 jumpZoneSize); +CSYNERGYHOOK_API void setMode(EHookMode mode); + +} + +#endif diff --git a/lib/platform/CXWindowsClipboard.cpp b/lib/platform/CXWindowsClipboard.cpp new file mode 100644 index 00000000..33252af9 --- /dev/null +++ b/lib/platform/CXWindowsClipboard.cpp @@ -0,0 +1,1507 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboard.h" +#include "CXWindowsClipboardTextConverter.h" +#include "CXWindowsClipboardUCS2Converter.h" +#include "CXWindowsClipboardUTF8Converter.h" +#include "CXWindowsClipboardHTMLConverter.h" +#include "CXWindowsClipboardBMPConverter.h" +#include "CXWindowsUtil.h" +#include "CThread.h" +#include "CLog.h" +#include "CStopwatch.h" +#include "CArch.h" +#include "stdvector.h" +#include +#include + +// +// CXWindowsClipboard +// + +CXWindowsClipboard::CXWindowsClipboard(Display* display, + Window window, ClipboardID id) : + m_display(display), + m_window(window), + m_id(id), + m_open(false), + m_time(0), + m_owner(false), + m_timeOwned(0), + m_timeLost(0) +{ + // get some atoms + m_atomTargets = XInternAtom(m_display, "TARGETS", False); + m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False); + m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False); + m_atomInteger = XInternAtom(m_display, "INTEGER", False); + m_atomAtom = XInternAtom(m_display, "ATOM", False); + m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False); + m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False); + m_atomINCR = XInternAtom(m_display, "INCR", False); + m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False); + m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False); + m_atomMotifClipAccess = XInternAtom(m_display, + "_MOTIF_CLIP_LOCK_ACCESS_VALID", False); + m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False); + + // set selection atom based on clipboard id + switch (id) { + case kClipboardClipboard: + m_selection = XInternAtom(m_display, "CLIPBOARD", False); + break; + + case kClipboardSelection: + default: + m_selection = XA_PRIMARY; + break; + } + + // add converters, most desired first + m_converters.push_back(new CXWindowsClipboardHTMLConverter(m_display, + "text/html")); + m_converters.push_back(new CXWindowsClipboardBMPConverter(m_display)); + m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display, + "text/plain;charset=UTF-8")); + m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display, + "UTF8_STRING")); + m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display, + "text/plain;charset=ISO-10646-UCS-2")); + m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display, + "text/unicode")); + m_converters.push_back(new CXWindowsClipboardTextConverter(m_display, + "text/plain")); + m_converters.push_back(new CXWindowsClipboardTextConverter(m_display, + "STRING")); + + // we have no data + clearCache(); +} + +CXWindowsClipboard::~CXWindowsClipboard() +{ + clearReplies(); + clearConverters(); +} + +void +CXWindowsClipboard::lost(Time time) +{ + LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time)); + if (m_owner) { + m_owner = false; + m_timeLost = time; + clearCache(); + } +} + +void +CXWindowsClipboard::addRequest(Window owner, Window requestor, + Atom target, ::Time time, Atom property) +{ + // must be for our window and we must have owned the selection + // at the given time. + bool success = false; + if (owner == m_window) { + LOG((CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection, CXWindowsUtil::atomToString(m_display, target).c_str(), requestor, CXWindowsUtil::atomToString(m_display, property).c_str())); + if (wasOwnedAtTime(time)) { + if (target == m_atomMultiple) { + // add a multiple request. property may not be None + // according to ICCCM. + if (property != None) { + success = insertMultipleReply(requestor, time, property); + } + } + else { + addSimpleRequest(requestor, target, time, property); + + // addSimpleRequest() will have already handled failure + success = true; + } + } + else { + LOG((CLOG_DEBUG1 "failed, not owned at time %d", time)); + } + } + + if (!success) { + // send failure + LOG((CLOG_DEBUG1 "failed")); + insertReply(new CReply(requestor, target, time)); + } + + // send notifications that are pending + pushReplies(); +} + +bool +CXWindowsClipboard::addSimpleRequest(Window requestor, + Atom target, ::Time time, Atom property) +{ + // obsolete requestors may supply a None property. in + // that case we use the target as the property to store + // the conversion. + if (property == None) { + property = target; + } + + // handle targets + CString data; + Atom type = None; + int format = 0; + if (target == m_atomTargets) { + type = getTargetsData(data, &format); + } + else if (target == m_atomTimestamp) { + type = getTimestampData(data, &format); + } + else { + IXWindowsClipboardConverter* converter = getConverter(target); + if (converter != NULL) { + IClipboard::EFormat clipboardFormat = converter->getFormat(); + if (m_added[clipboardFormat]) { + try { + data = converter->fromIClipboard(m_data[clipboardFormat]); + format = converter->getDataSize(); + type = converter->getAtom(); + } + catch (...) { + // ignore -- cannot convert + } + } + } + } + + if (type != None) { + // success + LOG((CLOG_DEBUG1 "success")); + insertReply(new CReply(requestor, target, time, + property, data, type, format)); + return true; + } + else { + // failure + LOG((CLOG_DEBUG1 "failed")); + insertReply(new CReply(requestor, target, time)); + return false; + } +} + +bool +CXWindowsClipboard::processRequest(Window requestor, + ::Time /*time*/, Atom property) +{ + CReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + LOG((CLOG_DEBUG1 "received property %s delete from 0x08%x", CXWindowsUtil::atomToString(m_display, property).c_str(), requestor)); + + // find the property in the known requests. it should be the + // first property but we'll check 'em all if we have to. + CReplyList& replies = index->second; + for (CReplyList::iterator index2 = replies.begin(); + index2 != replies.end(); ++index2) { + CReply* reply = *index2; + if (reply->m_replied && reply->m_property == property) { + // if reply is complete then remove it and start the + // next one. + pushReplies(index, replies, index2); + return true; + } + } + + return false; +} + +bool +CXWindowsClipboard::destroyRequest(Window requestor) +{ + CReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + + // destroy all replies for this window + clearReplies(index->second); + m_replies.erase(index); + + // note -- we don't stop watching the window for events because + // we're called in response to the window being destroyed. + + return true; +} + +Window +CXWindowsClipboard::getWindow() const +{ + return m_window; +} + +Atom +CXWindowsClipboard::getSelection() const +{ + return m_selection; +} + +bool +CXWindowsClipboard::empty() +{ + assert(m_open); + + LOG((CLOG_DEBUG "empty clipboard %d", m_id)); + + // assert ownership of clipboard + XSetSelectionOwner(m_display, m_selection, m_window, m_time); + if (XGetSelectionOwner(m_display, m_selection) != m_window) { + LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id)); + return false; + } + + // clear all data. since we own the data now, the cache is up + // to date. + clearCache(); + m_cached = true; + + // FIXME -- actually delete motif clipboard items? + // FIXME -- do anything to motif clipboard properties? + + // save time + m_timeOwned = m_time; + m_timeLost = 0; + + // we're the owner now + m_owner = true; + LOG((CLOG_DEBUG "grabbed clipboard %d", m_id)); + + return true; +} + +void +CXWindowsClipboard::add(EFormat format, const CString& data) +{ + assert(m_open); + assert(m_owner); + + LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format)); + + m_data[format] = data; + m_added[format] = true; + + // FIXME -- set motif clipboard item? +} + +bool +CXWindowsClipboard::open(Time time) const +{ + assert(!m_open); + + LOG((CLOG_DEBUG "open clipboard %d", m_id)); + + // assume not motif + m_motif = false; + + // lock clipboard + if (m_id == kClipboardClipboard) { + if (!motifLockClipboard()) { + return false; + } + + // check if motif owns the selection. unlock motif clipboard + // if it does not. + m_motif = motifOwnsClipboard(); + LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not ")); + if (!m_motif) { + motifUnlockClipboard(); + } + } + + // now open + m_open = true; + m_time = time; + + // be sure to flush the cache later if it's dirty + m_checkCache = true; + + return true; +} + +void +CXWindowsClipboard::close() const +{ + assert(m_open); + + LOG((CLOG_DEBUG "close clipboard %d", m_id)); + + // unlock clipboard + if (m_motif) { + motifUnlockClipboard(); + } + + m_motif = false; + m_open = false; +} + +IClipboard::Time +CXWindowsClipboard::getTime() const +{ + checkCache(); + return m_timeOwned; +} + +bool +CXWindowsClipboard::has(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_added[format]; +} + +CString +CXWindowsClipboard::get(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_data[format]; +} + +void +CXWindowsClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +IXWindowsClipboardConverter* +CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const +{ + IXWindowsClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + if (converter->getAtom() == target) { + break; + } + } + if (converter == NULL) { + LOG((CLOG_DEBUG1 " no converter for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + return NULL; + } + + // optionally skip already handled targets + if (onlyIfNotAdded) { + if (m_added[converter->getFormat()]) { + LOG((CLOG_DEBUG1 " skipping handled format %d", converter->getFormat())); + return NULL; + } + } + + return converter; +} + +void +CXWindowsClipboard::checkCache() const +{ + if (!m_checkCache) { + return; + } + m_checkCache = false; + + // get the time the clipboard ownership was taken by the current + // owner. + if (m_motif) { + m_timeOwned = motifGetTime(); + } + else { + m_timeOwned = icccmGetTime(); + } + + // if we can't get the time then use the time passed to us + if (m_timeOwned == 0) { + m_timeOwned = m_time; + } + + // if the cache is dirty then flush it + if (m_timeOwned != m_cacheTime) { + clearCache(); + } +} + +void +CXWindowsClipboard::clearCache() const +{ + const_cast(this)->doClearCache(); +} + +void +CXWindowsClipboard::doClearCache() +{ + m_checkCache = false; + m_cached = false; + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } +} + +void +CXWindowsClipboard::fillCache() const +{ + // get the selection data if not already cached + checkCache(); + if (!m_cached) { + const_cast(this)->doFillCache(); + } +} + +void +CXWindowsClipboard::doFillCache() +{ + if (m_motif) { + motifFillCache(); + } + else { + icccmFillCache(); + } + m_checkCache = false; + m_cached = true; + m_cacheTime = m_timeOwned; +} + +void +CXWindowsClipboard::icccmFillCache() +{ + LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id)); + + // see if we can get the list of available formats from the selection. + // if not then use a default list of formats. note that some clipboard + // owners are broken and report TARGETS as the type of the TARGETS data + // instead of the correct type ATOM; allow either. + const Atom atomTargets = m_atomTargets; + Atom target; + CString data; + if (!icccmGetSelection(atomTargets, &target, &data) || + (target != m_atomAtom && target != m_atomTargets)) { + LOG((CLOG_DEBUG1 "selection doesn't support TARGETS")); + data = ""; + CXWindowsUtil::appendAtomData(data, XA_STRING); + } + + CXWindowsUtil::convertAtomProperty(data); + const Atom* targets = reinterpret_cast(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + LOG((CLOG_DEBUG " available targets: %s", CXWindowsUtil::atomsToString(m_display, targets, numTargets).c_str())); + + // try each converter in order (because they're in order of + // preference). + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip already handled targets + if (m_added[converter->getFormat()]) { + continue; + } + + // see if atom is in target list + Atom target = None; + // XXX -- just ask for the converter's target to see if it's + // available rather than checking TARGETS. i've seen clipboard + // owners that don't report all the targets they support. + target = converter->getAtom(); + /* + for (UInt32 i = 0; i < numTargets; ++i) { + if (converter->getAtom() == targets[i]) { + target = targets[i]; + break; + } + } + */ + if (target == None) { + continue; + } + + // get the data + Atom actualTarget; + CString targetData; + if (!icccmGetSelection(target, &actualTarget, &targetData)) { + LOG((CLOG_DEBUG1 " no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + continue; + } + + // add to clipboard and note we've done it + IClipboard::EFormat format = converter->getFormat(); + m_data[format] = converter->toIClipboard(targetData); + m_added[format] = true; + LOG((CLOG_DEBUG " added format %d for target %s (%u %s)", format, CXWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes")); + } +} + +bool +CXWindowsClipboard::icccmGetSelection(Atom target, + Atom* actualTarget, CString* data) const +{ + assert(actualTarget != NULL); + assert(data != NULL); + + // request data conversion + CICCCMGetClipboard getter(m_window, m_time, m_atomData); + if (!getter.readClipboard(m_display, m_selection, + target, actualTarget, data)) { + LOG((CLOG_DEBUG1 "can't get data for selection target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner")); + return false; + } + else if (*actualTarget == None) { + LOG((CLOG_DEBUG1 "selection conversion failed for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + return false; + } + return true; +} + +IClipboard::Time +CXWindowsClipboard::icccmGetTime() const +{ + Atom actualTarget; + CString data; + if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) && + actualTarget == m_atomInteger) { + Time time = *reinterpret_cast(data.data()); + LOG((CLOG_DEBUG1 "got ICCCM time %d", time)); + return time; + } + else { + // no timestamp + LOG((CLOG_DEBUG1 "can't get ICCCM time")); + return 0; + } +} + +bool +CXWindowsClipboard::motifLockClipboard() const +{ + // fail if anybody owns the lock (even us, so this is non-recursive) + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != None) { + LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner)); + return false; + } + + // try to grab the lock + // FIXME -- is this right? there's a race condition here -- + // A grabs successfully, B grabs successfully, A thinks it + // still has the grab until it gets a SelectionClear. + Time time = CXWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time); + lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner)); + return false; + } + + LOG((CLOG_DEBUG1 "locked motif clipboard")); + return true; +} + +void +CXWindowsClipboard::motifUnlockClipboard() const +{ + LOG((CLOG_DEBUG1 "unlocked motif clipboard")); + + // fail if we don't own the lock + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + return; + } + + // release lock + Time time = CXWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time); +} + +bool +CXWindowsClipboard::motifOwnsClipboard() const +{ + // get the current selection owner + // FIXME -- this can't be right. even if the window is destroyed + // Motif will still have a valid clipboard. how can we tell if + // some other client owns CLIPBOARD? + Window owner = XGetSelectionOwner(m_display, m_selection); + if (owner == None) { + return false; + } + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + CString data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!CXWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return false; + } + + // check the owner window against the current clipboard owner + const CMotifClipHeader* header = + reinterpret_cast(data.data()); + if (data.size() >= sizeof(CMotifClipHeader) && + header->m_id == kMotifClipHeader) { + if (static_cast(header->m_selectionOwner) == owner) { + return true; + } + } + + return false; +} + +void +CXWindowsClipboard::motifFillCache() +{ + LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id)); + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + CString data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!CXWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return; + } + + // check that the header is okay + const CMotifClipHeader* header = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipHeader) || + header->m_id != kMotifClipHeader || + header->m_numItems < 1) { + return; + } + + // get the Motif item property from the root window + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item); + Atom atomItem = XInternAtom(m_display, name, False); + data = ""; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomItem, &data, + &target, &format, False)) { + return; + } + + // check that the item is okay + const CMotifClipItem* item = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipItem) || + item->m_id != kMotifClipItem || + item->m_numFormats - item->m_numDeletedFormats < 1) { + return; + } + + // format list is after static item structure elements + const SInt32 numFormats = item->m_numFormats - item->m_numDeletedFormats; + const SInt32* formats = reinterpret_cast(item->m_size + + reinterpret_cast(data.data())); + + // get the available formats + typedef std::map CMotifFormatMap; + CMotifFormatMap motifFormats; + for (SInt32 i = 0; i < numFormats; ++i) { + // get Motif format property from the root window + sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]); + Atom atomFormat = XInternAtom(m_display, name, False); + CString data; + if (!CXWindowsUtil::getWindowProperty(m_display, root, + atomFormat, &data, + &target, &format, False)) { + continue; + } + + // check that the format is okay + const CMotifClipFormat* motifFormat = + reinterpret_cast(data.data()); + if (data.size() < sizeof(CMotifClipFormat) || + motifFormat->m_id != kMotifClipFormat || + motifFormat->m_length < 0 || + motifFormat->m_type == None || + motifFormat->m_deleted != 0) { + continue; + } + + // save it + motifFormats.insert(std::make_pair(motifFormat->m_type, data)); + } + //const UInt32 numMotifFormats = motifFormats.size(); + + // try each converter in order (because they're in order of + // preference). + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip already handled targets + if (m_added[converter->getFormat()]) { + continue; + } + + // see if atom is in target list + CMotifFormatMap::const_iterator index2 = + motifFormats.find(converter->getAtom()); + if (index2 == motifFormats.end()) { + continue; + } + + // get format + const CMotifClipFormat* motifFormat = + reinterpret_cast( + index2->second.data()); + const Atom target = motifFormat->m_type; + + // get the data (finally) + Atom actualTarget; + CString targetData; + if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) { + LOG((CLOG_DEBUG1 " no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str())); + continue; + } + + // add to clipboard and note we've done it + IClipboard::EFormat format = converter->getFormat(); + m_data[format] = converter->toIClipboard(targetData); + m_added[format] = true; + LOG((CLOG_DEBUG " added format %d for target %s", format, CXWindowsUtil::atomToString(m_display, target).c_str())); + } +} + +bool +CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format, + Atom* actualTarget, CString* data) const +{ + // if the current clipboard owner and the owner indicated by the + // motif clip header are the same then transfer via a property on + // the root window, otherwise transfer as a normal ICCCM client. + if (!motifOwnsClipboard()) { + return icccmGetSelection(format->m_type, actualTarget, data); + } + + // use motif way + // FIXME -- this isn't right. it'll only work if the data is + // already stored on the root window and only if it fits in a + // property. motif has some scheme for transferring part by + // part that i don't know. + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data); + Atom target = XInternAtom(m_display, name, False); + Window root = RootWindow(m_display, DefaultScreen(m_display)); + return CXWindowsUtil::getWindowProperty(m_display, root, + target, data, + actualTarget, NULL, False); +} + +IClipboard::Time +CXWindowsClipboard::motifGetTime() const +{ + return icccmGetTime(); +} + +bool +CXWindowsClipboard::insertMultipleReply(Window requestor, + ::Time time, Atom property) +{ + // get the requested targets + Atom target; + SInt32 format; + CString data; + if (!CXWindowsUtil::getWindowProperty(m_display, requestor, + property, &data, &target, &format, False)) { + // can't get the requested targets + return false; + } + + // fail if the requested targets isn't of the correct form + if (format != 32 || target != m_atomAtomPair) { + return false; + } + + // data is a list of atom pairs: target, property + CXWindowsUtil::convertAtomProperty(data); + const Atom* targets = reinterpret_cast(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + + // add replies for each target + bool changed = false; + for (UInt32 i = 0; i < numTargets; i += 2) { + const Atom target = targets[i + 0]; + const Atom property = targets[i + 1]; + if (!addSimpleRequest(requestor, target, time, property)) { + // note that we can't perform the requested conversion + CXWindowsUtil::replaceAtomData(data, i, None); + changed = true; + } + } + + // update the targets property if we changed it + if (changed) { + CXWindowsUtil::setWindowProperty(m_display, requestor, + property, data.data(), data.size(), + target, format); + } + + // add reply for MULTIPLE request + insertReply(new CReply(requestor, m_atomMultiple, + time, property, CString(), None, 32)); + + return true; +} + +void +CXWindowsClipboard::insertReply(CReply* reply) +{ + assert(reply != NULL); + + // note -- we must respond to requests in order if requestor,target,time + // are the same, otherwise we can use whatever order we like with one + // exception: each reply in a MULTIPLE reply must be handled in order + // as well. those replies will almost certainly not share targets so + // we can't simply use requestor,target,time as map index. + // + // instead we'll use just the requestor. that's more restrictive than + // necessary but we're guaranteed to do things in the right order. + // note that we could also include the time in the map index and still + // ensure the right order. but since that'll just make it harder to + // find the right reply when handling property notify events we stick + // to just the requestor. + + const bool newWindow = (m_replies.count(reply->m_requestor) == 0); + m_replies[reply->m_requestor].push_back(reply); + + // adjust requestor's event mask if we haven't done so already. we + // want events in case the window is destroyed or any of its + // properties change. + if (newWindow) { + // note errors while we adjust event masks + bool error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + + // get and save the current event mask + XWindowAttributes attr; + XGetWindowAttributes(m_display, reply->m_requestor, &attr); + m_eventMasks[reply->m_requestor] = attr.your_event_mask; + + // add the events we want + XSelectInput(m_display, reply->m_requestor, attr.your_event_mask | + StructureNotifyMask | PropertyChangeMask); + } + + // if we failed then the window has already been destroyed + if (error) { + m_replies.erase(reply->m_requestor); + delete reply; + } + } +} + +void +CXWindowsClipboard::pushReplies() +{ + // send the first reply for each window if that reply hasn't + // been sent yet. + for (CReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ) { + assert(!index->second.empty()); + if (!index->second.front()->m_replied) { + pushReplies(index, index->second, index->second.begin()); + } + else { + ++index; + } + } +} + +void +CXWindowsClipboard::pushReplies(CReplyMap::iterator& mapIndex, + CReplyList& replies, CReplyList::iterator index) +{ + CReply* reply = *index; + while (sendReply(reply)) { + // reply is complete. discard it and send the next reply, + // if any. + index = replies.erase(index); + delete reply; + if (index == replies.end()) { + break; + } + reply = *index; + } + + // if there are no more replies in the list then remove the list + // and stop watching the requestor for events. + if (replies.empty()) { + CXWindowsUtil::CErrorLock lock(m_display); + Window requestor = mapIndex->first; + XSelectInput(m_display, requestor, m_eventMasks[requestor]); + m_replies.erase(mapIndex++); + m_eventMasks.erase(requestor); + } + else { + ++mapIndex; + } +} + +bool +CXWindowsClipboard::sendReply(CReply* reply) +{ + assert(reply != NULL); + + // bail out immediately if reply is done + if (reply->m_done) { + LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + return true; + } + + // start in failed state if property is None + bool failed = (reply->m_property == None); + if (!failed) { + LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + + // send using INCR if already sending incrementally or if reply + // is too large, otherwise just send it. + const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display); + const bool useINCR = (reply->m_data.size() > maxRequestSize); + + // send INCR reply if incremental and we haven't replied yet + if (useINCR && !reply->m_replied) { + UInt32 size = reply->m_data.size(); + if (!CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &size, 4, m_atomINCR, 32)) { + failed = true; + } + } + + // send more INCR reply or entire non-incremental reply + else { + // how much more data should we send? + UInt32 size = reply->m_data.size() - reply->m_ptr; + if (size > maxRequestSize) + size = maxRequestSize; + + // send it + if (!CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + reply->m_data.data() + reply->m_ptr, + size, + reply->m_type, reply->m_format)) { + failed = true; + } + else { + reply->m_ptr += size; + + // we've finished the reply if we just sent the zero + // size incremental chunk or if we're not incremental. + reply->m_done = (size == 0 || !useINCR); + } + } + } + + // if we've failed then delete the property and say we're done. + // if we haven't replied yet then we can send a failure notify, + // otherwise we've failed in the middle of an incremental + // transfer; i don't know how to cancel that so i'll just send + // the final zero-length property. + // FIXME -- how do you gracefully cancel an incremental transfer? + if (failed) { + LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_done = true; + if (reply->m_property != None) { + CXWindowsUtil::CErrorLock lock(m_display); + XDeleteProperty(m_display, reply->m_requestor, reply->m_property); + } + + if (!reply->m_replied) { + sendNotify(reply->m_requestor, m_selection, + reply->m_target, None, + reply->m_time); + + // don't wait for any reply (because we're not expecting one) + return true; + } + else { + static const char dummy = 0; + CXWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &dummy, + 0, + reply->m_type, reply->m_format); + + // wait for delete notify + return false; + } + } + + // send notification if we haven't yet + if (!reply->m_replied) { + LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_replied = true; + + // dump every property on the requestor window to the debug2 + // log. we've seen what appears to be a bug in lesstif and + // knowing the properties may help design a workaround, if + // it becomes necessary. + if (CLOG->getFilter() >= CLog::kDEBUG2) { + CXWindowsUtil::CErrorLock lock(m_display); + int n; + Atom* props = XListProperties(m_display, reply->m_requestor, &n); + LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor)); + for (int i = 0; i < n; ++i) { + Atom target; + CString data; + char* name = XGetAtomName(m_display, props[i]); + if (!CXWindowsUtil::getWindowProperty(m_display, + reply->m_requestor, + props[i], &data, &target, NULL, False)) { + LOG((CLOG_DEBUG2 " %s: ", name)); + } + else { + // if there are any non-ascii characters in string + // then print the binary data. + static const char* hex = "0123456789abcdef"; + for (CString::size_type j = 0; j < data.size(); ++j) { + if (data[j] < 32 || data[j] > 126) { + CString tmp; + tmp.reserve(data.size() * 3); + for (j = 0; j < data.size(); ++j) { + unsigned char v = (unsigned char)data[j]; + tmp += hex[v >> 16]; + tmp += hex[v & 15]; + tmp += ' '; + } + data = tmp; + break; + } + } + char* type = XGetAtomName(m_display, target); + LOG((CLOG_DEBUG2 " %s (%s): %s", name, type, data.c_str())); + if (type != NULL) { + XFree(type); + } + } + if (name != NULL) { + XFree(name); + } + } + if (props != NULL) { + XFree(props); + } + } + + sendNotify(reply->m_requestor, m_selection, + reply->m_target, reply->m_property, + reply->m_time); + } + + // wait for delete notify + return false; +} + +void +CXWindowsClipboard::clearReplies() +{ + for (CReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ++index) { + clearReplies(index->second); + } + m_replies.clear(); + m_eventMasks.clear(); +} + +void +CXWindowsClipboard::clearReplies(CReplyList& replies) +{ + for (CReplyList::iterator index = replies.begin(); + index != replies.end(); ++index) { + delete *index; + } + replies.clear(); +} + +void +CXWindowsClipboard::sendNotify(Window requestor, + Atom selection, Atom target, Atom property, Time time) +{ + XEvent event; + event.xselection.type = SelectionNotify; + event.xselection.display = m_display; + event.xselection.requestor = requestor; + event.xselection.selection = selection; + event.xselection.target = target; + event.xselection.property = property; + event.xselection.time = time; + CXWindowsUtil::CErrorLock lock(m_display); + XSendEvent(m_display, requestor, False, 0, &event); +} + +bool +CXWindowsClipboard::wasOwnedAtTime(::Time time) const +{ + // not owned if we've never owned the selection + checkCache(); + if (m_timeOwned == 0) { + return false; + } + + // if time is CurrentTime then return true if we still own the + // selection and false if we do not. else if we still own the + // selection then get the current time, otherwise use + // m_timeLost as the end time. + Time lost = m_timeLost; + if (m_timeLost == 0) { + if (time == CurrentTime) { + return true; + } + else { + lost = CXWindowsUtil::getCurrentTime(m_display, m_window); + } + } + else { + if (time == CurrentTime) { + return false; + } + } + + // compare time to range + Time duration = lost - m_timeOwned; + Time when = time - m_timeOwned; + return (/*when >= 0 &&*/ when <= duration); +} + +Atom +CXWindowsClipboard::getTargetsData(CString& data, int* format) const +{ + assert(format != NULL); + + // add standard targets + CXWindowsUtil::appendAtomData(data, m_atomTargets); + CXWindowsUtil::appendAtomData(data, m_atomMultiple); + CXWindowsUtil::appendAtomData(data, m_atomTimestamp); + + // add targets we can convert to + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip formats we don't have + if (m_added[converter->getFormat()]) { + CXWindowsUtil::appendAtomData(data, converter->getAtom()); + } + } + + *format = 32; + return m_atomAtom; +} + +Atom +CXWindowsClipboard::getTimestampData(CString& data, int* format) const +{ + assert(format != NULL); + + checkCache(); + CXWindowsUtil::appendTimeData(data, m_timeOwned); + *format = 32; + return m_atomInteger; +} + + +// +// CXWindowsClipboard::CICCCMGetClipboard +// + +CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard( + Window requestor, Time time, Atom property) : + m_requestor(requestor), + m_time(time), + m_property(property), + m_incr(false), + m_failed(false), + m_done(false), + m_reading(false), + m_data(NULL), + m_actualTarget(NULL), + m_error(false) +{ + // do nothing +} + +CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard() +{ + // do nothing +} + +bool +CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display, + Atom selection, Atom target, Atom* actualTarget, CString* data) +{ + assert(actualTarget != NULL); + assert(data != NULL); + + LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", CXWindowsUtil::atomToString(display, selection).c_str(), CXWindowsUtil::atomToString(display, target).c_str(), m_requestor)); + + m_atomNone = XInternAtom(display, "NONE", False); + m_atomIncr = XInternAtom(display, "INCR", False); + + // save output pointers + m_actualTarget = actualTarget; + m_data = data; + + // assume failure + *m_actualTarget = None; + *m_data = ""; + + // delete target property + XDeleteProperty(display, m_requestor, m_property); + + // select window for property changes + XWindowAttributes attr; + XGetWindowAttributes(display, m_requestor, &attr); + XSelectInput(display, m_requestor, + attr.your_event_mask | PropertyChangeMask); + + // request data conversion + XConvertSelection(display, selection, target, + m_property, m_requestor, m_time); + + // synchronize with server before we start following timeout countdown + XSync(display, False); + + // Xlib inexplicably omits the ability to wait for an event with + // a timeout. (it's inexplicable because there's no portable way + // to do it.) we'll poll until we have what we're looking for or + // a timeout expires. we use a timeout so we don't get locked up + // by badly behaved selection owners. + XEvent xevent; + std::vector events; + CStopwatch timeout(true); + static const double s_timeout = 0.25; // FIXME -- is this too short? + bool noWait = false; + while (!m_done && !m_failed) { + // fail if timeout has expired + if (timeout.getTime() >= s_timeout) { + m_failed = true; + break; + } + + // process events if any otherwise sleep + if (noWait || XPending(display) > 0) { + while (!m_done && !m_failed && (noWait || XPending(display) > 0)) { + XNextEvent(display, &xevent); + if (!processEvent(display, &xevent)) { + // not processed so save it + events.push_back(xevent); + } + else { + // reset timer since we've made some progress + timeout.reset(); + + // don't sleep anymore, just block waiting for events. + // we're assuming here that the clipboard owner will + // complete the protocol correctly. if we continue to + // sleep we'll get very bad performance. + noWait = true; + } + } + } + else { + ARCH->sleep(0.01); + } + } + + // put unprocessed events back + for (UInt32 i = events.size(); i > 0; --i) { + XPutBackEvent(display, &events[i - 1]); + } + + // restore mask + XSelectInput(display, m_requestor, attr.your_event_mask); + + // return success or failure + LOG((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded")); + return !m_failed; +} + +bool +CXWindowsClipboard::CICCCMGetClipboard::processEvent( + Display* display, XEvent* xevent) +{ + // process event + switch (xevent->type) { + case DestroyNotify: + if (xevent->xdestroywindow.window == m_requestor) { + m_failed = true; + return true; + } + + // not interested + return false; + + case SelectionNotify: + if (xevent->xselection.requestor == m_requestor) { + // done if we can't convert + if (xevent->xselection.property == None || + xevent->xselection.property == m_atomNone) { + m_done = true; + return true; + } + + // proceed if conversion successful + else if (xevent->xselection.property == m_property) { + m_reading = true; + break; + } + } + + // otherwise not interested + return false; + + case PropertyNotify: + // proceed if conversion successful and we're receiving more data + if (xevent->xproperty.window == m_requestor && + xevent->xproperty.atom == m_property && + xevent->xproperty.state == PropertyNewValue) { + if (!m_reading) { + // we haven't gotten the SelectionNotify yet + return true; + } + break; + } + + // otherwise not interested + return false; + + default: + // not interested + return false; + } + + // get the data from the property + Atom target; + const CString::size_type oldSize = m_data->size(); + if (!CXWindowsUtil::getWindowProperty(display, m_requestor, + m_property, m_data, &target, NULL, True)) { + // unable to read property + m_failed = true; + return true; + } + + // note if incremental. if we're already incremental then the + // selection owner is busted. if the INCR property has no size + // then the selection owner is busted. + if (target == m_atomIncr) { + if (m_incr) { + m_failed = true; + m_error = true; + } + else if (m_data->size() == oldSize) { + m_failed = true; + m_error = true; + } + else { + m_incr = true; + + // discard INCR data + *m_data = ""; + } + } + + // handle incremental chunks + else if (m_incr) { + // if first incremental chunk then save target + if (oldSize == 0) { + LOG((CLOG_DEBUG1 " INCR first chunk, target %s", CXWindowsUtil::atomToString(display, target).c_str())); + *m_actualTarget = target; + } + + // secondary chunks must have the same target + else { + if (target != *m_actualTarget) { + LOG((CLOG_WARN " INCR target mismatch")); + m_failed = true; + m_error = true; + } + } + + // note if this is the final chunk + if (m_data->size() == oldSize) { + LOG((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size())); + m_done = true; + } + } + + // not incremental; save the target. + else { + LOG((CLOG_DEBUG1 " target %s", CXWindowsUtil::atomToString(display, target).c_str())); + *m_actualTarget = target; + m_done = true; + } + + // this event has been processed + LOGC(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size())); + return true; +} + + +// +// CXWindowsClipboard::CReply +// + +CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(None), + m_replied(false), + m_done(false), + m_data(), + m_type(None), + m_format(32), + m_ptr(0) +{ + // do nothing +} + +CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time, + Atom property, const CString& data, Atom type, int format) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(property), + m_replied(false), + m_done(false), + m_data(data), + m_type(type), + m_format(format), + m_ptr(0) +{ + // do nothing +} diff --git a/lib/platform/CXWindowsClipboard.h b/lib/platform/CXWindowsClipboard.h new file mode 100644 index 00000000..b36a6c8d --- /dev/null +++ b/lib/platform/CXWindowsClipboard.h @@ -0,0 +1,376 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARD_H +#define CXWINDOWSCLIPBOARD_H + +#include "IClipboard.h" +#include "ClipboardTypes.h" +#include "stdmap.h" +#include "stdlist.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +class IXWindowsClipboardConverter; + +//! X11 clipboard implementation +class CXWindowsClipboard : public IClipboard { +public: + /*! + Use \c window as the window that owns or interacts with the + clipboard identified by \c id. + */ + CXWindowsClipboard(Display*, Window window, ClipboardID id); + virtual ~CXWindowsClipboard(); + + //! Notify clipboard was lost + /*! + Tells clipboard it lost ownership at the given time. + */ + void lost(Time); + + //! Add clipboard request + /*! + Adds a selection request to the request list. If the given + owner window isn't this clipboard's window then this simply + sends a failure event to the requestor. + */ + void addRequest(Window owner, + Window requestor, Atom target, + ::Time time, Atom property); + + //! Process clipboard request + /*! + Continues processing a selection request. Returns true if the + request was handled, false if the request was unknown. + */ + bool processRequest(Window requestor, + ::Time time, Atom property); + + //! Cancel clipboard request + /*! + Terminate a selection request. Returns true iff the request + was known and handled. + */ + bool destroyRequest(Window requestor); + + //! Get window + /*! + Returns the clipboard's window (passed the c'tor). + */ + Window getWindow() const; + + //! Get selection atom + /*! + Returns the selection atom that identifies the clipboard to X11 + (e.g. XA_PRIMARY). + */ + Atom getSelection() const; + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + // remove all converters from our list + void clearConverters(); + + // get the converter for a clipboard format. returns NULL if no + // suitable converter. iff onlyIfNotAdded is true then also + // return NULL if a suitable converter was found but we already + // have data of the converter's clipboard format. + IXWindowsClipboardConverter* + getConverter(Atom target, + bool onlyIfNotAdded = false) const; + + // convert target atom to clipboard format + EFormat getFormat(Atom target) const; + + // add a non-MULTIPLE request. does not verify that the selection + // was owned at the given time. returns true if the conversion + // could be performed, false otherwise. in either case, the + // reply is inserted. + bool addSimpleRequest( + Window requestor, Atom target, + ::Time time, Atom property); + + // if not already checked then see if the cache is stale and, if so, + // clear it. this has the side effect of updating m_timeOwned. + void checkCache() const; + + // clear the cache, resetting the cached flag and the added flag for + // each format. + void clearCache() const; + void doClearCache(); + + // cache all formats of the selection + void fillCache() const; + void doFillCache(); + + // + // helper classes + // + + // read an ICCCM conforming selection + class CICCCMGetClipboard { + public: + CICCCMGetClipboard(Window requestor, Time time, Atom property); + ~CICCCMGetClipboard(); + + // convert the given selection to the given type. returns + // true iff the conversion was successful or the conversion + // cannot be performed (in which case *actualTarget == None). + bool readClipboard(Display* display, + Atom selection, Atom target, + Atom* actualTarget, CString* data); + + private: + bool processEvent(Display* display, XEvent* event); + + private: + Window m_requestor; + Time m_time; + Atom m_property; + bool m_incr; + bool m_failed; + bool m_done; + + // atoms needed for the protocol + Atom m_atomNone; // NONE, not None + Atom m_atomIncr; + + // true iff we've received the selection notify + bool m_reading; + + // the converted selection data + CString* m_data; + + // the actual type of the data. if this is None then the + // selection owner cannot convert to the requested type. + Atom* m_actualTarget; + + public: + // true iff the selection owner didn't follow ICCCM conventions + bool m_error; + }; + + // Motif structure IDs + enum { kMotifClipFormat = 1, kMotifClipItem, kMotifClipHeader }; + + // _MOTIF_CLIP_HEADER structure + class CMotifClipHeader { + public: + SInt32 m_id; // kMotifClipHeader + SInt32 m_pad1[3]; + SInt32 m_item; + SInt32 m_pad2[4]; + SInt32 m_numItems; + SInt32 m_pad3[3]; + SInt32 m_selectionOwner; // a Window + SInt32 m_pad4[2]; + }; + + // Motif clip item structure + class CMotifClipItem { + public: + SInt32 m_id; // kMotifClipItem + SInt32 m_pad1[5]; + SInt32 m_size; + SInt32 m_numFormats; + SInt32 m_numDeletedFormats; + SInt32 m_pad2[6]; + }; + + // Motif clip format structure + class CMotifClipFormat { + public: + SInt32 m_id; // kMotifClipFormat + SInt32 m_pad1[6]; + SInt32 m_length; + SInt32 m_data; + SInt32 m_type; // an Atom + SInt32 m_pad2[1]; + SInt32 m_deleted; + SInt32 m_pad3[4]; + }; + + // stores data needed to respond to a selection request + class CReply { + public: + CReply(Window, Atom target, ::Time); + CReply(Window, Atom target, ::Time, Atom property, + const CString& data, Atom type, int format); + + public: + // information about the request + Window m_requestor; + Atom m_target; + ::Time m_time; + Atom m_property; + + // true iff we've sent the notification for this reply + bool m_replied; + + // true iff the reply has sent its last message + bool m_done; + + // the data to send and its type and format + CString m_data; + Atom m_type; + int m_format; + + // index of next byte in m_data to send + UInt32 m_ptr; + }; + typedef std::list CReplyList; + typedef std::map CReplyMap; + typedef std::map CReplyEventMask; + + // ICCCM interoperability methods + void icccmFillCache(); + bool icccmGetSelection(Atom target, + Atom* actualTarget, CString* data) const; + Time icccmGetTime() const; + + // motif interoperability methods + bool motifLockClipboard() const; + void motifUnlockClipboard() const; + bool motifOwnsClipboard() const; + void motifFillCache(); + bool motifGetSelection(const CMotifClipFormat*, + Atom* actualTarget, CString* data) const; + Time motifGetTime() const; + + // reply methods + bool insertMultipleReply(Window, ::Time, Atom); + void insertReply(CReply*); + void pushReplies(); + void pushReplies(CReplyMap::iterator&, + CReplyList&, CReplyList::iterator); + bool sendReply(CReply*); + void clearReplies(); + void clearReplies(CReplyList&); + void sendNotify(Window requestor, Atom selection, + Atom target, Atom property, Time time); + bool wasOwnedAtTime(::Time) const; + + // data conversion methods + Atom getTargetsData(CString&, int* format) const; + Atom getTimestampData(CString&, int* format) const; + +private: + typedef std::vector ConverterList; + + Display* m_display; + Window m_window; + ClipboardID m_id; + Atom m_selection; + mutable bool m_open; + mutable Time m_time; + bool m_owner; + mutable Time m_timeOwned; + Time m_timeLost; + + // true iff open and clipboard owned by a motif app + mutable bool m_motif; + + // the added/cached clipboard data + mutable bool m_checkCache; + bool m_cached; + Time m_cacheTime; + bool m_added[kNumFormats]; + CString m_data[kNumFormats]; + + // conversion request replies + CReplyMap m_replies; + CReplyEventMask m_eventMasks; + + // clipboard format converters + ConverterList m_converters; + + // atoms we'll need + Atom m_atomTargets; + Atom m_atomMultiple; + Atom m_atomTimestamp; + Atom m_atomInteger; + Atom m_atomAtom; + Atom m_atomAtomPair; + Atom m_atomData; + Atom m_atomINCR; + Atom m_atomMotifClipLock; + Atom m_atomMotifClipHeader; + Atom m_atomMotifClipAccess; + Atom m_atomGDKSelection; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all X11 clipboard format +converters. +*/ +class IXWindowsClipboardConverter : public IInterface { +public: + //! @name accessors + //@{ + + //! Get clipboard format + /*! + Return the clipboard format this object converts from/to. + */ + virtual IClipboard::EFormat + getFormat() const = 0; + + //! Get X11 format atom + /*! + Return the atom representing the X selection format that + this object converts from/to. + */ + virtual Atom getAtom() const = 0; + + //! Get X11 property datum size + /*! + Return the size (in bits) of data elements returned by + toIClipboard(). + */ + virtual int getDataSize() const = 0; + + //! Convert from IClipboard format + /*! + Convert from the IClipboard format to the X selection format. + The input data must be in the IClipboard format returned by + getFormat(). The return data will be in the X selection + format returned by getAtom(). + */ + virtual CString fromIClipboard(const CString&) const = 0; + + //! Convert to IClipboard format + /*! + Convert from the X selection format to the IClipboard format + (i.e., the reverse of fromIClipboard()). + */ + virtual CString toIClipboard(const CString&) const = 0; + + //@} +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp b/lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp new file mode 100644 index 00000000..ab6afae2 --- /dev/null +++ b/lib/platform/CXWindowsClipboardAnyBitmapConverter.cpp @@ -0,0 +1,187 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboardAnyBitmapConverter.h" + +// BMP info header structure +struct CBMPInfoHeader { +public: + UInt32 biSize; + SInt32 biWidth; + SInt32 biHeight; + UInt16 biPlanes; + UInt16 biBitCount; + UInt32 biCompression; + UInt32 biSizeImage; + SInt32 biXPelsPerMeter; + SInt32 biYPelsPerMeter; + UInt32 biClrUsed; + UInt32 biClrImportant; +}; + +// BMP is little-endian + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, SInt32 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst[2] = static_cast((src >> 16) & 0xffu); + dst[3] = static_cast((src >> 24) & 0xffu); + dst += 4; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst[2] = static_cast((src >> 16) & 0xffu); + dst[3] = static_cast((src >> 24) & 0xffu); + dst += 4; +} + +static inline +UInt16 +fromLEU16(const UInt8* data) +{ + return static_cast(data[0]) | + (static_cast(data[1]) << 8); +} + +static inline +SInt32 +fromLES32(const UInt8* data) +{ + return static_cast(static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24)); +} + +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24); +} + + +// +// CXWindowsClipboardAnyBitmapConverter +// + +CXWindowsClipboardAnyBitmapConverter::CXWindowsClipboardAnyBitmapConverter() +{ + // do nothing +} + +CXWindowsClipboardAnyBitmapConverter::~CXWindowsClipboardAnyBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardAnyBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +int +CXWindowsClipboardAnyBitmapConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardAnyBitmapConverter::fromIClipboard(const CString& bmp) const +{ + // fill BMP info header with native-endian data + CBMPInfoHeader infoHeader; + const UInt8* rawBMPInfoHeader = reinterpret_cast(bmp.data()); + infoHeader.biSize = fromLEU32(rawBMPInfoHeader + 0); + infoHeader.biWidth = fromLES32(rawBMPInfoHeader + 4); + infoHeader.biHeight = fromLES32(rawBMPInfoHeader + 8); + infoHeader.biPlanes = fromLEU16(rawBMPInfoHeader + 12); + infoHeader.biBitCount = fromLEU16(rawBMPInfoHeader + 14); + infoHeader.biCompression = fromLEU32(rawBMPInfoHeader + 16); + infoHeader.biSizeImage = fromLEU32(rawBMPInfoHeader + 20); + infoHeader.biXPelsPerMeter = fromLES32(rawBMPInfoHeader + 24); + infoHeader.biYPelsPerMeter = fromLES32(rawBMPInfoHeader + 28); + infoHeader.biClrUsed = fromLEU32(rawBMPInfoHeader + 32); + infoHeader.biClrImportant = fromLEU32(rawBMPInfoHeader + 36); + + // check that format is acceptable + if (infoHeader.biSize != 40 || + infoHeader.biWidth == 0 || infoHeader.biHeight == 0 || + infoHeader.biPlanes != 0 || infoHeader.biCompression != 0 || + (infoHeader.biBitCount != 24 && infoHeader.biBitCount != 32)) { + return CString(); + } + + // convert to image format + const UInt8* rawBMPPixels = rawBMPInfoHeader + 40; + if (infoHeader.biBitCount == 24) { + return doBGRFromIClipboard(rawBMPPixels, + infoHeader.biWidth, infoHeader.biHeight); + } + else { + return doBGRAFromIClipboard(rawBMPPixels, + infoHeader.biWidth, infoHeader.biHeight); + } +} + +CString +CXWindowsClipboardAnyBitmapConverter::toIClipboard(const CString& image) const +{ + // convert to raw BMP data + UInt32 w, h, depth; + CString rawBMP = doToIClipboard(image, w, h, depth); + if (rawBMP.empty() || w == 0 || h == 0 || (depth != 24 && depth != 32)) { + return CString(); + } + + // fill BMP info header with little-endian data + UInt8 infoHeader[40]; + UInt8* dst = infoHeader; + toLE(dst, static_cast(40)); + toLE(dst, static_cast(w)); + toLE(dst, static_cast(h)); + toLE(dst, static_cast(1)); + toLE(dst, static_cast(depth)); + toLE(dst, static_cast(0)); // BI_RGB + toLE(dst, static_cast(image.size())); + toLE(dst, static_cast(2834)); // 72 dpi + toLE(dst, static_cast(2834)); // 72 dpi + toLE(dst, static_cast(0)); + toLE(dst, static_cast(0)); + + // construct image + return CString(reinterpret_cast(infoHeader), + sizeof(infoHeader)) + rawBMP; +} diff --git a/lib/platform/CXWindowsClipboardAnyBitmapConverter.h b/lib/platform/CXWindowsClipboardAnyBitmapConverter.h new file mode 100644 index 00000000..6a153422 --- /dev/null +++ b/lib/platform/CXWindowsClipboardAnyBitmapConverter.h @@ -0,0 +1,59 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDANYBITMAPCONVERTER_H +#define CXWINDOWSCLIPBOARDANYBITMAPCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from some text encoding +class CXWindowsClipboardAnyBitmapConverter : + public IXWindowsClipboardConverter { +public: + CXWindowsClipboardAnyBitmapConverter(); + virtual ~CXWindowsClipboardAnyBitmapConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const = 0; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +protected: + //! Convert from IClipboard format + /*! + Convert raw BGR pixel data to another image format. + */ + virtual CString doBGRFromIClipboard(const UInt8* bgrData, + UInt32 w, UInt32 h) const = 0; + + //! Convert from IClipboard format + /*! + Convert raw BGRA pixel data to another image format. + */ + virtual CString doBGRAFromIClipboard(const UInt8* bgrData, + UInt32 w, UInt32 h) const = 0; + + //! Convert to IClipboard format + /*! + Convert an image into raw BGR or BGRA image data and store the + width, height, and image depth (24 or 32). + */ + virtual CString doToIClipboard(const CString&, + UInt32& w, UInt32& h, UInt32& depth) const = 0; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardBMPConverter.cpp b/lib/platform/CXWindowsClipboardBMPConverter.cpp new file mode 100644 index 00000000..747d6850 --- /dev/null +++ b/lib/platform/CXWindowsClipboardBMPConverter.cpp @@ -0,0 +1,139 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboardBMPConverter.h" + +// BMP file header structure +struct CBMPHeader { +public: + UInt16 type; + UInt32 size; + UInt16 reserved1; + UInt16 reserved2; + UInt32 offset; +}; + +// BMP is little-endian +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast(data[0]) | + (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | + (static_cast(data[3]) << 24); +} + +static +void +toLE(UInt8*& dst, char src) +{ + dst[0] = static_cast(src); + dst += 1; +} + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast(src & 0xffu); + dst[1] = static_cast((src >> 8) & 0xffu); + dst[2] = static_cast((src >> 16) & 0xffu); + dst[3] = static_cast((src >> 24) & 0xffu); + dst += 4; +} + +// +// CXWindowsClipboardBMPConverter +// + +CXWindowsClipboardBMPConverter::CXWindowsClipboardBMPConverter( + Display* display) : + m_atom(XInternAtom(display, "image/bmp", False)) +{ + // do nothing +} + +CXWindowsClipboardBMPConverter::~CXWindowsClipboardBMPConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardBMPConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +Atom +CXWindowsClipboardBMPConverter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardBMPConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardBMPConverter::fromIClipboard(const CString& bmp) const +{ + // create BMP image + UInt8 header[14]; + UInt8* dst = header; + toLE(dst, 'B'); + toLE(dst, 'M'); + toLE(dst, static_cast(14 + bmp.size())); + toLE(dst, static_cast(0)); + toLE(dst, static_cast(0)); + toLE(dst, static_cast(14 + 40)); + return CString(reinterpret_cast(header), 14) + bmp; +} + +CString +CXWindowsClipboardBMPConverter::toIClipboard(const CString& bmp) const +{ + // make sure data is big enough for a BMP file + if (bmp.size() <= 14 + 40) { + return CString(); + } + + // check BMP file header + const UInt8* rawBMPHeader = reinterpret_cast(bmp.data()); + if (rawBMPHeader[0] != 'B' || rawBMPHeader[1] != 'M') { + return CString(); + } + + // get offset to image data + UInt32 offset = fromLEU32(rawBMPHeader + 10); + + // construct BMP + if (offset == 14 + 40) { + return bmp.substr(14); + } + else { + return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset); + } +} diff --git a/lib/platform/CXWindowsClipboardBMPConverter.h b/lib/platform/CXWindowsClipboardBMPConverter.h new file mode 100644 index 00000000..a7d44549 --- /dev/null +++ b/lib/platform/CXWindowsClipboardBMPConverter.h @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDBMPCONVERTER_H +#define CXWINDOWSCLIPBOARDBMPCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from some text encoding +class CXWindowsClipboardBMPConverter : + public IXWindowsClipboardConverter { +public: + CXWindowsClipboardBMPConverter(Display* display); + virtual ~CXWindowsClipboardBMPConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardHTMLConverter.cpp b/lib/platform/CXWindowsClipboardHTMLConverter.cpp new file mode 100644 index 00000000..fafca54e --- /dev/null +++ b/lib/platform/CXWindowsClipboardHTMLConverter.cpp @@ -0,0 +1,62 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboardHTMLConverter.h" +#include "CUnicode.h" + +// +// CXWindowsClipboardHTMLConverter +// + +CXWindowsClipboardHTMLConverter::CXWindowsClipboardHTMLConverter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardHTMLConverter::~CXWindowsClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +Atom +CXWindowsClipboardHTMLConverter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardHTMLConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardHTMLConverter::fromIClipboard(const CString& data) const +{ + return CUnicode::UTF8ToUTF16(data); +} + +CString +CXWindowsClipboardHTMLConverter::toIClipboard(const CString& data) const +{ + return CUnicode::UTF16ToUTF8(data); +} diff --git a/lib/platform/CXWindowsClipboardHTMLConverter.h b/lib/platform/CXWindowsClipboardHTMLConverter.h new file mode 100644 index 00000000..7f761f20 --- /dev/null +++ b/lib/platform/CXWindowsClipboardHTMLConverter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDHTMLCONVERTER_H +#define CXWINDOWSCLIPBOARDHTMLCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from HTML encoding +class CXWindowsClipboardHTMLConverter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardHTMLConverter(Display* display, const char* name); + virtual ~CXWindowsClipboardHTMLConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardTextConverter.cpp b/lib/platform/CXWindowsClipboardTextConverter.cpp new file mode 100644 index 00000000..bd1a520c --- /dev/null +++ b/lib/platform/CXWindowsClipboardTextConverter.cpp @@ -0,0 +1,74 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboardTextConverter.h" +#include "CUnicode.h" + +// +// CXWindowsClipboardTextConverter +// + +CXWindowsClipboardTextConverter::CXWindowsClipboardTextConverter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardTextConverter::~CXWindowsClipboardTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +CXWindowsClipboardTextConverter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardTextConverter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardTextConverter::fromIClipboard(const CString& data) const +{ + return CUnicode::UTF8ToText(data); +} + +CString +CXWindowsClipboardTextConverter::toIClipboard(const CString& data) const +{ + // convert to UTF-8 + bool errors; + CString utf8 = CUnicode::textToUTF8(data, &errors); + + // if there were decoding errors then, to support old applications + // that don't understand UTF-8 but can report the exact binary + // UTF-8 representation, see if the data appears to be UTF-8. if + // so then use it as is. + if (errors && CUnicode::isUTF8(data)) { + return data; + } + + return utf8; +} diff --git a/lib/platform/CXWindowsClipboardTextConverter.h b/lib/platform/CXWindowsClipboardTextConverter.h new file mode 100644 index 00000000..3840b7df --- /dev/null +++ b/lib/platform/CXWindowsClipboardTextConverter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDTEXTCONVERTER_H +#define CXWINDOWSCLIPBOARDTEXTCONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from locale text encoding +class CXWindowsClipboardTextConverter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardTextConverter(Display* display, const char* name); + virtual ~CXWindowsClipboardTextConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardUCS2Converter.cpp b/lib/platform/CXWindowsClipboardUCS2Converter.cpp new file mode 100644 index 00000000..86b8d13f --- /dev/null +++ b/lib/platform/CXWindowsClipboardUCS2Converter.cpp @@ -0,0 +1,62 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboardUCS2Converter.h" +#include "CUnicode.h" + +// +// CXWindowsClipboardUCS2Converter +// + +CXWindowsClipboardUCS2Converter::CXWindowsClipboardUCS2Converter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardUCS2Converter::~CXWindowsClipboardUCS2Converter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardUCS2Converter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +CXWindowsClipboardUCS2Converter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardUCS2Converter::getDataSize() const +{ + return 16; +} + +CString +CXWindowsClipboardUCS2Converter::fromIClipboard(const CString& data) const +{ + return CUnicode::UTF8ToUCS2(data); +} + +CString +CXWindowsClipboardUCS2Converter::toIClipboard(const CString& data) const +{ + return CUnicode::UCS2ToUTF8(data); +} diff --git a/lib/platform/CXWindowsClipboardUCS2Converter.h b/lib/platform/CXWindowsClipboardUCS2Converter.h new file mode 100644 index 00000000..e848e302 --- /dev/null +++ b/lib/platform/CXWindowsClipboardUCS2Converter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDUCS2CONVERTER_H +#define CXWINDOWSCLIPBOARDUCS2CONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from UCS-2 encoding +class CXWindowsClipboardUCS2Converter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardUCS2Converter(Display* display, const char* name); + virtual ~CXWindowsClipboardUCS2Converter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsClipboardUTF8Converter.cpp b/lib/platform/CXWindowsClipboardUTF8Converter.cpp new file mode 100644 index 00000000..7edc850f --- /dev/null +++ b/lib/platform/CXWindowsClipboardUTF8Converter.cpp @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsClipboardUTF8Converter.h" + +// +// CXWindowsClipboardUTF8Converter +// + +CXWindowsClipboardUTF8Converter::CXWindowsClipboardUTF8Converter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +CXWindowsClipboardUTF8Converter::~CXWindowsClipboardUTF8Converter() +{ + // do nothing +} + +IClipboard::EFormat +CXWindowsClipboardUTF8Converter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +CXWindowsClipboardUTF8Converter::getAtom() const +{ + return m_atom; +} + +int +CXWindowsClipboardUTF8Converter::getDataSize() const +{ + return 8; +} + +CString +CXWindowsClipboardUTF8Converter::fromIClipboard(const CString& data) const +{ + return data; +} + +CString +CXWindowsClipboardUTF8Converter::toIClipboard(const CString& data) const +{ + return data; +} diff --git a/lib/platform/CXWindowsClipboardUTF8Converter.h b/lib/platform/CXWindowsClipboardUTF8Converter.h new file mode 100644 index 00000000..5ac8b153 --- /dev/null +++ b/lib/platform/CXWindowsClipboardUTF8Converter.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSCLIPBOARDUTF8CONVERTER_H +#define CXWINDOWSCLIPBOARDUTF8CONVERTER_H + +#include "CXWindowsClipboard.h" + +//! Convert to/from UTF-8 encoding +class CXWindowsClipboardUTF8Converter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + CXWindowsClipboardUTF8Converter(Display* display, const char* name); + virtual ~CXWindowsClipboardUTF8Converter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual CString fromIClipboard(const CString&) const; + virtual CString toIClipboard(const CString&) const; + +private: + Atom m_atom; +}; + +#endif diff --git a/lib/platform/CXWindowsEventQueueBuffer.cpp b/lib/platform/CXWindowsEventQueueBuffer.cpp new file mode 100644 index 00000000..170e5bc6 --- /dev/null +++ b/lib/platform/CXWindowsEventQueueBuffer.cpp @@ -0,0 +1,208 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsEventQueueBuffer.h" +#include "CLock.h" +#include "CThread.h" +#include "CEvent.h" +#include "IEventQueue.h" +#if HAVE_POLL +# include +#else +# if HAVE_SYS_SELECT_H +# include +# endif +# if HAVE_SYS_TIME_H +# include +# endif +# if HAVE_SYS_TYPES_H +# include +# endif +# if HAVE_UNISTD_H +# include +# endif +#endif + +// +// CEventQueueTimer +// + +class CEventQueueTimer { }; + + +// +// CXWindowsEventQueueBuffer +// + +CXWindowsEventQueueBuffer::CXWindowsEventQueueBuffer( + Display* display, Window window) : + m_display(display), + m_window(window), + m_waiting(false) +{ + assert(m_display != NULL); + assert(m_window != None); + + m_userEvent = XInternAtom(m_display, "SYNERGY_USER_EVENT", False); +} + +CXWindowsEventQueueBuffer::~CXWindowsEventQueueBuffer() +{ + // do nothing +} + +void +CXWindowsEventQueueBuffer::waitForEvent(double dtimeout) +{ + CThread::testCancel(); + + { + CLock lock(&m_mutex); + // we're now waiting for events + m_waiting = true; + + // push out pending events + flush(); + } + + // use poll() to wait for a message from the X server or for timeout. + // this is a good deal more efficient than polling and sleeping. +#if HAVE_POLL + struct pollfd pfds[1]; + pfds[0].fd = ConnectionNumber(m_display); + pfds[0].events = POLLIN; + int timeout = (dtimeout < 0.0) ? -1 : + static_cast(1000.0 * dtimeout); +#else + struct timeval timeout; + struct timeval* timeoutPtr; + if (dtimeout < 0.0) { + timeoutPtr = NULL; + } + else { + timeout.tv_sec = static_cast(dtimeout); + timeout.tv_usec = static_cast(1.0e+6 * + (dtimeout - timeout.tv_sec)); + timeoutPtr = &timeout; + } + + // initialize file descriptor sets + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(ConnectionNumber(m_display), &rfds); +#endif + + // wait for message from X server or for timeout. also check + // if the thread has been cancelled. poll() should return -1 + // with EINTR when the thread is cancelled. +#if HAVE_POLL + poll(pfds, 1, timeout); +#else + select(ConnectionNumber(m_display) + 1, + SELECT_TYPE_ARG234 &rfds, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 timeoutPtr); +#endif + + { + // we're no longer waiting for events + CLock lock(&m_mutex); + m_waiting = false; + } + + CThread::testCancel(); +} + +IEventQueueBuffer::Type +CXWindowsEventQueueBuffer::getEvent(CEvent& event, UInt32& dataID) +{ + CLock lock(&m_mutex); + + // push out pending events + flush(); + + // get next event + XNextEvent(m_display, &m_event); + + // process event + if (m_event.xany.type == ClientMessage && + m_event.xclient.message_type == m_userEvent) { + dataID = static_cast(m_event.xclient.data.l[0]); + return kUser; + } + else { + event = CEvent(CEvent::kSystem, + IEventQueue::getSystemTarget(), &m_event); + return kSystem; + } +} + +bool +CXWindowsEventQueueBuffer::addEvent(UInt32 dataID) +{ + // prepare a message + XEvent xevent; + xevent.xclient.type = ClientMessage; + xevent.xclient.window = m_window; + xevent.xclient.message_type = m_userEvent; + xevent.xclient.format = 32; + xevent.xclient.data.l[0] = static_cast(dataID); + + // save the message + CLock lock(&m_mutex); + m_postedEvents.push_back(xevent); + + // if we're currently waiting for an event then send saved events to + // the X server now. if we're not waiting then some other thread + // might be using the display connection so we can't safely use it + // too. + if (m_waiting) { + flush(); + } + + return true; +} + +bool +CXWindowsEventQueueBuffer::isEmpty() const +{ + CLock lock(&m_mutex); + return (XPending(m_display) == 0); +} + +CEventQueueTimer* +CXWindowsEventQueueBuffer::newTimer(double, bool) const +{ + return new CEventQueueTimer; +} + +void +CXWindowsEventQueueBuffer::deleteTimer(CEventQueueTimer* timer) const +{ + delete timer; +} + +void +CXWindowsEventQueueBuffer::flush() +{ + // note -- m_mutex must be locked on entry + + // flush the posted event list to the X server + for (size_t i = 0; i < m_postedEvents.size(); ++i) { + XSendEvent(m_display, m_window, False, 0, &m_postedEvents[i]); + } + XFlush(m_display); + m_postedEvents.clear(); +} diff --git a/lib/platform/CXWindowsEventQueueBuffer.h b/lib/platform/CXWindowsEventQueueBuffer.h new file mode 100644 index 00000000..5d9b6dd4 --- /dev/null +++ b/lib/platform/CXWindowsEventQueueBuffer.h @@ -0,0 +1,57 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSEVENTQUEUEBUFFER_H +#define CXWINDOWSEVENTQUEUEBUFFER_H + +#include "IEventQueueBuffer.h" +#include "CMutex.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +//! Event queue buffer for X11 +class CXWindowsEventQueueBuffer : public IEventQueueBuffer { +public: + CXWindowsEventQueueBuffer(Display*, Window); + virtual ~CXWindowsEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void waitForEvent(double timeout); + virtual Type getEvent(CEvent& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual CEventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(CEventQueueTimer*) const; + +private: + void flush(); + +private: + typedef std::vector CEventList; + + CMutex m_mutex; + Display* m_display; + Window m_window; + Atom m_userEvent; + XEvent m_event; + CEventList m_postedEvents; + bool m_waiting; +}; + +#endif diff --git a/lib/platform/CXWindowsKeyState.cpp b/lib/platform/CXWindowsKeyState.cpp new file mode 100644 index 00000000..9f98ded4 --- /dev/null +++ b/lib/platform/CXWindowsKeyState.cpp @@ -0,0 +1,826 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsKeyState.h" +#include "CXWindowsUtil.h" +#include "CLog.h" +#include "CStringUtil.h" +#include "stdmap.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +# include +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include +#if HAVE_XKB_EXTENSION +# include +#endif +#endif + +CXWindowsKeyState::CXWindowsKeyState(Display* display, bool useXKB) : + m_display(display) +{ + XGetKeyboardControl(m_display, &m_keyboardState); +#if HAVE_XKB_EXTENSION + if (useXKB) { + m_xkb = XkbGetMap(m_display, XkbKeyActionsMask | XkbKeyBehaviorsMask | + XkbAllClientInfoMask, XkbUseCoreKbd); + } + else { + m_xkb = NULL; + } +#endif + setActiveGroup(kGroupPollAndSet); +} + +CXWindowsKeyState::~CXWindowsKeyState() +{ +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbFreeKeyboard(m_xkb, 0, True); + } +#endif +} + +void +CXWindowsKeyState::setActiveGroup(SInt32 group) +{ + if (group == kGroupPollAndSet) { + m_group = -1; + m_group = pollActiveGroup(); + } + else if (group == kGroupPoll) { + m_group = -1; + } + else { + assert(group >= 0); + m_group = group; + } +} + +void +CXWindowsKeyState::setAutoRepeat(const XKeyboardState& state) +{ + m_keyboardState = state; +} + +KeyModifierMask +CXWindowsKeyState::mapModifiersFromX(unsigned int state) const +{ + UInt32 offset = 8 * getGroupFromState(state); + KeyModifierMask mask = 0; + for (int i = 0; i < 8; ++i) { + if ((state & (1u << i)) != 0) { + mask |= m_modifierFromX[offset + i]; + } + } + return mask; +} + +bool +CXWindowsKeyState::mapModifiersToX(KeyModifierMask mask, + unsigned int& modifiers) const +{ + modifiers = 0; + + for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) { + KeyModifierMask bit = (1u << i); + if ((mask & bit) != 0) { + KeyModifierToXMask::const_iterator j = m_modifierToX.find(bit); + if (j == m_modifierToX.end()) { + return false; + } + else { + modifiers |= j->second; + } + } + } + + return true; +} + +void +CXWindowsKeyState::mapKeyToKeycodes(KeyID key, CKeycodeList& keycodes) const +{ + keycodes.clear(); + std::pair range = + m_keyCodeFromKey.equal_range(key); + for (KeyToKeyCodeMap::const_iterator i = range.first; + i != range.second; ++i) { + keycodes.push_back(i->second); + } +} + +bool +CXWindowsKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + +KeyModifierMask +CXWindowsKeyState::pollActiveModifiers() const +{ + Window root = DefaultRootWindow(m_display), window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (!XQueryPointer(m_display, root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + state = 0; + } + return mapModifiersFromX(state); +} + +SInt32 +CXWindowsKeyState::pollActiveGroup() const +{ + if (m_group != -1) { + assert(m_group >= 0); + return m_group; + } + +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbStateRec state; + if (XkbGetState(m_display, XkbUseCoreKbd, &state)) { + return state.group; + } + } +#endif + return 0; +} + +void +CXWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + char keys[32]; + XQueryKeymap(m_display, keys); + for (UInt32 i = 0; i < 32; ++i) { + for (UInt32 j = 0; j < 8; ++j) { + if ((keys[i] & (1u << j)) != 0) { + pressedKeys.insert(8 * i + j); + } + } + } +} + +void +CXWindowsKeyState::getKeyMap(CKeyMap& keyMap) +{ + // get autorepeat info. we must use the global_auto_repeat told to + // us because it may have modified by synergy. + int oldGlobalAutoRepeat = m_keyboardState.global_auto_repeat; + XGetKeyboardControl(m_display, &m_keyboardState); + m_keyboardState.global_auto_repeat = oldGlobalAutoRepeat; + +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbGetUpdatedMap(m_display, XkbKeyActionsMask | XkbKeyBehaviorsMask | + XkbAllClientInfoMask, m_xkb); + updateKeysymMapXKB(keyMap); + } + else +#endif + { + updateKeysymMap(keyMap); + } +} + +void +CXWindowsKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + if (keystroke.m_data.m_button.m_repeat) { + int c = keystroke.m_data.m_button.m_button; + int i = (c >> 3); + int b = 1 << (c & 7); + if (m_keyboardState.global_auto_repeat == AutoRepeatModeOff || + (m_keyboardState.auto_repeats[i] & b) == 0) { + LOG((CLOG_DEBUG1 " discard autorepeat")); + break; + } + } + XTestFakeKeyEvent(m_display, keystroke.m_data.m_button.m_button, + keystroke.m_data.m_button.m_press ? True : False, + CurrentTime); + break; + + case Keystroke::kGroup: + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbLockGroup(m_display, XkbUseCoreKbd, + keystroke.m_data.m_group.m_group); + } + else +#endif + { + LOG((CLOG_DEBUG1 " ignored")); + } + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbLockGroup(m_display, XkbUseCoreKbd, + getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + else +#endif + { + LOG((CLOG_DEBUG1 " ignored")); + } + } + break; + } + XFlush(m_display); +} + +void +CXWindowsKeyState::updateKeysymMap(CKeyMap& keyMap) +{ + // there are up to 4 keysyms per keycode + static const int maxKeysyms = 4; + + LOG((CLOG_DEBUG1 "non-XKB mapping")); + + // prepare map from X modifier to KeyModifierMask. certain bits + // are predefined. + m_modifierFromX.clear(); + m_modifierFromX.resize(8); + m_modifierFromX[ShiftMapIndex] = KeyModifierShift; + m_modifierFromX[LockMapIndex] = KeyModifierCapsLock; + m_modifierFromX[ControlMapIndex] = KeyModifierControl; + m_modifierToX.clear(); + m_modifierToX[KeyModifierShift] = ShiftMask; + m_modifierToX[KeyModifierCapsLock] = LockMask; + m_modifierToX[KeyModifierControl] = ControlMask; + + // prepare map from KeyID to KeyCode + m_keyCodeFromKey.clear(); + + // get the number of keycodes + int minKeycode, maxKeycode; + XDisplayKeycodes(m_display, &minKeycode, &maxKeycode); + int numKeycodes = maxKeycode - minKeycode + 1; + + // get the keyboard mapping for all keys + int keysymsPerKeycode; + KeySym* allKeysyms = XGetKeyboardMapping(m_display, + minKeycode, numKeycodes, + &keysymsPerKeycode); + + // it's more convenient to always have maxKeysyms KeySyms per key + { + KeySym* tmpKeysyms = new KeySym[maxKeysyms * numKeycodes]; + for (int i = 0; i < numKeycodes; ++i) { + for (int j = 0; j < maxKeysyms; ++j) { + if (j < keysymsPerKeycode) { + tmpKeysyms[maxKeysyms * i + j] = + allKeysyms[keysymsPerKeycode * i + j]; + } + else { + tmpKeysyms[maxKeysyms * i + j] = NoSymbol; + } + } + } + XFree(allKeysyms); + allKeysyms = tmpKeysyms; + } + + // get the buttons assigned to modifiers. X11 does not predefine + // the meaning of any modifiers except shift, caps lock, and the + // control key. the meaning of a modifier bit (other than those) + // depends entirely on the KeySyms mapped to that bit. unfortunately + // you cannot map a bit back to the KeySym used to produce it. + // for example, let's say button 1 maps to Alt_L without shift and + // Meta_L with shift. now if mod1 is mapped to button 1 that could + // mean the user used Alt or Meta to turn on that modifier and there's + // no way to know which. it's also possible for one button to be + // mapped to multiple bits so both mod1 and mod2 could be generated + // by button 1. + // + // we're going to ignore any modifier for a button except the first. + // with the above example, that means we'll ignore the mod2 modifier + // bit unless it's also mapped to some other button. we're also + // going to ignore all KeySyms except the first modifier KeySym, + // which means button 1 above won't map to Meta, just Alt. + std::map modifierButtons; + XModifierKeymap* modifiers = XGetModifierMapping(m_display); + for (unsigned int i = 0; i < 8; ++i) { + const KeyCode* buttons = + modifiers->modifiermap + i * modifiers->max_keypermod; + for (int j = 0; j < modifiers->max_keypermod; ++j) { + modifierButtons.insert(std::make_pair(buttons[j], i)); + } + } + XFreeModifiermap(modifiers); + modifierButtons.erase(0); + + // Hack to deal with VMware. When a VMware client grabs input the + // player clears out the X modifier map for whatever reason. We're + // notified of the change and arrive here to discover that there + // are no modifiers at all. Since this prevents the modifiers from + // working in the VMware client we'll use the last known good set + // of modifiers when there are no modifiers. If there are modifiers + // we update the last known good set. + if (!modifierButtons.empty()) { + m_lastGoodNonXKBModifiers = modifierButtons; + } + else { + modifierButtons = m_lastGoodNonXKBModifiers; + } + + // add entries for each keycode + CKeyMap::KeyItem item; + for (int i = 0; i < numKeycodes; ++i) { + KeySym* keysyms = allKeysyms + maxKeysyms * i; + KeyCode keycode = static_cast(i + minKeycode); + item.m_button = static_cast(keycode); + item.m_client = 0; + + // determine modifier sensitivity + item.m_sensitive = 0; + + // if the keysyms in levels 2 or 3 exist and differ from levels + // 0 and 1 then the key is sensitive AltGr (Mode_switch) + if ((keysyms[2] != NoSymbol && keysyms[2] != keysyms[0]) || + (keysyms[3] != NoSymbol && keysyms[2] != keysyms[1])) { + item.m_sensitive |= KeyModifierAltGr; + } + + // check if the key is caps-lock sensitive. some systems only + // provide one keysym for keys sensitive to caps-lock. if we + // find that then fill in the missing keysym. + if (keysyms[0] != NoSymbol && keysyms[1] == NoSymbol && + keysyms[2] == NoSymbol && keysyms[3] == NoSymbol) { + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[0], &lKeysym, &uKeysym); + if (lKeysym != uKeysym) { + keysyms[0] = lKeysym; + keysyms[1] = uKeysym; + item.m_sensitive |= KeyModifierCapsLock; + } + } + else if (keysyms[0] != NoSymbol && keysyms[1] != NoSymbol) { + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[0], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[0] && + uKeysym == keysyms[1]) { + item.m_sensitive |= KeyModifierCapsLock; + } + else if (keysyms[2] != NoSymbol && keysyms[3] != NoSymbol) { + XConvertCase(keysyms[2], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[2] && + uKeysym == keysyms[3]) { + item.m_sensitive |= KeyModifierCapsLock; + } + } + } + + // key is sensitive to shift if keysyms in levels 0 and 1 or + // levels 2 and 3 don't match. it's also sensitive to shift + // if it's sensitive to caps-lock. + if ((item.m_sensitive & KeyModifierCapsLock) != 0) { + item.m_sensitive |= KeyModifierShift; + } + else if ((keysyms[0] != NoSymbol && keysyms[1] != NoSymbol && + keysyms[0] != keysyms[1]) || + (keysyms[2] != NoSymbol && keysyms[3] != NoSymbol && + keysyms[2] != keysyms[3])) { + item.m_sensitive |= KeyModifierShift; + } + + // key is sensitive to numlock if any keysym on it is + if (IsKeypadKey(keysyms[0]) || IsPrivateKeypadKey(keysyms[0]) || + IsKeypadKey(keysyms[1]) || IsPrivateKeypadKey(keysyms[1]) || + IsKeypadKey(keysyms[2]) || IsPrivateKeypadKey(keysyms[2]) || + IsKeypadKey(keysyms[3]) || IsPrivateKeypadKey(keysyms[3])) { + item.m_sensitive |= KeyModifierNumLock; + } + + // do each keysym (shift level) + for (int j = 0; j < maxKeysyms; ++j) { + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysyms[j]); + if (item.m_id == kKeyNone) { + if (j != 0 && modifierButtons.count(keycode) > 0) { + // pretend the modifier works in other shift levels + // because it probably does. + if (keysyms[1] == NoSymbol || j != 3) { + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysyms[0]); + } + else { + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysyms[1]); + } + } + if (item.m_id == kKeyNone) { + continue; + } + } + + // group is 0 for levels 0 and 1 and 1 for levels 2 and 3 + item.m_group = (j >= 2) ? 1 : 0; + + // compute required modifiers + item.m_required = 0; + if ((j & 1) != 0) { + item.m_required |= KeyModifierShift; + } + if ((j & 2) != 0) { + item.m_required |= KeyModifierAltGr; + } + + item.m_generates = 0; + item.m_lock = false; + if (modifierButtons.count(keycode) > 0) { + // get flags for modifier keys + CKeyMap::initModifierKey(item); + + // add mapping from X (unless we already have) + if (item.m_generates != 0) { + unsigned int bit = modifierButtons[keycode]; + if (m_modifierFromX[bit] == 0) { + m_modifierFromX[bit] = item.m_generates; + m_modifierToX[item.m_generates] = (1u << bit); + } + } + } + + // add key + keyMap.addKeyEntry(item); + m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode)); + + // add other ways to synthesize the key + if ((j & 1) != 0) { + // add capslock version of key is sensitive to capslock + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[j], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[j - 1] && + uKeysym == keysyms[j]) { + item.m_required &= ~KeyModifierShift; + item.m_required |= KeyModifierCapsLock; + keyMap.addKeyEntry(item); + item.m_required |= KeyModifierShift; + item.m_required &= ~KeyModifierCapsLock; + } + + // add numlock version of key if sensitive to numlock + if (IsKeypadKey(keysyms[j]) || IsPrivateKeypadKey(keysyms[j])) { + item.m_required &= ~KeyModifierShift; + item.m_required |= KeyModifierNumLock; + keyMap.addKeyEntry(item); + item.m_required |= KeyModifierShift; + item.m_required &= ~KeyModifierNumLock; + } + } + } + } + + delete[] allKeysyms; +} + +#if HAVE_XKB_EXTENSION +void +CXWindowsKeyState::updateKeysymMapXKB(CKeyMap& keyMap) +{ + static const XkbKTMapEntryRec defMapEntry = { + True, // active + 0, // level + { + 0, // mods.mask + 0, // mods.real_mods + 0 // mods.vmods + } + }; + + LOG((CLOG_DEBUG1 "XKB mapping")); + + // find the number of groups + int maxNumGroups = 0; + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + int numGroups = XkbKeyNumGroups(m_xkb, static_cast(i)); + if (numGroups > maxNumGroups) { + maxNumGroups = numGroups; + } + } + + // prepare map from X modifier to KeyModifierMask + std::vector modifierLevel(maxNumGroups * 8, 4); + m_modifierFromX.clear(); + m_modifierFromX.resize(maxNumGroups * 8); + m_modifierToX.clear(); + + // prepare map from KeyID to KeyCode + m_keyCodeFromKey.clear(); + + // Hack to deal with VMware. When a VMware client grabs input the + // player clears out the X modifier map for whatever reason. We're + // notified of the change and arrive here to discover that there + // are no modifiers at all. Since this prevents the modifiers from + // working in the VMware client we'll use the last known good set + // of modifiers when there are no modifiers. If there are modifiers + // we update the last known good set. + bool useLastGoodModifiers = !hasModifiersXKB(); + if (!useLastGoodModifiers) { + m_lastGoodXKBModifiers.clear(); + } + + // check every button. on this pass we save all modifiers as native + // X modifier masks. + CKeyMap::KeyItem item; + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + KeyCode keycode = static_cast(i); + item.m_button = static_cast(keycode); + item.m_client = 0; + + // skip keys with no groups (they generate no symbols) + if (XkbKeyNumGroups(m_xkb, keycode) == 0) { + continue; + } + + // note half-duplex keys + const XkbBehavior& b = m_xkb->server->behaviors[keycode]; + if ((b.type & XkbKB_OpMask) == XkbKB_Lock) { + keyMap.addHalfDuplexButton(item.m_button); + } + + // iterate over all groups + for (int group = 0; group < maxNumGroups; ++group) { + item.m_group = group; + int eGroup = getEffectiveGroup(keycode, group); + + // get key info + XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, keycode, eGroup); + + // set modifiers the item is sensitive to + item.m_sensitive = type->mods.mask; + + // iterate over all shift levels for the button (including none) + for (int j = -1; j < type->map_count; ++j) { + const XkbKTMapEntryRec* mapEntry = + ((j == -1) ? &defMapEntry : type->map + j); + if (!mapEntry->active) { + continue; + } + int level = mapEntry->level; + + // set required modifiers for this item + item.m_required = mapEntry->mods.mask; + if ((item.m_required & LockMask) != 0 && + j != -1 && type->preserve != NULL && + (type->preserve[j].mask & LockMask) != 0) { + // sensitive caps lock and we preserve caps-lock. + // preserving caps-lock means we Xlib functions would + // yield the capitialized KeySym so we'll adjust the + // level accordingly. + if ((level ^ 1) < type->num_levels) { + level ^= 1; + } + } + + // get the keysym for this item + KeySym keysym = XkbKeySymEntry(m_xkb, keycode, level, eGroup); + + // check for group change actions, locking modifiers, and + // modifier masks. + item.m_lock = false; + bool isModifier = false; + UInt32 modifierMask = m_xkb->map->modmap[keycode]; + if (XkbKeyHasActions(m_xkb, keycode)) { + XkbAction* action = + XkbKeyActionEntry(m_xkb, keycode, level, eGroup); + if (action->type == XkbSA_SetMods || + action->type == XkbSA_LockMods) { + isModifier = true; + + // note toggles + item.m_lock = (action->type == XkbSA_LockMods); + + // maybe use action's mask + if ((action->mods.flags & XkbSA_UseModMapMods) == 0) { + modifierMask = action->mods.mask; + } + } + else if (action->type == XkbSA_SetGroup || + action->type == XkbSA_LatchGroup || + action->type == XkbSA_LockGroup) { + // ignore group change key + continue; + } + } + level = mapEntry->level; + + // VMware modifier hack + if (useLastGoodModifiers) { + XKBModifierMap::const_iterator k = + m_lastGoodXKBModifiers.find(eGroup * 256 + keycode); + if (k != m_lastGoodXKBModifiers.end()) { + // Use last known good modifier + isModifier = true; + level = k->second.m_level; + modifierMask = k->second.m_mask; + item.m_lock = k->second.m_lock; + } + } + else if (isModifier) { + // Save known good modifier + XKBModifierInfo& info = + m_lastGoodXKBModifiers[eGroup * 256 + keycode]; + info.m_level = level; + info.m_mask = modifierMask; + info.m_lock = item.m_lock; + } + + // record the modifier mask for this key. don't bother + // for keys that change the group. + item.m_generates = 0; + UInt32 modifierBit = + CXWindowsUtil::getModifierBitForKeySym(keysym); + if (isModifier && modifierBit != kKeyModifierBitNone) { + item.m_generates = (1u << modifierBit); + for (SInt32 j = 0; j < 8; ++j) { + // skip modifiers this key doesn't generate + if ((modifierMask & (1u << j)) == 0) { + continue; + } + + // skip keys that map to a modifier that we've + // already seen using fewer modifiers. that is + // if this key must combine with other modifiers + // and we know of a key that combines with fewer + // modifiers (or no modifiers) then prefer the + // other key. + if (level >= modifierLevel[8 * group + j]) { + continue; + } + modifierLevel[8 * group + j] = level; + + // save modifier + m_modifierFromX[8 * group + j] |= (1u << modifierBit); + m_modifierToX.insert(std::make_pair( + 1u << modifierBit, 1u << j)); + } + } + + // handle special cases of just one keysym for the keycode + if (type->num_levels == 1) { + // if there are upper- and lowercase versions of the + // keysym then add both. + KeySym lKeysym, uKeysym; + XConvertCase(keysym, &lKeysym, &uKeysym); + if (lKeysym != uKeysym) { + if (j != -1) { + continue; + } + + item.m_sensitive |= ShiftMask | LockMask; + + KeyID lKeyID = CXWindowsUtil::mapKeySymToKeyID(lKeysym); + KeyID uKeyID = CXWindowsUtil::mapKeySymToKeyID(uKeysym); + if (lKeyID == kKeyNone || uKeyID == kKeyNone) { + continue; + } + + item.m_id = lKeyID; + item.m_required = 0; + keyMap.addKeyEntry(item); + + item.m_id = uKeyID; + item.m_required = ShiftMask; + keyMap.addKeyEntry(item); + item.m_required = LockMask; + keyMap.addKeyEntry(item); + + if (group == 0) { + m_keyCodeFromKey.insert( + std::make_pair(lKeyID, keycode)); + m_keyCodeFromKey.insert( + std::make_pair(uKeyID, keycode)); + } + continue; + } + } + + // add entry + item.m_id = CXWindowsUtil::mapKeySymToKeyID(keysym); + keyMap.addKeyEntry(item); + if (group == 0) { + m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode)); + } + } + } + } + + // change all modifier masks to synergy masks from X masks + keyMap.foreachKey(&CXWindowsKeyState::remapKeyModifiers, this); + + // allow composition across groups + keyMap.allowGroupSwitchDuringCompose(); +} +#endif + +void +CXWindowsKeyState::remapKeyModifiers(KeyID id, SInt32 group, + CKeyMap::KeyItem& item, void* vself) +{ + CXWindowsKeyState* self = reinterpret_cast(vself); + item.m_required = + self->mapModifiersFromX(XkbBuildCoreState(item.m_required, group)); + item.m_sensitive = + self->mapModifiersFromX(XkbBuildCoreState(item.m_sensitive, group)); +} + +bool +CXWindowsKeyState::hasModifiersXKB() const +{ +#if HAVE_XKB_EXTENSION + // iterate over all keycodes + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + KeyCode keycode = static_cast(i); + if (XkbKeyHasActions(m_xkb, keycode)) { + // iterate over all groups + int numGroups = XkbKeyNumGroups(m_xkb, keycode); + for (int group = 0; group < numGroups; ++group) { + // iterate over all shift levels for the button (including none) + XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, keycode, group); + for (int j = -1; j < type->map_count; ++j) { + if (j != -1 && !type->map[j].active) { + continue; + } + int level = ((j == -1) ? 0 : type->map[j].level); + XkbAction* action = + XkbKeyActionEntry(m_xkb, keycode, level, group); + if (action->type == XkbSA_SetMods || + action->type == XkbSA_LockMods) { + return true; + } + } + } + } + } +#endif + return false; +} + +int +CXWindowsKeyState::getEffectiveGroup(KeyCode keycode, int group) const +{ + (void)keycode; +#if HAVE_XKB_EXTENSION + // get effective group for key + int numGroups = XkbKeyNumGroups(m_xkb, keycode); + if (group >= numGroups) { + unsigned char groupInfo = XkbKeyGroupInfo(m_xkb, keycode); + switch (XkbOutOfRangeGroupAction(groupInfo)) { + case XkbClampIntoRange: + group = numGroups - 1; + break; + + case XkbRedirectIntoRange: + group = XkbOutOfRangeGroupNumber(groupInfo); + if (group >= numGroups) { + group = 0; + } + break; + + default: + // wrap + group %= numGroups; + break; + } + } +#endif + return group; +} + +UInt32 +CXWindowsKeyState::getGroupFromState(unsigned int state) const +{ +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + return XkbGroupForCoreState(state); + } +#endif + return 0; +} diff --git a/lib/platform/CXWindowsKeyState.h b/lib/platform/CXWindowsKeyState.h new file mode 100644 index 00000000..02ed4520 --- /dev/null +++ b/lib/platform/CXWindowsKeyState.h @@ -0,0 +1,155 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSKEYSTATE_H +#define CXWINDOWSKEYSTATE_H + +#include "CKeyState.h" +#include "stdmap.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +# if HAVE_X11_EXTENSIONS_XTEST_H +# include +# else +# error The XTest extension is required to build synergy +# endif +# if HAVE_XKB_EXTENSION +# include +# endif +#endif + +//! X Windows key state +/*! +A key state for X Windows. +*/ +class CXWindowsKeyState : public CKeyState { +public: + typedef std::vector CKeycodeList; + enum { + kGroupPoll = -1, + kGroupPollAndSet = -2 + }; + + CXWindowsKeyState(Display*, bool useXKB); + ~CXWindowsKeyState(); + + //! @name modifiers + //@{ + + //! Set active group + /*! + Sets the active group to \p group. This is the group returned by + \c pollActiveGroup(). If \p group is \c kGroupPoll then + \c pollActiveGroup() will really poll, but that's a slow operation + on X11. If \p group is \c kGroupPollAndSet then this will poll the + active group now and use it for future calls to \c pollActiveGroup(). + */ + void setActiveGroup(SInt32 group); + + //! Set the auto-repeat state + /*! + Sets the auto-repeat state. + */ + void setAutoRepeat(const XKeyboardState&); + + //@} + //! @name accessors + //@{ + + //! Convert X modifier mask to synergy mask + /*! + Returns the synergy modifier mask corresponding to the X modifier + mask in \p state. + */ + KeyModifierMask mapModifiersFromX(unsigned int state) const; + + //! Convert synergy modifier mask to X mask + /*! + Converts the synergy modifier mask to the corresponding X modifier + mask. Returns \c true if successful and \c false if any modifier + could not be converted. + */ + bool mapModifiersToX(KeyModifierMask, unsigned int&) const; + + //! Convert synergy key to all corresponding X keycodes + /*! + Converts the synergy key \p key to all of the keycodes that map to + that key. + */ + void mapKeyToKeycodes(KeyID key, + CKeycodeList& keycodes) const; + + //@} + + // IKeyState overrides + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + +protected: + // CKeyState overrides + virtual void getKeyMap(CKeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + +private: + void updateKeysymMap(CKeyMap&); + void updateKeysymMapXKB(CKeyMap&); + bool hasModifiersXKB() const; + int getEffectiveGroup(KeyCode, int group) const; + UInt32 getGroupFromState(unsigned int state) const; + + static void remapKeyModifiers(KeyID, SInt32, + CKeyMap::KeyItem&, void*); + +private: + struct XKBModifierInfo { + public: + unsigned char m_level; + UInt32 m_mask; + bool m_lock; + }; + + typedef std::vector KeyModifierMaskList; + typedef std::map KeyModifierToXMask; + typedef std::multimap KeyToKeyCodeMap; + typedef std::map NonXKBModifierMap; + typedef std::map XKBModifierMap; + + Display* m_display; +#if HAVE_XKB_EXTENSION + XkbDescPtr m_xkb; +#endif + SInt32 m_group; + XKBModifierMap m_lastGoodXKBModifiers; + NonXKBModifierMap m_lastGoodNonXKBModifiers; + + // X modifier (bit number) to synergy modifier (mask) mapping + KeyModifierMaskList m_modifierFromX; + + // synergy modifier (mask) to X modifier (mask) + KeyModifierToXMask m_modifierToX; + + // map KeyID to all keycodes that can synthesize that KeyID + KeyToKeyCodeMap m_keyCodeFromKey; + + // autorepeat state + XKeyboardState m_keyboardState; +}; + +#endif diff --git a/lib/platform/CXWindowsScreen.cpp b/lib/platform/CXWindowsScreen.cpp new file mode 100644 index 00000000..9c4ab134 --- /dev/null +++ b/lib/platform/CXWindowsScreen.cpp @@ -0,0 +1,1901 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsScreen.h" +#include "CXWindowsClipboard.h" +#include "CXWindowsEventQueueBuffer.h" +#include "CXWindowsKeyState.h" +#include "CXWindowsScreenSaver.h" +#include "CXWindowsUtil.h" +#include "CClipboard.h" +#include "CKeyMap.h" +#include "XScreen.h" +#include "CLog.h" +#include "CStopwatch.h" +#include "CStringUtil.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +# include +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include +# if HAVE_X11_EXTENSIONS_XTEST_H +# include +# else +# error The XTest extension is required to build synergy +# endif +# if HAVE_X11_EXTENSIONS_XINERAMA_H + // Xinerama.h may lack extern "C" for inclusion by C++ + extern "C" { +# include + } +# endif +# if HAVE_XKB_EXTENSION +# include +# endif +#endif +#include "CArch.h" + + +// +// CXWindowsScreen +// + +// NOTE -- the X display is shared among several objects but is owned +// by the CXWindowsScreen. Xlib is not reentrant so we must ensure +// that no two objects can simultaneously call Xlib with the display. +// this is easy since we only make X11 calls from the main thread. +// we must also ensure that these objects do not use the display in +// their destructors or, if they do, we can tell them not to. This +// is to handle unexpected disconnection of the X display, when any +// call on the display is invalid. In that situation we discard the +// display and the X11 event queue buffer, ignore any calls that try +// to use the display, and wait to be destroyed. + +CXWindowsScreen* CXWindowsScreen::s_screen = NULL; + +CXWindowsScreen::CXWindowsScreen(const char* displayName, bool isPrimary) : + m_isPrimary(isPrimary), + m_display(NULL), + m_root(None), + m_window(None), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_xCursor(0), m_yCursor(0), + m_keyState(NULL), + m_lastFocus(None), + m_lastFocusRevert(RevertToNone), + m_im(NULL), + m_ic(NULL), + m_lastKeycode(0), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_xtestIsXineramaUnaware(true), + m_xkb(false) +{ + assert(s_screen == NULL); + + s_screen = this; + + // set the X I/O error handler so we catch the display disconnecting + XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler); + + try { + m_display = openDisplay(displayName); + m_root = DefaultRootWindow(m_display); + saveShape(); + m_window = openWindow(); + m_screensaver = new CXWindowsScreenSaver(m_display, + m_window, getEventTarget()); + m_keyState = new CXWindowsKeyState(m_display, m_xkb); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_xinerama ? "(xinerama)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + } + catch (...) { + if (m_display != NULL) { + XCloseDisplay(m_display); + } + throw; + } + + // primary/secondary screen only initialization + if (m_isPrimary) { + // start watching for events on other windows + selectEvents(m_root); + + // prepare to use input methods + openIM(); + } + else { + // become impervious to server grabs + XTestGrabControl(m_display, True); + } + + // initialize the clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id); + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CXWindowsScreen::handleSystemEvent)); + + // install the platform event queue + EVENTQUEUE->adoptBuffer(new CXWindowsEventQueueBuffer(m_display, m_window)); +} + +CXWindowsScreen::~CXWindowsScreen() +{ + assert(s_screen != NULL); + assert(m_display != NULL); + + EVENTQUEUE->adoptBuffer(NULL); + EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + delete m_clipboard[id]; + } + delete m_keyState; + delete m_screensaver; + m_keyState = NULL; + m_screensaver = NULL; + if (m_display != NULL) { + // FIXME -- is it safe to clean up the IC and IM without a display? + if (m_ic != NULL) { + XDestroyIC(m_ic); + } + if (m_im != NULL) { + XCloseIM(m_im); + } + XDestroyWindow(m_display, m_window); + XCloseDisplay(m_display); + } + XSetIOErrorHandler(NULL); + + s_screen = NULL; +} + +void +CXWindowsScreen::enable() +{ + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + + // raise and show the window + // FIXME -- take focus? + XMapRaised(m_display, m_window); + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + } +} + +void +CXWindowsScreen::disable() +{ + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + + // restore auto-repeat state + if (!m_isPrimary && m_autoRepeat) { + XAutoRepeatOn(m_display); + } +} + +void +CXWindowsScreen::enter() +{ + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // set the input focus to what it had been when we took it + if (m_lastFocus != None) { + // the window may not exist anymore so ignore errors + CXWindowsUtil::CErrorLock lock(m_display); + XSetInputFocus(m_display, m_lastFocus, m_lastFocusRevert, CurrentTime); + } + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + +/* maybe call this if entering for the screensaver + // set keyboard focus to root window. the screensaver should then + // pick up key events for when the user enters a password to unlock. + XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime); +*/ + + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // turn off auto-repeat. we do this so fake key press events don't + // cause the local server to generate their own auto-repeats of + // those keys. + XAutoRepeatOff(m_display); + } + + // now on screen + m_isOnScreen = true; +} + +bool +CXWindowsScreen::leave() +{ + if (!m_isPrimary) { + // restore the previous keyboard auto-repeat state. if the user + // changed the auto-repeat configuration while on the client then + // that state is lost. that's because we can't get notified by + // the X server when the auto-repeat configuration is changed so + // we can't track the desired configuration. + if (m_autoRepeat) { + XAutoRepeatOn(m_display); + } + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + } + + // raise and show the window + XMapRaised(m_display, m_window); + + // grab the mouse and keyboard, if primary and possible + if (m_isPrimary && !grabMouseAndKeyboard()) { + XUnmapWindow(m_display, m_window); + return false; + } + + // save current focus + XGetInputFocus(m_display, &m_lastFocus, &m_lastFocusRevert); + + // take focus + XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime); + + // now warp the mouse. we warp after showing the window so we're + // guaranteed to get the mouse leave event and to prevent the + // keyboard focus from changing under point-to-focus policies. + if (m_isPrimary) { + warpCursor(m_xCenter, m_yCenter); + } + else { + fakeMouseMove(m_xCenter, m_yCenter); + } + + // set input context focus to our window + if (m_ic != NULL) { + XmbResetIC(m_ic); + XSetICFocus(m_ic); + m_filtered.clear(); + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +CXWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = CXWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + if (clipboard != NULL) { + // save clipboard data + return CClipboard::copy(m_clipboard[id], clipboard, timestamp); + } + else { + // assert clipboard ownership + if (!m_clipboard[id]->open(timestamp)) { + return false; + } + m_clipboard[id]->empty(); + m_clipboard[id]->close(); + return true; + } +} + +void +CXWindowsScreen::checkClipboards() +{ + // do nothing, we're always up to date +} + +void +CXWindowsScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +CXWindowsScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +CXWindowsScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +CXWindowsScreen::resetOptions() +{ + m_xtestIsXineramaUnaware = true; +} + +void +CXWindowsScreen::setOptions(const COptionsList& options) +{ + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionXTestXineramaUnaware) { + m_xtestIsXineramaUnaware = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false")); + } + } +} + +void +CXWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +CXWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +CXWindowsScreen::getEventTarget() const +{ + return const_cast(this); +} + +bool +CXWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + assert(clipboard != NULL); + + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = CXWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + // copy the clipboard + return CClipboard::copy(clipboard, m_clipboard[id], timestamp); +} + +void +CXWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + Window root, window; + int mx, my, xWindow, yWindow; + unsigned int mask; + if (XQueryPointer(m_display, m_root, &root, &window, + &mx, &my, &xWindow, &yWindow, &mask)) { + x = mx; + y = my; + } + else { + x = m_xCenter; + y = m_yCenter; + } +} + +void +CXWindowsScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +CXWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + XEvent event; + while (XCheckMaskEvent(m_display, PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask, + &event)) { + // do nothing + } + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +CXWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to X + unsigned int modifiers; + if (!m_keyState->mapModifiersToX(mask, modifiers)) { + // can't map all modifiers + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + CXWindowsKeyState::CKeycodeList keycodes; + m_keyState->mapKeyToKeycodes(key, keycodes); + if (key != kKeyNone && keycodes.empty()) { + // can't map key + LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + HotKeyList& hotKeys = m_hotKeys[id]; + + // all modifier hotkey must be treated specially. for each modifier + // we need to grab the modifier key in combination with all the other + // requested modifiers. + bool err = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &err); + if (key == kKeyNone) { + static const KeyModifierMask s_hotKeyModifiers[] = { + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper + }; + + XModifierKeymap* modKeymap = XGetModifierMapping(m_display); + for (size_t j = 0; j < sizeof(s_hotKeyModifiers) / + sizeof(s_hotKeyModifiers[0]) && !err; ++j) { + // skip modifier if not in mask + if ((mask & s_hotKeyModifiers[j]) == 0) { + continue; + } + + // skip with error if we can't map remaining modifiers + unsigned int modifiers2; + KeyModifierMask mask2 = (mask & ~s_hotKeyModifiers[j]); + if (!m_keyState->mapModifiersToX(mask2, modifiers2)) { + err = true; + continue; + } + + // compute modifier index for modifier. there should be + // exactly one X modifier missing + int index; + switch (modifiers ^ modifiers2) { + case ShiftMask: + index = ShiftMapIndex; + break; + + case LockMask: + index = LockMapIndex; + break; + + case ControlMask: + index = ControlMapIndex; + break; + + case Mod1Mask: + index = Mod1MapIndex; + break; + + case Mod2Mask: + index = Mod2MapIndex; + break; + + case Mod3Mask: + index = Mod3MapIndex; + break; + + case Mod4Mask: + index = Mod4MapIndex; + break; + + case Mod5Mask: + index = Mod5MapIndex; + break; + + default: + err = true; + continue; + } + + // grab each key for the modifier + const KeyCode* modifiermap = + modKeymap->modifiermap + index * modKeymap->max_keypermod; + for (int k = 0; k < modKeymap->max_keypermod && !err; ++k) { + KeyCode code = modifiermap[k]; + if (modifiermap[k] != 0) { + XGrabKey(m_display, code, modifiers2, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(code, modifiers2)); + m_hotKeyToIDMap[CHotKeyItem(code, modifiers2)] = id; + } + } + } + } + XFreeModifiermap(modKeymap); + } + + // a non-modifier key must be insensitive to CapsLock, NumLock and + // ScrollLock, so we have to grab the key with every combination of + // those. + else { + // collect available toggle modifiers + unsigned int modifier; + unsigned int toggleModifiers[3]; + size_t numToggleModifiers = 0; + if (m_keyState->mapModifiersToX(KeyModifierCapsLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierNumLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierScrollLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + + + for (CXWindowsKeyState::CKeycodeList::iterator j = keycodes.begin(); + j != keycodes.end() && !err; ++j) { + for (size_t i = 0; i < (1u << numToggleModifiers); ++i) { + // add toggle modifiers for index i + unsigned int tmpModifiers = modifiers; + if ((i & 1) != 0) { + tmpModifiers |= toggleModifiers[0]; + } + if ((i & 2) != 0) { + tmpModifiers |= toggleModifiers[1]; + } + if ((i & 4) != 0) { + tmpModifiers |= toggleModifiers[2]; + } + + // add grab + XGrabKey(m_display, *j, tmpModifiers, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(*j, tmpModifiers)); + m_hotKeyToIDMap[CHotKeyItem(*j, tmpModifiers)] = id; + } + } + } + } + } + + if (err) { + // if any failed then unregister any we did get + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(CHotKeyItem(j->first, j->second)); + } + + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +CXWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &err); + HotKeyList& hotKeys = i->second; + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(CHotKeyItem(j->first, j->second)); + } + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +CXWindowsScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +CXWindowsScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +CXWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +CXWindowsScreen::isAnyMouseButtonDown() const +{ + // query the pointer to get the button state + Window root, window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (XQueryPointer(m_display, m_root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + return ((state & (Button1Mask | Button2Mask | Button3Mask | + Button4Mask | Button5Mask)) != 0); + } + + return false; +} + +void +CXWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const +{ + const unsigned int xButton = mapButtonToX(button); + if (xButton != 0) { + XTestFakeButtonEvent(m_display, xButton, + press ? True : False, CurrentTime); + XFlush(m_display); + } +} + +void +CXWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + if (m_xinerama && m_xtestIsXineramaUnaware) { + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeMotionEvent(m_display, DefaultScreen(m_display), + x, y, CurrentTime); + } + XFlush(m_display); +} + +void +CXWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // FIXME -- ignore xinerama for now + if (false && m_xinerama && m_xtestIsXineramaUnaware) { +// XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeRelativeMotionEvent(m_display, dx, dy, CurrentTime); + } + XFlush(m_display); +} + +void +CXWindowsScreen::fakeMouseWheel(SInt32, SInt32 yDelta) const +{ + // XXX -- support x-axis scrolling + if (yDelta == 0) { + return; + } + + // choose button depending on rotation direction + const unsigned int xButton = mapButtonToX(static_cast( + (yDelta >= 0) ? -1 : -2)); + if (xButton == 0) { + // If we get here, then the XServer does not support the scroll + // wheel buttons, so send PageUp/PageDown keystrokes instead. + // Patch by Tom Chadwick. + KeyCode keycode = 0; + if (yDelta >= 0) { + keycode = XKeysymToKeycode(m_display, XK_Page_Up); + } + else { + keycode = XKeysymToKeycode(m_display, XK_Page_Down); + } + if (keycode != 0) { + XTestFakeKeyEvent(m_display, keycode, True, CurrentTime); + XTestFakeKeyEvent(m_display, keycode, False, CurrentTime); + } + return; + } + + // now use absolute value of delta + if (yDelta < 0) { + yDelta = -yDelta; + } + + // send as many clicks as necessary + for (; yDelta >= 120; yDelta -= 120) { + XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); + XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); + } + XFlush(m_display); +} + +Display* +CXWindowsScreen::openDisplay(const char* displayName) +{ + // get the DISPLAY + if (displayName == NULL) { + displayName = getenv("DISPLAY"); + if (displayName == NULL) { + displayName = ":0.0"; + } + } + + // open the display + LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", displayName)); + Display* display = XOpenDisplay(displayName); + if (display == NULL) { + throw XScreenUnavailable(60.0); + } + + // verify the availability of the XTest extension + if (!m_isPrimary) { + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + LOG((CLOG_ERR "XTEST extension not available")); + XCloseDisplay(display); + throw XScreenOpenFailure(); + } + } + +#if HAVE_XKB_EXTENSION + { + m_xkb = false; + int major = XkbMajorVersion, minor = XkbMinorVersion; + if (XkbLibraryVersion(&major, &minor)) { + int opcode, firstError; + if (XkbQueryExtension(display, &opcode, &m_xkbEventBase, + &firstError, &major, &minor)) { + m_xkb = true; + XkbSelectEvents(display, XkbUseCoreKbd, + XkbMapNotifyMask, XkbMapNotifyMask); + XkbSelectEventDetails(display, XkbUseCoreKbd, + XkbStateNotifyMask, + XkbGroupStateMask, XkbGroupStateMask); + } + } + } +#endif + + return display; +} + +void +CXWindowsScreen::saveShape() +{ + // get shape of default screen + m_x = 0; + m_y = 0; + m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); + m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); + + // get center of default screen + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + + // check if xinerama is enabled and there is more than one screen. + // get center of first Xinerama screen. Xinerama appears to have + // a bug when XWarpPointer() is used in combination with + // XGrabPointer(). in that case, the warp is successful but the + // next pointer motion warps the pointer again, apparently to + // constrain it to some unknown region, possibly the region from + // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over + // all physical screens. this warp only seems to happen if the + // pointer wasn't in that region before the XWarpPointer(). the + // second (unexpected) warp causes synergy to think the pointer + // has been moved when it hasn't. to work around the problem, + // we warp the pointer to the center of the first physical + // screen instead of the logical screen. + m_xinerama = false; +#if HAVE_X11_EXTENSIONS_XINERAMA_H + int eventBase, errorBase; + if (XineramaQueryExtension(m_display, &eventBase, &errorBase) && + XineramaIsActive(m_display)) { + int numScreens; + XineramaScreenInfo* screens; + screens = XineramaQueryScreens(m_display, &numScreens); + if (screens != NULL) { + if (numScreens > 1) { + m_xinerama = true; + m_xCenter = screens[0].x_org + (screens[0].width >> 1); + m_yCenter = screens[0].y_org + (screens[0].height >> 1); + } + XFree(screens); + } + } +#endif +} + +Window +CXWindowsScreen::openWindow() const +{ + // default window attributes. we don't want the window manager + // messing with our window and we don't want the cursor to be + // visible inside the window. + XSetWindowAttributes attr; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = createBlankCursor(); + + // adjust attributes and get size and shape + SInt32 x, y, w, h; + if (m_isPrimary) { + // grab window attributes. this window is used to capture user + // input when the user is focused on another client. it covers + // the whole screen. + attr.event_mask = PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask | PropertyChangeMask; + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + else { + // cursor hider window attributes. this window is used to hide the + // cursor when it's not on the screen. the window is hidden as soon + // as the cursor enters the screen or the display's real mouse is + // moved. we'll reposition the window as necessary so its + // position here doesn't matter. it only needs to be 1x1 because + // it only needs to contain the cursor's hotspot. + attr.event_mask = LeaveWindowMask; + x = 0; + y = 0; + w = 1; + h = 1; + } + + // create and return the window + Window window = XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + if (window == None) { + throw XScreenOpenFailure(); + } + return window; +} + +void +CXWindowsScreen::openIM() +{ + // open the input methods + XIM im = XOpenIM(m_display, NULL, NULL, NULL); + if (im == NULL) { + LOG((CLOG_INFO "no support for IM")); + return; + } + + // find the appropriate style. synergy supports XIMPreeditNothing + // only at the moment. + XIMStyles* styles; + if (XGetIMValues(im, XNQueryInputStyle, &styles, NULL) != NULL || + styles == NULL) { + LOG((CLOG_WARN "cannot get IM styles")); + XCloseIM(im); + return; + } + XIMStyle style = 0; + for (unsigned short i = 0; i < styles->count_styles; ++i) { + style = styles->supported_styles[i]; + if ((style & XIMPreeditNothing) != 0) { + if ((style & (XIMStatusNothing | XIMStatusNone)) != 0) { + break; + } + } + } + XFree(styles); + if (style == 0) { + LOG((CLOG_INFO "no supported IM styles")); + XCloseIM(im); + return; + } + + // create an input context for the style and tell it about our window + XIC ic = XCreateIC(im, XNInputStyle, style, XNClientWindow, m_window, NULL); + if (ic == NULL) { + LOG((CLOG_WARN "cannot create IC")); + XCloseIM(im); + return; + } + + // find out the events we must select for and do so + unsigned long mask; + if (XGetICValues(ic, XNFilterEvents, &mask, NULL) != NULL) { + LOG((CLOG_WARN "cannot get IC filter events")); + XDestroyIC(ic); + XCloseIM(im); + return; + } + + // we have IM + m_im = im; + m_ic = ic; + m_lastKeycode = 0; + + // select events on our window that IM requires + XWindowAttributes attr; + XGetWindowAttributes(m_display, m_window, &attr); + XSelectInput(m_display, m_window, attr.your_event_mask | mask); +} + +void +CXWindowsScreen::sendEvent(CEvent::Type type, void* data) +{ + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); +} + +void +CXWindowsScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) +{ + CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +IKeyState* +CXWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +Bool +CXWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg) +{ + CKeyEventFilter* filter = reinterpret_cast(arg); + return (xevent->type == filter->m_event && + xevent->xkey.window == filter->m_window && + xevent->xkey.time == filter->m_time && + xevent->xkey.keycode == filter->m_keycode) ? True : False; +} + +void +CXWindowsScreen::handleSystemEvent(const CEvent& event, void*) +{ + XEvent* xevent = reinterpret_cast(event.getData()); + assert(xevent != NULL); + + // update key state + bool isRepeat = false; + if (m_isPrimary) { + if (xevent->type == KeyRelease) { + // check if this is a key repeat by getting the next + // KeyPress event that has the same key and time as + // this release event, if any. first prepare the + // filter info. + CKeyEventFilter filter; + filter.m_event = KeyPress; + filter.m_window = xevent->xkey.window; + filter.m_time = xevent->xkey.time; + filter.m_keycode = xevent->xkey.keycode; + XEvent xevent2; + isRepeat = (XCheckIfEvent(m_display, &xevent2, + &CXWindowsScreen::findKeyEvent, + (XPointer)&filter) == True); + } + + if (xevent->type == KeyPress || xevent->type == KeyRelease) { + if (xevent->xkey.window == m_root) { + // this is a hot key + onHotKey(xevent->xkey, isRepeat); + return; + } + else if (!m_isOnScreen) { + // this might be a hot key + if (onHotKey(xevent->xkey, isRepeat)) { + return; + } + } + + bool down = (isRepeat || xevent->type == KeyPress); + KeyModifierMask state = + m_keyState->mapModifiersFromX(xevent->xkey.state); + m_keyState->onKey(xevent->xkey.keycode, down, state); + } + } + + // let input methods try to handle event first + if (m_ic != NULL) { + // XFilterEvent() may eat the event and generate a new KeyPress + // event with a keycode of 0 because there isn't an actual key + // associated with the keysym. but the KeyRelease may pass + // through XFilterEvent() and keep its keycode. this means + // there's a mismatch between KeyPress and KeyRelease keycodes. + // since we use the keycode on the client to detect when a key + // is released this won't do. so we remember the keycode on + // the most recent KeyPress (and clear it on a matching + // KeyRelease) so we have a keycode for a synthesized KeyPress. + if (xevent->type == KeyPress && xevent->xkey.keycode != 0) { + m_lastKeycode = xevent->xkey.keycode; + } + else if (xevent->type == KeyRelease && + xevent->xkey.keycode == m_lastKeycode) { + m_lastKeycode = 0; + } + + // now filter the event + if (XFilterEvent(xevent, None)) { + if (xevent->type == KeyPress) { + // add filtered presses to the filtered list + m_filtered.insert(m_lastKeycode); + } + return; + } + + // discard matching key releases for key presses that were + // filtered and remove them from our filtered list. + else if (xevent->type == KeyRelease && + m_filtered.count(xevent->xkey.keycode) > 0) { + m_filtered.erase(xevent->xkey.keycode); + return; + } + } + + // let screen saver have a go + if (m_screensaver->handleXEvent(xevent)) { + // screen saver handled it + return; + } + + // handle the event ourself + switch (xevent->type) { + case CreateNotify: + if (m_isPrimary) { + // select events on new window + selectEvents(xevent->xcreatewindow.window); + } + break; + + case MappingNotify: + refreshKeyboard(xevent); + break; + + case LeaveNotify: + if (!m_isPrimary) { + // mouse moved out of hider window somehow. hide the window. + XUnmapWindow(m_display, m_window); + } + break; + + case SelectionClear: + { + // we just lost the selection. that means someone else + // grabbed the selection so this screen is now the + // selection owner. report that to the receiver. + ClipboardID id = getClipboardID(xevent->xselectionclear.selection); + if (id != kClipboardEnd) { + LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); + m_clipboard[id]->lost(xevent->xselectionclear.time); + sendClipboardEvent(getClipboardGrabbedEvent(), id); + return; + } + } + break; + + case SelectionNotify: + // notification of selection transferred. we shouldn't + // get this here because we handle them in the selection + // retrieval methods. we'll just delete the property + // with the data (satisfying the usual ICCCM protocol). + if (xevent->xselection.property != None) { + XDeleteProperty(m_display, + xevent->xselection.requestor, + xevent->xselection.property); + } + break; + + case SelectionRequest: + { + // somebody is asking for clipboard data + ClipboardID id = getClipboardID( + xevent->xselectionrequest.selection); + if (id != kClipboardEnd) { + m_clipboard[id]->addRequest( + xevent->xselectionrequest.owner, + xevent->xselectionrequest.requestor, + xevent->xselectionrequest.target, + xevent->xselectionrequest.time, + xevent->xselectionrequest.property); + return; + } + } + break; + + case PropertyNotify: + // property delete may be part of a selection conversion + if (xevent->xproperty.state == PropertyDelete) { + processClipboardRequest(xevent->xproperty.window, + xevent->xproperty.time, + xevent->xproperty.atom); + } + break; + + case DestroyNotify: + // looks like one of the windows that requested a clipboard + // transfer has gone bye-bye. + destroyClipboardRequest(xevent->xdestroywindow.window); + break; + + case KeyPress: + if (m_isPrimary) { + onKeyPress(xevent->xkey); + } + return; + + case KeyRelease: + if (m_isPrimary) { + onKeyRelease(xevent->xkey, isRepeat); + } + return; + + case ButtonPress: + if (m_isPrimary) { + onMousePress(xevent->xbutton); + } + return; + + case ButtonRelease: + if (m_isPrimary) { + onMouseRelease(xevent->xbutton); + } + return; + + case MotionNotify: + if (m_isPrimary) { + onMouseMove(xevent->xmotion); + } + return; + + default: +#if HAVE_XKB_EXTENSION + if (m_xkb && xevent->type == m_xkbEventBase) { + XkbEvent* xkbEvent = reinterpret_cast(xevent); + switch (xkbEvent->any.xkb_type) { + case XkbMapNotify: + refreshKeyboard(xevent); + return; + + case XkbStateNotify: + LOG((CLOG_INFO "group change: %d", xkbEvent->state.group)); + m_keyState->setActiveGroup((SInt32)xkbEvent->state.group); + return; + } + } +#endif + break; + } +} + +void +CXWindowsScreen::onKeyPress(XKeyEvent& xkey) +{ + LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state)); + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + } + + // get which button. see call to XFilterEvent() in onEvent() + // for more info. + bool isFake = false; + KeyButton keycode = static_cast(xkey.keycode); + if (keycode == 0) { + isFake = true; + keycode = static_cast(m_lastKeycode); + if (keycode == 0) { + // no keycode + return; + } + } + + // handle key + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, keycode); + + // do fake release if this is a fake press + if (isFake) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + } +} + +void +CXWindowsScreen::onKeyRelease(XKeyEvent& xkey, bool isRepeat) +{ + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del and ignore autorepeat + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + isRepeat = false; + } + + KeyButton keycode = static_cast(xkey.keycode); + if (!isRepeat) { + // no press event follows so it's a plain release + LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + else { + // found a press event following so it's a repeat. + // we could attempt to count the already queued + // repeats but we'll just send a repeat of 1. + // note that we discard the press event. + LOG((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, true, key, mask, 1, keycode); + } + } +} + +bool +CXWindowsScreen::onHotKey(XKeyEvent& xkey, bool isRepeat) +{ + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(CHotKeyItem(xkey.keycode, xkey.state)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + CEvent::Type type; + if (xkey.type == KeyPress) { + type = getHotKeyDownEvent(); + } + else if (xkey.type == KeyRelease) { + type = getHotKeyUpEvent(); + } + else { + return false; + } + + // generate event (ignore key repeats) + if (!isRepeat) { + EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), + CHotKeyInfo::alloc(i->second))); + } + return true; +} + +void +CXWindowsScreen::onMousePress(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask)); + } +} + +void +CXWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask)); + } + else if (xbutton.button == 4) { + // wheel forward (away from user) + sendEvent(getWheelEvent(), CWheelInfo::alloc(0, 120)); + } + else if (xbutton.button == 5) { + // wheel backward (toward user) + sendEvent(getWheelEvent(), CWheelInfo::alloc(0, -120)); + } + // XXX -- support x-axis scrolling +} + +void +CXWindowsScreen::onMouseMove(const XMotionEvent& xmotion) +{ + LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xmotion.x_root, xmotion.y_root)); + + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = xmotion.x_root - m_xCursor; + SInt32 y = xmotion.y_root - m_yCursor; + + // save position to compute delta of next motion + m_xCursor = xmotion.x_root; + m_yCursor = xmotion.y_root; + + if (xmotion.send_event) { + // we warped the mouse. discard events until we + // find the matching sent event. see + // warpCursorNoFlush() for where the events are + // sent. we discard the matching sent event and + // can be sure we've skipped the warp event. + XEvent xevent; + do { + XMaskEvent(m_display, PointerMotionMask, &xevent); + } while (!xevent.xany.send_event); + } + else if (m_isOnScreen) { + // motion on primary screen + sendEvent(getMotionOnPrimaryEvent(), + CMotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + // + // my lombard (powerbook g3) running linux and + // using the adbmouse driver has two problems: + // first, the driver only sends motions of +/-2 + // pixels and, second, it seems to discard some + // physical input after a warp. the former isn't a + // big deal (we're just limited to every other + // pixel) but the latter is a PITA. to work around + // it we only warp when the mouse has moved more + // than s_size pixels from the center. + static const SInt32 s_size = 32; + if (xmotion.x_root - m_xCenter < -s_size || + xmotion.x_root - m_xCenter > s_size || + xmotion.y_root - m_yCenter < -s_size || + xmotion.y_root - m_yCenter > s_size) { + warpCursorNoFlush(m_xCenter, m_yCenter); + } + + // send event if mouse moved. do this after warping + // back to center in case the motion takes us onto + // the primary screen. if we sent the event first + // in that case then the warp would happen after + // warping to the primary screen's enter position, + // effectively overriding it. + if (x != 0 || y != 0) { + sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); + } + } +} + +Cursor +CXWindowsScreen::createBlankCursor() const +{ + // this seems just a bit more complicated than really necessary + + // get the closet cursor size to 1x1 + unsigned int w, h; + XQueryBestCursor(m_display, m_root, 1, 1, &w, &h); + + // make bitmap data for cursor of closet size. since the cursor + // is blank we can use the same bitmap for shape and mask: all + // zeros. + const int size = ((w + 7) >> 3) * h; + char* data = new char[size]; + memset(data, 0, size); + + // make bitmap + Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h); + + // need an arbitrary color for the cursor + XColor color; + color.pixel = 0; + color.red = color.green = color.blue = 0; + color.flags = DoRed | DoGreen | DoBlue; + + // make cursor from bitmap + Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap, + &color, &color, 0, 0); + + // don't need bitmap or the data anymore + delete[] data; + XFreePixmap(m_display, bitmap); + + return cursor; +} + +ClipboardID +CXWindowsScreen::getClipboardID(Atom selection) const +{ + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->getSelection() == selection) { + return id; + } + } + return kClipboardEnd; +} + +void +CXWindowsScreen::processClipboardRequest(Window requestor, + Time time, Atom property) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->processRequest(requestor, time, property)) { + break; + } + } +} + +void +CXWindowsScreen::destroyClipboardRequest(Window requestor) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->destroyRequest(requestor)) { + break; + } + } +} + +void +CXWindowsScreen::onError() +{ + // prevent further access to the X display + EVENTQUEUE->adoptBuffer(NULL); + m_screensaver->destroy(); + m_screensaver = NULL; + m_display = NULL; + + // notify of failure + sendEvent(getErrorEvent(), NULL); + + // FIXME -- should ensure that we ignore operations that involve + // m_display from now on. however, Xlib will simply exit the + // application in response to the X I/O error so there's no + // point in trying to really handle the error. if we did want + // to handle the error, it'd probably be easiest to delegate to + // one of two objects. one object would take the implementation + // from this class. the other object would be stub methods that + // don't use X11. on error, we'd switch to the latter. +} + +int +CXWindowsScreen::ioErrorHandler(Display*) +{ + // the display has disconnected, probably because X is shutting + // down. X forces us to exit at this point which is annoying. + // we'll pretend as if we won't exit so we try to make sure we + // don't access the display anymore. + LOG((CLOG_CRIT "X display has unexpectedly disconnected")); + s_screen->onError(); + return 0; +} + +void +CXWindowsScreen::selectEvents(Window w) const +{ + // ignore errors while we adjust event masks. windows could be + // destroyed at any time after the XQueryTree() in doSelectEvents() + // so we must ignore BadWindow errors. + CXWindowsUtil::CErrorLock lock(m_display); + + // adjust event masks + doSelectEvents(w); +} + +void +CXWindowsScreen::doSelectEvents(Window w) const +{ + // we want to track the mouse everywhere on the display. to achieve + // that we select PointerMotionMask on every window. we also select + // SubstructureNotifyMask in order to get CreateNotify events so we + // select events on new windows too. + // + // note that this can break certain clients due a design flaw of X. + // X will deliver a PointerMotion event to the deepest window in the + // hierarchy that contains the pointer and has PointerMotionMask + // selected by *any* client. if another client doesn't select + // motion events in a subwindow so the parent window will get them + // then by selecting for motion events on the subwindow we break + // that client because the parent will no longer get the events. + + // FIXME -- should provide some workaround for event selection + // design flaw. perhaps only select for motion events on windows + // that already do or are top-level windows or don't propagate + // pointer events. or maybe an option to simply poll the mouse. + + // we don't want to adjust our grab window + if (w == m_window) { + return; + } + + // select events of interest. do this before querying the tree so + // we'll get notifications of children created after the XQueryTree() + // so we won't miss them. + XSelectInput(m_display, w, PointerMotionMask | SubstructureNotifyMask); + + // recurse on child windows + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + doSelectEvents(cw[i]); + } + XFree(cw); + } +} + +KeyID +CXWindowsScreen::mapKeyFromX(XKeyEvent* event) const +{ + // convert to a keysym + KeySym keysym; + if (event->type == KeyPress && m_ic != NULL) { + // do multibyte lookup. can only call XmbLookupString with a + // key press event and a valid XIC so we checked those above. + char scratch[32]; + int n = sizeof(scratch) / sizeof(scratch[0]); + char* buffer = scratch; + int status; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + if (status == XBufferOverflow) { + // not enough space. grow buffer and try again. + buffer = new char[n]; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + delete[] buffer; + } + + // see what we got. since we don't care about the string + // we'll just look for a keysym. + switch (status) { + default: + case XLookupNone: + case XLookupChars: + keysym = 0; + break; + + case XLookupKeySym: + case XLookupBoth: + break; + } + } + else { + // plain old lookup + char dummy[1]; + XLookupString(event, dummy, 0, &keysym, NULL); + } + + // convert key + return CXWindowsUtil::mapKeySymToKeyID(keysym); +} + +ButtonID +CXWindowsScreen::mapButtonFromX(const XButtonEvent* event) const +{ + unsigned int button = event->button; + + // first three buttons map to 1, 2, 3 (kButtonLeft, Middle, Right) + if (button >= 1 && button <= 3) { + return static_cast(button); + } + + // buttons 4 and 5 are ignored here. they're used for the wheel. + // buttons 6, 7, etc and up map to 4, 5, etc. + else if (button >= 6) { + return static_cast(button - 2); + } + + // unknown button + else { + return kButtonNone; + } +} + +unsigned int +CXWindowsScreen::mapButtonToX(ButtonID id) const +{ + // map button -1 to button 4 (+wheel) + if (id == static_cast(-1)) { + id = 4; + } + + // map button -2 to button 5 (-wheel) + else if (id == static_cast(-2)) { + id = 5; + } + + // map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons + // 4 and 5 used to simulate the mouse wheel. + else if (id >= 4) { + id += 2; + } + + // check button is in legal range + if (id < 1 || id > m_buttons.size()) { + // out of range + return 0; + } + + // map button + return static_cast(id); +} + +void +CXWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + assert(m_window != None); + + // send an event that we can recognize before the mouse warp + XEvent eventBefore; + eventBefore.type = MotionNotify; + eventBefore.xmotion.display = m_display; + eventBefore.xmotion.window = m_window; + eventBefore.xmotion.root = m_root; + eventBefore.xmotion.subwindow = m_window; + eventBefore.xmotion.time = CurrentTime; + eventBefore.xmotion.x = x; + eventBefore.xmotion.y = y; + eventBefore.xmotion.x_root = x; + eventBefore.xmotion.y_root = y; + eventBefore.xmotion.state = 0; + eventBefore.xmotion.is_hint = NotifyNormal; + eventBefore.xmotion.same_screen = True; + XEvent eventAfter = eventBefore; + XSendEvent(m_display, m_window, False, 0, &eventBefore); + + // warp mouse + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + + // send an event that we can recognize after the mouse warp + XSendEvent(m_display, m_window, False, 0, &eventAfter); + XSync(m_display, False); + + LOG((CLOG_DEBUG2 "warped to %d,%d", x, y)); +} + +void +CXWindowsScreen::updateButtons() +{ + // query the button mapping + UInt32 numButtons = XGetPointerMapping(m_display, NULL, 0); + unsigned char* tmpButtons = new unsigned char[numButtons]; + XGetPointerMapping(m_display, tmpButtons, numButtons); + + // find the largest logical button id + unsigned char maxButton = 0; + for (UInt32 i = 0; i < numButtons; ++i) { + if (tmpButtons[i] > maxButton) { + maxButton = tmpButtons[i]; + } + } + + // allocate button array + m_buttons.resize(maxButton); + + // fill in button array values. m_buttons[i] is the physical + // button number for logical button i+1. + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[i] = 0; + } + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[tmpButtons[i] - 1] = i + 1; + } + + // clean up + delete[] tmpButtons; +} + +bool +CXWindowsScreen::grabMouseAndKeyboard() +{ + // grab the mouse and keyboard. keep trying until we get them. + // if we can't grab one after grabbing the other then ungrab + // and wait before retrying. give up after s_timeout seconds. + static const double s_timeout = 1.0; + int result; + CStopwatch timer; + do { + // keyboard first + do { + result = XGrabKeyboard(m_display, m_window, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + LOG((CLOG_DEBUG2 "waiting to grab keyboard")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab keyboard timed out")); + return false; + } + } + } while (result != GrabSuccess); + LOG((CLOG_DEBUG2 "grabbed keyboard")); + + // now the mouse + result = XGrabPointer(m_display, m_window, True, 0, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + // back off to avoid grab deadlock + XUngrabKeyboard(m_display, CurrentTime); + LOG((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab pointer timed out")); + return false; + } + } + } while (result != GrabSuccess); + + LOG((CLOG_DEBUG1 "grabbed pointer and keyboard")); + return true; +} + +void +CXWindowsScreen::refreshKeyboard(XEvent* event) +{ + if (XPending(m_display) > 0) { + XEvent tmpEvent; + XPeekEvent(m_display, &tmpEvent); + if (tmpEvent.type == MappingNotify) { + // discard this event since another follows. + // we tend to get a bunch of these in a row. + return; + } + } + + // keyboard mapping changed +#if HAVE_XKB_EXTENSION + if (m_xkb && event->type == m_xkbEventBase) { + XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)event); + } + else +#else + { + XRefreshKeyboardMapping(&event->xmapping); + } +#endif + m_keyState->updateKeyMap(); + m_keyState->updateKeyState(); +} + + +// +// CXWindowsScreen::CHotKeyItem +// + +CXWindowsScreen::CHotKeyItem::CHotKeyItem(int keycode, unsigned int mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +bool +CXWindowsScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} diff --git a/lib/platform/CXWindowsScreen.h b/lib/platform/CXWindowsScreen.h new file mode 100644 index 00000000..99c45576 --- /dev/null +++ b/lib/platform/CXWindowsScreen.h @@ -0,0 +1,229 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSSCREEN_H +#define CXWINDOWSSCREEN_H + +#include "CPlatformScreen.h" +#include "stdset.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +class CXWindowsClipboard; +class CXWindowsKeyState; +class CXWindowsScreenSaver; + +//! Implementation of IPlatformScreen for X11 +class CXWindowsScreen : public CPlatformScreen { +public: + CXWindowsScreen(const char* displayName, bool isPrimary); + virtual ~CXWindowsScreen(); + + //! @name manipulators + //@{ + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + // event sending + void sendEvent(CEvent::Type, void* = NULL); + void sendClipboardEvent(CEvent::Type, ClipboardID); + + // create the transparent cursor + Cursor createBlankCursor() const; + + // determine the clipboard from the X selection. returns + // kClipboardEnd if no such clipboard. + ClipboardID getClipboardID(Atom selection) const; + + // continue processing a selection request + void processClipboardRequest(Window window, + Time time, Atom property); + + // terminate a selection request + void destroyClipboardRequest(Window window); + + // X I/O error handler + void onError(); + static int ioErrorHandler(Display*); + +private: + class CKeyEventFilter { + public: + int m_event; + Window m_window; + Time m_time; + KeyCode m_keycode; + }; + + Display* openDisplay(const char* displayName); + void saveShape(); + Window openWindow() const; + void openIM(); + + bool grabMouseAndKeyboard(); + void onKeyPress(XKeyEvent&); + void onKeyRelease(XKeyEvent&, bool isRepeat); + bool onHotKey(XKeyEvent&, bool isRepeat); + void onMousePress(const XButtonEvent&); + void onMouseRelease(const XButtonEvent&); + void onMouseMove(const XMotionEvent&); + + void selectEvents(Window) const; + void doSelectEvents(Window) const; + + KeyID mapKeyFromX(XKeyEvent*) const; + ButtonID mapButtonFromX(const XButtonEvent*) const; + unsigned int mapButtonToX(ButtonID id) const; + + void warpCursorNoFlush(SInt32 x, SInt32 y); + + void refreshKeyboard(XEvent*); + + static Bool findKeyEvent(Display*, XEvent* xevent, XPointer arg); + +private: + struct CHotKeyItem { + public: + CHotKeyItem(int, unsigned int); + + bool operator<(const CHotKeyItem&) const; + + private: + int m_keycode; + unsigned int m_mask; + }; + typedef std::set CFilteredKeycodes; + typedef std::vector > HotKeyList; + typedef std::map HotKeyMap; + typedef std::vector HotKeyIDList; + typedef std::map HotKeyToIDMap; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + Display* m_display; + Window m_root; + Window m_window; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // last mouse position + SInt32 m_xCursor, m_yCursor; + + // keyboard stuff + CXWindowsKeyState* m_keyState; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + HotKeyToIDMap m_hotKeyToIDMap; + + // input focus stuff + Window m_lastFocus; + int m_lastFocusRevert; + + // input method stuff + XIM m_im; + XIC m_ic; + KeyCode m_lastKeycode; + CFilteredKeycodes m_filtered; + + // clipboards + CXWindowsClipboard* m_clipboard[kClipboardEnd]; + UInt32 m_sequenceNumber; + + // screen saver stuff + CXWindowsScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // logical to physical button mapping. m_buttons[i] gives the + // physical button for logical button i+1. + std::vector m_buttons; + + // true if global auto-repeat was enabled before we turned it off + bool m_autoRepeat; + + // stuff to workaround xtest being xinerama unaware. attempting + // to fake a mouse motion under xinerama may behave strangely, + // especially if screen 0 is not at 0,0 or if faking a motion on + // a screen other than screen 0. + bool m_xtestIsXineramaUnaware; + bool m_xinerama; + + // XKB extension stuff + bool m_xkb; + int m_xkbEventBase; + + // pointer to (singleton) screen. this is only needed by + // ioErrorHandler(). + static CXWindowsScreen* s_screen; +}; + +#endif diff --git a/lib/platform/CXWindowsScreenSaver.cpp b/lib/platform/CXWindowsScreenSaver.cpp new file mode 100644 index 00000000..65af34e1 --- /dev/null +++ b/lib/platform/CXWindowsScreenSaver.cpp @@ -0,0 +1,598 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsScreenSaver.h" +#include "CXWindowsUtil.h" +#include "IPlatformScreen.h" +#include "CLog.h" +#include "CEvent.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include +#if HAVE_X11_EXTENSIONS_XTEST_H +# include +#else +# error The XTest extension is required to build synergy +#endif +#if HAVE_X11_EXTENSIONS_DPMS_H +extern "C" { +# include +# include +# if !HAVE_DPMS_PROTOTYPES +# undef DPMSModeOn +# undef DPMSModeStandby +# undef DPMSModeSuspend +# undef DPMSModeOff +# define DPMSModeOn 0 +# define DPMSModeStandby 1 +# define DPMSModeSuspend 2 +# define DPMSModeOff 3 +extern Bool DPMSQueryExtension(Display *, int *, int *); +extern Bool DPMSCapable(Display *); +extern Status DPMSEnable(Display *); +extern Status DPMSDisable(Display *); +extern Status DPMSForceLevel(Display *, CARD16); +extern Status DPMSInfo(Display *, CARD16 *, BOOL *); +# endif +} +#endif + +// +// CXWindowsScreenSaver +// + +CXWindowsScreenSaver::CXWindowsScreenSaver( + Display* display, Window window, void* eventTarget) : + m_display(display), + m_xscreensaverSink(window), + m_eventTarget(eventTarget), + m_xscreensaver(None), + m_xscreensaverActive(false), + m_dpms(false), + m_disabled(false), + m_suppressDisable(false), + m_disableTimer(NULL), + m_disablePos(0) +{ + // get atoms + m_atomScreenSaver = XInternAtom(m_display, + "SCREENSAVER", False); + m_atomScreenSaverVersion = XInternAtom(m_display, + "_SCREENSAVER_VERSION", False); + m_atomScreenSaverActivate = XInternAtom(m_display, + "ACTIVATE", False); + m_atomScreenSaverDeactivate = XInternAtom(m_display, + "DEACTIVATE", False); + + // check for DPMS extension. this is an alternative screen saver + // that powers down the display. +#if HAVE_X11_EXTENSIONS_DPMS_H + int eventBase, errorBase; + if (DPMSQueryExtension(m_display, &eventBase, &errorBase)) { + if (DPMSCapable(m_display)) { + // we have DPMS + m_dpms = true; + } + } +#endif + + // watch top-level windows for changes + bool error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + Window root = DefaultRootWindow(m_display); + XWindowAttributes attr; + XGetWindowAttributes(m_display, root, &attr); + m_rootEventMask = attr.your_event_mask; + XSelectInput(m_display, root, m_rootEventMask | SubstructureNotifyMask); + } + if (error) { + LOG((CLOG_DEBUG "didn't set root event mask")); + m_rootEventMask = 0; + } + + // get the built-in settings + XGetScreenSaver(m_display, &m_timeout, &m_interval, + &m_preferBlanking, &m_allowExposures); + + // get the DPMS settings + m_dpmsEnabled = isDPMSEnabled(); + + // get the xscreensaver window, if any + if (!findXScreenSaver()) { + setXScreenSaver(None); + } + + // install disable timer event handler + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CXWindowsScreenSaver::handleDisableTimer)); +} + +CXWindowsScreenSaver::~CXWindowsScreenSaver() +{ + // done with disable job + if (m_disableTimer != NULL) { + EVENTQUEUE->deleteTimer(m_disableTimer); + } + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + + if (m_display != NULL) { + enableDPMS(m_dpmsEnabled); + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + clearWatchForXScreenSaver(); + CXWindowsUtil::CErrorLock lock(m_display); + XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask); + } +} + +void +CXWindowsScreenSaver::destroy() +{ + m_display = NULL; + delete this; +} + +bool +CXWindowsScreenSaver::handleXEvent(const XEvent* xevent) +{ + switch (xevent->type) { + case CreateNotify: + if (m_xscreensaver == None) { + if (isXScreenSaver(xevent->xcreatewindow.window)) { + // found the xscreensaver + setXScreenSaver(xevent->xcreatewindow.window); + } + else { + // another window to watch. to detect the xscreensaver + // window we look for a property but that property may + // not yet exist by the time we get this event so we + // have to watch the window for property changes. + // this would be so much easier if xscreensaver did the + // smart thing and stored its window in a property on + // the root window. + addWatchXScreenSaver(xevent->xcreatewindow.window); + } + } + break; + + case DestroyNotify: + if (xevent->xdestroywindow.window == m_xscreensaver) { + // xscreensaver is gone + LOG((CLOG_DEBUG "xscreensaver died")); + setXScreenSaver(None); + return true; + } + break; + + case PropertyNotify: + if (xevent->xproperty.state == PropertyNewValue) { + if (isXScreenSaver(xevent->xproperty.window)) { + // found the xscreensaver + setXScreenSaver(xevent->xcreatewindow.window); + } + } + break; + + case MapNotify: + if (xevent->xmap.window == m_xscreensaver) { + // xscreensaver has activated + setXScreenSaverActive(true); + return true; + } + break; + + case UnmapNotify: + if (xevent->xunmap.window == m_xscreensaver) { + // xscreensaver has deactivated + setXScreenSaverActive(false); + return true; + } + break; + } + + return false; +} + +void +CXWindowsScreenSaver::enable() +{ + // for xscreensaver + m_disabled = false; + updateDisableTimer(); + + // for built-in X screen saver + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + + // for DPMS + enableDPMS(m_dpmsEnabled); +} + +void +CXWindowsScreenSaver::disable() +{ + // for xscreensaver + m_disabled = true; + updateDisableTimer(); + + // use built-in X screen saver + XGetScreenSaver(m_display, &m_timeout, &m_interval, + &m_preferBlanking, &m_allowExposures); + XSetScreenSaver(m_display, 0, m_interval, + m_preferBlanking, m_allowExposures); + + // for DPMS + m_dpmsEnabled = isDPMSEnabled(); + enableDPMS(false); + + // FIXME -- now deactivate? +} + +void +CXWindowsScreenSaver::activate() +{ + // remove disable job timer + m_suppressDisable = true; + updateDisableTimer(); + + // enable DPMS if it was enabled + enableDPMS(m_dpmsEnabled); + + // try xscreensaver + findXScreenSaver(); + if (m_xscreensaver != None) { + sendXScreenSaverCommand(m_atomScreenSaverActivate); + return; + } + + // try built-in X screen saver + if (m_timeout != 0) { + XForceScreenSaver(m_display, ScreenSaverActive); + } + + // try DPMS + activateDPMS(true); +} + +void +CXWindowsScreenSaver::deactivate() +{ + // reinstall disable job timer + m_suppressDisable = false; + updateDisableTimer(); + + // try DPMS + activateDPMS(false); + + // disable DPMS if screen saver is disabled + if (m_disabled) { + enableDPMS(false); + } + + // try xscreensaver + findXScreenSaver(); + if (m_xscreensaver != None) { + sendXScreenSaverCommand(m_atomScreenSaverDeactivate); + return; + } + + // use built-in X screen saver + XForceScreenSaver(m_display, ScreenSaverReset); +} + +bool +CXWindowsScreenSaver::isActive() const +{ + // check xscreensaver + if (m_xscreensaver != None) { + return m_xscreensaverActive; + } + + // check DPMS + if (isDPMSActivated()) { + return true; + } + + // can't check built-in X screen saver activity + return false; +} + +bool +CXWindowsScreenSaver::findXScreenSaver() +{ + // do nothing if we've already got the xscreensaver window + if (m_xscreensaver == None) { + // find top-level window xscreensaver window + Window root = DefaultRootWindow(m_display); + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + if (isXScreenSaver(cw[i])) { + setXScreenSaver(cw[i]); + break; + } + } + XFree(cw); + } + } + + return (m_xscreensaver != None); +} + +void +CXWindowsScreenSaver::setXScreenSaver(Window window) +{ + LOG((CLOG_DEBUG "xscreensaver window: 0x%08x", window)); + + // save window + m_xscreensaver = window; + + if (m_xscreensaver != None) { + // clear old watch list + clearWatchForXScreenSaver(); + + // see if xscreensaver is active + bool error = false; + XWindowAttributes attr; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XGetWindowAttributes(m_display, m_xscreensaver, &attr); + } + setXScreenSaverActive(!error && attr.map_state != IsUnmapped); + + // save current DPMS state; xscreensaver may have changed it. + m_dpmsEnabled = isDPMSEnabled(); + } + else { + // screen saver can't be active if it doesn't exist + setXScreenSaverActive(false); + + // start watching for xscreensaver + watchForXScreenSaver(); + } +} + +bool +CXWindowsScreenSaver::isXScreenSaver(Window w) const +{ + // check for m_atomScreenSaverVersion string property + Atom type; + return (CXWindowsUtil::getWindowProperty(m_display, w, + m_atomScreenSaverVersion, + NULL, &type, NULL, False) && + type == XA_STRING); +} + +void +CXWindowsScreenSaver::setXScreenSaverActive(bool activated) +{ + if (m_xscreensaverActive != activated) { + LOG((CLOG_DEBUG "xscreensaver %s on window 0x%08x", activated ? "activated" : "deactivated", m_xscreensaver)); + m_xscreensaverActive = activated; + + // if screen saver was activated forcefully (i.e. against + // our will) then just accept it. don't try to keep it + // from activating since that'll just pop up the password + // dialog if locking is enabled. + m_suppressDisable = activated; + updateDisableTimer(); + + if (activated) { + EVENTQUEUE->addEvent(CEvent( + IPlatformScreen::getScreensaverActivatedEvent(), + m_eventTarget)); + } + else { + EVENTQUEUE->addEvent(CEvent( + IPlatformScreen::getScreensaverDeactivatedEvent(), + m_eventTarget)); + } + } +} + +void +CXWindowsScreenSaver::sendXScreenSaverCommand(Atom cmd, long arg1, long arg2) +{ + XEvent event; + event.xclient.type = ClientMessage; + event.xclient.display = m_display; + event.xclient.window = m_xscreensaverSink; + event.xclient.message_type = m_atomScreenSaver; + event.xclient.format = 32; + event.xclient.data.l[0] = static_cast(cmd); + event.xclient.data.l[1] = arg1; + event.xclient.data.l[2] = arg2; + event.xclient.data.l[3] = 0; + event.xclient.data.l[4] = 0; + + LOG((CLOG_DEBUG "send xscreensaver command: %d %d %d", (long)cmd, arg1, arg2)); + bool error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XSendEvent(m_display, m_xscreensaver, False, 0, &event); + } + if (error) { + findXScreenSaver(); + } +} + +void +CXWindowsScreenSaver::watchForXScreenSaver() +{ + // clear old watch list + clearWatchForXScreenSaver(); + + // add every child of the root to the list of windows to watch + Window root = DefaultRootWindow(m_display); + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + addWatchXScreenSaver(cw[i]); + } + XFree(cw); + } + + // now check for xscreensaver window in case it set the property + // before we could request property change events. + if (findXScreenSaver()) { + // found it so clear out our watch list + clearWatchForXScreenSaver(); + } +} + +void +CXWindowsScreenSaver::clearWatchForXScreenSaver() +{ + // stop watching all windows + CXWindowsUtil::CErrorLock lock(m_display); + for (CWatchList::iterator index = m_watchWindows.begin(); + index != m_watchWindows.end(); ++index) { + XSelectInput(m_display, index->first, index->second); + } + m_watchWindows.clear(); +} + +void +CXWindowsScreenSaver::addWatchXScreenSaver(Window window) +{ + // get window attributes + bool error = false; + XWindowAttributes attr; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XGetWindowAttributes(m_display, window, &attr); + } + + // if successful and window uses override_redirect (like xscreensaver + // does) then watch it for property changes. + if (!error && attr.override_redirect == True) { + error = false; + { + CXWindowsUtil::CErrorLock lock(m_display, &error); + XSelectInput(m_display, window, + attr.your_event_mask | PropertyChangeMask); + } + if (!error) { + // if successful then add the window to our list + m_watchWindows.insert(std::make_pair(window, attr.your_event_mask)); + } + } +} + +void +CXWindowsScreenSaver::updateDisableTimer() +{ + if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) { + // 5 seconds should be plenty often to suppress the screen saver + m_disableTimer = EVENTQUEUE->newTimer(5.0, this); + } + else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) { + EVENTQUEUE->deleteTimer(m_disableTimer); + m_disableTimer = NULL; + } +} + +void +CXWindowsScreenSaver::handleDisableTimer(const CEvent&, void*) +{ + // send fake mouse motion directly to xscreensaver + if (m_xscreensaver != None) { + XEvent event; + event.xmotion.type = MotionNotify; + event.xmotion.display = m_display; + event.xmotion.window = m_xscreensaver; + event.xmotion.root = DefaultRootWindow(m_display); + event.xmotion.subwindow = None; + event.xmotion.time = CurrentTime; + event.xmotion.x = m_disablePos; + event.xmotion.y = 0; + event.xmotion.x_root = m_disablePos; + event.xmotion.y_root = 0; + event.xmotion.state = 0; + event.xmotion.is_hint = NotifyNormal; + event.xmotion.same_screen = True; + + CXWindowsUtil::CErrorLock lock(m_display); + XSendEvent(m_display, m_xscreensaver, False, 0, &event); + + m_disablePos = 20 - m_disablePos; + } +} + +void +CXWindowsScreenSaver::activateDPMS(bool activate) +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + // DPMSForceLevel will generate a BadMatch if DPMS is disabled + CXWindowsUtil::CErrorLock lock(m_display); + DPMSForceLevel(m_display, activate ? DPMSModeStandby : DPMSModeOn); + } +#endif +} + +void +CXWindowsScreenSaver::enableDPMS(bool enable) +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + if (enable) { + DPMSEnable(m_display); + } + else { + DPMSDisable(m_display); + } + } +#endif +} + +bool +CXWindowsScreenSaver::isDPMSEnabled() const +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + CARD16 level; + BOOL state; + DPMSInfo(m_display, &level, &state); + return (state != False); + } + else { + return false; + } +#else + return false; +#endif +} + +bool +CXWindowsScreenSaver::isDPMSActivated() const +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + CARD16 level; + BOOL state; + DPMSInfo(m_display, &level, &state); + return (level != DPMSModeOn); + } + else { + return false; + } +#else + return false; +#endif +} diff --git a/lib/platform/CXWindowsScreenSaver.h b/lib/platform/CXWindowsScreenSaver.h new file mode 100644 index 00000000..991b20e1 --- /dev/null +++ b/lib/platform/CXWindowsScreenSaver.h @@ -0,0 +1,164 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSSCREENSAVER_H +#define CXWINDOWSSCREENSAVER_H + +#include "IScreenSaver.h" +#include "stdmap.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +class CEvent; +class CEventQueueTimer; + +//! X11 screen saver implementation +class CXWindowsScreenSaver : public IScreenSaver { +public: + CXWindowsScreenSaver(Display*, Window, void* eventTarget); + virtual ~CXWindowsScreenSaver(); + + //! @name manipulators + //@{ + + //! Event filtering + /*! + Should be called for each system event before event translation and + dispatch. Returns true to skip translation and dispatch. + */ + bool handleXEvent(const XEvent*); + + //! Destroy without the display + /*! + Tells this object to delete itself without using the X11 display. + It may leak some resources as a result. + */ + void destroy(); + + //@} + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + // find and set the running xscreensaver's window. returns true iff + // found. + bool findXScreenSaver(); + + // set the xscreensaver's window, updating the activation state flag + void setXScreenSaver(Window); + + // returns true if the window appears to be the xscreensaver window + bool isXScreenSaver(Window) const; + + // set xscreensaver's activation state flag. sends notification + // if the state has changed. + void setXScreenSaverActive(bool activated); + + // send a command to xscreensaver + void sendXScreenSaverCommand(Atom, long = 0, long = 0); + + // watch all windows that could potentially be the xscreensaver for + // the events that will confirm it. + void watchForXScreenSaver(); + + // stop watching all watched windows + void clearWatchForXScreenSaver(); + + // add window to the watch list + void addWatchXScreenSaver(Window window); + + // install/uninstall the job used to suppress the screensaver + void updateDisableTimer(); + + // called periodically to prevent the screen saver from starting + void handleDisableTimer(const CEvent&, void*); + + // force DPMS to activate or deactivate + void activateDPMS(bool activate); + + // enable/disable DPMS screen saver + void enableDPMS(bool); + + // check if DPMS is enabled + bool isDPMSEnabled() const; + + // check if DPMS is activate + bool isDPMSActivated() const; + +private: + typedef std::map CWatchList; + + // the X display + Display* m_display; + + // window to receive xscreensaver repsonses + Window m_xscreensaverSink; + + // the target for the events we generate + void* m_eventTarget; + + // xscreensaver's window + Window m_xscreensaver; + + // xscreensaver activation state + bool m_xscreensaverActive; + + // old event mask on root window + long m_rootEventMask; + + // potential xscreensaver windows being watched + CWatchList m_watchWindows; + + // atoms used to communicate with xscreensaver's window + Atom m_atomScreenSaver; + Atom m_atomScreenSaverVersion; + Atom m_atomScreenSaverActivate; + Atom m_atomScreenSaverDeactivate; + + // built-in screen saver settings + int m_timeout; + int m_interval; + int m_preferBlanking; + int m_allowExposures; + + // DPMS screen saver settings + bool m_dpms; + bool m_dpmsEnabled; + + // true iff the client wants the screen saver suppressed + bool m_disabled; + + // true iff we're ignoring m_disabled. this is true, for example, + // when the client has called activate() and so presumably wants + // to activate the screen saver even if disabled. + bool m_suppressDisable; + + // the disable timer (NULL if not installed) + CEventQueueTimer* m_disableTimer; + + // fake mouse motion position for suppressing the screen saver. + // xscreensaver since 2.21 requires the mouse to move more than 10 + // pixels to be considered significant. + SInt32 m_disablePos; +}; + +#endif diff --git a/lib/platform/CXWindowsUtil.cpp b/lib/platform/CXWindowsUtil.cpp new file mode 100644 index 00000000..a33fc4ee --- /dev/null +++ b/lib/platform/CXWindowsUtil.cpp @@ -0,0 +1,1766 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CXWindowsUtil.h" +#include "KeyTypes.h" +#include "CThread.h" +#include "CLog.h" +#include "CStringUtil.h" +#include +#define XK_APL +#define XK_ARABIC +#define XK_ARMENIAN +#define XK_CAUCASUS +#define XK_CURRENCY +#define XK_CYRILLIC +#define XK_GEORGIAN +#define XK_GREEK +#define XK_HEBREW +#define XK_KATAKANA +#define XK_KOREAN +#define XK_LATIN1 +#define XK_LATIN2 +#define XK_LATIN3 +#define XK_LATIN4 +#define XK_LATIN8 +#define XK_LATIN9 +#define XK_MISCELLANY +#define XK_PUBLISHING +#define XK_SPECIAL +#define XK_TECHNICAL +#define XK_THAI +#define XK_VIETNAMESE +#define XK_XKB_KEYS +#include + +#if !defined(XK_OE) +#define XK_OE 0x13bc +#endif +#if !defined(XK_oe) +#define XK_oe 0x13bd +#endif +#if !defined(XK_Ydiaeresis) +#define XK_Ydiaeresis 0x13be +#endif + +/* + * This table maps keysym values into the corresponding ISO 10646 + * (UCS, Unicode) values. + * + * The array keysymtab[] contains pairs of X11 keysym values for graphical + * characters and the corresponding Unicode value. + * + * Author: Markus G. Kuhn , + * University of Cambridge, April 2001 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + */ + +struct codepair { + KeySym keysym; + UInt32 ucs4; +} s_keymap[] = { +{ XK_Aogonek, 0x0104 }, /* LATIN CAPITAL LETTER A WITH OGONEK */ +{ XK_breve, 0x02d8 }, /* BREVE */ +{ XK_Lstroke, 0x0141 }, /* LATIN CAPITAL LETTER L WITH STROKE */ +{ XK_Lcaron, 0x013d }, /* LATIN CAPITAL LETTER L WITH CARON */ +{ XK_Sacute, 0x015a }, /* LATIN CAPITAL LETTER S WITH ACUTE */ +{ XK_Scaron, 0x0160 }, /* LATIN CAPITAL LETTER S WITH CARON */ +{ XK_Scedilla, 0x015e }, /* LATIN CAPITAL LETTER S WITH CEDILLA */ +{ XK_Tcaron, 0x0164 }, /* LATIN CAPITAL LETTER T WITH CARON */ +{ XK_Zacute, 0x0179 }, /* LATIN CAPITAL LETTER Z WITH ACUTE */ +{ XK_Zcaron, 0x017d }, /* LATIN CAPITAL LETTER Z WITH CARON */ +{ XK_Zabovedot, 0x017b }, /* LATIN CAPITAL LETTER Z WITH DOT ABOVE */ +{ XK_aogonek, 0x0105 }, /* LATIN SMALL LETTER A WITH OGONEK */ +{ XK_ogonek, 0x02db }, /* OGONEK */ +{ XK_lstroke, 0x0142 }, /* LATIN SMALL LETTER L WITH STROKE */ +{ XK_lcaron, 0x013e }, /* LATIN SMALL LETTER L WITH CARON */ +{ XK_sacute, 0x015b }, /* LATIN SMALL LETTER S WITH ACUTE */ +{ XK_caron, 0x02c7 }, /* CARON */ +{ XK_scaron, 0x0161 }, /* LATIN SMALL LETTER S WITH CARON */ +{ XK_scedilla, 0x015f }, /* LATIN SMALL LETTER S WITH CEDILLA */ +{ XK_tcaron, 0x0165 }, /* LATIN SMALL LETTER T WITH CARON */ +{ XK_zacute, 0x017a }, /* LATIN SMALL LETTER Z WITH ACUTE */ +{ XK_doubleacute, 0x02dd }, /* DOUBLE ACUTE ACCENT */ +{ XK_zcaron, 0x017e }, /* LATIN SMALL LETTER Z WITH CARON */ +{ XK_zabovedot, 0x017c }, /* LATIN SMALL LETTER Z WITH DOT ABOVE */ +{ XK_Racute, 0x0154 }, /* LATIN CAPITAL LETTER R WITH ACUTE */ +{ XK_Abreve, 0x0102 }, /* LATIN CAPITAL LETTER A WITH BREVE */ +{ XK_Lacute, 0x0139 }, /* LATIN CAPITAL LETTER L WITH ACUTE */ +{ XK_Cacute, 0x0106 }, /* LATIN CAPITAL LETTER C WITH ACUTE */ +{ XK_Ccaron, 0x010c }, /* LATIN CAPITAL LETTER C WITH CARON */ +{ XK_Eogonek, 0x0118 }, /* LATIN CAPITAL LETTER E WITH OGONEK */ +{ XK_Ecaron, 0x011a }, /* LATIN CAPITAL LETTER E WITH CARON */ +{ XK_Dcaron, 0x010e }, /* LATIN CAPITAL LETTER D WITH CARON */ +{ XK_Dstroke, 0x0110 }, /* LATIN CAPITAL LETTER D WITH STROKE */ +{ XK_Nacute, 0x0143 }, /* LATIN CAPITAL LETTER N WITH ACUTE */ +{ XK_Ncaron, 0x0147 }, /* LATIN CAPITAL LETTER N WITH CARON */ +{ XK_Odoubleacute, 0x0150 }, /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ +{ XK_Rcaron, 0x0158 }, /* LATIN CAPITAL LETTER R WITH CARON */ +{ XK_Uring, 0x016e }, /* LATIN CAPITAL LETTER U WITH RING ABOVE */ +{ XK_Udoubleacute, 0x0170 }, /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ +{ XK_Tcedilla, 0x0162 }, /* LATIN CAPITAL LETTER T WITH CEDILLA */ +{ XK_racute, 0x0155 }, /* LATIN SMALL LETTER R WITH ACUTE */ +{ XK_abreve, 0x0103 }, /* LATIN SMALL LETTER A WITH BREVE */ +{ XK_lacute, 0x013a }, /* LATIN SMALL LETTER L WITH ACUTE */ +{ XK_cacute, 0x0107 }, /* LATIN SMALL LETTER C WITH ACUTE */ +{ XK_ccaron, 0x010d }, /* LATIN SMALL LETTER C WITH CARON */ +{ XK_eogonek, 0x0119 }, /* LATIN SMALL LETTER E WITH OGONEK */ +{ XK_ecaron, 0x011b }, /* LATIN SMALL LETTER E WITH CARON */ +{ XK_dcaron, 0x010f }, /* LATIN SMALL LETTER D WITH CARON */ +{ XK_dstroke, 0x0111 }, /* LATIN SMALL LETTER D WITH STROKE */ +{ XK_nacute, 0x0144 }, /* LATIN SMALL LETTER N WITH ACUTE */ +{ XK_ncaron, 0x0148 }, /* LATIN SMALL LETTER N WITH CARON */ +{ XK_odoubleacute, 0x0151 }, /* LATIN SMALL LETTER O WITH DOUBLE ACUTE */ +{ XK_rcaron, 0x0159 }, /* LATIN SMALL LETTER R WITH CARON */ +{ XK_uring, 0x016f }, /* LATIN SMALL LETTER U WITH RING ABOVE */ +{ XK_udoubleacute, 0x0171 }, /* LATIN SMALL LETTER U WITH DOUBLE ACUTE */ +{ XK_tcedilla, 0x0163 }, /* LATIN SMALL LETTER T WITH CEDILLA */ +{ XK_abovedot, 0x02d9 }, /* DOT ABOVE */ +{ XK_Hstroke, 0x0126 }, /* LATIN CAPITAL LETTER H WITH STROKE */ +{ XK_Hcircumflex, 0x0124 }, /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ +{ XK_Iabovedot, 0x0130 }, /* LATIN CAPITAL LETTER I WITH DOT ABOVE */ +{ XK_Gbreve, 0x011e }, /* LATIN CAPITAL LETTER G WITH BREVE */ +{ XK_Jcircumflex, 0x0134 }, /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ +{ XK_hstroke, 0x0127 }, /* LATIN SMALL LETTER H WITH STROKE */ +{ XK_hcircumflex, 0x0125 }, /* LATIN SMALL LETTER H WITH CIRCUMFLEX */ +{ XK_idotless, 0x0131 }, /* LATIN SMALL LETTER DOTLESS I */ +{ XK_gbreve, 0x011f }, /* LATIN SMALL LETTER G WITH BREVE */ +{ XK_jcircumflex, 0x0135 }, /* LATIN SMALL LETTER J WITH CIRCUMFLEX */ +{ XK_Cabovedot, 0x010a }, /* LATIN CAPITAL LETTER C WITH DOT ABOVE */ +{ XK_Ccircumflex, 0x0108 }, /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ +{ XK_Gabovedot, 0x0120 }, /* LATIN CAPITAL LETTER G WITH DOT ABOVE */ +{ XK_Gcircumflex, 0x011c }, /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ +{ XK_Ubreve, 0x016c }, /* LATIN CAPITAL LETTER U WITH BREVE */ +{ XK_Scircumflex, 0x015c }, /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ +{ XK_cabovedot, 0x010b }, /* LATIN SMALL LETTER C WITH DOT ABOVE */ +{ XK_ccircumflex, 0x0109 }, /* LATIN SMALL LETTER C WITH CIRCUMFLEX */ +{ XK_gabovedot, 0x0121 }, /* LATIN SMALL LETTER G WITH DOT ABOVE */ +{ XK_gcircumflex, 0x011d }, /* LATIN SMALL LETTER G WITH CIRCUMFLEX */ +{ XK_ubreve, 0x016d }, /* LATIN SMALL LETTER U WITH BREVE */ +{ XK_scircumflex, 0x015d }, /* LATIN SMALL LETTER S WITH CIRCUMFLEX */ +{ XK_kra, 0x0138 }, /* LATIN SMALL LETTER KRA */ +{ XK_Rcedilla, 0x0156 }, /* LATIN CAPITAL LETTER R WITH CEDILLA */ +{ XK_Itilde, 0x0128 }, /* LATIN CAPITAL LETTER I WITH TILDE */ +{ XK_Lcedilla, 0x013b }, /* LATIN CAPITAL LETTER L WITH CEDILLA */ +{ XK_Emacron, 0x0112 }, /* LATIN CAPITAL LETTER E WITH MACRON */ +{ XK_Gcedilla, 0x0122 }, /* LATIN CAPITAL LETTER G WITH CEDILLA */ +{ XK_Tslash, 0x0166 }, /* LATIN CAPITAL LETTER T WITH STROKE */ +{ XK_rcedilla, 0x0157 }, /* LATIN SMALL LETTER R WITH CEDILLA */ +{ XK_itilde, 0x0129 }, /* LATIN SMALL LETTER I WITH TILDE */ +{ XK_lcedilla, 0x013c }, /* LATIN SMALL LETTER L WITH CEDILLA */ +{ XK_emacron, 0x0113 }, /* LATIN SMALL LETTER E WITH MACRON */ +{ XK_gcedilla, 0x0123 }, /* LATIN SMALL LETTER G WITH CEDILLA */ +{ XK_tslash, 0x0167 }, /* LATIN SMALL LETTER T WITH STROKE */ +{ XK_ENG, 0x014a }, /* LATIN CAPITAL LETTER ENG */ +{ XK_eng, 0x014b }, /* LATIN SMALL LETTER ENG */ +{ XK_Amacron, 0x0100 }, /* LATIN CAPITAL LETTER A WITH MACRON */ +{ XK_Iogonek, 0x012e }, /* LATIN CAPITAL LETTER I WITH OGONEK */ +{ XK_Eabovedot, 0x0116 }, /* LATIN CAPITAL LETTER E WITH DOT ABOVE */ +{ XK_Imacron, 0x012a }, /* LATIN CAPITAL LETTER I WITH MACRON */ +{ XK_Ncedilla, 0x0145 }, /* LATIN CAPITAL LETTER N WITH CEDILLA */ +{ XK_Omacron, 0x014c }, /* LATIN CAPITAL LETTER O WITH MACRON */ +{ XK_Kcedilla, 0x0136 }, /* LATIN CAPITAL LETTER K WITH CEDILLA */ +{ XK_Uogonek, 0x0172 }, /* LATIN CAPITAL LETTER U WITH OGONEK */ +{ XK_Utilde, 0x0168 }, /* LATIN CAPITAL LETTER U WITH TILDE */ +{ XK_Umacron, 0x016a }, /* LATIN CAPITAL LETTER U WITH MACRON */ +{ XK_amacron, 0x0101 }, /* LATIN SMALL LETTER A WITH MACRON */ +{ XK_iogonek, 0x012f }, /* LATIN SMALL LETTER I WITH OGONEK */ +{ XK_eabovedot, 0x0117 }, /* LATIN SMALL LETTER E WITH DOT ABOVE */ +{ XK_imacron, 0x012b }, /* LATIN SMALL LETTER I WITH MACRON */ +{ XK_ncedilla, 0x0146 }, /* LATIN SMALL LETTER N WITH CEDILLA */ +{ XK_omacron, 0x014d }, /* LATIN SMALL LETTER O WITH MACRON */ +{ XK_kcedilla, 0x0137 }, /* LATIN SMALL LETTER K WITH CEDILLA */ +{ XK_uogonek, 0x0173 }, /* LATIN SMALL LETTER U WITH OGONEK */ +{ XK_utilde, 0x0169 }, /* LATIN SMALL LETTER U WITH TILDE */ +{ XK_umacron, 0x016b }, /* LATIN SMALL LETTER U WITH MACRON */ +#if defined(XK_Babovedot) +{ XK_Babovedot, 0x1e02 }, /* LATIN CAPITAL LETTER B WITH DOT ABOVE */ +{ XK_babovedot, 0x1e03 }, /* LATIN SMALL LETTER B WITH DOT ABOVE */ +{ XK_Dabovedot, 0x1e0a }, /* LATIN CAPITAL LETTER D WITH DOT ABOVE */ +{ XK_Wgrave, 0x1e80 }, /* LATIN CAPITAL LETTER W WITH GRAVE */ +{ XK_Wacute, 0x1e82 }, /* LATIN CAPITAL LETTER W WITH ACUTE */ +{ XK_dabovedot, 0x1e0b }, /* LATIN SMALL LETTER D WITH DOT ABOVE */ +{ XK_Ygrave, 0x1ef2 }, /* LATIN CAPITAL LETTER Y WITH GRAVE */ +{ XK_Fabovedot, 0x1e1e }, /* LATIN CAPITAL LETTER F WITH DOT ABOVE */ +{ XK_fabovedot, 0x1e1f }, /* LATIN SMALL LETTER F WITH DOT ABOVE */ +{ XK_Mabovedot, 0x1e40 }, /* LATIN CAPITAL LETTER M WITH DOT ABOVE */ +{ XK_mabovedot, 0x1e41 }, /* LATIN SMALL LETTER M WITH DOT ABOVE */ +{ XK_Pabovedot, 0x1e56 }, /* LATIN CAPITAL LETTER P WITH DOT ABOVE */ +{ XK_wgrave, 0x1e81 }, /* LATIN SMALL LETTER W WITH GRAVE */ +{ XK_pabovedot, 0x1e57 }, /* LATIN SMALL LETTER P WITH DOT ABOVE */ +{ XK_wacute, 0x1e83 }, /* LATIN SMALL LETTER W WITH ACUTE */ +{ XK_Sabovedot, 0x1e60 }, /* LATIN CAPITAL LETTER S WITH DOT ABOVE */ +{ XK_ygrave, 0x1ef3 }, /* LATIN SMALL LETTER Y WITH GRAVE */ +{ XK_Wdiaeresis, 0x1e84 }, /* LATIN CAPITAL LETTER W WITH DIAERESIS */ +{ XK_wdiaeresis, 0x1e85 }, /* LATIN SMALL LETTER W WITH DIAERESIS */ +{ XK_sabovedot, 0x1e61 }, /* LATIN SMALL LETTER S WITH DOT ABOVE */ +{ XK_Wcircumflex, 0x0174 }, /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX */ +{ XK_Tabovedot, 0x1e6a }, /* LATIN CAPITAL LETTER T WITH DOT ABOVE */ +{ XK_Ycircumflex, 0x0176 }, /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */ +{ XK_wcircumflex, 0x0175 }, /* LATIN SMALL LETTER W WITH CIRCUMFLEX */ +{ XK_tabovedot, 0x1e6b }, /* LATIN SMALL LETTER T WITH DOT ABOVE */ +{ XK_ycircumflex, 0x0177 }, /* LATIN SMALL LETTER Y WITH CIRCUMFLEX */ +#endif // defined(XK_Babovedot) +#if defined(XK_overline) +{ XK_overline, 0x203e }, /* OVERLINE */ +{ XK_kana_fullstop, 0x3002 }, /* IDEOGRAPHIC FULL STOP */ +{ XK_kana_openingbracket, 0x300c }, /* LEFT CORNER BRACKET */ +{ XK_kana_closingbracket, 0x300d }, /* RIGHT CORNER BRACKET */ +{ XK_kana_comma, 0x3001 }, /* IDEOGRAPHIC COMMA */ +{ XK_kana_conjunctive, 0x30fb }, /* KATAKANA MIDDLE DOT */ +{ XK_kana_WO, 0x30f2 }, /* KATAKANA LETTER WO */ +{ XK_kana_a, 0x30a1 }, /* KATAKANA LETTER SMALL A */ +{ XK_kana_i, 0x30a3 }, /* KATAKANA LETTER SMALL I */ +{ XK_kana_u, 0x30a5 }, /* KATAKANA LETTER SMALL U */ +{ XK_kana_e, 0x30a7 }, /* KATAKANA LETTER SMALL E */ +{ XK_kana_o, 0x30a9 }, /* KATAKANA LETTER SMALL O */ +{ XK_kana_ya, 0x30e3 }, /* KATAKANA LETTER SMALL YA */ +{ XK_kana_yu, 0x30e5 }, /* KATAKANA LETTER SMALL YU */ +{ XK_kana_yo, 0x30e7 }, /* KATAKANA LETTER SMALL YO */ +{ XK_kana_tsu, 0x30c3 }, /* KATAKANA LETTER SMALL TU */ +{ XK_prolongedsound, 0x30fc }, /* KATAKANA-HIRAGANA PROLONGED SOUND MARK */ +{ XK_kana_A, 0x30a2 }, /* KATAKANA LETTER A */ +{ XK_kana_I, 0x30a4 }, /* KATAKANA LETTER I */ +{ XK_kana_U, 0x30a6 }, /* KATAKANA LETTER U */ +{ XK_kana_E, 0x30a8 }, /* KATAKANA LETTER E */ +{ XK_kana_O, 0x30aa }, /* KATAKANA LETTER O */ +{ XK_kana_KA, 0x30ab }, /* KATAKANA LETTER KA */ +{ XK_kana_KI, 0x30ad }, /* KATAKANA LETTER KI */ +{ XK_kana_KU, 0x30af }, /* KATAKANA LETTER KU */ +{ XK_kana_KE, 0x30b1 }, /* KATAKANA LETTER KE */ +{ XK_kana_KO, 0x30b3 }, /* KATAKANA LETTER KO */ +{ XK_kana_SA, 0x30b5 }, /* KATAKANA LETTER SA */ +{ XK_kana_SHI, 0x30b7 }, /* KATAKANA LETTER SI */ +{ XK_kana_SU, 0x30b9 }, /* KATAKANA LETTER SU */ +{ XK_kana_SE, 0x30bb }, /* KATAKANA LETTER SE */ +{ XK_kana_SO, 0x30bd }, /* KATAKANA LETTER SO */ +{ XK_kana_TA, 0x30bf }, /* KATAKANA LETTER TA */ +{ XK_kana_CHI, 0x30c1 }, /* KATAKANA LETTER TI */ +{ XK_kana_TSU, 0x30c4 }, /* KATAKANA LETTER TU */ +{ XK_kana_TE, 0x30c6 }, /* KATAKANA LETTER TE */ +{ XK_kana_TO, 0x30c8 }, /* KATAKANA LETTER TO */ +{ XK_kana_NA, 0x30ca }, /* KATAKANA LETTER NA */ +{ XK_kana_NI, 0x30cb }, /* KATAKANA LETTER NI */ +{ XK_kana_NU, 0x30cc }, /* KATAKANA LETTER NU */ +{ XK_kana_NE, 0x30cd }, /* KATAKANA LETTER NE */ +{ XK_kana_NO, 0x30ce }, /* KATAKANA LETTER NO */ +{ XK_kana_HA, 0x30cf }, /* KATAKANA LETTER HA */ +{ XK_kana_HI, 0x30d2 }, /* KATAKANA LETTER HI */ +{ XK_kana_FU, 0x30d5 }, /* KATAKANA LETTER HU */ +{ XK_kana_HE, 0x30d8 }, /* KATAKANA LETTER HE */ +{ XK_kana_HO, 0x30db }, /* KATAKANA LETTER HO */ +{ XK_kana_MA, 0x30de }, /* KATAKANA LETTER MA */ +{ XK_kana_MI, 0x30df }, /* KATAKANA LETTER MI */ +{ XK_kana_MU, 0x30e0 }, /* KATAKANA LETTER MU */ +{ XK_kana_ME, 0x30e1 }, /* KATAKANA LETTER ME */ +{ XK_kana_MO, 0x30e2 }, /* KATAKANA LETTER MO */ +{ XK_kana_YA, 0x30e4 }, /* KATAKANA LETTER YA */ +{ XK_kana_YU, 0x30e6 }, /* KATAKANA LETTER YU */ +{ XK_kana_YO, 0x30e8 }, /* KATAKANA LETTER YO */ +{ XK_kana_RA, 0x30e9 }, /* KATAKANA LETTER RA */ +{ XK_kana_RI, 0x30ea }, /* KATAKANA LETTER RI */ +{ XK_kana_RU, 0x30eb }, /* KATAKANA LETTER RU */ +{ XK_kana_RE, 0x30ec }, /* KATAKANA LETTER RE */ +{ XK_kana_RO, 0x30ed }, /* KATAKANA LETTER RO */ +{ XK_kana_WA, 0x30ef }, /* KATAKANA LETTER WA */ +{ XK_kana_N, 0x30f3 }, /* KATAKANA LETTER N */ +{ XK_voicedsound, 0x309b }, /* KATAKANA-HIRAGANA VOICED SOUND MARK */ +{ XK_semivoicedsound, 0x309c }, /* KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ +#endif // defined(XK_overline) +#if defined(XK_Farsi_0) +{ XK_Farsi_0, 0x06f0 }, /* EXTENDED ARABIC-INDIC DIGIT 0 */ +{ XK_Farsi_1, 0x06f1 }, /* EXTENDED ARABIC-INDIC DIGIT 1 */ +{ XK_Farsi_2, 0x06f2 }, /* EXTENDED ARABIC-INDIC DIGIT 2 */ +{ XK_Farsi_3, 0x06f3 }, /* EXTENDED ARABIC-INDIC DIGIT 3 */ +{ XK_Farsi_4, 0x06f4 }, /* EXTENDED ARABIC-INDIC DIGIT 4 */ +{ XK_Farsi_5, 0x06f5 }, /* EXTENDED ARABIC-INDIC DIGIT 5 */ +{ XK_Farsi_6, 0x06f6 }, /* EXTENDED ARABIC-INDIC DIGIT 6 */ +{ XK_Farsi_7, 0x06f7 }, /* EXTENDED ARABIC-INDIC DIGIT 7 */ +{ XK_Farsi_8, 0x06f8 }, /* EXTENDED ARABIC-INDIC DIGIT 8 */ +{ XK_Farsi_9, 0x06f9 }, /* EXTENDED ARABIC-INDIC DIGIT 9 */ +{ XK_Arabic_percent, 0x066a }, /* ARABIC PERCENT */ +{ XK_Arabic_superscript_alef, 0x0670 }, /* ARABIC LETTER SUPERSCRIPT ALEF */ +{ XK_Arabic_tteh, 0x0679 }, /* ARABIC LETTER TTEH */ +{ XK_Arabic_peh, 0x067e }, /* ARABIC LETTER PEH */ +{ XK_Arabic_tcheh, 0x0686 }, /* ARABIC LETTER TCHEH */ +{ XK_Arabic_ddal, 0x0688 }, /* ARABIC LETTER DDAL */ +{ XK_Arabic_rreh, 0x0691 }, /* ARABIC LETTER RREH */ +{ XK_Arabic_comma, 0x060c }, /* ARABIC COMMA */ +{ XK_Arabic_fullstop, 0x06d4 }, /* ARABIC FULLSTOP */ +{ XK_Arabic_semicolon, 0x061b }, /* ARABIC SEMICOLON */ +{ XK_Arabic_0, 0x0660 }, /* ARABIC 0 */ +{ XK_Arabic_1, 0x0661 }, /* ARABIC 1 */ +{ XK_Arabic_2, 0x0662 }, /* ARABIC 2 */ +{ XK_Arabic_3, 0x0663 }, /* ARABIC 3 */ +{ XK_Arabic_4, 0x0664 }, /* ARABIC 4 */ +{ XK_Arabic_5, 0x0665 }, /* ARABIC 5 */ +{ XK_Arabic_6, 0x0666 }, /* ARABIC 6 */ +{ XK_Arabic_7, 0x0667 }, /* ARABIC 7 */ +{ XK_Arabic_8, 0x0668 }, /* ARABIC 8 */ +{ XK_Arabic_9, 0x0669 }, /* ARABIC 9 */ +{ XK_Arabic_question_mark, 0x061f }, /* ARABIC QUESTION MARK */ +{ XK_Arabic_hamza, 0x0621 }, /* ARABIC LETTER HAMZA */ +{ XK_Arabic_maddaonalef, 0x0622 }, /* ARABIC LETTER ALEF WITH MADDA ABOVE */ +{ XK_Arabic_hamzaonalef, 0x0623 }, /* ARABIC LETTER ALEF WITH HAMZA ABOVE */ +{ XK_Arabic_hamzaonwaw, 0x0624 }, /* ARABIC LETTER WAW WITH HAMZA ABOVE */ +{ XK_Arabic_hamzaunderalef, 0x0625 }, /* ARABIC LETTER ALEF WITH HAMZA BELOW */ +{ XK_Arabic_hamzaonyeh, 0x0626 }, /* ARABIC LETTER YEH WITH HAMZA ABOVE */ +{ XK_Arabic_alef, 0x0627 }, /* ARABIC LETTER ALEF */ +{ XK_Arabic_beh, 0x0628 }, /* ARABIC LETTER BEH */ +{ XK_Arabic_tehmarbuta, 0x0629 }, /* ARABIC LETTER TEH MARBUTA */ +{ XK_Arabic_teh, 0x062a }, /* ARABIC LETTER TEH */ +{ XK_Arabic_theh, 0x062b }, /* ARABIC LETTER THEH */ +{ XK_Arabic_jeem, 0x062c }, /* ARABIC LETTER JEEM */ +{ XK_Arabic_hah, 0x062d }, /* ARABIC LETTER HAH */ +{ XK_Arabic_khah, 0x062e }, /* ARABIC LETTER KHAH */ +{ XK_Arabic_dal, 0x062f }, /* ARABIC LETTER DAL */ +{ XK_Arabic_thal, 0x0630 }, /* ARABIC LETTER THAL */ +{ XK_Arabic_ra, 0x0631 }, /* ARABIC LETTER REH */ +{ XK_Arabic_zain, 0x0632 }, /* ARABIC LETTER ZAIN */ +{ XK_Arabic_seen, 0x0633 }, /* ARABIC LETTER SEEN */ +{ XK_Arabic_sheen, 0x0634 }, /* ARABIC LETTER SHEEN */ +{ XK_Arabic_sad, 0x0635 }, /* ARABIC LETTER SAD */ +{ XK_Arabic_dad, 0x0636 }, /* ARABIC LETTER DAD */ +{ XK_Arabic_tah, 0x0637 }, /* ARABIC LETTER TAH */ +{ XK_Arabic_zah, 0x0638 }, /* ARABIC LETTER ZAH */ +{ XK_Arabic_ain, 0x0639 }, /* ARABIC LETTER AIN */ +{ XK_Arabic_ghain, 0x063a }, /* ARABIC LETTER GHAIN */ +{ XK_Arabic_tatweel, 0x0640 }, /* ARABIC TATWEEL */ +{ XK_Arabic_feh, 0x0641 }, /* ARABIC LETTER FEH */ +{ XK_Arabic_qaf, 0x0642 }, /* ARABIC LETTER QAF */ +{ XK_Arabic_kaf, 0x0643 }, /* ARABIC LETTER KAF */ +{ XK_Arabic_lam, 0x0644 }, /* ARABIC LETTER LAM */ +{ XK_Arabic_meem, 0x0645 }, /* ARABIC LETTER MEEM */ +{ XK_Arabic_noon, 0x0646 }, /* ARABIC LETTER NOON */ +{ XK_Arabic_ha, 0x0647 }, /* ARABIC LETTER HEH */ +{ XK_Arabic_waw, 0x0648 }, /* ARABIC LETTER WAW */ +{ XK_Arabic_alefmaksura, 0x0649 }, /* ARABIC LETTER ALEF MAKSURA */ +{ XK_Arabic_yeh, 0x064a }, /* ARABIC LETTER YEH */ +{ XK_Arabic_fathatan, 0x064b }, /* ARABIC FATHATAN */ +{ XK_Arabic_dammatan, 0x064c }, /* ARABIC DAMMATAN */ +{ XK_Arabic_kasratan, 0x064d }, /* ARABIC KASRATAN */ +{ XK_Arabic_fatha, 0x064e }, /* ARABIC FATHA */ +{ XK_Arabic_damma, 0x064f }, /* ARABIC DAMMA */ +{ XK_Arabic_kasra, 0x0650 }, /* ARABIC KASRA */ +{ XK_Arabic_shadda, 0x0651 }, /* ARABIC SHADDA */ +{ XK_Arabic_sukun, 0x0652 }, /* ARABIC SUKUN */ +{ XK_Arabic_madda_above, 0x0653 }, /* ARABIC MADDA ABOVE */ +{ XK_Arabic_hamza_above, 0x0654 }, /* ARABIC HAMZA ABOVE */ +{ XK_Arabic_hamza_below, 0x0655 }, /* ARABIC HAMZA BELOW */ +{ XK_Arabic_jeh, 0x0698 }, /* ARABIC LETTER JEH */ +{ XK_Arabic_veh, 0x06a4 }, /* ARABIC LETTER VEH */ +{ XK_Arabic_keheh, 0x06a9 }, /* ARABIC LETTER KEHEH */ +{ XK_Arabic_gaf, 0x06af }, /* ARABIC LETTER GAF */ +{ XK_Arabic_noon_ghunna, 0x06ba }, /* ARABIC LETTER NOON GHUNNA */ +{ XK_Arabic_heh_doachashmee, 0x06be }, /* ARABIC LETTER HEH DOACHASHMEE */ +{ XK_Arabic_farsi_yeh, 0x06cc }, /* ARABIC LETTER FARSI YEH */ +{ XK_Arabic_yeh_baree, 0x06d2 }, /* ARABIC LETTER YEH BAREE */ +{ XK_Arabic_heh_goal, 0x06c1 }, /* ARABIC LETTER HEH GOAL */ +#endif // defined(XK_Farsi_0) +#if defined(XK_Serbian_dje) +{ XK_Serbian_dje, 0x0452 }, /* CYRILLIC SMALL LETTER DJE */ +{ XK_Macedonia_gje, 0x0453 }, /* CYRILLIC SMALL LETTER GJE */ +{ XK_Cyrillic_io, 0x0451 }, /* CYRILLIC SMALL LETTER IO */ +{ XK_Ukrainian_ie, 0x0454 }, /* CYRILLIC SMALL LETTER UKRAINIAN IE */ +{ XK_Macedonia_dse, 0x0455 }, /* CYRILLIC SMALL LETTER DZE */ +{ XK_Ukrainian_i, 0x0456 }, /* CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ +{ XK_Ukrainian_yi, 0x0457 }, /* CYRILLIC SMALL LETTER YI */ +{ XK_Cyrillic_je, 0x0458 }, /* CYRILLIC SMALL LETTER JE */ +{ XK_Cyrillic_lje, 0x0459 }, /* CYRILLIC SMALL LETTER LJE */ +{ XK_Cyrillic_nje, 0x045a }, /* CYRILLIC SMALL LETTER NJE */ +{ XK_Serbian_tshe, 0x045b }, /* CYRILLIC SMALL LETTER TSHE */ +{ XK_Macedonia_kje, 0x045c }, /* CYRILLIC SMALL LETTER KJE */ +#if defined(XK_Ukrainian_ghe_with_upturn) +{ XK_Ukrainian_ghe_with_upturn, 0x0491 }, /* CYRILLIC SMALL LETTER GHE WITH UPTURN */ +#endif +{ XK_Byelorussian_shortu, 0x045e }, /* CYRILLIC SMALL LETTER SHORT U */ +{ XK_Cyrillic_dzhe, 0x045f }, /* CYRILLIC SMALL LETTER DZHE */ +{ XK_numerosign, 0x2116 }, /* NUMERO SIGN */ +{ XK_Serbian_DJE, 0x0402 }, /* CYRILLIC CAPITAL LETTER DJE */ +{ XK_Macedonia_GJE, 0x0403 }, /* CYRILLIC CAPITAL LETTER GJE */ +{ XK_Cyrillic_IO, 0x0401 }, /* CYRILLIC CAPITAL LETTER IO */ +{ XK_Ukrainian_IE, 0x0404 }, /* CYRILLIC CAPITAL LETTER UKRAINIAN IE */ +{ XK_Macedonia_DSE, 0x0405 }, /* CYRILLIC CAPITAL LETTER DZE */ +{ XK_Ukrainian_I, 0x0406 }, /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ +{ XK_Ukrainian_YI, 0x0407 }, /* CYRILLIC CAPITAL LETTER YI */ +{ XK_Cyrillic_JE, 0x0408 }, /* CYRILLIC CAPITAL LETTER JE */ +{ XK_Cyrillic_LJE, 0x0409 }, /* CYRILLIC CAPITAL LETTER LJE */ +{ XK_Cyrillic_NJE, 0x040a }, /* CYRILLIC CAPITAL LETTER NJE */ +{ XK_Serbian_TSHE, 0x040b }, /* CYRILLIC CAPITAL LETTER TSHE */ +{ XK_Macedonia_KJE, 0x040c }, /* CYRILLIC CAPITAL LETTER KJE */ +#if defined(XK_Ukrainian_GHE_WITH_UPTURN) +{ XK_Ukrainian_GHE_WITH_UPTURN, 0x0490 }, /* CYRILLIC CAPITAL LETTER GHE WITH UPTURN */ +#endif +{ XK_Byelorussian_SHORTU, 0x040e }, /* CYRILLIC CAPITAL LETTER SHORT U */ +{ XK_Cyrillic_DZHE, 0x040f }, /* CYRILLIC CAPITAL LETTER DZHE */ +{ XK_Cyrillic_yu, 0x044e }, /* CYRILLIC SMALL LETTER YU */ +{ XK_Cyrillic_a, 0x0430 }, /* CYRILLIC SMALL LETTER A */ +{ XK_Cyrillic_be, 0x0431 }, /* CYRILLIC SMALL LETTER BE */ +{ XK_Cyrillic_tse, 0x0446 }, /* CYRILLIC SMALL LETTER TSE */ +{ XK_Cyrillic_de, 0x0434 }, /* CYRILLIC SMALL LETTER DE */ +{ XK_Cyrillic_ie, 0x0435 }, /* CYRILLIC SMALL LETTER IE */ +{ XK_Cyrillic_ef, 0x0444 }, /* CYRILLIC SMALL LETTER EF */ +{ XK_Cyrillic_ghe, 0x0433 }, /* CYRILLIC SMALL LETTER GHE */ +{ XK_Cyrillic_ha, 0x0445 }, /* CYRILLIC SMALL LETTER HA */ +{ XK_Cyrillic_i, 0x0438 }, /* CYRILLIC SMALL LETTER I */ +{ XK_Cyrillic_shorti, 0x0439 }, /* CYRILLIC SMALL LETTER SHORT I */ +{ XK_Cyrillic_ka, 0x043a }, /* CYRILLIC SMALL LETTER KA */ +{ XK_Cyrillic_el, 0x043b }, /* CYRILLIC SMALL LETTER EL */ +{ XK_Cyrillic_em, 0x043c }, /* CYRILLIC SMALL LETTER EM */ +{ XK_Cyrillic_en, 0x043d }, /* CYRILLIC SMALL LETTER EN */ +{ XK_Cyrillic_o, 0x043e }, /* CYRILLIC SMALL LETTER O */ +{ XK_Cyrillic_pe, 0x043f }, /* CYRILLIC SMALL LETTER PE */ +{ XK_Cyrillic_ya, 0x044f }, /* CYRILLIC SMALL LETTER YA */ +{ XK_Cyrillic_er, 0x0440 }, /* CYRILLIC SMALL LETTER ER */ +{ XK_Cyrillic_es, 0x0441 }, /* CYRILLIC SMALL LETTER ES */ +{ XK_Cyrillic_te, 0x0442 }, /* CYRILLIC SMALL LETTER TE */ +{ XK_Cyrillic_u, 0x0443 }, /* CYRILLIC SMALL LETTER U */ +{ XK_Cyrillic_zhe, 0x0436 }, /* CYRILLIC SMALL LETTER ZHE */ +{ XK_Cyrillic_ve, 0x0432 }, /* CYRILLIC SMALL LETTER VE */ +{ XK_Cyrillic_softsign, 0x044c }, /* CYRILLIC SMALL LETTER SOFT SIGN */ +{ XK_Cyrillic_yeru, 0x044b }, /* CYRILLIC SMALL LETTER YERU */ +{ XK_Cyrillic_ze, 0x0437 }, /* CYRILLIC SMALL LETTER ZE */ +{ XK_Cyrillic_sha, 0x0448 }, /* CYRILLIC SMALL LETTER SHA */ +{ XK_Cyrillic_e, 0x044d }, /* CYRILLIC SMALL LETTER E */ +{ XK_Cyrillic_shcha, 0x0449 }, /* CYRILLIC SMALL LETTER SHCHA */ +{ XK_Cyrillic_che, 0x0447 }, /* CYRILLIC SMALL LETTER CHE */ +{ XK_Cyrillic_hardsign, 0x044a }, /* CYRILLIC SMALL LETTER HARD SIGN */ +{ XK_Cyrillic_YU, 0x042e }, /* CYRILLIC CAPITAL LETTER YU */ +{ XK_Cyrillic_A, 0x0410 }, /* CYRILLIC CAPITAL LETTER A */ +{ XK_Cyrillic_BE, 0x0411 }, /* CYRILLIC CAPITAL LETTER BE */ +{ XK_Cyrillic_TSE, 0x0426 }, /* CYRILLIC CAPITAL LETTER TSE */ +{ XK_Cyrillic_DE, 0x0414 }, /* CYRILLIC CAPITAL LETTER DE */ +{ XK_Cyrillic_IE, 0x0415 }, /* CYRILLIC CAPITAL LETTER IE */ +{ XK_Cyrillic_EF, 0x0424 }, /* CYRILLIC CAPITAL LETTER EF */ +{ XK_Cyrillic_GHE, 0x0413 }, /* CYRILLIC CAPITAL LETTER GHE */ +{ XK_Cyrillic_HA, 0x0425 }, /* CYRILLIC CAPITAL LETTER HA */ +{ XK_Cyrillic_I, 0x0418 }, /* CYRILLIC CAPITAL LETTER I */ +{ XK_Cyrillic_SHORTI, 0x0419 }, /* CYRILLIC CAPITAL LETTER SHORT I */ +{ XK_Cyrillic_KA, 0x041a }, /* CYRILLIC CAPITAL LETTER KA */ +{ XK_Cyrillic_EL, 0x041b }, /* CYRILLIC CAPITAL LETTER EL */ +{ XK_Cyrillic_EM, 0x041c }, /* CYRILLIC CAPITAL LETTER EM */ +{ XK_Cyrillic_EN, 0x041d }, /* CYRILLIC CAPITAL LETTER EN */ +{ XK_Cyrillic_O, 0x041e }, /* CYRILLIC CAPITAL LETTER O */ +{ XK_Cyrillic_PE, 0x041f }, /* CYRILLIC CAPITAL LETTER PE */ +{ XK_Cyrillic_YA, 0x042f }, /* CYRILLIC CAPITAL LETTER YA */ +{ XK_Cyrillic_ER, 0x0420 }, /* CYRILLIC CAPITAL LETTER ER */ +{ XK_Cyrillic_ES, 0x0421 }, /* CYRILLIC CAPITAL LETTER ES */ +{ XK_Cyrillic_TE, 0x0422 }, /* CYRILLIC CAPITAL LETTER TE */ +{ XK_Cyrillic_U, 0x0423 }, /* CYRILLIC CAPITAL LETTER U */ +{ XK_Cyrillic_ZHE, 0x0416 }, /* CYRILLIC CAPITAL LETTER ZHE */ +{ XK_Cyrillic_VE, 0x0412 }, /* CYRILLIC CAPITAL LETTER VE */ +{ XK_Cyrillic_SOFTSIGN, 0x042c }, /* CYRILLIC CAPITAL LETTER SOFT SIGN */ +{ XK_Cyrillic_YERU, 0x042b }, /* CYRILLIC CAPITAL LETTER YERU */ +{ XK_Cyrillic_ZE, 0x0417 }, /* CYRILLIC CAPITAL LETTER ZE */ +{ XK_Cyrillic_SHA, 0x0428 }, /* CYRILLIC CAPITAL LETTER SHA */ +{ XK_Cyrillic_E, 0x042d }, /* CYRILLIC CAPITAL LETTER E */ +{ XK_Cyrillic_SHCHA, 0x0429 }, /* CYRILLIC CAPITAL LETTER SHCHA */ +{ XK_Cyrillic_CHE, 0x0427 }, /* CYRILLIC CAPITAL LETTER CHE */ +{ XK_Cyrillic_HARDSIGN, 0x042a }, /* CYRILLIC CAPITAL LETTER HARD SIGN */ +#endif // defined(XK_Serbian_dje) +#if defined(XK_Greek_ALPHAaccent) +{ XK_Greek_ALPHAaccent, 0x0386 }, /* GREEK CAPITAL LETTER ALPHA WITH TONOS */ +{ XK_Greek_EPSILONaccent, 0x0388 }, /* GREEK CAPITAL LETTER EPSILON WITH TONOS */ +{ XK_Greek_ETAaccent, 0x0389 }, /* GREEK CAPITAL LETTER ETA WITH TONOS */ +{ XK_Greek_IOTAaccent, 0x038a }, /* GREEK CAPITAL LETTER IOTA WITH TONOS */ +{ XK_Greek_IOTAdiaeresis, 0x03aa }, /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ +{ XK_Greek_OMICRONaccent, 0x038c }, /* GREEK CAPITAL LETTER OMICRON WITH TONOS */ +{ XK_Greek_UPSILONaccent, 0x038e }, /* GREEK CAPITAL LETTER UPSILON WITH TONOS */ +{ XK_Greek_UPSILONdieresis, 0x03ab }, /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ +{ XK_Greek_OMEGAaccent, 0x038f }, /* GREEK CAPITAL LETTER OMEGA WITH TONOS */ +{ XK_Greek_accentdieresis, 0x0385 }, /* GREEK DIALYTIKA TONOS */ +{ XK_Greek_horizbar, 0x2015 }, /* HORIZONTAL BAR */ +{ XK_Greek_alphaaccent, 0x03ac }, /* GREEK SMALL LETTER ALPHA WITH TONOS */ +{ XK_Greek_epsilonaccent, 0x03ad }, /* GREEK SMALL LETTER EPSILON WITH TONOS */ +{ XK_Greek_etaaccent, 0x03ae }, /* GREEK SMALL LETTER ETA WITH TONOS */ +{ XK_Greek_iotaaccent, 0x03af }, /* GREEK SMALL LETTER IOTA WITH TONOS */ +{ XK_Greek_iotadieresis, 0x03ca }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA */ +{ XK_Greek_iotaaccentdieresis, 0x0390 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ +{ XK_Greek_omicronaccent, 0x03cc }, /* GREEK SMALL LETTER OMICRON WITH TONOS */ +{ XK_Greek_upsilonaccent, 0x03cd }, /* GREEK SMALL LETTER UPSILON WITH TONOS */ +{ XK_Greek_upsilondieresis, 0x03cb }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ +{ XK_Greek_upsilonaccentdieresis, 0x03b0 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ +{ XK_Greek_omegaaccent, 0x03ce }, /* GREEK SMALL LETTER OMEGA WITH TONOS */ +{ XK_Greek_ALPHA, 0x0391 }, /* GREEK CAPITAL LETTER ALPHA */ +{ XK_Greek_BETA, 0x0392 }, /* GREEK CAPITAL LETTER BETA */ +{ XK_Greek_GAMMA, 0x0393 }, /* GREEK CAPITAL LETTER GAMMA */ +{ XK_Greek_DELTA, 0x0394 }, /* GREEK CAPITAL LETTER DELTA */ +{ XK_Greek_EPSILON, 0x0395 }, /* GREEK CAPITAL LETTER EPSILON */ +{ XK_Greek_ZETA, 0x0396 }, /* GREEK CAPITAL LETTER ZETA */ +{ XK_Greek_ETA, 0x0397 }, /* GREEK CAPITAL LETTER ETA */ +{ XK_Greek_THETA, 0x0398 }, /* GREEK CAPITAL LETTER THETA */ +{ XK_Greek_IOTA, 0x0399 }, /* GREEK CAPITAL LETTER IOTA */ +{ XK_Greek_KAPPA, 0x039a }, /* GREEK CAPITAL LETTER KAPPA */ +{ XK_Greek_LAMBDA, 0x039b }, /* GREEK CAPITAL LETTER LAMDA */ +{ XK_Greek_MU, 0x039c }, /* GREEK CAPITAL LETTER MU */ +{ XK_Greek_NU, 0x039d }, /* GREEK CAPITAL LETTER NU */ +{ XK_Greek_XI, 0x039e }, /* GREEK CAPITAL LETTER XI */ +{ XK_Greek_OMICRON, 0x039f }, /* GREEK CAPITAL LETTER OMICRON */ +{ XK_Greek_PI, 0x03a0 }, /* GREEK CAPITAL LETTER PI */ +{ XK_Greek_RHO, 0x03a1 }, /* GREEK CAPITAL LETTER RHO */ +{ XK_Greek_SIGMA, 0x03a3 }, /* GREEK CAPITAL LETTER SIGMA */ +{ XK_Greek_TAU, 0x03a4 }, /* GREEK CAPITAL LETTER TAU */ +{ XK_Greek_UPSILON, 0x03a5 }, /* GREEK CAPITAL LETTER UPSILON */ +{ XK_Greek_PHI, 0x03a6 }, /* GREEK CAPITAL LETTER PHI */ +{ XK_Greek_CHI, 0x03a7 }, /* GREEK CAPITAL LETTER CHI */ +{ XK_Greek_PSI, 0x03a8 }, /* GREEK CAPITAL LETTER PSI */ +{ XK_Greek_OMEGA, 0x03a9 }, /* GREEK CAPITAL LETTER OMEGA */ +{ XK_Greek_alpha, 0x03b1 }, /* GREEK SMALL LETTER ALPHA */ +{ XK_Greek_beta, 0x03b2 }, /* GREEK SMALL LETTER BETA */ +{ XK_Greek_gamma, 0x03b3 }, /* GREEK SMALL LETTER GAMMA */ +{ XK_Greek_delta, 0x03b4 }, /* GREEK SMALL LETTER DELTA */ +{ XK_Greek_epsilon, 0x03b5 }, /* GREEK SMALL LETTER EPSILON */ +{ XK_Greek_zeta, 0x03b6 }, /* GREEK SMALL LETTER ZETA */ +{ XK_Greek_eta, 0x03b7 }, /* GREEK SMALL LETTER ETA */ +{ XK_Greek_theta, 0x03b8 }, /* GREEK SMALL LETTER THETA */ +{ XK_Greek_iota, 0x03b9 }, /* GREEK SMALL LETTER IOTA */ +{ XK_Greek_kappa, 0x03ba }, /* GREEK SMALL LETTER KAPPA */ +{ XK_Greek_lambda, 0x03bb }, /* GREEK SMALL LETTER LAMDA */ +{ XK_Greek_mu, 0x03bc }, /* GREEK SMALL LETTER MU */ +{ XK_Greek_nu, 0x03bd }, /* GREEK SMALL LETTER NU */ +{ XK_Greek_xi, 0x03be }, /* GREEK SMALL LETTER XI */ +{ XK_Greek_omicron, 0x03bf }, /* GREEK SMALL LETTER OMICRON */ +{ XK_Greek_pi, 0x03c0 }, /* GREEK SMALL LETTER PI */ +{ XK_Greek_rho, 0x03c1 }, /* GREEK SMALL LETTER RHO */ +{ XK_Greek_sigma, 0x03c3 }, /* GREEK SMALL LETTER SIGMA */ +{ XK_Greek_finalsmallsigma, 0x03c2 }, /* GREEK SMALL LETTER FINAL SIGMA */ +{ XK_Greek_tau, 0x03c4 }, /* GREEK SMALL LETTER TAU */ +{ XK_Greek_upsilon, 0x03c5 }, /* GREEK SMALL LETTER UPSILON */ +{ XK_Greek_phi, 0x03c6 }, /* GREEK SMALL LETTER PHI */ +{ XK_Greek_chi, 0x03c7 }, /* GREEK SMALL LETTER CHI */ +{ XK_Greek_psi, 0x03c8 }, /* GREEK SMALL LETTER PSI */ +{ XK_Greek_omega, 0x03c9 }, /* GREEK SMALL LETTER OMEGA */ +#endif // defined(XK_Greek_ALPHAaccent) +{ XK_leftradical, 0x23b7 }, /* ??? */ +{ XK_topleftradical, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ +{ XK_horizconnector, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ +{ XK_topintegral, 0x2320 }, /* TOP HALF INTEGRAL */ +{ XK_botintegral, 0x2321 }, /* BOTTOM HALF INTEGRAL */ +{ XK_vertconnector, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ +{ XK_topleftsqbracket, 0x23a1 }, /* ??? */ +{ XK_botleftsqbracket, 0x23a3 }, /* ??? */ +{ XK_toprightsqbracket, 0x23a4 }, /* ??? */ +{ XK_botrightsqbracket, 0x23a6 }, /* ??? */ +{ XK_topleftparens, 0x239b }, /* ??? */ +{ XK_botleftparens, 0x239d }, /* ??? */ +{ XK_toprightparens, 0x239e }, /* ??? */ +{ XK_botrightparens, 0x23a0 }, /* ??? */ +{ XK_leftmiddlecurlybrace, 0x23a8 }, /* ??? */ +{ XK_rightmiddlecurlybrace, 0x23ac }, /* ??? */ +{ XK_lessthanequal, 0x2264 }, /* LESS-THAN OR EQUAL TO */ +{ XK_notequal, 0x2260 }, /* NOT EQUAL TO */ +{ XK_greaterthanequal, 0x2265 }, /* GREATER-THAN OR EQUAL TO */ +{ XK_integral, 0x222b }, /* INTEGRAL */ +{ XK_therefore, 0x2234 }, /* THEREFORE */ +{ XK_variation, 0x221d }, /* PROPORTIONAL TO */ +{ XK_infinity, 0x221e }, /* INFINITY */ +{ XK_nabla, 0x2207 }, /* NABLA */ +{ XK_approximate, 0x223c }, /* TILDE OPERATOR */ +{ XK_similarequal, 0x2243 }, /* ASYMPTOTICALLY EQUAL TO */ +{ XK_ifonlyif, 0x21d4 }, /* LEFT RIGHT DOUBLE ARROW */ +{ XK_implies, 0x21d2 }, /* RIGHTWARDS DOUBLE ARROW */ +{ XK_identical, 0x2261 }, /* IDENTICAL TO */ +{ XK_radical, 0x221a }, /* SQUARE ROOT */ +{ XK_includedin, 0x2282 }, /* SUBSET OF */ +{ XK_includes, 0x2283 }, /* SUPERSET OF */ +{ XK_intersection, 0x2229 }, /* INTERSECTION */ +{ XK_union, 0x222a }, /* UNION */ +{ XK_logicaland, 0x2227 }, /* LOGICAL AND */ +{ XK_logicalor, 0x2228 }, /* LOGICAL OR */ +{ XK_partialderivative, 0x2202 }, /* PARTIAL DIFFERENTIAL */ +{ XK_function, 0x0192 }, /* LATIN SMALL LETTER F WITH HOOK */ +{ XK_leftarrow, 0x2190 }, /* LEFTWARDS ARROW */ +{ XK_uparrow, 0x2191 }, /* UPWARDS ARROW */ +{ XK_rightarrow, 0x2192 }, /* RIGHTWARDS ARROW */ +{ XK_downarrow, 0x2193 }, /* DOWNWARDS ARROW */ +/*{ XK_blank, ??? }, */ +{ XK_soliddiamond, 0x25c6 }, /* BLACK DIAMOND */ +{ XK_checkerboard, 0x2592 }, /* MEDIUM SHADE */ +{ XK_ht, 0x2409 }, /* SYMBOL FOR HORIZONTAL TABULATION */ +{ XK_ff, 0x240c }, /* SYMBOL FOR FORM FEED */ +{ XK_cr, 0x240d }, /* SYMBOL FOR CARRIAGE RETURN */ +{ XK_lf, 0x240a }, /* SYMBOL FOR LINE FEED */ +{ XK_nl, 0x2424 }, /* SYMBOL FOR NEWLINE */ +{ XK_vt, 0x240b }, /* SYMBOL FOR VERTICAL TABULATION */ +{ XK_lowrightcorner, 0x2518 }, /* BOX DRAWINGS LIGHT UP AND LEFT */ +{ XK_uprightcorner, 0x2510 }, /* BOX DRAWINGS LIGHT DOWN AND LEFT */ +{ XK_upleftcorner, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ +{ XK_lowleftcorner, 0x2514 }, /* BOX DRAWINGS LIGHT UP AND RIGHT */ +{ XK_crossinglines, 0x253c }, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ +{ XK_horizlinescan1, 0x23ba }, /* HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ +{ XK_horizlinescan3, 0x23bb }, /* HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ +{ XK_horizlinescan5, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ +{ XK_horizlinescan7, 0x23bc }, /* HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ +{ XK_horizlinescan9, 0x23bd }, /* HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ +{ XK_leftt, 0x251c }, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ +{ XK_rightt, 0x2524 }, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */ +{ XK_bott, 0x2534 }, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */ +{ XK_topt, 0x252c }, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ +{ XK_vertbar, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ +{ XK_emspace, 0x2003 }, /* EM SPACE */ +{ XK_enspace, 0x2002 }, /* EN SPACE */ +{ XK_em3space, 0x2004 }, /* THREE-PER-EM SPACE */ +{ XK_em4space, 0x2005 }, /* FOUR-PER-EM SPACE */ +{ XK_digitspace, 0x2007 }, /* FIGURE SPACE */ +{ XK_punctspace, 0x2008 }, /* PUNCTUATION SPACE */ +{ XK_thinspace, 0x2009 }, /* THIN SPACE */ +{ XK_hairspace, 0x200a }, /* HAIR SPACE */ +{ XK_emdash, 0x2014 }, /* EM DASH */ +{ XK_endash, 0x2013 }, /* EN DASH */ +/*{ XK_signifblank, ??? }, */ +{ XK_ellipsis, 0x2026 }, /* HORIZONTAL ELLIPSIS */ +{ XK_doubbaselinedot, 0x2025 }, /* TWO DOT LEADER */ +{ XK_onethird, 0x2153 }, /* VULGAR FRACTION ONE THIRD */ +{ XK_twothirds, 0x2154 }, /* VULGAR FRACTION TWO THIRDS */ +{ XK_onefifth, 0x2155 }, /* VULGAR FRACTION ONE FIFTH */ +{ XK_twofifths, 0x2156 }, /* VULGAR FRACTION TWO FIFTHS */ +{ XK_threefifths, 0x2157 }, /* VULGAR FRACTION THREE FIFTHS */ +{ XK_fourfifths, 0x2158 }, /* VULGAR FRACTION FOUR FIFTHS */ +{ XK_onesixth, 0x2159 }, /* VULGAR FRACTION ONE SIXTH */ +{ XK_fivesixths, 0x215a }, /* VULGAR FRACTION FIVE SIXTHS */ +{ XK_careof, 0x2105 }, /* CARE OF */ +{ XK_figdash, 0x2012 }, /* FIGURE DASH */ +{ XK_leftanglebracket, 0x2329 }, /* LEFT-POINTING ANGLE BRACKET */ +/*{ XK_decimalpoint, ??? }, */ +{ XK_rightanglebracket, 0x232a }, /* RIGHT-POINTING ANGLE BRACKET */ +/*{ XK_marker, ??? }, */ +{ XK_oneeighth, 0x215b }, /* VULGAR FRACTION ONE EIGHTH */ +{ XK_threeeighths, 0x215c }, /* VULGAR FRACTION THREE EIGHTHS */ +{ XK_fiveeighths, 0x215d }, /* VULGAR FRACTION FIVE EIGHTHS */ +{ XK_seveneighths, 0x215e }, /* VULGAR FRACTION SEVEN EIGHTHS */ +{ XK_trademark, 0x2122 }, /* TRADE MARK SIGN */ +{ XK_signaturemark, 0x2613 }, /* SALTIRE */ +/*{ XK_trademarkincircle, ??? }, */ +{ XK_leftopentriangle, 0x25c1 }, /* WHITE LEFT-POINTING TRIANGLE */ +{ XK_rightopentriangle, 0x25b7 }, /* WHITE RIGHT-POINTING TRIANGLE */ +{ XK_emopencircle, 0x25cb }, /* WHITE CIRCLE */ +{ XK_emopenrectangle, 0x25af }, /* WHITE VERTICAL RECTANGLE */ +{ XK_leftsinglequotemark, 0x2018 }, /* LEFT SINGLE QUOTATION MARK */ +{ XK_rightsinglequotemark, 0x2019 }, /* RIGHT SINGLE QUOTATION MARK */ +{ XK_leftdoublequotemark, 0x201c }, /* LEFT DOUBLE QUOTATION MARK */ +{ XK_rightdoublequotemark, 0x201d }, /* RIGHT DOUBLE QUOTATION MARK */ +{ XK_prescription, 0x211e }, /* PRESCRIPTION TAKE */ +{ XK_minutes, 0x2032 }, /* PRIME */ +{ XK_seconds, 0x2033 }, /* DOUBLE PRIME */ +{ XK_latincross, 0x271d }, /* LATIN CROSS */ +/*{ XK_hexagram, ??? }, */ +{ XK_filledrectbullet, 0x25ac }, /* BLACK RECTANGLE */ +{ XK_filledlefttribullet, 0x25c0 }, /* BLACK LEFT-POINTING TRIANGLE */ +{ XK_filledrighttribullet, 0x25b6 }, /* BLACK RIGHT-POINTING TRIANGLE */ +{ XK_emfilledcircle, 0x25cf }, /* BLACK CIRCLE */ +{ XK_emfilledrect, 0x25ae }, /* BLACK VERTICAL RECTANGLE */ +{ XK_enopencircbullet, 0x25e6 }, /* WHITE BULLET */ +{ XK_enopensquarebullet, 0x25ab }, /* WHITE SMALL SQUARE */ +{ XK_openrectbullet, 0x25ad }, /* WHITE RECTANGLE */ +{ XK_opentribulletup, 0x25b3 }, /* WHITE UP-POINTING TRIANGLE */ +{ XK_opentribulletdown, 0x25bd }, /* WHITE DOWN-POINTING TRIANGLE */ +{ XK_openstar, 0x2606 }, /* WHITE STAR */ +{ XK_enfilledcircbullet, 0x2022 }, /* BULLET */ +{ XK_enfilledsqbullet, 0x25aa }, /* BLACK SMALL SQUARE */ +{ XK_filledtribulletup, 0x25b2 }, /* BLACK UP-POINTING TRIANGLE */ +{ XK_filledtribulletdown, 0x25bc }, /* BLACK DOWN-POINTING TRIANGLE */ +{ XK_leftpointer, 0x261c }, /* WHITE LEFT POINTING INDEX */ +{ XK_rightpointer, 0x261e }, /* WHITE RIGHT POINTING INDEX */ +{ XK_club, 0x2663 }, /* BLACK CLUB SUIT */ +{ XK_diamond, 0x2666 }, /* BLACK DIAMOND SUIT */ +{ XK_heart, 0x2665 }, /* BLACK HEART SUIT */ +{ XK_maltesecross, 0x2720 }, /* MALTESE CROSS */ +{ XK_dagger, 0x2020 }, /* DAGGER */ +{ XK_doubledagger, 0x2021 }, /* DOUBLE DAGGER */ +{ XK_checkmark, 0x2713 }, /* CHECK MARK */ +{ XK_ballotcross, 0x2717 }, /* BALLOT X */ +{ XK_musicalsharp, 0x266f }, /* MUSIC SHARP SIGN */ +{ XK_musicalflat, 0x266d }, /* MUSIC FLAT SIGN */ +{ XK_malesymbol, 0x2642 }, /* MALE SIGN */ +{ XK_femalesymbol, 0x2640 }, /* FEMALE SIGN */ +{ XK_telephone, 0x260e }, /* BLACK TELEPHONE */ +{ XK_telephonerecorder, 0x2315 }, /* TELEPHONE RECORDER */ +{ XK_phonographcopyright, 0x2117 }, /* SOUND RECORDING COPYRIGHT */ +{ XK_caret, 0x2038 }, /* CARET */ +{ XK_singlelowquotemark, 0x201a }, /* SINGLE LOW-9 QUOTATION MARK */ +{ XK_doublelowquotemark, 0x201e }, /* DOUBLE LOW-9 QUOTATION MARK */ +/*{ XK_cursor, ??? }, */ +{ XK_leftcaret, 0x003c }, /* LESS-THAN SIGN */ +{ XK_rightcaret, 0x003e }, /* GREATER-THAN SIGN */ +{ XK_downcaret, 0x2228 }, /* LOGICAL OR */ +{ XK_upcaret, 0x2227 }, /* LOGICAL AND */ +{ XK_overbar, 0x00af }, /* MACRON */ +{ XK_downtack, 0x22a5 }, /* UP TACK */ +{ XK_upshoe, 0x2229 }, /* INTERSECTION */ +{ XK_downstile, 0x230a }, /* LEFT FLOOR */ +{ XK_underbar, 0x005f }, /* LOW LINE */ +{ XK_jot, 0x2218 }, /* RING OPERATOR */ +{ XK_quad, 0x2395 }, /* APL FUNCTIONAL SYMBOL QUAD */ +{ XK_uptack, 0x22a4 }, /* DOWN TACK */ +{ XK_circle, 0x25cb }, /* WHITE CIRCLE */ +{ XK_upstile, 0x2308 }, /* LEFT CEILING */ +{ XK_downshoe, 0x222a }, /* UNION */ +{ XK_rightshoe, 0x2283 }, /* SUPERSET OF */ +{ XK_leftshoe, 0x2282 }, /* SUBSET OF */ +{ XK_lefttack, 0x22a2 }, /* RIGHT TACK */ +{ XK_righttack, 0x22a3 }, /* LEFT TACK */ +#if defined(XK_hebrew_doublelowline) +{ XK_hebrew_doublelowline, 0x2017 }, /* DOUBLE LOW LINE */ +{ XK_hebrew_aleph, 0x05d0 }, /* HEBREW LETTER ALEF */ +{ XK_hebrew_bet, 0x05d1 }, /* HEBREW LETTER BET */ +{ XK_hebrew_gimel, 0x05d2 }, /* HEBREW LETTER GIMEL */ +{ XK_hebrew_dalet, 0x05d3 }, /* HEBREW LETTER DALET */ +{ XK_hebrew_he, 0x05d4 }, /* HEBREW LETTER HE */ +{ XK_hebrew_waw, 0x05d5 }, /* HEBREW LETTER VAV */ +{ XK_hebrew_zain, 0x05d6 }, /* HEBREW LETTER ZAYIN */ +{ XK_hebrew_chet, 0x05d7 }, /* HEBREW LETTER HET */ +{ XK_hebrew_tet, 0x05d8 }, /* HEBREW LETTER TET */ +{ XK_hebrew_yod, 0x05d9 }, /* HEBREW LETTER YOD */ +{ XK_hebrew_finalkaph, 0x05da }, /* HEBREW LETTER FINAL KAF */ +{ XK_hebrew_kaph, 0x05db }, /* HEBREW LETTER KAF */ +{ XK_hebrew_lamed, 0x05dc }, /* HEBREW LETTER LAMED */ +{ XK_hebrew_finalmem, 0x05dd }, /* HEBREW LETTER FINAL MEM */ +{ XK_hebrew_mem, 0x05de }, /* HEBREW LETTER MEM */ +{ XK_hebrew_finalnun, 0x05df }, /* HEBREW LETTER FINAL NUN */ +{ XK_hebrew_nun, 0x05e0 }, /* HEBREW LETTER NUN */ +{ XK_hebrew_samech, 0x05e1 }, /* HEBREW LETTER SAMEKH */ +{ XK_hebrew_ayin, 0x05e2 }, /* HEBREW LETTER AYIN */ +{ XK_hebrew_finalpe, 0x05e3 }, /* HEBREW LETTER FINAL PE */ +{ XK_hebrew_pe, 0x05e4 }, /* HEBREW LETTER PE */ +{ XK_hebrew_finalzade, 0x05e5 }, /* HEBREW LETTER FINAL TSADI */ +{ XK_hebrew_zade, 0x05e6 }, /* HEBREW LETTER TSADI */ +{ XK_hebrew_qoph, 0x05e7 }, /* HEBREW LETTER QOF */ +{ XK_hebrew_resh, 0x05e8 }, /* HEBREW LETTER RESH */ +{ XK_hebrew_shin, 0x05e9 }, /* HEBREW LETTER SHIN */ +{ XK_hebrew_taw, 0x05ea }, /* HEBREW LETTER TAV */ +#endif // defined(XK_hebrew_doublelowline) +#if defined(XK_Thai_kokai) +{ XK_Thai_kokai, 0x0e01 }, /* THAI CHARACTER KO KAI */ +{ XK_Thai_khokhai, 0x0e02 }, /* THAI CHARACTER KHO KHAI */ +{ XK_Thai_khokhuat, 0x0e03 }, /* THAI CHARACTER KHO KHUAT */ +{ XK_Thai_khokhwai, 0x0e04 }, /* THAI CHARACTER KHO KHWAI */ +{ XK_Thai_khokhon, 0x0e05 }, /* THAI CHARACTER KHO KHON */ +{ XK_Thai_khorakhang, 0x0e06 }, /* THAI CHARACTER KHO RAKHANG */ +{ XK_Thai_ngongu, 0x0e07 }, /* THAI CHARACTER NGO NGU */ +{ XK_Thai_chochan, 0x0e08 }, /* THAI CHARACTER CHO CHAN */ +{ XK_Thai_choching, 0x0e09 }, /* THAI CHARACTER CHO CHING */ +{ XK_Thai_chochang, 0x0e0a }, /* THAI CHARACTER CHO CHANG */ +{ XK_Thai_soso, 0x0e0b }, /* THAI CHARACTER SO SO */ +{ XK_Thai_chochoe, 0x0e0c }, /* THAI CHARACTER CHO CHOE */ +{ XK_Thai_yoying, 0x0e0d }, /* THAI CHARACTER YO YING */ +{ XK_Thai_dochada, 0x0e0e }, /* THAI CHARACTER DO CHADA */ +{ XK_Thai_topatak, 0x0e0f }, /* THAI CHARACTER TO PATAK */ +{ XK_Thai_thothan, 0x0e10 }, /* THAI CHARACTER THO THAN */ +{ XK_Thai_thonangmontho, 0x0e11 }, /* THAI CHARACTER THO NANGMONTHO */ +{ XK_Thai_thophuthao, 0x0e12 }, /* THAI CHARACTER THO PHUTHAO */ +{ XK_Thai_nonen, 0x0e13 }, /* THAI CHARACTER NO NEN */ +{ XK_Thai_dodek, 0x0e14 }, /* THAI CHARACTER DO DEK */ +{ XK_Thai_totao, 0x0e15 }, /* THAI CHARACTER TO TAO */ +{ XK_Thai_thothung, 0x0e16 }, /* THAI CHARACTER THO THUNG */ +{ XK_Thai_thothahan, 0x0e17 }, /* THAI CHARACTER THO THAHAN */ +{ XK_Thai_thothong, 0x0e18 }, /* THAI CHARACTER THO THONG */ +{ XK_Thai_nonu, 0x0e19 }, /* THAI CHARACTER NO NU */ +{ XK_Thai_bobaimai, 0x0e1a }, /* THAI CHARACTER BO BAIMAI */ +{ XK_Thai_popla, 0x0e1b }, /* THAI CHARACTER PO PLA */ +{ XK_Thai_phophung, 0x0e1c }, /* THAI CHARACTER PHO PHUNG */ +{ XK_Thai_fofa, 0x0e1d }, /* THAI CHARACTER FO FA */ +{ XK_Thai_phophan, 0x0e1e }, /* THAI CHARACTER PHO PHAN */ +{ XK_Thai_fofan, 0x0e1f }, /* THAI CHARACTER FO FAN */ +{ XK_Thai_phosamphao, 0x0e20 }, /* THAI CHARACTER PHO SAMPHAO */ +{ XK_Thai_moma, 0x0e21 }, /* THAI CHARACTER MO MA */ +{ XK_Thai_yoyak, 0x0e22 }, /* THAI CHARACTER YO YAK */ +{ XK_Thai_rorua, 0x0e23 }, /* THAI CHARACTER RO RUA */ +{ XK_Thai_ru, 0x0e24 }, /* THAI CHARACTER RU */ +{ XK_Thai_loling, 0x0e25 }, /* THAI CHARACTER LO LING */ +{ XK_Thai_lu, 0x0e26 }, /* THAI CHARACTER LU */ +{ XK_Thai_wowaen, 0x0e27 }, /* THAI CHARACTER WO WAEN */ +{ XK_Thai_sosala, 0x0e28 }, /* THAI CHARACTER SO SALA */ +{ XK_Thai_sorusi, 0x0e29 }, /* THAI CHARACTER SO RUSI */ +{ XK_Thai_sosua, 0x0e2a }, /* THAI CHARACTER SO SUA */ +{ XK_Thai_hohip, 0x0e2b }, /* THAI CHARACTER HO HIP */ +{ XK_Thai_lochula, 0x0e2c }, /* THAI CHARACTER LO CHULA */ +{ XK_Thai_oang, 0x0e2d }, /* THAI CHARACTER O ANG */ +{ XK_Thai_honokhuk, 0x0e2e }, /* THAI CHARACTER HO NOKHUK */ +{ XK_Thai_paiyannoi, 0x0e2f }, /* THAI CHARACTER PAIYANNOI */ +{ XK_Thai_saraa, 0x0e30 }, /* THAI CHARACTER SARA A */ +{ XK_Thai_maihanakat, 0x0e31 }, /* THAI CHARACTER MAI HAN-AKAT */ +{ XK_Thai_saraaa, 0x0e32 }, /* THAI CHARACTER SARA AA */ +{ XK_Thai_saraam, 0x0e33 }, /* THAI CHARACTER SARA AM */ +{ XK_Thai_sarai, 0x0e34 }, /* THAI CHARACTER SARA I */ +{ XK_Thai_saraii, 0x0e35 }, /* THAI CHARACTER SARA II */ +{ XK_Thai_saraue, 0x0e36 }, /* THAI CHARACTER SARA UE */ +{ XK_Thai_sarauee, 0x0e37 }, /* THAI CHARACTER SARA UEE */ +{ XK_Thai_sarau, 0x0e38 }, /* THAI CHARACTER SARA U */ +{ XK_Thai_sarauu, 0x0e39 }, /* THAI CHARACTER SARA UU */ +{ XK_Thai_phinthu, 0x0e3a }, /* THAI CHARACTER PHINTHU */ +/*{ XK_Thai_maihanakat_maitho, ??? }, */ +{ XK_Thai_baht, 0x0e3f }, /* THAI CURRENCY SYMBOL BAHT */ +{ XK_Thai_sarae, 0x0e40 }, /* THAI CHARACTER SARA E */ +{ XK_Thai_saraae, 0x0e41 }, /* THAI CHARACTER SARA AE */ +{ XK_Thai_sarao, 0x0e42 }, /* THAI CHARACTER SARA O */ +{ XK_Thai_saraaimaimuan, 0x0e43 }, /* THAI CHARACTER SARA AI MAIMUAN */ +{ XK_Thai_saraaimaimalai, 0x0e44 }, /* THAI CHARACTER SARA AI MAIMALAI */ +{ XK_Thai_lakkhangyao, 0x0e45 }, /* THAI CHARACTER LAKKHANGYAO */ +{ XK_Thai_maiyamok, 0x0e46 }, /* THAI CHARACTER MAIYAMOK */ +{ XK_Thai_maitaikhu, 0x0e47 }, /* THAI CHARACTER MAITAIKHU */ +{ XK_Thai_maiek, 0x0e48 }, /* THAI CHARACTER MAI EK */ +{ XK_Thai_maitho, 0x0e49 }, /* THAI CHARACTER MAI THO */ +{ XK_Thai_maitri, 0x0e4a }, /* THAI CHARACTER MAI TRI */ +{ XK_Thai_maichattawa, 0x0e4b }, /* THAI CHARACTER MAI CHATTAWA */ +{ XK_Thai_thanthakhat, 0x0e4c }, /* THAI CHARACTER THANTHAKHAT */ +{ XK_Thai_nikhahit, 0x0e4d }, /* THAI CHARACTER NIKHAHIT */ +{ XK_Thai_leksun, 0x0e50 }, /* THAI DIGIT ZERO */ +{ XK_Thai_leknung, 0x0e51 }, /* THAI DIGIT ONE */ +{ XK_Thai_leksong, 0x0e52 }, /* THAI DIGIT TWO */ +{ XK_Thai_leksam, 0x0e53 }, /* THAI DIGIT THREE */ +{ XK_Thai_leksi, 0x0e54 }, /* THAI DIGIT FOUR */ +{ XK_Thai_lekha, 0x0e55 }, /* THAI DIGIT FIVE */ +{ XK_Thai_lekhok, 0x0e56 }, /* THAI DIGIT SIX */ +{ XK_Thai_lekchet, 0x0e57 }, /* THAI DIGIT SEVEN */ +{ XK_Thai_lekpaet, 0x0e58 }, /* THAI DIGIT EIGHT */ +{ XK_Thai_lekkao, 0x0e59 }, /* THAI DIGIT NINE */ +#endif // defined(XK_Thai_kokai) +#if defined(XK_Hangul_Kiyeog) +{ XK_Hangul_Kiyeog, 0x3131 }, /* HANGUL LETTER KIYEOK */ +{ XK_Hangul_SsangKiyeog, 0x3132 }, /* HANGUL LETTER SSANGKIYEOK */ +{ XK_Hangul_KiyeogSios, 0x3133 }, /* HANGUL LETTER KIYEOK-SIOS */ +{ XK_Hangul_Nieun, 0x3134 }, /* HANGUL LETTER NIEUN */ +{ XK_Hangul_NieunJieuj, 0x3135 }, /* HANGUL LETTER NIEUN-CIEUC */ +{ XK_Hangul_NieunHieuh, 0x3136 }, /* HANGUL LETTER NIEUN-HIEUH */ +{ XK_Hangul_Dikeud, 0x3137 }, /* HANGUL LETTER TIKEUT */ +{ XK_Hangul_SsangDikeud, 0x3138 }, /* HANGUL LETTER SSANGTIKEUT */ +{ XK_Hangul_Rieul, 0x3139 }, /* HANGUL LETTER RIEUL */ +{ XK_Hangul_RieulKiyeog, 0x313a }, /* HANGUL LETTER RIEUL-KIYEOK */ +{ XK_Hangul_RieulMieum, 0x313b }, /* HANGUL LETTER RIEUL-MIEUM */ +{ XK_Hangul_RieulPieub, 0x313c }, /* HANGUL LETTER RIEUL-PIEUP */ +{ XK_Hangul_RieulSios, 0x313d }, /* HANGUL LETTER RIEUL-SIOS */ +{ XK_Hangul_RieulTieut, 0x313e }, /* HANGUL LETTER RIEUL-THIEUTH */ +{ XK_Hangul_RieulPhieuf, 0x313f }, /* HANGUL LETTER RIEUL-PHIEUPH */ +{ XK_Hangul_RieulHieuh, 0x3140 }, /* HANGUL LETTER RIEUL-HIEUH */ +{ XK_Hangul_Mieum, 0x3141 }, /* HANGUL LETTER MIEUM */ +{ XK_Hangul_Pieub, 0x3142 }, /* HANGUL LETTER PIEUP */ +{ XK_Hangul_SsangPieub, 0x3143 }, /* HANGUL LETTER SSANGPIEUP */ +{ XK_Hangul_PieubSios, 0x3144 }, /* HANGUL LETTER PIEUP-SIOS */ +{ XK_Hangul_Sios, 0x3145 }, /* HANGUL LETTER SIOS */ +{ XK_Hangul_SsangSios, 0x3146 }, /* HANGUL LETTER SSANGSIOS */ +{ XK_Hangul_Ieung, 0x3147 }, /* HANGUL LETTER IEUNG */ +{ XK_Hangul_Jieuj, 0x3148 }, /* HANGUL LETTER CIEUC */ +{ XK_Hangul_SsangJieuj, 0x3149 }, /* HANGUL LETTER SSANGCIEUC */ +{ XK_Hangul_Cieuc, 0x314a }, /* HANGUL LETTER CHIEUCH */ +{ XK_Hangul_Khieuq, 0x314b }, /* HANGUL LETTER KHIEUKH */ +{ XK_Hangul_Tieut, 0x314c }, /* HANGUL LETTER THIEUTH */ +{ XK_Hangul_Phieuf, 0x314d }, /* HANGUL LETTER PHIEUPH */ +{ XK_Hangul_Hieuh, 0x314e }, /* HANGUL LETTER HIEUH */ +{ XK_Hangul_A, 0x314f }, /* HANGUL LETTER A */ +{ XK_Hangul_AE, 0x3150 }, /* HANGUL LETTER AE */ +{ XK_Hangul_YA, 0x3151 }, /* HANGUL LETTER YA */ +{ XK_Hangul_YAE, 0x3152 }, /* HANGUL LETTER YAE */ +{ XK_Hangul_EO, 0x3153 }, /* HANGUL LETTER EO */ +{ XK_Hangul_E, 0x3154 }, /* HANGUL LETTER E */ +{ XK_Hangul_YEO, 0x3155 }, /* HANGUL LETTER YEO */ +{ XK_Hangul_YE, 0x3156 }, /* HANGUL LETTER YE */ +{ XK_Hangul_O, 0x3157 }, /* HANGUL LETTER O */ +{ XK_Hangul_WA, 0x3158 }, /* HANGUL LETTER WA */ +{ XK_Hangul_WAE, 0x3159 }, /* HANGUL LETTER WAE */ +{ XK_Hangul_OE, 0x315a }, /* HANGUL LETTER OE */ +{ XK_Hangul_YO, 0x315b }, /* HANGUL LETTER YO */ +{ XK_Hangul_U, 0x315c }, /* HANGUL LETTER U */ +{ XK_Hangul_WEO, 0x315d }, /* HANGUL LETTER WEO */ +{ XK_Hangul_WE, 0x315e }, /* HANGUL LETTER WE */ +{ XK_Hangul_WI, 0x315f }, /* HANGUL LETTER WI */ +{ XK_Hangul_YU, 0x3160 }, /* HANGUL LETTER YU */ +{ XK_Hangul_EU, 0x3161 }, /* HANGUL LETTER EU */ +{ XK_Hangul_YI, 0x3162 }, /* HANGUL LETTER YI */ +{ XK_Hangul_I, 0x3163 }, /* HANGUL LETTER I */ +{ XK_Hangul_J_Kiyeog, 0x11a8 }, /* HANGUL JONGSEONG KIYEOK */ +{ XK_Hangul_J_SsangKiyeog, 0x11a9 }, /* HANGUL JONGSEONG SSANGKIYEOK */ +{ XK_Hangul_J_KiyeogSios, 0x11aa }, /* HANGUL JONGSEONG KIYEOK-SIOS */ +{ XK_Hangul_J_Nieun, 0x11ab }, /* HANGUL JONGSEONG NIEUN */ +{ XK_Hangul_J_NieunJieuj, 0x11ac }, /* HANGUL JONGSEONG NIEUN-CIEUC */ +{ XK_Hangul_J_NieunHieuh, 0x11ad }, /* HANGUL JONGSEONG NIEUN-HIEUH */ +{ XK_Hangul_J_Dikeud, 0x11ae }, /* HANGUL JONGSEONG TIKEUT */ +{ XK_Hangul_J_Rieul, 0x11af }, /* HANGUL JONGSEONG RIEUL */ +{ XK_Hangul_J_RieulKiyeog, 0x11b0 }, /* HANGUL JONGSEONG RIEUL-KIYEOK */ +{ XK_Hangul_J_RieulMieum, 0x11b1 }, /* HANGUL JONGSEONG RIEUL-MIEUM */ +{ XK_Hangul_J_RieulPieub, 0x11b2 }, /* HANGUL JONGSEONG RIEUL-PIEUP */ +{ XK_Hangul_J_RieulSios, 0x11b3 }, /* HANGUL JONGSEONG RIEUL-SIOS */ +{ XK_Hangul_J_RieulTieut, 0x11b4 }, /* HANGUL JONGSEONG RIEUL-THIEUTH */ +{ XK_Hangul_J_RieulPhieuf, 0x11b5 }, /* HANGUL JONGSEONG RIEUL-PHIEUPH */ +{ XK_Hangul_J_RieulHieuh, 0x11b6 }, /* HANGUL JONGSEONG RIEUL-HIEUH */ +{ XK_Hangul_J_Mieum, 0x11b7 }, /* HANGUL JONGSEONG MIEUM */ +{ XK_Hangul_J_Pieub, 0x11b8 }, /* HANGUL JONGSEONG PIEUP */ +{ XK_Hangul_J_PieubSios, 0x11b9 }, /* HANGUL JONGSEONG PIEUP-SIOS */ +{ XK_Hangul_J_Sios, 0x11ba }, /* HANGUL JONGSEONG SIOS */ +{ XK_Hangul_J_SsangSios, 0x11bb }, /* HANGUL JONGSEONG SSANGSIOS */ +{ XK_Hangul_J_Ieung, 0x11bc }, /* HANGUL JONGSEONG IEUNG */ +{ XK_Hangul_J_Jieuj, 0x11bd }, /* HANGUL JONGSEONG CIEUC */ +{ XK_Hangul_J_Cieuc, 0x11be }, /* HANGUL JONGSEONG CHIEUCH */ +{ XK_Hangul_J_Khieuq, 0x11bf }, /* HANGUL JONGSEONG KHIEUKH */ +{ XK_Hangul_J_Tieut, 0x11c0 }, /* HANGUL JONGSEONG THIEUTH */ +{ XK_Hangul_J_Phieuf, 0x11c1 }, /* HANGUL JONGSEONG PHIEUPH */ +{ XK_Hangul_J_Hieuh, 0x11c2 }, /* HANGUL JONGSEONG HIEUH */ +{ XK_Hangul_RieulYeorinHieuh, 0x316d }, /* HANGUL LETTER RIEUL-YEORINHIEUH */ +{ XK_Hangul_SunkyeongeumMieum, 0x3171 }, /* HANGUL LETTER KAPYEOUNMIEUM */ +{ XK_Hangul_SunkyeongeumPieub, 0x3178 }, /* HANGUL LETTER KAPYEOUNPIEUP */ +{ XK_Hangul_PanSios, 0x317f }, /* HANGUL LETTER PANSIOS */ +{ XK_Hangul_KkogjiDalrinIeung, 0x3181 }, /* HANGUL LETTER YESIEUNG */ +{ XK_Hangul_SunkyeongeumPhieuf, 0x3184 }, /* HANGUL LETTER KAPYEOUNPHIEUPH */ +{ XK_Hangul_YeorinHieuh, 0x3186 }, /* HANGUL LETTER YEORINHIEUH */ +{ XK_Hangul_AraeA, 0x318d }, /* HANGUL LETTER ARAEA */ +{ XK_Hangul_AraeAE, 0x318e }, /* HANGUL LETTER ARAEAE */ +{ XK_Hangul_J_PanSios, 0x11eb }, /* HANGUL JONGSEONG PANSIOS */ +{ XK_Hangul_J_KkogjiDalrinIeung, 0x11f0 }, /* HANGUL JONGSEONG YESIEUNG */ +{ XK_Hangul_J_YeorinHieuh, 0x11f9 }, /* HANGUL JONGSEONG YEORINHIEUH */ +{ XK_Korean_Won, 0x20a9 }, /* WON SIGN */ +#endif // defined(XK_Hangul_Kiyeog) +{ XK_OE, 0x0152 }, /* LATIN CAPITAL LIGATURE OE */ +{ XK_oe, 0x0153 }, /* LATIN SMALL LIGATURE OE */ +{ XK_Ydiaeresis, 0x0178 }, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */ +{ XK_EuroSign, 0x20ac }, /* EURO SIGN */ + +/* combining dead keys */ +{ XK_dead_abovedot, 0x0307 }, /* COMBINING DOT ABOVE */ +{ XK_dead_abovering, 0x030a }, /* COMBINING RING ABOVE */ +{ XK_dead_acute, 0x0301 }, /* COMBINING ACUTE ACCENT */ +{ XK_dead_breve, 0x0306 }, /* COMBINING BREVE */ +{ XK_dead_caron, 0x030c }, /* COMBINING CARON */ +{ XK_dead_cedilla, 0x0327 }, /* COMBINING CEDILLA */ +{ XK_dead_circumflex, 0x0302 }, /* COMBINING CIRCUMFLEX ACCENT */ +{ XK_dead_diaeresis, 0x0308 }, /* COMBINING DIAERESIS */ +{ XK_dead_doubleacute, 0x030b }, /* COMBINING DOUBLE ACUTE ACCENT */ +{ XK_dead_grave, 0x0300 }, /* COMBINING GRAVE ACCENT */ +{ XK_dead_macron, 0x0304 }, /* COMBINING MACRON */ +{ XK_dead_ogonek, 0x0328 }, /* COMBINING OGONEK */ +{ XK_dead_tilde, 0x0303 } /* COMBINING TILDE */ +}; +/* XXX -- map these too +XK_Cyrillic_GHE_bar +XK_Cyrillic_ZHE_descender +XK_Cyrillic_KA_descender +XK_Cyrillic_KA_vertstroke +XK_Cyrillic_EN_descender +XK_Cyrillic_U_straight +XK_Cyrillic_U_straight_bar +XK_Cyrillic_HA_descender +XK_Cyrillic_CHE_descender +XK_Cyrillic_CHE_vertstroke +XK_Cyrillic_SHHA +XK_Cyrillic_SCHWA +XK_Cyrillic_I_macron +XK_Cyrillic_O_bar +XK_Cyrillic_U_macron +XK_Cyrillic_ghe_bar +XK_Cyrillic_zhe_descender +XK_Cyrillic_ka_descender +XK_Cyrillic_ka_vertstroke +XK_Cyrillic_en_descender +XK_Cyrillic_u_straight +XK_Cyrillic_u_straight_bar +XK_Cyrillic_ha_descender +XK_Cyrillic_che_descender +XK_Cyrillic_che_vertstroke +XK_Cyrillic_shha +XK_Cyrillic_schwa +XK_Cyrillic_i_macron +XK_Cyrillic_o_bar +XK_Cyrillic_u_macron + +XK_Armenian_eternity +XK_Armenian_ligature_ew +XK_Armenian_full_stop +XK_Armenian_verjaket +XK_Armenian_parenright +XK_Armenian_parenleft +XK_Armenian_guillemotright +XK_Armenian_guillemotleft +XK_Armenian_em_dash +XK_Armenian_dot +XK_Armenian_mijaket +XK_Armenian_but +XK_Armenian_separation_mark +XK_Armenian_comma +XK_Armenian_en_dash +XK_Armenian_hyphen +XK_Armenian_yentamna +XK_Armenian_ellipsis +XK_Armenian_amanak +XK_Armenian_exclam +XK_Armenian_accent +XK_Armenian_shesht +XK_Armenian_paruyk +XK_Armenian_question +XK_Armenian_AYB +XK_Armenian_ayb +XK_Armenian_BEN +XK_Armenian_ben +XK_Armenian_GIM +XK_Armenian_gim +XK_Armenian_DA +XK_Armenian_da +XK_Armenian_YECH +XK_Armenian_yech +XK_Armenian_ZA +XK_Armenian_za +XK_Armenian_E +XK_Armenian_e +XK_Armenian_AT +XK_Armenian_at +XK_Armenian_TO +XK_Armenian_to +XK_Armenian_ZHE +XK_Armenian_zhe +XK_Armenian_INI +XK_Armenian_ini +XK_Armenian_LYUN +XK_Armenian_lyun +XK_Armenian_KHE +XK_Armenian_khe +XK_Armenian_TSA +XK_Armenian_tsa +XK_Armenian_KEN +XK_Armenian_ken +XK_Armenian_HO +XK_Armenian_ho +XK_Armenian_DZA +XK_Armenian_dza +XK_Armenian_GHAT +XK_Armenian_ghat +XK_Armenian_TCHE +XK_Armenian_tche +XK_Armenian_MEN +XK_Armenian_men +XK_Armenian_HI +XK_Armenian_hi +XK_Armenian_NU +XK_Armenian_nu +XK_Armenian_SHA +XK_Armenian_sha +XK_Armenian_VO +XK_Armenian_vo +XK_Armenian_CHA +XK_Armenian_cha +XK_Armenian_PE +XK_Armenian_pe +XK_Armenian_JE +XK_Armenian_je +XK_Armenian_RA +XK_Armenian_ra +XK_Armenian_SE +XK_Armenian_se +XK_Armenian_VEV +XK_Armenian_vev +XK_Armenian_TYUN +XK_Armenian_tyun +XK_Armenian_RE +XK_Armenian_re +XK_Armenian_TSO +XK_Armenian_tso +XK_Armenian_VYUN +XK_Armenian_vyun +XK_Armenian_PYUR +XK_Armenian_pyur +XK_Armenian_KE +XK_Armenian_ke +XK_Armenian_O +XK_Armenian_o +XK_Armenian_FE +XK_Armenian_fe +XK_Armenian_apostrophe +XK_Armenian_section_sign + +XK_Georgian_an +XK_Georgian_ban +XK_Georgian_gan +XK_Georgian_don +XK_Georgian_en +XK_Georgian_vin +XK_Georgian_zen +XK_Georgian_tan +XK_Georgian_in +XK_Georgian_kan +XK_Georgian_las +XK_Georgian_man +XK_Georgian_nar +XK_Georgian_on +XK_Georgian_par +XK_Georgian_zhar +XK_Georgian_rae +XK_Georgian_san +XK_Georgian_tar +XK_Georgian_un +XK_Georgian_phar +XK_Georgian_khar +XK_Georgian_ghan +XK_Georgian_qar +XK_Georgian_shin +XK_Georgian_chin +XK_Georgian_can +XK_Georgian_jil +XK_Georgian_cil +XK_Georgian_char +XK_Georgian_xan +XK_Georgian_jhan +XK_Georgian_hae +XK_Georgian_he +XK_Georgian_hie +XK_Georgian_we +XK_Georgian_har +XK_Georgian_hoe +XK_Georgian_fi + +XK_Ccedillaabovedot +XK_Xabovedot +XK_Qabovedot +XK_Ibreve +XK_IE +XK_UO +XK_Zstroke +XK_Gcaron +XK_Obarred +XK_ccedillaabovedot +XK_xabovedot +XK_Ocaron +XK_qabovedot +XK_ibreve +XK_ie +XK_uo +XK_zstroke +XK_gcaron +XK_ocaron +XK_obarred +XK_SCHWA +XK_Lbelowdot +XK_Lstrokebelowdot +XK_Gtilde +XK_lbelowdot +XK_lstrokebelowdot +XK_gtilde +XK_schwa + +XK_Abelowdot +XK_abelowdot +XK_Ahook +XK_ahook +XK_Acircumflexacute +XK_acircumflexacute +XK_Acircumflexgrave +XK_acircumflexgrave +XK_Acircumflexhook +XK_acircumflexhook +XK_Acircumflextilde +XK_acircumflextilde +XK_Acircumflexbelowdot +XK_acircumflexbelowdot +XK_Abreveacute +XK_abreveacute +XK_Abrevegrave +XK_abrevegrave +XK_Abrevehook +XK_abrevehook +XK_Abrevetilde +XK_abrevetilde +XK_Abrevebelowdot +XK_abrevebelowdot +XK_Ebelowdot +XK_ebelowdot +XK_Ehook +XK_ehook +XK_Etilde +XK_etilde +XK_Ecircumflexacute +XK_ecircumflexacute +XK_Ecircumflexgrave +XK_ecircumflexgrave +XK_Ecircumflexhook +XK_ecircumflexhook +XK_Ecircumflextilde +XK_ecircumflextilde +XK_Ecircumflexbelowdot +XK_ecircumflexbelowdot +XK_Ihook +XK_ihook +XK_Ibelowdot +XK_ibelowdot +XK_Obelowdot +XK_obelowdot +XK_Ohook +XK_ohook +XK_Ocircumflexacute +XK_ocircumflexacute +XK_Ocircumflexgrave +XK_ocircumflexgrave +XK_Ocircumflexhook +XK_ocircumflexhook +XK_Ocircumflextilde +XK_ocircumflextilde +XK_Ocircumflexbelowdot +XK_ocircumflexbelowdot +XK_Ohornacute +XK_ohornacute +XK_Ohorngrave +XK_ohorngrave +XK_Ohornhook +XK_ohornhook +XK_Ohorntilde +XK_ohorntilde +XK_Ohornbelowdot +XK_ohornbelowdot +XK_Ubelowdot +XK_ubelowdot +XK_Uhook +XK_uhook +XK_Uhornacute +XK_uhornacute +XK_Uhorngrave +XK_uhorngrave +XK_Uhornhook +XK_uhornhook +XK_Uhorntilde +XK_uhorntilde +XK_Uhornbelowdot +XK_uhornbelowdot +XK_Ybelowdot +XK_ybelowdot +XK_Yhook +XK_yhook +XK_Ytilde +XK_ytilde +XK_Ohorn +XK_ohorn +XK_Uhorn +XK_uhorn +*/ + +// map "Internet" keys to KeyIDs +static const KeySym s_map1008FF[] = +{ + /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 */ 0, kKeyAudioDown, kKeyAudioMute, kKeyAudioUp, + /* 0x14 */ kKeyAudioPlay, kKeyAudioStop, kKeyAudioPrev, kKeyAudioNext, + /* 0x18 */ kKeyWWWHome, kKeyAppMail, 0, kKeyWWWSearch, 0, 0, 0, 0, + /* 0x20 */ 0, 0, 0, 0, 0, 0, kKeyWWWBack, kKeyWWWForward, + /* 0x28 */ kKeyWWWStop, kKeyWWWRefresh, 0, 0, kKeyEject, 0, 0, 0, + /* 0x30 */ kKeyWWWFavorites, 0, kKeyAppMedia, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ kKeyAppUser1, kKeyAppUser2, 0, 0, 0, 0, 0, 0, + /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +// +// CXWindowsUtil +// + +CXWindowsUtil::CKeySymMap CXWindowsUtil::s_keySymToUCS4; + +bool +CXWindowsUtil::getWindowProperty(Display* display, Window window, + Atom property, CString* data, Atom* type, + SInt32* format, bool deleteProperty) +{ + assert(display != NULL); + + Atom actualType; + int actualDatumSize; + + // ignore errors. XGetWindowProperty() will report failure. + CXWindowsUtil::CErrorLock lock(display); + + // read the property + bool okay = true; + const long length = XMaxRequestSize(display); + long offset = 0; + unsigned long bytesLeft = 1; + while (bytesLeft != 0) { + // get more data + unsigned long numItems; + unsigned char* rawData; + if (XGetWindowProperty(display, window, property, + offset, length, False, AnyPropertyType, + &actualType, &actualDatumSize, + &numItems, &bytesLeft, &rawData) != Success || + actualType == None || actualDatumSize == 0) { + // failed + okay = false; + break; + } + + // compute bytes read and advance offset + unsigned long numBytes; + switch (actualDatumSize) { + case 8: + default: + numBytes = numItems; + offset += numItems / 4; + break; + + case 16: + numBytes = 2 * numItems; + offset += numItems / 2; + break; + + case 32: + numBytes = 4 * numItems; + offset += numItems; + break; + } + + // append data + if (data != NULL) { + data->append((char*)rawData, numBytes); + } + else { + // data is not required so don't try to get any more + bytesLeft = 0; + } + + // done with returned data + XFree(rawData); + } + + // delete the property if requested + if (deleteProperty) { + XDeleteProperty(display, window, property); + } + + // save property info + if (type != NULL) { + *type = actualType; + } + if (format != NULL) { + *format = static_cast(actualDatumSize); + } + + if (okay) { + LOG((CLOG_DEBUG2 "read property %d on window 0x%08x: bytes=%d", property, window, (data == NULL) ? 0 : data->size())); + return true; + } + else { + LOG((CLOG_DEBUG2 "can't read property %d on window 0x%08x", property, window)); + return false; + } +} + +bool +CXWindowsUtil::setWindowProperty(Display* display, Window window, + Atom property, const void* vdata, UInt32 size, + Atom type, SInt32 format) +{ + const UInt32 length = 4 * XMaxRequestSize(display); + const unsigned char* data = reinterpret_cast(vdata); + const UInt32 datumSize = static_cast(format / 8); + + // save errors + bool error = false; + CXWindowsUtil::CErrorLock lock(display, &error); + + // how much data to send in first chunk? + UInt32 chunkSize = size; + if (chunkSize > length) { + chunkSize = length; + } + + // send first chunk + XChangeProperty(display, window, property, + type, format, PropModeReplace, + data, chunkSize / datumSize); + + // append remaining chunks + data += chunkSize; + size -= chunkSize; + while (!error && size > 0) { + chunkSize = size; + if (chunkSize > length) { + chunkSize = length; + } + XChangeProperty(display, window, property, + type, format, PropModeAppend, + data, chunkSize / datumSize); + data += chunkSize; + size -= chunkSize; + } + + return !error; +} + +Time +CXWindowsUtil::getCurrentTime(Display* display, Window window) +{ + // select property events on window + XWindowAttributes attr; + XGetWindowAttributes(display, window, &attr); + XSelectInput(display, window, attr.your_event_mask | PropertyChangeMask); + + // make a property name to receive dummy change + Atom atom = XInternAtom(display, "TIMESTAMP", False); + + // do a zero-length append to get the current time + unsigned char dummy; + XChangeProperty(display, window, atom, + XA_INTEGER, 8, + PropModeAppend, + &dummy, 0); + + // look for property notify events with the following + CPropertyNotifyPredicateInfo filter; + filter.m_window = window; + filter.m_property = atom; + + // wait for reply + XEvent xevent; + XIfEvent(display, &xevent, &CXWindowsUtil::propertyNotifyPredicate, + (XPointer)&filter); + assert(xevent.type == PropertyNotify); + assert(xevent.xproperty.window == window); + assert(xevent.xproperty.atom == atom); + + // restore event mask + XSelectInput(display, window, attr.your_event_mask); + + return xevent.xproperty.time; +} + +KeyID +CXWindowsUtil::mapKeySymToKeyID(KeySym k) +{ + initKeyMaps(); + + switch (k & 0xffffff00) { + case 0x0000: + // Latin-1 + return static_cast(k); + + case 0xfe00: + // ISO 9995 Function and Modifier Keys + switch (k) { + case XK_ISO_Left_Tab: + return kKeyLeftTab; + + case XK_ISO_Level3_Shift: + return kKeyAltGr; + + case XK_ISO_Next_Group: + return kKeyNextGroup; + + case XK_ISO_Prev_Group: + return kKeyPrevGroup; + + case XK_dead_grave: + return kKeyDeadGrave; + + case XK_dead_acute: + return kKeyDeadAcute; + + case XK_dead_circumflex: + return kKeyDeadCircumflex; + + case XK_dead_tilde: + return kKeyDeadTilde; + + case XK_dead_macron: + return kKeyDeadMacron; + + case XK_dead_breve: + return kKeyDeadBreve; + + case XK_dead_abovedot: + return kKeyDeadAbovedot; + + case XK_dead_diaeresis: + return kKeyDeadDiaeresis; + + case XK_dead_abovering: + return kKeyDeadAbovering; + + case XK_dead_doubleacute: + return kKeyDeadDoubleacute; + + case XK_dead_caron: + return kKeyDeadCaron; + + case XK_dead_cedilla: + return kKeyDeadCedilla; + + case XK_dead_ogonek: + return kKeyDeadOgonek; + + default: + return kKeyNone; + } + + case 0xff00: + // MISCELLANY + return static_cast(k - 0xff00 + 0xef00); + + case 0x1008ff00: + // "Internet" keys + return s_map1008FF[k & 0xff]; + + default: { + // lookup character in table + CKeySymMap::const_iterator index = s_keySymToUCS4.find(k); + if (index != s_keySymToUCS4.end()) { + return static_cast(index->second); + } + + // unknown character + return kKeyNone; + } + } +} + +UInt32 +CXWindowsUtil::getModifierBitForKeySym(KeySym keysym) +{ + switch (keysym) { + case XK_Shift_L: + case XK_Shift_R: + return kKeyModifierBitShift; + + case XK_Control_L: + case XK_Control_R: + return kKeyModifierBitControl; + + case XK_Alt_L: + case XK_Alt_R: + return kKeyModifierBitAlt; + + case XK_Meta_L: + case XK_Meta_R: + return kKeyModifierBitMeta; + + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + return kKeyModifierBitSuper; + + case XK_Mode_switch: + case XK_ISO_Level3_Shift: + return kKeyModifierBitAltGr; + + case XK_Caps_Lock: + return kKeyModifierBitCapsLock; + + case XK_Num_Lock: + return kKeyModifierBitNumLock; + + case XK_Scroll_Lock: + return kKeyModifierBitScrollLock; + + default: + return kKeyModifierBitNone; + } +} + +CString +CXWindowsUtil::atomToString(Display* display, Atom atom) +{ + if (atom == 0) { + return "None"; + } + + bool error = false; + CXWindowsUtil::CErrorLock lock(display, &error); + char* name = XGetAtomName(display, atom); + if (error) { + return CStringUtil::print(" (%d)", (int)atom); + } + else { + CString msg = CStringUtil::print("%s (%d)", name, (int)atom); + XFree(name); + return msg; + } +} + +CString +CXWindowsUtil::atomsToString(Display* display, const Atom* atom, UInt32 num) +{ + char** names = new char*[num]; + bool error = false; + CXWindowsUtil::CErrorLock lock(display, &error); + XGetAtomNames(display, const_cast(atom), (int)num, names); + CString msg; + if (error) { + for (UInt32 i = 0; i < num; ++i) { + msg += CStringUtil::print(" (%d), ", (int)atom[i]); + } + } + else { + for (UInt32 i = 0; i < num; ++i) { + msg += CStringUtil::print("%s (%d), ", names[i], (int)atom[i]); + XFree(names[i]); + } + } + delete[] names; + if (msg.size() > 2) { + msg.erase(msg.size() - 2); + } + return msg; +} + +void +CXWindowsUtil::convertAtomProperty(CString& data) +{ + // as best i can tell, 64-bit systems don't pack Atoms into properties + // as 32-bit numbers but rather as the 64-bit numbers they are. that + // seems wrong but we have to cope. sometimes we'll get a list of + // atoms that's 8*n+4 bytes long, missing the trailing 4 bytes which + // should all be 0. since we're going to reference the Atoms as + // 64-bit numbers we have to ensure the last number is a full 64 bits. + if (sizeof(Atom) != 4 && ((data.size() / 4) & 1) != 0) { + UInt32 zero = 0; + data.append(reinterpret_cast(&zero), sizeof(zero)); + } +} + +void +CXWindowsUtil::appendAtomData(CString& data, Atom atom) +{ + data.append(reinterpret_cast(&atom), sizeof(Atom)); +} + +void +CXWindowsUtil::replaceAtomData(CString& data, UInt32 index, Atom atom) +{ + data.replace(index * sizeof(Atom), sizeof(Atom), + reinterpret_cast(&atom), + sizeof(Atom)); +} + +void +CXWindowsUtil::appendTimeData(CString& data, Time time) +{ + data.append(reinterpret_cast(&time), sizeof(Time)); +} + +Bool +CXWindowsUtil::propertyNotifyPredicate(Display*, XEvent* xevent, XPointer arg) +{ + CPropertyNotifyPredicateInfo* filter = + reinterpret_cast(arg); + return (xevent->type == PropertyNotify && + xevent->xproperty.window == filter->m_window && + xevent->xproperty.atom == filter->m_property && + xevent->xproperty.state == PropertyNewValue) ? True : False; +} + +void +CXWindowsUtil::initKeyMaps() +{ + if (s_keySymToUCS4.empty()) { + for (size_t i =0; i < sizeof(s_keymap) / sizeof(s_keymap[0]); ++i) { + s_keySymToUCS4[s_keymap[i].keysym] = s_keymap[i].ucs4; + } + } +} + + +// +// CXWindowsUtil::CErrorLock +// + +CXWindowsUtil::CErrorLock* CXWindowsUtil::CErrorLock::s_top = NULL; + +CXWindowsUtil::CErrorLock::CErrorLock(Display* display) : + m_display(display) +{ + install(&CXWindowsUtil::CErrorLock::ignoreHandler, NULL); +} + +CXWindowsUtil::CErrorLock::CErrorLock(Display* display, bool* flag) : + m_display(display) +{ + install(&CXWindowsUtil::CErrorLock::saveHandler, flag); +} + +CXWindowsUtil::CErrorLock::CErrorLock(Display* display, + ErrorHandler handler, void* data) : + m_display(display) +{ + install(handler, data); +} + +CXWindowsUtil::CErrorLock::~CErrorLock() +{ + // make sure everything finishes before uninstalling handler + if (m_display != NULL) { + XSync(m_display, False); + } + + // restore old handler + XSetErrorHandler(m_oldXHandler); + s_top = m_next; +} + +void +CXWindowsUtil::CErrorLock::install(ErrorHandler handler, void* data) +{ + // make sure everything finishes before installing handler + if (m_display != NULL) { + XSync(m_display, False); + } + + // install handler + m_handler = handler; + m_userData = data; + m_oldXHandler = XSetErrorHandler( + &CXWindowsUtil::CErrorLock::internalHandler); + m_next = s_top; + s_top = this; +} + +int +CXWindowsUtil::CErrorLock::internalHandler(Display* display, XErrorEvent* event) +{ + if (s_top != NULL && s_top->m_handler != NULL) { + s_top->m_handler(display, event, s_top->m_userData); + } + return 0; +} + +void +CXWindowsUtil::CErrorLock::ignoreHandler(Display*, XErrorEvent* e, void*) +{ + LOG((CLOG_DEBUG1 "ignoring X error: %d", e->error_code)); +} + +void +CXWindowsUtil::CErrorLock::saveHandler(Display*, XErrorEvent* e, void* flag) +{ + LOG((CLOG_DEBUG1 "flagging X error: %d", e->error_code)); + *reinterpret_cast(flag) = true; +} diff --git a/lib/platform/CXWindowsUtil.h b/lib/platform/CXWindowsUtil.h new file mode 100644 index 00000000..a9049ef6 --- /dev/null +++ b/lib/platform/CXWindowsUtil.h @@ -0,0 +1,185 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CXWINDOWSUTIL_H +#define CXWINDOWSUTIL_H + +#include "CString.h" +#include "BasicTypes.h" +#include "stdmap.h" +#include "stdvector.h" +#if X_DISPLAY_MISSING +# error X11 is required to build synergy +#else +# include +#endif + +//! X11 utility functions +class CXWindowsUtil { +public: + typedef std::vector KeySyms; + + //! Get property + /*! + Gets property \c property on \c window. \b Appends the data to + \c *data if \c data is not NULL, saves the property type in \c *type + if \c type is not NULL, and saves the property format in \c *format + if \c format is not NULL. If \c deleteProperty is true then the + property is deleted after being read. + */ + static bool getWindowProperty(Display*, + Window window, Atom property, + CString* data, Atom* type, + SInt32* format, bool deleteProperty); + + //! Set property + /*! + Sets property \c property on \c window to \c size bytes of data from + \c data. + */ + static bool setWindowProperty(Display*, + Window window, Atom property, + const void* data, UInt32 size, + Atom type, SInt32 format); + + //! Get X server time + /*! + Returns the current X server time. + */ + static Time getCurrentTime(Display*, Window); + + //! Convert KeySym to KeyID + /*! + Converts a KeySym to the equivalent KeyID. Returns kKeyNone if the + KeySym cannot be mapped. + */ + static UInt32 mapKeySymToKeyID(KeySym); + + //! Convert KeySym to corresponding KeyModifierMask + /*! + Converts a KeySym to the corresponding KeyModifierMask, or 0 if the + KeySym is not a modifier. + */ + static UInt32 getModifierBitForKeySym(KeySym keysym); + + //! Convert Atom to its string + /*! + Converts \p atom to its string representation. + */ + static CString atomToString(Display*, Atom atom); + + //! Convert several Atoms to a string + /*! + Converts each atom in \p atoms to its string representation and + concatenates the results. + */ + static CString atomsToString(Display* display, + const Atom* atom, UInt32 num); + + //! Prepare a property of atoms for use + /*! + 64-bit systems may need to modify a property's data if it's a + list of Atoms before using it. + */ + static void convertAtomProperty(CString& data); + + //! Append an Atom to property data + /*! + Converts \p atom to a 32-bit on-the-wire format and appends it to + \p data. + */ + static void appendAtomData(CString& data, Atom atom); + + //! Replace an Atom in property data + /*! + Converts \p atom to a 32-bit on-the-wire format and replaces the atom + at index \p index in \p data. + */ + static void replaceAtomData(CString& data, + UInt32 index, Atom atom); + + //! Append an Time to property data + /*! + Converts \p time to a 32-bit on-the-wire format and appends it to + \p data. + */ + static void appendTimeData(CString& data, Time time); + + //! X11 error handler + /*! + This class sets an X error handler in the c'tor and restores the + previous error handler in the d'tor. A lock should only be + installed while the display is locked by the thread. + + CErrorLock() ignores errors + CErrorLock(bool* flag) sets *flag to true if any error occurs + */ + class CErrorLock { + public: + //! Error handler type + typedef void (*ErrorHandler)(Display*, XErrorEvent*, void* userData); + + /*! + Ignore X11 errors. + */ + CErrorLock(Display*); + + /*! + Set \c *errorFlag if any error occurs. + */ + CErrorLock(Display*, bool* errorFlag); + + /*! + Call \c handler on each error. + */ + CErrorLock(Display*, ErrorHandler handler, void* userData); + + ~CErrorLock(); + + private: + void install(ErrorHandler, void*); + static int internalHandler(Display*, XErrorEvent*); + static void ignoreHandler(Display*, XErrorEvent*, void*); + static void saveHandler(Display*, XErrorEvent*, void*); + + private: + typedef int (*XErrorHandler)(Display*, XErrorEvent*); + + Display* m_display; + ErrorHandler m_handler; + void* m_userData; + XErrorHandler m_oldXHandler; + CErrorLock* m_next; + static CErrorLock* s_top; + }; + +private: + class CPropertyNotifyPredicateInfo { + public: + Window m_window; + Atom m_property; + }; + + static Bool propertyNotifyPredicate(Display*, + XEvent* xevent, XPointer arg); + + static void initKeyMaps(); + +private: + typedef std::map CKeySymMap; + + static CKeySymMap s_keySymToUCS4; +}; + +#endif diff --git a/lib/platform/Makefile.am b/lib/platform/Makefile.am new file mode 100644 index 00000000..6ff0523c --- /dev/null +++ b/lib/platform/Makefile.am @@ -0,0 +1,123 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +XWINDOWS_SOURCE_FILES = \ + CXWindowsClipboard.cpp \ + CXWindowsClipboardAnyBitmapConverter.cpp\ + CXWindowsClipboardBMPConverter.cpp \ + CXWindowsClipboardHTMLConverter.cpp \ + CXWindowsClipboardTextConverter.cpp \ + CXWindowsClipboardUCS2Converter.cpp \ + CXWindowsClipboardUTF8Converter.cpp \ + CXWindowsEventQueueBuffer.cpp \ + CXWindowsKeyState.cpp \ + CXWindowsScreen.cpp \ + CXWindowsScreenSaver.cpp \ + CXWindowsUtil.cpp \ + CXWindowsClipboard.h \ + CXWindowsClipboardAnyBitmapConverter.h \ + CXWindowsClipboardBMPConverter.h \ + CXWindowsClipboardHTMLConverter.h \ + CXWindowsClipboardTextConverter.h \ + CXWindowsClipboardUCS2Converter.h \ + CXWindowsClipboardUTF8Converter.h \ + CXWindowsEventQueueBuffer.h \ + CXWindowsKeyState.h \ + CXWindowsScreen.h \ + CXWindowsScreenSaver.h \ + CXWindowsUtil.h \ + $(NULL) +MSWINDOWS_SOURCE_FILES = \ + CMSWindowsClipboard.cpp \ + CMSWindowsClipboardAnyTextConverter.cpp \ + CMSWindowsClipboardBitmapConverter.cpp \ + CMSWindowsClipboardHTMLConverter.cpp \ + CMSWindowsClipboardTextConverter.cpp \ + CMSWindowsClipboardUTF16Converter.cpp \ + CMSWindowsDesks.cpp \ + CMSWindowsEventQueueBuffer.cpp \ + CMSWindowsKeyState.cpp \ + CMSWindowsScreen.cpp \ + CMSWindowsScreenSaver.cpp \ + CMSWindowsUtil.cpp \ + CMSWindowsClipboard.h \ + CMSWindowsClipboardAnyTextConverter.h \ + CMSWindowsClipboardBitmapConverter.h \ + CMSWindowsClipboardHTMLConverter.h \ + CMSWindowsClipboardTextConverter.h \ + CMSWindowsClipboardUTF16Converter.h \ + CMSWindowsDesks.h \ + CMSWindowsEventQueueBuffer.h \ + CMSWindowsKeyState.h \ + CMSWindowsScreen.h \ + CMSWindowsScreenSaver.h \ + CMSWindowsUtil.h \ + $(NULL) +MSWINDOWS_HOOK_SOURCE_FILES = \ + CSynergyHook.cpp \ + CSynergyHook.h \ + $(NULL) +CARBON_SOURCE_FILES = \ + COSXClipboard.cpp \ + COSXClipboardAnyTextConverter.cpp \ + COSXClipboardTextConverter.cpp \ + COSXClipboardUTF16Converter.cpp \ + COSXEventQueueBuffer.cpp \ + COSXKeyState.cpp \ + COSXScreen.cpp \ + COSXScreenSaver.cpp \ + COSXScreenSaverUtil.m \ + COSXClipboard.h \ + COSXClipboardAnyTextConverter.h \ + COSXClipboardTextConverter.h \ + COSXClipboardUTF16Converter.h \ + COSXEventQueueBuffer.h \ + COSXKeyState.h \ + COSXScreen.h \ + COSXScreenSaver.h \ + COSXScreenSaverUtil.h \ + OSXScreenSaverControl.h \ + $(NULL) + +EXTRA_DIST = \ + Makefile.win \ + $(XWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_SOURCE_FILES) \ + $(MSWINDOWS_HOOK_SOURCE_FILES) \ + $(CARBON_SOURCE_FILES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libplatform.a +if XWINDOWS +libplatform_a_SOURCES = $(XWINDOWS_SOURCE_FILES) +endif +if MSWINDOWS +libplatform_a_SOURCES = $(MSWINDOWS_SOURCE_FILES) +endif +if CARBON +libplatform_a_SOURCES = $(CARBON_SOURCE_FILES) +endif +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/synergy \ + $(NULL) diff --git a/lib/platform/Makefile.win b/lib/platform/Makefile.win new file mode 100644 index 00000000..001200d1 --- /dev/null +++ b/lib/platform/Makefile.win @@ -0,0 +1,119 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_PLATFORM_SRC = lib\platform +LIB_PLATFORM_DST = $(BUILD_DST)\$(LIB_PLATFORM_SRC) +LIB_PLATFORM_LIB = "$(LIB_PLATFORM_DST)\platform.lib" +LIB_PLATFORM_CPP = \ + "CMSWindowsClipboard.cpp" \ + "CMSWindowsClipboardAnyTextConverter.cpp" \ + "CMSWindowsClipboardBitmapConverter.cpp" \ + "CMSWindowsClipboardHTMLConverter.cpp" \ + "CMSWindowsClipboardTextConverter.cpp" \ + "CMSWindowsClipboardUTF16Converter.cpp" \ + "CMSWindowsDesks.cpp" \ + "CMSWindowsEventQueueBuffer.cpp" \ + "CMSWindowsKeyState.cpp" \ + "CMSWindowsScreen.cpp" \ + "CMSWindowsScreenSaver.cpp" \ + "CMSWindowsUtil.cpp" \ + $(NULL) +LIB_PLATFORM_OBJ = \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboard.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardAnyTextConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardBitmapConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardHTMLConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardTextConverter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsClipboardUTF16Converter.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsDesks.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsEventQueueBuffer.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsKeyState.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsScreen.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsScreenSaver.obj" \ + "$(LIB_PLATFORM_DST)\CMSWindowsUtil.obj" \ + $(NULL) +LIB_PLATFORM_HOOK_CPP = \ + "$(LIB_PLATFORM_SRC)\CSynergyHook.cpp" \ + $(NULL) +LIB_PLATFORM_HOOK_OBJ = \ + "$(LIB_PLATFORM_DST)\CSynergyHook.obj" \ + $(NULL) +LIB_PLATFORM_HOOK_DLL = "$(BUILD_DST)\synrgyhk.dll" +LIB_PLATFORM_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_PLATFORM_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_PLATFORM_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_PLATFORM_LIB) $(LIB_PLATFORM_HOOK_DLL) + +# Hook should be as small as possible. +cpphookdebug = $(cppdebug:-Ox=-O1) + +# Don't do security checks or run time error checking on hook. +cpphookflags = $(cppflags:-GS=) +cpphookdebug = $(cpphookdebug:/GZ=) +cpphookdebug = $(cpphookdebug:/RTC1=) + +# Dependency rules +$(LIB_PLATFORM_OBJ): $(AUTODEP) +!if EXIST($(LIB_PLATFORM_DST)\deps.mak) +!include $(LIB_PLATFORM_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_PLATFORM_SRC)\}.cpp{$(LIB_PLATFORM_DST)\}.obj:: +!else +{$(LIB_PLATFORM_SRC)\}.cpp{$(LIB_PLATFORM_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_PLATFORM_SRC) + -@$(MKDIR) $(LIB_PLATFORM_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_PLATFORM_INC) \ + /Fo$(LIB_PLATFORM_DST)\ \ + /Fd$(LIB_PLATFORM_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) +$(LIB_PLATFORM_LIB): $(LIB_PLATFORM_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) \ + $(LIB_PLATFORM_OBJ:.obj=.d) $(LIB_PLATFORM_HOOK_OBJ:.obj=.d) + +# Hook build rules +$(LIB_PLATFORM_HOOK_OBJ): \ + $(LIB_PLATFORM_HOOK_CPP) $(LIB_PLATFORM_HOOK_CPP:.cpp=.h) + @$(ECHO) Compile $(LIB_PLATFORM_HOOK_CPP) + -@$(MKDIR) $(LIB_PLATFORM_DST) 2>NUL: + $(cpp) $(cpphookdebug) $(cpphookflags) $(cppvarsmt) /showIncludes \ + -D_DLL -D_USRDLL -DSYNRGYHK_EXPORTS \ + $(LIB_PLATFORM_INC) \ + /Fo$(LIB_PLATFORM_DST)\ \ + /Fd$(@:.obj=.pdb) \ + $(LIB_PLATFORM_HOOK_CPP) | \ + $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) +$(LIB_PLATFORM_HOOK_DLL): $(LIB_PLATFORM_HOOK_OBJ) + @$(ECHO) Link $(@F) + $(link) $(ldebug) $(lflags) $(guilibsmt) \ + /entry:"DllMain$(DLLENTRY)" /dll \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_PLATFORM_SRC) $(LIB_PLATFORM_DST) \ + $(LIB_PLATFORM_OBJ:.obj=.d) $(LIB_PLATFORM_HOOK_OBJ:.obj=.d) diff --git a/lib/platform/OSXScreenSaverControl.h b/lib/platform/OSXScreenSaverControl.h new file mode 100644 index 00000000..75aecb17 --- /dev/null +++ b/lib/platform/OSXScreenSaverControl.h @@ -0,0 +1,36 @@ +// ScreenSaver.framework private API +// Class dumping by Alex Harper http://www.ragingmenace.com/ + +#import + +@protocol ScreenSaverControl +- (double)screenSaverTimeRemaining; +- (void)restartForUser:fp12; +- (void)screenSaverStopNow; +- (void)screenSaverStartNow; +- (void)setScreenSaverCanRun:(char)fp12; +- (BOOL)screenSaverCanRun; +- (BOOL)screenSaverIsRunning; +@end + + +@interface ScreenSaverController:NSObject + ++ controller; ++ monitor; ++ daemonConnectionName; ++ daemonPath; ++ enginePath; +- init; +- (void)dealloc; +- (void)_connectionClosed:fp12; +- (BOOL)screenSaverIsRunning; +- (BOOL)screenSaverCanRun; +- (void)setScreenSaverCanRun:(char)fp12; +- (void)screenSaverStartNow; +- (void)screenSaverStopNow; +- (void)restartForUser:fp12; +- (double)screenSaverTimeRemaining; + +@end + diff --git a/lib/server/CBaseClientProxy.cpp b/lib/server/CBaseClientProxy.cpp new file mode 100644 index 00000000..0f049e35 --- /dev/null +++ b/lib/server/CBaseClientProxy.cpp @@ -0,0 +1,52 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CBaseClientProxy.h" + +// +// CBaseClientProxy +// + +CBaseClientProxy::CBaseClientProxy(const CString& name) : + m_name(name), + m_x(0), + m_y(0) +{ + // do nothing +} + +CBaseClientProxy::~CBaseClientProxy() +{ + // do nothing +} + +void +CBaseClientProxy::setJumpCursorPos(SInt32 x, SInt32 y) +{ + m_x = x; + m_y = y; +} + +void +CBaseClientProxy::getJumpCursorPos(SInt32& x, SInt32& y) const +{ + x = m_x; + y = m_y; +} + +CString +CBaseClientProxy::getName() const +{ + return m_name; +} diff --git a/lib/server/CBaseClientProxy.h b/lib/server/CBaseClientProxy.h new file mode 100644 index 00000000..e9cceca4 --- /dev/null +++ b/lib/server/CBaseClientProxy.h @@ -0,0 +1,85 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CBASECLIENTPROXY_H +#define CBASECLIENTPROXY_H + +#include "IClient.h" +#include "CString.h" + +//! Generic proxy for client or primary +class CBaseClientProxy : public IClient { +public: + /*! + \c name is the name of the client. + */ + CBaseClientProxy(const CString& name); + ~CBaseClientProxy(); + + //! @name manipulators + //@{ + + //! Save cursor position + /*! + Save the position of the cursor when jumping from client. + */ + void setJumpCursorPos(SInt32 x, SInt32 y); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Get the position of the cursor when last jumping from client. + */ + void getJumpCursorPos(SInt32& x, SInt32& y) const; + + //@} + + // IScreen + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + virtual bool leave() = 0; + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void grabClipboard(ClipboardID) = 0; + virtual void setClipboardDirty(ClipboardID, bool) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void mouseDown(ButtonID) = 0; + virtual void mouseUp(ButtonID) = 0; + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const COptionsList& options) = 0; + virtual CString getName() const; + +private: + CString m_name; + SInt32 m_x, m_y; +}; + +#endif diff --git a/lib/server/CClientListener.cpp b/lib/server/CClientListener.cpp new file mode 100644 index 00000000..803f44a0 --- /dev/null +++ b/lib/server/CClientListener.cpp @@ -0,0 +1,194 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientListener.h" +#include "CClientProxy.h" +#include "CClientProxyUnknown.h" +#include "CPacketStreamFilter.h" +#include "IStreamFilterFactory.h" +#include "IDataSocket.h" +#include "IListenSocket.h" +#include "ISocketFactory.h" +#include "XSocket.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientListener +// + +CEvent::Type CClientListener::s_connectedEvent = CEvent::kUnknown; + +CClientListener::CClientListener(const CNetworkAddress& address, + ISocketFactory* socketFactory, + IStreamFilterFactory* streamFilterFactory) : + m_socketFactory(socketFactory), + m_streamFilterFactory(streamFilterFactory) +{ + assert(m_socketFactory != NULL); + + try { + // create listen socket + m_listen = m_socketFactory->createListen(); + + // bind listen address + LOG((CLOG_DEBUG1 "binding listen socket")); + m_listen->bind(address); + } + catch (XSocketAddressInUse&) { + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; + throw; + } + catch (XBase&) { + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; + throw; + } + LOG((CLOG_DEBUG1 "listening for clients")); + + // setup event handler + EVENTQUEUE->adoptHandler(IListenSocket::getConnectingEvent(), m_listen, + new TMethodEventJob(this, + &CClientListener::handleClientConnecting)); +} + +CClientListener::~CClientListener() +{ + LOG((CLOG_DEBUG1 "stop listening for clients")); + + // discard already connected clients + for (CNewClients::iterator index = m_newClients.begin(); + index != m_newClients.end(); ++index) { + CClientProxyUnknown* client = *index; + EVENTQUEUE->removeHandler( + CClientProxyUnknown::getSuccessEvent(), client); + EVENTQUEUE->removeHandler( + CClientProxyUnknown::getFailureEvent(), client); + EVENTQUEUE->removeHandler( + CClientProxy::getDisconnectedEvent(), client); + delete client; + } + + // discard waiting clients + CClientProxy* client = getNextClient(); + while (client != NULL) { + delete client; + client = getNextClient(); + } + + EVENTQUEUE->removeHandler(IListenSocket::getConnectingEvent(), m_listen); + delete m_listen; + delete m_socketFactory; + delete m_streamFilterFactory; +} + +CClientProxy* +CClientListener::getNextClient() +{ + CClientProxy* client = NULL; + if (!m_waitingClients.empty()) { + client = m_waitingClients.front(); + m_waitingClients.pop_front(); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + } + return client; +} + +CEvent::Type +CClientListener::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CClientListener::connected"); +} + +void +CClientListener::handleClientConnecting(const CEvent&, void*) +{ + // accept client connection + IStream* stream = m_listen->accept(); + if (stream == NULL) { + return; + } + LOG((CLOG_NOTE "accepted client connection")); + + // filter socket messages, including a packetizing filter + if (m_streamFilterFactory != NULL) { + stream = m_streamFilterFactory->create(stream, true); + } + stream = new CPacketStreamFilter(stream, true); + + // create proxy for unknown client + CClientProxyUnknown* client = new CClientProxyUnknown(stream, 30.0); + m_newClients.insert(client); + + // watch for events from unknown client + EVENTQUEUE->adoptHandler(CClientProxyUnknown::getSuccessEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleUnknownClient, client)); + EVENTQUEUE->adoptHandler(CClientProxyUnknown::getFailureEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleUnknownClient, client)); +} + +void +CClientListener::handleUnknownClient(const CEvent&, void* vclient) +{ + CClientProxyUnknown* unknownClient = + reinterpret_cast(vclient); + + // we should have the client in our new client list + assert(m_newClients.count(unknownClient) == 1); + + // get the real client proxy and install it + CClientProxy* client = unknownClient->orphanClientProxy(); + if (client != NULL) { + // handshake was successful + m_waitingClients.push_back(client); + EVENTQUEUE->addEvent(CEvent(getConnectedEvent(), this)); + + // watch for client to disconnect while it's in our queue + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client, + new TMethodEventJob(this, + &CClientListener::handleClientDisconnected, + client)); + } + + // now finished with unknown client + EVENTQUEUE->removeHandler(CClientProxyUnknown::getSuccessEvent(), client); + EVENTQUEUE->removeHandler(CClientProxyUnknown::getFailureEvent(), client); + m_newClients.erase(unknownClient); + delete unknownClient; +} + +void +CClientListener::handleClientDisconnected(const CEvent&, void* vclient) +{ + CClientProxy* client = reinterpret_cast(vclient); + + // find client in waiting clients queue + for (CWaitingClients::iterator i = m_waitingClients.begin(), + n = m_waitingClients.end(); i != n; ++i) { + if (*i == client) { + m_waitingClients.erase(i); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), + client); + delete client; + break; + } + } +} diff --git a/lib/server/CClientListener.h b/lib/server/CClientListener.h new file mode 100644 index 00000000..e5302e69 --- /dev/null +++ b/lib/server/CClientListener.h @@ -0,0 +1,76 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTLISTENER_H +#define CCLIENTLISTENER_H + +#include "CConfig.h" +#include "CEvent.h" +#include "stddeque.h" +#include "stdset.h" + +class CClientProxy; +class CClientProxyUnknown; +class CNetworkAddress; +class IListenSocket; +class ISocketFactory; +class IStreamFilterFactory; + +class CClientListener { +public: + // The factories are adopted. + CClientListener(const CNetworkAddress&, + ISocketFactory*, IStreamFilterFactory*); + ~CClientListener(); + + //! @name accessors + //@{ + + //! Get next connected client + /*! + Returns the next connected client and removes it from the internal + list. The client is responsible for deleting the returned client. + Returns NULL if no clients are available. + */ + CClientProxy* getNextClient(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent whenever a + a client connects. + */ + static CEvent::Type getConnectedEvent(); + + //@} + +private: + // client connection event handlers + void handleClientConnecting(const CEvent&, void*); + void handleUnknownClient(const CEvent&, void*); + void handleClientDisconnected(const CEvent&, void*); + +private: + typedef std::set CNewClients; + typedef std::deque CWaitingClients; + + IListenSocket* m_listen; + ISocketFactory* m_socketFactory; + IStreamFilterFactory* m_streamFilterFactory; + CNewClients m_newClients; + CWaitingClients m_waitingClients; + + static CEvent::Type s_connectedEvent; +}; + +#endif diff --git a/lib/server/CClientProxy.cpp b/lib/server/CClientProxy.cpp new file mode 100644 index 00000000..715126d5 --- /dev/null +++ b/lib/server/CClientProxy.cpp @@ -0,0 +1,81 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientProxy.h" +#include "CProtocolUtil.h" +#include "IStream.h" +#include "CLog.h" + +// +// CClientProxy +// + +CEvent::Type CClientProxy::s_readyEvent = CEvent::kUnknown; +CEvent::Type CClientProxy::s_disconnectedEvent = CEvent::kUnknown; +CEvent::Type CClientProxy::s_clipboardChangedEvent= CEvent::kUnknown; + +CClientProxy::CClientProxy(const CString& name, IStream* stream) : + CBaseClientProxy(name), + m_stream(stream) +{ + // do nothing +} + +CClientProxy::~CClientProxy() +{ + delete m_stream; +} + +void +CClientProxy::close(const char* msg) +{ + LOG((CLOG_DEBUG1 "send close \"%s\" to \"%s\"", msg, getName().c_str())); + CProtocolUtil::writef(getStream(), msg); + + // force the close to be sent before we return + getStream()->flush(); +} + +IStream* +CClientProxy::getStream() const +{ + return m_stream; +} + +CEvent::Type +CClientProxy::getReadyEvent() +{ + return CEvent::registerTypeOnce(s_readyEvent, + "CClientProxy::ready"); +} + +CEvent::Type +CClientProxy::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CClientProxy::disconnected"); +} + +CEvent::Type +CClientProxy::getClipboardChangedEvent() +{ + return CEvent::registerTypeOnce(s_clipboardChangedEvent, + "CClientProxy::clipboardChanged"); +} + +void* +CClientProxy::getEventTarget() const +{ + return static_cast(const_cast(this)); +} diff --git a/lib/server/CClientProxy.h b/lib/server/CClientProxy.h new file mode 100644 index 00000000..51ad014b --- /dev/null +++ b/lib/server/CClientProxy.h @@ -0,0 +1,113 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY_H +#define CCLIENTPROXY_H + +#include "CBaseClientProxy.h" +#include "CEvent.h" +#include "CString.h" + +class IStream; + +//! Generic proxy for client +class CClientProxy : public CBaseClientProxy { +public: + /*! + \c name is the name of the client. + */ + CClientProxy(const CString& name, IStream* adoptedStream); + ~CClientProxy(); + + //! @name manipulators + //@{ + + //! Disconnect + /*! + Ask the client to disconnect, using \p msg as the reason. + */ + void close(const char* msg); + + //@} + //! @name accessors + //@{ + + //! Get stream + /*! + Returns the stream passed to the c'tor. + */ + IStream* getStream() const; + + //! Get ready event type + /*! + Returns the ready event type. This is sent when the client has + completed the initial handshake. Until it is sent, the client is + not fully connected. + */ + static CEvent::Type getReadyEvent(); + + //! Get disconnect event type + /*! + Returns the disconnect event type. This is sent when the client + disconnects or is disconnected. The target is getEventTarget(). + */ + static CEvent::Type getDisconnectedEvent(); + + //! Get clipboard changed event type + /*! + Returns the clipboard changed event type. This is sent whenever the + contents of the clipboard has changed. The data is a pointer to a + IScreen::CClipboardInfo. + */ + static CEvent::Type getClipboardChangedEvent(); + + //@} + + // IScreen + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + virtual bool leave() = 0; + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void grabClipboard(ClipboardID) = 0; + virtual void setClipboardDirty(ClipboardID, bool) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void mouseDown(ButtonID) = 0; + virtual void mouseUp(ButtonID) = 0; + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const COptionsList& options) = 0; + +private: + IStream* m_stream; + + static CEvent::Type s_readyEvent; + static CEvent::Type s_disconnectedEvent; + static CEvent::Type s_clipboardChangedEvent; +}; + +#endif diff --git a/lib/server/CClientProxy1_0.cpp b/lib/server/CClientProxy1_0.cpp new file mode 100644 index 00000000..b40372e5 --- /dev/null +++ b/lib/server/CClientProxy1_0.cpp @@ -0,0 +1,498 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientProxy1_0.h" +#include "CProtocolUtil.h" +#include "XSynergy.h" +#include "IStream.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" +#include + +// +// CClientProxy1_0 +// + +CClientProxy1_0::CClientProxy1_0(const CString& name, IStream* stream) : + CClientProxy(name, stream), + m_heartbeatTimer(NULL), + m_parser(&CClientProxy1_0::parseHandshakeMessage) +{ + // install event handlers + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleData, NULL)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleWriteError, NULL)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleDisconnect, NULL)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxy1_0::handleWriteError, NULL)); + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CClientProxy1_0::handleFlatline, NULL)); + + setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath); + + LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgQInfo); +} + +CClientProxy1_0::~CClientProxy1_0() +{ + removeHandlers(); +} + +void +CClientProxy1_0::disconnect() +{ + removeHandlers(); + getStream()->close(); + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), getEventTarget())); +} + +void +CClientProxy1_0::removeHandlers() +{ + // uninstall event handlers + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + getStream()->getEventTarget()); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + + // remove timer + removeHeartbeatTimer(); +} + +void +CClientProxy1_0::addHeartbeatTimer() +{ + if (m_heartbeatAlarm > 0.0) { + m_heartbeatTimer = EVENTQUEUE->newOneShotTimer(m_heartbeatAlarm, this); + } +} + +void +CClientProxy1_0::removeHeartbeatTimer() +{ + if (m_heartbeatTimer != NULL) { + EVENTQUEUE->deleteTimer(m_heartbeatTimer); + m_heartbeatTimer = NULL; + } +} + +void +CClientProxy1_0::resetHeartbeatTimer() +{ + // reset the alarm + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +void +CClientProxy1_0::resetHeartbeatRate() +{ + setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath); +} + +void +CClientProxy1_0::setHeartbeatRate(double, double alarm) +{ + m_heartbeatAlarm = alarm; +} + +void +CClientProxy1_0::handleData(const CEvent&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = getStream()->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n)); + disconnect(); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + if (!(this->*m_parser)(code)) { + LOG((CLOG_ERR "invalid message from client \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + disconnect(); + return; + } + + // next message + n = getStream()->read(code, 4); + } + + // restart heartbeat timer + resetHeartbeatTimer(); +} + +bool +CClientProxy1_0::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgDInfo, 4) == 0) { + // future messages get parsed by parseMessage + m_parser = &CClientProxy1_0::parseMessage; + if (recvInfo()) { + EVENTQUEUE->addEvent(CEvent(getReadyEvent(), getEventTarget())); + addHeartbeatTimer(); + return true; + } + } + return false; +} + +bool +CClientProxy1_0::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDInfo, 4) == 0) { + if (recvInfo()) { + EVENTQUEUE->addEvent( + CEvent(getShapeChangedEvent(), getEventTarget())); + return true; + } + return false; + } + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + return recvGrabClipboard(); + } + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + return recvClipboard(); + } + return false; +} + +void +CClientProxy1_0::handleDisconnect(const CEvent&, void*) +{ + LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str())); + disconnect(); +} + +void +CClientProxy1_0::handleWriteError(const CEvent&, void*) +{ + LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str())); + disconnect(); +} + +void +CClientProxy1_0::handleFlatline(const CEvent&, void*) +{ + // didn't get a heartbeat fast enough. assume client is dead. + LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str())); + disconnect(); +} + +bool +CClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + CClipboard::copy(clipboard, &m_clipboard[id].m_clipboard); + return true; +} + +void +CClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_info.m_x; + y = m_info.m_y; + w = m_info.m_w; + h = m_info.m_h; +} + +void +CClientProxy1_0::getCursorPos(SInt32& x, SInt32& y) const +{ + // note -- this returns the cursor pos from when we last got client info + x = m_info.m_mx; + y = m_info.m_my; +} + +void +CClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool) +{ + LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask)); + CProtocolUtil::writef(getStream(), kMsgCEnter, + xAbs, yAbs, seqNum, mask); +} + +bool +CClientProxy1_0::leave() +{ + LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCLeave); + + // we can never prevent the user from leaving + return true; +} + +void +CClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore if this clipboard is already clean + if (m_clipboard[id].m_dirty) { + // this clipboard is now clean + m_clipboard[id].m_dirty = false; + CClipboard::copy(&m_clipboard[id].m_clipboard, clipboard); + + CString data = m_clipboard[id].m_clipboard.marshall(); + LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size())); + CProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data); + } +} + +void +CClientProxy1_0::grabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0); + + // this clipboard is now dirty + m_clipboard[id].m_dirty = true; +} + +void +CClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) +{ + m_clipboard[id].m_dirty = dirty; +} + +void +CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); + CProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask); +} + +void +CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count)); + CProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count); +} + +void +CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); + CProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask); +} + +void +CClientProxy1_0::mouseDown(ButtonID button) +{ + LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button)); + CProtocolUtil::writef(getStream(), kMsgDMouseDown, button); +} + +void +CClientProxy1_0::mouseUp(ButtonID button) +{ + LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button)); + CProtocolUtil::writef(getStream(), kMsgDMouseUp, button); +} + +void +CClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs) +{ + LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs)); + CProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs); +} + +void +CClientProxy1_0::mouseRelativeMove(SInt32, SInt32) +{ + // ignore -- not supported in protocol 1.0 +} + +void +CClientProxy1_0::mouseWheel(SInt32, SInt32 yDelta) +{ + // clients prior to 1.3 only support the y axis + LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), yDelta)); + CProtocolUtil::writef(getStream(), kMsgDMouseWheel1_0, yDelta); +} + +void +CClientProxy1_0::screensaver(bool on) +{ + LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0)); + CProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0); +} + +void +CClientProxy1_0::resetOptions() +{ + LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCResetOptions); + + // reset heart rate and death + resetHeartbeatRate(); + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +void +CClientProxy1_0::setOptions(const COptionsList& options) +{ + LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size())); + CProtocolUtil::writef(getStream(), kMsgDSetOptions, &options); + + // check options + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionHeartbeat) { + double rate = 1.0e-3 * static_cast(options[i + 1]); + if (rate <= 0.0) { + rate = -1.0; + } + setHeartbeatRate(rate, rate * kHeartBeatsUntilDeath); + removeHeartbeatTimer(); + addHeartbeatTimer(); + } + } +} + +bool +CClientProxy1_0::recvInfo() +{ + // parse the message + SInt16 x, y, w, h, dummy1, mx, my; + if (!CProtocolUtil::readf(getStream(), kMsgDInfo + 4, + &x, &y, &w, &h, &dummy1, &mx, &my)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d at %d,%d", getName().c_str(), x, y, w, h, mx, my)); + + // validate + if (w <= 0 || h <= 0) { + return false; + } + if (mx < x || mx >= x + w || my < y || my >= y + h) { + mx = x + w / 2; + my = y + h / 2; + } + + // save + m_info.m_x = x; + m_info.m_y = y; + m_info.m_w = w; + m_info.m_h = h; + m_info.m_mx = mx; + m_info.m_my = my; + + // acknowledge receipt + LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str())); + CProtocolUtil::writef(getStream(), kMsgCInfoAck); + return true; +} + +bool +CClientProxy1_0::recvClipboard() +{ + // parse message + ClipboardID id; + UInt32 seqNum; + CString data; + if (!CProtocolUtil::readf(getStream(), + kMsgDClipboard + 4, &id, &seqNum, &data)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size())); + + // validate + if (id >= kClipboardEnd) { + return false; + } + + // save clipboard + m_clipboard[id].m_clipboard.unmarshall(data, 0); + m_clipboard[id].m_sequenceNumber = seqNum; + + // notify + CClipboardInfo* info = new CClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = seqNum; + EVENTQUEUE->addEvent(CEvent(getClipboardChangedEvent(), + getEventTarget(), info)); + + return true; +} + +bool +CClientProxy1_0::recvGrabClipboard() +{ + // parse message + ClipboardID id; + UInt32 seqNum; + if (!CProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum)); + + // validate + if (id >= kClipboardEnd) { + return false; + } + + // notify + CClipboardInfo* info = new CClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = seqNum; + EVENTQUEUE->addEvent(CEvent(getClipboardGrabbedEvent(), + getEventTarget(), info)); + + return true; +} + + +// +// CClientProxy1_0::CClientClipboard +// + +CClientProxy1_0::CClientClipboard::CClientClipboard() : + m_clipboard(), + m_sequenceNumber(0), + m_dirty(true) +{ + // do nothing +} diff --git a/lib/server/CClientProxy1_0.h b/lib/server/CClientProxy1_0.h new file mode 100644 index 00000000..9ebfc18d --- /dev/null +++ b/lib/server/CClientProxy1_0.h @@ -0,0 +1,100 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_0_H +#define CCLIENTPROXY1_0_H + +#include "CClientProxy.h" +#include "CClipboard.h" +#include "ProtocolTypes.h" + +class CEvent; +class CEventQueueTimer; + +//! Proxy for client implementing protocol version 1.0 +class CClientProxy1_0 : public CClientProxy { +public: + CClientProxy1_0(const CString& name, IStream* adoptedStream); + ~CClientProxy1_0(); + + // IScreen + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + +protected: + virtual bool parseHandshakeMessage(const UInt8* code); + virtual bool parseMessage(const UInt8* code); + + virtual void resetHeartbeatRate(); + virtual void setHeartbeatRate(double rate, double alarm); + virtual void resetHeartbeatTimer(); + virtual void addHeartbeatTimer(); + virtual void removeHeartbeatTimer(); + +private: + void disconnect(); + void removeHandlers(); + + void handleData(const CEvent&, void*); + void handleDisconnect(const CEvent&, void*); + void handleWriteError(const CEvent&, void*); + void handleFlatline(const CEvent&, void*); + + bool recvInfo(); + bool recvClipboard(); + bool recvGrabClipboard(); + +private: + typedef bool (CClientProxy1_0::*MessageParser)(const UInt8*); + struct CClientClipboard { + public: + CClientClipboard(); + + public: + CClipboard m_clipboard; + UInt32 m_sequenceNumber; + bool m_dirty; + }; + + CClientInfo m_info; + CClientClipboard m_clipboard[kClipboardEnd]; + double m_heartbeatAlarm; + CEventQueueTimer* m_heartbeatTimer; + MessageParser m_parser; +}; + +#endif diff --git a/lib/server/CClientProxy1_1.cpp b/lib/server/CClientProxy1_1.cpp new file mode 100644 index 00000000..ccce9305 --- /dev/null +++ b/lib/server/CClientProxy1_1.cpp @@ -0,0 +1,55 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientProxy1_1.h" +#include "CProtocolUtil.h" +#include "CLog.h" +#include + +// +// CClientProxy1_1 +// + +CClientProxy1_1::CClientProxy1_1(const CString& name, IStream* stream) : + CClientProxy1_0(name, stream) +{ + // do nothing +} + +CClientProxy1_1::~CClientProxy1_1() +{ + // do nothing +} + +void +CClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + CProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button); +} + +void +CClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button)); + CProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button); +} + +void +CClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + CProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button); +} diff --git a/lib/server/CClientProxy1_1.h b/lib/server/CClientProxy1_1.h new file mode 100644 index 00000000..08764829 --- /dev/null +++ b/lib/server/CClientProxy1_1.h @@ -0,0 +1,33 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_1_H +#define CCLIENTPROXY1_1_H + +#include "CClientProxy1_0.h" + +//! Proxy for client implementing protocol version 1.1 +class CClientProxy1_1 : public CClientProxy1_0 { +public: + CClientProxy1_1(const CString& name, IStream* adoptedStream); + ~CClientProxy1_1(); + + // IClient overrides + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); +}; + +#endif diff --git a/lib/server/CClientProxy1_2.cpp b/lib/server/CClientProxy1_2.cpp new file mode 100644 index 00000000..29f0a56b --- /dev/null +++ b/lib/server/CClientProxy1_2.cpp @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientProxy1_2.h" +#include "CProtocolUtil.h" +#include "CLog.h" + +// +// CClientProxy1_1 +// + +CClientProxy1_2::CClientProxy1_2(const CString& name, IStream* stream) : + CClientProxy1_1(name, stream) +{ + // do nothing +} + +CClientProxy1_2::~CClientProxy1_2() +{ + // do nothing +} + +void +CClientProxy1_2::mouseRelativeMove(SInt32 xRel, SInt32 yRel) +{ + LOG((CLOG_DEBUG2 "send mouse relative move to \"%s\" %d,%d", getName().c_str(), xRel, yRel)); + CProtocolUtil::writef(getStream(), kMsgDMouseRelMove, xRel, yRel); +} diff --git a/lib/server/CClientProxy1_2.h b/lib/server/CClientProxy1_2.h new file mode 100644 index 00000000..3f8bb0e3 --- /dev/null +++ b/lib/server/CClientProxy1_2.h @@ -0,0 +1,30 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_2_H +#define CCLIENTPROXY1_2_H + +#include "CClientProxy1_1.h" + +//! Proxy for client implementing protocol version 1.2 +class CClientProxy1_2 : public CClientProxy1_1 { +public: + CClientProxy1_2(const CString& name, IStream* adoptedStream); + ~CClientProxy1_2(); + + // IClient overrides + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); +}; + +#endif diff --git a/lib/server/CClientProxy1_3.cpp b/lib/server/CClientProxy1_3.cpp new file mode 100644 index 00000000..f68b550e --- /dev/null +++ b/lib/server/CClientProxy1_3.cpp @@ -0,0 +1,114 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientProxy1_3.h" +#include "CProtocolUtil.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientProxy1_3 +// + +CClientProxy1_3::CClientProxy1_3(const CString& name, IStream* stream) : + CClientProxy1_2(name, stream), + m_keepAliveRate(kKeepAliveRate), + m_keepAliveTimer(NULL) +{ + setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath); +} + +CClientProxy1_3::~CClientProxy1_3() +{ + // cannot do this in superclass or our override wouldn't get called + removeHeartbeatTimer(); +} + +void +CClientProxy1_3::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d,%+d", getName().c_str(), xDelta, yDelta)); + CProtocolUtil::writef(getStream(), kMsgDMouseWheel, xDelta, yDelta); +} + +bool +CClientProxy1_3::parseMessage(const UInt8* code) +{ + // process message + if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // reset alarm + resetHeartbeatTimer(); + return true; + } + else { + return CClientProxy1_2::parseMessage(code); + } +} + +void +CClientProxy1_3::resetHeartbeatRate() +{ + setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath); +} + +void +CClientProxy1_3::setHeartbeatRate(double rate, double) +{ + m_keepAliveRate = rate; + CClientProxy1_2::setHeartbeatRate(rate, rate * kKeepAlivesUntilDeath); +} + +void +CClientProxy1_3::resetHeartbeatTimer() +{ + // reset the alarm but not the keep alive timer + CClientProxy1_2::removeHeartbeatTimer(); + CClientProxy1_2::addHeartbeatTimer(); +} + +void +CClientProxy1_3::addHeartbeatTimer() +{ + // create and install a timer to periodically send keep alives + if (m_keepAliveRate > 0.0) { + m_keepAliveTimer = EVENTQUEUE->newTimer(m_keepAliveRate, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_keepAliveTimer, + new TMethodEventJob(this, + &CClientProxy1_3::handleKeepAlive, NULL)); + } + + // superclass does the alarm + CClientProxy1_2::addHeartbeatTimer(); +} + +void +CClientProxy1_3::removeHeartbeatTimer() +{ + // remove the timer that sends keep alives periodically + if (m_keepAliveTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_keepAliveTimer); + EVENTQUEUE->deleteTimer(m_keepAliveTimer); + m_keepAliveTimer = NULL; + } + + // superclass does the alarm + CClientProxy1_2::removeHeartbeatTimer(); +} + +void +CClientProxy1_3::handleKeepAlive(const CEvent&, void*) +{ + CProtocolUtil::writef(getStream(), kMsgCKeepAlive); +} diff --git a/lib/server/CClientProxy1_3.h b/lib/server/CClientProxy1_3.h new file mode 100644 index 00000000..681094b6 --- /dev/null +++ b/lib/server/CClientProxy1_3.h @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2006 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXY1_3_H +#define CCLIENTPROXY1_3_H + +#include "CClientProxy1_2.h" + +//! Proxy for client implementing protocol version 1.3 +class CClientProxy1_3 : public CClientProxy1_2 { +public: + CClientProxy1_3(const CString& name, IStream* adoptedStream); + ~CClientProxy1_3(); + + // IClient overrides + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + +protected: + // CClientProxy overrides + virtual bool parseMessage(const UInt8* code); + virtual void resetHeartbeatRate(); + virtual void setHeartbeatRate(double rate, double alarm); + virtual void resetHeartbeatTimer(); + virtual void addHeartbeatTimer(); + virtual void removeHeartbeatTimer(); + +private: + void handleKeepAlive(const CEvent&, void*); + + +private: + double m_keepAliveRate; + CEventQueueTimer* m_keepAliveTimer; +}; + +#endif diff --git a/lib/server/CClientProxyUnknown.cpp b/lib/server/CClientProxyUnknown.cpp new file mode 100644 index 00000000..51a25efa --- /dev/null +++ b/lib/server/CClientProxyUnknown.cpp @@ -0,0 +1,287 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClientProxyUnknown.h" +#include "CClientProxy1_0.h" +#include "CClientProxy1_1.h" +#include "CClientProxy1_2.h" +#include "CClientProxy1_3.h" +#include "ProtocolTypes.h" +#include "CProtocolUtil.h" +#include "XSynergy.h" +#include "IStream.h" +#include "XIO.h" +#include "CLog.h" +#include "CString.h" +#include "IEventQueue.h" +#include "TMethodEventJob.h" + +// +// CClientProxyUnknown +// + +CEvent::Type CClientProxyUnknown::s_successEvent = CEvent::kUnknown; +CEvent::Type CClientProxyUnknown::s_failureEvent = CEvent::kUnknown; + +CClientProxyUnknown::CClientProxyUnknown(IStream* stream, double timeout) : + m_stream(stream), + m_proxy(NULL), + m_ready(false) +{ + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CClientProxyUnknown::handleTimeout, NULL)); + m_timer = EVENTQUEUE->newOneShotTimer(timeout, this); + addStreamHandlers(); + + LOG((CLOG_DEBUG1 "saying hello")); + CProtocolUtil::writef(m_stream, kMsgHello, + kProtocolMajorVersion, + kProtocolMinorVersion); +} + +CClientProxyUnknown::~CClientProxyUnknown() +{ + removeHandlers(); + removeTimer(); + delete m_stream; + delete m_proxy; +} + +CClientProxy* +CClientProxyUnknown::orphanClientProxy() +{ + if (m_ready) { + removeHandlers(); + CClientProxy* proxy = m_proxy; + m_proxy = NULL; + return proxy; + } + else { + return NULL; + } +} + +CEvent::Type +CClientProxyUnknown::getSuccessEvent() +{ + return CEvent::registerTypeOnce(s_successEvent, + "CClientProxy::success"); +} + +CEvent::Type +CClientProxyUnknown::getFailureEvent() +{ + return CEvent::registerTypeOnce(s_failureEvent, + "CClientProxy::failure"); +} + +void +CClientProxyUnknown::sendSuccess() +{ + m_ready = true; + removeTimer(); + EVENTQUEUE->addEvent(CEvent(getSuccessEvent(), this)); +} + +void +CClientProxyUnknown::sendFailure() +{ + delete m_proxy; + m_proxy = NULL; + m_ready = false; + removeHandlers(); + removeTimer(); + EVENTQUEUE->addEvent(CEvent(getFailureEvent(), this)); +} + +void +CClientProxyUnknown::addStreamHandlers() +{ + assert(m_stream != NULL); + + EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleData)); + EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleWriteError)); + EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleDisconnect)); + EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget(), + new TMethodEventJob(this, + &CClientProxyUnknown::handleWriteError)); +} + +void +CClientProxyUnknown::addProxyHandlers() +{ + assert(m_proxy != NULL); + + EVENTQUEUE->adoptHandler(CClientProxy::getReadyEvent(), + m_proxy, + new TMethodEventJob(this, + &CClientProxyUnknown::handleReady)); + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), + m_proxy, + new TMethodEventJob(this, + &CClientProxyUnknown::handleDisconnect)); +} + +void +CClientProxyUnknown::removeHandlers() +{ + if (m_stream != NULL) { + EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(), + m_stream->getEventTarget()); + EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(), + m_stream->getEventTarget()); + } + if (m_proxy != NULL) { + EVENTQUEUE->removeHandler(CClientProxy::getReadyEvent(), + m_proxy); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), + m_proxy); + } +} + +void +CClientProxyUnknown::removeTimer() +{ + if (m_timer != NULL) { + EVENTQUEUE->deleteTimer(m_timer); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + m_timer = NULL; + } +} + +void +CClientProxyUnknown::handleData(const CEvent&, void*) +{ + LOG((CLOG_DEBUG1 "parsing hello reply")); + + CString name(""); + try { + // limit the maximum length of the hello + UInt32 n = m_stream->getSize(); + if (n > kMaxHelloLength) { + LOG((CLOG_DEBUG1 "hello reply too long")); + throw XBadClient(); + } + + // parse the reply to hello + SInt16 major, minor; + if (!CProtocolUtil::readf(m_stream, kMsgHelloBack, + &major, &minor, &name)) { + throw XBadClient(); + } + + // disallow invalid version numbers + if (major <= 0 || minor < 0) { + throw XIncompatibleClient(major, minor); + } + + // remove stream event handlers. the proxy we're about to create + // may install its own handlers and we don't want to accidentally + // remove those later. + removeHandlers(); + + // create client proxy for highest version supported by the client + if (major == 1) { + switch (minor) { + case 0: + m_proxy = new CClientProxy1_0(name, m_stream); + break; + + case 1: + m_proxy = new CClientProxy1_1(name, m_stream); + break; + + case 2: + m_proxy = new CClientProxy1_2(name, m_stream); + break; + + case 3: + m_proxy = new CClientProxy1_3(name, m_stream); + break; + } + } + + // hangup (with error) if version isn't supported + if (m_proxy == NULL) { + throw XIncompatibleClient(major, minor); + } + + // the proxy is created and now proxy now owns the stream + LOG((CLOG_DEBUG1 "created proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); + m_stream = NULL; + + // wait until the proxy signals that it's ready or has disconnected + addProxyHandlers(); + return; + } + catch (XIncompatibleClient& e) { + // client is incompatible + LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); + CProtocolUtil::writef(m_stream, + kMsgEIncompatible, + kProtocolMajorVersion, kProtocolMinorVersion); + } + catch (XBadClient&) { + // client not behaving + LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); + CProtocolUtil::writef(m_stream, kMsgEBad); + } + catch (XBase& e) { + // misc error + LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what())); + } + sendFailure(); +} + +void +CClientProxyUnknown::handleWriteError(const CEvent&, void*) +{ + LOG((CLOG_NOTE "error communicating with new client")); + sendFailure(); +} + +void +CClientProxyUnknown::handleTimeout(const CEvent&, void*) +{ + LOG((CLOG_NOTE "new client is unresponsive")); + sendFailure(); +} + +void +CClientProxyUnknown::handleDisconnect(const CEvent&, void*) +{ + LOG((CLOG_NOTE "new client disconnected")); + sendFailure(); +} + +void +CClientProxyUnknown::handleReady(const CEvent&, void*) +{ + sendSuccess(); +} diff --git a/lib/server/CClientProxyUnknown.h b/lib/server/CClientProxyUnknown.h new file mode 100644 index 00000000..140a1aee --- /dev/null +++ b/lib/server/CClientProxyUnknown.h @@ -0,0 +1,83 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIENTPROXYUNKNOWN_H +#define CCLIENTPROXYUNKNOWN_H + +#include "CEvent.h" + +class CClientProxy; +class CEventQueueTimer; +class IStream; + +class CClientProxyUnknown { +public: + CClientProxyUnknown(IStream* stream, double timeout); + ~CClientProxyUnknown(); + + //! @name manipulators + //@{ + + //! Get the client proxy + /*! + Returns the client proxy created after a successful handshake + (i.e. when this object sends a success event). Returns NULL + if the handshake is unsuccessful or incomplete. + */ + CClientProxy* orphanClientProxy(); + + //@} + //! @name accessors + //@{ + + //! Get success event type + /*! + Returns the success event type. This is sent when the client has + correctly responded to the hello message. The target is this. + */ + static CEvent::Type getSuccessEvent(); + + //! Get failure event type + /*! + Returns the failure event type. This is sent when a client fails + to correctly respond to the hello message. The target is this. + */ + static CEvent::Type getFailureEvent(); + + //@} + +private: + void sendSuccess(); + void sendFailure(); + void addStreamHandlers(); + void addProxyHandlers(); + void removeHandlers(); + void removeTimer(); + void handleData(const CEvent&, void*); + void handleWriteError(const CEvent&, void*); + void handleTimeout(const CEvent&, void*); + void handleDisconnect(const CEvent&, void*); + void handleReady(const CEvent&, void*); + +private: + IStream* m_stream; + CEventQueueTimer* m_timer; + CClientProxy* m_proxy; + bool m_ready; + + static CEvent::Type s_successEvent; + static CEvent::Type s_failureEvent; +}; + +#endif diff --git a/lib/server/CConfig.cpp b/lib/server/CConfig.cpp new file mode 100644 index 00000000..92054f42 --- /dev/null +++ b/lib/server/CConfig.cpp @@ -0,0 +1,2277 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CConfig.h" +#include "CServer.h" +#include "CKeyMap.h" +#include "KeyTypes.h" +#include "XSocket.h" +#include "stdistream.h" +#include "stdostream.h" +#include + +// +// CConfig +// + +CConfig::CConfig() : m_hasLockToScreenAction(false) +{ + // do nothing +} + +CConfig::~CConfig() +{ + // do nothing +} + +bool +CConfig::addScreen(const CString& name) +{ + // alias name must not exist + if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) { + return false; + } + + // add cell + m_map.insert(std::make_pair(name, CCell())); + + // add name + m_nameToCanonicalName.insert(std::make_pair(name, name)); + + return true; +} + +bool +CConfig::renameScreen(const CString& oldName, + const CString& newName) +{ + // get canonical name and find cell + CString oldCanonical = getCanonicalName(oldName); + CCellMap::iterator index = m_map.find(oldCanonical); + if (index == m_map.end()) { + return false; + } + + // accept if names are equal but replace with new name to maintain + // case. otherwise, the new name must not exist. + if (!CStringUtil::CaselessCmp::equal(oldName, newName) && + m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) { + return false; + } + + // update cell + CCell tmpCell = index->second; + m_map.erase(index); + m_map.insert(std::make_pair(newName, tmpCell)); + + // update name + m_nameToCanonicalName.erase(oldCanonical); + m_nameToCanonicalName.insert(std::make_pair(newName, newName)); + + // update connections + CName oldNameObj(this, oldName); + for (index = m_map.begin(); index != m_map.end(); ++index) { + index->second.rename(oldNameObj, newName); + } + + // update alias targets + if (CStringUtil::CaselessCmp::equal(oldName, oldCanonical)) { + for (CNameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ++index) { + if (CStringUtil::CaselessCmp::equal( + index->second, oldCanonical)) { + index->second = newName; + } + } + } + + return true; +} + +void +CConfig::removeScreen(const CString& name) +{ + // get canonical name and find cell + CString canonical = getCanonicalName(name); + CCellMap::iterator index = m_map.find(canonical); + if (index == m_map.end()) { + return; + } + + // remove from map + m_map.erase(index); + + // disconnect + CName nameObj(this, name); + for (index = m_map.begin(); index != m_map.end(); ++index) { + index->second.remove(nameObj); + } + + // remove aliases (and canonical name) + for (CNameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ) { + if (index->second == canonical) { + m_nameToCanonicalName.erase(index++); + } + else { + ++index; + } + } +} + +void +CConfig::removeAllScreens() +{ + m_map.clear(); + m_nameToCanonicalName.clear(); +} + +bool +CConfig::addAlias(const CString& canonical, const CString& alias) +{ + // alias name must not exist + if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) { + return false; + } + + // canonical name must be known + if (m_map.find(canonical) == m_map.end()) { + return false; + } + + // insert alias + m_nameToCanonicalName.insert(std::make_pair(alias, canonical)); + + return true; +} + +bool +CConfig::removeAlias(const CString& alias) +{ + // must not be a canonical name + if (m_map.find(alias) != m_map.end()) { + return false; + } + + // find alias + CNameMap::iterator index = m_nameToCanonicalName.find(alias); + if (index == m_nameToCanonicalName.end()) { + return false; + } + + // remove alias + m_nameToCanonicalName.erase(index); + + return true; +} + +bool +CConfig::removeAliases(const CString& canonical) +{ + // must be a canonical name + if (m_map.find(canonical) == m_map.end()) { + return false; + } + + // find and removing matching aliases + for (CNameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ) { + if (index->second == canonical && index->first != canonical) { + m_nameToCanonicalName.erase(index++); + } + else { + ++index; + } + } + + return true; +} + +void +CConfig::removeAllAliases() +{ + // remove all names + m_nameToCanonicalName.clear(); + + // put the canonical names back in + for (CCellMap::iterator index = m_map.begin(); + index != m_map.end(); ++index) { + m_nameToCanonicalName.insert( + std::make_pair(index->first, index->first)); + } +} + +bool +CConfig::connect(const CString& srcName, + EDirection srcSide, + float srcStart, float srcEnd, + const CString& dstName, + float dstStart, float dstEnd) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return false; + } + + // add link + CCellEdge srcEdge(srcSide, CInterval(srcStart, srcEnd)); + CCellEdge dstEdge(dstName, srcSide, CInterval(dstStart, dstEnd)); + return index->second.add(srcEdge, dstEdge); +} + +bool +CConfig::disconnect(const CString& srcName, EDirection srcSide) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + return false; + } + + // disconnect side + index->second.remove(srcSide); + + return true; +} + +bool +CConfig::disconnect(const CString& srcName, EDirection srcSide, float position) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + return false; + } + + // disconnect side + index->second.remove(srcSide, position); + + return true; +} + +void +CConfig::setSynergyAddress(const CNetworkAddress& addr) +{ + m_synergyAddress = addr; +} + +bool +CConfig::addOption(const CString& name, OptionID option, OptionValue value) +{ + // find options + CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // add option + options->insert(std::make_pair(option, value)); + return true; +} + +bool +CConfig::removeOption(const CString& name, OptionID option) +{ + // find options + CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // remove option + options->erase(option); + return true; +} + +bool +CConfig::removeOptions(const CString& name) +{ + // find options + CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // remove options + options->clear(); + return true; +} + +bool +CConfig::isValidScreenName(const CString& name) const +{ + // name is valid if matches validname + // name ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9] + // domain ::= . name + // validname ::= name domain* + // we also accept names ending in . because many OS X users have + // so misconfigured their systems. + + // empty name is invalid + if (name.empty()) { + return false; + } + + // check each dot separated part + CString::size_type b = 0; + for (;;) { + // accept trailing . + if (b == name.size()) { + break; + } + + // find end of part + CString::size_type e = name.find('.', b); + if (e == CString::npos) { + e = name.size(); + } + + // part may not be empty + if (e - b < 1) { + return false; + } + + // check first and last characters + if (!(isalnum(name[b]) || name[b] == '_') || + !(isalnum(name[e - 1]) || name[e - 1] == '_')) { + return false; + } + + // check interior characters + for (CString::size_type i = b; i < e; ++i) { + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') { + return false; + } + } + + // next part + if (e == name.size()) { + // no more parts + break; + } + b = e + 1; + } + + return true; +} + +CConfig::const_iterator +CConfig::begin() const +{ + return const_iterator(m_map.begin()); +} + +CConfig::const_iterator +CConfig::end() const +{ + return const_iterator(m_map.end()); +} + +CConfig::all_const_iterator +CConfig::beginAll() const +{ + return m_nameToCanonicalName.begin(); +} + +CConfig::all_const_iterator +CConfig::endAll() const +{ + return m_nameToCanonicalName.end(); +} + +bool +CConfig::isScreen(const CString& name) const +{ + return (m_nameToCanonicalName.count(name) > 0); +} + +bool +CConfig::isCanonicalName(const CString& name) const +{ + return (!name.empty() && + CStringUtil::CaselessCmp::equal(getCanonicalName(name), name)); +} + +CString +CConfig::getCanonicalName(const CString& name) const +{ + CNameMap::const_iterator index = m_nameToCanonicalName.find(name); + if (index == m_nameToCanonicalName.end()) { + return CString(); + } + else { + return index->second; + } +} + +CString +CConfig::getNeighbor(const CString& srcName, EDirection srcSide, + float position, float* positionOut) const +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return CString(); + } + + // find edge + const CCellEdge* srcEdge, *dstEdge; + if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) { + // no neighbor + return ""; + } + else { + // compute position on neighbor + if (positionOut != NULL) { + *positionOut = + dstEdge->inverseTransform(srcEdge->transform(position)); + } + + // return neighbor's name + return getCanonicalName(dstEdge->getName()); + } +} + +bool +CConfig::hasNeighbor(const CString& srcName, EDirection srcSide) const +{ + return hasNeighbor(srcName, srcSide, 0.0f, 1.0f); +} + +bool +CConfig::hasNeighbor(const CString& srcName, EDirection srcSide, + float start, float end) const +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return false; + } + + return index->second.overlaps(CCellEdge(srcSide, CInterval(start, end))); +} + +CConfig::link_const_iterator +CConfig::beginNeighbor(const CString& srcName) const +{ + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + assert(index != m_map.end()); + return index->second.begin(); +} + +CConfig::link_const_iterator +CConfig::endNeighbor(const CString& srcName) const +{ + CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + assert(index != m_map.end()); + return index->second.end(); +} + +const CNetworkAddress& +CConfig::getSynergyAddress() const +{ + return m_synergyAddress; +} + +const CConfig::CScreenOptions* +CConfig::getOptions(const CString& name) const +{ + // find options + const CScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CCellMap::const_iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + + // return options + return options; +} + +bool +CConfig::hasLockToScreenAction() const +{ + return m_hasLockToScreenAction; +} + +bool +CConfig::operator==(const CConfig& x) const +{ + if (m_synergyAddress != x.m_synergyAddress) { + return false; + } + if (m_map.size() != x.m_map.size()) { + return false; + } + if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) { + return false; + } + + // compare global options + if (m_globalOptions != x.m_globalOptions) { + return false; + } + + for (CCellMap::const_iterator index1 = m_map.begin(), + index2 = x.m_map.begin(); + index1 != m_map.end(); ++index1, ++index2) { + // compare names + if (!CStringUtil::CaselessCmp::equal(index1->first, index2->first)) { + return false; + } + + // compare cells + if (index1->second != index2->second) { + return false; + } + } + + for (CNameMap::const_iterator index1 = m_nameToCanonicalName.begin(), + index2 = x.m_nameToCanonicalName.begin(); + index1 != m_nameToCanonicalName.end(); + ++index1, ++index2) { + if (!CStringUtil::CaselessCmp::equal(index1->first, index2->first) || + !CStringUtil::CaselessCmp::equal(index1->second, index2->second)) { + return false; + } + } + + // compare input filters + if (m_inputFilter != x.m_inputFilter) { + return false; + } + + return true; +} + +bool +CConfig::operator!=(const CConfig& x) const +{ + return !operator==(x); +} + +void +CConfig::read(CConfigReadContext& context) +{ + CConfig tmp; + while (context) { + tmp.readSection(context); + } + *this = tmp; +} + +const char* +CConfig::dirName(EDirection dir) +{ + static const char* s_name[] = { "left", "right", "up", "down" }; + + assert(dir >= kFirstDirection && dir <= kLastDirection); + + return s_name[dir - kFirstDirection]; +} + +CInputFilter* +CConfig::getInputFilter() +{ + return &m_inputFilter; +} + +CString +CConfig::formatInterval(const CInterval& x) +{ + if (x.first == 0.0f && x.second == 1.0f) { + return ""; + } + return CStringUtil::print("(%d,%d)", (int)(x.first * 100.0f + 0.5f), + (int)(x.second * 100.0f + 0.5f)); +} + +void +CConfig::readSection(CConfigReadContext& s) +{ + static const char s_section[] = "section:"; + static const char s_options[] = "options"; + static const char s_screens[] = "screens"; + static const char s_links[] = "links"; + static const char s_aliases[] = "aliases"; + + CString line; + if (!s.readLine(line)) { + // no more sections + return; + } + + // should be a section header + if (line.find(s_section) != 0) { + throw XConfigRead(s, "found data outside section"); + } + + // get section name + CString::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1); + if (i == CString::npos) { + throw XConfigRead(s, "section name is missing"); + } + CString name = line.substr(i); + i = name.find_first_of(" \t"); + if (i != CString::npos) { + throw XConfigRead(s, "unexpected data after section name"); + } + + // read section + if (name == s_options) { + readSectionOptions(s); + } + else if (name == s_screens) { + readSectionScreens(s); + } + else if (name == s_links) { + readSectionLinks(s); + } + else if (name == s_aliases) { + readSectionAliases(s); + } + else { + throw XConfigRead(s, "unknown section name \"%{1}\"", name); + } +} + +void +CConfig::readSectionOptions(CConfigReadContext& s) +{ + CString line; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // parse argument: `nameAndArgs = [values][;[values]]' + // nameAndArgs := [(arg[,...])] + // values := valueAndArgs[,valueAndArgs]... + // valueAndArgs := [(arg[,...])] + CString::size_type i = 0; + CString name, value; + CConfigReadContext::ArgList nameArgs, valueArgs; + s.parseNameWithArgs("name", line, "=", i, name, nameArgs); + ++i; + s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); + + bool handled = true; + if (name == "address") { + try { + m_synergyAddress = CNetworkAddress(value, kDefaultPort); + m_synergyAddress.resolve(); + } + catch (XSocketAddress& e) { + throw XConfigRead(s, + CString("invalid address argument ") + e.what()); + } + } + else if (name == "heartbeat") { + addOption("", kOptionHeartbeat, s.parseInt(value)); + } + else if (name == "switchCorners") { + addOption("", kOptionScreenSwitchCorners, s.parseCorners(value)); + } + else if (name == "switchCornerSize") { + addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value)); + } + else if (name == "switchDelay") { + addOption("", kOptionScreenSwitchDelay, s.parseInt(value)); + } + else if (name == "switchDoubleTap") { + addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value)); + } + else if (name == "screenSaverSync") { + addOption("", kOptionScreenSaverSync, s.parseBoolean(value)); + } + else if (name == "relativeMouseMoves") { + addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value)); + } + else if (name == "win32KeepForeground") { + addOption("", kOptionWin32KeepForeground, s.parseBoolean(value)); + } + else { + handled = false; + } + + if (handled) { + // make sure handled options aren't followed by more values + if (i < line.size() && (line[i] == ',' || line[i] == ';')) { + throw XConfigRead(s, "to many arguments to %s", name.c_str()); + } + } + else { + // make filter rule + CInputFilter::CRule rule(parseCondition(s, name, nameArgs)); + + // save first action (if any) + if (!value.empty() || line[i] != ';') { + parseAction(s, value, valueArgs, rule, true); + } + + // get remaining activate actions + while (i < line.length() && line[i] != ';') { + ++i; + s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); + parseAction(s, value, valueArgs, rule, true); + } + + // get deactivate actions + if (i < line.length() && line[i] == ';') { + // allow trailing ';' + i = line.find_first_not_of(" \t", i + 1); + if (i == CString::npos) { + i = line.length(); + } + else { + --i; + } + + // get actions + while (i < line.length()) { + ++i; + s.parseNameWithArgs("value", line, ",\n", + i, value, valueArgs); + parseAction(s, value, valueArgs, rule, false); + } + } + + // add rule + m_inputFilter.addFilterRule(rule); + } + } + throw XConfigRead(s, "unexpected end of options section"); +} + +void +CConfig::readSectionScreens(CConfigReadContext& s) +{ + CString line; + CString screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify validity of screen name + if (!isValidScreenName(screen)) { + throw XConfigRead(s, "invalid screen name \"%{1}\"", screen); + } + + // add the screen to the configuration + if (!addScreen(screen)) { + throw XConfigRead(s, "duplicate screen name \"%{1}\"", screen); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // parse argument: `=' + CString::size_type i = line.find_first_of(" \t="); + if (i == 0) { + throw XConfigRead(s, "missing argument name"); + } + if (i == CString::npos) { + throw XConfigRead(s, "missing ="); + } + CString name = line.substr(0, i); + i = line.find_first_not_of(" \t", i); + if (i == CString::npos || line[i] != '=') { + throw XConfigRead(s, "missing ="); + } + i = line.find_first_not_of(" \t", i + 1); + CString value; + if (i != CString::npos) { + value = line.substr(i); + } + + // handle argument + if (name == "halfDuplexCapsLock") { + addOption(screen, kOptionHalfDuplexCapsLock, + s.parseBoolean(value)); + } + else if (name == "halfDuplexNumLock") { + addOption(screen, kOptionHalfDuplexNumLock, + s.parseBoolean(value)); + } + else if (name == "halfDuplexScrollLock") { + addOption(screen, kOptionHalfDuplexScrollLock, + s.parseBoolean(value)); + } + else if (name == "shift") { + addOption(screen, kOptionModifierMapForShift, + s.parseModifierKey(value)); + } + else if (name == "ctrl") { + addOption(screen, kOptionModifierMapForControl, + s.parseModifierKey(value)); + } + else if (name == "alt") { + addOption(screen, kOptionModifierMapForAlt, + s.parseModifierKey(value)); + } + else if (name == "meta") { + addOption(screen, kOptionModifierMapForMeta, + s.parseModifierKey(value)); + } + else if (name == "super") { + addOption(screen, kOptionModifierMapForSuper, + s.parseModifierKey(value)); + } + else if (name == "xtestIsXineramaUnaware") { + addOption(screen, kOptionXTestXineramaUnaware, + s.parseBoolean(value)); + } + else if (name == "switchCorners") { + addOption(screen, kOptionScreenSwitchCorners, + s.parseCorners(value)); + } + else if (name == "switchCornerSize") { + addOption(screen, kOptionScreenSwitchCornerSize, + s.parseInt(value)); + } + else { + // unknown argument + throw XConfigRead(s, "unknown argument \"%{1}\"", name); + } + } + } + throw XConfigRead(s, "unexpected end of screens section"); +} + +void +CConfig::readSectionLinks(CConfigReadContext& s) +{ + CString line; + CString screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we know about the screen + if (!isScreen(screen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", screen); + } + if (!isCanonicalName(screen)) { + throw XConfigRead(s, "cannot use screen name alias here"); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // parse argument: `[(,)]=[(,)]' + // the stuff in brackets is optional. interval values must be + // in the range [0,100] and start < end. if not given the + // interval is taken to be (0,100). + CString::size_type i = 0; + CString side, dstScreen, srcArgString, dstArgString; + CConfigReadContext::ArgList srcArgs, dstArgs; + s.parseNameWithArgs("link", line, "=", i, side, srcArgs); + ++i; + s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs); + CInterval srcInterval(s.parseInterval(srcArgs)); + CInterval dstInterval(s.parseInterval(dstArgs)); + + // handle argument + EDirection dir; + if (side == "left") { + dir = kLeft; + } + else if (side == "right") { + dir = kRight; + } + else if (side == "up") { + dir = kTop; + } + else if (side == "down") { + dir = kBottom; + } + else { + // unknown argument + throw XConfigRead(s, "unknown side \"%{1}\" in link", side); + } + if (!isScreen(dstScreen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", dstScreen); + } + if (!connect(screen, dir, + srcInterval.first, srcInterval.second, + dstScreen, + dstInterval.first, dstInterval.second)) { + throw XConfigRead(s, "overlapping range"); + } + } + } + throw XConfigRead(s, "unexpected end of links section"); +} + +void +CConfig::readSectionAliases(CConfigReadContext& s) +{ + CString line; + CString screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we know about the screen + if (!isScreen(screen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", screen); + } + if (!isCanonicalName(screen)) { + throw XConfigRead(s, "cannot use screen name alias here"); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // verify validity of screen name + if (!isValidScreenName(line)) { + throw XConfigRead(s, "invalid screen alias \"%{1}\"", line); + } + + // add alias + if (!addAlias(screen, line)) { + throw XConfigRead(s, "alias \"%{1}\" is already used", line); + } + } + } + throw XConfigRead(s, "unexpected end of aliases section"); +} + + +CInputFilter::CCondition* +CConfig::parseCondition(CConfigReadContext& s, + const CString& name, const std::vector& args) +{ + if (name == "keystroke") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)"); + } + + IPlatformScreen::CKeyInfo* keyInfo = s.parseKeystroke(args[0]); + + return new CInputFilter::CKeystrokeCondition(keyInfo); + } + + if (name == "mousebutton") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)"); + } + + IPlatformScreen::CButtonInfo* mouseInfo = s.parseMouse(args[0]); + + return new CInputFilter::CMouseButtonCondition(mouseInfo); + } + + if (name == "connect") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: connect([screen])"); + } + + CString screen = args[0]; + if (isScreen(screen)) { + screen = getCanonicalName(screen); + } + else if (!screen.empty()) { + throw XConfigRead(s, "unknown screen name \"%{1}\" in connect", screen); + } + + return new CInputFilter::CScreenConnectedCondition(screen); + } + + throw XConfigRead(s, "unknown argument \"%{1}\"", name); +} + +void +CConfig::parseAction(CConfigReadContext& s, + const CString& name, const std::vector& args, + CInputFilter::CRule& rule, bool activate) +{ + CInputFilter::CAction* action; + + if (name == "keystroke" || name == "keyDown" || name == "keyUp") { + if (args.size() < 1 || args.size() > 2) { + throw XConfigRead(s, "syntax for action: keystroke(modifiers+key[,screens])"); + } + + IPlatformScreen::CKeyInfo* keyInfo; + if (args.size() == 1) { + keyInfo = s.parseKeystroke(args[0]); + } + else { + std::set screens; + parseScreens(s, args[1], screens); + keyInfo = s.parseKeystroke(args[0], screens); + } + + if (name == "keystroke") { + IPlatformScreen::CKeyInfo* keyInfo2 = + IKeyState::CKeyInfo::alloc(*keyInfo); + action = new CInputFilter::CKeystrokeAction(keyInfo2, true); + rule.adoptAction(action, true); + action = new CInputFilter::CKeystrokeAction(keyInfo, false); + activate = false; + } + else if (name == "keyDown") { + action = new CInputFilter::CKeystrokeAction(keyInfo, true); + } + else { + action = new CInputFilter::CKeystrokeAction(keyInfo, false); + } + } + + else if (name == "mousebutton" || + name == "mouseDown" || name == "mouseUp") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)"); + } + + IPlatformScreen::CButtonInfo* mouseInfo = s.parseMouse(args[0]); + + if (name == "mousebutton") { + IPlatformScreen::CButtonInfo* mouseInfo2 = + IPlatformScreen::CButtonInfo::alloc(*mouseInfo); + action = new CInputFilter::CMouseButtonAction(mouseInfo2, true); + rule.adoptAction(action, true); + action = new CInputFilter::CMouseButtonAction(mouseInfo, false); + activate = false; + } + else if (name == "mouseDown") { + action = new CInputFilter::CMouseButtonAction(mouseInfo, true); + } + else { + action = new CInputFilter::CMouseButtonAction(mouseInfo, false); + } + } + +/* XXX -- not supported + else if (name == "modifier") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: modifier(modifiers)"); + } + + KeyModifierMask mask = s.parseModifier(args[0]); + + action = new CInputFilter::CModifierAction(mask, ~mask); + } +*/ + + else if (name == "switchToScreen") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: switchToScreen(name)"); + } + + CString screen = args[0]; + if (isScreen(screen)) { + screen = getCanonicalName(screen); + } + else if (!screen.empty()) { + throw XConfigRead(s, "unknown screen name in switchToScreen"); + } + + action = new CInputFilter::CSwitchToScreenAction(screen); + } + + else if (name == "switchInDirection") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: switchInDirection()"); + } + + EDirection direction; + if (args[0] == "left") { + direction = kLeft; + } + else if (args[0] == "right") { + direction = kRight; + } + else if (args[0] == "up") { + direction = kTop; + } + else if (args[0] == "down") { + direction = kBottom; + } + else { + throw XConfigRead(s, "unknown direction \"%{1}\" in switchToScreen", args[0]); + } + + action = new CInputFilter::CSwitchInDirectionAction(direction); + } + + else if (name == "lockCursorToScreen") { + if (args.size() > 1) { + throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])"); + } + + CInputFilter::CLockCursorToScreenAction::Mode mode = + CInputFilter::CLockCursorToScreenAction::kToggle; + if (args.size() == 1) { + if (args[0] == "off") { + mode = CInputFilter::CLockCursorToScreenAction::kOff; + } + else if (args[0] == "on") { + mode = CInputFilter::CLockCursorToScreenAction::kOn; + } + else if (args[0] == "toggle") { + mode = CInputFilter::CLockCursorToScreenAction::kToggle; + } + else { + throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])"); + } + } + + if (mode != CInputFilter::CLockCursorToScreenAction::kOff) { + m_hasLockToScreenAction = true; + } + + action = new CInputFilter::CLockCursorToScreenAction(mode); + } + + else if (name == "keyboardBroadcast") { + if (args.size() > 2) { + throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])"); + } + + CInputFilter::CKeyboardBroadcastAction::Mode mode = + CInputFilter::CKeyboardBroadcastAction::kToggle; + if (args.size() >= 1) { + if (args[0] == "off") { + mode = CInputFilter::CKeyboardBroadcastAction::kOff; + } + else if (args[0] == "on") { + mode = CInputFilter::CKeyboardBroadcastAction::kOn; + } + else if (args[0] == "toggle") { + mode = CInputFilter::CKeyboardBroadcastAction::kToggle; + } + else { + throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])"); + } + } + + std::set screens; + if (args.size() >= 2) { + parseScreens(s, args[1], screens); + } + + action = new CInputFilter::CKeyboardBroadcastAction(mode, screens); + } + + else { + throw XConfigRead(s, "unknown action argument \"%{1}\"", name); + } + + rule.adoptAction(action, activate); +} + +void +CConfig::parseScreens(CConfigReadContext& c, + const CString& s, std::set& screens) const +{ + screens.clear(); + + CString::size_type i = 0; + while (i < s.size()) { + // find end of next screen name + CString::size_type j = s.find(':', i); + if (j == CString::npos) { + j = s.size(); + } + + // extract name + CString rawName; + i = s.find_first_not_of(" \t", i); + if (i < j) { + rawName = s.substr(i, s.find_last_not_of(" \t", j - 1) - i + 1); + } + + // add name + if (rawName == "*") { + screens.insert("*"); + } + else if (!rawName.empty()) { + CString name = getCanonicalName(rawName); + if (name.empty()) { + throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName); + } + screens.insert(name); + } + + // next + i = j + 1; + } +} + +const char* +CConfig::getOptionName(OptionID id) +{ + if (id == kOptionHalfDuplexCapsLock) { + return "halfDuplexCapsLock"; + } + if (id == kOptionHalfDuplexNumLock) { + return "halfDuplexNumLock"; + } + if (id == kOptionHalfDuplexScrollLock) { + return "halfDuplexScrollLock"; + } + if (id == kOptionModifierMapForShift) { + return "shift"; + } + if (id == kOptionModifierMapForControl) { + return "ctrl"; + } + if (id == kOptionModifierMapForAlt) { + return "alt"; + } + if (id == kOptionModifierMapForMeta) { + return "meta"; + } + if (id == kOptionModifierMapForSuper) { + return "super"; + } + if (id == kOptionHeartbeat) { + return "heartbeat"; + } + if (id == kOptionScreenSwitchCorners) { + return "switchCorners"; + } + if (id == kOptionScreenSwitchCornerSize) { + return "switchCornerSize"; + } + if (id == kOptionScreenSwitchDelay) { + return "switchDelay"; + } + if (id == kOptionScreenSwitchTwoTap) { + return "switchDoubleTap"; + } + if (id == kOptionScreenSaverSync) { + return "screenSaverSync"; + } + if (id == kOptionXTestXineramaUnaware) { + return "xtestIsXineramaUnaware"; + } + if (id == kOptionRelativeMouseMoves) { + return "relativeMouseMoves"; + } + if (id == kOptionWin32KeepForeground) { + return "win32KeepForeground"; + } + return NULL; +} + +CString +CConfig::getOptionValue(OptionID id, OptionValue value) +{ + if (id == kOptionHalfDuplexCapsLock || + id == kOptionHalfDuplexNumLock || + id == kOptionHalfDuplexScrollLock || + id == kOptionScreenSaverSync || + id == kOptionXTestXineramaUnaware || + id == kOptionRelativeMouseMoves || + id == kOptionWin32KeepForeground) { + return (value != 0) ? "true" : "false"; + } + if (id == kOptionModifierMapForShift || + id == kOptionModifierMapForControl || + id == kOptionModifierMapForAlt || + id == kOptionModifierMapForMeta || + id == kOptionModifierMapForSuper) { + switch (value) { + case kKeyModifierIDShift: + return "shift"; + + case kKeyModifierIDControl: + return "ctrl"; + + case kKeyModifierIDAlt: + return "alt"; + + case kKeyModifierIDMeta: + return "meta"; + + case kKeyModifierIDSuper: + return "super"; + + default: + return "none"; + } + } + if (id == kOptionHeartbeat || + id == kOptionScreenSwitchCornerSize || + id == kOptionScreenSwitchDelay || + id == kOptionScreenSwitchTwoTap) { + return CStringUtil::print("%d", value); + } + if (id == kOptionScreenSwitchCorners) { + std::string result("none"); + if ((value & kTopLeftMask) != 0) { + result += " +top-left"; + } + if ((value & kTopRightMask) != 0) { + result += " +top-right"; + } + if ((value & kBottomLeftMask) != 0) { + result += " +bottom-left"; + } + if ((value & kBottomRightMask) != 0) { + result += " +bottom-right"; + } + return result; + } + + return ""; +} + + +// +// CConfig::CName +// + +CConfig::CName::CName(CConfig* config, const CString& name) : + m_config(config), + m_name(config->getCanonicalName(name)) +{ + // do nothing +} + +bool +CConfig::CName::operator==(const CString& name) const +{ + CString canonical = m_config->getCanonicalName(name); + return CStringUtil::CaselessCmp::equal(canonical, m_name); +} + + +// +// CConfig::CCellEdge +// + +CConfig::CCellEdge::CCellEdge(EDirection side, float position) +{ + init("", side, CInterval(position, position)); +} + +CConfig::CCellEdge::CCellEdge(EDirection side, const CInterval& interval) +{ + assert(interval.first >= 0.0f); + assert(interval.second <= 1.0f); + assert(interval.first < interval.second); + + init("", side, interval); +} + +CConfig::CCellEdge::CCellEdge(const CString& name, + EDirection side, const CInterval& interval) +{ + assert(interval.first >= 0.0f); + assert(interval.second <= 1.0f); + assert(interval.first < interval.second); + + init(name, side, interval); +} + +CConfig::CCellEdge::~CCellEdge() +{ + // do nothing +} + +void +CConfig::CCellEdge::init(const CString& name, EDirection side, + const CInterval& interval) +{ + assert(side != kNoDirection); + + m_name = name; + m_side = side; + m_interval = interval; +} + +CConfig::CInterval +CConfig::CCellEdge::getInterval() const +{ + return m_interval; +} + +void +CConfig::CCellEdge::setName(const CString& newName) +{ + m_name = newName; +} + +CString +CConfig::CCellEdge::getName() const +{ + return m_name; +} + +EDirection +CConfig::CCellEdge::getSide() const +{ + return m_side; +} + +bool +CConfig::CCellEdge::overlaps(const CCellEdge& edge) const +{ + const CInterval& x = m_interval; + const CInterval& y = edge.m_interval; + if (m_side != edge.m_side) { + return false; + } + return (x.first >= y.first && x.first < y.second) || + (x.second > y.first && x.second <= y.second) || + (y.first >= x.first && y.first < x.second) || + (y.second > x.first && y.second <= x.second); +} + +bool +CConfig::CCellEdge::isInside(float x) const +{ + return (x >= m_interval.first && x < m_interval.second); +} + +float +CConfig::CCellEdge::transform(float x) const +{ + return (x - m_interval.first) / (m_interval.second - m_interval.first); +} + + +float +CConfig::CCellEdge::inverseTransform(float x) const +{ + return x * (m_interval.second - m_interval.first) + m_interval.first; +} + +bool +CConfig::CCellEdge::operator<(const CCellEdge& o) const +{ + if (static_cast(m_side) < static_cast(o.m_side)) { + return true; + } + else if (static_cast(m_side) > static_cast(o.m_side)) { + return false; + } + + return (m_interval.first < o.m_interval.first); +} + +bool +CConfig::CCellEdge::operator==(const CCellEdge& x) const +{ + return (m_side == x.m_side && m_interval == x.m_interval); +} + +bool +CConfig::CCellEdge::operator!=(const CCellEdge& x) const +{ + return !operator==(x); +} + + +// +// CConfig::CCell +// + +bool +CConfig::CCell::add(const CCellEdge& src, const CCellEdge& dst) +{ + // cannot add an edge that overlaps other existing edges but we + // can exactly replace an edge. + if (!hasEdge(src) && overlaps(src)) { + return false; + } + + m_neighbors.erase(src); + m_neighbors.insert(std::make_pair(src, dst)); + return true; +} + +void +CConfig::CCell::remove(EDirection side) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ) { + if (j->first.getSide() == side) { + m_neighbors.erase(j++); + } + else { + ++j; + } + } +} + +void +CConfig::CCell::remove(EDirection side, float position) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ++j) { + if (j->first.getSide() == side && j->first.isInside(position)) { + m_neighbors.erase(j); + break; + } + } +} +void +CConfig::CCell::remove(const CName& name) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ) { + if (name == j->second.getName()) { + m_neighbors.erase(j++); + } + else { + ++j; + } + } +} + +void +CConfig::CCell::rename(const CName& oldName, const CString& newName) +{ + for (CEdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ++j) { + if (oldName == j->second.getName()) { + j->second.setName(newName); + } + } +} + +bool +CConfig::CCell::hasEdge(const CCellEdge& edge) const +{ + CEdgeLinks::const_iterator i = m_neighbors.find(edge); + return (i != m_neighbors.end() && i->first == edge); +} + +bool +CConfig::CCell::overlaps(const CCellEdge& edge) const +{ + CEdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); + if (i != m_neighbors.end() && i->first.overlaps(edge)) { + return true; + } + if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) { + return true; + } + return false; +} + +bool +CConfig::CCell::getLink(EDirection side, float position, + const CCellEdge*& src, const CCellEdge*& dst) const +{ + CCellEdge edge(side, position); + CEdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); + if (i == m_neighbors.begin()) { + return false; + } + --i; + if (i->first.getSide() == side && i->first.isInside(position)) { + src = &i->first; + dst = &i->second; + return true; + } + return false; +} + +bool +CConfig::CCell::operator==(const CCell& x) const +{ + // compare options + if (m_options != x.m_options) { + return false; + } + + // compare links + if (m_neighbors.size() != x.m_neighbors.size()) { + return false; + } + for (CEdgeLinks::const_iterator index1 = m_neighbors.begin(), + index2 = x.m_neighbors.begin(); + index1 != m_neighbors.end(); + ++index1, ++index2) { + if (index1->first != index2->first) { + return false; + } + if (index1->second != index2->second) { + return false; + } + + // operator== doesn't compare names. only compare destination + // names. + if (!CStringUtil::CaselessCmp::equal(index1->second.getName(), + index2->second.getName())) { + return false; + } + } + return true; +} + +bool +CConfig::CCell::operator!=(const CCell& x) const +{ + return !operator==(x); +} + +CConfig::CCell::const_iterator +CConfig::CCell::begin() const +{ + return m_neighbors.begin(); +} + +CConfig::CCell::const_iterator +CConfig::CCell::end() const +{ + return m_neighbors.end(); +} + + +// +// CConfig I/O +// + +std::istream& +operator>>(std::istream& s, CConfig& config) +{ + CConfigReadContext context(s); + config.read(context); + return s; +} + +std::ostream& +operator<<(std::ostream& s, const CConfig& config) +{ + // screens section + s << "section: screens" << std::endl; + for (CConfig::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << std::endl; + const CConfig::CScreenOptions* options = config.getOptions(*screen); + if (options != NULL && options->size() > 0) { + for (CConfig::CScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = CConfig::getOptionName(option->first); + CString value = CConfig::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t\t" << name << " = " << value << std::endl; + } + } + } + } + s << "end" << std::endl; + + // links section + CString neighbor; + s << "section: links" << std::endl; + for (CConfig::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << std::endl; + + for (CConfig::link_const_iterator + link = config.beginNeighbor(*screen), + nend = config.endNeighbor(*screen); link != nend; ++link) { + s << "\t\t" << CConfig::dirName(link->first.getSide()) << + CConfig::formatInterval(link->first.getInterval()) << + " = " << link->second.getName().c_str() << + CConfig::formatInterval(link->second.getInterval()) << + std::endl; + } + } + s << "end" << std::endl; + + // aliases section (if there are any) + if (config.m_map.size() != config.m_nameToCanonicalName.size()) { + // map canonical to alias + typedef std::multimap CMNameMap; + CMNameMap aliases; + for (CConfig::CNameMap::const_iterator + index = config.m_nameToCanonicalName.begin(); + index != config.m_nameToCanonicalName.end(); + ++index) { + if (index->first != index->second) { + aliases.insert(std::make_pair(index->second, index->first)); + } + } + + // dump it + CString screen; + s << "section: aliases" << std::endl; + for (CMNameMap::const_iterator index = aliases.begin(); + index != aliases.end(); ++index) { + if (index->first != screen) { + screen = index->first; + s << "\t" << screen.c_str() << ":" << std::endl; + } + s << "\t\t" << index->second.c_str() << std::endl; + } + s << "end" << std::endl; + } + + // options section + s << "section: options" << std::endl; + const CConfig::CScreenOptions* options = config.getOptions(""); + if (options != NULL && options->size() > 0) { + for (CConfig::CScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = CConfig::getOptionName(option->first); + CString value = CConfig::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t" << name << " = " << value << std::endl; + } + } + } + if (config.m_synergyAddress.isValid()) { + s << "\taddress = " << + config.m_synergyAddress.getHostname().c_str() << std::endl; + } + s << config.m_inputFilter.format("\t"); + s << "end" << std::endl; + + return s; +} + + +// +// CConfigReadContext +// + +CConfigReadContext::CConfigReadContext(std::istream& s, SInt32 firstLine) : + m_stream(s), + m_line(firstLine - 1) +{ + // do nothing +} + +CConfigReadContext::~CConfigReadContext() +{ + // do nothing +} + +bool +CConfigReadContext::readLine(CString& line) +{ + ++m_line; + while (std::getline(m_stream, line)) { + // strip leading whitespace + CString::size_type i = line.find_first_not_of(" \t"); + if (i != CString::npos) { + line.erase(0, i); + } + + // strip comments and then trailing whitespace + i = line.find('#'); + if (i != CString::npos) { + line.erase(i); + } + i = line.find_last_not_of(" \r\t"); + if (i != CString::npos) { + line.erase(i + 1); + } + + // return non empty line + if (!line.empty()) { + // make sure there are no invalid characters + for (i = 0; i < line.length(); ++i) { + if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') { + throw XConfigRead(*this, + "invalid character %{1}", + CStringUtil::print("%#2x", line[i])); + } + } + + return true; + } + + // next line + ++m_line; + } + return false; +} + +UInt32 +CConfigReadContext::getLineNumber() const +{ + return m_line; +} + +CConfigReadContext::operator void*() const +{ + return m_stream; +} + +bool +CConfigReadContext::operator!() const +{ + return !m_stream; +} + +OptionValue +CConfigReadContext::parseBoolean(const CString& arg) const +{ + if (CStringUtil::CaselessCmp::equal(arg, "true")) { + return static_cast(true); + } + if (CStringUtil::CaselessCmp::equal(arg, "false")) { + return static_cast(false); + } + throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg); +} + +OptionValue +CConfigReadContext::parseInt(const CString& arg) const +{ + const char* s = arg.c_str(); + char* end; + long tmp = strtol(s, &end, 10); + if (*end != '\0') { + // invalid characters + throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg); + } + OptionValue value = static_cast(tmp); + if (value != tmp) { + // out of range + throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg); + } + return value; +} + +OptionValue +CConfigReadContext::parseModifierKey(const CString& arg) const +{ + if (CStringUtil::CaselessCmp::equal(arg, "shift")) { + return static_cast(kKeyModifierIDShift); + } + if (CStringUtil::CaselessCmp::equal(arg, "ctrl")) { + return static_cast(kKeyModifierIDControl); + } + if (CStringUtil::CaselessCmp::equal(arg, "alt")) { + return static_cast(kKeyModifierIDAlt); + } + if (CStringUtil::CaselessCmp::equal(arg, "meta")) { + return static_cast(kKeyModifierIDMeta); + } + if (CStringUtil::CaselessCmp::equal(arg, "super")) { + return static_cast(kKeyModifierIDSuper); + } + if (CStringUtil::CaselessCmp::equal(arg, "none")) { + return static_cast(kKeyModifierIDNull); + } + throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); +} + +OptionValue +CConfigReadContext::parseCorner(const CString& arg) const +{ + if (CStringUtil::CaselessCmp::equal(arg, "left")) { + return kTopLeftMask | kBottomLeftMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "right")) { + return kTopRightMask | kBottomRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "top")) { + return kTopLeftMask | kTopRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "bottom")) { + return kBottomLeftMask | kBottomRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "top-left")) { + return kTopLeftMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "top-right")) { + return kTopRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "bottom-left")) { + return kBottomLeftMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "bottom-right")) { + return kBottomRightMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "none")) { + return kNoCornerMask; + } + else if (CStringUtil::CaselessCmp::equal(arg, "all")) { + return kAllCornersMask; + } + throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); +} + +OptionValue +CConfigReadContext::parseCorners(const CString& args) const +{ + // find first token + CString::size_type i = args.find_first_not_of(" \t", 0); + if (i == CString::npos) { + throw XConfigRead(*this, "missing corner argument"); + } + CString::size_type j = args.find_first_of(" \t", i); + + // parse first corner token + OptionValue corners = parseCorner(args.substr(i, j - i)); + + // get +/- + i = args.find_first_not_of(" \t", j); + while (i != CString::npos) { + // parse +/- + bool add; + if (args[i] == '-') { + add = false; + } + else if (args[i] == '+') { + add = true; + } + else { + throw XConfigRead(*this, + "invalid corner operator \"%{1}\"", + CString(args.c_str() + i, 1)); + } + + // get next corner token + i = args.find_first_not_of(" \t", i + 1); + j = args.find_first_of(" \t", i); + if (i == CString::npos) { + throw XConfigRead(*this, "missing corner argument"); + } + + // parse next corner token + if (add) { + corners |= parseCorner(args.substr(i, j - i)); + } + else { + corners &= ~parseCorner(args.substr(i, j - i)); + } + i = args.find_first_not_of(" \t", j); + } + + return corners; +} + +CConfig::CInterval +CConfigReadContext::parseInterval(const ArgList& args) const +{ + if (args.size() == 0) { + return CConfig::CInterval(0.0f, 1.0f); + } + if (args.size() != 2 || args[0].empty() || args[1].empty()) { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + char* end; + long startValue = strtol(args[0].c_str(), &end, 10); + if (end[0] != '\0') { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + long endValue = strtol(args[1].c_str(), &end, 10); + if (end[0] != '\0') { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + if (startValue < 0 || startValue > 100 || + endValue < 0 || endValue > 100 || + startValue >= endValue) { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + return CConfig::CInterval(startValue / 100.0f, endValue / 100.0f); +} + +void +CConfigReadContext::parseNameWithArgs( + const CString& type, const CString& line, + const CString& delim, CString::size_type& index, + CString& name, ArgList& args) const +{ + // skip leading whitespace + CString::size_type i = line.find_first_not_of(" \t", index); + if (i == CString::npos) { + throw XConfigRead(*this, CString("missing ") + type); + } + + // find end of name + CString::size_type j = line.find_first_of(" \t(" + delim, i); + if (j == CString::npos) { + j = line.length(); + } + + // save name + name = line.substr(i, j - i); + args.clear(); + + // is it okay to not find a delimiter? + bool needDelim = (!delim.empty() && delim.find('\n') == CString::npos); + + // skip whitespace + i = line.find_first_not_of(" \t", j); + if (i == CString::npos && needDelim) { + // expected delimiter but didn't find it + throw XConfigRead(*this, CString("missing ") + delim[0]); + } + if (i == CString::npos) { + // no arguments + index = line.length(); + return; + } + if (line[i] != '(') { + // no arguments + index = i; + return; + } + + // eat '(' + ++i; + + // parse arguments + j = line.find_first_of(",)", i); + while (j != CString::npos) { + // extract arg + CString arg(line.substr(i, j - i)); + i = j; + + // trim whitespace + j = arg.find_first_not_of(" \t"); + if (j != CString::npos) { + arg.erase(0, j); + } + j = arg.find_last_not_of(" \t"); + if (j != CString::npos) { + arg.erase(j + 1); + } + + // save arg + args.push_back(arg); + + // exit loop at end of arguments + if (line[i] == ')') { + break; + } + + // eat ',' + ++i; + + // next + j = line.find_first_of(",)", i); + } + + // verify ')' + if (j == CString::npos) { + // expected ) + throw XConfigRead(*this, "missing )"); + } + + // eat ')' + ++i; + + // skip whitespace + j = line.find_first_not_of(" \t", i); + if (j == CString::npos && needDelim) { + // expected delimiter but didn't find it + throw XConfigRead(*this, CString("missing ") + delim[0]); + } + + // verify delimiter + if (needDelim && delim.find(line[j]) == CString::npos) { + throw XConfigRead(*this, CString("expected ") + delim[0]); + } + + if (j == CString::npos) { + j = line.length(); + } + + index = j; + return; +} + +IPlatformScreen::CKeyInfo* +CConfigReadContext::parseKeystroke(const CString& keystroke) const +{ + return parseKeystroke(keystroke, std::set()); +} + +IPlatformScreen::CKeyInfo* +CConfigReadContext::parseKeystroke(const CString& keystroke, + const std::set& screens) const +{ + CString s = keystroke; + + KeyModifierMask mask; + if (!CKeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse key modifiers"); + } + + KeyID key; + if (!CKeyMap::parseKey(s, key)) { + throw XConfigRead(*this, "unable to parse key"); + } + + if (key == kKeyNone && mask == 0) { + throw XConfigRead(*this, "missing key and/or modifiers in keystroke"); + } + + return IPlatformScreen::CKeyInfo::alloc(key, mask, 0, 0, screens); +} + +IPlatformScreen::CButtonInfo* +CConfigReadContext::parseMouse(const CString& mouse) const +{ + CString s = mouse; + + KeyModifierMask mask; + if (!CKeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse button modifiers"); + } + + char* end; + ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10); + if (*end != '\0') { + throw XConfigRead(*this, "unable to parse button"); + } + if (s.empty() || button <= 0) { + throw XConfigRead(*this, "invalid button"); + } + + return IPlatformScreen::CButtonInfo::alloc(button, mask); +} + +KeyModifierMask +CConfigReadContext::parseModifier(const CString& modifiers) const +{ + CString s = modifiers; + + KeyModifierMask mask; + if (!CKeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse modifiers"); + } + + if (mask == 0) { + throw XConfigRead(*this, "no modifiers specified"); + } + + return mask; +} + +CString +CConfigReadContext::concatArgs(const ArgList& args) +{ + CString s("("); + for (size_t i = 0; i < args.size(); ++i) { + if (i != 0) { + s += ","; + } + s += args[i]; + } + s += ")"; + return s; +} + + +// +// CConfig I/O exceptions +// + +XConfigRead::XConfigRead(const CConfigReadContext& context, + const CString& error) : + m_error(CStringUtil::print("line %d: %s", + context.getLineNumber(), error.c_str())) +{ + // do nothing +} + +XConfigRead::XConfigRead(const CConfigReadContext& context, + const char* errorFmt, const CString& arg) : + m_error(CStringUtil::print("line %d: ", context.getLineNumber()) + + CStringUtil::format(errorFmt, arg.c_str())) +{ + // do nothing +} + +XConfigRead::~XConfigRead() +{ + // do nothing +} + +CString +XConfigRead::getWhat() const throw() +{ + return format("XConfigRead", "read error: %{1}", m_error.c_str()); +} diff --git a/lib/server/CConfig.h b/lib/server/CConfig.h new file mode 100644 index 00000000..c0d2faa8 --- /dev/null +++ b/lib/server/CConfig.h @@ -0,0 +1,536 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCONFIG_H +#define CCONFIG_H + +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "CNetworkAddress.h" +#include "CStringUtil.h" +#include "CInputFilter.h" +#include "XBase.h" +#include "stdmap.h" +#include "stdset.h" +#include "IPlatformScreen.h" +#include + +class CConfig; +class CConfigReadContext; + +namespace std { +template <> +struct iterator_traits { + typedef CString value_type; + typedef ptrdiff_t difference_type; + typedef bidirectional_iterator_tag iterator_category; + typedef CString* pointer; + typedef CString& reference; +}; +}; + +//! Server configuration +/*! +This class holds server configuration information. That includes +the names of screens and their aliases, the links between them, +and network addresses. + +Note that case is preserved in screen names but is ignored when +comparing names. Screen names and their aliases share a +namespace and must be unique. +*/ +class CConfig { +public: + typedef std::map CScreenOptions; + typedef std::pair CInterval; + + class CCellEdge { + public: + CCellEdge(EDirection side, float position); + CCellEdge(EDirection side, const CInterval&); + CCellEdge(const CString& name, EDirection side, const CInterval&); + ~CCellEdge(); + + CInterval getInterval() const; + void setName(const CString& newName); + CString getName() const; + EDirection getSide() const; + bool overlaps(const CCellEdge&) const; + bool isInside(float x) const; + + // transform position to [0,1] + float transform(float x) const; + + // transform [0,1] to position + float inverseTransform(float x) const; + + // compares side and start of interval + bool operator<(const CCellEdge&) const; + + // compares side and interval + bool operator==(const CCellEdge&) const; + bool operator!=(const CCellEdge&) const; + + private: + void init(const CString& name, EDirection side, + const CInterval&); + + private: + CString m_name; + EDirection m_side; + CInterval m_interval; + }; + +private: + class CName { + public: + CName(CConfig*, const CString& name); + + bool operator==(const CString& name) const; + + private: + CConfig* m_config; + CString m_name; + }; + + class CCell { + private: + typedef std::map CEdgeLinks; + + public: + typedef CEdgeLinks::const_iterator const_iterator; + + bool add(const CCellEdge& src, const CCellEdge& dst); + void remove(EDirection side); + void remove(EDirection side, float position); + void remove(const CName& destinationName); + void rename(const CName& oldName, const CString& newName); + + bool hasEdge(const CCellEdge&) const; + bool overlaps(const CCellEdge&) const; + + bool getLink(EDirection side, float position, + const CCellEdge*& src, const CCellEdge*& dst) const; + + bool operator==(const CCell&) const; + bool operator!=(const CCell&) const; + + const_iterator begin() const; + const_iterator end() const; + + private: + CEdgeLinks m_neighbors; + + public: + CScreenOptions m_options; + }; + typedef std::map CCellMap; + typedef std::map CNameMap; + +public: + typedef CCell::const_iterator link_const_iterator; + typedef CCellMap::const_iterator internal_const_iterator; + typedef CNameMap::const_iterator all_const_iterator; + class const_iterator : std::iterator_traits { + public: + explicit const_iterator() : m_i() { } + explicit const_iterator(const internal_const_iterator& i) : m_i(i) { } + + const_iterator& operator=(const const_iterator& i) { + m_i = i.m_i; + return *this; + } + CString operator*() { return m_i->first; } + const CString* operator->() { return &(m_i->first); } + const_iterator& operator++() { ++m_i; return *this; } + const_iterator operator++(int) { return const_iterator(m_i++); } + const_iterator& operator--() { --m_i; return *this; } + const_iterator operator--(int) { return const_iterator(m_i--); } + bool operator==(const const_iterator& i) const { + return (m_i == i.m_i); + } + bool operator!=(const const_iterator& i) const { + return (m_i != i.m_i); + } + + private: + internal_const_iterator m_i; + }; + + CConfig(); + virtual ~CConfig(); + + //! @name manipulators + //@{ + + //! Add screen + /*! + Adds a screen, returning true iff successful. If a screen or + alias with the given name exists then it fails. + */ + bool addScreen(const CString& name); + + //! Rename screen + /*! + Renames a screen. All references to the name are updated. + Returns true iff successful. + */ + bool renameScreen(const CString& oldName, + const CString& newName); + + //! Remove screen + /*! + Removes a screen. This also removes aliases for the screen and + disconnects any connections to the screen. \c name may be an + alias. + */ + void removeScreen(const CString& name); + + //! Remove all screens + /*! + Removes all screens, aliases, and connections. + */ + void removeAllScreens(); + + //! Add alias + /*! + Adds an alias for a screen name. An alias can be used + any place the canonical screen name can (except addScreen()). + Returns false if the alias name already exists or the canonical + name is unknown, otherwise returns true. + */ + bool addAlias(const CString& canonical, + const CString& alias); + + //! Remove alias + /*! + Removes an alias for a screen name. It returns false if the + alias is unknown or a canonical name, otherwise returns true. + */ + bool removeAlias(const CString& alias); + + //! Remove aliases + /*! + Removes all aliases for a canonical screen name. It returns false + if the canonical name is unknown, otherwise returns true. + */ + bool removeAliases(const CString& canonical); + + //! Remove all aliases + /*! + This removes all aliases but not the screens. + */ + void removeAllAliases(); + + //! Connect screens + /*! + Establishes a one-way connection between portions of opposite edges + of two screens. Each portion is described by an interval defined + by two numbers, the start and end of the interval half-open on the + end. The numbers range from 0 to 1, inclusive, for the left/top + to the right/bottom. The user will be able to jump from the + \c srcStart to \c srcSend interval of \c srcSide of screen + \c srcName to the opposite side of screen \c dstName in the interval + \c dstStart and \c dstEnd when both screens are connected to the + server and the user isn't locked to a screen. Returns false if + \c srcName is unknown. \c srcStart must be less than or equal to + \c srcEnd and \c dstStart must be less then or equal to \c dstEnd + and all of \c srcStart, \c srcEnd, \c dstStart, or \c dstEnd must + be inside the range [0,1]. + */ + bool connect(const CString& srcName, + EDirection srcSide, + float srcStart, float srcEnd, + const CString& dstName, + float dstStart, float dstEnd); + + //! Disconnect screens + /*! + Removes all connections created by connect() on side \c srcSide. + Returns false if \c srcName is unknown. + */ + bool disconnect(const CString& srcName, + EDirection srcSide); + + //! Disconnect screens + /*! + Removes the connections created by connect() on side \c srcSide + covering position \c position. Returns false if \c srcName is + unknown. + */ + bool disconnect(const CString& srcName, + EDirection srcSide, float position); + + //! Set server address + /*! + Set the synergy listen addresses. There is no default address so + this must be called to run a server using this configuration. + */ + void setSynergyAddress(const CNetworkAddress&); + + //! Add a screen option + /*! + Adds an option and its value to the named screen. Replaces the + existing option's value if there is one. Returns true iff \c name + is a known screen. + */ + bool addOption(const CString& name, + OptionID option, OptionValue value); + + //! Remove a screen option + /*! + Removes an option and its value from the named screen. Does + nothing if the option doesn't exist on the screen. Returns true + iff \c name is a known screen. + */ + bool removeOption(const CString& name, OptionID option); + + //! Remove a screen options + /*! + Removes all options and values from the named screen. Returns true + iff \c name is a known screen. + */ + bool removeOptions(const CString& name); + + //! Get the hot key input filter + /*! + Returns the hot key input filter. Clients can modify hotkeys using + that object. + */ + CInputFilter* getInputFilter(); + + //@} + //! @name accessors + //@{ + + //! Test screen name validity + /*! + Returns true iff \c name is a valid screen name. + */ + bool isValidScreenName(const CString& name) const; + + //! Get beginning (canonical) screen name iterator + const_iterator begin() const; + //! Get ending (canonical) screen name iterator + const_iterator end() const; + + //! Get beginning screen name iterator + all_const_iterator beginAll() const; + //! Get ending screen name iterator + all_const_iterator endAll() const; + + //! Test for screen name + /*! + Returns true iff \c name names a screen. + */ + bool isScreen(const CString& name) const; + + //! Test for canonical screen name + /*! + Returns true iff \c name is the canonical name of a screen. + */ + bool isCanonicalName(const CString& name) const; + + //! Get canonical name + /*! + Returns the canonical name of a screen or the empty string if + the name is unknown. Returns the canonical name if one is given. + */ + CString getCanonicalName(const CString& name) const; + + //! Get neighbor + /*! + Returns the canonical screen name of the neighbor in the given + direction (set through connect()) at position \c position. Returns + the empty string if there is no neighbor in that direction, otherwise + saves the position on the neighbor in \c positionOut if it's not + \c NULL. + */ + CString getNeighbor(const CString&, EDirection, + float position, float* positionOut) const; + + //! Check for neighbor + /*! + Returns \c true if the screen has a neighbor anywhere along the edge + given by the direction. + */ + bool hasNeighbor(const CString&, EDirection) const; + + //! Check for neighbor + /*! + Returns \c true if the screen has a neighbor in the given range along + the edge given by the direction. + */ + bool hasNeighbor(const CString&, EDirection, + float start, float end) const; + + //! Get beginning neighbor iterator + link_const_iterator beginNeighbor(const CString&) const; + //! Get ending neighbor iterator + link_const_iterator endNeighbor(const CString&) const; + + //! Get the server address + const CNetworkAddress& getSynergyAddress() const; + + //! Get the screen options + /*! + Returns all the added options for the named screen. Returns NULL + if the screen is unknown and an empty collection if there are no + options. + */ + const CScreenOptions* getOptions(const CString& name) const; + + //! Check for lock to screen action + /*! + Returns \c true if this configuration has a lock to screen action. + This is for backwards compatible support of ScrollLock locking. + */ + bool hasLockToScreenAction() const; + + //! Compare configurations + bool operator==(const CConfig&) const; + //! Compare configurations + bool operator!=(const CConfig&) const; + + //! Read configuration + /*! + Reads a configuration from a context. Throws XConfigRead on error + and context is unchanged. + */ + void read(CConfigReadContext& context); + + //! Read configuration + /*! + Reads a configuration from a stream. Throws XConfigRead on error. + */ + friend std::istream& operator>>(std::istream&, CConfig&); + + //! Write configuration + /*! + Writes a configuration to a stream. + */ + friend std::ostream& operator<<(std::ostream&, const CConfig&); + + //! Get direction name + /*! + Returns the name of a direction (for debugging). + */ + static const char* dirName(EDirection); + + //! Get interval as string + /*! + Returns an interval as a parseable string. + */ + static CString formatInterval(const CInterval&); + + //@} + +private: + void readSection(CConfigReadContext&); + void readSectionOptions(CConfigReadContext&); + void readSectionScreens(CConfigReadContext&); + void readSectionLinks(CConfigReadContext&); + void readSectionAliases(CConfigReadContext&); + + CInputFilter::CCondition* + parseCondition(CConfigReadContext&, + const CString& condition, + const std::vector& args); + void parseAction(CConfigReadContext&, + const CString& action, + const std::vector& args, + CInputFilter::CRule&, bool activate); + + void parseScreens(CConfigReadContext&, const CString&, + std::set& screens) const; + static const char* getOptionName(OptionID); + static CString getOptionValue(OptionID, OptionValue); + +private: + CCellMap m_map; + CNameMap m_nameToCanonicalName; + CNetworkAddress m_synergyAddress; + CScreenOptions m_globalOptions; + CInputFilter m_inputFilter; + bool m_hasLockToScreenAction; +}; + +//! Configuration read context +/*! +Maintains a context when reading a configuration from a stream. +*/ +class CConfigReadContext { +public: + typedef std::vector ArgList; + + CConfigReadContext(std::istream&, SInt32 firstLine = 1); + ~CConfigReadContext(); + + bool readLine(CString&); + UInt32 getLineNumber() const; + + operator void*() const; + bool operator!() const; + + OptionValue parseBoolean(const CString&) const; + OptionValue parseInt(const CString&) const; + OptionValue parseModifierKey(const CString&) const; + OptionValue parseCorner(const CString&) const; + OptionValue parseCorners(const CString&) const; + CConfig::CInterval + parseInterval(const ArgList& args) const; + void parseNameWithArgs( + const CString& type, const CString& line, + const CString& delim, CString::size_type& index, + CString& name, ArgList& args) const; + IPlatformScreen::CKeyInfo* + parseKeystroke(const CString& keystroke) const; + IPlatformScreen::CKeyInfo* + parseKeystroke(const CString& keystroke, + const std::set& screens) const; + IPlatformScreen::CButtonInfo* + parseMouse(const CString& mouse) const; + KeyModifierMask parseModifier(const CString& modifiers) const; + +private: + // not implemented + CConfigReadContext& operator=(const CConfigReadContext&); + + static CString concatArgs(const ArgList& args); + +private: + std::istream& m_stream; + SInt32 m_line; +}; + +//! Configuration stream read exception +/*! +Thrown when a configuration stream cannot be parsed. +*/ +class XConfigRead : public XBase { +public: + XConfigRead(const CConfigReadContext& context, const CString&); + XConfigRead(const CConfigReadContext& context, + const char* errorFmt, const CString& arg); + ~XConfigRead(); + +protected: + // XBase overrides + virtual CString getWhat() const throw(); + +private: + CString m_error; +}; + +#endif diff --git a/lib/server/CInputFilter.cpp b/lib/server/CInputFilter.cpp new file mode 100644 index 00000000..d5d7fc20 --- /dev/null +++ b/lib/server/CInputFilter.cpp @@ -0,0 +1,1066 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CInputFilter.h" +#include "CServer.h" +#include "CPrimaryClient.h" +#include "CKeyMap.h" +#include "CEventQueue.h" +#include "CLog.h" +#include "TMethodEventJob.h" +#include +#include + +// ----------------------------------------------------------------------------- +// Input Filter Condition Classes +// ----------------------------------------------------------------------------- +CInputFilter::CCondition::CCondition() +{ + // do nothing +} + +CInputFilter::CCondition::~CCondition() +{ + // do nothing +} + +void +CInputFilter::CCondition::enablePrimary(CPrimaryClient*) +{ + // do nothing +} + +void +CInputFilter::CCondition::disablePrimary(CPrimaryClient*) +{ + // do nothing +} + +CInputFilter::CKeystrokeCondition::CKeystrokeCondition( + IPlatformScreen::CKeyInfo* info) : + m_id(0), + m_key(info->m_key), + m_mask(info->m_mask) +{ + free(info); +} + +CInputFilter::CKeystrokeCondition::CKeystrokeCondition( + KeyID key, KeyModifierMask mask) : + m_id(0), + m_key(key), + m_mask(mask) +{ + // do nothing +} + +CInputFilter::CKeystrokeCondition::~CKeystrokeCondition() +{ + // do nothing +} + +KeyID +CInputFilter::CKeystrokeCondition::getKey() const +{ + return m_key; +} + +KeyModifierMask +CInputFilter::CKeystrokeCondition::getMask() const +{ + return m_mask; +} + +CInputFilter::CCondition* +CInputFilter::CKeystrokeCondition::clone() const +{ + return new CKeystrokeCondition(m_key, m_mask); +} + +CString +CInputFilter::CKeystrokeCondition::format() const +{ + return CStringUtil::print("keystroke(%s)", + CKeyMap::formatKey(m_key, m_mask).c_str()); +} + +CInputFilter::EFilterStatus +CInputFilter::CKeystrokeCondition::match(const CEvent& event) +{ + EFilterStatus status; + + // check for hotkey events + CEvent::Type type = event.getType(); + if (type == IPrimaryScreen::getHotKeyDownEvent()) { + status = kActivate; + } + else if (type == IPrimaryScreen::getHotKeyUpEvent()) { + status = kDeactivate; + } + else { + return kNoMatch; + } + + // check if it's our hotkey + IPrimaryScreen::CHotKeyInfo* kinfo = + reinterpret_cast(event.getData()); + if (kinfo->m_id != m_id) { + return kNoMatch; + } + + return status; +} + +void +CInputFilter::CKeystrokeCondition::enablePrimary(CPrimaryClient* primary) +{ + m_id = primary->registerHotKey(m_key, m_mask); +} + +void +CInputFilter::CKeystrokeCondition::disablePrimary(CPrimaryClient* primary) +{ + primary->unregisterHotKey(m_id); + m_id = 0; +} + +CInputFilter::CMouseButtonCondition::CMouseButtonCondition( + IPlatformScreen::CButtonInfo* info) : + m_button(info->m_button), + m_mask(info->m_mask) +{ + free(info); +} + +CInputFilter::CMouseButtonCondition::CMouseButtonCondition( + ButtonID button, KeyModifierMask mask) : + m_button(button), + m_mask(mask) +{ + // do nothing +} + +CInputFilter::CMouseButtonCondition::~CMouseButtonCondition() +{ + // do nothing +} + +ButtonID +CInputFilter::CMouseButtonCondition::getButton() const +{ + return m_button; +} + +KeyModifierMask +CInputFilter::CMouseButtonCondition::getMask() const +{ + return m_mask; +} + +CInputFilter::CCondition* +CInputFilter::CMouseButtonCondition::clone() const +{ + return new CMouseButtonCondition(m_button, m_mask); +} + +CString +CInputFilter::CMouseButtonCondition::format() const +{ + CString key = CKeyMap::formatKey(kKeyNone, m_mask); + if (!key.empty()) { + key += "+"; + } + return CStringUtil::print("mousebutton(%s%d)", key.c_str(), m_button); +} + +CInputFilter::EFilterStatus +CInputFilter::CMouseButtonCondition::match(const CEvent& event) +{ + static const KeyModifierMask s_ignoreMask = + KeyModifierAltGr | KeyModifierCapsLock | + KeyModifierNumLock | KeyModifierScrollLock; + + EFilterStatus status; + + // check for hotkey events + CEvent::Type type = event.getType(); + if (type == IPrimaryScreen::getButtonDownEvent()) { + status = kActivate; + } + else if (type == IPrimaryScreen::getButtonUpEvent()) { + status = kDeactivate; + } + else { + return kNoMatch; + } + + // check if it's the right button and modifiers. ignore modifiers + // that cannot be combined with a mouse button. + IPlatformScreen::CButtonInfo* minfo = + reinterpret_cast(event.getData()); + if (minfo->m_button != m_button || + (minfo->m_mask & ~s_ignoreMask) != m_mask) { + return kNoMatch; + } + + return status; +} + +CInputFilter::CScreenConnectedCondition::CScreenConnectedCondition( + const CString& screen) : + m_screen(screen) +{ + // do nothing +} + +CInputFilter::CScreenConnectedCondition::~CScreenConnectedCondition() +{ + // do nothing +} + +CInputFilter::CCondition* +CInputFilter::CScreenConnectedCondition::clone() const +{ + return new CScreenConnectedCondition(m_screen); +} + +CString +CInputFilter::CScreenConnectedCondition::format() const +{ + return CStringUtil::print("connect(%s)", m_screen.c_str()); +} + +CInputFilter::EFilterStatus +CInputFilter::CScreenConnectedCondition::match(const CEvent& event) +{ + if (event.getType() == CServer::getConnectedEvent()) { + CServer::CScreenConnectedInfo* info = + reinterpret_cast(event.getData()); + if (m_screen == info->m_screen || m_screen.empty()) { + return kActivate; + } + } + + return kNoMatch; +} + +// ----------------------------------------------------------------------------- +// Input Filter Action Classes +// ----------------------------------------------------------------------------- +CInputFilter::CAction::CAction() +{ + // do nothing +} + +CInputFilter::CAction::~CAction() +{ + // do nothing +} + +CInputFilter::CLockCursorToScreenAction::CLockCursorToScreenAction(Mode mode) : + m_mode(mode) +{ + // do nothing +} + +CInputFilter::CLockCursorToScreenAction::Mode +CInputFilter::CLockCursorToScreenAction::getMode() const +{ + return m_mode; +} + +CInputFilter::CAction* +CInputFilter::CLockCursorToScreenAction::clone() const +{ + return new CLockCursorToScreenAction(*this); +} + +CString +CInputFilter::CLockCursorToScreenAction::format() const +{ + static const char* s_mode[] = { "off", "on", "toggle" }; + + return CStringUtil::print("lockCursorToScreen(%s)", s_mode[m_mode]); +} + +void +CInputFilter::CLockCursorToScreenAction::perform(const CEvent& event) +{ + static const CServer::CLockCursorToScreenInfo::State s_state[] = { + CServer::CLockCursorToScreenInfo::kOff, + CServer::CLockCursorToScreenInfo::kOn, + CServer::CLockCursorToScreenInfo::kToggle + }; + + // send event + CServer::CLockCursorToScreenInfo* info = + CServer::CLockCursorToScreenInfo::alloc(s_state[m_mode]); + EVENTQUEUE->addEvent(CEvent(CServer::getLockCursorToScreenEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CSwitchToScreenAction::CSwitchToScreenAction( + const CString& screen) : + m_screen(screen) +{ + // do nothing +} + +CString +CInputFilter::CSwitchToScreenAction::getScreen() const +{ + return m_screen; +} + +CInputFilter::CAction* +CInputFilter::CSwitchToScreenAction::clone() const +{ + return new CSwitchToScreenAction(*this); +} + +CString +CInputFilter::CSwitchToScreenAction::format() const +{ + return CStringUtil::print("switchToScreen(%s)", m_screen.c_str()); +} + +void +CInputFilter::CSwitchToScreenAction::perform(const CEvent& event) +{ + // pick screen name. if m_screen is empty then use the screen from + // event if it has one. + CString screen = m_screen; + if (screen.empty() && event.getType() == CServer::getConnectedEvent()) { + CServer::CScreenConnectedInfo* info = + reinterpret_cast(event.getData()); + screen = info->m_screen; + } + + // send event + CServer::CSwitchToScreenInfo* info = + CServer::CSwitchToScreenInfo::alloc(screen); + EVENTQUEUE->addEvent(CEvent(CServer::getSwitchToScreenEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CSwitchInDirectionAction::CSwitchInDirectionAction( + EDirection direction) : + m_direction(direction) +{ + // do nothing +} + +EDirection +CInputFilter::CSwitchInDirectionAction::getDirection() const +{ + return m_direction; +} + +CInputFilter::CAction* +CInputFilter::CSwitchInDirectionAction::clone() const +{ + return new CSwitchInDirectionAction(*this); +} + +CString +CInputFilter::CSwitchInDirectionAction::format() const +{ + static const char* s_names[] = { + "", + "left", + "right", + "up", + "down" + }; + + return CStringUtil::print("switchInDirection(%s)", s_names[m_direction]); +} + +void +CInputFilter::CSwitchInDirectionAction::perform(const CEvent& event) +{ + CServer::CSwitchInDirectionInfo* info = + CServer::CSwitchInDirectionInfo::alloc(m_direction); + EVENTQUEUE->addEvent(CEvent(CServer::getSwitchInDirectionEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CKeyboardBroadcastAction::CKeyboardBroadcastAction(Mode mode) : + m_mode(mode) +{ + // do nothing +} + +CInputFilter::CKeyboardBroadcastAction::CKeyboardBroadcastAction( + Mode mode, + const std::set& screens) : + m_mode(mode), + m_screens(IKeyState::CKeyInfo::join(screens)) +{ + // do nothing +} + +CInputFilter::CKeyboardBroadcastAction::Mode +CInputFilter::CKeyboardBroadcastAction::getMode() const +{ + return m_mode; +} + +std::set +CInputFilter::CKeyboardBroadcastAction::getScreens() const +{ + std::set screens; + IKeyState::CKeyInfo::split(m_screens.c_str(), screens); + return screens; +} + +CInputFilter::CAction* +CInputFilter::CKeyboardBroadcastAction::clone() const +{ + return new CKeyboardBroadcastAction(*this); +} + +CString +CInputFilter::CKeyboardBroadcastAction::format() const +{ + static const char* s_mode[] = { "off", "on", "toggle" }; + static const char* s_name = "keyboardBroadcast"; + + if (m_screens.empty() || m_screens[0] == '*') { + return CStringUtil::print("%s(%s)", s_name, s_mode[m_mode]); + } + else { + return CStringUtil::print("%s(%s,%.*s)", s_name, s_mode[m_mode], + m_screens.size() - 2, + m_screens.c_str() + 1); + } +} + +void +CInputFilter::CKeyboardBroadcastAction::perform(const CEvent& event) +{ + static const CServer::CKeyboardBroadcastInfo::State s_state[] = { + CServer::CKeyboardBroadcastInfo::kOff, + CServer::CKeyboardBroadcastInfo::kOn, + CServer::CKeyboardBroadcastInfo::kToggle + }; + + // send event + CServer::CKeyboardBroadcastInfo* info = + CServer::CKeyboardBroadcastInfo::alloc(s_state[m_mode], m_screens); + EVENTQUEUE->addEvent(CEvent(CServer::getKeyboardBroadcastEvent(), + event.getTarget(), info, + CEvent::kDeliverImmediately)); +} + +CInputFilter::CKeystrokeAction::CKeystrokeAction( + IPlatformScreen::CKeyInfo* info, bool press) : + m_keyInfo(info), + m_press(press) +{ + // do nothing +} + +CInputFilter::CKeystrokeAction::~CKeystrokeAction() +{ + free(m_keyInfo); +} + +void +CInputFilter::CKeystrokeAction::adoptInfo(IPlatformScreen::CKeyInfo* info) +{ + free(m_keyInfo); + m_keyInfo = info; +} + +const IPlatformScreen::CKeyInfo* +CInputFilter::CKeystrokeAction::getInfo() const +{ + return m_keyInfo; +} + +bool +CInputFilter::CKeystrokeAction::isOnPress() const +{ + return m_press; +} + +CInputFilter::CAction* +CInputFilter::CKeystrokeAction::clone() const +{ + IKeyState::CKeyInfo* info = IKeyState::CKeyInfo::alloc(*m_keyInfo); + return new CKeystrokeAction(info, m_press); +} + +CString +CInputFilter::CKeystrokeAction::format() const +{ + const char* type = formatName(); + + if (m_keyInfo->m_screens[0] == '\0') { + return CStringUtil::print("%s(%s)", type, + CKeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str()); + } + else if (m_keyInfo->m_screens[0] == '*') { + return CStringUtil::print("%s(%s,*)", type, + CKeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str()); + } + else { + return CStringUtil::print("%s(%s,%.*s)", type, + CKeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str(), + strlen(m_keyInfo->m_screens + 1) - 1, + m_keyInfo->m_screens + 1); + } +} + +void +CInputFilter::CKeystrokeAction::perform(const CEvent& event) +{ + CEvent::Type type = m_press ? IPlatformScreen::getKeyDownEvent() : + IPlatformScreen::getKeyUpEvent(); + EVENTQUEUE->addEvent(CEvent(IPlatformScreen::getFakeInputBeginEvent(), + event.getTarget(), NULL, + CEvent::kDeliverImmediately)); + EVENTQUEUE->addEvent(CEvent(type, event.getTarget(), m_keyInfo, + CEvent::kDeliverImmediately | + CEvent::kDontFreeData)); + EVENTQUEUE->addEvent(CEvent(IPlatformScreen::getFakeInputEndEvent(), + event.getTarget(), NULL, + CEvent::kDeliverImmediately)); +} + +const char* +CInputFilter::CKeystrokeAction::formatName() const +{ + return (m_press ? "keyDown" : "keyUp"); +} + +CInputFilter::CMouseButtonAction::CMouseButtonAction( + IPlatformScreen::CButtonInfo* info, bool press) : + m_buttonInfo(info), + m_press(press) +{ + // do nothing +} + +CInputFilter::CMouseButtonAction::~CMouseButtonAction() +{ + free(m_buttonInfo); +} + +const IPlatformScreen::CButtonInfo* +CInputFilter::CMouseButtonAction::getInfo() const +{ + return m_buttonInfo; +} + +bool +CInputFilter::CMouseButtonAction::isOnPress() const +{ + return m_press; +} + +CInputFilter::CAction* +CInputFilter::CMouseButtonAction::clone() const +{ + IPlatformScreen::CButtonInfo* info = + IPrimaryScreen::CButtonInfo::alloc(*m_buttonInfo); + return new CMouseButtonAction(info, m_press); +} + +CString +CInputFilter::CMouseButtonAction::format() const +{ + const char* type = formatName(); + + CString key = CKeyMap::formatKey(kKeyNone, m_buttonInfo->m_mask); + return CStringUtil::print("%s(%s%s%d)", type, + key.c_str(), key.empty() ? "" : "+", + m_buttonInfo->m_button); +} + +void +CInputFilter::CMouseButtonAction::perform(const CEvent& event) + +{ + // send modifiers + IPlatformScreen::CKeyInfo* modifierInfo = NULL; + if (m_buttonInfo->m_mask != 0) { + KeyID key = m_press ? kKeySetModifiers : kKeyClearModifiers; + modifierInfo = + IKeyState::CKeyInfo::alloc(key, m_buttonInfo->m_mask, 0, 1); + EVENTQUEUE->addEvent(CEvent(IPlatformScreen::getKeyDownEvent(), + event.getTarget(), modifierInfo, + CEvent::kDeliverImmediately)); + } + + // send button + CEvent::Type type = m_press ? IPlatformScreen::getButtonDownEvent() : + IPlatformScreen::getButtonUpEvent(); + EVENTQUEUE->addEvent(CEvent(type, event.getTarget(), m_buttonInfo, + CEvent::kDeliverImmediately | + CEvent::kDontFreeData)); +} + +const char* +CInputFilter::CMouseButtonAction::formatName() const +{ + return (m_press ? "mouseDown" : "mouseUp"); +} + +// +// CInputFilter::CRule +// + +CInputFilter::CRule::CRule() : + m_condition(NULL) +{ + // do nothing +} + +CInputFilter::CRule::CRule(CCondition* adoptedCondition) : + m_condition(adoptedCondition) +{ + // do nothing +} + +CInputFilter::CRule::CRule(const CRule& rule) : + m_condition(NULL) +{ + copy(rule); +} + +CInputFilter::CRule::~CRule() +{ + clear(); +} + +CInputFilter::CRule& +CInputFilter::CRule::operator=(const CRule& rule) +{ + if (&rule != this) { + copy(rule); + } + return *this; +} + +void +CInputFilter::CRule::clear() +{ + delete m_condition; + for (CActionList::iterator i = m_activateActions.begin(); + i != m_activateActions.end(); ++i) { + delete *i; + } + for (CActionList::iterator i = m_deactivateActions.begin(); + i != m_deactivateActions.end(); ++i) { + delete *i; + } + + m_condition = NULL; + m_activateActions.clear(); + m_deactivateActions.clear(); +} + +void +CInputFilter::CRule::copy(const CRule& rule) +{ + clear(); + if (rule.m_condition != NULL) { + m_condition = rule.m_condition->clone(); + } + for (CActionList::const_iterator i = rule.m_activateActions.begin(); + i != rule.m_activateActions.end(); ++i) { + m_activateActions.push_back((*i)->clone()); + } + for (CActionList::const_iterator i = rule.m_deactivateActions.begin(); + i != rule.m_deactivateActions.end(); ++i) { + m_deactivateActions.push_back((*i)->clone()); + } +} + +void +CInputFilter::CRule::setCondition(CCondition* adopted) +{ + delete m_condition; + m_condition = adopted; +} + +void +CInputFilter::CRule::adoptAction(CAction* action, bool onActivation) +{ + if (action != NULL) { + if (onActivation) { + m_activateActions.push_back(action); + } + else { + m_deactivateActions.push_back(action); + } + } +} + +void +CInputFilter::CRule::removeAction(bool onActivation, UInt32 index) +{ + if (onActivation) { + delete m_activateActions[index]; + m_activateActions.erase(m_activateActions.begin() + index); + } + else { + delete m_deactivateActions[index]; + m_deactivateActions.erase(m_deactivateActions.begin() + index); + } +} + +void +CInputFilter::CRule::replaceAction(CAction* adopted, + bool onActivation, UInt32 index) +{ + if (adopted == NULL) { + removeAction(onActivation, index); + } + else if (onActivation) { + delete m_activateActions[index]; + m_activateActions[index] = adopted; + } + else { + delete m_deactivateActions[index]; + m_deactivateActions[index] = adopted; + } +} + +void +CInputFilter::CRule::enable(CPrimaryClient* primaryClient) +{ + if (m_condition != NULL) { + m_condition->enablePrimary(primaryClient); + } +} + +void +CInputFilter::CRule::disable(CPrimaryClient* primaryClient) +{ + if (m_condition != NULL) { + m_condition->disablePrimary(primaryClient); + } +} + +bool +CInputFilter::CRule::handleEvent(const CEvent& event) +{ + // NULL condition never matches + if (m_condition == NULL) { + return false; + } + + // match + const CActionList* actions; + switch (m_condition->match(event)) { + default: + // not handled + return false; + + case kActivate: + actions = &m_activateActions; + LOG((CLOG_DEBUG1 "activate actions")); + break; + + case kDeactivate: + actions = &m_deactivateActions; + LOG((CLOG_DEBUG1 "deactivate actions")); + break; + } + + // perform actions + for (CActionList::const_iterator i = actions->begin(); + i != actions->end(); ++i) { + LOG((CLOG_DEBUG1 "hotkey: %s", (*i)->format().c_str())); + (*i)->perform(event); + } + + return true; +} + +CString +CInputFilter::CRule::format() const +{ + CString s; + if (m_condition != NULL) { + // condition + s += m_condition->format(); + s += " = "; + + // activate actions + CActionList::const_iterator i = m_activateActions.begin(); + if (i != m_activateActions.end()) { + s += (*i)->format(); + while (++i != m_activateActions.end()) { + s += ", "; + s += (*i)->format(); + } + } + + // deactivate actions + if (!m_deactivateActions.empty()) { + s += "; "; + i = m_deactivateActions.begin(); + if (i != m_deactivateActions.end()) { + s += (*i)->format(); + while (++i != m_deactivateActions.end()) { + s += ", "; + s += (*i)->format(); + } + } + } + } + return s; +} + +const CInputFilter::CCondition* +CInputFilter::CRule::getCondition() const +{ + return m_condition; +} + +UInt32 +CInputFilter::CRule::getNumActions(bool onActivation) const +{ + if (onActivation) { + return static_cast(m_activateActions.size()); + } + else { + return static_cast(m_deactivateActions.size()); + } +} + +const CInputFilter::CAction& +CInputFilter::CRule::getAction(bool onActivation, UInt32 index) const +{ + if (onActivation) { + return *m_activateActions[index]; + } + else { + return *m_deactivateActions[index]; + } +} + + +// ----------------------------------------------------------------------------- +// Input Filter Class +// ----------------------------------------------------------------------------- +CInputFilter::CInputFilter() : + m_primaryClient(NULL) +{ + // do nothing +} + +CInputFilter::CInputFilter(const CInputFilter& x) : + m_ruleList(x.m_ruleList), + m_primaryClient(NULL) +{ + setPrimaryClient(x.m_primaryClient); +} + +CInputFilter::~CInputFilter() +{ + setPrimaryClient(NULL); +} + +CInputFilter& +CInputFilter::operator=(const CInputFilter& x) +{ + if (&x != this) { + CPrimaryClient* oldClient = m_primaryClient; + setPrimaryClient(NULL); + + m_ruleList = x.m_ruleList; + + setPrimaryClient(oldClient); + } + return *this; +} + +void +CInputFilter::addFilterRule(const CRule& rule) +{ + m_ruleList.push_back(rule); + if (m_primaryClient != NULL) { + m_ruleList.back().enable(m_primaryClient); + } +} + +void +CInputFilter::removeFilterRule(UInt32 index) +{ + if (m_primaryClient != NULL) { + m_ruleList[index].disable(m_primaryClient); + } + m_ruleList.erase(m_ruleList.begin() + index); +} + +CInputFilter::CRule& +CInputFilter::getRule(UInt32 index) +{ + return m_ruleList[index]; +} + +void +CInputFilter::setPrimaryClient(CPrimaryClient* client) +{ + if (m_primaryClient == client) { + return; + } + + if (m_primaryClient != NULL) { + for (CRuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + rule->disable(m_primaryClient); + } + + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getHotKeyDownEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getHotKeyUpEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(CServer::getConnectedEvent(), + m_primaryClient->getEventTarget()); + } + + m_primaryClient = client; + + if (m_primaryClient != NULL) { + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getHotKeyDownEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getHotKeyUpEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + EVENTQUEUE->adoptHandler(CServer::getConnectedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CInputFilter::handleEvent)); + + for (CRuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + rule->enable(m_primaryClient); + } + } +} + +CString +CInputFilter::format(const CString& linePrefix) const +{ + CString s; + for (CRuleList::const_iterator i = m_ruleList.begin(); + i != m_ruleList.end(); ++i) { + s += linePrefix; + s += i->format(); + s += "\n"; + } + return s; +} + +UInt32 +CInputFilter::getNumRules() const +{ + return static_cast(m_ruleList.size()); +} + +bool +CInputFilter::operator==(const CInputFilter& x) const +{ + // if there are different numbers of rules then we can't be equal + if (m_ruleList.size() != x.m_ruleList.size()) { + return false; + } + + // compare rule lists. the easiest way to do that is to format each + // rule into a string, sort the strings, then compare the results. + std::vector aList, bList; + for (CRuleList::const_iterator i = m_ruleList.begin(); + i != m_ruleList.end(); ++i) { + aList.push_back(i->format()); + } + for (CRuleList::const_iterator i = x.m_ruleList.begin(); + i != x.m_ruleList.end(); ++i) { + bList.push_back(i->format()); + } + std::partial_sort(aList.begin(), aList.end(), aList.end()); + std::partial_sort(bList.begin(), bList.end(), bList.end()); + return (aList == bList); +} + +bool +CInputFilter::operator!=(const CInputFilter& x) const +{ + return !operator==(x); +} + +void +CInputFilter::handleEvent(const CEvent& event, void*) +{ + // copy event and adjust target + CEvent myEvent(event.getType(), this, event.getData(), + event.getFlags() | CEvent::kDontFreeData | + CEvent::kDeliverImmediately); + + // let each rule try to match the event until one does + for (CRuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + if (rule->handleEvent(myEvent)) { + // handled + return; + } + } + + // not handled so pass through + EVENTQUEUE->addEvent(myEvent); +} diff --git a/lib/server/CInputFilter.h b/lib/server/CInputFilter.h new file mode 100644 index 00000000..571ec82b --- /dev/null +++ b/lib/server/CInputFilter.h @@ -0,0 +1,344 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CINPUTFILTER_H +#define CINPUTFILTER_H + +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "ProtocolTypes.h" +#include "IPlatformScreen.h" +#include "CString.h" +#include "stdmap.h" +#include "stdset.h" + +class CPrimaryClient; +class CEvent; + +class CInputFilter { +public: + // ------------------------------------------------------------------------- + // Input Filter Condition Classes + // ------------------------------------------------------------------------- + enum EFilterStatus { + kNoMatch, + kActivate, + kDeactivate + }; + + class CCondition { + public: + CCondition(); + virtual ~CCondition(); + + virtual CCondition* clone() const = 0; + virtual CString format() const = 0; + + virtual EFilterStatus match(const CEvent&) = 0; + + virtual void enablePrimary(CPrimaryClient*); + virtual void disablePrimary(CPrimaryClient*); + }; + + // CKeystrokeCondition + class CKeystrokeCondition : public CCondition { + public: + CKeystrokeCondition(IPlatformScreen::CKeyInfo*); + CKeystrokeCondition(KeyID key, KeyModifierMask mask); + virtual ~CKeystrokeCondition(); + + KeyID getKey() const; + KeyModifierMask getMask() const; + + // CCondition overrides + virtual CCondition* clone() const; + virtual CString format() const; + virtual EFilterStatus match(const CEvent&); + virtual void enablePrimary(CPrimaryClient*); + virtual void disablePrimary(CPrimaryClient*); + + private: + UInt32 m_id; + KeyID m_key; + KeyModifierMask m_mask; + }; + + // CMouseButtonCondition + class CMouseButtonCondition : public CCondition { + public: + CMouseButtonCondition(IPlatformScreen::CButtonInfo*); + CMouseButtonCondition(ButtonID, KeyModifierMask mask); + virtual ~CMouseButtonCondition(); + + ButtonID getButton() const; + KeyModifierMask getMask() const; + + // CCondition overrides + virtual CCondition* clone() const; + virtual CString format() const; + virtual EFilterStatus match(const CEvent&); + + private: + ButtonID m_button; + KeyModifierMask m_mask; + }; + + // CScreenConnectedCondition + class CScreenConnectedCondition : public CCondition { + public: + CScreenConnectedCondition(const CString& screen); + virtual ~CScreenConnectedCondition(); + + // CCondition overrides + virtual CCondition* clone() const; + virtual CString format() const; + virtual EFilterStatus match(const CEvent&); + + private: + CString m_screen; + }; + + // ------------------------------------------------------------------------- + // Input Filter Action Classes + // ------------------------------------------------------------------------- + + class CAction { + public: + CAction(); + virtual ~CAction(); + + virtual CAction* clone() const = 0; + virtual CString format() const = 0; + + virtual void perform(const CEvent&) = 0; + }; + + // CLockCursorToScreenAction + class CLockCursorToScreenAction : public CAction { + public: + enum Mode { kOff, kOn, kToggle }; + + CLockCursorToScreenAction(Mode = kToggle); + + Mode getMode() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + Mode m_mode; + }; + + // CSwitchToScreenAction + class CSwitchToScreenAction : public CAction { + public: + CSwitchToScreenAction(const CString& screen); + + CString getScreen() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + CString m_screen; + }; + + // CSwitchInDirectionAction + class CSwitchInDirectionAction : public CAction { + public: + CSwitchInDirectionAction(EDirection); + + EDirection getDirection() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + EDirection m_direction; + }; + + // CKeyboardBroadcastAction + class CKeyboardBroadcastAction : public CAction { + public: + enum Mode { kOff, kOn, kToggle }; + + CKeyboardBroadcastAction(Mode = kToggle); + CKeyboardBroadcastAction(Mode, const std::set& screens); + + Mode getMode() const; + std::set getScreens() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + private: + Mode m_mode; + CString m_screens; + }; + + // CKeystrokeAction + class CKeystrokeAction : public CAction { + public: + CKeystrokeAction(IPlatformScreen::CKeyInfo* adoptedInfo, bool press); + ~CKeystrokeAction(); + + void adoptInfo(IPlatformScreen::CKeyInfo*); + const IPlatformScreen::CKeyInfo* + getInfo() const; + bool isOnPress() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + protected: + virtual const char* formatName() const; + + private: + IPlatformScreen::CKeyInfo* m_keyInfo; + bool m_press; + }; + + // CMouseButtonAction -- modifier combinations not implemented yet + class CMouseButtonAction : public CAction { + public: + CMouseButtonAction(IPlatformScreen::CButtonInfo* adoptedInfo, + bool press); + ~CMouseButtonAction(); + + const IPlatformScreen::CButtonInfo* + getInfo() const; + bool isOnPress() const; + + // CAction overrides + virtual CAction* clone() const; + virtual CString format() const; + virtual void perform(const CEvent&); + + protected: + virtual const char* formatName() const; + + private: + IPlatformScreen::CButtonInfo* m_buttonInfo; + bool m_press; + }; + + class CRule { + public: + CRule(); + CRule(CCondition* adopted); + CRule(const CRule&); + ~CRule(); + + CRule& operator=(const CRule&); + + // replace the condition + void setCondition(CCondition* adopted); + + // add an action to the rule + void adoptAction(CAction*, bool onActivation); + + // remove an action from the rule + void removeAction(bool onActivation, UInt32 index); + + // replace an action in the rule + void replaceAction(CAction* adopted, + bool onActivation, UInt32 index); + + // enable/disable + void enable(CPrimaryClient*); + void disable(CPrimaryClient*); + + // event handling + bool handleEvent(const CEvent&); + + // convert rule to a string + CString format() const; + + // get the rule's condition + const CCondition* + getCondition() const; + + // get number of actions + UInt32 getNumActions(bool onActivation) const; + + // get action by index + const CAction& getAction(bool onActivation, UInt32 index) const; + + private: + void clear(); + void copy(const CRule&); + + private: + typedef std::vector CActionList; + + CCondition* m_condition; + CActionList m_activateActions; + CActionList m_deactivateActions; + }; + + // ------------------------------------------------------------------------- + // Input Filter Class + // ------------------------------------------------------------------------- + typedef std::vector CRuleList; + + CInputFilter(); + CInputFilter(const CInputFilter&); + virtual ~CInputFilter(); + + CInputFilter& operator=(const CInputFilter&); + + // add rule, adopting the condition and the actions + void addFilterRule(const CRule& rule); + + // remove a rule + void removeFilterRule(UInt32 index); + + // get rule by index + CRule& getRule(UInt32 index); + + // enable event filtering using the given primary client. disable + // if client is NULL. + void setPrimaryClient(CPrimaryClient* client); + + // convert rules to a string + CString format(const CString& linePrefix) const; + + // get number of rules + UInt32 getNumRules() const; + + //! Compare filters + bool operator==(const CInputFilter&) const; + //! Compare filters + bool operator!=(const CInputFilter&) const; + +private: + // event handling + void handleEvent(const CEvent&, void*); + +private: + CRuleList m_ruleList; + CPrimaryClient* m_primaryClient; +}; + +#endif diff --git a/lib/server/CPrimaryClient.cpp b/lib/server/CPrimaryClient.cpp new file mode 100644 index 00000000..03146655 --- /dev/null +++ b/lib/server/CPrimaryClient.cpp @@ -0,0 +1,257 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CPrimaryClient.h" +#include "CScreen.h" +#include "CClipboard.h" +#include "CLog.h" + +// +// CPrimaryClient +// + +CPrimaryClient::CPrimaryClient(const CString& name, CScreen* screen) : + CBaseClientProxy(name), + m_screen(screen), + m_fakeInputCount(0) +{ + // all clipboards are clean + for (UInt32 i = 0; i < kClipboardEnd; ++i) { + m_clipboardDirty[i] = false; + } +} + +CPrimaryClient::~CPrimaryClient() +{ + // do nothing +} + +void +CPrimaryClient::reconfigure(UInt32 activeSides) +{ + m_screen->reconfigure(activeSides); +} + +UInt32 +CPrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask) +{ + return m_screen->registerHotKey(key, mask); +} + +void +CPrimaryClient::unregisterHotKey(UInt32 id) +{ + m_screen->unregisterHotKey(id); +} + +void +CPrimaryClient::fakeInputBegin() +{ + if (++m_fakeInputCount == 1) { + m_screen->fakeInputBegin(); + } +} + +void +CPrimaryClient::fakeInputEnd() +{ + if (--m_fakeInputCount == 0) { + m_screen->fakeInputEnd(); + } +} + +SInt32 +CPrimaryClient::getJumpZoneSize() const +{ + return m_screen->getJumpZoneSize(); +} + +void +CPrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +KeyModifierMask +CPrimaryClient::getToggleMask() const +{ + return m_screen->pollActiveModifiers(); +} + +bool +CPrimaryClient::isLockedToScreen() const +{ + return m_screen->isLockedToScreen(); +} + +void* +CPrimaryClient::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +CPrimaryClient::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +CPrimaryClient::getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const +{ + m_screen->getShape(x, y, width, height); +} + +void +CPrimaryClient::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +CPrimaryClient::enable() +{ + m_screen->enable(); +} + +void +CPrimaryClient::disable() +{ + m_screen->disable(); +} + +void +CPrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool screensaver) +{ + m_screen->setSequenceNumber(seqNum); + if (!screensaver) { + m_screen->warpCursor(xAbs, yAbs); + } + m_screen->enter(mask); +} + +bool +CPrimaryClient::leave() +{ + return m_screen->leave(); +} + +void +CPrimaryClient::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore if this clipboard is already clean + if (m_clipboardDirty[id]) { + // this clipboard is now clean + m_clipboardDirty[id] = false; + + // set clipboard + m_screen->setClipboard(id, clipboard); + } +} + +void +CPrimaryClient::grabClipboard(ClipboardID id) +{ + // grab clipboard + m_screen->grabClipboard(id); + + // clipboard is dirty (because someone else owns it now) + m_clipboardDirty[id] = true; +} + +void +CPrimaryClient::setClipboardDirty(ClipboardID id, bool dirty) +{ + m_clipboardDirty[id] = dirty; +} + +void +CPrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + if (m_fakeInputCount > 0) { +// XXX -- don't forward keystrokes to primary screen for now + (void)key; + (void)mask; + (void)button; +// m_screen->keyDown(key, mask, button); + } +} + +void +CPrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton) +{ + // ignore +} + +void +CPrimaryClient::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + if (m_fakeInputCount > 0) { +// XXX -- don't forward keystrokes to primary screen for now + (void)key; + (void)mask; + (void)button; +// m_screen->keyUp(key, mask, button); + } +} + +void +CPrimaryClient::mouseDown(ButtonID) +{ + // ignore +} + +void +CPrimaryClient::mouseUp(ButtonID) +{ + // ignore +} + +void +CPrimaryClient::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->warpCursor(x, y); +} + +void +CPrimaryClient::mouseRelativeMove(SInt32, SInt32) +{ + // ignore +} + +void +CPrimaryClient::mouseWheel(SInt32, SInt32) +{ + // ignore +} + +void +CPrimaryClient::screensaver(bool) +{ + // ignore +} + +void +CPrimaryClient::resetOptions() +{ + m_screen->resetOptions(); +} + +void +CPrimaryClient::setOptions(const COptionsList& options) +{ + m_screen->setOptions(options); +} diff --git a/lib/server/CPrimaryClient.h b/lib/server/CPrimaryClient.h new file mode 100644 index 00000000..e768a21d --- /dev/null +++ b/lib/server/CPrimaryClient.h @@ -0,0 +1,145 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPRIMARYCLIENT_H +#define CPRIMARYCLIENT_H + +#include "CBaseClientProxy.h" +#include "ProtocolTypes.h" + +class CScreen; + +//! Primary screen as pseudo-client +/*! +The primary screen does not have a client associated with it. This +class provides a pseudo-client to allow the primary screen to be +treated as if it was a client. +*/ +class CPrimaryClient : public CBaseClientProxy { +public: + /*! + \c name is the name of the server and \p screen is primary screen. + */ + CPrimaryClient(const CString& name, CScreen* screen); + ~CPrimaryClient(); + + //! @name manipulators + //@{ + + //! Update configuration + /*! + Handles reconfiguration of jump zones. + */ + void reconfigure(UInt32 activeSides); + + //! Register a system hotkey + /*! + Registers a system-wide hotkey for key \p key with modifiers \p mask. + Returns an id used to unregister the hotkey. + */ + UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + void unregisterHotKey(UInt32 id); + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() and + \c fakeInputEnd() may be nested; only the outermost have an effect. + */ + void fakeInputBegin(); + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + SInt32 getJumpZoneSize() const; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + void getCursorCenter(SInt32& x, SInt32& y) const; + + //! Get toggle key state + /*! + Returns the primary screen's current toggle modifier key state. + */ + KeyModifierMask getToggleMask() const; + + //! Get screen lock state + /*! + Returns true if the user is locked to the screen. + */ + bool isLockedToScreen() const; + + //@} + + // FIXME -- these probably belong on IScreen + virtual void enable(); + virtual void disable(); + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + +private: + CScreen* m_screen; + bool m_clipboardDirty[kClipboardEnd]; + SInt32 m_fakeInputCount; +}; + +#endif diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp new file mode 100644 index 00000000..3d4d32c2 --- /dev/null +++ b/lib/server/CServer.cpp @@ -0,0 +1,2176 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CServer.h" +#include "CClientProxy.h" +#include "CClientProxyUnknown.h" +#include "CPrimaryClient.h" +#include "IPlatformScreen.h" +#include "OptionTypes.h" +#include "ProtocolTypes.h" +#include "XScreen.h" +#include "XSynergy.h" +#include "IDataSocket.h" +#include "IListenSocket.h" +#include "XSocket.h" +#include "IEventQueue.h" +#include "CLog.h" +#include "TMethodEventJob.h" +#include "CArch.h" +#include + +// +// CServer +// + +CEvent::Type CServer::s_errorEvent = CEvent::kUnknown; +CEvent::Type CServer::s_connectedEvent = CEvent::kUnknown; +CEvent::Type CServer::s_disconnectedEvent = CEvent::kUnknown; +CEvent::Type CServer::s_switchToScreen = CEvent::kUnknown; +CEvent::Type CServer::s_switchInDirection = CEvent::kUnknown; +CEvent::Type CServer::s_keyboardBroadcast = CEvent::kUnknown; +CEvent::Type CServer::s_lockCursorToScreen = CEvent::kUnknown; + +CServer::CServer(const CConfig& config, CPrimaryClient* primaryClient) : + m_primaryClient(primaryClient), + m_active(primaryClient), + m_seqNum(0), + m_xDelta(0), + m_yDelta(0), + m_xDelta2(0), + m_yDelta2(0), + m_config(), + m_inputFilter(m_config.getInputFilter()), + m_activeSaver(NULL), + m_switchDir(kNoDirection), + m_switchScreen(NULL), + m_switchWaitDelay(0.0), + m_switchWaitTimer(NULL), + m_switchTwoTapDelay(0.0), + m_switchTwoTapEngaged(false), + m_switchTwoTapArmed(false), + m_switchTwoTapZone(3), + m_relativeMoves(false), + m_keyboardBroadcasting(false), + m_lockedToScreen(false) +{ + // must have a primary client and it must have a canonical name + assert(m_primaryClient != NULL); + assert(config.isScreen(primaryClient->getName())); + + CString primaryName = getName(primaryClient); + + // clear clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + CClipboardInfo& clipboard = m_clipboards[id]; + clipboard.m_clipboardOwner = primaryName; + clipboard.m_clipboardSeqNum = m_seqNum; + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + } + + // install event handlers + EVENTQUEUE->adoptHandler(CEvent::kTimer, this, + new TMethodEventJob(this, + &CServer::handleSwitchWaitTimeout)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyDownEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyDownEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyUpEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyUpEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getKeyRepeatEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyRepeatEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonDownEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleButtonDownEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getButtonUpEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleButtonUpEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnPrimaryEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleMotionPrimaryEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getMotionOnSecondaryEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleMotionSecondaryEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getWheelEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleWheelEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverActivatedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleScreensaverActivatedEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getScreensaverDeactivatedEvent(), + m_primaryClient->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleScreensaverDeactivatedEvent)); + EVENTQUEUE->adoptHandler(getSwitchToScreenEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleSwitchToScreenEvent)); + EVENTQUEUE->adoptHandler(getSwitchInDirectionEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleSwitchInDirectionEvent)); + EVENTQUEUE->adoptHandler(getKeyboardBroadcastEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleKeyboardBroadcastEvent)); + EVENTQUEUE->adoptHandler(getLockCursorToScreenEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleLockCursorToScreenEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputBeginEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleFakeInputBeginEvent)); + EVENTQUEUE->adoptHandler(IPlatformScreen::getFakeInputEndEvent(), + m_inputFilter, + new TMethodEventJob(this, + &CServer::handleFakeInputEndEvent)); + + // add connection + addClient(m_primaryClient); + + // set initial configuration + setConfig(config); + + // enable primary client + m_primaryClient->enable(); + m_inputFilter->setPrimaryClient(m_primaryClient); +} + +CServer::~CServer() +{ + // remove event handlers and timers + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyDownEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyUpEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getKeyRepeatEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonDownEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getButtonUpEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnPrimaryEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getMotionOnSecondaryEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getWheelEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverActivatedEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getScreensaverDeactivatedEvent(), + m_primaryClient->getEventTarget()); + EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputBeginEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(IPlatformScreen::getFakeInputEndEvent(), + m_inputFilter); + EVENTQUEUE->removeHandler(CEvent::kTimer, this); + stopSwitch(); + + // force immediate disconnection of secondary clients + disconnect(); + for (COldClients::iterator index = m_oldClients.begin(); + index != m_oldClients.begin(); ++index) { + CBaseClientProxy* client = index->first; + EVENTQUEUE->deleteTimer(index->second); + EVENTQUEUE->removeHandler(CEvent::kTimer, client); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + delete client; + } + + // remove input filter + m_inputFilter->setPrimaryClient(NULL); + + // disable and disconnect primary client + m_primaryClient->disable(); + removeClient(m_primaryClient); +} + +bool +CServer::setConfig(const CConfig& config) +{ + // refuse configuration if it doesn't include the primary screen + if (!config.isScreen(m_primaryClient->getName())) { + return false; + } + + // close clients that are connected but being dropped from the + // configuration. + closeClients(config); + + // cut over + m_config = config; + processOptions(); + + // add ScrollLock as a hotkey to lock to the screen. this was a + // built-in feature in earlier releases and is now supported via + // the user configurable hotkey mechanism. if the user has already + // registered ScrollLock for something else then that will win but + // we will unfortunately generate a warning. if the user has + // configured a CLockCursorToScreenAction then we don't add + // ScrollLock as a hotkey. + if (!m_config.hasLockToScreenAction()) { + IPlatformScreen::CKeyInfo* key = + IPlatformScreen::CKeyInfo::alloc(kKeyScrollLock, 0, 0, 0); + CInputFilter::CRule rule(new CInputFilter::CKeystrokeCondition(key)); + rule.adoptAction(new CInputFilter::CLockCursorToScreenAction, true); + m_inputFilter->addFilterRule(rule); + } + + // tell primary screen about reconfiguration + m_primaryClient->reconfigure(getActivePrimarySides()); + + // tell all (connected) clients about current options + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + sendOptions(client); + } + + return true; +} + +void +CServer::adoptClient(CBaseClientProxy* client) +{ + assert(client != NULL); + + // watch for client disconnection + EVENTQUEUE->adoptHandler(CClientProxy::getDisconnectedEvent(), client, + new TMethodEventJob(this, + &CServer::handleClientDisconnected, client)); + + // name must be in our configuration + if (!m_config.isScreen(client->getName())) { + LOG((CLOG_WARN "a client with name \"%s\" is not in the map", client->getName().c_str())); + closeClient(client, kMsgEUnknown); + return; + } + + // add client to client list + if (!addClient(client)) { + // can only have one screen with a given name at any given time + LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str())); + closeClient(client, kMsgEBusy); + return; + } + LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str())); + + // send configuration options to client + sendOptions(client); + + // activate screen saver on new client if active on the primary screen + if (m_activeSaver != NULL) { + client->screensaver(true); + } + + // send notification + CServer::CScreenConnectedInfo* info = + CServer::CScreenConnectedInfo::alloc(getName(client)); + EVENTQUEUE->addEvent(CEvent(CServer::getConnectedEvent(), + m_primaryClient->getEventTarget(), info)); +} + +void +CServer::disconnect() +{ + // close all secondary clients + if (m_clients.size() > 1 || !m_oldClients.empty()) { + CConfig emptyConfig; + closeClients(emptyConfig); + } + else { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } +} + +UInt32 +CServer::getNumClients() const +{ + return m_clients.size(); +} + +void +CServer::getClients(std::vector& list) const +{ + list.clear(); + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + list.push_back(index->first); + } +} + +CEvent::Type +CServer::getErrorEvent() +{ + return CEvent::registerTypeOnce(s_errorEvent, + "CServer::error"); +} + +CEvent::Type +CServer::getConnectedEvent() +{ + return CEvent::registerTypeOnce(s_connectedEvent, + "CServer::connected"); +} + +CEvent::Type +CServer::getDisconnectedEvent() +{ + return CEvent::registerTypeOnce(s_disconnectedEvent, + "CServer::disconnected"); +} + +CEvent::Type +CServer::getSwitchToScreenEvent() +{ + return CEvent::registerTypeOnce(s_switchToScreen, + "CServer::switchToScreen"); +} + +CEvent::Type +CServer::getSwitchInDirectionEvent() +{ + return CEvent::registerTypeOnce(s_switchInDirection, + "CServer::switchInDirection"); +} + +CEvent::Type +CServer::getKeyboardBroadcastEvent() +{ + return CEvent::registerTypeOnce(s_keyboardBroadcast, + "CServer:keyboardBroadcast"); +} + +CEvent::Type +CServer::getLockCursorToScreenEvent() +{ + return CEvent::registerTypeOnce(s_lockCursorToScreen, + "CServer::lockCursorToScreen"); +} + +CString +CServer::getName(const CBaseClientProxy* client) const +{ + CString name = m_config.getCanonicalName(client->getName()); + if (name.empty()) { + name = client->getName(); + } + return name; +} + +UInt32 +CServer::getActivePrimarySides() const +{ + UInt32 sides = 0; + if (!isLockedToScreenServer()) { + if (hasAnyNeighbor(m_primaryClient, kLeft)) { + sides |= kLeftMask; + } + if (hasAnyNeighbor(m_primaryClient, kRight)) { + sides |= kRightMask; + } + if (hasAnyNeighbor(m_primaryClient, kTop)) { + sides |= kTopMask; + } + if (hasAnyNeighbor(m_primaryClient, kBottom)) { + sides |= kBottomMask; + } + } + return sides; +} + +bool +CServer::isLockedToScreenServer() const +{ + // locked if scroll-lock is toggled on + return m_lockedToScreen; +} + +bool +CServer::isLockedToScreen() const +{ + // locked if we say we're locked + if (isLockedToScreenServer()) { + LOG((CLOG_DEBUG "locked to screen")); + return true; + } + + // locked if primary says we're locked + if (m_primaryClient->isLockedToScreen()) { + return true; + } + + // not locked + return false; +} + +SInt32 +CServer::getJumpZoneSize(CBaseClientProxy* client) const +{ + if (client == m_primaryClient) { + return m_primaryClient->getJumpZoneSize(); + } + else { + return 0; + } +} + +void +CServer::switchScreen(CBaseClientProxy* dst, + SInt32 x, SInt32 y, bool forScreensaver) +{ + assert(dst != NULL); +#ifndef NDEBUG + { + SInt32 dx, dy, dw, dh; + dst->getShape(dx, dy, dw, dh); + assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh); + } +#endif + assert(m_active != NULL); + + LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y)); + + // stop waiting to switch + stopSwitch(); + + // record new position + m_x = x; + m_y = y; + m_xDelta = 0; + m_yDelta = 0; + m_xDelta2 = 0; + m_yDelta2 = 0; + + // wrapping means leaving the active screen and entering it again. + // since that's a waste of time we skip that and just warp the + // mouse. + if (m_active != dst) { + // leave active screen + if (!m_active->leave()) { + // cannot leave screen + LOG((CLOG_WARN "can't leave screen")); + return; + } + + // update the primary client's clipboards if we're leaving the + // primary screen. + if (m_active == m_primaryClient) { + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + CClipboardInfo& clipboard = m_clipboards[id]; + if (clipboard.m_clipboardOwner == getName(m_primaryClient)) { + onClipboardChanged(m_primaryClient, + id, clipboard.m_clipboardSeqNum); + } + } + } + + // cut over + m_active = dst; + + // increment enter sequence number + ++m_seqNum; + + // enter new screen + m_active->enter(x, y, m_seqNum, + m_primaryClient->getToggleMask(), + forScreensaver); + + // send the clipboard data to new active screen + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_active->setClipboard(id, &m_clipboards[id].m_clipboard); + } + } + else { + m_active->mouseMove(x, y); + } +} + +void +CServer::jumpToScreen(CBaseClientProxy* newScreen) +{ + assert(newScreen != NULL); + + // record the current cursor position on the active screen + m_active->setJumpCursorPos(m_x, m_y); + + // get the last cursor position on the target screen + SInt32 x, y; + newScreen->getJumpCursorPos(x, y); + + switchScreen(newScreen, x, y, false); +} + +float +CServer::mapToFraction(CBaseClientProxy* client, + EDirection dir, SInt32 x, SInt32 y) const +{ + SInt32 sx, sy, sw, sh; + client->getShape(sx, sy, sw, sh); + switch (dir) { + case kLeft: + case kRight: + return static_cast(y - sy + 0.5f) / static_cast(sh); + + case kTop: + case kBottom: + return static_cast(x - sx + 0.5f) / static_cast(sw); + + case kNoDirection: + assert(0 && "bad direction"); + break; + } + return 0.0f; +} + +void +CServer::mapToPixel(CBaseClientProxy* client, + EDirection dir, float f, SInt32& x, SInt32& y) const +{ + SInt32 sx, sy, sw, sh; + client->getShape(sx, sy, sw, sh); + switch (dir) { + case kLeft: + case kRight: + y = static_cast(f * sh) + sy; + break; + + case kTop: + case kBottom: + x = static_cast(f * sw) + sx; + break; + + case kNoDirection: + assert(0 && "bad direction"); + break; + } +} + +bool +CServer::hasAnyNeighbor(CBaseClientProxy* client, EDirection dir) const +{ + assert(client != NULL); + + return m_config.hasNeighbor(getName(client), dir); +} + +CBaseClientProxy* +CServer::getNeighbor(CBaseClientProxy* src, + EDirection dir, SInt32& x, SInt32& y) const +{ + // note -- must be locked on entry + + assert(src != NULL); + + // get source screen name + CString srcName = getName(src); + assert(!srcName.empty()); + LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str())); + + // convert position to fraction + float t = mapToFraction(src, dir, x, y); + + // search for the closest neighbor that exists in direction dir + float tTmp; + for (;;) { + CString dstName(m_config.getNeighbor(srcName, dir, t, &tTmp)); + + // if nothing in that direction then return NULL. if the + // destination is the source then we can make no more + // progress in this direction. since we haven't found a + // connected neighbor we return NULL. + if (dstName.empty()) { + LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", CConfig::dirName(dir), srcName.c_str())); + return NULL; + } + + // look up neighbor cell. if the screen is connected and + // ready then we can stop. + CClientList::const_iterator index = m_clients.find(dstName); + if (index != m_clients.end()) { + LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), CConfig::dirName(dir), srcName.c_str(), t)); + mapToPixel(index->second, dir, tTmp, x, y); + return index->second; + } + + // skip over unconnected screen + LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), CConfig::dirName(dir), srcName.c_str())); + srcName = dstName; + + // use position on skipped screen + t = tTmp; + } +} + +CBaseClientProxy* +CServer::mapToNeighbor(CBaseClientProxy* src, + EDirection srcSide, SInt32& x, SInt32& y) const +{ + // note -- must be locked on entry + + assert(src != NULL); + + // get the first neighbor + CBaseClientProxy* dst = getNeighbor(src, srcSide, x, y); + if (dst == NULL) { + return NULL; + } + + // get the source screen's size + SInt32 dx, dy, dw, dh; + CBaseClientProxy* lastGoodScreen = src; + lastGoodScreen->getShape(dx, dy, dw, dh); + + // find destination screen, adjusting x or y (but not both). the + // searches are done in a sort of canonical screen space where + // the upper-left corner is 0,0 for each screen. we adjust from + // actual to canonical position on entry to and from canonical to + // actual on exit from the search. + switch (srcSide) { + case kLeft: + x -= dx; + while (dst != NULL) { + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + x += dw; + if (x >= 0) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + x += dx; + break; + + case kRight: + x -= dx; + while (dst != NULL) { + x -= dw; + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + if (x < dw) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + x += dx; + break; + + case kTop: + y -= dy; + while (dst != NULL) { + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + y += dh; + if (y >= 0) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + y += dy; + break; + + case kBottom: + y -= dy; + while (dst != NULL) { + y -= dh; + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + if (y < dh) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + y += dy; + break; + + case kNoDirection: + assert(0 && "bad direction"); + return NULL; + } + + // save destination screen + assert(lastGoodScreen != NULL); + dst = lastGoodScreen; + + // if entering primary screen then be sure to move in far enough + // to avoid the jump zone. if entering a side that doesn't have + // a neighbor (i.e. an asymmetrical side) then we don't need to + // move inwards because that side can't provoke a jump. + avoidJumpZone(dst, srcSide, x, y); + + return dst; +} + +void +CServer::avoidJumpZone(CBaseClientProxy* dst, + EDirection dir, SInt32& x, SInt32& y) const +{ + // we only need to avoid jump zones on the primary screen + if (dst != m_primaryClient) { + return; + } + + const CString dstName(getName(dst)); + SInt32 dx, dy, dw, dh; + dst->getShape(dx, dy, dw, dh); + float t = mapToFraction(dst, dir, x, y); + SInt32 z = getJumpZoneSize(dst); + + // move in far enough to avoid the jump zone. if entering a side + // that doesn't have a neighbor (i.e. an asymmetrical side) then we + // don't need to move inwards because that side can't provoke a jump. + switch (dir) { + case kLeft: + if (!m_config.getNeighbor(dstName, kRight, t, NULL).empty() && + x > dx + dw - 1 - z) + x = dx + dw - 1 - z; + break; + + case kRight: + if (!m_config.getNeighbor(dstName, kLeft, t, NULL).empty() && + x < dx + z) + x = dx + z; + break; + + case kTop: + if (!m_config.getNeighbor(dstName, kBottom, t, NULL).empty() && + y > dy + dh - 1 - z) + y = dy + dh - 1 - z; + break; + + case kBottom: + if (!m_config.getNeighbor(dstName, kTop, t, NULL).empty() && + y < dy + z) + y = dy + z; + break; + + case kNoDirection: + assert(0 && "bad direction"); + } +} + +bool +CServer::isSwitchOkay(CBaseClientProxy* newScreen, + EDirection dir, SInt32 x, SInt32 y, + SInt32 xActive, SInt32 yActive) +{ + LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), CConfig::dirName(dir))); + + // is there a neighbor? + if (newScreen == NULL) { + // there's no neighbor. we don't want to switch and we don't + // want to try to switch later. + LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir))); + stopSwitch(); + return false; + } + + // should we switch or not? + bool preventSwitch = false; + bool allowSwitch = false; + + // note if the switch direction has changed. save the new + // direction and screen if so. + bool isNewDirection = (dir != m_switchDir); + if (isNewDirection || m_switchScreen == NULL) { + m_switchDir = dir; + m_switchScreen = newScreen; + } + + // is this a double tap and do we care? + if (!allowSwitch && m_switchTwoTapDelay > 0.0) { + if (isNewDirection || + !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) { + // tapping a different or new edge or second tap not + // fast enough. prepare for second tap. + preventSwitch = true; + startSwitchTwoTap(); + } + else { + // got second tap + allowSwitch = true; + } + } + + // if waiting before a switch then prepare to switch later + if (!allowSwitch && m_switchWaitDelay > 0.0) { + if (isNewDirection || !isSwitchWaitStarted()) { + startSwitchWait(x, y); + } + preventSwitch = true; + } + + // are we in a locked corner? first check if screen has the option set + // and, if not, check the global options. + const CConfig::CScreenOptions* options = + m_config.getOptions(getName(m_active)); + if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) { + options = m_config.getOptions(""); + } + if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) { + // get corner mask and size + CConfig::CScreenOptions::const_iterator i = + options->find(kOptionScreenSwitchCorners); + UInt32 corners = static_cast(i->second); + i = options->find(kOptionScreenSwitchCornerSize); + SInt32 size = 0; + if (i != options->end()) { + size = i->second; + } + + // see if we're in a locked corner + if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) { + // yep, no switching + LOG((CLOG_DEBUG1 "locked in corner")); + preventSwitch = true; + stopSwitch(); + } + } + + // ignore if mouse is locked to screen and don't try to switch later + if (!preventSwitch && isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + preventSwitch = true; + stopSwitch(); + } + + return !preventSwitch; +} + +void +CServer::noSwitch(SInt32 x, SInt32 y) +{ + armSwitchTwoTap(x, y); + stopSwitchWait(); +} + +void +CServer::stopSwitch() +{ + if (m_switchScreen != NULL) { + m_switchScreen = NULL; + m_switchDir = kNoDirection; + stopSwitchTwoTap(); + stopSwitchWait(); + } +} + +void +CServer::startSwitchTwoTap() +{ + m_switchTwoTapEngaged = true; + m_switchTwoTapArmed = false; + m_switchTwoTapTimer.reset(); + LOG((CLOG_DEBUG1 "waiting for second tap")); +} + +void +CServer::armSwitchTwoTap(SInt32 x, SInt32 y) +{ + if (m_switchTwoTapEngaged) { + if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) { + // second tap took too long. disengage. + stopSwitchTwoTap(); + } + else if (!m_switchTwoTapArmed) { + // still time for a double tap. see if we left the tap + // zone and, if so, arm the two tap. + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 tapZone = m_primaryClient->getJumpZoneSize(); + if (tapZone < m_switchTwoTapZone) { + tapZone = m_switchTwoTapZone; + } + if (x >= ax + tapZone && x < ax + aw - tapZone && + y >= ay + tapZone && y < ay + ah - tapZone) { + // win32 can generate bogus mouse events that appear to + // move in the opposite direction that the mouse actually + // moved. try to ignore that crap here. + switch (m_switchDir) { + case kLeft: + m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0); + break; + + case kRight: + m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0); + break; + + case kTop: + m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0); + break; + + case kBottom: + m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0); + break; + + default: + break; + } + } + } + } +} + +void +CServer::stopSwitchTwoTap() +{ + m_switchTwoTapEngaged = false; + m_switchTwoTapArmed = false; +} + +bool +CServer::isSwitchTwoTapStarted() const +{ + return m_switchTwoTapEngaged; +} + +bool +CServer::shouldSwitchTwoTap() const +{ + // this is the second tap if two-tap is armed and this tap + // came fast enough + return (m_switchTwoTapArmed && + m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay); +} + +void +CServer::startSwitchWait(SInt32 x, SInt32 y) +{ + stopSwitchWait(); + m_switchWaitX = x; + m_switchWaitY = y; + m_switchWaitTimer = EVENTQUEUE->newOneShotTimer(m_switchWaitDelay, this); + LOG((CLOG_DEBUG1 "waiting to switch")); +} + +void +CServer::stopSwitchWait() +{ + if (m_switchWaitTimer != NULL) { + EVENTQUEUE->deleteTimer(m_switchWaitTimer); + m_switchWaitTimer = NULL; + } +} + +bool +CServer::isSwitchWaitStarted() const +{ + return (m_switchWaitTimer != NULL); +} + +UInt32 +CServer::getCorner(CBaseClientProxy* client, + SInt32 x, SInt32 y, SInt32 size) const +{ + assert(client != NULL); + + // get client screen shape + SInt32 ax, ay, aw, ah; + client->getShape(ax, ay, aw, ah); + + // check for x,y on the left or right + SInt32 xSide; + if (x <= ax) { + xSide = -1; + } + else if (x >= ax + aw - 1) { + xSide = 1; + } + else { + xSide = 0; + } + + // check for x,y on the top or bottom + SInt32 ySide; + if (y <= ay) { + ySide = -1; + } + else if (y >= ay + ah - 1) { + ySide = 1; + } + else { + ySide = 0; + } + + // if against the left or right then check if y is within size + if (xSide != 0) { + if (y < ay + size) { + return (xSide < 0) ? kTopLeftMask : kTopRightMask; + } + else if (y >= ay + ah - size) { + return (xSide < 0) ? kBottomLeftMask : kBottomRightMask; + } + } + + // if against the left or right then check if y is within size + if (ySide != 0) { + if (x < ax + size) { + return (ySide < 0) ? kTopLeftMask : kBottomLeftMask; + } + else if (x >= ax + aw - size) { + return (ySide < 0) ? kTopRightMask : kBottomRightMask; + } + } + + return kNoCornerMask; +} + +void +CServer::stopRelativeMoves() +{ + if (m_relativeMoves && m_active != m_primaryClient) { + // warp to the center of the active client so we know where we are + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + m_x = ax + (aw >> 1); + m_y = ay + (ah >> 1); + m_xDelta = 0; + m_yDelta = 0; + m_xDelta2 = 0; + m_yDelta2 = 0; + LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } +} + +void +CServer::sendOptions(CBaseClientProxy* client) const +{ + COptionsList optionsList; + + // look up options for client + const CConfig::CScreenOptions* options = + m_config.getOptions(getName(client)); + if (options != NULL) { + // convert options to a more convenient form for sending + optionsList.reserve(2 * options->size()); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast(index->second)); + } + } + + // look up global options + options = m_config.getOptions(""); + if (options != NULL) { + // convert options to a more convenient form for sending + optionsList.reserve(optionsList.size() + 2 * options->size()); + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast(index->second)); + } + } + + // send the options + client->resetOptions(); + client->setOptions(optionsList); +} + +void +CServer::processOptions() +{ + const CConfig::CScreenOptions* options = m_config.getOptions(""); + if (options == NULL) { + return; + } + + bool newRelativeMoves = m_relativeMoves; + for (CConfig::CScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + m_switchWaitDelay = 1.0e-3 * static_cast(value); + if (m_switchWaitDelay < 0.0) { + m_switchWaitDelay = 0.0; + } + stopSwitchWait(); + } + else if (id == kOptionScreenSwitchTwoTap) { + m_switchTwoTapDelay = 1.0e-3 * static_cast(value); + if (m_switchTwoTapDelay < 0.0) { + m_switchTwoTapDelay = 0.0; + } + stopSwitchTwoTap(); + } + else if (id == kOptionRelativeMouseMoves) { + newRelativeMoves = (value != 0); + } + } + + if (m_relativeMoves && !newRelativeMoves) { + stopRelativeMoves(); + } + m_relativeMoves = newRelativeMoves; +} + +void +CServer::handleShapeChanged(const CEvent&, void* vclient) +{ + // ignore events from unknown clients + CBaseClientProxy* client = reinterpret_cast(vclient); + if (m_clientSet.count(client) == 0) { + return; + } + + LOG((CLOG_INFO "screen \"%s\" shape changed", getName(client).c_str())); + + // update jump coordinate + SInt32 x, y; + client->getCursorPos(x, y); + client->setJumpCursorPos(x, y); + + // update the mouse coordinates + if (client == m_active) { + m_x = x; + m_y = y; + } + + // handle resolution change to primary screen + if (client == m_primaryClient) { + if (client == m_active) { + onMouseMovePrimary(m_x, m_y); + } + else { + onMouseMoveSecondary(0, 0); + } + } +} + +void +CServer::handleClipboardGrabbed(const CEvent& event, void* vclient) +{ + // ignore events from unknown clients + CBaseClientProxy* grabber = reinterpret_cast(vclient); + if (m_clientSet.count(grabber) == 0) { + return; + } + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + + // ignore grab if sequence number is old. always allow primary + // screen to grab. + CClipboardInfo& clipboard = m_clipboards[info->m_id]; + if (grabber != m_primaryClient && + info->m_sequenceNumber < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id)); + return; + } + + // mark screen as owning clipboard + LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str())); + clipboard.m_clipboardOwner = getName(grabber); + clipboard.m_clipboardSeqNum = info->m_sequenceNumber; + + // clear the clipboard data (since it's not known at this point) + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + + // tell all other screens to take ownership of clipboard. tell the + // grabber that it's clipboard isn't dirty. + for (CClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + if (client == grabber) { + client->setClipboardDirty(info->m_id, false); + } + else { + client->grabClipboard(info->m_id); + } + } +} + +void +CServer::handleClipboardChanged(const CEvent& event, void* vclient) +{ + // ignore events from unknown clients + CBaseClientProxy* sender = reinterpret_cast(vclient); + if (m_clientSet.count(sender) == 0) { + return; + } + const IScreen::CClipboardInfo* info = + reinterpret_cast(event.getData()); + onClipboardChanged(sender, info->m_id, info->m_sequenceNumber); +} + +void +CServer::handleKeyDownEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens); +} + +void +CServer::handleKeyUpEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens); +} + +void +CServer::handleKeyRepeatEvent(const CEvent& event, void*) +{ + IPlatformScreen::CKeyInfo* info = + reinterpret_cast(event.getData()); + onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button); +} + +void +CServer::handleButtonDownEvent(const CEvent& event, void*) +{ + IPlatformScreen::CButtonInfo* info = + reinterpret_cast(event.getData()); + onMouseDown(info->m_button); +} + +void +CServer::handleButtonUpEvent(const CEvent& event, void*) +{ + IPlatformScreen::CButtonInfo* info = + reinterpret_cast(event.getData()); + onMouseUp(info->m_button); +} + +void +CServer::handleMotionPrimaryEvent(const CEvent& event, void*) +{ + IPlatformScreen::CMotionInfo* info = + reinterpret_cast(event.getData()); + onMouseMovePrimary(info->m_x, info->m_y); +} + +void +CServer::handleMotionSecondaryEvent(const CEvent& event, void*) +{ + IPlatformScreen::CMotionInfo* info = + reinterpret_cast(event.getData()); + onMouseMoveSecondary(info->m_x, info->m_y); +} + +void +CServer::handleWheelEvent(const CEvent& event, void*) +{ + IPlatformScreen::CWheelInfo* info = + reinterpret_cast(event.getData()); + onMouseWheel(info->m_xDelta, info->m_yDelta); +} + +void +CServer::handleScreensaverActivatedEvent(const CEvent&, void*) +{ + onScreensaver(true); +} + +void +CServer::handleScreensaverDeactivatedEvent(const CEvent&, void*) +{ + onScreensaver(false); +} + +void +CServer::handleSwitchWaitTimeout(const CEvent&, void*) +{ + // ignore if mouse is locked to screen + if (isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + stopSwitch(); + return; + } + + // switch screen + switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false); +} + +void +CServer::handleClientDisconnected(const CEvent&, void* vclient) +{ + // client has disconnected. it might be an old client or an + // active client. we don't care so just handle it both ways. + CBaseClientProxy* client = reinterpret_cast(vclient); + removeActiveClient(client); + removeOldClient(client); + delete client; +} + +void +CServer::handleClientCloseTimeout(const CEvent&, void* vclient) +{ + // client took too long to disconnect. just dump it. + CBaseClientProxy* client = reinterpret_cast(vclient); + LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str())); + removeOldClient(client); + delete client; +} + +void +CServer::handleSwitchToScreenEvent(const CEvent& event, void*) +{ + CSwitchToScreenInfo* info = + reinterpret_cast(event.getData()); + + CClientList::const_iterator index = m_clients.find(info->m_screen); + if (index == m_clients.end()) { + LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen)); + } + else { + jumpToScreen(index->second); + } +} + +void +CServer::handleSwitchInDirectionEvent(const CEvent& event, void*) +{ + CSwitchInDirectionInfo* info = + reinterpret_cast(event.getData()); + + // jump to screen in chosen direction from center of this screen + SInt32 x = m_x, y = m_y; + CBaseClientProxy* newScreen = + getNeighbor(m_active, info->m_direction, x, y); + if (newScreen == NULL) { + LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(info->m_direction))); + } + else { + jumpToScreen(newScreen); + } +} + +void +CServer::handleKeyboardBroadcastEvent(const CEvent& event, void*) +{ + CKeyboardBroadcastInfo* info = (CKeyboardBroadcastInfo*)event.getData(); + + // choose new state + bool newState; + switch (info->m_state) { + case CKeyboardBroadcastInfo::kOff: + newState = false; + break; + + default: + case CKeyboardBroadcastInfo::kOn: + newState = true; + break; + + case CKeyboardBroadcastInfo::kToggle: + newState = !m_keyboardBroadcasting; + break; + } + + // enter new state + if (newState != m_keyboardBroadcasting || + info->m_screens != m_keyboardBroadcastingScreens) { + m_keyboardBroadcasting = newState; + m_keyboardBroadcastingScreens = info->m_screens; + LOG((CLOG_DEBUG "keyboard broadcasting %s: %s", m_keyboardBroadcasting ? "on" : "off", m_keyboardBroadcastingScreens.c_str())); + } +} + +void +CServer::handleLockCursorToScreenEvent(const CEvent& event, void*) +{ + CLockCursorToScreenInfo* info = (CLockCursorToScreenInfo*)event.getData(); + + // choose new state + bool newState; + switch (info->m_state) { + case CLockCursorToScreenInfo::kOff: + newState = false; + break; + + default: + case CLockCursorToScreenInfo::kOn: + newState = true; + break; + + case CLockCursorToScreenInfo::kToggle: + newState = !m_lockedToScreen; + break; + } + + // enter new state + if (newState != m_lockedToScreen) { + m_lockedToScreen = newState; + LOG((CLOG_DEBUG "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from")); + + m_primaryClient->reconfigure(getActivePrimarySides()); + if (!isLockedToScreenServer()) { + stopRelativeMoves(); + } + } +} + +void +CServer::handleFakeInputBeginEvent(const CEvent&, void*) +{ + m_primaryClient->fakeInputBegin(); +} + +void +CServer::handleFakeInputEndEvent(const CEvent&, void*) +{ + m_primaryClient->fakeInputEnd(); +} + +void +CServer::onClipboardChanged(CBaseClientProxy* sender, + ClipboardID id, UInt32 seqNum) +{ + CClipboardInfo& clipboard = m_clipboards[id]; + + // ignore update if sequence number is old + if (seqNum < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id)); + return; + } + + // should be the expected client + assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second); + + // get data + sender->getClipboard(id, &clipboard.m_clipboard); + + // ignore if data hasn't changed + CString data = clipboard.m_clipboard.marshall(); + if (data == clipboard.m_clipboardData) { + LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id)); + return; + } + + // got new data + LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id)); + clipboard.m_clipboardData = data; + + // tell all clients except the sender that the clipboard is dirty + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + client->setClipboardDirty(id, client != sender); + } + + // send the new clipboard to the active screen + m_active->setClipboard(id, &clipboard.m_clipboard); +} + +void +CServer::onScreensaver(bool activated) +{ + LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); + + if (activated) { + // save current screen and position + m_activeSaver = m_active; + m_xSaver = m_x; + m_ySaver = m_y; + + // jump to primary screen + if (m_active != m_primaryClient) { + switchScreen(m_primaryClient, 0, 0, true); + } + } + else { + // jump back to previous screen and position. we must check + // that the position is still valid since the screen may have + // changed resolutions while the screen saver was running. + if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { + // check position + CBaseClientProxy* screen = m_activeSaver; + SInt32 x, y, w, h; + screen->getShape(x, y, w, h); + SInt32 zoneSize = getJumpZoneSize(screen); + if (m_xSaver < x + zoneSize) { + m_xSaver = x + zoneSize; + } + else if (m_xSaver >= x + w - zoneSize) { + m_xSaver = x + w - zoneSize - 1; + } + if (m_ySaver < y + zoneSize) { + m_ySaver = y + zoneSize; + } + else if (m_ySaver >= y + h - zoneSize) { + m_ySaver = y + h - zoneSize - 1; + } + + // jump + switchScreen(screen, m_xSaver, m_ySaver, false); + } + + // reset state + m_activeSaver = NULL; + } + + // send message to all clients + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + CBaseClientProxy* client = index->second; + client->screensaver(activated); + } +} + +void +CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, + const char* screens) +{ + LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // relay + if (!m_keyboardBroadcasting || + (screens && IKeyState::CKeyInfo::isDefault(screens))) { + m_active->keyDown(id, mask, button); + } + else { + if (!screens && m_keyboardBroadcasting) { + screens = m_keyboardBroadcastingScreens.c_str(); + if (IKeyState::CKeyInfo::isDefault(screens)) { + screens = "*"; + } + } + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (IKeyState::CKeyInfo::contains(screens, index->first)) { + index->second->keyDown(id, mask, button); + } + } + } +} + +void +CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, + const char* screens) +{ + LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // relay + if (!m_keyboardBroadcasting || + (screens && IKeyState::CKeyInfo::isDefault(screens))) { + m_active->keyUp(id, mask, button); + } + else { + if (!screens && m_keyboardBroadcasting) { + screens = m_keyboardBroadcastingScreens.c_str(); + if (IKeyState::CKeyInfo::isDefault(screens)) { + screens = "*"; + } + } + for (CClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (IKeyState::CKeyInfo::contains(screens, index->first)) { + index->second->keyUp(id, mask, button); + } + } + } +} + +void +CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); + assert(m_active != NULL); + + // relay + m_active->keyRepeat(id, mask, count, button); +} + +void +CServer::onMouseDown(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseDown id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseDown(id); +} + +void +CServer::onMouseUp(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseUp id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseUp(id); +} + +bool +CServer::onMouseMovePrimary(SInt32 x, SInt32 y) +{ + LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y)); + + // mouse move on primary (server's) screen + if (m_active != m_primaryClient) { + // stale event -- we're actually on a secondary screen + return false; + } + + // save last delta + m_xDelta2 = m_xDelta; + m_yDelta2 = m_yDelta; + + // save current delta + m_xDelta = x - m_x; + m_yDelta = y - m_y; + + // save position + m_x = x; + m_y = y; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 zoneSize = getJumpZoneSize(m_active); + + // clamp position to screen + SInt32 xc = x, yc = y; + if (xc < ax + zoneSize) { + xc = ax; + } + else if (xc >= ax + aw - zoneSize) { + xc = ax + aw - 1; + } + if (yc < ay + zoneSize) { + yc = ay; + } + else if (yc >= ay + ah - zoneSize) { + yc = ay + ah - 1; + } + + // see if we should change screens + EDirection dir; + if (x < ax + zoneSize) { + x -= zoneSize; + dir = kLeft; + } + else if (x >= ax + aw - zoneSize) { + x += zoneSize; + dir = kRight; + } + else if (y < ay + zoneSize) { + y -= zoneSize; + dir = kTop; + } + else if (y >= ay + ah - zoneSize) { + y += zoneSize; + dir = kBottom; + } + else { + // still on local screen + noSwitch(x, y); + return false; + } + + // get jump destination + CBaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y); + + // should we switch or not? + if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { + // switch screen + switchScreen(newScreen, x, y, false); + return true; + } + else { + return false; + } +} + +void +CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy) +{ + LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy)); + + // mouse move on secondary (client's) screen + assert(m_active != NULL); + if (m_active == m_primaryClient) { + // stale event -- we're actually on the primary screen + return; + } + + // if doing relative motion on secondary screens and we're locked + // to the screen (which activates relative moves) then send a + // relative mouse motion. when we're doing this we pretend as if + // the mouse isn't actually moving because we're expecting some + // program on the secondary screen to warp the mouse on us, so we + // have no idea where it really is. + if (m_relativeMoves && isLockedToScreenServer()) { + LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy)); + m_active->mouseRelativeMove(dx, dy); + return; + } + + // save old position + const SInt32 xOld = m_x; + const SInt32 yOld = m_y; + + // save last delta + m_xDelta2 = m_xDelta; + m_yDelta2 = m_yDelta; + + // save current delta + m_xDelta = dx; + m_yDelta = dy; + + // accumulate motion + m_x += dx; + m_y += dy; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + + // find direction of neighbor and get the neighbor + bool jump = true; + CBaseClientProxy* newScreen; + do { + // clamp position to screen + SInt32 xc = m_x, yc = m_y; + if (xc < ax) { + xc = ax; + } + else if (xc >= ax + aw) { + xc = ax + aw - 1; + } + if (yc < ay) { + yc = ay; + } + else if (yc >= ay + ah) { + yc = ay + ah - 1; + } + + EDirection dir; + if (m_x < ax) { + dir = kLeft; + } + else if (m_x > ax + aw - 1) { + dir = kRight; + } + else if (m_y < ay) { + dir = kTop; + } + else if (m_y > ay + ah - 1) { + dir = kBottom; + } + else { + // we haven't left the screen + newScreen = m_active; + jump = false; + + // if waiting and mouse is not on the border we're waiting + // on then stop waiting. also if it's not on the border + // then arm the double tap. + if (m_switchScreen != NULL) { + bool clearWait; + SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); + switch (m_switchDir) { + case kLeft: + clearWait = (m_x >= ax + zoneSize); + break; + + case kRight: + clearWait = (m_x <= ax + aw - 1 - zoneSize); + break; + + case kTop: + clearWait = (m_y >= ay + zoneSize); + break; + + case kBottom: + clearWait = (m_y <= ay + ah - 1 + zoneSize); + break; + + default: + clearWait = false; + break; + } + if (clearWait) { + // still on local screen + noSwitch(m_x, m_y); + } + } + + // skip rest of block + break; + } + + // try to switch screen. get the neighbor. + newScreen = mapToNeighbor(m_active, dir, m_x, m_y); + + // see if we should switch + if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) { + newScreen = m_active; + jump = false; + } + } while (false); + + if (jump) { + // switch screens + switchScreen(newScreen, m_x, m_y, false); + } + else { + // same screen. clamp mouse to edge. + m_x = xOld + dx; + m_y = yOld + dy; + if (m_x < ax) { + m_x = ax; + LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str())); + } + else if (m_x > ax + aw - 1) { + m_x = ax + aw - 1; + LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str())); + } + if (m_y < ay) { + m_y = ay; + LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str())); + } + else if (m_y > ay + ah - 1) { + m_y = ay + ah - 1; + LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str())); + } + + // warp cursor if it moved. + if (m_x != xOld || m_y != yOld) { + LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } + } +} + +void +CServer::onMouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta)); + assert(m_active != NULL); + + // relay + m_active->mouseWheel(xDelta, yDelta); +} + +bool +CServer::addClient(CBaseClientProxy* client) +{ + CString name = getName(client); + if (m_clients.count(name) != 0) { + return false; + } + + // add event handlers + EVENTQUEUE->adoptHandler(IScreen::getShapeChangedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleShapeChanged, client)); + EVENTQUEUE->adoptHandler(IScreen::getClipboardGrabbedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleClipboardGrabbed, client)); + EVENTQUEUE->adoptHandler(CClientProxy::getClipboardChangedEvent(), + client->getEventTarget(), + new TMethodEventJob(this, + &CServer::handleClipboardChanged, client)); + + // add to list + m_clientSet.insert(client); + m_clients.insert(std::make_pair(name, client)); + + // initialize client data + SInt32 x, y; + client->getCursorPos(x, y); + client->setJumpCursorPos(x, y); + + // tell primary client about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); + + return true; +} + +bool +CServer::removeClient(CBaseClientProxy* client) +{ + // return false if not in list + CClientSet::iterator i = m_clientSet.find(client); + if (i == m_clientSet.end()) { + return false; + } + + // remove event handlers + EVENTQUEUE->removeHandler(IScreen::getShapeChangedEvent(), + client->getEventTarget()); + EVENTQUEUE->removeHandler(IScreen::getClipboardGrabbedEvent(), + client->getEventTarget()); + EVENTQUEUE->removeHandler(CClientProxy::getClipboardChangedEvent(), + client->getEventTarget()); + + // remove from list + m_clients.erase(getName(client)); + m_clientSet.erase(i); + + return true; +} + +void +CServer::closeClient(CBaseClientProxy* client, const char* msg) +{ + assert(client != m_primaryClient); + assert(msg != NULL); + + // send message to client. this message should cause the client + // to disconnect. we add this client to the closed client list + // and install a timer to remove the client if it doesn't respond + // quickly enough. we also remove the client from the active + // client list since we're not going to listen to it anymore. + // note that this method also works on clients that are not in + // the m_clients list. adoptClient() may call us with such a + // client. + LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str())); + + // send message + // FIXME -- avoid type cast (kinda hard, though) + ((CClientProxy*)client)->close(msg); + + // install timer. wait timeout seconds for client to close. + double timeout = 5.0; + CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, timer, + new TMethodEventJob(this, + &CServer::handleClientCloseTimeout, client)); + + // move client to closing list + removeClient(client); + m_oldClients.insert(std::make_pair(client, timer)); + + // if this client is the active screen then we have to + // jump off of it + forceLeaveClient(client); +} + +void +CServer::closeClients(const CConfig& config) +{ + // collect the clients that are connected but are being dropped + // from the configuration (or who's canonical name is changing). + typedef std::set CRemovedClients; + CRemovedClients removed; + for (CClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (!config.isCanonicalName(index->first)) { + removed.insert(index->second); + } + } + + // don't close the primary client + removed.erase(m_primaryClient); + + // now close them. we collect the list then close in two steps + // because closeClient() modifies the collection we iterate over. + for (CRemovedClients::iterator index = removed.begin(); + index != removed.end(); ++index) { + closeClient(*index, kMsgCClose); + } +} + +void +CServer::removeActiveClient(CBaseClientProxy* client) +{ + if (removeClient(client)) { + forceLeaveClient(client); + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + if (m_clients.size() == 1 && m_oldClients.empty()) { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } + } +} + +void +CServer::removeOldClient(CBaseClientProxy* client) +{ + COldClients::iterator i = m_oldClients.find(client); + if (i != m_oldClients.end()) { + EVENTQUEUE->removeHandler(CClientProxy::getDisconnectedEvent(), client); + EVENTQUEUE->removeHandler(CEvent::kTimer, i->second); + EVENTQUEUE->deleteTimer(i->second); + m_oldClients.erase(i); + if (m_clients.size() == 1 && m_oldClients.empty()) { + EVENTQUEUE->addEvent(CEvent(getDisconnectedEvent(), this)); + } + } +} + +void +CServer::forceLeaveClient(CBaseClientProxy* client) +{ + CBaseClientProxy* active = + (m_activeSaver != NULL) ? m_activeSaver : m_active; + if (active == client) { + // record new position (center of primary screen) + m_primaryClient->getCursorCenter(m_x, m_y); + + // stop waiting to switch to this client + if (active == m_switchScreen) { + stopSwitch(); + } + + // don't notify active screen since it has probably already + // disconnected. + LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y)); + + // cut over + m_active = m_primaryClient; + + // enter new screen (unless we already have because of the + // screen saver) + if (m_activeSaver == NULL) { + m_primaryClient->enter(m_x, m_y, m_seqNum, + m_primaryClient->getToggleMask(), false); + } + } + + // if this screen had the cursor when the screen saver activated + // then we can't switch back to it when the screen saver + // deactivates. + if (m_activeSaver == client) { + m_activeSaver = NULL; + } + + // tell primary client about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); +} + + +// +// CServer::CClipboardInfo +// + +CServer::CClipboardInfo::CClipboardInfo() : + m_clipboard(), + m_clipboardData(), + m_clipboardOwner(), + m_clipboardSeqNum(0) +{ + // do nothing +} + + +// +// CServer::CLockCursorToScreenInfo +// + +CServer::CLockCursorToScreenInfo* +CServer::CLockCursorToScreenInfo::alloc(State state) +{ + CLockCursorToScreenInfo* info = + (CLockCursorToScreenInfo*)malloc(sizeof(CLockCursorToScreenInfo)); + info->m_state = state; + return info; +} + + +// +// CServer::CSwitchToScreenInfo +// + +CServer::CSwitchToScreenInfo* +CServer::CSwitchToScreenInfo::alloc(const CString& screen) +{ + CSwitchToScreenInfo* info = + (CSwitchToScreenInfo*)malloc(sizeof(CSwitchToScreenInfo) + + screen.size()); + strcpy(info->m_screen, screen.c_str()); + return info; +} + + +// +// CServer::CSwitchInDirectionInfo +// + +CServer::CSwitchInDirectionInfo* +CServer::CSwitchInDirectionInfo::alloc(EDirection direction) +{ + CSwitchInDirectionInfo* info = + (CSwitchInDirectionInfo*)malloc(sizeof(CSwitchInDirectionInfo)); + info->m_direction = direction; + return info; +} + + +// +// CServer::CScreenConnectedInfo +// + +CServer::CScreenConnectedInfo* +CServer::CScreenConnectedInfo::alloc(const CString& screen) +{ + CScreenConnectedInfo* info = + (CScreenConnectedInfo*)malloc(sizeof(CScreenConnectedInfo) + + screen.size()); + strcpy(info->m_screen, screen.c_str()); + return info; +} + + +// +// CServer::CKeyboardBroadcastInfo +// + +CServer::CKeyboardBroadcastInfo* +CServer::CKeyboardBroadcastInfo::alloc(State state) +{ + CKeyboardBroadcastInfo* info = + (CKeyboardBroadcastInfo*)malloc(sizeof(CKeyboardBroadcastInfo)); + info->m_state = state; + info->m_screens[0] = '\0'; + return info; +} + +CServer::CKeyboardBroadcastInfo* +CServer::CKeyboardBroadcastInfo::alloc(State state, const CString& screens) +{ + CKeyboardBroadcastInfo* info = + (CKeyboardBroadcastInfo*)malloc(sizeof(CKeyboardBroadcastInfo) + + screens.size()); + info->m_state = state; + strcpy(info->m_screens, screens.c_str()); + return info; +} diff --git a/lib/server/CServer.h b/lib/server/CServer.h new file mode 100644 index 00000000..36cf5e83 --- /dev/null +++ b/lib/server/CServer.h @@ -0,0 +1,464 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSERVER_H +#define CSERVER_H + +#include "CConfig.h" +#include "CClipboard.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CEvent.h" +#include "CStopwatch.h" +#include "stdmap.h" +#include "stdset.h" +#include "stdvector.h" + +class CBaseClientProxy; +class CEventQueueTimer; +class CPrimaryClient; +class CInputFilter; + +//! Synergy server +/*! +This class implements the top-level server algorithms for synergy. +*/ +class CServer { +public: + //! Lock cursor to screen data + class CLockCursorToScreenInfo { + public: + enum State { kOff, kOn, kToggle }; + + static CLockCursorToScreenInfo* alloc(State state = kToggle); + + public: + State m_state; + }; + + //! Switch to screen data + class CSwitchToScreenInfo { + public: + static CSwitchToScreenInfo* alloc(const CString& screen); + + public: + // this is a C-string; this type is a variable size structure + char m_screen[1]; + }; + + //! Switch in direction data + class CSwitchInDirectionInfo { + public: + static CSwitchInDirectionInfo* alloc(EDirection direction); + + public: + EDirection m_direction; + }; + + //! Screen connected data + class CScreenConnectedInfo { + public: + static CScreenConnectedInfo* alloc(const CString& screen); + + public: + // this is a C-string; this type is a variable size structure + char m_screen[1]; + }; + + //! Keyboard broadcast data + class CKeyboardBroadcastInfo { + public: + enum State { kOff, kOn, kToggle }; + + static CKeyboardBroadcastInfo* alloc(State state = kToggle); + static CKeyboardBroadcastInfo* alloc(State state, + const CString& screens); + + public: + State m_state; + char m_screens[1]; + }; + + /*! + Start the server with the configuration \p config and the primary + client (local screen) \p primaryClient. The client retains + ownership of \p primaryClient. + */ + CServer(const CConfig& config, CPrimaryClient* primaryClient); + ~CServer(); + + //! @name manipulators + //@{ + + //! Set configuration + /*! + Change the server's configuration. Returns true iff the new + configuration was accepted (it must include the server's name). + This will disconnect any clients no longer in the configuration. + */ + bool setConfig(const CConfig&); + + //! Add a client + /*! + Adds \p client to the server. The client is adopted and will be + destroyed when the client disconnects or is disconnected. + */ + void adoptClient(CBaseClientProxy* client); + + //! Disconnect clients + /*! + Disconnect clients. This tells them to disconnect but does not wait + for them to actually do so. The server sends the disconnected event + when they're all disconnected (or immediately if none are connected). + The caller can also just destroy this object to force the disconnection. + */ + void disconnect(); + + //@} + //! @name accessors + //@{ + + //! Get number of connected clients + /*! + Returns the number of connected clients, including the server itself. + */ + UInt32 getNumClients() const; + + //! Get the list of connected clients + /*! + Set the \c list to the names of the currently connected clients. + */ + void getClients(std::vector& list) const; + + //! Get error event type + /*! + Returns the error event type. This is sent when the server fails + for some reason. + */ + static CEvent::Type getErrorEvent(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent when a client screen + has connected. The event data is a \c CScreenConnectedInfo* that + indicates the connected screen. + */ + static CEvent::Type getConnectedEvent(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when all the + clients have disconnected. + */ + static CEvent::Type getDisconnectedEvent(); + + //! Get switch to screen event type + /*! + Returns the switch to screen event type. The server responds to this + by switching screens. The event data is a \c CSwitchToScreenInfo* + that indicates the target screen. + */ + static CEvent::Type getSwitchToScreenEvent(); + + //! Get switch in direction event type + /*! + Returns the switch in direction event type. The server responds to this + by switching screens. The event data is a \c CSwitchInDirectionInfo* + that indicates the target direction. + */ + static CEvent::Type getSwitchInDirectionEvent(); + + //! Get keyboard broadcast event type + /*! + Returns the keyboard broadcast event type. The server responds + to this by turning on keyboard broadcasting or turning it off. The + event data is a \c CKeyboardBroadcastInfo*. + */ + static CEvent::Type getKeyboardBroadcastEvent(); + + //! Get lock cursor event type + /*! + Returns the lock cursor event type. The server responds to this + by locking the cursor to the active screen or unlocking it. The + event data is a \c CLockCursorToScreenInfo*. + */ + static CEvent::Type getLockCursorToScreenEvent(); + + //@} + +private: + // get canonical name of client + CString getName(const CBaseClientProxy*) const; + + // get the sides of the primary screen that have neighbors + UInt32 getActivePrimarySides() const; + + // returns true iff mouse should be locked to the current screen + // according to this object only, ignoring what the primary client + // says. + bool isLockedToScreenServer() const; + + // returns true iff mouse should be locked to the current screen + // according to this object or the primary client. + bool isLockedToScreen() const; + + // returns the jump zone of the client + SInt32 getJumpZoneSize(CBaseClientProxy*) const; + + // change the active screen + void switchScreen(CBaseClientProxy*, + SInt32 x, SInt32 y, bool forScreenSaver); + + // jump to screen + void jumpToScreen(CBaseClientProxy*); + + // convert pixel position to fraction, using x or y depending on the + // direction. + float mapToFraction(CBaseClientProxy*, EDirection, + SInt32 x, SInt32 y) const; + + // convert fraction to pixel position, writing only x or y depending + // on the direction. + void mapToPixel(CBaseClientProxy*, EDirection, float f, + SInt32& x, SInt32& y) const; + + // returns true if the client has a neighbor anywhere along the edge + // indicated by the direction. + bool hasAnyNeighbor(CBaseClientProxy*, EDirection) const; + + // lookup neighboring screen, mapping the coordinate independent of + // the direction to the neighbor's coordinate space. + CBaseClientProxy* getNeighbor(CBaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // lookup neighboring screen. given a position relative to the + // source screen, find the screen we should move onto and where. + // if the position is sufficiently far from the source then we + // cross multiple screens. if there is no suitable screen then + // return NULL and x,y are not modified. + CBaseClientProxy* mapToNeighbor(CBaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // adjusts x and y or neither to avoid ending up in a jump zone + // after entering the client in the given direction. + void avoidJumpZone(CBaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // test if a switch is permitted. this includes testing user + // options like switch delay and tracking any state required to + // implement them. returns true iff a switch is permitted. + bool isSwitchOkay(CBaseClientProxy* dst, EDirection, + SInt32 x, SInt32 y, SInt32 xActive, SInt32 yActive); + + // update switch state due to a mouse move at \p x, \p y that + // doesn't switch screens. + void noSwitch(SInt32 x, SInt32 y); + + // stop switch timers + void stopSwitch(); + + // start two tap switch timer + void startSwitchTwoTap(); + + // arm the two tap switch timer if \p x, \p y is outside the tap zone + void armSwitchTwoTap(SInt32 x, SInt32 y); + + // stop the two tap switch timer + void stopSwitchTwoTap(); + + // returns true iff the two tap switch timer is started + bool isSwitchTwoTapStarted() const; + + // returns true iff should switch because of two tap + bool shouldSwitchTwoTap() const; + + // start delay switch timer + void startSwitchWait(SInt32 x, SInt32 y); + + // stop delay switch timer + void stopSwitchWait(); + + // returns true iff the delay switch timer is started + bool isSwitchWaitStarted() const; + + // returns the corner (EScreenSwitchCornerMasks) where x,y is on the + // given client. corners have the given size. + UInt32 getCorner(CBaseClientProxy*, + SInt32 x, SInt32 y, SInt32 size) const; + + // stop relative mouse moves + void stopRelativeMoves(); + + // send screen options to \c client + void sendOptions(CBaseClientProxy* client) const; + + // process options from configuration + void processOptions(); + + // event handlers + void handleShapeChanged(const CEvent&, void*); + void handleClipboardGrabbed(const CEvent&, void*); + void handleClipboardChanged(const CEvent&, void*); + void handleKeyDownEvent(const CEvent&, void*); + void handleKeyUpEvent(const CEvent&, void*); + void handleKeyRepeatEvent(const CEvent&, void*); + void handleButtonDownEvent(const CEvent&, void*); + void handleButtonUpEvent(const CEvent&, void*); + void handleMotionPrimaryEvent(const CEvent&, void*); + void handleMotionSecondaryEvent(const CEvent&, void*); + void handleWheelEvent(const CEvent&, void*); + void handleScreensaverActivatedEvent(const CEvent&, void*); + void handleScreensaverDeactivatedEvent(const CEvent&, void*); + void handleSwitchWaitTimeout(const CEvent&, void*); + void handleClientDisconnected(const CEvent&, void*); + void handleClientCloseTimeout(const CEvent&, void*); + void handleSwitchToScreenEvent(const CEvent&, void*); + void handleSwitchInDirectionEvent(const CEvent&, void*); + void handleKeyboardBroadcastEvent(const CEvent&,void*); + void handleLockCursorToScreenEvent(const CEvent&, void*); + void handleFakeInputBeginEvent(const CEvent&, void*); + void handleFakeInputEndEvent(const CEvent&, void*); + + // event processing + void onClipboardChanged(CBaseClientProxy* sender, + ClipboardID id, UInt32 seqNum); + void onScreensaver(bool activated); + void onKeyDown(KeyID, KeyModifierMask, KeyButton, + const char* screens); + void onKeyUp(KeyID, KeyModifierMask, KeyButton, + const char* screens); + void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton); + void onMouseDown(ButtonID); + void onMouseUp(ButtonID); + bool onMouseMovePrimary(SInt32 x, SInt32 y); + void onMouseMoveSecondary(SInt32 dx, SInt32 dy); + void onMouseWheel(SInt32 xDelta, SInt32 yDelta); + + // add client to list and attach event handlers for client + bool addClient(CBaseClientProxy*); + + // remove client from list and detach event handlers for client + bool removeClient(CBaseClientProxy*); + + // close a client + void closeClient(CBaseClientProxy*, const char* msg); + + // close clients not in \p config + void closeClients(const CConfig& config); + + // close all clients whether they've completed the handshake or not, + // except the primary client + void closeAllClients(); + + // remove clients from internal state + void removeActiveClient(CBaseClientProxy*); + void removeOldClient(CBaseClientProxy*); + + // force the cursor off of \p client + void forceLeaveClient(CBaseClientProxy* client); + +private: + class CClipboardInfo { + public: + CClipboardInfo(); + + public: + CClipboard m_clipboard; + CString m_clipboardData; + CString m_clipboardOwner; + UInt32 m_clipboardSeqNum; + }; + + // the primary screen client + CPrimaryClient* m_primaryClient; + + // all clients (including the primary client) indexed by name + typedef std::map CClientList; + typedef std::set CClientSet; + CClientList m_clients; + CClientSet m_clientSet; + + // all old connections that we're waiting to hangup + typedef std::map COldClients; + COldClients m_oldClients; + + // the client with focus + CBaseClientProxy* m_active; + + // the sequence number of enter messages + UInt32 m_seqNum; + + // current mouse position (in absolute screen coordinates) on + // whichever screen is active + SInt32 m_x, m_y; + + // last mouse deltas. this is needed to smooth out double tap + // on win32 which reports bogus mouse motion at the edge of + // the screen when using low level hooks, synthesizing motion + // in the opposite direction the mouse actually moved. + SInt32 m_xDelta, m_yDelta; + SInt32 m_xDelta2, m_yDelta2; + + // current configuration + CConfig m_config; + + // input filter (from m_config); + CInputFilter* m_inputFilter; + + // clipboard cache + CClipboardInfo m_clipboards[kClipboardEnd]; + + // state saved when screen saver activates + CBaseClientProxy* m_activeSaver; + SInt32 m_xSaver, m_ySaver; + + // common state for screen switch tests. all tests are always + // trying to reach the same screen in the same direction. + EDirection m_switchDir; + CBaseClientProxy* m_switchScreen; + + // state for delayed screen switching + double m_switchWaitDelay; + CEventQueueTimer* m_switchWaitTimer; + SInt32 m_switchWaitX, m_switchWaitY; + + // state for double-tap screen switching + double m_switchTwoTapDelay; + CStopwatch m_switchTwoTapTimer; + bool m_switchTwoTapEngaged; + bool m_switchTwoTapArmed; + SInt32 m_switchTwoTapZone; + + // relative mouse move option + bool m_relativeMoves; + + // flag whether or not we have broadcasting enabled and the screens to + // which we should send broadcasted keys. + bool m_keyboardBroadcasting; + CString m_keyboardBroadcastingScreens; + + // screen locking (former scroll lock) + bool m_lockedToScreen; + + static CEvent::Type s_errorEvent; + static CEvent::Type s_connectedEvent; + static CEvent::Type s_disconnectedEvent; + static CEvent::Type s_switchToScreen; + static CEvent::Type s_switchInDirection; + static CEvent::Type s_keyboardBroadcast; + static CEvent::Type s_lockCursorToScreen; +}; + +#endif diff --git a/lib/server/Makefile.am b/lib/server/Makefile.am new file mode 100644 index 00000000..e03d9b7c --- /dev/null +++ b/lib/server/Makefile.am @@ -0,0 +1,60 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libserver.a +libserver_a_SOURCES = \ + CBaseClientProxy.cpp \ + CClientListener.cpp \ + CClientProxy.cpp \ + CClientProxy1_0.cpp \ + CClientProxy1_1.cpp \ + CClientProxy1_2.cpp \ + CClientProxy1_3.cpp \ + CClientProxyUnknown.cpp \ + CConfig.cpp \ + CInputFilter.cpp \ + CPrimaryClient.cpp \ + CServer.cpp \ + CBaseClientProxy.h \ + CClientListener.h \ + CClientProxy.h \ + CClientProxy1_0.h \ + CClientProxy1_1.h \ + CClientProxy1_2.h \ + CClientProxy1_3.h \ + CClientProxyUnknown.h \ + CConfig.h \ + CInputFilter.h \ + CPrimaryClient.h \ + CServer.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + -I$(top_srcdir)/lib/synergy \ + -I$(top_srcdir)/lib/platform \ + $(NULL) diff --git a/lib/server/Makefile.win b/lib/server/Makefile.win new file mode 100644 index 00000000..ed059e51 --- /dev/null +++ b/lib/server/Makefile.win @@ -0,0 +1,83 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_SERVER_SRC = lib\server +LIB_SERVER_DST = $(BUILD_DST)\$(LIB_SERVER_SRC) +LIB_SERVER_LIB = "$(LIB_SERVER_DST)\server.lib" +LIB_SERVER_CPP = \ + "CBaseClientProxy.cpp" \ + "CClientListener.cpp" \ + "CClientProxy.cpp" \ + "CClientProxy1_0.cpp" \ + "CClientProxy1_1.cpp" \ + "CClientProxy1_2.cpp" \ + "CClientProxy1_3.cpp" \ + "CClientProxyUnknown.cpp" \ + "CConfig.cpp" \ + "CInputFilter.cpp" \ + "CPrimaryClient.cpp" \ + "CServer.cpp" \ + $(NULL) +LIB_SERVER_OBJ = \ + "$(LIB_SERVER_DST)\CBaseClientProxy.obj" \ + "$(LIB_SERVER_DST)\CClientListener.obj" \ + "$(LIB_SERVER_DST)\CClientProxy.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_0.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_1.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_2.obj" \ + "$(LIB_SERVER_DST)\CClientProxy1_3.obj" \ + "$(LIB_SERVER_DST)\CClientProxyUnknown.obj" \ + "$(LIB_SERVER_DST)\CConfig.obj" \ + "$(LIB_SERVER_DST)\CInputFilter.obj" \ + "$(LIB_SERVER_DST)\CPrimaryClient.obj" \ + "$(LIB_SERVER_DST)\CServer.obj" \ + $(NULL) +LIB_SERVER_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + /I"lib\synergy" \ + /I"lib\platform" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_SERVER_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_SERVER_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_SERVER_LIB) + +# Dependency rules +$(LIB_SERVER_OBJ): $(AUTODEP) +!if EXIST($(LIB_SERVER_DST)\deps.mak) +!include $(LIB_SERVER_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_SERVER_SRC)\}.cpp{$(LIB_SERVER_DST)\}.obj:: +!else +{$(LIB_SERVER_SRC)\}.cpp{$(LIB_SERVER_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_SERVER_SRC) + -@$(MKDIR) $(LIB_SERVER_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_SERVER_INC) \ + /Fo$(LIB_SERVER_DST)\ \ + /Fd$(LIB_SERVER_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_SERVER_SRC) $(LIB_SERVER_DST) +$(LIB_SERVER_LIB): $(LIB_SERVER_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_SERVER_SRC) $(LIB_SERVER_DST) $(**:.obj=.d) diff --git a/lib/synergy/CClipboard.cpp b/lib/synergy/CClipboard.cpp new file mode 100644 index 00000000..d0388a01 --- /dev/null +++ b/lib/synergy/CClipboard.cpp @@ -0,0 +1,114 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CClipboard.h" + +// +// CClipboard +// + +CClipboard::CClipboard() : + m_open(false), + m_owner(false) +{ + open(0); + empty(); + close(); +} + +CClipboard::~CClipboard() +{ + // do nothing +} + +bool +CClipboard::empty() +{ + assert(m_open); + + // clear all data + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } + + // save time + m_timeOwned = m_time; + + // we're the owner now + m_owner = true; + + return true; +} + +void +CClipboard::add(EFormat format, const CString& data) +{ + assert(m_open); + assert(m_owner); + + m_data[format] = data; + m_added[format] = true; +} + +bool +CClipboard::open(Time time) const +{ + assert(!m_open); + + m_open = true; + m_time = time; + + return true; +} + +void +CClipboard::close() const +{ + assert(m_open); + + m_open = false; +} + +CClipboard::Time +CClipboard::getTime() const +{ + return m_timeOwned; +} + +bool +CClipboard::has(EFormat format) const +{ + assert(m_open); + return m_added[format]; +} + +CString +CClipboard::get(EFormat format) const +{ + assert(m_open); + return m_data[format]; +} + +void +CClipboard::unmarshall(const CString& data, Time time) +{ + IClipboard::unmarshall(this, data, time); +} + +CString +CClipboard::marshall() const +{ + return IClipboard::marshall(this); +} diff --git a/lib/synergy/CClipboard.h b/lib/synergy/CClipboard.h new file mode 100644 index 00000000..f8d10aff --- /dev/null +++ b/lib/synergy/CClipboard.h @@ -0,0 +1,70 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CCLIPBOARD_H +#define CCLIPBOARD_H + +#include "IClipboard.h" + +//! Memory buffer clipboard +/*! +This class implements a clipboard that stores data in memory. +*/ +class CClipboard : public IClipboard { +public: + CClipboard(); + virtual ~CClipboard(); + + //! @name manipulators + //@{ + + //! Unmarshall clipboard data + /*! + Extract marshalled clipboard data and store it in this clipboard. + Sets the clipboard time to \c time. + */ + void unmarshall(const CString& data, Time time); + + //@} + //! @name accessors + //@{ + + //! Marshall clipboard data + /*! + Merge this clipboard's data into a single buffer that can be later + unmarshalled to restore the clipboard and return the buffer. + */ + CString marshall() const; + + //@} + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const CString& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual CString get(EFormat) const; + +private: + mutable bool m_open; + mutable Time m_time; + bool m_owner; + Time m_timeOwned; + bool m_added[kNumFormats]; + CString m_data[kNumFormats]; +}; + +#endif diff --git a/lib/synergy/CKeyMap.cpp b/lib/synergy/CKeyMap.cpp new file mode 100644 index 00000000..4141c8b1 --- /dev/null +++ b/lib/synergy/CKeyMap.cpp @@ -0,0 +1,1330 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CKeyMap.h" +#include "KeyTypes.h" +#include "CLog.h" +#include +#include +#include + +CKeyMap::CNameToKeyMap* CKeyMap::s_nameToKeyMap = NULL; +CKeyMap::CNameToModifierMap* CKeyMap::s_nameToModifierMap = NULL; +CKeyMap::CKeyToNameMap* CKeyMap::s_keyToNameMap = NULL; +CKeyMap::CModifierToNameMap* CKeyMap::s_modifierToNameMap = NULL; + +CKeyMap::CKeyMap() : + m_numGroups(0), + m_composeAcrossGroups(false) +{ + m_modifierKeyItem.m_id = kKeyNone; + m_modifierKeyItem.m_group = 0; + m_modifierKeyItem.m_button = 0; + m_modifierKeyItem.m_required = 0; + m_modifierKeyItem.m_sensitive = 0; + m_modifierKeyItem.m_generates = 0; + m_modifierKeyItem.m_dead = false; + m_modifierKeyItem.m_lock = false; + m_modifierKeyItem.m_client = 0; +} + +CKeyMap::~CKeyMap() +{ + // do nothing +} + +void +CKeyMap::swap(CKeyMap& x) +{ + m_keyIDMap.swap(x.m_keyIDMap); + m_modifierKeys.swap(x.m_modifierKeys); + m_halfDuplex.swap(x.m_halfDuplex); + m_halfDuplexMods.swap(x.m_halfDuplexMods); + SInt32 tmp1 = m_numGroups; + m_numGroups = x.m_numGroups; + x.m_numGroups = tmp1; + bool tmp2 = m_composeAcrossGroups; + m_composeAcrossGroups = x.m_composeAcrossGroups; + x.m_composeAcrossGroups = tmp2; +} + +void +CKeyMap::addKeyEntry(const KeyItem& item) +{ + // ignore kKeyNone + if (item.m_id == kKeyNone) { + return; + } + + // resize number of groups for key + SInt32 numGroups = item.m_group + 1; + if (getNumGroups() > numGroups) { + numGroups = getNumGroups(); + } + KeyGroupTable& groupTable = m_keyIDMap[item.m_id]; + if (groupTable.size() < static_cast(numGroups)) { + groupTable.resize(numGroups); + } + + // make a list from the item + KeyItemList items; + items.push_back(item); + + // set group and dead key flag on the item + KeyItem& newItem = items.back(); + newItem.m_dead = isDeadKey(item.m_id); + + // mask the required bits with the sensitive bits + newItem.m_required &= newItem.m_sensitive; + + // see if we already have this item; just return if so + KeyEntryList& entries = groupTable[item.m_group]; + for (size_t i = 0, n = entries.size(); i < n; ++i) { + if (entries[i].size() == 1 && newItem == entries[i][0]) { + return; + } + } + + // add item list + entries.push_back(items); + LOG((CLOG_DEBUG1 "add key: %04x %d %03x %04x (%04x %04x %04x)%s", newItem.m_id, newItem.m_group, newItem.m_button, newItem.m_client, newItem.m_required, newItem.m_sensitive, newItem.m_generates, newItem.m_dead ? " dead" : "")); +} + +void +CKeyMap::addKeyAliasEntry(KeyID targetID, SInt32 group, + KeyModifierMask targetRequired, + KeyModifierMask targetSensitive, + KeyID sourceID, + KeyModifierMask sourceRequired, + KeyModifierMask sourceSensitive) +{ + // if we can already generate the target as desired then we're done. + if (findCompatibleKey(targetID, group, targetRequired, + targetSensitive) != NULL) { + return; + } + + // find a compatible source, preferably in the same group + for (SInt32 gd = 0, n = getNumGroups(); gd < n; ++gd) { + SInt32 eg = getEffectiveGroup(group, gd); + const KeyItemList* sourceEntry = + findCompatibleKey(sourceID, eg, + sourceRequired, sourceSensitive); + if (sourceEntry != NULL && sourceEntry->size() == 1) { + CKeyMap::KeyItem targetItem = sourceEntry->back(); + targetItem.m_id = targetID; + targetItem.m_group = eg; + addKeyEntry(targetItem); + break; + } + } +} + +bool +CKeyMap::addKeyCombinationEntry(KeyID id, SInt32 group, + const KeyID* keys, UInt32 numKeys) +{ + // disallow kKeyNone + if (id == kKeyNone) { + return false; + } + + SInt32 numGroups = group + 1; + if (getNumGroups() > numGroups) { + numGroups = getNumGroups(); + } + KeyGroupTable& groupTable = m_keyIDMap[id]; + if (groupTable.size() < static_cast(numGroups)) { + groupTable.resize(numGroups); + } + if (!groupTable[group].empty()) { + // key is already in the table + return false; + } + + // convert to buttons + KeyItemList items; + for (UInt32 i = 0; i < numKeys; ++i) { + KeyIDMap::const_iterator gtIndex = m_keyIDMap.find(keys[i]); + if (gtIndex == m_keyIDMap.end()) { + return false; + } + const KeyGroupTable& groupTable = gtIndex->second; + + // if we allow group switching during composition then search all + // groups for keys, otherwise search just the given group. + SInt32 n = 1; + if (m_composeAcrossGroups) { + n = (SInt32)groupTable.size(); + } + + bool found = false; + for (SInt32 gd = 0; gd < n && !found; ++gd) { + SInt32 eg = (group + gd) % getNumGroups(); + const KeyEntryList& entries = groupTable[eg]; + for (size_t j = 0; j < entries.size(); ++j) { + if (entries[j].size() == 1) { + found = true; + items.push_back(entries[j][0]); + break; + } + } + } + if (!found) { + // required key is not in keyboard group + return false; + } + } + + // add key + groupTable[group].push_back(items); + return true; +} + +void +CKeyMap::allowGroupSwitchDuringCompose() +{ + m_composeAcrossGroups = true; +} + +void +CKeyMap::addHalfDuplexButton(KeyButton button) +{ + m_halfDuplex.insert(button); +} + +void +CKeyMap::clearHalfDuplexModifiers() +{ + m_halfDuplexMods.clear(); +} + +void +CKeyMap::addHalfDuplexModifier(KeyID key) +{ + m_halfDuplexMods.insert(key); +} + +void +CKeyMap::finish() +{ + m_numGroups = findNumGroups(); + + // make sure every key has the same number of groups + for (KeyIDMap::iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + i->second.resize(m_numGroups); + } + + // compute keys that generate each modifier + setModifierKeys(); +} + +void +CKeyMap::foreachKey(ForeachKeyCallback cb, void* userData) +{ + for (KeyIDMap::iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + KeyGroupTable& groupTable = i->second; + for (size_t group = 0; group < groupTable.size(); ++group) { + KeyEntryList& entryList = groupTable[group]; + for (size_t j = 0; j < entryList.size(); ++j) { + KeyItemList& itemList = entryList[j]; + for (size_t k = 0; k < itemList.size(); ++k) { + (*cb)(i->first, static_cast(group), + itemList[k], userData); + } + } + } + } +} + +const CKeyMap::KeyItem* +CKeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x", id, id, desiredMask, currentState)); + + // handle group change + if (id == kKeyNextGroup) { + keys.push_back(Keystroke(1, false, false)); + return NULL; + } + else if (id == kKeyPrevGroup) { + keys.push_back(Keystroke(-1, false, false)); + return NULL; + } + + const KeyItem* item; + switch (id) { + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyAltGr: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + item = mapModifierKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + break; + + case kKeySetModifiers: + if (!keysForModifierState(0, group, activeModifiers, currentState, + desiredMask, desiredMask, 0, keys)) { + LOG((CLOG_DEBUG1 "unable to set modifiers %04x", desiredMask)); + return NULL; + } + return &m_modifierKeyItem; + + case kKeyClearModifiers: + if (!keysForModifierState(0, group, activeModifiers, currentState, + currentState & ~desiredMask, + desiredMask, 0, keys)) { + LOG((CLOG_DEBUG1 "unable to clear modifiers %04x", desiredMask)); + return NULL; + } + return &m_modifierKeyItem; + + default: + if (isCommand(desiredMask)) { + item = mapCommandKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + } + else { + item = mapCharacterKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + } + break; + } + + if (item != NULL) { + LOG((CLOG_DEBUG1 "mapped to %03x, new state %04x", item->m_button, currentState)); + } + return item; +} + +SInt32 +CKeyMap::getNumGroups() const +{ + return m_numGroups; +} + +SInt32 +CKeyMap::getEffectiveGroup(SInt32 group, SInt32 offset) const +{ + return (group + offset + getNumGroups()) % getNumGroups(); +} + +const CKeyMap::KeyItemList* +CKeyMap::findCompatibleKey(KeyID id, SInt32 group, + KeyModifierMask required, KeyModifierMask sensitive) const +{ + assert(group >= 0 && group < getNumGroups()); + + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + return NULL; + } + + const KeyEntryList& entries = i->second[group]; + for (size_t j = 0; j < entries.size(); ++j) { + if ((entries[j].back().m_sensitive & sensitive) == 0 || + (entries[j].back().m_required & sensitive) == + (required & sensitive)) { + return &entries[j]; + } + } + + return NULL; +} + +bool +CKeyMap::isHalfDuplex(KeyID key, KeyButton button) const +{ + return (m_halfDuplex.count(button) + m_halfDuplexMods.count(key) > 0); +} + +bool +CKeyMap::isCommand(KeyModifierMask mask) const +{ + return ((mask & getCommandModifiers()) != 0); +} + +KeyModifierMask +CKeyMap::getCommandModifiers() const +{ + // we currently treat ctrl, alt, meta and super as command modifiers. + // some platforms may have a more limited set (OS X only needs Alt) + // but this works anyway. + return KeyModifierControl | + KeyModifierAlt | + KeyModifierMeta | + KeyModifierSuper; +} + +void +CKeyMap::collectButtons(const ModifierToKeys& mods, ButtonToKeyMap& keys) +{ + keys.clear(); + for (ModifierToKeys::const_iterator i = mods.begin(); + i != mods.end(); ++i) { + keys.insert(std::make_pair(i->second.m_button, &i->second)); + } +} + +void +CKeyMap::initModifierKey(KeyItem& item) +{ + item.m_generates = 0; + item.m_lock = false; + switch (item.m_id) { + case kKeyShift_L: + case kKeyShift_R: + item.m_generates = KeyModifierShift; + break; + + case kKeyControl_L: + case kKeyControl_R: + item.m_generates = KeyModifierControl; + break; + + case kKeyAlt_L: + case kKeyAlt_R: + item.m_generates = KeyModifierAlt; + break; + + case kKeyMeta_L: + case kKeyMeta_R: + item.m_generates = KeyModifierMeta; + break; + + case kKeySuper_L: + case kKeySuper_R: + item.m_generates = KeyModifierSuper; + break; + + case kKeyAltGr: + item.m_generates = KeyModifierAltGr; + break; + + case kKeyCapsLock: + item.m_generates = KeyModifierCapsLock; + item.m_lock = true; + break; + + case kKeyNumLock: + item.m_generates = KeyModifierNumLock; + item.m_lock = true; + break; + + case kKeyScrollLock: + item.m_generates = KeyModifierScrollLock; + item.m_lock = true; + break; + + default: + // not a modifier + break; + } +} + +SInt32 +CKeyMap::findNumGroups() const +{ + size_t max = 0; + for (KeyIDMap::const_iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + if (i->second.size() > max) { + max = i->second.size(); + } + } + return static_cast(max); +} + +void +CKeyMap::setModifierKeys() +{ + m_modifierKeys.clear(); + m_modifierKeys.resize(kKeyModifierNumBits * getNumGroups()); + for (KeyIDMap::const_iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + const KeyGroupTable& groupTable = i->second; + for (size_t g = 0; g < groupTable.size(); ++g) { + const KeyEntryList& entries = groupTable[g]; + for (size_t j = 0; j < entries.size(); ++j) { + // skip multi-key sequences + if (entries[j].size() != 1) { + continue; + } + + // skip keys that don't generate a modifier + const KeyItem& item = entries[j].back(); + if (item.m_generates == 0) { + continue; + } + + // add key to each indicated modifier in this group + for (SInt32 b = 0; b < kKeyModifierNumBits; ++b) { + // skip if item doesn't generate bit b + if (((1u << b) & item.m_generates) != 0) { + SInt32 mIndex = g * kKeyModifierNumBits + b; + m_modifierKeys[mIndex].push_back(&item); + } + } + } + } + } +} + +const CKeyMap::KeyItem* +CKeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + static const KeyModifierMask s_overrideModifiers = 0xffffu; + + // find KeySym in table + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + // unknown key + LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); + return NULL; + } + const KeyGroupTable& keyGroupTable = i->second; + + // find the first key that generates this KeyID + const KeyItem* keyItem = NULL; + SInt32 numGroups = getNumGroups(); + for (SInt32 groupOffset = 0; groupOffset < numGroups; ++groupOffset) { + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + const KeyEntryList& entryList = keyGroupTable[effectiveGroup]; + for (size_t i = 0; i < entryList.size(); ++i) { + if (entryList[i].size() != 1) { + // ignore multikey entries + continue; + } + + // only match based on shift; we're after the right button + // not the right character. we'll use desiredMask as-is, + // overriding the key's required modifiers, when synthesizing + // this button. + const KeyItem& item = entryList[i].back(); + if ((item.m_required & KeyModifierShift & desiredMask) == + (item.m_sensitive & KeyModifierShift & desiredMask)) { + LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup)); + keyItem = &item; + break; + } + } + if (keyItem != NULL) { + break; + } + } + if (keyItem == NULL) { + // no mapping for this keysym + LOG((CLOG_DEBUG1 "no mapping for key %04x", id)); + return NULL; + } + + // make working copy of modifiers + ModifierToKeys newModifiers = activeModifiers; + KeyModifierMask newState = currentState; + SInt32 newGroup = group; + + // don't try to change CapsLock + desiredMask = (desiredMask & ~KeyModifierCapsLock) | + (currentState & KeyModifierCapsLock); + + // add the key + if (!keysForKeyItem(*keyItem, newGroup, newModifiers, + newState, desiredMask, + s_overrideModifiers, isAutoRepeat, keys)) { + LOG((CLOG_DEBUG1 "can't map key")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore modifier keys + if (!keysToRestoreModifiers(*keyItem, group, newModifiers, newState, + activeModifiers, keys)) { + LOG((CLOG_DEBUG1 "failed to restore modifiers")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore group + if (newGroup != group) { + keys.push_back(Keystroke(group, true, true)); + } + + // save new modifiers + activeModifiers = newModifiers; + currentState = newState; + + return keyItem; +} + +const CKeyMap::KeyItem* +CKeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + // find KeySym in table + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + // unknown key + LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); + return NULL; + } + const KeyGroupTable& keyGroupTable = i->second; + + // find best key in any group, starting with the active group + SInt32 keyIndex = -1; + SInt32 numGroups = getNumGroups(); + SInt32 groupOffset; + LOG((CLOG_DEBUG1 "find best: %04x %04x", currentState, desiredMask)); + for (groupOffset = 0; groupOffset < numGroups; ++groupOffset) { + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + keyIndex = findBestKey(keyGroupTable[effectiveGroup], + currentState, desiredMask); + if (keyIndex != -1) { + LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup)); + break; + } + } + if (keyIndex == -1) { + // no mapping for this keysym + LOG((CLOG_DEBUG1 "no mapping for key %04x", id)); + return NULL; + } + + // get keys to press for key + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + const KeyItemList& itemList = keyGroupTable[effectiveGroup][keyIndex]; + if (itemList.empty()) { + return NULL; + } + const KeyItem& keyItem = itemList.back(); + + // make working copy of modifiers + ModifierToKeys newModifiers = activeModifiers; + KeyModifierMask newState = currentState; + SInt32 newGroup = group; + + // add each key + for (size_t j = 0; j < itemList.size(); ++j) { + if (!keysForKeyItem(itemList[j], newGroup, newModifiers, + newState, desiredMask, + 0, isAutoRepeat, keys)) { + LOG((CLOG_DEBUG1 "can't map key")); + keys.clear(); + return NULL; + } + } + + // add keystrokes to restore modifier keys + if (!keysToRestoreModifiers(keyItem, group, newModifiers, newState, + activeModifiers, keys)) { + LOG((CLOG_DEBUG1 "failed to restore modifiers")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore group + if (newGroup != group) { + keys.push_back(Keystroke(group, true, true)); + } + + // save new modifiers + activeModifiers = newModifiers; + currentState = newState; + + return &keyItem; +} + +const CKeyMap::KeyItem* +CKeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + return mapCharacterKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); +} + +SInt32 +CKeyMap::findBestKey(const KeyEntryList& entryList, + KeyModifierMask /*currentState*/, + KeyModifierMask desiredState) const +{ + // check for an item that can accommodate the desiredState exactly + for (size_t i = 0; i < entryList.size(); ++i) { + const KeyItem& item = entryList[i].back(); + if ((item.m_required & desiredState) == + (item.m_sensitive & desiredState)) { + LOG((CLOG_DEBUG1 "best key index %d of %d (exact)", i, entryList.size())); + return i; + } + } + + // choose the item that requires the fewest modifier changes + SInt32 bestCount = 32; + SInt32 bestIndex = -1; + for (size_t i = 0; i < entryList.size(); ++i) { + const KeyItem& item = entryList[i].back(); + KeyModifierMask change = + ((item.m_required ^ desiredState) & item.m_sensitive); + SInt32 n = getNumModifiers(change); + if (n < bestCount) { + bestCount = n; + bestIndex = i; + } + } + if (bestIndex != -1) { + LOG((CLOG_DEBUG1 "best key index %d of %d (%d modifiers)", bestIndex, entryList.size(), bestCount)); + } + + return bestIndex; +} + + +const CKeyMap::KeyItem* +CKeyMap::keyForModifier(KeyButton button, SInt32 group, + SInt32 modifierBit) const +{ + assert(modifierBit >= 0 && modifierBit < kKeyModifierNumBits); + assert(group >= 0 && group < getNumGroups()); + + // find a key that generates the given modifier in the given group + // but doesn't use the given button, presumably because we're trying + // to generate a KeyID that's only bound the the given button. + // this is important when a shift button is modified by shift; we + // must use the other shift button to do the shifting. + const ModifierKeyItemList& items = + m_modifierKeys[group * kKeyModifierNumBits + modifierBit]; + for (ModifierKeyItemList::const_iterator i = items.begin(); + i != items.end(); ++i) { + if ((*i)->m_button != button) { + return (*i); + } + } + return NULL; +} + +bool +CKeyMap::keysForKeyItem(const KeyItem& keyItem, SInt32& group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, KeyModifierMask desiredState, + KeyModifierMask overrideModifiers, + bool isAutoRepeat, + Keystrokes& keystrokes) const +{ + static const KeyModifierMask s_notRequiredMask = + KeyModifierAltGr | KeyModifierNumLock | KeyModifierScrollLock; + + // add keystrokes to adjust the group + if (group != keyItem.m_group) { + group = keyItem.m_group; + keystrokes.push_back(Keystroke(group, true, false)); + } + + EKeystroke type; + if (keyItem.m_dead) { + // adjust modifiers for dead key + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + keyItem.m_required, keyItem.m_sensitive, + 0, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match modifier state for dead key %d", keyItem.m_button)); + return false; + } + + // press and release the dead key + type = kKeystrokeClick; + } + else { + // if this a command key then we don't have to match some of the + // key's required modifiers. + KeyModifierMask sensitive = keyItem.m_sensitive & ~overrideModifiers; + + // XXX -- must handle pressing a modifier. in particular, if we want + // to synthesize a KeyID on level 1 of a KeyButton that has Shift_L + // mapped to level 0 then we must release that button if it's down + // (in order to satisfy a shift modifier) then press a different + // button (any other button) mapped to the shift modifier and then + // the Shift_L button. + // match key's required state + LOG((CLOG_DEBUG1 "state: %04x,%04x,%04x", currentState, keyItem.m_required, sensitive)); + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + keyItem.m_required, sensitive, + 0, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match modifier state (%04x,%04x) for key %d", keyItem.m_required, keyItem.m_sensitive, keyItem.m_button)); + return false; + } + + // match desiredState as closely as possible. we must not + // change any modifiers in keyItem.m_sensitive. and if the key + // is a modifier, we don't want to change that modifier. + LOG((CLOG_DEBUG1 "desired state: %04x %04x,%04x,%04x", desiredState, currentState, keyItem.m_required, keyItem.m_sensitive)); + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + desiredState, + ~(sensitive | keyItem.m_generates), + s_notRequiredMask, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match desired modifier state (%04x,%04x) for key %d", desiredState, ~keyItem.m_sensitive & 0xffffu, keyItem.m_button)); + return false; + } + + // repeat or press of key + type = isAutoRepeat ? kKeystrokeRepeat : kKeystrokePress; + } + addKeystrokes(type, keyItem, activeModifiers, currentState, keystrokes); + + return true; +} + +bool +CKeyMap::keysToRestoreModifiers(const KeyItem& keyItem, SInt32, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + const ModifierToKeys& desiredModifiers, + Keystrokes& keystrokes) const +{ + // XXX -- we're not considering modified modifiers here + + ModifierToKeys oldModifiers = activeModifiers; + + // get the pressed modifier buttons before and after + ButtonToKeyMap oldKeys, newKeys; + collectButtons(oldModifiers, oldKeys); + collectButtons(desiredModifiers, newKeys); + + // release unwanted keys + for (ModifierToKeys::const_iterator i = oldModifiers.begin(); + i != oldModifiers.end(); ++i) { + KeyButton button = i->second.m_button; + if (button != keyItem.m_button && newKeys.count(button) == 0) { + EKeystroke type = kKeystrokeRelease; + if (i->second.m_lock) { + type = kKeystrokeUnmodify; + } + addKeystrokes(type, i->second, + activeModifiers, currentState, keystrokes); + } + } + + // press wanted keys + for (ModifierToKeys::const_iterator i = desiredModifiers.begin(); + i != desiredModifiers.end(); ++i) { + KeyButton button = i->second.m_button; + if (button != keyItem.m_button && oldKeys.count(button) == 0) { + EKeystroke type = kKeystrokePress; + if (i->second.m_lock) { + type = kKeystrokeModify; + } + addKeystrokes(type, i->second, + activeModifiers, currentState, keystrokes); + } + } + + return true; +} + +bool +CKeyMap::keysForModifierState(KeyButton button, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask requiredState, KeyModifierMask sensitiveMask, + KeyModifierMask notRequiredMask, + Keystrokes& keystrokes) const +{ + // compute which modifiers need changing + KeyModifierMask flipMask = ((currentState ^ requiredState) & sensitiveMask); + // if a modifier is not required then don't even try to match it. if + // we don't mask out notRequiredMask then we'll try to match those + // modifiers but succeed if we can't. however, this is known not + // to work if the key itself is a modifier (the numlock toggle can + // interfere) so we don't try to match at all. + flipMask &= ~notRequiredMask; + LOG((CLOG_DEBUG1 "flip: %04x (%04x vs %04x in %04x - %04x)", flipMask, currentState, requiredState, sensitiveMask & 0xffffu, notRequiredMask & 0xffffu)); + if (flipMask == 0) { + return true; + } + + // fix modifiers. this is complicated by the fact that a modifier may + // be sensitive to other modifiers! (who thought that up?) + // + // we'll assume that modifiers with higher bits are affected by modifiers + // with lower bits. there's not much basis for that assumption except + // that we're pretty sure shift isn't changed by other modifiers. + for (SInt32 bit = kKeyModifierNumBits; bit-- > 0; ) { + KeyModifierMask mask = (1u << bit); + if ((flipMask & mask) == 0) { + // modifier is already correct + continue; + } + + // do we want the modifier active or inactive? + bool active = ((requiredState & mask) != 0); + + // get the KeyItem for the modifier in the group + const KeyItem* keyItem = keyForModifier(button, group, bit); + if (keyItem == NULL) { + if ((mask & notRequiredMask) == 0) { + LOG((CLOG_DEBUG1 "no key for modifier %04x", mask)); + return false; + } + else { + continue; + } + } + + // if this modifier is sensitive to modifiers then adjust those + // modifiers. also check if our assumption was correct. note + // that we only need to adjust the modifiers on key down. + KeyModifierMask sensitive = keyItem->m_sensitive; + if ((sensitive & mask) != 0) { + // modifier is sensitive to itself. that makes no sense + // so ignore it. + LOG((CLOG_DEBUG1 "modifier %04x modified by itself", mask)); + sensitive &= ~mask; + } + if (sensitive != 0) { + if (sensitive > mask) { + // our assumption is incorrect + LOG((CLOG_DEBUG1 "modifier %04x modified by %04x", mask, sensitive)); + return false; + } + if (active && !keysForModifierState(button, group, + activeModifiers, currentState, + keyItem->m_required, sensitive, + notRequiredMask, keystrokes)) { + return false; + } + else if (!active) { + // release the modifier + // XXX -- this doesn't work! if Alt and Meta are mapped + // to one key and we want to release Meta we can't do + // that without also releasing Alt. + // need to think about support for modified modifiers. + } + } + + // current state should match required state + if ((currentState & sensitive) != (keyItem->m_required & sensitive)) { + LOG((CLOG_DEBUG1 "unable to match modifier state for modifier %04x (%04x vs %04x in %04x)", mask, currentState, keyItem->m_required, sensitive)); + return false; + } + + // add keystrokes + EKeystroke type = active ? kKeystrokeModify : kKeystrokeUnmodify; + addKeystrokes(type, *keyItem, activeModifiers, currentState, + keystrokes); + } + + return true; +} + +void +CKeyMap::addKeystrokes(EKeystroke type, const KeyItem& keyItem, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + Keystrokes& keystrokes) const +{ + KeyButton button = keyItem.m_button; + UInt32 data = keyItem.m_client; + switch (type) { + case kKeystrokePress: + keystrokes.push_back(Keystroke(button, true, false, data)); + if (keyItem.m_generates != 0) { + if (!keyItem.m_lock || (currentState & keyItem.m_generates) == 0) { + // add modifier key and activate modifier + activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + currentState |= keyItem.m_generates; + } + else { + // deactivate locking modifier + activeModifiers.erase(keyItem.m_generates); + currentState &= ~keyItem.m_generates; + } + } + break; + + case kKeystrokeRelease: + keystrokes.push_back(Keystroke(button, false, false, data)); + if (keyItem.m_generates != 0 && !keyItem.m_lock) { + // remove key from active modifiers + std::pair range = + activeModifiers.equal_range(keyItem.m_generates); + for (ModifierToKeys::iterator i = range.first; + i != range.second; ++i) { + if (i->second.m_button == button) { + activeModifiers.erase(i); + break; + } + } + + // if no more keys for this modifier then deactivate modifier + if (activeModifiers.count(keyItem.m_generates) == 0) { + currentState &= ~keyItem.m_generates; + } + } + break; + + case kKeystrokeRepeat: + keystrokes.push_back(Keystroke(button, false, true, data)); + keystrokes.push_back(Keystroke(button, true, true, data)); + // no modifier changes on key repeat + break; + + case kKeystrokeClick: + keystrokes.push_back(Keystroke(button, true, false, data)); + keystrokes.push_back(Keystroke(button, false, false, data)); + // no modifier changes on key click + break; + + case kKeystrokeModify: + case kKeystrokeUnmodify: + if (keyItem.m_lock) { + // we assume there's just one button for this modifier + if (m_halfDuplex.count(button) > 0) { + if (type == kKeystrokeModify) { + // turn half-duplex toggle on (press) + keystrokes.push_back(Keystroke(button, true, false, data)); + } + else { + // turn half-duplex toggle off (release) + keystrokes.push_back(Keystroke(button, false, false, data)); + } + } + else { + // toggle (click) + keystrokes.push_back(Keystroke(button, true, false, data)); + keystrokes.push_back(Keystroke(button, false, false, data)); + } + } + else if (type == kKeystrokeModify) { + // press modifier + keystrokes.push_back(Keystroke(button, true, false, data)); + } + else { + // release all the keys that generate the modifier that are + // currently down + std::pair range = + activeModifiers.equal_range(keyItem.m_generates); + for (ModifierToKeys::const_iterator i = range.first; + i != range.second; ++i) { + keystrokes.push_back(Keystroke(i->second.m_button, + false, false, i->second.m_client)); + } + } + + if (type == kKeystrokeModify) { + activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + currentState |= keyItem.m_generates; + } + else { + activeModifiers.erase(keyItem.m_generates); + currentState &= ~keyItem.m_generates; + } + break; + } +} + +SInt32 +CKeyMap::getNumModifiers(KeyModifierMask state) +{ + SInt32 n = 0; + for (; state != 0; state >>= 1) { + if ((state & 1) != 0) { + ++n; + } + } + return n; +} + +bool +CKeyMap::isDeadKey(KeyID key) +{ + return (key == kKeyCompose || (key >= 0x0300 && key <= 0x036f)); +} + +KeyID +CKeyMap::getDeadKey(KeyID key) +{ + if (isDeadKey(key)) { + // already dead + return key; + } + + switch (key) { + case '`': + return kKeyDeadGrave; + + case 0xb4u: + return kKeyDeadAcute; + + case '^': + case 0x2c6: + return kKeyDeadCircumflex; + + case '~': + case 0x2dcu: + return kKeyDeadTilde; + + case 0xafu: + return kKeyDeadMacron; + + case 0x2d8u: + return kKeyDeadBreve; + + case 0x2d9u: + return kKeyDeadAbovedot; + + case 0xa8u: + return kKeyDeadDiaeresis; + + case 0xb0u: + case 0x2dau: + return kKeyDeadAbovering; + + case '\"': + case 0x2ddu: + return kKeyDeadDoubleacute; + + case 0x2c7u: + return kKeyDeadCaron; + + case 0xb8u: + return kKeyDeadCedilla; + + case 0x2dbu: + return kKeyDeadOgonek; + + default: + // unknown + return kKeyNone; + } +} + +CString +CKeyMap::formatKey(KeyID key, KeyModifierMask mask) +{ + // initialize tables + initKeyNameMaps(); + + CString x; + for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) { + KeyModifierMask mod = (1u << i); + if ((mask & mod) != 0 && s_modifierToNameMap->count(mod) > 0) { + x += s_modifierToNameMap->find(mod)->second; + x += "+"; + } + } + if (key != kKeyNone) { + if (s_keyToNameMap->count(key) > 0) { + x += s_keyToNameMap->find(key)->second; + } + // XXX -- we're assuming ASCII here + else if (key >= 33 && key < 127) { + x += (char)key; + } + else { + x += CStringUtil::print("\\u%04x", key); + } + } + else if (!x.empty()) { + // remove trailing '+' + x.erase(x.size() - 1); + } + return x; +} + +bool +CKeyMap::parseKey(const CString& x, KeyID& key) +{ + // initialize tables + initKeyNameMaps(); + + // parse the key + key = kKeyNone; + if (s_nameToKeyMap->count(x) > 0) { + key = s_nameToKeyMap->find(x)->second; + } + // XXX -- we're assuming ASCII encoding here + else if (x.size() == 1) { + if (!isgraph(x[0])) { + // unknown key + return false; + } + key = (KeyID)x[0]; + } + else if (x.size() == 6 && x[0] == '\\' && x[1] == 'u') { + // escaped unicode (\uXXXX where XXXX is a hex number) + char* end; + key = (KeyID)strtol(x.c_str() + 2, &end, 16); + if (*end != '\0') { + return false; + } + } + else if (!x.empty()) { + // unknown key + return false; + } + + return true; +} + +bool +CKeyMap::parseModifiers(CString& x, KeyModifierMask& mask) +{ + // initialize tables + initKeyNameMaps(); + + mask = 0; + CString::size_type tb = x.find_first_not_of(" \t", 0); + while (tb != CString::npos) { + // get next component + CString::size_type te = x.find_first_of(" \t+)", tb); + if (te == CString::npos) { + te = x.size(); + } + CString c = x.substr(tb, te - tb); + if (c.empty()) { + // missing component + return false; + } + + if (s_nameToModifierMap->count(c) > 0) { + KeyModifierMask mod = s_nameToModifierMap->find(c)->second; + if ((mask & mod) != 0) { + // modifier appears twice + return false; + } + mask |= mod; + } + else { + // unknown string + x.erase(0, tb); + CString::size_type tb = x.find_first_not_of(" \t"); + CString::size_type te = x.find_last_not_of(" \t"); + if (tb == CString::npos) { + x = ""; + } + else { + x = x.substr(tb, te - tb + 1); + } + return true; + } + + // check for '+' or end of string + tb = x.find_first_not_of(" \t", te); + if (tb != CString::npos) { + if (x[tb] != '+') { + // expected '+' + return false; + } + tb = x.find_first_not_of(" \t", tb + 1); + } + } + + // parsed the whole thing + x = ""; + return true; +} + +void +CKeyMap::initKeyNameMaps() +{ + // initialize tables + if (s_nameToKeyMap == NULL) { + s_nameToKeyMap = new CNameToKeyMap; + s_keyToNameMap = new CKeyToNameMap; + for (const KeyNameMapEntry* i = kKeyNameMap; i->m_name != NULL; ++i) { + (*s_nameToKeyMap)[i->m_name] = i->m_id; + (*s_keyToNameMap)[i->m_id] = i->m_name; + } + } + if (s_nameToModifierMap == NULL) { + s_nameToModifierMap = new CNameToModifierMap; + s_modifierToNameMap = new CModifierToNameMap; + for (const KeyModifierNameMapEntry* i = kModifierNameMap; + i->m_name != NULL; ++i) { + (*s_nameToModifierMap)[i->m_name] = i->m_mask; + (*s_modifierToNameMap)[i->m_mask] = i->m_name; + } + } +} + + +// +// CKeyMap::KeyItem +// + +bool +CKeyMap::KeyItem::operator==(const KeyItem& x) const +{ + return (m_id == x.m_id && + m_group == x.m_group && + m_button == x.m_button && + m_required == x.m_required && + m_sensitive == x.m_sensitive && + m_generates == x.m_generates && + m_dead == x.m_dead && + m_lock == x.m_lock && + m_client == x.m_client); +} + + +// +// CKeyMap::Keystroke +// + +CKeyMap::Keystroke::Keystroke(KeyButton button, + bool press, bool repeat, UInt32 data) : + m_type(kButton) +{ + m_data.m_button.m_button = button; + m_data.m_button.m_press = press; + m_data.m_button.m_repeat = repeat; + m_data.m_button.m_client = data; +} + +CKeyMap::Keystroke::Keystroke(SInt32 group, bool absolute, bool restore) : + m_type(kGroup) +{ + m_data.m_group.m_group = group; + m_data.m_group.m_absolute = absolute; + m_data.m_group.m_restore = restore; +} diff --git a/lib/synergy/CKeyMap.h b/lib/synergy/CKeyMap.h new file mode 100644 index 00000000..7f34113b --- /dev/null +++ b/lib/synergy/CKeyMap.h @@ -0,0 +1,491 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2005 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CKEYMAP_H +#define CKEYMAP_H + +#include "KeyTypes.h" +#include "CString.h" +#include "CStringUtil.h" +#include "stdmap.h" +#include "stdset.h" +#include "stdvector.h" + +//! Key map +/*! +This class provides a keyboard mapping. +*/ +class CKeyMap { +public: + CKeyMap(); + ~CKeyMap(); + + //! KeyID synthesis info + /*! + This structure contains the information necessary to synthesize a + keystroke that generates a KeyID (stored elsewhere). \c m_sensitive + lists the modifiers that the key is affected by and must therefore + be in the correct state, which is listed in \c m_required. If the + key is mapped to a modifier, that modifier is in \c m_generates and + is not in \c m_sensitive. + */ + struct KeyItem { + public: + KeyID m_id; //!< KeyID + SInt32 m_group; //!< Group for key + KeyButton m_button; //!< Button to generate KeyID + KeyModifierMask m_required; //!< Modifiers required for KeyID + KeyModifierMask m_sensitive; //!< Modifiers key is sensitive to + KeyModifierMask m_generates; //!< Modifiers key is mapped to + bool m_dead; //!< \c true if this is a dead KeyID + bool m_lock; //!< \c true if this locks a modifier + UInt32 m_client; //!< Client data + + public: + bool operator==(const KeyItem&) const; + }; + + //! The KeyButtons needed to synthesize a KeyID + /*! + An ordered list of \c KeyItems produces a particular KeyID. If + the KeyID can be synthesized directly then there is one entry in + the list. If dead keys are required then they're listed first. + A list is the minimal set of keystrokes necessary to synthesize + the KeyID, so it doesn't include no-ops. A list does not include + any modifier keys unless the KeyID is a modifier, in which case + it has exactly one KeyItem for the modifier itself. + */ + typedef std::vector KeyItemList; + + //! A keystroke + class Keystroke { + public: + enum EType { + kButton, //!< Synthesize button + kGroup //!< Set new group + }; + + Keystroke(KeyButton, bool press, bool repeat, UInt32 clientData); + Keystroke(SInt32 group, bool absolute, bool restore); + + public: + struct CButton { + public: + KeyButton m_button; //!< Button to synthesize + bool m_press; //!< \c true iff press + bool m_repeat; //!< \c true iff for an autorepeat + UInt32 m_client; //!< Client data + }; + struct CGroup { + public: + SInt32 m_group; //!< Group/offset to change to/by + bool m_absolute; //!< \c true iff change to, else by + bool m_restore; //!< \c true iff for restoring state + }; + union CData { + public: + CButton m_button; + CGroup m_group; + }; + + EType m_type; + CData m_data; + }; + + //! A sequence of keystrokes + typedef std::vector Keystrokes; + + //! A mapping of a modifier to keys for that modifier + typedef std::multimap ModifierToKeys; + + //! A set of buttons + typedef std::map ButtonToKeyMap; + + //! Callback type for \c foreachKey + typedef void (*ForeachKeyCallback)(KeyID, SInt32 group, + KeyItem&, void* userData); + + //! @name manipulators + //@{ + + //! Swap with another \c CKeyMap + void swap(CKeyMap&); + + //! Add a key entry + /*! + Adds \p item to the entries for the item's id and group. The + \c m_dead member is set automatically. + */ + void addKeyEntry(const KeyItem& item); + + //! Add an alias key entry + /*! + If \p targetID with the modifiers given by \p targetRequired and + \p targetSensitive is not available in group \p group then find an + entry for \p sourceID with modifiers given by \p sourceRequired and + \p sourceSensitive in any group with exactly one item and, if found, + add a new item just like it except using id \p targetID. This + effectively makes the \p sourceID an alias for \p targetID (i.e. we + can generate \p targetID using \p sourceID). + */ + void addKeyAliasEntry(KeyID targetID, SInt32 group, + KeyModifierMask targetRequired, + KeyModifierMask targetSensitive, + KeyID sourceID, + KeyModifierMask sourceRequired, + KeyModifierMask sourceSensitive); + + //! Add a key sequence entry + /*! + Adds the sequence of keys \p keys (\p numKeys elements long) to + synthesize key \p id in group \p group. This looks up in the + map each key in \p keys. If all are found then each key is + converted to the button for that key and the buttons are added + as the entry for \p id. If \p id is already in the map or at + least one key in \p keys is not in the map then nothing is added + and this returns \c false, otherwise it returns \c true. + */ + bool addKeyCombinationEntry(KeyID id, SInt32 group, + const KeyID* keys, UInt32 numKeys); + + //! Enable composition across groups + /*! + If called then the keyboard map will allow switching between groups + during key composition. Not all systems allow that. + */ + void allowGroupSwitchDuringCompose(); + + //! Add a half-duplex button + /*! + Records that button \p button is a half-duplex key. This is called + when translating the system's keyboard map. It's independent of the + half-duplex modifier calls. + */ + void addHalfDuplexButton(KeyButton button); + + //! Remove all half-duplex modifiers + /*! + Removes all half-duplex modifiers. This is called to set user + configurable half-duplex settings. + */ + void clearHalfDuplexModifiers(); + + //! Add a half-duplex modifier + /*! + Records that modifier key \p key is half-duplex. This is called to + set user configurable half-duplex settings. + */ + void addHalfDuplexModifier(KeyID key); + + //! Finish adding entries + /*! + Called after adding entries, this does some internal housekeeping. + */ + void finish(); + + //! Iterate over all added keys items + /*! + Calls \p cb for every key item. + */ + void foreachKey(ForeachKeyCallback cb, void* userData); + + //@} + //! @name accessors + //@{ + + //! Map key press/repeat to keystrokes. + /*! + Converts press/repeat of key \p id in group \p group with current + modifiers as given in \p currentState and the desired modifiers in + \p desiredMask into the keystrokes necessary to synthesize that key + event in \p keys. It returns the \c KeyItem of the key being + pressed/repeated, or NULL if the key cannot be mapped. + */ + const KeyItem* mapKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + //! Get number of groups + /*! + Returns the number of keyboard groups (independent layouts) in the map. + */ + SInt32 getNumGroups() const; + + //! Compute a group number + /*! + Returns the number of the group \p offset groups after group \p group. + */ + SInt32 getEffectiveGroup(SInt32 group, SInt32 offset) const; + + //! Find key entry compatible with modifiers + /*! + Returns the \c KeyItemList for the first entry for \p id in group + \p group that is compatible with the given modifiers, or NULL + if there isn't one. A button list is compatible with a modifiers + if it is either insensitive to all modifiers in \p sensitive or + it requires the modifiers to be in the state indicated by \p required + for every modifier indicated by \p sensitive. + */ + const KeyItemList* findCompatibleKey(KeyID id, SInt32 group, + KeyModifierMask required, + KeyModifierMask sensitive) const; + + //! Test if modifier is half-duplex + /*! + Returns \c true iff modifier key \p key or button \p button is + half-duplex. + */ + bool isHalfDuplex(KeyID key, KeyButton button) const; + + //! Test if modifiers indicate a command + /*! + Returns \c true iff the modifiers in \p mask contain any command + modifiers. A command modifier is used for keyboard shortcuts and + hotkeys, Rather than trying to synthesize a character, a command + is trying to synthesize a particular set of buttons. So it's not + important to match the shift or AltGr state to achieve a character + but it is important to match the modifier state exactly. + */ + bool isCommand(KeyModifierMask mask) const; + + // Get the modifiers that indicate a command + /*! + Returns the modifiers that when combined with other keys indicate + a command (e.g. shortcut or hotkey). + */ + KeyModifierMask getCommandModifiers() const; + + //! Get buttons from modifier map + /*! + Put all the keys in \p modifiers into \p keys. + */ + static void collectButtons(const ModifierToKeys& modifiers, + ButtonToKeyMap& keys); + + //! Set modifier key state + /*! + Sets the modifier key state (\c m_generates and \c m_lock) in \p item + based on the \c m_id in \p item. + */ + static void initModifierKey(KeyItem& item); + + //! Test for a dead key + /*! + Returns \c true if \p key is a dead key. + */ + static bool isDeadKey(KeyID key); + + //! Get corresponding dead key + /*! + Returns the dead key corresponding to \p key if one exists, otherwise + return \c kKeyNone. This returns \p key if it's already a dead key. + */ + static KeyID getDeadKey(KeyID key); + + //! Get string for a key and modifier mask + /*! + Converts a key and modifier mask into a string representing the + combination. + */ + static CString formatKey(KeyID key, KeyModifierMask); + + //! Parse a string into a key + /*! + Converts a string into a key. Returns \c true on success and \c false + if the string cannot be parsed. + */ + static bool parseKey(const CString&, KeyID&); + + //! Parse a string into a modifier mask + /*! + Converts a string into a modifier mask. Returns \c true on success + and \c false if the string cannot be parsed. The modifiers plus any + remaining leading and trailing whitespace is stripped from the input + string. + */ + static bool parseModifiers(CString&, KeyModifierMask&); + + //@} + +private: + //! Ways to synthesize a key + enum EKeystroke { + kKeystrokePress, //!< Synthesize a press + kKeystrokeRelease, //!< Synthesize a release + kKeystrokeRepeat, //!< Synthesize an autorepeat + kKeystrokeClick, //!< Synthesize a press and release + kKeystrokeModify, //!< Synthesize pressing a modifier + kKeystrokeUnmodify //!< Synthesize releasing a modifier + }; + + // A list of ways to synthesize a KeyID + typedef std::vector KeyEntryList; + + // computes the number of groups + SInt32 findNumGroups() const; + + // computes the map of modifiers to the keys that generate the modifiers + void setModifierKeys(); + + // maps a command key. a command key is a keyboard shortcut and we're + // trying to synthesize a button press with an exact sets of modifiers, + // not trying to synthesize a character. so we just need to find the + // right button and synthesize the requested modifiers without regard + // to what character they would synthesize. we disallow multikey + // entries since they don't make sense as hotkeys. + const KeyItem* mapCommandKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // maps a character key. a character key is trying to synthesize a + // particular KeyID and isn't entirely concerned with the modifiers + // used to do it. + const KeyItem* mapCharacterKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // maps a modifier key + const KeyItem* mapModifierKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // returns the index into \p entryList of the KeyItemList requiring + // the fewest modifier changes between \p currentState and + // \p desiredState. + SInt32 findBestKey(const KeyEntryList& entryList, + KeyModifierMask currentState, + KeyModifierMask desiredState) const; + + // gets the \c KeyItem used to synthesize the modifier who's bit is + // given by \p modifierBit in group \p group and does not synthesize + // the key \p button. + const KeyItem* keyForModifier(KeyButton button, SInt32 group, + SInt32 modifierBit) const; + + // fills \p keystrokes with the keys to synthesize the key in + // \p keyItem taking the modifiers into account. returns \c true + // iff successful and sets \p currentState to the + // resulting modifier state. + bool keysForKeyItem(const KeyItem& keyItem, + SInt32& group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredState, + KeyModifierMask overrideModifiers, + bool isAutoRepeat, + Keystrokes& keystrokes) const; + + // fills \p keystrokes with the keys to synthesize the modifiers + // in \p desiredModifiers from the active modifiers listed in + // \p activeModifiers not including the key in \p keyItem. + // returns \c true iff successful. + bool keysToRestoreModifiers(const KeyItem& keyItem, + SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + const ModifierToKeys& desiredModifiers, + Keystrokes& keystrokes) const; + + // fills \p keystrokes and \p undo with the keys to change the + // current modifier state in \p currentState to match the state in + // \p requiredState for each modifier indicated in \p sensitiveMask. + // returns \c true iff successful and sets \p currentState to the + // resulting modifier state. + bool keysForModifierState(KeyButton button, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask requiredState, + KeyModifierMask sensitiveMask, + KeyModifierMask notRequiredMask, + Keystrokes& keystrokes) const; + + // Adds keystrokes to synthesize key \p keyItem in mode \p type to + // \p keystrokes and to undo the synthesis to \p undo. + void addKeystrokes(EKeystroke type, + const KeyItem& keyItem, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + Keystrokes& keystrokes) const; + + // Returns the number of modifiers indicated in \p state. + static SInt32 getNumModifiers(KeyModifierMask state); + + // Initialize key name/id maps + static void initKeyNameMaps(); + + // not implemented + CKeyMap(const CKeyMap&); + CKeyMap& operator=(const CKeyMap&); + +private: + // Ways to synthesize a KeyID over multiple keyboard groups + typedef std::vector KeyGroupTable; + + // Table of KeyID to ways to synthesize that KeyID + typedef std::map KeyIDMap; + + // List of KeyItems that generate a particular modifier + typedef std::vector ModifierKeyItemList; + + // Map a modifier to the KeyItems that synthesize that modifier + typedef std::vector ModifierToKeyTable; + + // A set of keys + typedef std::set KeySet; + + // A set of buttons + typedef std::set KeyButtonSet; + + // Key maps for parsing/formatting + typedef std::map CNameToKeyMap; + typedef std::map CNameToModifierMap; + typedef std::map CKeyToNameMap; + typedef std::map CModifierToNameMap; + + // KeyID info + KeyIDMap m_keyIDMap; + SInt32 m_numGroups; + ModifierToKeyTable m_modifierKeys; + + // composition info + bool m_composeAcrossGroups; + + // half-duplex info + KeyButtonSet m_halfDuplex; // half-duplex set by synergy + KeySet m_halfDuplexMods; // half-duplex set by user + + // dummy KeyItem for changing modifiers + KeyItem m_modifierKeyItem; + + // parsing/formatting tables + static CNameToKeyMap* s_nameToKeyMap; + static CNameToModifierMap* s_nameToModifierMap; + static CKeyToNameMap* s_keyToNameMap; + static CModifierToNameMap* s_modifierToNameMap; +}; + +#endif diff --git a/lib/synergy/CKeyState.cpp b/lib/synergy/CKeyState.cpp new file mode 100644 index 00000000..ff374828 --- /dev/null +++ b/lib/synergy/CKeyState.cpp @@ -0,0 +1,886 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CKeyState.h" +#include "IEventQueue.h" +#include "CLog.h" +#include +#include + +static const KeyButton kButtonMask = (KeyButton)(IKeyState::kNumButtons - 1); + +static const KeyID s_decomposeTable[] = { + // spacing version of dead keys + 0x0060, 0x0300, 0x0020, 0, // grave, dead_grave, space + 0x00b4, 0x0301, 0x0020, 0, // acute, dead_acute, space + 0x005e, 0x0302, 0x0020, 0, // asciicircum, dead_circumflex, space + 0x007e, 0x0303, 0x0020, 0, // asciitilde, dead_tilde, space + 0x00a8, 0x0308, 0x0020, 0, // diaeresis, dead_diaeresis, space + 0x00b0, 0x030a, 0x0020, 0, // degree, dead_abovering, space + 0x00b8, 0x0327, 0x0020, 0, // cedilla, dead_cedilla, space + 0x02db, 0x0328, 0x0020, 0, // ogonek, dead_ogonek, space + 0x02c7, 0x030c, 0x0020, 0, // caron, dead_caron, space + 0x02d9, 0x0307, 0x0020, 0, // abovedot, dead_abovedot, space + 0x02dd, 0x030b, 0x0020, 0, // doubleacute, dead_doubleacute, space + 0x02d8, 0x0306, 0x0020, 0, // breve, dead_breve, space + 0x00af, 0x0304, 0x0020, 0, // macron, dead_macron, space + + // Latin-1 (ISO 8859-1) + 0x00c0, 0x0300, 0x0041, 0, // Agrave, dead_grave, A + 0x00c1, 0x0301, 0x0041, 0, // Aacute, dead_acute, A + 0x00c2, 0x0302, 0x0041, 0, // Acircumflex, dead_circumflex, A + 0x00c3, 0x0303, 0x0041, 0, // Atilde, dead_tilde, A + 0x00c4, 0x0308, 0x0041, 0, // Adiaeresis, dead_diaeresis, A + 0x00c5, 0x030a, 0x0041, 0, // Aring, dead_abovering, A + 0x00c7, 0x0327, 0x0043, 0, // Ccedilla, dead_cedilla, C + 0x00c8, 0x0300, 0x0045, 0, // Egrave, dead_grave, E + 0x00c9, 0x0301, 0x0045, 0, // Eacute, dead_acute, E + 0x00ca, 0x0302, 0x0045, 0, // Ecircumflex, dead_circumflex, E + 0x00cb, 0x0308, 0x0045, 0, // Ediaeresis, dead_diaeresis, E + 0x00cc, 0x0300, 0x0049, 0, // Igrave, dead_grave, I + 0x00cd, 0x0301, 0x0049, 0, // Iacute, dead_acute, I + 0x00ce, 0x0302, 0x0049, 0, // Icircumflex, dead_circumflex, I + 0x00cf, 0x0308, 0x0049, 0, // Idiaeresis, dead_diaeresis, I + 0x00d1, 0x0303, 0x004e, 0, // Ntilde, dead_tilde, N + 0x00d2, 0x0300, 0x004f, 0, // Ograve, dead_grave, O + 0x00d3, 0x0301, 0x004f, 0, // Oacute, dead_acute, O + 0x00d4, 0x0302, 0x004f, 0, // Ocircumflex, dead_circumflex, O + 0x00d5, 0x0303, 0x004f, 0, // Otilde, dead_tilde, O + 0x00d6, 0x0308, 0x004f, 0, // Odiaeresis, dead_diaeresis, O + 0x00d9, 0x0300, 0x0055, 0, // Ugrave, dead_grave, U + 0x00da, 0x0301, 0x0055, 0, // Uacute, dead_acute, U + 0x00db, 0x0302, 0x0055, 0, // Ucircumflex, dead_circumflex, U + 0x00dc, 0x0308, 0x0055, 0, // Udiaeresis, dead_diaeresis, U + 0x00dd, 0x0301, 0x0059, 0, // Yacute, dead_acute, Y + 0x00e0, 0x0300, 0x0061, 0, // agrave, dead_grave, a + 0x00e1, 0x0301, 0x0061, 0, // aacute, dead_acute, a + 0x00e2, 0x0302, 0x0061, 0, // acircumflex, dead_circumflex, a + 0x00e3, 0x0303, 0x0061, 0, // atilde, dead_tilde, a + 0x00e4, 0x0308, 0x0061, 0, // adiaeresis, dead_diaeresis, a + 0x00e5, 0x030a, 0x0061, 0, // aring, dead_abovering, a + 0x00e7, 0x0327, 0x0063, 0, // ccedilla, dead_cedilla, c + 0x00e8, 0x0300, 0x0065, 0, // egrave, dead_grave, e + 0x00e9, 0x0301, 0x0065, 0, // eacute, dead_acute, e + 0x00ea, 0x0302, 0x0065, 0, // ecircumflex, dead_circumflex, e + 0x00eb, 0x0308, 0x0065, 0, // ediaeresis, dead_diaeresis, e + 0x00ec, 0x0300, 0x0069, 0, // igrave, dead_grave, i + 0x00ed, 0x0301, 0x0069, 0, // iacute, dead_acute, i + 0x00ee, 0x0302, 0x0069, 0, // icircumflex, dead_circumflex, i + 0x00ef, 0x0308, 0x0069, 0, // idiaeresis, dead_diaeresis, i + 0x00f1, 0x0303, 0x006e, 0, // ntilde, dead_tilde, n + 0x00f2, 0x0300, 0x006f, 0, // ograve, dead_grave, o + 0x00f3, 0x0301, 0x006f, 0, // oacute, dead_acute, o + 0x00f4, 0x0302, 0x006f, 0, // ocircumflex, dead_circumflex, o + 0x00f5, 0x0303, 0x006f, 0, // otilde, dead_tilde, o + 0x00f6, 0x0308, 0x006f, 0, // odiaeresis, dead_diaeresis, o + 0x00f9, 0x0300, 0x0075, 0, // ugrave, dead_grave, u + 0x00fa, 0x0301, 0x0075, 0, // uacute, dead_acute, u + 0x00fb, 0x0302, 0x0075, 0, // ucircumflex, dead_circumflex, u + 0x00fc, 0x0308, 0x0075, 0, // udiaeresis, dead_diaeresis, u + 0x00fd, 0x0301, 0x0079, 0, // yacute, dead_acute, y + 0x00ff, 0x0308, 0x0079, 0, // ydiaeresis, dead_diaeresis, y + + // Latin-2 (ISO 8859-2) + 0x0104, 0x0328, 0x0041, 0, // Aogonek, dead_ogonek, A + 0x013d, 0x030c, 0x004c, 0, // Lcaron, dead_caron, L + 0x015a, 0x0301, 0x0053, 0, // Sacute, dead_acute, S + 0x0160, 0x030c, 0x0053, 0, // Scaron, dead_caron, S + 0x015e, 0x0327, 0x0053, 0, // Scedilla, dead_cedilla, S + 0x0164, 0x030c, 0x0054, 0, // Tcaron, dead_caron, T + 0x0179, 0x0301, 0x005a, 0, // Zacute, dead_acute, Z + 0x017d, 0x030c, 0x005a, 0, // Zcaron, dead_caron, Z + 0x017b, 0x0307, 0x005a, 0, // Zabovedot, dead_abovedot, Z + 0x0105, 0x0328, 0x0061, 0, // aogonek, dead_ogonek, a + 0x013e, 0x030c, 0x006c, 0, // lcaron, dead_caron, l + 0x015b, 0x0301, 0x0073, 0, // sacute, dead_acute, s + 0x0161, 0x030c, 0x0073, 0, // scaron, dead_caron, s + 0x015f, 0x0327, 0x0073, 0, // scedilla, dead_cedilla, s + 0x0165, 0x030c, 0x0074, 0, // tcaron, dead_caron, t + 0x017a, 0x0301, 0x007a, 0, // zacute, dead_acute, z + 0x017e, 0x030c, 0x007a, 0, // zcaron, dead_caron, z + 0x017c, 0x0307, 0x007a, 0, // zabovedot, dead_abovedot, z + 0x0154, 0x0301, 0x0052, 0, // Racute, dead_acute, R + 0x0102, 0x0306, 0x0041, 0, // Abreve, dead_breve, A + 0x0139, 0x0301, 0x004c, 0, // Lacute, dead_acute, L + 0x0106, 0x0301, 0x0043, 0, // Cacute, dead_acute, C + 0x010c, 0x030c, 0x0043, 0, // Ccaron, dead_caron, C + 0x0118, 0x0328, 0x0045, 0, // Eogonek, dead_ogonek, E + 0x011a, 0x030c, 0x0045, 0, // Ecaron, dead_caron, E + 0x010e, 0x030c, 0x0044, 0, // Dcaron, dead_caron, D + 0x0143, 0x0301, 0x004e, 0, // Nacute, dead_acute, N + 0x0147, 0x030c, 0x004e, 0, // Ncaron, dead_caron, N + 0x0150, 0x030b, 0x004f, 0, // Odoubleacute, dead_doubleacute, O + 0x0158, 0x030c, 0x0052, 0, // Rcaron, dead_caron, R + 0x016e, 0x030a, 0x0055, 0, // Uring, dead_abovering, U + 0x0170, 0x030b, 0x0055, 0, // Udoubleacute, dead_doubleacute, U + 0x0162, 0x0327, 0x0054, 0, // Tcedilla, dead_cedilla, T + 0x0155, 0x0301, 0x0072, 0, // racute, dead_acute, r + 0x0103, 0x0306, 0x0061, 0, // abreve, dead_breve, a + 0x013a, 0x0301, 0x006c, 0, // lacute, dead_acute, l + 0x0107, 0x0301, 0x0063, 0, // cacute, dead_acute, c + 0x010d, 0x030c, 0x0063, 0, // ccaron, dead_caron, c + 0x0119, 0x0328, 0x0065, 0, // eogonek, dead_ogonek, e + 0x011b, 0x030c, 0x0065, 0, // ecaron, dead_caron, e + 0x010f, 0x030c, 0x0064, 0, // dcaron, dead_caron, d + 0x0144, 0x0301, 0x006e, 0, // nacute, dead_acute, n + 0x0148, 0x030c, 0x006e, 0, // ncaron, dead_caron, n + 0x0151, 0x030b, 0x006f, 0, // odoubleacute, dead_doubleacute, o + 0x0159, 0x030c, 0x0072, 0, // rcaron, dead_caron, r + 0x016f, 0x030a, 0x0075, 0, // uring, dead_abovering, u + 0x0171, 0x030b, 0x0075, 0, // udoubleacute, dead_doubleacute, u + 0x0163, 0x0327, 0x0074, 0, // tcedilla, dead_cedilla, t + + // Latin-3 (ISO 8859-3) + 0x0124, 0x0302, 0x0048, 0, // Hcircumflex, dead_circumflex, H + 0x0130, 0x0307, 0x0049, 0, // Iabovedot, dead_abovedot, I + 0x011e, 0x0306, 0x0047, 0, // Gbreve, dead_breve, G + 0x0134, 0x0302, 0x004a, 0, // Jcircumflex, dead_circumflex, J + 0x0125, 0x0302, 0x0068, 0, // hcircumflex, dead_circumflex, h + 0x011f, 0x0306, 0x0067, 0, // gbreve, dead_breve, g + 0x0135, 0x0302, 0x006a, 0, // jcircumflex, dead_circumflex, j + 0x010a, 0x0307, 0x0043, 0, // Cabovedot, dead_abovedot, C + 0x0108, 0x0302, 0x0043, 0, // Ccircumflex, dead_circumflex, C + 0x0120, 0x0307, 0x0047, 0, // Gabovedot, dead_abovedot, G + 0x011c, 0x0302, 0x0047, 0, // Gcircumflex, dead_circumflex, G + 0x016c, 0x0306, 0x0055, 0, // Ubreve, dead_breve, U + 0x015c, 0x0302, 0x0053, 0, // Scircumflex, dead_circumflex, S + 0x010b, 0x0307, 0x0063, 0, // cabovedot, dead_abovedot, c + 0x0109, 0x0302, 0x0063, 0, // ccircumflex, dead_circumflex, c + 0x0121, 0x0307, 0x0067, 0, // gabovedot, dead_abovedot, g + 0x011d, 0x0302, 0x0067, 0, // gcircumflex, dead_circumflex, g + 0x016d, 0x0306, 0x0075, 0, // ubreve, dead_breve, u + 0x015d, 0x0302, 0x0073, 0, // scircumflex, dead_circumflex, s + + // Latin-4 (ISO 8859-4) + 0x0156, 0x0327, 0x0052, 0, // Rcedilla, dead_cedilla, R + 0x0128, 0x0303, 0x0049, 0, // Itilde, dead_tilde, I + 0x013b, 0x0327, 0x004c, 0, // Lcedilla, dead_cedilla, L + 0x0112, 0x0304, 0x0045, 0, // Emacron, dead_macron, E + 0x0122, 0x0327, 0x0047, 0, // Gcedilla, dead_cedilla, G + 0x0157, 0x0327, 0x0072, 0, // rcedilla, dead_cedilla, r + 0x0129, 0x0303, 0x0069, 0, // itilde, dead_tilde, i + 0x013c, 0x0327, 0x006c, 0, // lcedilla, dead_cedilla, l + 0x0113, 0x0304, 0x0065, 0, // emacron, dead_macron, e + 0x0123, 0x0327, 0x0067, 0, // gcedilla, dead_cedilla, g + 0x0100, 0x0304, 0x0041, 0, // Amacron, dead_macron, A + 0x012e, 0x0328, 0x0049, 0, // Iogonek, dead_ogonek, I + 0x0116, 0x0307, 0x0045, 0, // Eabovedot, dead_abovedot, E + 0x012a, 0x0304, 0x0049, 0, // Imacron, dead_macron, I + 0x0145, 0x0327, 0x004e, 0, // Ncedilla, dead_cedilla, N + 0x014c, 0x0304, 0x004f, 0, // Omacron, dead_macron, O + 0x0136, 0x0327, 0x004b, 0, // Kcedilla, dead_cedilla, K + 0x0172, 0x0328, 0x0055, 0, // Uogonek, dead_ogonek, U + 0x0168, 0x0303, 0x0055, 0, // Utilde, dead_tilde, U + 0x016a, 0x0304, 0x0055, 0, // Umacron, dead_macron, U + 0x0101, 0x0304, 0x0061, 0, // amacron, dead_macron, a + 0x012f, 0x0328, 0x0069, 0, // iogonek, dead_ogonek, i + 0x0117, 0x0307, 0x0065, 0, // eabovedot, dead_abovedot, e + 0x012b, 0x0304, 0x0069, 0, // imacron, dead_macron, i + 0x0146, 0x0327, 0x006e, 0, // ncedilla, dead_cedilla, n + 0x014d, 0x0304, 0x006f, 0, // omacron, dead_macron, o + 0x0137, 0x0327, 0x006b, 0, // kcedilla, dead_cedilla, k + 0x0173, 0x0328, 0x0075, 0, // uogonek, dead_ogonek, u + 0x0169, 0x0303, 0x0075, 0, // utilde, dead_tilde, u + 0x016b, 0x0304, 0x0075, 0, // umacron, dead_macron, u + + // Latin-8 (ISO 8859-14) + 0x1e02, 0x0307, 0x0042, 0, // Babovedot, dead_abovedot, B + 0x1e03, 0x0307, 0x0062, 0, // babovedot, dead_abovedot, b + 0x1e0a, 0x0307, 0x0044, 0, // Dabovedot, dead_abovedot, D + 0x1e80, 0x0300, 0x0057, 0, // Wgrave, dead_grave, W + 0x1e82, 0x0301, 0x0057, 0, // Wacute, dead_acute, W + 0x1e0b, 0x0307, 0x0064, 0, // dabovedot, dead_abovedot, d + 0x1ef2, 0x0300, 0x0059, 0, // Ygrave, dead_grave, Y + 0x1e1e, 0x0307, 0x0046, 0, // Fabovedot, dead_abovedot, F + 0x1e1f, 0x0307, 0x0066, 0, // fabovedot, dead_abovedot, f + 0x1e40, 0x0307, 0x004d, 0, // Mabovedot, dead_abovedot, M + 0x1e41, 0x0307, 0x006d, 0, // mabovedot, dead_abovedot, m + 0x1e56, 0x0307, 0x0050, 0, // Pabovedot, dead_abovedot, P + 0x1e81, 0x0300, 0x0077, 0, // wgrave, dead_grave, w + 0x1e57, 0x0307, 0x0070, 0, // pabovedot, dead_abovedot, p + 0x1e83, 0x0301, 0x0077, 0, // wacute, dead_acute, w + 0x1e60, 0x0307, 0x0053, 0, // Sabovedot, dead_abovedot, S + 0x1ef3, 0x0300, 0x0079, 0, // ygrave, dead_grave, y + 0x1e84, 0x0308, 0x0057, 0, // Wdiaeresis, dead_diaeresis, W + 0x1e85, 0x0308, 0x0077, 0, // wdiaeresis, dead_diaeresis, w + 0x1e61, 0x0307, 0x0073, 0, // sabovedot, dead_abovedot, s + 0x0174, 0x0302, 0x0057, 0, // Wcircumflex, dead_circumflex, W + 0x1e6a, 0x0307, 0x0054, 0, // Tabovedot, dead_abovedot, T + 0x0176, 0x0302, 0x0059, 0, // Ycircumflex, dead_circumflex, Y + 0x0175, 0x0302, 0x0077, 0, // wcircumflex, dead_circumflex, w + 0x1e6b, 0x0307, 0x0074, 0, // tabovedot, dead_abovedot, t + 0x0177, 0x0302, 0x0079, 0, // ycircumflex, dead_circumflex, y + + // Latin-9 (ISO 8859-15) + 0x0178, 0x0308, 0x0059, 0, // Ydiaeresis, dead_diaeresis, Y + + // Compose key sequences + 0x00c6, kKeyCompose, 0x0041, 0x0045, 0, // AE, A, E + 0x00c1, kKeyCompose, 0x0041, 0x0027, 0, // Aacute, A, apostrophe + 0x00c2, kKeyCompose, 0x0041, 0x0053, 0, // Acircumflex, A, asciicircum + 0x00c3, kKeyCompose, 0x0041, 0x0022, 0, // Adiaeresis, A, quotedbl + 0x00c0, kKeyCompose, 0x0041, 0x0060, 0, // Agrave, A, grave + 0x00c5, kKeyCompose, 0x0041, 0x002a, 0, // Aring, A, asterisk + 0x00c3, kKeyCompose, 0x0041, 0x007e, 0, // Atilde, A, asciitilde + 0x00c7, kKeyCompose, 0x0043, 0x002c, 0, // Ccedilla, C, comma + 0x00d0, kKeyCompose, 0x0044, 0x002d, 0, // ETH, D, minus + 0x00c9, kKeyCompose, 0x0045, 0x0027, 0, // Eacute, E, apostrophe + 0x00ca, kKeyCompose, 0x0045, 0x0053, 0, // Ecircumflex, E, asciicircum + 0x00cb, kKeyCompose, 0x0045, 0x0022, 0, // Ediaeresis, E, quotedbl + 0x00c8, kKeyCompose, 0x0045, 0x0060, 0, // Egrave, E, grave + 0x00cd, kKeyCompose, 0x0049, 0x0027, 0, // Iacute, I, apostrophe + 0x00ce, kKeyCompose, 0x0049, 0x0053, 0, // Icircumflex, I, asciicircum + 0x00cf, kKeyCompose, 0x0049, 0x0022, 0, // Idiaeresis, I, quotedbl + 0x00cc, kKeyCompose, 0x0049, 0x0060, 0, // Igrave, I, grave + 0x00d1, kKeyCompose, 0x004e, 0x007e, 0, // Ntilde, N, asciitilde + 0x00d3, kKeyCompose, 0x004f, 0x0027, 0, // Oacute, O, apostrophe + 0x00d4, kKeyCompose, 0x004f, 0x0053, 0, // Ocircumflex, O, asciicircum + 0x00d6, kKeyCompose, 0x004f, 0x0022, 0, // Odiaeresis, O, quotedbl + 0x00d2, kKeyCompose, 0x004f, 0x0060, 0, // Ograve, O, grave + 0x00d8, kKeyCompose, 0x004f, 0x002f, 0, // Ooblique, O, slash + 0x00d5, kKeyCompose, 0x004f, 0x007e, 0, // Otilde, O, asciitilde + 0x00de, kKeyCompose, 0x0054, 0x0048, 0, // THORN, T, H + 0x00da, kKeyCompose, 0x0055, 0x0027, 0, // Uacute, U, apostrophe + 0x00db, kKeyCompose, 0x0055, 0x0053, 0, // Ucircumflex, U, asciicircum + 0x00dc, kKeyCompose, 0x0055, 0x0022, 0, // Udiaeresis, U, quotedbl + 0x00d9, kKeyCompose, 0x0055, 0x0060, 0, // Ugrave, U, grave + 0x00dd, kKeyCompose, 0x0059, 0x0027, 0, // Yacute, Y, apostrophe + 0x00e1, kKeyCompose, 0x0061, 0x0027, 0, // aacute, a, apostrophe + 0x00e2, kKeyCompose, 0x0061, 0x0053, 0, // acircumflex, a, asciicircum + 0x00b4, kKeyCompose, 0x0027, 0x0027, 0, // acute, apostrophe, apostrophe + 0x00e4, kKeyCompose, 0x0061, 0x0022, 0, // adiaeresis, a, quotedbl + 0x00e6, kKeyCompose, 0x0061, 0x0065, 0, // ae, a, e + 0x00e0, kKeyCompose, 0x0061, 0x0060, 0, // agrave, a, grave + 0x00e5, kKeyCompose, 0x0061, 0x002a, 0, // aring, a, asterisk + 0x0040, kKeyCompose, 0x0041, 0x0054, 0, // at, A, T + 0x00e3, kKeyCompose, 0x0061, 0x007e, 0, // atilde, a, asciitilde + 0x005c, kKeyCompose, 0x002f, 0x002f, 0, // backslash, slash, slash + 0x007c, kKeyCompose, 0x004c, 0x0056, 0, // bar, L, V + 0x007b, kKeyCompose, 0x0028, 0x002d, 0, // braceleft, parenleft, minus + 0x007d, kKeyCompose, 0x0029, 0x002d, 0, // braceright, parenright, minus + 0x005b, kKeyCompose, 0x0028, 0x0028, 0, // bracketleft, parenleft, parenleft + 0x005d, kKeyCompose, 0x0029, 0x0029, 0, // bracketright, parenright, parenright + 0x00a6, kKeyCompose, 0x0042, 0x0056, 0, // brokenbar, B, V + 0x00e7, kKeyCompose, 0x0063, 0x002c, 0, // ccedilla, c, comma + 0x00b8, kKeyCompose, 0x002c, 0x002c, 0, // cedilla, comma, comma + 0x00a2, kKeyCompose, 0x0063, 0x002f, 0, // cent, c, slash + 0x00a9, kKeyCompose, 0x0028, 0x0063, 0, // copyright, parenleft, c + 0x00a4, kKeyCompose, 0x006f, 0x0078, 0, // currency, o, x + 0x00b0, kKeyCompose, 0x0030, 0x0053, 0, // degree, 0, asciicircum + 0x00a8, kKeyCompose, 0x0022, 0x0022, 0, // diaeresis, quotedbl, quotedbl + 0x00f7, kKeyCompose, 0x003a, 0x002d, 0, // division, colon, minus + 0x00e9, kKeyCompose, 0x0065, 0x0027, 0, // eacute, e, apostrophe + 0x00ea, kKeyCompose, 0x0065, 0x0053, 0, // ecircumflex, e, asciicircum + 0x00eb, kKeyCompose, 0x0065, 0x0022, 0, // ediaeresis, e, quotedbl + 0x00e8, kKeyCompose, 0x0065, 0x0060, 0, // egrave, e, grave + 0x00f0, kKeyCompose, 0x0064, 0x002d, 0, // eth, d, minus + 0x00a1, kKeyCompose, 0x0021, 0x0021, 0, // exclamdown, exclam, exclam + 0x00ab, kKeyCompose, 0x003c, 0x003c, 0, // guillemotleft, less, less + 0x00bb, kKeyCompose, 0x003e, 0x003e, 0, // guillemotright, greater, greater + 0x0023, kKeyCompose, 0x002b, 0x002b, 0, // numbersign, plus, plus + 0x00ad, kKeyCompose, 0x002d, 0x002d, 0, // hyphen, minus, minus + 0x00ed, kKeyCompose, 0x0069, 0x0027, 0, // iacute, i, apostrophe + 0x00ee, kKeyCompose, 0x0069, 0x0053, 0, // icircumflex, i, asciicircum + 0x00ef, kKeyCompose, 0x0069, 0x0022, 0, // idiaeresis, i, quotedbl + 0x00ec, kKeyCompose, 0x0069, 0x0060, 0, // igrave, i, grave + 0x00af, kKeyCompose, 0x002d, 0x0053, 0, // macron, minus, asciicircum + 0x00ba, kKeyCompose, 0x006f, 0x005f, 0, // masculine, o, underscore + 0x00b5, kKeyCompose, 0x0075, 0x002f, 0, // mu, u, slash + 0x00d7, kKeyCompose, 0x0078, 0x0078, 0, // multiply, x, x + 0x00a0, kKeyCompose, 0x0020, 0x0020, 0, // nobreakspace, space, space + 0x00ac, kKeyCompose, 0x002c, 0x002d, 0, // notsign, comma, minus + 0x00f1, kKeyCompose, 0x006e, 0x007e, 0, // ntilde, n, asciitilde + 0x00f3, kKeyCompose, 0x006f, 0x0027, 0, // oacute, o, apostrophe + 0x00f4, kKeyCompose, 0x006f, 0x0053, 0, // ocircumflex, o, asciicircum + 0x00f6, kKeyCompose, 0x006f, 0x0022, 0, // odiaeresis, o, quotedbl + 0x00f2, kKeyCompose, 0x006f, 0x0060, 0, // ograve, o, grave + 0x00bd, kKeyCompose, 0x0031, 0x0032, 0, // onehalf, 1, 2 + 0x00bc, kKeyCompose, 0x0031, 0x0034, 0, // onequarter, 1, 4 + 0x00b9, kKeyCompose, 0x0031, 0x0053, 0, // onesuperior, 1, asciicircum + 0x00aa, kKeyCompose, 0x0061, 0x005f, 0, // ordfeminine, a, underscore + 0x00f8, kKeyCompose, 0x006f, 0x002f, 0, // oslash, o, slash + 0x00f5, kKeyCompose, 0x006f, 0x007e, 0, // otilde, o, asciitilde + 0x00b6, kKeyCompose, 0x0070, 0x0021, 0, // paragraph, p, exclam + 0x00b7, kKeyCompose, 0x002e, 0x002e, 0, // periodcentered, period, period + 0x00b1, kKeyCompose, 0x002b, 0x002d, 0, // plusminus, plus, minus + 0x00bf, kKeyCompose, 0x003f, 0x003f, 0, // questiondown, question, question + 0x00ae, kKeyCompose, 0x0028, 0x0072, 0, // registered, parenleft, r + 0x00a7, kKeyCompose, 0x0073, 0x006f, 0, // section, s, o + 0x00df, kKeyCompose, 0x0073, 0x0073, 0, // ssharp, s, s + 0x00a3, kKeyCompose, 0x004c, 0x002d, 0, // sterling, L, minus + 0x00fe, kKeyCompose, 0x0074, 0x0068, 0, // thorn, t, h + 0x00be, kKeyCompose, 0x0033, 0x0034, 0, // threequarters, 3, 4 + 0x00b3, kKeyCompose, 0x0033, 0x0053, 0, // threesuperior, 3, asciicircum + 0x00b2, kKeyCompose, 0x0032, 0x0053, 0, // twosuperior, 2, asciicircum + 0x00fa, kKeyCompose, 0x0075, 0x0027, 0, // uacute, u, apostrophe + 0x00fb, kKeyCompose, 0x0075, 0x0053, 0, // ucircumflex, u, asciicircum + 0x00fc, kKeyCompose, 0x0075, 0x0022, 0, // udiaeresis, u, quotedbl + 0x00f9, kKeyCompose, 0x0075, 0x0060, 0, // ugrave, u, grave + 0x00fd, kKeyCompose, 0x0079, 0x0027, 0, // yacute, y, apostrophe + 0x00ff, kKeyCompose, 0x0079, 0x0022, 0, // ydiaeresis, y, quotedbl + 0x00a5, kKeyCompose, 0x0079, 0x003d, 0, // yen, y, equal + + // end of table + 0 +}; + +static const KeyID s_numpadTable[] = { + kKeyKP_Space, 0x0020, + kKeyKP_Tab, kKeyTab, + kKeyKP_Enter, kKeyReturn, + kKeyKP_F1, kKeyF1, + kKeyKP_F2, kKeyF2, + kKeyKP_F3, kKeyF3, + kKeyKP_F4, kKeyF4, + kKeyKP_Home, kKeyHome, + kKeyKP_Left, kKeyLeft, + kKeyKP_Up, kKeyUp, + kKeyKP_Right, kKeyRight, + kKeyKP_Down, kKeyDown, + kKeyKP_PageUp, kKeyPageUp, + kKeyKP_PageDown, kKeyPageDown, + kKeyKP_End, kKeyEnd, + kKeyKP_Begin, kKeyBegin, + kKeyKP_Insert, kKeyInsert, + kKeyKP_Delete, kKeyDelete, + kKeyKP_Equal, 0x003d, + kKeyKP_Multiply, 0x002a, + kKeyKP_Add, 0x002b, + kKeyKP_Separator, 0x002c, + kKeyKP_Subtract, 0x002d, + kKeyKP_Decimal, 0x002e, + kKeyKP_Divide, 0x002f, + kKeyKP_0, 0x0030, + kKeyKP_1, 0x0031, + kKeyKP_2, 0x0032, + kKeyKP_3, 0x0033, + kKeyKP_4, 0x0034, + kKeyKP_5, 0x0035, + kKeyKP_6, 0x0036, + kKeyKP_7, 0x0037, + kKeyKP_8, 0x0038, + kKeyKP_9, 0x0039 +}; + +// +// CKeyState +// + +CKeyState::CKeyState() : + m_mask(0) +{ + memset(&m_keys, 0, sizeof(m_keys)); + memset(&m_syntheticKeys, 0, sizeof(m_syntheticKeys)); + memset(&m_keyClientData, 0, sizeof(m_keyClientData)); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); +} + +CKeyState::~CKeyState() +{ + // do nothing +} + +void +CKeyState::onKey(KeyButton button, bool down, KeyModifierMask newState) +{ + // update modifier state + m_mask = newState; + LOG((CLOG_DEBUG1 "new mask: 0x%04x", m_mask)); + + // ignore bogus buttons + button &= kButtonMask; + if (button == 0) { + return; + } + + // update key state + if (down) { + m_keys[button] = 1; + m_syntheticKeys[button] = 1; + } + else { + m_keys[button] = 0; + m_syntheticKeys[button] = 0; + } +} + +void +CKeyState::sendKeyEvent( + void* target, bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (m_keyMap.isHalfDuplex(key, button)) { + if (isAutoRepeat) { + // ignore auto-repeat on half-duplex keys + } + else { + EVENTQUEUE->addEvent(CEvent(getKeyDownEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + EVENTQUEUE->addEvent(CEvent(getKeyUpEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + } + } + else { + if (isAutoRepeat) { + EVENTQUEUE->addEvent(CEvent(getKeyRepeatEvent(), target, + CKeyInfo::alloc(key, mask, button, count))); + } + else if (press) { + EVENTQUEUE->addEvent(CEvent(getKeyDownEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + } + else { + EVENTQUEUE->addEvent(CEvent(getKeyUpEvent(), target, + CKeyInfo::alloc(key, mask, button, 1))); + } + } +} + +void +CKeyState::updateKeyMap() +{ + // get the current keyboard map + CKeyMap keyMap; + getKeyMap(keyMap); + m_keyMap.swap(keyMap); + m_keyMap.finish(); + + // add special keys + addCombinationEntries(); + addKeypadEntries(); + addAliasEntries(); +} + +void +CKeyState::updateKeyState() +{ + // reset our state + memset(&m_keys, 0, sizeof(m_keys)); + memset(&m_syntheticKeys, 0, sizeof(m_syntheticKeys)); + memset(&m_keyClientData, 0, sizeof(m_keyClientData)); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); + m_activeModifiers.clear(); + + // get the current keyboard state + KeyButtonSet keysDown; + pollPressedKeys(keysDown); + for (KeyButtonSet::const_iterator i = keysDown.begin(); + i != keysDown.end(); ++i) { + m_keys[*i] = 1; + } + + // get the current modifier state + m_mask = pollActiveModifiers(); + + // set active modifiers + CAddActiveModifierContext addModifierContext(pollActiveGroup(), m_mask, + m_activeModifiers); + m_keyMap.foreachKey(&CKeyState::addActiveModifierCB, &addModifierContext); + + LOG((CLOG_DEBUG1 "modifiers on update: 0x%04x", m_mask)); +} + +void +CKeyState::addActiveModifierCB(KeyID, SInt32 group, + CKeyMap::KeyItem& keyItem, void* vcontext) +{ + CAddActiveModifierContext* context = + reinterpret_cast(vcontext); + if (group == context->m_activeGroup && + (keyItem.m_generates & context->m_mask) != 0) { + context->m_activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + } +} + +void +CKeyState::setHalfDuplexMask(KeyModifierMask mask) +{ + m_keyMap.clearHalfDuplexModifiers(); + if ((mask & KeyModifierCapsLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyCapsLock); + } + if ((mask & KeyModifierNumLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyNumLock); + } + if ((mask & KeyModifierScrollLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyScrollLock); + } +} + +void +CKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) +{ + // if this server key is already down then this is probably a + // mis-reported autorepeat. + serverID &= kButtonMask; + if (m_serverKeys[serverID] != 0) { + fakeKeyRepeat(id, mask, 1, serverID); + return; + } + + // ignore certain keys + if (isIgnoredKey(id, mask)) { + LOG((CLOG_DEBUG1 "ignored key %04x %04x", id, mask)); + return; + } + + // get keys for key press + Keystrokes keys; + ModifierToKeys oldActiveModifiers = m_activeModifiers; + const CKeyMap::KeyItem* keyItem = + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, false); + if (keyItem == NULL) { + return; + } + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); + updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); + if (localID != 0) { + // note keys down + ++m_keys[localID]; + ++m_syntheticKeys[localID]; + m_keyClientData[localID] = keyItem->m_client; + m_serverKeys[serverID] = localID; + } + + // generate key events + fakeKeys(keys, 1); +} + +void +CKeyState::fakeKeyRepeat( + KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton serverID) +{ + serverID &= kButtonMask; + + // if we haven't seen this button go down then ignore it + KeyButton oldLocalID = m_serverKeys[serverID]; + if (oldLocalID == 0) { + return; + } + + // get keys for key repeat + Keystrokes keys; + ModifierToKeys oldActiveModifiers = m_activeModifiers; + const CKeyMap::KeyItem* keyItem = + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, true); + if (keyItem == NULL) { + return; + } + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); + if (localID == 0) { + return; + } + + // if the KeyButton for the auto-repeat is not the same as for the + // initial press then mark the initial key as released and the new + // key as pressed. this can happen when we auto-repeat after a + // dead key. for example, a dead accent followed by 'a' will + // generate an 'a with accent' followed by a repeating 'a'. the + // KeyButtons for the two KeyIDs might be different. + if (localID != oldLocalID) { + // replace key up with previous KeyButton but leave key down + // alone so it uses the new KeyButton. + for (Keystrokes::iterator index = keys.begin(); + index != keys.end(); ++index) { + if (index->m_type == Keystroke::kButton && + index->m_data.m_button.m_button == localID) { + index->m_data.m_button.m_button = oldLocalID; + break; + } + } + + // note that old key is now up + --m_keys[oldLocalID]; + --m_syntheticKeys[oldLocalID]; + + // note keys down + updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); + ++m_keys[localID]; + ++m_syntheticKeys[localID]; + m_keyClientData[localID] = keyItem->m_client; + m_serverKeys[serverID] = localID; + } + + // generate key events + fakeKeys(keys, count); +} + +void +CKeyState::fakeKeyUp(KeyButton serverID) +{ + // if we haven't seen this button go down then ignore it + KeyButton localID = m_serverKeys[serverID & kButtonMask]; + if (localID == 0) { + return; + } + + // get the sequence of keys to simulate key release + Keystrokes keys; + keys.push_back(Keystroke(localID, false, false, m_keyClientData[localID])); + + // note keys down + --m_keys[localID]; + --m_syntheticKeys[localID]; + m_serverKeys[serverID] = 0; + + // check if this is a modifier + for (ModifierToKeys::iterator i = m_activeModifiers.begin(); + i != m_activeModifiers.end(); ++i) { + if (i->second.m_button == localID && !i->second.m_lock) { + // modifier is no longer down + KeyModifierMask mask = i->first; + m_activeModifiers.erase(i); + + if (m_activeModifiers.count(mask) == 0) { + // no key for modifier is down so deactivate modifier + m_mask &= ~mask; + LOG((CLOG_DEBUG1 "new state %04x", m_mask)); + } + + break; + } + } + + // generate key events + fakeKeys(keys, 1); +} + +void +CKeyState::fakeAllKeysUp() +{ + Keystrokes keys; + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (m_syntheticKeys[i] > 0) { + keys.push_back(Keystroke(i, false, false, m_keyClientData[i])); + m_keys[i] = 0; + m_syntheticKeys[i] = 0; + } + } + fakeKeys(keys, 1); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); + m_activeModifiers.clear(); + m_mask = pollActiveModifiers(); +} + +bool +CKeyState::isKeyDown(KeyButton button) const +{ + return (m_keys[button & kButtonMask] > 0); +} + +KeyModifierMask +CKeyState::getActiveModifiers() const +{ + return m_mask; +} + +KeyModifierMask& +CKeyState::getActiveModifiersRValue() +{ + return m_mask; +} + +SInt32 +CKeyState::getEffectiveGroup(SInt32 group, SInt32 offset) const +{ + return m_keyMap.getEffectiveGroup(group, offset); +} + +bool +CKeyState::isIgnoredKey(KeyID key, KeyModifierMask) const +{ + switch (key) { + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + return true; + + default: + return false; + } +} + +KeyButton +CKeyState::getButton(KeyID id, SInt32 group) const +{ + const CKeyMap::KeyItemList* items = + m_keyMap.findCompatibleKey(id, group, 0, 0); + if (items == NULL) { + return 0; + } + else { + return items->back().m_button; + } +} + +void +CKeyState::addAliasEntries() +{ + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + // if we can't shift any kKeyTab key in a particular group but we can + // shift kKeyLeftTab then add a shifted kKeyTab entry that matches a + // shifted kKeyLeftTab entry. + m_keyMap.addKeyAliasEntry(kKeyTab, g, + KeyModifierShift, KeyModifierShift, + kKeyLeftTab, + KeyModifierShift, KeyModifierShift); + + // if we have no kKeyLeftTab but we do have a kKeyTab that can be + // shifted then add kKeyLeftTab that matches a kKeyTab. + m_keyMap.addKeyAliasEntry(kKeyLeftTab, g, + KeyModifierShift, KeyModifierShift, + kKeyTab, + 0, KeyModifierShift); + + // map non-breaking space to space + m_keyMap.addKeyAliasEntry(0x20, g, 0, 0, 0xa0, 0, 0); + } +} + +void +CKeyState::addKeypadEntries() +{ + // map every numpad key to its equivalent non-numpad key if it's not + // on the keyboard. + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + for (size_t i = 0; i < sizeof(s_numpadTable) / + sizeof(s_numpadTable[0]); i += 2) { + m_keyMap.addKeyCombinationEntry(s_numpadTable[i], g, + s_numpadTable + i + 1, 1); + } + } +} + +void +CKeyState::addCombinationEntries() +{ + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + // add dead and compose key composition sequences + for (const KeyID* i = s_decomposeTable; *i != 0; ++i) { + // count the decomposed keys for this key + UInt32 numKeys = 0; + for (const KeyID* j = i; *++j != 0; ) { + ++numKeys; + } + + // add an entry for this key + m_keyMap.addKeyCombinationEntry(*i, g, i + 1, numKeys); + + // next key + i += numKeys + 1; + } + } +} + +void +CKeyState::fakeKeys(const Keystrokes& keys, UInt32 count) +{ + // do nothing if no keys or no repeats + if (count == 0 || keys.empty()) { + return; + } + + // generate key events + LOG((CLOG_DEBUG1 "keystrokes:")); + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { + if (k->m_type == Keystroke::kButton && k->m_data.m_button.m_repeat) { + // repeat from here up to but not including the next key + // with m_repeat == false count times. + Keystrokes::const_iterator start = k; + while (count-- > 0) { + // send repeating events + for (k = start; k != keys.end() && + k->m_type == Keystroke::kButton && + k->m_data.m_button.m_repeat; ++k) { + fakeKey(*k); + } + } + + // note -- k is now on the first non-repeat key after the + // repeat keys, exactly where we'd like to continue from. + } + else { + // send event + fakeKey(*k); + + // next key + ++k; + } + } +} + +void +CKeyState::updateModifierKeyState(KeyButton button, + const ModifierToKeys& oldModifiers, + const ModifierToKeys& newModifiers) +{ + // get the pressed modifier buttons before and after + CKeyMap::ButtonToKeyMap oldKeys, newKeys; + for (ModifierToKeys::const_iterator i = oldModifiers.begin(); + i != oldModifiers.end(); ++i) { + oldKeys.insert(std::make_pair(i->second.m_button, &i->second)); + } + for (ModifierToKeys::const_iterator i = newModifiers.begin(); + i != newModifiers.end(); ++i) { + newKeys.insert(std::make_pair(i->second.m_button, &i->second)); + } + + // get the modifier buttons that were pressed or released + CKeyMap::ButtonToKeyMap pressed, released; + std::set_difference(oldKeys.begin(), oldKeys.end(), + newKeys.begin(), newKeys.end(), + std::inserter(released, released.end()), + ButtonToKeyLess()); + std::set_difference(newKeys.begin(), newKeys.end(), + oldKeys.begin(), oldKeys.end(), + std::inserter(pressed, pressed.end()), + ButtonToKeyLess()); + + // update state + for (CKeyMap::ButtonToKeyMap::const_iterator i = released.begin(); + i != released.end(); ++i) { + if (i->first != button) { + m_keys[i->first] = 0; + m_syntheticKeys[i->first] = 0; + } + } + for (CKeyMap::ButtonToKeyMap::const_iterator i = pressed.begin(); + i != pressed.end(); ++i) { + if (i->first != button) { + m_keys[i->first] = 1; + m_syntheticKeys[i->first] = 1; + m_keyClientData[i->first] = i->second->m_client; + } + } +} + + +// +// CKeyState::CAddActiveModifierContext +// + +CKeyState::CAddActiveModifierContext::CAddActiveModifierContext( + SInt32 group, KeyModifierMask mask, + ModifierToKeys& activeModifiers) : + m_activeGroup(group), + m_mask(mask), + m_activeModifiers(activeModifiers) +{ + // do nothing +} diff --git a/lib/synergy/CKeyState.h b/lib/synergy/CKeyState.h new file mode 100644 index 00000000..6bfd9d8b --- /dev/null +++ b/lib/synergy/CKeyState.h @@ -0,0 +1,216 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CKEYSTATE_H +#define CKEYSTATE_H + +#include "IKeyState.h" +#include "CKeyMap.h" + +//! Core key state +/*! +This class provides key state services. Subclasses must implement a few +platform specific methods. +*/ +class CKeyState : public IKeyState { +public: + CKeyState(); + virtual ~CKeyState(); + + //! @name manipulators + //@{ + + //! Handle key event + /*! + Sets the state of \p button to down or up and updates the current + modifier state to \p newState. This method should be called by + primary screens only in response to local events. For auto-repeat + set \p down to \c true. Overrides must forward to the superclass. + */ + virtual void onKey(KeyButton button, bool down, + KeyModifierMask newState); + + //! Post a key event + /*! + Posts a key event. This may adjust the event or post additional + events in some circumstances. If this is overridden it must forward + to the superclass. + */ + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + + //@} + //! @name accessors + //@{ + + //@} + + // IKeyState overrides + virtual void updateKeyMap(); + virtual void updateKeyState(); + virtual void setHalfDuplexMask(KeyModifierMask); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual void fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + virtual bool fakeCtrlAltDel() = 0; + virtual bool isKeyDown(KeyButton) const; + virtual KeyModifierMask + getActiveModifiers() const; + virtual KeyModifierMask + pollActiveModifiers() const = 0; + virtual SInt32 pollActiveGroup() const = 0; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + +protected: + typedef CKeyMap::Keystroke Keystroke; + + //! @name protected manipulators + //@{ + + //! Get the keyboard map + /*! + Fills \p keyMap with the current keyboard map. + */ + virtual void getKeyMap(CKeyMap& keyMap) = 0; + + //! Fake a key event + /*! + Synthesize an event for \p keystroke. + */ + virtual void fakeKey(const Keystroke& keystroke) = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. The state may be modified. + */ + virtual KeyModifierMask& + getActiveModifiersRValue(); + + //@} + //! @name protected accessors + //@{ + + //! Compute a group number + /*! + Returns the number of the group \p offset groups after group \p group. + */ + SInt32 getEffectiveGroup(SInt32 group, SInt32 offset) const; + + //! Check if key is ignored + /*! + Returns \c true if and only if the key should always be ignored. + The default returns \c true only for the toggle keys. + */ + virtual bool isIgnoredKey(KeyID key, KeyModifierMask mask) const; + + //! Get button for a KeyID + /*! + Return the button mapped to key \p id in group \p group if any, + otherwise returns 0. + */ + KeyButton getButton(KeyID id, SInt32 group) const; + + //@} + +private: + typedef CKeyMap::Keystrokes Keystrokes; + typedef CKeyMap::ModifierToKeys ModifierToKeys; + struct CAddActiveModifierContext { + public: + CAddActiveModifierContext(SInt32 group, KeyModifierMask mask, + ModifierToKeys& activeModifiers); + + public: + SInt32 m_activeGroup; + KeyModifierMask m_mask; + ModifierToKeys& m_activeModifiers; + + private: + // not implemented + CAddActiveModifierContext(const CAddActiveModifierContext&); + CAddActiveModifierContext& operator=(const CAddActiveModifierContext&); + }; + + class ButtonToKeyLess { + public: + bool operator()(const CKeyMap::ButtonToKeyMap::value_type& a, + const CKeyMap::ButtonToKeyMap::value_type b) const + { + return (a.first < b.first); + } + }; + + // not implemented + CKeyState(const CKeyState&); + CKeyState& operator=(const CKeyState&); + + // adds alias key sequences. these are sequences that are equivalent + // to other sequences. + void addAliasEntries(); + + // adds non-keypad key sequences for keypad KeyIDs + void addKeypadEntries(); + + // adds key sequences for combination KeyIDs (those built using + // dead keys) + void addCombinationEntries(); + + // synthesize key events. synthesize auto-repeat events count times. + void fakeKeys(const Keystrokes&, UInt32 count); + + // update key state to match changes to modifiers + void updateModifierKeyState(KeyButton button, + const ModifierToKeys& oldModifiers, + const ModifierToKeys& newModifiers); + + // active modifiers collection callback + static void addActiveModifierCB(KeyID id, SInt32 group, + CKeyMap::KeyItem& keyItem, void* vcontext); + +private: + // the keyboard map + CKeyMap m_keyMap; + + // current modifier state + KeyModifierMask m_mask; + + // the active modifiers and the buttons activating them + ModifierToKeys m_activeModifiers; + + // current keyboard state (> 0 if pressed, 0 otherwise). this is + // initialized to the keyboard state according to the system then + // it tracks synthesized events. + SInt32 m_keys[kNumButtons]; + + // synthetic keyboard state (> 0 if pressed, 0 otherwise). this + // tracks the synthesized keyboard state. if m_keys[n] > 0 but + // m_syntheticKeys[n] == 0 then the key was pressed locally and + // not synthesized yet. + SInt32 m_syntheticKeys[kNumButtons]; + + // client data for each pressed key + UInt32 m_keyClientData[kNumButtons]; + + // server keyboard state. an entry is 0 if not the key isn't pressed + // otherwise it's the local KeyButton synthesized for the server key. + KeyButton m_serverKeys[kNumButtons]; +}; + +#endif diff --git a/lib/synergy/CPacketStreamFilter.cpp b/lib/synergy/CPacketStreamFilter.cpp new file mode 100644 index 00000000..4aad9c02 --- /dev/null +++ b/lib/synergy/CPacketStreamFilter.cpp @@ -0,0 +1,190 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CPacketStreamFilter.h" +#include "IEventQueue.h" +#include "CLock.h" +#include "TMethodEventJob.h" + +// +// CPacketStreamFilter +// + +CPacketStreamFilter::CPacketStreamFilter(IStream* stream, bool adoptStream) : + CStreamFilter(stream, adoptStream), + m_size(0), + m_inputShutdown(false) +{ + // do nothing +} + +CPacketStreamFilter::~CPacketStreamFilter() +{ + // do nothing +} + +void +CPacketStreamFilter::close() +{ + CLock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + CStreamFilter::close(); +} + +UInt32 +CPacketStreamFilter::read(void* buffer, UInt32 n) +{ + if (n == 0) { + return 0; + } + + CLock lock(&m_mutex); + + // if not enough data yet then give up + if (!isReadyNoLock()) { + return 0; + } + + // read no more than what's left in the buffered packet + if (n > m_size) { + n = m_size; + } + + // read it + if (buffer != NULL) { + memcpy(buffer, m_buffer.peek(n), n); + } + m_buffer.pop(n); + m_size -= n; + + // get next packet's size if we've finished with this packet and + // there's enough data to do so. + readPacketSize(); + + if (m_inputShutdown && m_size == 0) { + EVENTQUEUE->addEvent(CEvent(getInputShutdownEvent(), + getEventTarget(), NULL)); + } + + return n; +} + +void +CPacketStreamFilter::write(const void* buffer, UInt32 count) +{ + // write the length of the payload + UInt8 length[4]; + length[0] = (UInt8)((count >> 24) & 0xff); + length[1] = (UInt8)((count >> 16) & 0xff); + length[2] = (UInt8)((count >> 8) & 0xff); + length[3] = (UInt8)( count & 0xff); + getStream()->write(length, sizeof(length)); + + // write the payload + getStream()->write(buffer, count); +} + +void +CPacketStreamFilter::shutdownInput() +{ + CLock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + CStreamFilter::shutdownInput(); +} + +bool +CPacketStreamFilter::isReady() const +{ + CLock lock(&m_mutex); + return isReadyNoLock(); +} + +UInt32 +CPacketStreamFilter::getSize() const +{ + CLock lock(&m_mutex); + return isReadyNoLock() ? m_size : 0; +} + +bool +CPacketStreamFilter::isReadyNoLock() const +{ + return (m_size != 0 && m_buffer.getSize() >= m_size); +} + +void +CPacketStreamFilter::readPacketSize() +{ + // note -- m_mutex must be locked on entry + + if (m_size == 0 && m_buffer.getSize() >= 4) { + UInt8 buffer[4]; + memcpy(buffer, m_buffer.peek(sizeof(buffer)), sizeof(buffer)); + m_buffer.pop(sizeof(buffer)); + m_size = ((UInt32)buffer[0] << 24) | + ((UInt32)buffer[1] << 16) | + ((UInt32)buffer[2] << 8) | + (UInt32)buffer[3]; + } +} + +bool +CPacketStreamFilter::readMore() +{ + // note if we have whole packet + bool wasReady = isReadyNoLock(); + + // read more data + char buffer[4096]; + UInt32 n = getStream()->read(buffer, sizeof(buffer)); + while (n > 0) { + m_buffer.write(buffer, n); + n = getStream()->read(buffer, sizeof(buffer)); + } + + // if we don't yet have the next packet size then get it, + // if possible. + readPacketSize(); + + // note if we now have a whole packet + bool isReady = isReadyNoLock(); + + // if we weren't ready before but now we are then send a + // input ready event apparently from the filtered stream. + return (wasReady != isReady); +} + +void +CPacketStreamFilter::filterEvent(const CEvent& event) +{ + if (event.getType() == getInputReadyEvent()) { + CLock lock(&m_mutex); + if (!readMore()) { + return; + } + } + else if (event.getType() == getInputShutdownEvent()) { + // discard this if we have buffered data + CLock lock(&m_mutex); + m_inputShutdown = true; + if (m_size != 0) { + return; + } + } + + // pass event + CStreamFilter::filterEvent(event); +} diff --git a/lib/synergy/CPacketStreamFilter.h b/lib/synergy/CPacketStreamFilter.h new file mode 100644 index 00000000..93ddd8fa --- /dev/null +++ b/lib/synergy/CPacketStreamFilter.h @@ -0,0 +1,55 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPACKETSTREAMFILTER_H +#define CPACKETSTREAMFILTER_H + +#include "CStreamFilter.h" +#include "CStreamBuffer.h" +#include "CMutex.h" + +//! Packetizing stream filter +/*! +Filters a stream to read and write packets. +*/ +class CPacketStreamFilter : public CStreamFilter { +public: + CPacketStreamFilter(IStream* stream, bool adoptStream = true); + ~CPacketStreamFilter(); + + // IStream overrides + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void shutdownInput(); + virtual bool isReady() const; + virtual UInt32 getSize() const; + +protected: + // CStreamFilter overrides + virtual void filterEvent(const CEvent&); + +private: + bool isReadyNoLock() const; + void readPacketSize(); + bool readMore(); + +private: + CMutex m_mutex; + UInt32 m_size; + CStreamBuffer m_buffer; + bool m_inputShutdown; +}; + +#endif diff --git a/lib/synergy/CPlatformScreen.cpp b/lib/synergy/CPlatformScreen.cpp new file mode 100644 index 00000000..ca33d5a9 --- /dev/null +++ b/lib/synergy/CPlatformScreen.cpp @@ -0,0 +1,106 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CPlatformScreen.h" + +CPlatformScreen::CPlatformScreen() +{ + // do nothing +} + +CPlatformScreen::~CPlatformScreen() +{ + // do nothing +} + +void +CPlatformScreen::updateKeyMap() +{ + getKeyState()->updateKeyMap(); +} + +void +CPlatformScreen::updateKeyState() +{ + getKeyState()->updateKeyState(); + updateButtons(); +} + +void +CPlatformScreen::setHalfDuplexMask(KeyModifierMask mask) +{ + getKeyState()->setHalfDuplexMask(mask); +} + +void +CPlatformScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + getKeyState()->fakeKeyDown(id, mask, button); +} + +void +CPlatformScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + getKeyState()->fakeKeyRepeat(id, mask, count, button); +} + +void +CPlatformScreen::fakeKeyUp(KeyButton button) +{ + getKeyState()->fakeKeyUp(button); +} + +void +CPlatformScreen::fakeAllKeysUp() +{ + getKeyState()->fakeAllKeysUp(); +} + +bool +CPlatformScreen::fakeCtrlAltDel() +{ + return getKeyState()->fakeCtrlAltDel(); +} + +bool +CPlatformScreen::isKeyDown(KeyButton button) const +{ + return getKeyState()->isKeyDown(button); +} + +KeyModifierMask +CPlatformScreen::getActiveModifiers() const +{ + return getKeyState()->getActiveModifiers(); +} + +KeyModifierMask +CPlatformScreen::pollActiveModifiers() const +{ + return getKeyState()->pollActiveModifiers(); +} + +SInt32 +CPlatformScreen::pollActiveGroup() const +{ + return getKeyState()->pollActiveGroup(); +} + +void +CPlatformScreen::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + getKeyState()->pollPressedKeys(pressedKeys); +} diff --git a/lib/synergy/CPlatformScreen.h b/lib/synergy/CPlatformScreen.h new file mode 100644 index 00000000..2e5c87f1 --- /dev/null +++ b/lib/synergy/CPlatformScreen.h @@ -0,0 +1,109 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPLATFORMSCREEN_H +#define CPLATFORMSCREEN_H + +#include "IPlatformScreen.h" + +//! Base screen implementation +/*! +This screen implementation is the superclass of all other screen +implementations. It implements a handful of methods and requires +subclasses to implement the rest. +*/ +class CPlatformScreen : public IPlatformScreen { +public: + CPlatformScreen(); + virtual ~CPlatformScreen(); + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides) = 0; + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual UInt32 registerHotKey(KeyID key, + KeyModifierMask mask) = 0; + virtual void unregisterHotKey(UInt32 id) = 0; + virtual void fakeInputBegin() = 0; + virtual void fakeInputEnd() = 0; + virtual SInt32 getJumpZoneSize() const = 0; + virtual bool isAnyMouseButtonDown() const = 0; + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + // IKeyState overrides + virtual void updateKeyMap(); + virtual void updateKeyState(); + virtual void setHalfDuplexMask(KeyModifierMask); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual void fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + virtual bool fakeCtrlAltDel(); + virtual bool isKeyDown(KeyButton) const; + virtual KeyModifierMask + getActiveModifiers() const; + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + + // IPlatformScreen overrides + virtual void enable() = 0; + virtual void disable() = 0; + virtual void enter() = 0; + virtual bool leave() = 0; + virtual bool setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void checkClipboards() = 0; + virtual void openScreensaver(bool notify) = 0; + virtual void closeScreensaver() = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const COptionsList& options) = 0; + virtual void setSequenceNumber(UInt32) = 0; + virtual bool isPrimary() const = 0; + +protected: + //! Update mouse buttons + /*! + Subclasses must implement this method to update their internal mouse + button mapping and, if desired, state tracking. + */ + virtual void updateButtons() = 0; + + //! Get the key state + /*! + Subclasses must implement this method to return the platform specific + key state object that each subclass must have. + */ + virtual IKeyState* getKeyState() const = 0; + + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent& event, void*) = 0; +}; + +#endif diff --git a/lib/synergy/CProtocolUtil.cpp b/lib/synergy/CProtocolUtil.cpp new file mode 100644 index 00000000..f0f012a1 --- /dev/null +++ b/lib/synergy/CProtocolUtil.cpp @@ -0,0 +1,538 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CProtocolUtil.h" +#include "IStream.h" +#include "CLog.h" +#include "stdvector.h" +#include +#include + +// +// CProtocolUtil +// + +void +CProtocolUtil::writef(IStream* stream, const char* fmt, ...) +{ + assert(stream != NULL); + assert(fmt != NULL); + LOG((CLOG_DEBUG2 "writef(%s)", fmt)); + + va_list args; + va_start(args, fmt); + UInt32 size = getLength(fmt, args); + va_end(args); + va_start(args, fmt); + vwritef(stream, fmt, size, args); + va_end(args); +} + +bool +CProtocolUtil::readf(IStream* stream, const char* fmt, ...) +{ + assert(stream != NULL); + assert(fmt != NULL); + LOG((CLOG_DEBUG2 "readf(%s)", fmt)); + + bool result; + va_list args; + va_start(args, fmt); + try { + vreadf(stream, fmt, args); + result = true; + } + catch (XIO&) { + result = false; + } + va_end(args); + return result; +} + +void +CProtocolUtil::vwritef(IStream* stream, + const char* fmt, UInt32 size, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // done if nothing to write + if (size == 0) { + return; + } + + // fill buffer + UInt8* buffer = new UInt8[size]; + writef(buffer, fmt, args); + + try { + // write buffer + stream->write(buffer, size); + LOG((CLOG_DEBUG2 "wrote %d bytes", size)); + + delete[] buffer; + } + catch (XBase&) { + delete[] buffer; + throw; + } +} + +void +CProtocolUtil::vreadf(IStream* stream, const char* fmt, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // begin scanning + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the data + UInt8 buffer[4]; + read(stream, buffer, len); + + // convert it + void* v = va_arg(args, void*); + switch (len) { + case 1: + // 1 byte integer + *reinterpret_cast(v) = buffer[0]; + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *reinterpret_cast(v), *reinterpret_cast(v))); + break; + + case 2: + // 2 byte integer + *reinterpret_cast(v) = + static_cast( + (static_cast(buffer[0]) << 8) | + static_cast(buffer[1])); + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *reinterpret_cast(v), *reinterpret_cast(v))); + break; + + case 4: + // 4 byte integer + *reinterpret_cast(v) = + (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *reinterpret_cast(v), *reinterpret_cast(v))); + break; + } + break; + } + + case 'I': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the vector length + UInt8 buffer[4]; + read(stream, buffer, 4); + UInt32 n = (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + + // convert it + void* v = va_arg(args, void*); + switch (len) { + case 1: + // 1 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 1); + reinterpret_cast*>(v)->push_back( + buffer[0]); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, reinterpret_cast*>(v)->back(), reinterpret_cast*>(v)->back())); + } + break; + + case 2: + // 2 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 2); + reinterpret_cast*>(v)->push_back( + static_cast( + (static_cast(buffer[0]) << 8) | + static_cast(buffer[1]))); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, reinterpret_cast*>(v)->back(), reinterpret_cast*>(v)->back())); + } + break; + + case 4: + // 4 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 4); + reinterpret_cast*>(v)->push_back( + (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3])); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, reinterpret_cast*>(v)->back(), reinterpret_cast*>(v)->back())); + } + break; + } + break; + } + + case 's': { + assert(len == 0); + + // read the string length + UInt8 buffer[128]; + read(stream, buffer, 4); + UInt32 len = (static_cast(buffer[0]) << 24) | + (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + + // use a fixed size buffer if its big enough + const bool useFixed = (len <= sizeof(buffer)); + + // allocate a buffer to read the data + UInt8* sBuffer = buffer; + if (!useFixed) { + sBuffer = new UInt8[len]; + } + + // read the data + try { + read(stream, sBuffer, len); + } + catch (...) { + if (!useFixed) { + delete[] sBuffer; + } + throw; + } + LOG((CLOG_DEBUG2 "readf: read %d byte string: %.*s", len, len, sBuffer)); + + // save the data + CString* dst = va_arg(args, CString*); + dst->assign((const char*)sBuffer, len); + + // release the buffer + if (!useFixed) { + delete[] sBuffer; + } + break; + } + + case '%': + assert(len == 0); + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // read next character + char buffer[1]; + read(stream, buffer, 1); + + // verify match + if (buffer[0] != *fmt) { + LOG((CLOG_DEBUG2 "readf: format mismatch: %c vs %c", *fmt, buffer[0])); + throw XIOReadMismatch(); + } + + // next format character + ++fmt; + } + } +} + +UInt32 +CProtocolUtil::getLength(const char* fmt, va_list args) +{ + UInt32 n = 0; + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': + assert(len == 1 || len == 2 || len == 4); + (void)va_arg(args, UInt32); + break; + + case 'I': + assert(len == 1 || len == 2 || len == 4); + switch (len) { + case 1: + len = (va_arg(args, std::vector*))->size() + 4; + break; + + case 2: + len = 2 * (va_arg(args, std::vector*))->size() + 4; + break; + + case 4: + len = 4 * (va_arg(args, std::vector*))->size() + 4; + break; + } + break; + + case 's': + assert(len == 0); + len = (va_arg(args, CString*))->size() + 4; + (void)va_arg(args, UInt8*); + break; + + case 'S': + assert(len == 0); + len = va_arg(args, UInt32) + 4; + (void)va_arg(args, UInt8*); + break; + + case '%': + assert(len == 0); + len = 1; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // accumulate size + n += len; + ++fmt; + } + else { + // regular character + ++n; + ++fmt; + } + } + return n; +} + +void +CProtocolUtil::writef(void* buffer, const char* fmt, va_list args) +{ + UInt8* dst = reinterpret_cast(buffer); + + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + const UInt32 v = va_arg(args, UInt32); + switch (len) { + case 1: + // 1 byte integer + *dst++ = static_cast(v & 0xff); + break; + + case 2: + // 2 byte integer + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + break; + + case 4: + // 4 byte integer + *dst++ = static_cast((v >> 24) & 0xff); + *dst++ = static_cast((v >> 16) & 0xff); + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + break; + + default: + assert(0 && "invalid integer format length"); + return; + } + break; + } + + case 'I': { + switch (len) { + case 1: { + // 1 byte integers + const std::vector* list = + va_arg(args, const std::vector*); + const UInt32 n = list->size(); + *dst++ = static_cast((n >> 24) & 0xff); + *dst++ = static_cast((n >> 16) & 0xff); + *dst++ = static_cast((n >> 8) & 0xff); + *dst++ = static_cast( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + *dst++ = (*list)[i]; + } + break; + } + + case 2: { + // 2 byte integers + const std::vector* list = + va_arg(args, const std::vector*); + const UInt32 n = list->size(); + *dst++ = static_cast((n >> 24) & 0xff); + *dst++ = static_cast((n >> 16) & 0xff); + *dst++ = static_cast((n >> 8) & 0xff); + *dst++ = static_cast( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + const UInt16 v = (*list)[i]; + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + } + break; + } + + case 4: { + // 4 byte integers + const std::vector* list = + va_arg(args, const std::vector*); + const UInt32 n = list->size(); + *dst++ = static_cast((n >> 24) & 0xff); + *dst++ = static_cast((n >> 16) & 0xff); + *dst++ = static_cast((n >> 8) & 0xff); + *dst++ = static_cast( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + const UInt32 v = (*list)[i]; + *dst++ = static_cast((v >> 24) & 0xff); + *dst++ = static_cast((v >> 16) & 0xff); + *dst++ = static_cast((v >> 8) & 0xff); + *dst++ = static_cast( v & 0xff); + } + break; + } + + default: + assert(0 && "invalid integer vector format length"); + return; + } + break; + } + + case 's': { + assert(len == 0); + const CString* src = va_arg(args, CString*); + const UInt32 len = (src != NULL) ? src->size() : 0; + *dst++ = static_cast((len >> 24) & 0xff); + *dst++ = static_cast((len >> 16) & 0xff); + *dst++ = static_cast((len >> 8) & 0xff); + *dst++ = static_cast( len & 0xff); + if (len != 0) { + memcpy(dst, src->data(), len); + dst += len; + } + break; + } + + case 'S': { + assert(len == 0); + const UInt32 len = va_arg(args, UInt32); + const UInt8* src = va_arg(args, UInt8*); + *dst++ = static_cast((len >> 24) & 0xff); + *dst++ = static_cast((len >> 16) & 0xff); + *dst++ = static_cast((len >> 8) & 0xff); + *dst++ = static_cast( len & 0xff); + memcpy(dst, src, len); + dst += len; + break; + } + + case '%': + assert(len == 0); + *dst++ = '%'; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // copy regular character + *dst++ = *fmt++; + } + } +} + +UInt32 +CProtocolUtil::eatLength(const char** pfmt) +{ + const char* fmt = *pfmt; + UInt32 n = 0; + for (;;) { + UInt32 d; + switch (*fmt) { + case '0': d = 0; break; + case '1': d = 1; break; + case '2': d = 2; break; + case '3': d = 3; break; + case '4': d = 4; break; + case '5': d = 5; break; + case '6': d = 6; break; + case '7': d = 7; break; + case '8': d = 8; break; + case '9': d = 9; break; + default: *pfmt = fmt; return n; + } + n = 10 * n + d; + ++fmt; + } +} + +void +CProtocolUtil::read(IStream* stream, void* vbuffer, UInt32 count) +{ + assert(stream != NULL); + assert(vbuffer != NULL); + + UInt8* buffer = reinterpret_cast(vbuffer); + while (count > 0) { + // read more + UInt32 n = stream->read(buffer, count); + + // bail if stream has hungup + if (n == 0) { + LOG((CLOG_DEBUG2 "unexpected disconnect in readf(), %d bytes left", count)); + throw XIOEndOfStream(); + } + + // prepare for next read + buffer += n; + count -= n; + } +} + + +// +// XIOReadMismatch +// + +CString +XIOReadMismatch::getWhat() const throw() +{ + return format("XIOReadMismatch", "CProtocolUtil::readf() mismatch"); +} diff --git a/lib/synergy/CProtocolUtil.h b/lib/synergy/CProtocolUtil.h new file mode 100644 index 00000000..d4019b0b --- /dev/null +++ b/lib/synergy/CProtocolUtil.h @@ -0,0 +1,95 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CPROTOCOLUTIL_H +#define CPROTOCOLUTIL_H + +#include "BasicTypes.h" +#include "XIO.h" +#include + +class IStream; + +//! Synergy protocol utilities +/*! +This class provides various functions for implementing the synergy +protocol. +*/ +class CProtocolUtil { +public: + //! Write formatted data + /*! + Write formatted binary data to a stream. \c fmt consists of + regular characters and format specifiers. Format specifiers + begin with \%. All characters not part of a format specifier + are regular and are transmitted unchanged. + + Format specifiers are: + - \%\% -- literal `\%' + - \%1i -- converts integer argument to 1 byte integer + - \%2i -- converts integer argument to 2 byte integer in NBO + - \%4i -- converts integer argument to 4 byte integer in NBO + - \%1I -- converts std::vector* to 1 byte integers + - \%2I -- converts std::vector* to 2 byte integers in NBO + - \%4I -- converts std::vector* to 4 byte integers in NBO + - \%s -- converts CString* to stream of bytes + - \%S -- converts integer N and const UInt8* to stream of N bytes + */ + static void writef(IStream*, + const char* fmt, ...); + + //! Read formatted data + /*! + Read formatted binary data from a buffer. This performs the + reverse operation of writef(). Returns true if the entire + format was successfully parsed, false otherwise. + + Format specifiers are: + - \%\% -- read (and discard) a literal `\%' + - \%1i -- reads a 1 byte integer; argument is a SInt32* or UInt32* + - \%2i -- reads an NBO 2 byte integer; arg is SInt32* or UInt32* + - \%4i -- reads an NBO 4 byte integer; arg is SInt32* or UInt32* + - \%1I -- reads 1 byte integers; arg is std::vector* + - \%2I -- reads NBO 2 byte integers; arg is std::vector* + - \%4I -- reads NBO 4 byte integers; arg is std::vector* + - \%s -- reads bytes; argument must be a CString*, \b not a char* + */ + static bool readf(IStream*, + const char* fmt, ...); + +private: + static void vwritef(IStream*, + const char* fmt, UInt32 size, va_list); + static void vreadf(IStream*, + const char* fmt, va_list); + + static UInt32 getLength(const char* fmt, va_list); + static void writef(void*, const char* fmt, va_list); + static UInt32 eatLength(const char** fmt); + static void read(IStream*, void*, UInt32); +}; + +//! Mismatched read exception +/*! +Thrown by CProtocolUtil::readf() when the data being read does not +match the format. +*/ +class XIOReadMismatch : public XIO { +public: + // XBase overrides + virtual CString getWhat() const throw(); +}; + +#endif + diff --git a/lib/synergy/CScreen.cpp b/lib/synergy/CScreen.cpp new file mode 100644 index 00000000..bff8daca --- /dev/null +++ b/lib/synergy/CScreen.cpp @@ -0,0 +1,488 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CScreen.h" +#include "IPlatformScreen.h" +#include "ProtocolTypes.h" +#include "CLog.h" +#include "IEventQueue.h" + +// +// CScreen +// + +CScreen::CScreen(IPlatformScreen* platformScreen) : + m_screen(platformScreen), + m_isPrimary(platformScreen->isPrimary()), + m_enabled(false), + m_entered(m_isPrimary), + m_screenSaverSync(true), + m_fakeInput(false) +{ + assert(m_screen != NULL); + + // reset options + resetOptions(); + + LOG((CLOG_DEBUG "opened display")); +} + +CScreen::~CScreen() +{ + if (m_enabled) { + disable(); + } + assert(!m_enabled); + assert(m_entered == m_isPrimary); + delete m_screen; + LOG((CLOG_DEBUG "closed display")); +} + +void +CScreen::enable() +{ + assert(!m_enabled); + + m_screen->updateKeyMap(); + m_screen->updateKeyState(); + m_screen->enable(); + if (m_isPrimary) { + enablePrimary(); + } + else { + enableSecondary(); + } + + // note activation + m_enabled = true; +} + +void +CScreen::disable() +{ + assert(m_enabled); + + if (!m_isPrimary && m_entered) { + leave(); + } + else if (m_isPrimary && !m_entered) { + enter(0); + } + m_screen->disable(); + if (m_isPrimary) { + disablePrimary(); + } + else { + disableSecondary(); + } + + // note deactivation + m_enabled = false; +} + +void +CScreen::enter(KeyModifierMask toggleMask) +{ + assert(m_entered == false); + LOG((CLOG_INFO "entering screen")); + + // now on screen + m_entered = true; + + m_screen->enter(); + if (m_isPrimary) { + enterPrimary(); + } + else { + enterSecondary(toggleMask); + } +} + +bool +CScreen::leave() +{ + assert(m_entered == true); + LOG((CLOG_INFO "leaving screen")); + + if (!m_screen->leave()) { + return false; + } + if (m_isPrimary) { + leavePrimary(); + } + else { + leaveSecondary(); + } + + // make sure our idea of clipboard ownership is correct + m_screen->checkClipboards(); + + // now not on screen + m_entered = false; + + return true; +} + +void +CScreen::reconfigure(UInt32 activeSides) +{ + assert(m_isPrimary); + m_screen->reconfigure(activeSides); +} + +void +CScreen::warpCursor(SInt32 x, SInt32 y) +{ + assert(m_isPrimary); + m_screen->warpCursor(x, y); +} + +void +CScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); +} + +void +CScreen::grabClipboard(ClipboardID id) +{ + m_screen->setClipboard(id, NULL); +} + +void +CScreen::screensaver(bool activate) +{ + if (!m_isPrimary) { + // activate/deactivation screen saver iff synchronization enabled + if (m_screenSaverSync) { + m_screen->screensaver(activate); + } + } +} + +void +CScreen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + assert(!m_isPrimary || m_fakeInput); + + // check for ctrl+alt+del emulation + if (id == kKeyDelete && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + LOG((CLOG_DEBUG "emulating ctrl+alt+del press")); + if (m_screen->fakeCtrlAltDel()) { + return; + } + } + m_screen->fakeKeyDown(id, mask, button); +} + +void +CScreen::keyRepeat(KeyID id, + KeyModifierMask mask, SInt32 count, KeyButton button) +{ + assert(!m_isPrimary); + m_screen->fakeKeyRepeat(id, mask, count, button); +} + +void +CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button) +{ + assert(!m_isPrimary || m_fakeInput); + m_screen->fakeKeyUp(button); +} + +void +CScreen::mouseDown(ButtonID button) +{ + assert(!m_isPrimary); + m_screen->fakeMouseButton(button, true); +} + +void +CScreen::mouseUp(ButtonID button) +{ + assert(!m_isPrimary); + m_screen->fakeMouseButton(button, false); +} + +void +CScreen::mouseMove(SInt32 x, SInt32 y) +{ + assert(!m_isPrimary); + m_screen->fakeMouseMove(x, y); +} + +void +CScreen::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + assert(!m_isPrimary); + m_screen->fakeMouseRelativeMove(dx, dy); +} + +void +CScreen::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + assert(!m_isPrimary); + m_screen->fakeMouseWheel(xDelta, yDelta); +} + +void +CScreen::resetOptions() +{ + // reset options + m_halfDuplex = 0; + + // if screen saver synchronization was off then turn it on since + // that's the default option state. + if (!m_screenSaverSync) { + m_screenSaverSync = true; + if (!m_isPrimary) { + m_screen->openScreensaver(false); + } + } + + // let screen handle its own options + m_screen->resetOptions(); +} + +void +CScreen::setOptions(const COptionsList& options) +{ + // update options + bool oldScreenSaverSync = m_screenSaverSync; + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionScreenSaverSync) { + m_screenSaverSync = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexCapsLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierCapsLock; + } + else { + m_halfDuplex &= ~KeyModifierCapsLock; + } + LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", ((m_halfDuplex & KeyModifierCapsLock) != 0) ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexNumLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierNumLock; + } + else { + m_halfDuplex &= ~KeyModifierNumLock; + } + LOG((CLOG_DEBUG1 "half-duplex num-lock %s", ((m_halfDuplex & KeyModifierNumLock) != 0) ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexScrollLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierScrollLock; + } + else { + m_halfDuplex &= ~KeyModifierScrollLock; + } + LOG((CLOG_DEBUG1 "half-duplex scroll-lock %s", ((m_halfDuplex & KeyModifierScrollLock) != 0) ? "on" : "off")); + } + } + + // update half-duplex options + m_screen->setHalfDuplexMask(m_halfDuplex); + + // update screen saver synchronization + if (!m_isPrimary && oldScreenSaverSync != m_screenSaverSync) { + if (m_screenSaverSync) { + m_screen->openScreensaver(false); + } + else { + m_screen->closeScreensaver(); + } + } + + // let screen handle its own options + m_screen->setOptions(options); +} + +void +CScreen::setSequenceNumber(UInt32 seqNum) +{ + m_screen->setSequenceNumber(seqNum); +} + +UInt32 +CScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + return m_screen->registerHotKey(key, mask); +} + +void +CScreen::unregisterHotKey(UInt32 id) +{ + m_screen->unregisterHotKey(id); +} + +void +CScreen::fakeInputBegin() +{ + assert(!m_fakeInput); + + m_fakeInput = true; + m_screen->fakeInputBegin(); +} + +void +CScreen::fakeInputEnd() +{ + assert(m_fakeInput); + + m_fakeInput = false; + m_screen->fakeInputEnd(); +} + +bool +CScreen::isOnScreen() const +{ + return m_entered; +} + +bool +CScreen::isLockedToScreen() const +{ + // check for pressed mouse buttons + if (m_screen->isAnyMouseButtonDown()) { + LOG((CLOG_DEBUG "locked by mouse button")); + return true; + } + + // not locked + return false; +} + +SInt32 +CScreen::getJumpZoneSize() const +{ + if (!m_isPrimary) { + return 0; + } + else { + return m_screen->getJumpZoneSize(); + } +} + +void +CScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +KeyModifierMask +CScreen::getActiveModifiers() const +{ + return m_screen->getActiveModifiers(); +} + +KeyModifierMask +CScreen::pollActiveModifiers() const +{ + return m_screen->pollActiveModifiers(); +} + +void* +CScreen::getEventTarget() const +{ + return m_screen; +} + +bool +CScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +CScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +CScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +CScreen::enablePrimary() +{ + // get notified of screen saver activation/deactivation + m_screen->openScreensaver(true); + + // claim screen changed size + EVENTQUEUE->addEvent(CEvent(getShapeChangedEvent(), getEventTarget())); +} + +void +CScreen::enableSecondary() +{ + // assume primary has all clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + grabClipboard(id); + } + + // disable the screen saver if synchronization is enabled + if (m_screenSaverSync) { + m_screen->openScreensaver(false); + } +} + +void +CScreen::disablePrimary() +{ + // done with screen saver + m_screen->closeScreensaver(); +} + +void +CScreen::disableSecondary() +{ + // done with screen saver + m_screen->closeScreensaver(); +} + +void +CScreen::enterPrimary() +{ + // do nothing +} + +void +CScreen::enterSecondary(KeyModifierMask) +{ + // do nothing +} + +void +CScreen::leavePrimary() +{ + // we don't track keys while on the primary screen so update our + // idea of them now. this is particularly to update the state of + // the toggle modifiers. + m_screen->updateKeyState(); +} + +void +CScreen::leaveSecondary() +{ + // release any keys we think are still down + m_screen->fakeAllKeysUp(); +} diff --git a/lib/synergy/CScreen.h b/lib/synergy/CScreen.h new file mode 100644 index 00000000..4d216f8b --- /dev/null +++ b/lib/synergy/CScreen.h @@ -0,0 +1,304 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CSCREEN_H +#define CSCREEN_H + +#include "IScreen.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" + +class IClipboard; +class IPlatformScreen; + +//! Platform independent screen +/*! +This is a platform independent screen. It can work as either a +primary or secondary screen. +*/ +class CScreen : public IScreen { +public: + CScreen(IPlatformScreen* platformScreen); + virtual ~CScreen(); + + //! @name manipulators + //@{ + + //! Activate screen + /*! + Activate the screen, preparing it to report system and user events. + For a secondary screen it also means disabling the screen saver if + synchronizing it and preparing to synthesize events. + */ + void enable(); + + //! Deactivate screen + /*! + Undoes the operations in activate() and events are no longer + reported. It also releases keys that are logically pressed. + */ + void disable(); + + //! Enter screen + /*! + Called when the user navigates to this screen. \p toggleMask has the + toggle keys that should be turned on on the secondary screen. + */ + void enter(KeyModifierMask toggleMask); + + //! Leave screen + /*! + Called when the user navigates off this screen. + */ + bool leave(); + + //! Update configuration + /*! + This is called when the configuration has changed. \c activeSides + is a bitmask of EDirectionMask indicating which sides of the + primary screen are linked to clients. + */ + void reconfigure(UInt32 activeSides); + + //! Warp cursor + /*! + Warps the cursor to the absolute coordinates \c x,y. Also + discards input events up to and including the warp before + returning. + */ + void warpCursor(SInt32 x, SInt32 y); + + //! Set clipboard + /*! + Sets the system's clipboard contents. This is usually called + soon after an enter(). + */ + void setClipboard(ClipboardID, const IClipboard*); + + //! Grab clipboard + /*! + Grabs (i.e. take ownership of) the system clipboard. + */ + void grabClipboard(ClipboardID); + + //! Activate/deactivate screen saver + /*! + Forcibly activates the screen saver if \c activate is true otherwise + forcibly deactivates it. + */ + void screensaver(bool activate); + + //! Notify of key press + /*! + Synthesize key events to generate a press of key \c id. If possible + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). + */ + void keyDown(KeyID id, KeyModifierMask, KeyButton); + + //! Notify of key repeat + /*! + Synthesize key events to generate a press and release of key \c id + \c count times. If possible match the given modifier mask. + */ + void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton); + + //! Notify of key release + /*! + Synthesize key events to generate a release of key \c id. If possible + match the given modifier mask. + */ + void keyUp(KeyID id, KeyModifierMask, KeyButton); + + //! Notify of mouse press + /*! + Synthesize mouse events to generate a press of mouse button \c id. + */ + void mouseDown(ButtonID id); + + //! Notify of mouse release + /*! + Synthesize mouse events to generate a release of mouse button \c id. + */ + void mouseUp(ButtonID id); + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion to the absolute + screen position \c xAbs,yAbs. + */ + void mouseMove(SInt32 xAbs, SInt32 yAbs); + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion by the relative + amount \c xRel,yRel. + */ + void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + + //! Notify of mouse wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c xDelta + and \c yDelta. Deltas are positive for motion away from the user or + to the right and negative for motion towards the user or to the left. + Each wheel click should generate a delta of +/-120. + */ + void mouseWheel(SInt32 xDelta, SInt32 yDelta); + + //! Notify of options changes + /*! + Resets all options to their default values. + */ + void resetOptions(); + + //! Notify of options changes + /*! + Set options to given values. Ignores unknown options and doesn't + modify options that aren't given in \c options. + */ + void setOptions(const COptionsList& options); + + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + void setSequenceNumber(UInt32); + + //! Register a system hotkey + /*! + Registers a system-wide hotkey for key \p key with modifiers \p mask. + Returns an id used to unregister the hotkey. + */ + UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + void unregisterHotKey(UInt32 id); + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() may not be + nested. + */ + void fakeInputBegin(); + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Test if cursor on screen + /*! + Returns true iff the cursor is on the screen. + */ + bool isOnScreen() const; + + //! Get screen lock state + /*! + Returns true if there's any reason that the user should not be + allowed to leave the screen (usually because a button or key is + pressed). If this method returns true it logs a message as to + why at the CLOG_DEBUG level. + */ + bool isLockedToScreen() const; + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + SInt32 getJumpZoneSize() const; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + void getCursorCenter(SInt32& x, SInt32& y) const; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. + */ + KeyModifierMask getActiveModifiers() const; + + //! Get the active modifiers from OS + /*! + Returns the modifiers that are currently active according to the + operating system. + */ + KeyModifierMask pollActiveModifiers() const; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + +protected: + void enablePrimary(); + void enableSecondary(); + void disablePrimary(); + void disableSecondary(); + + void enterPrimary(); + void enterSecondary(KeyModifierMask toggleMask); + void leavePrimary(); + void leaveSecondary(); + +private: + // our platform dependent screen + IPlatformScreen* m_screen; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if screen is enabled + bool m_enabled; + + // true if the cursor is on this screen + bool m_entered; + + // true if screen saver should be synchronized to server + bool m_screenSaverSync; + + // note toggle keys that toggles on up/down (false) or on + // transition (true) + KeyModifierMask m_halfDuplex; + + // true if we're faking input on a primary screen + bool m_fakeInput; +}; + +#endif diff --git a/lib/synergy/ClipboardTypes.h b/lib/synergy/ClipboardTypes.h new file mode 100644 index 00000000..a638e821 --- /dev/null +++ b/lib/synergy/ClipboardTypes.h @@ -0,0 +1,41 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CLIPBOARDTYPES_H +#define CLIPBOARDTYPES_H + +#include "BasicTypes.h" + +//! Clipboard ID +/*! +Type to hold a clipboard identifier. +*/ +typedef UInt8 ClipboardID; + +//! @name Clipboard identifiers +//@{ +// clipboard identifiers. kClipboardClipboard is what is normally +// considered the clipboard (e.g. the cut/copy/paste menu items +// affect it). kClipboardSelection is the selection on those +// platforms that can treat the selection as a clipboard (e.g. X +// windows). clipboard identifiers must be sequential starting +// at zero. +static const ClipboardID kClipboardClipboard = 0; +static const ClipboardID kClipboardSelection = 1; + +// the number of clipboards (i.e. one greater than the last clipboard id) +static const ClipboardID kClipboardEnd = 2; +//@} + +#endif diff --git a/lib/synergy/IClient.h b/lib/synergy/IClient.h new file mode 100644 index 00000000..81cbf06a --- /dev/null +++ b/lib/synergy/IClient.h @@ -0,0 +1,175 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ICLIENT_H +#define ICLIENT_H + +#include "IScreen.h" +#include "ClipboardTypes.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" +#include "CString.h" + +//! Client interface +/*! +This interface defines the methods necessary for the server to +communicate with a client. +*/ +class IClient : public IScreen { +public: + //! @name manipulators + //@{ + + //! Enter screen + /*! + Enter the screen. The cursor should be warped to \p xAbs,yAbs. + \p mask is the expected toggle button state and the client should + update its state to match. \p forScreensaver is true iff the + screen is being entered because the screen saver is starting. + Subsequent clipboard events should report \p seqNum. + */ + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + + //! Leave screen + /*! + Leave the screen. Return false iff the user may not leave the + client's screen (because, for example, a button is down). + */ + virtual bool leave() = 0; + + //! Set clipboard + /*! + Update the client's clipboard. This implies that the client's + clipboard is now up to date. If the client's clipboard was + already known to be up to date then this may do nothing. \c data + has marshalled clipboard data. + */ + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + + //! Grab clipboard + /*! + Grab (i.e. take ownership of) the client's clipboard. Since this + is called when another client takes ownership of the clipboard it + implies that the client's clipboard is out of date. + */ + virtual void grabClipboard(ClipboardID) = 0; + + //! Mark clipboard dirty + /*! + Mark the client's clipboard as dirty (out of date) or clean (up to + date). + */ + virtual void setClipboardDirty(ClipboardID, bool dirty) = 0; + + //! Notify of key press + /*! + Synthesize key events to generate a press of key \c id. If possible + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). + */ + virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; + + //! Notify of key repeat + /*! + Synthesize key events to generate a press and release of key \c id + \c count times. If possible match the given modifier mask. + */ + virtual void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton) = 0; + + //! Notify of key release + /*! + Synthesize key events to generate a release of key \c id. If possible + match the given modifier mask. + */ + virtual void keyUp(KeyID id, KeyModifierMask, KeyButton) = 0; + + //! Notify of mouse press + /*! + Synthesize mouse events to generate a press of mouse button \c id. + */ + virtual void mouseDown(ButtonID id) = 0; + + //! Notify of mouse release + /*! + Synthesize mouse events to generate a release of mouse button \c id. + */ + virtual void mouseUp(ButtonID id) = 0; + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion to the absolute + screen position \c xAbs,yAbs. + */ + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion by the relative + amount \c xRel,yRel. + */ + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + + //! Notify of mouse wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c xDelta + and \c yDelta. Deltas are positive for motion away from the user or + to the right and negative for motion towards the user or to the left. + Each wheel click should generate a delta of +/-120. + */ + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + + //! Notify of screen saver change + virtual void screensaver(bool activate) = 0; + + //! Notify of options changes + /*! + Reset all options to their default values. + */ + virtual void resetOptions() = 0; + + //! Notify of options changes + /*! + Set options to given values. Ignore unknown options and don't + modify our options that aren't given in \c options. + */ + virtual void setOptions(const COptionsList& options) = 0; + + //@} + //! @name accessors + //@{ + + //! Get client name + /*! + Return the client's name. + */ + virtual CString getName() const = 0; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; +}; + +#endif diff --git a/lib/synergy/IClipboard.cpp b/lib/synergy/IClipboard.cpp new file mode 100644 index 00000000..7a055612 --- /dev/null +++ b/lib/synergy/IClipboard.cpp @@ -0,0 +1,155 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IClipboard.h" +#include "stdvector.h" + +// +// IClipboard +// + +void +IClipboard::unmarshall(IClipboard* clipboard, const CString& data, Time time) +{ + assert(clipboard != NULL); + + const char* index = data.data(); + + // clear existing data + clipboard->open(time); + clipboard->empty(); + + // read the number of formats + const UInt32 numFormats = readUInt32(index); + index += 4; + + // read each format + for (UInt32 i = 0; i < numFormats; ++i) { + // get the format id + IClipboard::EFormat format = + static_cast(readUInt32(index)); + index += 4; + + // get the size of the format data + UInt32 size = readUInt32(index); + index += 4; + + // save the data if it's a known format. if either the client + // or server supports more clipboard formats than the other + // then one of them will get a format >= kNumFormats here. + if (format add(format, CString(index, size)); + } + index += size; + } + + // done + clipboard->close(); +} + +CString +IClipboard::marshall(const IClipboard* clipboard) +{ + assert(clipboard != NULL); + + CString data; + + std::vector formatData; + formatData.resize(IClipboard::kNumFormats); + // FIXME -- use current time + clipboard->open(0); + + // compute size of marshalled data + UInt32 size = 4; + UInt32 numFormats = 0; + for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) { + if (clipboard->has(static_cast(format))) { + ++numFormats; + formatData[format] = + clipboard->get(static_cast(format)); + size += 4 + 4 + formatData[format].size(); + } + } + + // allocate space + data.reserve(size); + + // marshall the data + writeUInt32(&data, numFormats); + for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) { + if (clipboard->has(static_cast(format))) { + writeUInt32(&data, format); + writeUInt32(&data, formatData[format].size()); + data += formatData[format]; + } + } + clipboard->close(); + + return data; +} + +bool +IClipboard::copy(IClipboard* dst, const IClipboard* src) +{ + assert(dst != NULL); + assert(src != NULL); + + return copy(dst, src, src->getTime()); +} + +bool +IClipboard::copy(IClipboard* dst, const IClipboard* src, Time time) +{ + assert(dst != NULL); + assert(src != NULL); + + bool success = false; + if (src->open(time)) { + if (dst->open(time)) { + if (dst->empty()) { + for (SInt32 format = 0; + format != IClipboard::kNumFormats; ++format) { + IClipboard::EFormat eFormat = (IClipboard::EFormat)format; + if (src->has(eFormat)) { + dst->add(eFormat, src->get(eFormat)); + } + } + success = true; + } + dst->close(); + } + src->close(); + } + + return success; +} + +UInt32 +IClipboard::readUInt32(const char* buf) +{ + const unsigned char* ubuf = reinterpret_cast(buf); + return (static_cast(ubuf[0]) << 24) | + (static_cast(ubuf[1]) << 16) | + (static_cast(ubuf[2]) << 8) | + static_cast(ubuf[3]); +} + +void +IClipboard::writeUInt32(CString* buf, UInt32 v) +{ + *buf += static_cast((v >> 24) & 0xff); + *buf += static_cast((v >> 16) & 0xff); + *buf += static_cast((v >> 8) & 0xff); + *buf += static_cast( v & 0xff); +} diff --git a/lib/synergy/IClipboard.h b/lib/synergy/IClipboard.h new file mode 100644 index 00000000..c883da8a --- /dev/null +++ b/lib/synergy/IClipboard.h @@ -0,0 +1,168 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ICLIPBOARD_H +#define ICLIPBOARD_H + +#include "IInterface.h" +#include "CString.h" +#include "BasicTypes.h" + +//! Clipboard interface +/*! +This interface defines the methods common to all clipboards. +*/ +class IClipboard : public IInterface { +public: + //! Timestamp type + /*! + Timestamp type. Timestamps are in milliseconds from some + arbitrary starting time. Timestamps will wrap around to 0 + after about 49 3/4 days. + */ + typedef UInt32 Time; + + //! Clipboard formats + /*! + The list of known clipboard formats. kNumFormats must be last and + formats must be sequential starting from zero. Clipboard data set + via add() and retrieved via get() must be in one of these formats. + Platform dependent clipboard subclasses can and should present any + suitable formats derivable from these formats. + + \c kText is a text format encoded in UTF-8. Newlines are LF (not + CR or LF/CR). + + \k kBitmap is an image format. The data is a BMP file without the + 14 byte header (i.e. starting at the INFOHEADER) and with the image + data immediately following the 40 byte INFOHEADER. + + \k kHTML is a text format encoded in UTF-8 and containing a valid + HTML fragment (but not necessarily a complete HTML document). + Newlines are LF. + */ + enum EFormat { + kText, //!< Text format, UTF-8, newline is LF + kBitmap, //!< Bitmap format, BMP 24/32bpp, BI_RGB + kHTML, //!< HTML format, HTML fragment, UTF-8, newline is LF + kNumFormats //!< The number of clipboard formats + }; + + //! @name manipulators + //@{ + + //! Empty clipboard + /*! + Take ownership of the clipboard and clear all data from it. + This must be called between a successful open() and close(). + Return false if the clipboard ownership could not be taken; + the clipboard should not be emptied in this case. + */ + virtual bool empty() = 0; + + //! Add data + /*! + Add data in the given format to the clipboard. May only be + called after a successful empty(). + */ + virtual void add(EFormat, const CString& data) = 0; + + //@} + //! @name accessors + //@{ + + //! Open clipboard + /*! + Open the clipboard. Return true iff the clipboard could be + opened. If open() returns true then the client must call + close() at some later time; if it returns false then close() + must not be called. \c time should be the current time or + a time in the past when the open should effectively have taken + place. + */ + virtual bool open(Time time) const = 0; + + //! Close clipboard + /*! + Close the clipboard. close() must match a preceding successful + open(). This signals that the clipboard has been filled with + all the necessary data or all data has been read. It does not + mean the clipboard ownership should be released (if it was + taken). + */ + virtual void close() const = 0; + + //! Get time + /*! + Return the timestamp passed to the last successful open(). + */ + virtual Time getTime() const = 0; + + //! Check for data + /*! + Return true iff the clipboard contains data in the given + format. Must be called between a successful open() and close(). + */ + virtual bool has(EFormat) const = 0; + + //! Get data + /*! + Return the data in the given format. Returns the empty string + if there is no data in that format. Must be called between + a successful open() and close(). + */ + virtual CString get(EFormat) const = 0; + + //! Marshall clipboard data + /*! + Merge \p clipboard's data into a single buffer that can be later + unmarshalled to restore the clipboard and return the buffer. + */ + static CString marshall(const IClipboard* clipboard); + + //! Unmarshall clipboard data + /*! + Extract marshalled clipboard data and store it in \p clipboard. + Sets the clipboard time to \c time. + */ + static void unmarshall(IClipboard* clipboard, + const CString& data, Time time); + + //! Copy clipboard + /*! + Transfers all the data in one clipboard to another. The + clipboards can be of any concrete clipboard type (and + they don't have to be the same type). This also sets + the destination clipboard's timestamp to source clipboard's + timestamp. Returns true iff the copy succeeded. + */ + static bool copy(IClipboard* dst, const IClipboard* src); + + //! Copy clipboard + /*! + Transfers all the data in one clipboard to another. The + clipboards can be of any concrete clipboard type (and they + don't have to be the same type). This also sets the + timestamp to \c time. Returns true iff the copy succeeded. + */ + static bool copy(IClipboard* dst, const IClipboard* src, Time); + + //@} + +private: + static UInt32 readUInt32(const char*); + static void writeUInt32(CString*, UInt32); +}; + +#endif diff --git a/lib/synergy/IKeyState.cpp b/lib/synergy/IKeyState.cpp new file mode 100644 index 00000000..d44e17d3 --- /dev/null +++ b/lib/synergy/IKeyState.cpp @@ -0,0 +1,176 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IKeyState.h" +#include + +// +// IKeyState +// + +CEvent::Type IKeyState::s_keyDownEvent = CEvent::kUnknown; +CEvent::Type IKeyState::s_keyUpEvent = CEvent::kUnknown; +CEvent::Type IKeyState::s_keyRepeatEvent = CEvent::kUnknown; + +CEvent::Type +IKeyState::getKeyDownEvent() +{ + return CEvent::registerTypeOnce(s_keyDownEvent, + "IKeyState::keyDown"); +} + +CEvent::Type +IKeyState::getKeyUpEvent() +{ + return CEvent::registerTypeOnce(s_keyUpEvent, + "IKeyState::keyUp"); +} + +CEvent::Type +IKeyState::getKeyRepeatEvent() +{ + return CEvent::registerTypeOnce(s_keyRepeatEvent, + "IKeyState::keyRepeat"); +} + + +// +// IKeyState::CKeyInfo +// + +IKeyState::CKeyInfo* +IKeyState::CKeyInfo::alloc(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count) +{ + CKeyInfo* info = (CKeyInfo*)malloc(sizeof(CKeyInfo)); + info->m_key = id; + info->m_mask = mask; + info->m_button = button; + info->m_count = count; + info->m_screens = NULL; + info->m_screensBuffer[0] = '\0'; + return info; +} + +IKeyState::CKeyInfo* +IKeyState::CKeyInfo::alloc(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count, + const std::set& destinations) +{ + CString screens = join(destinations); + + // build structure + CKeyInfo* info = (CKeyInfo*)malloc(sizeof(CKeyInfo) + screens.size()); + info->m_key = id; + info->m_mask = mask; + info->m_button = button; + info->m_count = count; + info->m_screens = info->m_screensBuffer; + strcpy(info->m_screensBuffer, screens.c_str()); + return info; +} + +IKeyState::CKeyInfo* +IKeyState::CKeyInfo::alloc(const CKeyInfo& x) +{ + CKeyInfo* info = (CKeyInfo*)malloc(sizeof(CKeyInfo) + + strlen(x.m_screensBuffer)); + info->m_key = x.m_key; + info->m_mask = x.m_mask; + info->m_button = x.m_button; + info->m_count = x.m_count; + info->m_screens = x.m_screens ? info->m_screensBuffer : NULL; + strcpy(info->m_screensBuffer, x.m_screensBuffer); + return info; +} + +bool +IKeyState::CKeyInfo::isDefault(const char* screens) +{ + return (screens == NULL || screens[0] == '\0'); +} + +bool +IKeyState::CKeyInfo::contains(const char* screens, const CString& name) +{ + // special cases + if (isDefault(screens)) { + return false; + } + if (screens[0] == '*') { + return true; + } + + // search + CString match; + match.reserve(name.size() + 2); + match += ":"; + match += name; + match += ":"; + return (strstr(screens, match.c_str()) != NULL); +} + +bool +IKeyState::CKeyInfo::equal(const CKeyInfo* a, const CKeyInfo* b) +{ + return (a->m_key == b->m_key && + a->m_mask == b->m_mask && + a->m_button == b->m_button && + a->m_count == b->m_count && + strcmp(a->m_screensBuffer, b->m_screensBuffer) == 0); +} + +CString +IKeyState::CKeyInfo::join(const std::set& destinations) +{ + // collect destinations into a string. names are surrounded by ':' + // which makes searching easy. the string is empty if there are no + // destinations and "*" means all destinations. + CString screens; + for (std::set::const_iterator i = destinations.begin(); + i != destinations.end(); ++i) { + if (*i == "*") { + screens = "*"; + break; + } + else { + if (screens.empty()) { + screens = ":"; + } + screens += *i; + screens += ":"; + } + } + return screens; +} + +void +IKeyState::CKeyInfo::split(const char* screens, std::set& dst) +{ + dst.clear(); + if (isDefault(screens)) { + return; + } + if (screens[0] == '*') { + dst.insert("*"); + return; + } + + const char* i = screens + 1; + while (*i != '\0') { + const char* j = strchr(i, ':'); + dst.insert(CString(i, j - i)); + i = j + 1; + } +} diff --git a/lib/synergy/IKeyState.h b/lib/synergy/IKeyState.h new file mode 100644 index 00000000..2b282487 --- /dev/null +++ b/lib/synergy/IKeyState.h @@ -0,0 +1,174 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IKEYSTATE_H +#define IKEYSTATE_H + +#include "IInterface.h" +#include "KeyTypes.h" +#include "CEvent.h" +#include "CString.h" +#include "stdset.h" + +//! Key state interface +/*! +This interface provides access to set and query the keyboard state and +to synthesize key events. +*/ +class IKeyState : public IInterface { +public: + enum { + kNumButtons = 0x200 + }; + + //! Key event data + class CKeyInfo { + public: + static CKeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count); + static CKeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count, + const std::set& destinations); + static CKeyInfo* alloc(const CKeyInfo&); + + static bool isDefault(const char* screens); + static bool contains(const char* screens, const CString& name); + static bool equal(const CKeyInfo*, const CKeyInfo*); + static CString join(const std::set& destinations); + static void split(const char* screens, std::set&); + + public: + KeyID m_key; + KeyModifierMask m_mask; + KeyButton m_button; + SInt32 m_count; + char* m_screens; + char m_screensBuffer[1]; + }; + + typedef std::set KeyButtonSet; + + //! @name manipulators + //@{ + + //! Update the keyboard map + /*! + Causes the key state to get updated to reflect the current keyboard + mapping. + */ + virtual void updateKeyMap() = 0; + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state. + */ + virtual void updateKeyState() = 0; + + //! Set half-duplex mask + /*! + Sets which modifier toggle keys are half-duplex. A half-duplex + toggle key doesn't report a key release when toggled on and + doesn't report a key press when toggled off. + */ + virtual void setHalfDuplexMask(KeyModifierMask) = 0; + + //! Fake a key press + /*! + Synthesizes a key press event and updates the key state. + */ + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) = 0; + + //! Fake a key repeat + /*! + Synthesizes a key repeat event and updates the key state. + */ + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) = 0; + + //! Fake a key release + /*! + Synthesizes a key release event and updates the key state. + */ + virtual void fakeKeyUp(KeyButton button) = 0; + + //! Fake key releases for all fake pressed keys + /*! + Synthesizes a key release event for every key that is synthetically + pressed and updates the key state. + */ + virtual void fakeAllKeysUp() = 0; + + //! Fake ctrl+alt+del + /*! + Synthesize a press of ctrl+alt+del. Return true if processing is + complete and false if normal key processing should continue. + */ + virtual bool fakeCtrlAltDel() = 0; + + //@} + //! @name accessors + //@{ + + //! Test if key is pressed + /*! + Returns true iff the given key is down. Half-duplex toggles + always return false. + */ + virtual bool isKeyDown(KeyButton) const = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. + */ + virtual KeyModifierMask + getActiveModifiers() const = 0; + + //! Get the active modifiers from OS + /*! + Returns the modifiers that are currently active according to the + operating system. + */ + virtual KeyModifierMask + pollActiveModifiers() const = 0; + + //! Get the active keyboard layout from OS + /*! + Returns the active keyboard layout according to the operating system. + */ + virtual SInt32 pollActiveGroup() const = 0; + + //! Get the keys currently pressed from OS + /*! + Adds any keys that are currently pressed according to the operating + system to \p pressedKeys. + */ + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + + //! Get key down event type. Event data is CKeyInfo*, count == 1. + static CEvent::Type getKeyDownEvent(); + //! Get key up event type. Event data is CKeyInfo*, count == 1. + static CEvent::Type getKeyUpEvent(); + //! Get key repeat event type. Event data is CKeyInfo*. + static CEvent::Type getKeyRepeatEvent(); + + //@} + +private: + static CEvent::Type s_keyDownEvent; + static CEvent::Type s_keyUpEvent; + static CEvent::Type s_keyRepeatEvent; +}; + +#endif diff --git a/lib/synergy/IPlatformScreen.h b/lib/synergy/IPlatformScreen.h new file mode 100644 index 00000000..b4d1b785 --- /dev/null +++ b/lib/synergy/IPlatformScreen.h @@ -0,0 +1,210 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IPLATFORMSCREEN_H +#define IPLATFORMSCREEN_H + +#include "IScreen.h" +#include "IPrimaryScreen.h" +#include "ISecondaryScreen.h" +#include "IKeyState.h" +#include "ClipboardTypes.h" +#include "OptionTypes.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all platform dependent +screen implementations that are used by both primary and secondary +screens. +*/ +class IPlatformScreen : public IScreen, + public IPrimaryScreen, public ISecondaryScreen, + public IKeyState { +public: + //! @name manipulators + //@{ + + //! Enable screen + /*! + Enable the screen, preparing it to report system and user events. + For a secondary screen it also means preparing to synthesize events + and hiding the cursor. + */ + virtual void enable() = 0; + + //! Disable screen + /*! + Undoes the operations in enable() and events should no longer + be reported. + */ + virtual void disable() = 0; + + //! Enter screen + /*! + Called when the user navigates to this screen. + */ + virtual void enter() = 0; + + //! Leave screen + /*! + Called when the user navigates off the screen. Returns true on + success, false on failure. A typical reason for failure is being + unable to install the keyboard and mouse snoopers on a primary + screen. Secondary screens should not fail. + */ + virtual bool leave() = 0; + + //! Set clipboard + /*! + Set the contents of the system clipboard indicated by \c id. + */ + virtual bool setClipboard(ClipboardID id, const IClipboard*) = 0; + + //! Check clipboard owner + /*! + Check ownership of all clipboards and post grab events for any that + have changed. This is used as a backup in case the system doesn't + reliably report clipboard ownership changes. + */ + virtual void checkClipboards() = 0; + + //! Open screen saver + /*! + Open the screen saver. If \c notify is true then this object must + send events when the screen saver activates or deactivates until + \c closeScreensaver() is called. If \c notify is false then the + screen saver is disabled and restored on \c closeScreensaver(). + */ + virtual void openScreensaver(bool notify) = 0; + + //! Close screen saver + /*! + // Close the screen saver. Stop reporting screen saver activation + and deactivation and, if the screen saver was disabled by + openScreensaver(), enable the screen saver. + */ + virtual void closeScreensaver() = 0; + + //! Activate/deactivate screen saver + /*! + Forcibly activate the screen saver if \c activate is true otherwise + forcibly deactivate it. + */ + virtual void screensaver(bool activate) = 0; + + //! Notify of options changes + /*! + Reset all options to their default values. + */ + virtual void resetOptions() = 0; + + //! Notify of options changes + /*! + Set options to given values. Ignore unknown options and don't + modify options that aren't given in \c options. + */ + virtual void setOptions(const COptionsList& options) = 0; + + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + virtual void setSequenceNumber(UInt32) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if is primary screen + /*! + Return true iff this screen is a primary screen. + */ + virtual bool isPrimary() const = 0; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides) = 0; + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask) = 0; + virtual void unregisterHotKey(UInt32 id) = 0; + virtual void fakeInputBegin() = 0; + virtual void fakeInputEnd() = 0; + virtual SInt32 getJumpZoneSize() const = 0; + virtual bool isAnyMouseButtonDown() const = 0; + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + // IKeyState overrides + virtual void updateKeyMap() = 0; + virtual void updateKeyState() = 0; + virtual void setHalfDuplexMask(KeyModifierMask) = 0; + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) = 0; + virtual void fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) = 0; + virtual void fakeKeyUp(KeyButton button) = 0; + virtual void fakeAllKeysUp() = 0; + virtual bool fakeCtrlAltDel() = 0; + virtual bool isKeyDown(KeyButton) const = 0; + virtual KeyModifierMask + getActiveModifiers() const = 0; + virtual KeyModifierMask + pollActiveModifiers() const = 0; + virtual SInt32 pollActiveGroup() const = 0; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + +protected: + //! Handle system event + /*! + A platform screen is expected to install a handler for system + events in its c'tor like so: + \code + EVENTQUEUE->adoptHandler(CEvent::kSystem, + IEventQueue::getSystemTarget(), + new TMethodEventJob(this, + &CXXXPlatformScreen::handleSystemEvent)); + \endcode + It should remove the handler in its d'tor. Override the + \c handleSystemEvent() method to process system events. + It should post the events \c IScreen as appropriate. + + A primary screen has further responsibilities. It should post + the events in \c IPrimaryScreen as appropriate. It should also + call \c onKey() on its \c CKeyState whenever a key is pressed + or released (but not for key repeats). And it should call + \c updateKeyMap() on its \c CKeyState if necessary when the keyboard + mapping changes. + + The target of all events should be the value returned by + \c getEventTarget(). + */ + virtual void handleSystemEvent(const CEvent& event, void*) = 0; +}; + +#endif diff --git a/lib/synergy/IPrimaryScreen.cpp b/lib/synergy/IPrimaryScreen.cpp new file mode 100644 index 00000000..71b103f3 --- /dev/null +++ b/lib/synergy/IPrimaryScreen.cpp @@ -0,0 +1,178 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IPrimaryScreen.h" + +// +// IPrimaryScreen +// + +CEvent::Type IPrimaryScreen::s_buttonDownEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_buttonUpEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_motionPrimaryEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_motionSecondaryEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_wheelEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_ssActivatedEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_ssDeactivatedEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_hotKeyDownEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_hotKeyUpEvent = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_fakeInputBegin = CEvent::kUnknown; +CEvent::Type IPrimaryScreen::s_fakeInputEnd = CEvent::kUnknown; + +CEvent::Type +IPrimaryScreen::getButtonDownEvent() +{ + return CEvent::registerTypeOnce(s_buttonDownEvent, + "IPrimaryScreen::buttonDown"); +} + +CEvent::Type +IPrimaryScreen::getButtonUpEvent() +{ + return CEvent::registerTypeOnce(s_buttonUpEvent, + "IPrimaryScreen::buttonUp"); +} + +CEvent::Type +IPrimaryScreen::getMotionOnPrimaryEvent() +{ + return CEvent::registerTypeOnce(s_motionPrimaryEvent, + "IPrimaryScreen::motionPrimary"); +} + +CEvent::Type +IPrimaryScreen::getMotionOnSecondaryEvent() +{ + return CEvent::registerTypeOnce(s_motionSecondaryEvent, + "IPrimaryScreen::motionSecondary"); +} + +CEvent::Type +IPrimaryScreen::getWheelEvent() +{ + return CEvent::registerTypeOnce(s_wheelEvent, + "IPrimaryScreen::wheel"); +} + +CEvent::Type +IPrimaryScreen::getScreensaverActivatedEvent() +{ + return CEvent::registerTypeOnce(s_ssActivatedEvent, + "IPrimaryScreen::screensaverActivated"); +} + +CEvent::Type +IPrimaryScreen::getScreensaverDeactivatedEvent() +{ + return CEvent::registerTypeOnce(s_ssDeactivatedEvent, + "IPrimaryScreen::screensaverDeactivated"); +} + +CEvent::Type +IPrimaryScreen::getHotKeyDownEvent() +{ + return CEvent::registerTypeOnce(s_hotKeyDownEvent, + "IPrimaryScreen::hotKeyDown"); +} + +CEvent::Type +IPrimaryScreen::getHotKeyUpEvent() +{ + return CEvent::registerTypeOnce(s_hotKeyUpEvent, + "IPrimaryScreen::hotKeyUp"); +} + +CEvent::Type +IPrimaryScreen::getFakeInputBeginEvent() +{ + return CEvent::registerTypeOnce(s_fakeInputBegin, + "IPrimaryScreen::fakeInputBegin"); +} + +CEvent::Type +IPrimaryScreen::getFakeInputEndEvent() +{ + return CEvent::registerTypeOnce(s_fakeInputEnd, + "IPrimaryScreen::fakeInputEnd"); +} + + +// +// IPrimaryScreen::CButtonInfo +// + +IPrimaryScreen::CButtonInfo* +IPrimaryScreen::CButtonInfo::alloc(ButtonID id, KeyModifierMask mask) +{ + CButtonInfo* info = (CButtonInfo*)malloc(sizeof(CButtonInfo)); + info->m_button = id; + info->m_mask = mask; + return info; +} + +IPrimaryScreen::CButtonInfo* +IPrimaryScreen::CButtonInfo::alloc(const CButtonInfo& x) +{ + CButtonInfo* info = (CButtonInfo*)malloc(sizeof(CButtonInfo)); + info->m_button = x.m_button; + info->m_mask = x.m_mask; + return info; +} + +bool +IPrimaryScreen::CButtonInfo::equal(const CButtonInfo* a, const CButtonInfo* b) +{ + return (a->m_button == b->m_button && a->m_mask == b->m_mask); +} + + +// +// IPrimaryScreen::CMotionInfo +// + +IPrimaryScreen::CMotionInfo* +IPrimaryScreen::CMotionInfo::alloc(SInt32 x, SInt32 y) +{ + CMotionInfo* info = (CMotionInfo*)malloc(sizeof(CMotionInfo)); + info->m_x = x; + info->m_y = y; + return info; +} + + +// +// IPrimaryScreen::CWheelInfo +// + +IPrimaryScreen::CWheelInfo* +IPrimaryScreen::CWheelInfo::alloc(SInt32 xDelta, SInt32 yDelta) +{ + CWheelInfo* info = (CWheelInfo*)malloc(sizeof(CWheelInfo)); + info->m_xDelta = xDelta; + info->m_yDelta = yDelta; + return info; +} + + +// +// IPrimaryScreen::CHotKeyInfo +// + +IPrimaryScreen::CHotKeyInfo* +IPrimaryScreen::CHotKeyInfo::alloc(UInt32 id) +{ + CHotKeyInfo* info = (CHotKeyInfo*)malloc(sizeof(CHotKeyInfo)); + info->m_id = id; + return info; +} diff --git a/lib/synergy/IPrimaryScreen.h b/lib/synergy/IPrimaryScreen.h new file mode 100644 index 00000000..93166d23 --- /dev/null +++ b/lib/synergy/IPrimaryScreen.h @@ -0,0 +1,206 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IPRIMARYSCREEN_H +#define IPRIMARYSCREEN_H + +#include "IInterface.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CEvent.h" + +//! Primary screen interface +/*! +This interface defines the methods common to all platform dependent +primary screen implementations. +*/ +class IPrimaryScreen : public IInterface { +public: + //! Button event data + class CButtonInfo { + public: + static CButtonInfo* alloc(ButtonID, KeyModifierMask); + static CButtonInfo* alloc(const CButtonInfo&); + + static bool equal(const CButtonInfo*, const CButtonInfo*); + + public: + ButtonID m_button; + KeyModifierMask m_mask; + }; + //! Motion event data + class CMotionInfo { + public: + static CMotionInfo* alloc(SInt32 x, SInt32 y); + + public: + SInt32 m_x; + SInt32 m_y; + }; + //! Wheel motion event data + class CWheelInfo { + public: + static CWheelInfo* alloc(SInt32 xDelta, SInt32 yDelta); + + public: + SInt32 m_xDelta; + SInt32 m_yDelta; + }; + //! Hot key event data + class CHotKeyInfo { + public: + static CHotKeyInfo* alloc(UInt32 id); + + public: + UInt32 m_id; + }; + + //! @name manipulators + //@{ + + //! Update configuration + /*! + This is called when the configuration has changed. \c activeSides + is a bitmask of EDirectionMask indicating which sides of the + primary screen are linked to clients. Override to handle the + possible change in jump zones. + */ + virtual void reconfigure(UInt32 activeSides) = 0; + + //! Warp cursor + /*! + Warp the cursor to the absolute coordinates \c x,y. Also + discard input events up to and including the warp before + returning. + */ + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + + //! Register a system hotkey + /*! + Registers a system-wide hotkey. The screen should arrange for an event + to be delivered to itself when the hot key is pressed or released. When + that happens the screen should post a \c getHotKeyDownEvent() or + \c getHotKeyUpEvent(), respectively. The hot key is key \p key with + exactly the modifiers \p mask. Returns 0 on failure otherwise an id + that can be used to unregister the hotkey. + + A hot key is a set of modifiers and a key, which may itself be a modifier. + The hot key is pressed when the hot key's modifiers and only those + modifiers are logically down (active) and the key is pressed. The hot + key is released when the key is released, regardless of the modifiers. + + The hot key event should be generated no matter what window or application + has the focus. No other window or application should receive the key + press or release events (they can and should see the modifier key events). + When the key is a modifier, it's acceptable to allow the user to press + the modifiers in any order or to require the user to press the given key + last. + */ + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask) = 0; + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + virtual void unregisterHotKey(UInt32 id) = 0; + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() may not be + nested. + */ + virtual void fakeInputBegin() = 0; + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + virtual void fakeInputEnd() = 0; + + //@} + //! @name accessors + //@{ + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + virtual SInt32 getJumpZoneSize() const = 0; + + //! Test if mouse is pressed + /*! + Return true if any mouse button is currently pressed. Ideally, + "current" means up to the last processed event but it can mean + the current physical mouse button state. + */ + virtual bool isAnyMouseButtonDown() const = 0; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + //! Get button down event type. Event data is CButtonInfo*. + static CEvent::Type getButtonDownEvent(); + //! Get button up event type. Event data is CButtonInfo*. + static CEvent::Type getButtonUpEvent(); + //! Get mouse motion on the primary screen event type + /*! + Event data is CMotionInfo* and the values are an absolute position. + */ + static CEvent::Type getMotionOnPrimaryEvent(); + //! Get mouse motion on a secondary screen event type + /*! + Event data is CMotionInfo* and the values are motion deltas not + absolute coordinates. + */ + static CEvent::Type getMotionOnSecondaryEvent(); + //! Get mouse wheel event type. Event data is CWheelInfo*. + static CEvent::Type getWheelEvent(); + //! Get screensaver activated event type + static CEvent::Type getScreensaverActivatedEvent(); + //! Get screensaver deactivated event type + static CEvent::Type getScreensaverDeactivatedEvent(); + //! Get hot key down event type. Event data is CHotKeyInfo*. + static CEvent::Type getHotKeyDownEvent(); + //! Get hot key up event type. Event data is CHotKeyInfo*. + static CEvent::Type getHotKeyUpEvent(); + //! Get start of fake input event type + static CEvent::Type getFakeInputBeginEvent(); + //! Get end of fake input event type + static CEvent::Type getFakeInputEndEvent(); + + //@} + +private: + static CEvent::Type s_buttonDownEvent; + static CEvent::Type s_buttonUpEvent; + static CEvent::Type s_motionPrimaryEvent; + static CEvent::Type s_motionSecondaryEvent; + static CEvent::Type s_wheelEvent; + static CEvent::Type s_ssActivatedEvent; + static CEvent::Type s_ssDeactivatedEvent; + static CEvent::Type s_hotKeyDownEvent; + static CEvent::Type s_hotKeyUpEvent; + static CEvent::Type s_fakeInputBegin; + static CEvent::Type s_fakeInputEnd; +}; + +#endif diff --git a/lib/synergy/IScreen.cpp b/lib/synergy/IScreen.cpp new file mode 100644 index 00000000..3095b9d4 --- /dev/null +++ b/lib/synergy/IScreen.cpp @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "IScreen.h" + +// +// IScreen +// + +CEvent::Type IScreen::s_errorEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_shapeChangedEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_clipboardGrabbedEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_suspendEvent = CEvent::kUnknown; +CEvent::Type IScreen::s_resumeEvent = CEvent::kUnknown; + +CEvent::Type +IScreen::getErrorEvent() +{ + return CEvent::registerTypeOnce(s_errorEvent, + "IScreen::error"); +} + +CEvent::Type +IScreen::getShapeChangedEvent() +{ + return CEvent::registerTypeOnce(s_shapeChangedEvent, + "IScreen::shapeChanged"); +} + +CEvent::Type +IScreen::getClipboardGrabbedEvent() +{ + return CEvent::registerTypeOnce(s_clipboardGrabbedEvent, + "IScreen::clipboardGrabbed"); +} + +CEvent::Type +IScreen::getSuspendEvent() +{ + return CEvent::registerTypeOnce(s_suspendEvent, + "IScreen::suspend"); +} + +CEvent::Type +IScreen::getResumeEvent() +{ + return CEvent::registerTypeOnce(s_resumeEvent, + "IScreen::resume"); +} diff --git a/lib/synergy/IScreen.h b/lib/synergy/IScreen.h new file mode 100644 index 00000000..e1f94d59 --- /dev/null +++ b/lib/synergy/IScreen.h @@ -0,0 +1,112 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISCREEN_H +#define ISCREEN_H + +#include "IInterface.h" +#include "ClipboardTypes.h" +#include "CEvent.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all screens. +*/ +class IScreen : public IInterface { +public: + struct CClipboardInfo { + public: + ClipboardID m_id; + UInt32 m_sequenceNumber; + }; + + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the target used for events created by this object. + */ + virtual void* getEventTarget() const = 0; + + //! Get clipboard + /*! + Save the contents of the clipboard indicated by \c id and return + true iff successful. + */ + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + + //! Get screen shape + /*! + Return the position of the upper-left corner of the screen in \c x and + \c y and the size of the screen in \c width and \c height. + */ + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + //! Get error event type + /*! + Returns the error event type. This is sent whenever the screen has + failed for some reason (e.g. the X Windows server died). + */ + static CEvent::Type getErrorEvent(); + + //! Get shape changed event type + /*! + Returns the shape changed event type. This is sent whenever the + screen's shape changes. + */ + static CEvent::Type getShapeChangedEvent(); + + //! Get clipboard grabbed event type + /*! + Returns the clipboard grabbed event type. This is sent whenever the + clipboard is grabbed by some other application so we don't own it + anymore. The data is a pointer to a CClipboardInfo. + */ + static CEvent::Type getClipboardGrabbedEvent(); + + //! Get suspend event type + /*! + Returns the suspend event type. This is sent whenever the system goes + to sleep or a user session is deactivated (fast user switching). + */ + static CEvent::Type getSuspendEvent(); + + //! Get resume event type + /*! + Returns the suspend event type. This is sent whenever the system wakes + up or a user session is activated (fast user switching). + */ + static CEvent::Type getResumeEvent(); + + //@} + +private: + static CEvent::Type s_errorEvent; + static CEvent::Type s_shapeChangedEvent; + static CEvent::Type s_clipboardGrabbedEvent; + static CEvent::Type s_suspendEvent; + static CEvent::Type s_resumeEvent; +}; + +#endif diff --git a/lib/synergy/IScreenSaver.h b/lib/synergy/IScreenSaver.h new file mode 100644 index 00000000..9076b309 --- /dev/null +++ b/lib/synergy/IScreenSaver.h @@ -0,0 +1,74 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISCREENSAVER_H +#define ISCREENSAVER_H + +#include "IInterface.h" +#include "CEvent.h" + +//! Screen saver interface +/*! +This interface defines the methods common to all screen savers. +*/ +class IScreenSaver : public IInterface { +public: + // note -- the c'tor/d'tor must *not* enable/disable the screen saver + + //! @name manipulators + //@{ + + //! Enable screen saver + /*! + Enable the screen saver, restoring the screen saver settings to + what they were when disable() was previously called. If disable() + wasn't previously called then it should keep the current settings + or use reasonable defaults. + */ + virtual void enable() = 0; + + //! Disable screen saver + /*! + Disable the screen saver, saving the old settings for the next + call to enable(). + */ + virtual void disable() = 0; + + //! Activate screen saver + /*! + Activate (i.e. show) the screen saver. + */ + virtual void activate() = 0; + + //! Deactivate screen saver + /*! + Deactivate (i.e. hide) the screen saver, reseting the screen saver + timer. + */ + virtual void deactivate() = 0; + + //@} + //! @name accessors + //@{ + + //! Test if screen saver on + /*! + Returns true iff the screen saver is currently active (showing). + */ + virtual bool isActive() const = 0; + + //@} +}; + +#endif diff --git a/lib/synergy/ISecondaryScreen.h b/lib/synergy/ISecondaryScreen.h new file mode 100644 index 00000000..5b3a150d --- /dev/null +++ b/lib/synergy/ISecondaryScreen.h @@ -0,0 +1,58 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef ISECONDARYSCREEN_H +#define ISECONDARYSCREEN_H + +#include "IInterface.h" +#include "MouseTypes.h" + +//! Secondary screen interface +/*! +This interface defines the methods common to all platform dependent +secondary screen implementations. +*/ +class ISecondaryScreen : public IInterface { +public: + //! @name accessors + //@{ + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + + //! Fake mouse move + /*! + Synthesize a mouse move to the relative coordinates \c dx,dy. + */ + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c xDelta and \c yDelta. + */ + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + //@} +}; + +#endif diff --git a/lib/synergy/KeyTypes.cpp b/lib/synergy/KeyTypes.cpp new file mode 100644 index 00000000..b483745d --- /dev/null +++ b/lib/synergy/KeyTypes.cpp @@ -0,0 +1,204 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "KeyTypes.h" + +const KeyNameMapEntry kKeyNameMap[] = { + { "AltGr", kKeyAltGr }, + { "Alt_L", kKeyAlt_L }, + { "Alt_R", kKeyAlt_R }, + { "AppMail", kKeyAppMail }, + { "AppMedia", kKeyAppMedia }, + { "AppUser1", kKeyAppUser1 }, + { "AppUser2", kKeyAppUser2 }, + { "AudioDown", kKeyAudioDown }, + { "AudioMute", kKeyAudioMute }, + { "AudioNext", kKeyAudioNext }, + { "AudioPlay", kKeyAudioPlay }, + { "AudioPrev", kKeyAudioPrev }, + { "AudioStop", kKeyAudioStop }, + { "AudioUp", kKeyAudioUp }, + { "BackSpace", kKeyBackSpace }, + { "Begin", kKeyBegin }, + { "Break", kKeyBreak }, + { "Cancel", kKeyCancel }, + { "CapsLock", kKeyCapsLock }, + { "Clear", kKeyClear }, + { "Control_L", kKeyControl_L }, + { "Control_R", kKeyControl_R }, + { "Delete", kKeyDelete }, + { "Down", kKeyDown }, + { "Eject", kKeyEject }, + { "End", kKeyEnd }, + { "Escape", kKeyEscape }, + { "Execute", kKeyExecute }, + { "F1", kKeyF1 }, + { "F2", kKeyF2 }, + { "F3", kKeyF3 }, + { "F4", kKeyF4 }, + { "F5", kKeyF5 }, + { "F6", kKeyF6 }, + { "F7", kKeyF7 }, + { "F8", kKeyF8 }, + { "F9", kKeyF9 }, + { "F10", kKeyF10 }, + { "F11", kKeyF11 }, + { "F12", kKeyF12 }, + { "F13", kKeyF13 }, + { "F14", kKeyF14 }, + { "F15", kKeyF15 }, + { "F16", kKeyF16 }, + { "F17", kKeyF17 }, + { "F18", kKeyF18 }, + { "F19", kKeyF19 }, + { "F20", kKeyF20 }, + { "F21", kKeyF21 }, + { "F22", kKeyF22 }, + { "F23", kKeyF23 }, + { "F24", kKeyF24 }, + { "F25", kKeyF25 }, + { "F26", kKeyF26 }, + { "F27", kKeyF27 }, + { "F28", kKeyF28 }, + { "F29", kKeyF29 }, + { "F30", kKeyF30 }, + { "F31", kKeyF31 }, + { "F32", kKeyF32 }, + { "F33", kKeyF33 }, + { "F34", kKeyF34 }, + { "F35", kKeyF35 }, + { "Find", kKeyFind }, + { "Help", kKeyHelp }, + { "Henkan", kKeyHenkan }, + { "Home", kKeyHome }, + { "Hyper_L", kKeyHyper_L }, + { "Hyper_R", kKeyHyper_R }, + { "Insert", kKeyInsert }, + { "KP_0", kKeyKP_0 }, + { "KP_1", kKeyKP_1 }, + { "KP_2", kKeyKP_2 }, + { "KP_3", kKeyKP_3 }, + { "KP_4", kKeyKP_4 }, + { "KP_5", kKeyKP_5 }, + { "KP_6", kKeyKP_6 }, + { "KP_7", kKeyKP_7 }, + { "KP_8", kKeyKP_8 }, + { "KP_9", kKeyKP_9 }, + { "KP_Add", kKeyKP_Add }, + { "KP_Begin", kKeyKP_Begin }, + { "KP_Decimal", kKeyKP_Decimal }, + { "KP_Delete", kKeyKP_Delete }, + { "KP_Divide", kKeyKP_Divide }, + { "KP_Down", kKeyKP_Down }, + { "KP_End", kKeyKP_End }, + { "KP_Enter", kKeyKP_Enter }, + { "KP_Equal", kKeyKP_Equal }, + { "KP_F1", kKeyKP_F1 }, + { "KP_F2", kKeyKP_F2 }, + { "KP_F3", kKeyKP_F3 }, + { "KP_F4", kKeyKP_F4 }, + { "KP_Home", kKeyKP_Home }, + { "KP_Insert", kKeyKP_Insert }, + { "KP_Left", kKeyKP_Left }, + { "KP_Multiply", kKeyKP_Multiply }, + { "KP_PageDown", kKeyKP_PageDown }, + { "KP_PageUp", kKeyKP_PageUp }, + { "KP_Right", kKeyKP_Right }, + { "KP_Separator", kKeyKP_Separator }, + { "KP_Space", kKeyKP_Space }, + { "KP_Subtract", kKeyKP_Subtract }, + { "KP_Tab", kKeyKP_Tab }, + { "KP_Up", kKeyKP_Up }, + { "Left", kKeyLeft }, + { "LeftTab", kKeyLeftTab }, + { "Linefeed", kKeyLinefeed }, + { "Menu", kKeyMenu }, + { "Meta_L", kKeyMeta_L }, + { "Meta_R", kKeyMeta_R }, + { "NumLock", kKeyNumLock }, + { "PageDown", kKeyPageDown }, + { "PageUp", kKeyPageUp }, + { "Pause", kKeyPause }, + { "Print", kKeyPrint }, + { "Redo", kKeyRedo }, + { "Return", kKeyReturn }, + { "Right", kKeyRight }, + { "ScrollLock", kKeyScrollLock }, + { "Select", kKeySelect }, + { "ShiftLock", kKeyShiftLock }, + { "Shift_L", kKeyShift_L }, + { "Shift_R", kKeyShift_R }, + { "Sleep", kKeySleep }, + { "Super_L", kKeySuper_L }, + { "Super_R", kKeySuper_R }, + { "SysReq", kKeySysReq }, + { "Tab", kKeyTab }, + { "Undo", kKeyUndo }, + { "Up", kKeyUp }, + { "WWWBack", kKeyWWWBack }, + { "WWWFavorites", kKeyWWWFavorites }, + { "WWWForward", kKeyWWWForward }, + { "WWWHome", kKeyWWWHome }, + { "WWWRefresh", kKeyWWWRefresh }, + { "WWWSearch", kKeyWWWSearch }, + { "WWWStop", kKeyWWWStop }, + { "Zenkaku", kKeyZenkaku }, + { "Space", 0x0020 }, + { "Exclaim", 0x0021 }, + { "DoubleQuote", 0x0022 }, + { "Number", 0x0023 }, + { "Dollar", 0x0024 }, + { "Percent", 0x0025 }, + { "Ampersand", 0x0026 }, + { "Apostrophe", 0x0027 }, + { "ParenthesisL", 0x0028 }, + { "ParenthesisR", 0x0029 }, + { "Asterisk", 0x002a }, + { "Plus", 0x002b }, + { "Comma", 0x002c }, + { "Minus", 0x002d }, + { "Period", 0x002e }, + { "Slash", 0x002f }, + { "Colon", 0x003a }, + { "Semicolon", 0x003b }, + { "Less", 0x003c }, + { "Equal", 0x003d }, + { "Greater", 0x003e }, + { "Question", 0x003f }, + { "At", 0x0040 }, + { "BracketL", 0x005b }, + { "Backslash", 0x005c }, + { "BracketR", 0x005d }, + { "Circumflex", 0x005e }, + { "Underscore", 0x005f }, + { "Grave", 0x0060 }, + { "BraceL", 0x007b }, + { "Bar", 0x007c }, + { "BraceR", 0x007d }, + { "Tilde", 0x007e }, + { NULL, 0 }, +}; + +const KeyModifierNameMapEntry kModifierNameMap[] = { + { "Alt", KeyModifierAlt }, + { "AltGr", KeyModifierAltGr }, +// { "CapsLock", KeyModifierCapsLock }, + { "Control", KeyModifierControl }, + { "Meta", KeyModifierMeta }, +// { "NumLock", KeyModifierNumLock }, +// { "ScrollLock", KeyModifierScrollLock }, + { "Shift", KeyModifierShift }, + { "Super", KeyModifierSuper }, + { NULL, 0 }, +}; diff --git a/lib/synergy/KeyTypes.h b/lib/synergy/KeyTypes.h new file mode 100644 index 00000000..c8b4f6b8 --- /dev/null +++ b/lib/synergy/KeyTypes.h @@ -0,0 +1,306 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef KEYTYPES_H +#define KEYTYPES_H + +#include "BasicTypes.h" + +//! Key ID +/*! +Type to hold a key symbol identifier. The encoding is UTF-32, using +U+E000 through U+EFFF for the various control keys (e.g. arrow +keys, function keys, modifier keys, etc). +*/ +typedef UInt32 KeyID; + +//! Key Code +/*! +Type to hold a physical key identifier. That is, it identifies a +physical key on the keyboard. KeyButton 0 is reserved to be an +invalid key; platforms that use 0 as a physical key identifier +will have to remap that value to some arbitrary unused id. +*/ +typedef UInt16 KeyButton; + +//! Modifier key mask +/*! +Type to hold a bitmask of key modifiers (e.g. shift keys). +*/ +typedef UInt32 KeyModifierMask; + +//! Modifier key ID +/*! +Type to hold the id of a key modifier (e.g. a shift key). +*/ +typedef UInt32 KeyModifierID; + +//! @name Modifier key masks +//@{ +static const KeyModifierMask KeyModifierShift = 0x0001; +static const KeyModifierMask KeyModifierControl = 0x0002; +static const KeyModifierMask KeyModifierAlt = 0x0004; +static const KeyModifierMask KeyModifierMeta = 0x0008; +static const KeyModifierMask KeyModifierSuper = 0x0010; +static const KeyModifierMask KeyModifierAltGr = 0x0020; +static const KeyModifierMask KeyModifierCapsLock = 0x1000; +static const KeyModifierMask KeyModifierNumLock = 0x2000; +static const KeyModifierMask KeyModifierScrollLock = 0x4000; +//@} + +//! @name Modifier key bits +//@{ +static const UInt32 kKeyModifierBitNone = 16; +static const UInt32 kKeyModifierBitShift = 0; +static const UInt32 kKeyModifierBitControl = 1; +static const UInt32 kKeyModifierBitAlt = 2; +static const UInt32 kKeyModifierBitMeta = 3; +static const UInt32 kKeyModifierBitSuper = 4; +static const UInt32 kKeyModifierBitAltGr = 5; +static const UInt32 kKeyModifierBitCapsLock = 12; +static const UInt32 kKeyModifierBitNumLock = 13; +static const UInt32 kKeyModifierBitScrollLock = 14; +static const SInt32 kKeyModifierNumBits = 16; +//@} + +//! @name Modifier key identifiers +//@{ +static const KeyModifierID kKeyModifierIDNull = 0; +static const KeyModifierID kKeyModifierIDShift = 1; +static const KeyModifierID kKeyModifierIDControl = 2; +static const KeyModifierID kKeyModifierIDAlt = 3; +static const KeyModifierID kKeyModifierIDMeta = 4; +static const KeyModifierID kKeyModifierIDSuper = 5; +static const KeyModifierID kKeyModifierIDLast = 6; +//@} + +//! @name Key identifiers +//@{ +// all identifiers except kKeyNone and those in 0xE000 to 0xE0FF +// inclusive are equal to the corresponding X11 keysym - 0x1000. + +// no key +static const KeyID kKeyNone = 0x0000; + +// TTY functions +static const KeyID kKeyBackSpace = 0xEF08; /* back space, back char */ +static const KeyID kKeyTab = 0xEF09; +static const KeyID kKeyLinefeed = 0xEF0A; /* Linefeed, LF */ +static const KeyID kKeyClear = 0xEF0B; +static const KeyID kKeyReturn = 0xEF0D; /* Return, enter */ +static const KeyID kKeyPause = 0xEF13; /* Pause, hold */ +static const KeyID kKeyScrollLock = 0xEF14; +static const KeyID kKeySysReq = 0xEF15; +static const KeyID kKeyEscape = 0xEF1B; +static const KeyID kKeyHenkan = 0xEF23; /* Start/Stop Conversion */ +static const KeyID kKeyHangulKana = 0xEF26; /* Hangul, Kana */ +static const KeyID kKeyHiraganaKatakana = 0xEF27; /* Hiragana/Katakana toggle */ +static const KeyID kKeyZenkaku = 0xEF2A; /* Zenkaku/Hankaku */ +static const KeyID kKeyHanjaKanzi = 0xEF2A; /* Hanja, Kanzi */ +static const KeyID kKeyDelete = 0xEFFF; /* Delete, rubout */ + +// cursor control +static const KeyID kKeyHome = 0xEF50; +static const KeyID kKeyLeft = 0xEF51; /* Move left, left arrow */ +static const KeyID kKeyUp = 0xEF52; /* Move up, up arrow */ +static const KeyID kKeyRight = 0xEF53; /* Move right, right arrow */ +static const KeyID kKeyDown = 0xEF54; /* Move down, down arrow */ +static const KeyID kKeyPageUp = 0xEF55; +static const KeyID kKeyPageDown = 0xEF56; +static const KeyID kKeyEnd = 0xEF57; /* EOL */ +static const KeyID kKeyBegin = 0xEF58; /* BOL */ + +// misc functions +static const KeyID kKeySelect = 0xEF60; /* Select, mark */ +static const KeyID kKeyPrint = 0xEF61; +static const KeyID kKeyExecute = 0xEF62; /* Execute, run, do */ +static const KeyID kKeyInsert = 0xEF63; /* Insert, insert here */ +static const KeyID kKeyUndo = 0xEF65; /* Undo, oops */ +static const KeyID kKeyRedo = 0xEF66; /* redo, again */ +static const KeyID kKeyMenu = 0xEF67; +static const KeyID kKeyFind = 0xEF68; /* Find, search */ +static const KeyID kKeyCancel = 0xEF69; /* Cancel, stop, abort, exit */ +static const KeyID kKeyHelp = 0xEF6A; /* Help */ +static const KeyID kKeyBreak = 0xEF6B; +static const KeyID kKeyAltGr = 0xEF7E; /* Character set switch */ +static const KeyID kKeyNumLock = 0xEF7F; + +// keypad +static const KeyID kKeyKP_Space = 0xEF80; /* space */ +static const KeyID kKeyKP_Tab = 0xEF89; +static const KeyID kKeyKP_Enter = 0xEF8D; /* enter */ +static const KeyID kKeyKP_F1 = 0xEF91; /* PF1, KP_A, ... */ +static const KeyID kKeyKP_F2 = 0xEF92; +static const KeyID kKeyKP_F3 = 0xEF93; +static const KeyID kKeyKP_F4 = 0xEF94; +static const KeyID kKeyKP_Home = 0xEF95; +static const KeyID kKeyKP_Left = 0xEF96; +static const KeyID kKeyKP_Up = 0xEF97; +static const KeyID kKeyKP_Right = 0xEF98; +static const KeyID kKeyKP_Down = 0xEF99; +static const KeyID kKeyKP_PageUp = 0xEF9A; +static const KeyID kKeyKP_PageDown = 0xEF9B; +static const KeyID kKeyKP_End = 0xEF9C; +static const KeyID kKeyKP_Begin = 0xEF9D; +static const KeyID kKeyKP_Insert = 0xEF9E; +static const KeyID kKeyKP_Delete = 0xEF9F; +static const KeyID kKeyKP_Equal = 0xEFBD; /* equals */ +static const KeyID kKeyKP_Multiply = 0xEFAA; +static const KeyID kKeyKP_Add = 0xEFAB; +static const KeyID kKeyKP_Separator= 0xEFAC; /* separator, often comma */ +static const KeyID kKeyKP_Subtract = 0xEFAD; +static const KeyID kKeyKP_Decimal = 0xEFAE; +static const KeyID kKeyKP_Divide = 0xEFAF; +static const KeyID kKeyKP_0 = 0xEFB0; +static const KeyID kKeyKP_1 = 0xEFB1; +static const KeyID kKeyKP_2 = 0xEFB2; +static const KeyID kKeyKP_3 = 0xEFB3; +static const KeyID kKeyKP_4 = 0xEFB4; +static const KeyID kKeyKP_5 = 0xEFB5; +static const KeyID kKeyKP_6 = 0xEFB6; +static const KeyID kKeyKP_7 = 0xEFB7; +static const KeyID kKeyKP_8 = 0xEFB8; +static const KeyID kKeyKP_9 = 0xEFB9; + +// function keys +static const KeyID kKeyF1 = 0xEFBE; +static const KeyID kKeyF2 = 0xEFBF; +static const KeyID kKeyF3 = 0xEFC0; +static const KeyID kKeyF4 = 0xEFC1; +static const KeyID kKeyF5 = 0xEFC2; +static const KeyID kKeyF6 = 0xEFC3; +static const KeyID kKeyF7 = 0xEFC4; +static const KeyID kKeyF8 = 0xEFC5; +static const KeyID kKeyF9 = 0xEFC6; +static const KeyID kKeyF10 = 0xEFC7; +static const KeyID kKeyF11 = 0xEFC8; +static const KeyID kKeyF12 = 0xEFC9; +static const KeyID kKeyF13 = 0xEFCA; +static const KeyID kKeyF14 = 0xEFCB; +static const KeyID kKeyF15 = 0xEFCC; +static const KeyID kKeyF16 = 0xEFCD; +static const KeyID kKeyF17 = 0xEFCE; +static const KeyID kKeyF18 = 0xEFCF; +static const KeyID kKeyF19 = 0xEFD0; +static const KeyID kKeyF20 = 0xEFD1; +static const KeyID kKeyF21 = 0xEFD2; +static const KeyID kKeyF22 = 0xEFD3; +static const KeyID kKeyF23 = 0xEFD4; +static const KeyID kKeyF24 = 0xEFD5; +static const KeyID kKeyF25 = 0xEFD6; +static const KeyID kKeyF26 = 0xEFD7; +static const KeyID kKeyF27 = 0xEFD8; +static const KeyID kKeyF28 = 0xEFD9; +static const KeyID kKeyF29 = 0xEFDA; +static const KeyID kKeyF30 = 0xEFDB; +static const KeyID kKeyF31 = 0xEFDC; +static const KeyID kKeyF32 = 0xEFDD; +static const KeyID kKeyF33 = 0xEFDE; +static const KeyID kKeyF34 = 0xEFDF; +static const KeyID kKeyF35 = 0xEFE0; + +// modifiers +static const KeyID kKeyShift_L = 0xEFE1; /* Left shift */ +static const KeyID kKeyShift_R = 0xEFE2; /* Right shift */ +static const KeyID kKeyControl_L = 0xEFE3; /* Left control */ +static const KeyID kKeyControl_R = 0xEFE4; /* Right control */ +static const KeyID kKeyCapsLock = 0xEFE5; /* Caps lock */ +static const KeyID kKeyShiftLock = 0xEFE6; /* Shift lock */ +static const KeyID kKeyMeta_L = 0xEFE7; /* Left meta */ +static const KeyID kKeyMeta_R = 0xEFE8; /* Right meta */ +static const KeyID kKeyAlt_L = 0xEFE9; /* Left alt */ +static const KeyID kKeyAlt_R = 0xEFEA; /* Right alt */ +static const KeyID kKeySuper_L = 0xEFEB; /* Left super */ +static const KeyID kKeySuper_R = 0xEFEC; /* Right super */ +static const KeyID kKeyHyper_L = 0xEFED; /* Left hyper */ +static const KeyID kKeyHyper_R = 0xEFEE; /* Right hyper */ + +// multi-key character composition +static const KeyID kKeyCompose = 0xEF20; +static const KeyID kKeyDeadGrave = 0x0300; +static const KeyID kKeyDeadAcute = 0x0301; +static const KeyID kKeyDeadCircumflex = 0x0302; +static const KeyID kKeyDeadTilde = 0x0303; +static const KeyID kKeyDeadMacron = 0x0304; +static const KeyID kKeyDeadBreve = 0x0306; +static const KeyID kKeyDeadAbovedot = 0x0307; +static const KeyID kKeyDeadDiaeresis = 0x0308; +static const KeyID kKeyDeadAbovering = 0x030a; +static const KeyID kKeyDeadDoubleacute = 0x030b; +static const KeyID kKeyDeadCaron = 0x030c; +static const KeyID kKeyDeadCedilla = 0x0327; +static const KeyID kKeyDeadOgonek = 0x0328; + +// more function and modifier keys +static const KeyID kKeyLeftTab = 0xEE20; + +// update modifiers +static const KeyID kKeySetModifiers = 0xEE06; +static const KeyID kKeyClearModifiers = 0xEE07; + +// group change +static const KeyID kKeyNextGroup = 0xEE08; +static const KeyID kKeyPrevGroup = 0xEE0A; + +// extended keys +static const KeyID kKeyEject = 0xE001; +static const KeyID kKeySleep = 0xE05F; +static const KeyID kKeyWWWBack = 0xE0A6; +static const KeyID kKeyWWWForward = 0xE0A7; +static const KeyID kKeyWWWRefresh = 0xE0A8; +static const KeyID kKeyWWWStop = 0xE0A9; +static const KeyID kKeyWWWSearch = 0xE0AA; +static const KeyID kKeyWWWFavorites = 0xE0AB; +static const KeyID kKeyWWWHome = 0xE0AC; +static const KeyID kKeyAudioMute = 0xE0AD; +static const KeyID kKeyAudioDown = 0xE0AE; +static const KeyID kKeyAudioUp = 0xE0AF; +static const KeyID kKeyAudioNext = 0xE0B0; +static const KeyID kKeyAudioPrev = 0xE0B1; +static const KeyID kKeyAudioStop = 0xE0B2; +static const KeyID kKeyAudioPlay = 0xE0B3; +static const KeyID kKeyAppMail = 0xE0B4; +static const KeyID kKeyAppMedia = 0xE0B5; +static const KeyID kKeyAppUser1 = 0xE0B6; +static const KeyID kKeyAppUser2 = 0xE0B7; + +//@} + +struct KeyNameMapEntry { +public: + const char* m_name; + KeyID m_id; +}; +struct KeyModifierNameMapEntry { +public: + const char* m_name; + KeyModifierMask m_mask; +}; + +//! Key name to KeyID table +/*! +A table of key names to the corresponding KeyID. Only the keys listed +above plus non-alphanumeric ASCII characters are in the table. The end +of the table is the first pair with a NULL m_name. +*/ +extern const KeyNameMapEntry kKeyNameMap[]; + +//! Modifier key name to KeyModifierMask table +/*! +A table of modifier key names to the corresponding KeyModifierMask. +The end of the table is the first pair with a NULL m_name. +*/ +extern const KeyModifierNameMapEntry kModifierNameMap[]; + +#endif diff --git a/lib/synergy/Makefile.am b/lib/synergy/Makefile.am new file mode 100644 index 00000000..480102a4 --- /dev/null +++ b/lib/synergy/Makefile.am @@ -0,0 +1,71 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2002 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +## Process this file with automake to produce Makefile.in +NULL = + +EXTRA_DIST = \ + Makefile.win \ + $(NULL) + +MAINTAINERCLEANFILES = \ + Makefile.in \ + $(NULL) + +noinst_LIBRARIES = libsynergy.a +libsynergy_a_SOURCES = \ + CClipboard.cpp \ + CKeyMap.cpp \ + CKeyState.cpp \ + CPacketStreamFilter.cpp \ + CPlatformScreen.cpp \ + CProtocolUtil.cpp \ + CScreen.cpp \ + IClipboard.cpp \ + IKeyState.cpp \ + IPrimaryScreen.cpp \ + IScreen.cpp \ + KeyTypes.cpp \ + ProtocolTypes.cpp \ + XScreen.cpp \ + XSynergy.cpp \ + CClipboard.h \ + CKeyMap.h \ + CKeyState.h \ + CPacketStreamFilter.h \ + CPlatformScreen.h \ + CProtocolUtil.h \ + CScreen.h \ + ClipboardTypes.h \ + IClient.h \ + IClipboard.h \ + IKeyState.h \ + IPlatformScreen.h \ + IPrimaryScreen.h \ + IScreen.h \ + IScreenSaver.h \ + ISecondaryScreen.h \ + KeyTypes.h \ + MouseTypes.h \ + OptionTypes.h \ + ProtocolTypes.h \ + XScreen.h \ + XSynergy.h \ + $(NULL) +INCLUDES = \ + -I$(top_srcdir)/lib/common \ + -I$(top_srcdir)/lib/arch \ + -I$(top_srcdir)/lib/base \ + -I$(top_srcdir)/lib/mt \ + -I$(top_srcdir)/lib/io \ + -I$(top_srcdir)/lib/net \ + $(NULL) diff --git a/lib/synergy/Makefile.win b/lib/synergy/Makefile.win new file mode 100644 index 00000000..e6d56d42 --- /dev/null +++ b/lib/synergy/Makefile.win @@ -0,0 +1,87 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2007 Chris Schoeneman +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file COPYING that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +LIB_SYNERGY_SRC = lib\synergy +LIB_SYNERGY_DST = $(BUILD_DST)\$(LIB_SYNERGY_SRC) +LIB_SYNERGY_LIB = "$(LIB_SYNERGY_DST)\libsynergy.lib" +LIB_SYNERGY_CPP = \ + "CClipboard.cpp" \ + "CKeyMap.cpp" \ + "CKeyState.cpp" \ + "CPacketStreamFilter.cpp" \ + "CPlatformScreen.cpp" \ + "CProtocolUtil.cpp" \ + "CScreen.cpp" \ + "IClipboard.cpp" \ + "IKeyState.cpp" \ + "IPrimaryScreen.cpp" \ + "IScreen.cpp" \ + "KeyTypes.cpp" \ + "ProtocolTypes.cpp" \ + "XScreen.cpp" \ + "XSynergy.cpp" \ + $(NULL) +LIB_SYNERGY_OBJ = \ + "$(LIB_SYNERGY_DST)\CClipboard.obj" \ + "$(LIB_SYNERGY_DST)\CKeyMap.obj" \ + "$(LIB_SYNERGY_DST)\CKeyState.obj" \ + "$(LIB_SYNERGY_DST)\CPacketStreamFilter.obj" \ + "$(LIB_SYNERGY_DST)\CPlatformScreen.obj" \ + "$(LIB_SYNERGY_DST)\CProtocolUtil.obj" \ + "$(LIB_SYNERGY_DST)\CScreen.obj" \ + "$(LIB_SYNERGY_DST)\IClipboard.obj" \ + "$(LIB_SYNERGY_DST)\IKeyState.obj" \ + "$(LIB_SYNERGY_DST)\IPrimaryScreen.obj" \ + "$(LIB_SYNERGY_DST)\IScreen.obj" \ + "$(LIB_SYNERGY_DST)\KeyTypes.obj" \ + "$(LIB_SYNERGY_DST)\ProtocolTypes.obj" \ + "$(LIB_SYNERGY_DST)\XScreen.obj" \ + "$(LIB_SYNERGY_DST)\XSynergy.obj" \ + $(NULL) +LIB_SYNERGY_INC = \ + /I"lib\common" \ + /I"lib\arch" \ + /I"lib\base" \ + /I"lib\mt" \ + /I"lib\io" \ + /I"lib\net" \ + $(NULL) + +CPP_FILES = $(CPP_FILES) $(LIB_SYNERGY_CPP) +OBJ_FILES = $(OBJ_FILES) $(LIB_SYNERGY_OBJ) +LIB_FILES = $(LIB_FILES) $(LIB_SYNERGY_LIB) + +# Dependency rules +$(LIB_SYNERGY_OBJ): $(AUTODEP) +!if EXIST($(LIB_SYNERGY_DST)\deps.mak) +!include $(LIB_SYNERGY_DST)\deps.mak +!endif + +# Build rules. Use batch-mode rules if possible. +!if DEFINED(_NMAKE_VER) +{$(LIB_SYNERGY_SRC)\}.cpp{$(LIB_SYNERGY_DST)\}.obj:: +!else +{$(LIB_SYNERGY_SRC)\}.cpp{$(LIB_SYNERGY_DST)\}.obj: +!endif + @$(ECHO) Compile in $(LIB_SYNERGY_SRC) + -@$(MKDIR) $(LIB_SYNERGY_DST) 2>NUL: + $(cpp) $(cppdebug) $(cppflags) $(cppvarsmt) /showIncludes \ + $(LIB_SYNERGY_INC) \ + /Fo$(LIB_SYNERGY_DST)\ \ + /Fd$(LIB_SYNERGY_LIB:.lib=.pdb) \ + $< | $(AUTODEP) $(LIB_SYNERGY_SRC) $(LIB_SYNERGY_DST) +$(LIB_SYNERGY_LIB): $(LIB_SYNERGY_OBJ) + @$(ECHO) Link $(@F) + $(implib) $(ildebug) $(ilflags) \ + /out:$@ \ + $** + $(AUTODEP) $(LIB_SYNERGY_SRC) $(LIB_SYNERGY_DST) $(**:.obj=.d) diff --git a/lib/synergy/MouseTypes.h b/lib/synergy/MouseTypes.h new file mode 100644 index 00000000..e4988553 --- /dev/null +++ b/lib/synergy/MouseTypes.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MOUSETYPES_H +#define MOUSETYPES_H + +#include "BasicTypes.h" + +//! Mouse button ID +/*! +Type to hold a mouse button identifier. +*/ +typedef UInt8 ButtonID; + +//! @name Mouse button identifiers +//@{ +static const ButtonID kButtonNone = 0; +static const ButtonID kButtonLeft = 1; +static const ButtonID kButtonMiddle = 2; +static const ButtonID kButtonRight = 3; +static const ButtonID kButtonExtra0 = 4; +//@} + +#endif diff --git a/lib/synergy/OptionTypes.h b/lib/synergy/OptionTypes.h new file mode 100644 index 00000000..74beda00 --- /dev/null +++ b/lib/synergy/OptionTypes.h @@ -0,0 +1,92 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef OPTIONTYPES_H +#define OPTIONTYPES_H + +#include "BasicTypes.h" +#include "stdvector.h" + +//! Option ID +/*! +Type to hold an option identifier. +*/ +typedef UInt32 OptionID; + +//! Option Value +/*! +Type to hold an option value. +*/ +typedef SInt32 OptionValue; + +// for now, options are just pairs of integers +typedef std::vector COptionsList; + +// macro for packing 4 character strings into 4 byte integers +#define OPTION_CODE(_s) \ + (static_cast(static_cast(_s[0]) << 24) | \ + static_cast(static_cast(_s[1]) << 16) | \ + static_cast(static_cast(_s[2]) << 8) | \ + static_cast(static_cast(_s[3]) )) + +//! @name Option identifiers +//@{ +static const OptionID kOptionHalfDuplexCapsLock = OPTION_CODE("HDCL"); +static const OptionID kOptionHalfDuplexNumLock = OPTION_CODE("HDNL"); +static const OptionID kOptionHalfDuplexScrollLock = OPTION_CODE("HDSL"); +static const OptionID kOptionModifierMapForShift = OPTION_CODE("MMFS"); +static const OptionID kOptionModifierMapForControl = OPTION_CODE("MMFC"); +static const OptionID kOptionModifierMapForAlt = OPTION_CODE("MMFA"); +static const OptionID kOptionModifierMapForMeta = OPTION_CODE("MMFM"); +static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR"); +static const OptionID kOptionHeartbeat = OPTION_CODE("HART"); +static const OptionID kOptionScreenSwitchCorners = OPTION_CODE("SSCM"); +static const OptionID kOptionScreenSwitchCornerSize = OPTION_CODE("SSCS"); +static const OptionID kOptionScreenSwitchDelay = OPTION_CODE("SSWT"); +static const OptionID kOptionScreenSwitchTwoTap = OPTION_CODE("SSTT"); +static const OptionID kOptionScreenSaverSync = OPTION_CODE("SSVR"); +static const OptionID kOptionXTestXineramaUnaware = OPTION_CODE("XTXU"); +static const OptionID kOptionRelativeMouseMoves = OPTION_CODE("MDLT"); +static const OptionID kOptionWin32KeepForeground = OPTION_CODE("_KFW"); +//@} + +//! @name Screen switch corner enumeration +//@{ +enum EScreenSwitchCorners { + kNoCorner, + kTopLeft, + kTopRight, + kBottomLeft, + kBottomRight, + kFirstCorner = kTopLeft, + kLastCorner = kBottomRight +}; +//@} + +//! @name Screen switch corner masks +//@{ +enum EScreenSwitchCornerMasks { + kNoCornerMask = 0, + kTopLeftMask = 1 << (kTopLeft - kFirstCorner), + kTopRightMask = 1 << (kTopRight - kFirstCorner), + kBottomLeftMask = 1 << (kBottomLeft - kFirstCorner), + kBottomRightMask = 1 << (kBottomRight - kFirstCorner), + kAllCornersMask = kTopLeftMask | kTopRightMask | + kBottomLeftMask | kBottomRightMask +}; +//@} + +#undef OPTION_CODE + +#endif diff --git a/lib/synergy/ProtocolTypes.cpp b/lib/synergy/ProtocolTypes.cpp new file mode 100644 index 00000000..441b2f33 --- /dev/null +++ b/lib/synergy/ProtocolTypes.cpp @@ -0,0 +1,47 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ProtocolTypes.h" + +const char* kMsgHello = "Synergy%2i%2i"; +const char* kMsgHelloBack = "Synergy%2i%2i%s"; +const char* kMsgCNoop = "CNOP"; +const char* kMsgCClose = "CBYE"; +const char* kMsgCEnter = "CINN%2i%2i%4i%2i"; +const char* kMsgCLeave = "COUT"; +const char* kMsgCClipboard = "CCLP%1i%4i"; +const char* kMsgCScreenSaver = "CSEC%1i"; +const char* kMsgCResetOptions = "CROP"; +const char* kMsgCInfoAck = "CIAK"; +const char* kMsgCKeepAlive = "CALV"; +const char* kMsgDKeyDown = "DKDN%2i%2i%2i"; +const char* kMsgDKeyDown1_0 = "DKDN%2i%2i"; +const char* kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"; +const char* kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"; +const char* kMsgDKeyUp = "DKUP%2i%2i%2i"; +const char* kMsgDKeyUp1_0 = "DKUP%2i%2i"; +const char* kMsgDMouseDown = "DMDN%1i"; +const char* kMsgDMouseUp = "DMUP%1i"; +const char* kMsgDMouseMove = "DMMV%2i%2i"; +const char* kMsgDMouseRelMove = "DMRM%2i%2i"; +const char* kMsgDMouseWheel = "DMWM%2i%2i"; +const char* kMsgDMouseWheel1_0 = "DMWM%2i"; +const char* kMsgDClipboard = "DCLP%1i%4i%s"; +const char* kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i"; +const char* kMsgDSetOptions = "DSOP%4I"; +const char* kMsgQInfo = "QINF"; +const char* kMsgEIncompatible = "EICV%2i%2i"; +const char* kMsgEBusy = "EBSY"; +const char* kMsgEUnknown = "EUNK"; +const char* kMsgEBad = "EBAD"; diff --git a/lib/synergy/ProtocolTypes.h b/lib/synergy/ProtocolTypes.h new file mode 100644 index 00000000..c4d3c99d --- /dev/null +++ b/lib/synergy/ProtocolTypes.h @@ -0,0 +1,308 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PROTOCOLTYPES_H +#define PROTOCOLTYPES_H + +#include "BasicTypes.h" + +// protocol version number +// 1.0: initial protocol +// 1.1: adds KeyCode to key press, release, and repeat +// 1.2: adds mouse relative motion +// 1.3: adds keep alive and deprecates heartbeats, +// adds horizontal mouse scrolling +static const SInt16 kProtocolMajorVersion = 1; +static const SInt16 kProtocolMinorVersion = 3; + +// default contact port number +static const UInt16 kDefaultPort = 24800; + +// maximum total length for greeting returned by client +static const UInt32 kMaxHelloLength = 1024; + +// time between kMsgCKeepAlive (in seconds). a non-positive value disables +// keep alives. this is the default rate that can be overridden using an +// option. +static const double kKeepAliveRate = 3.0; + +// number of skipped kMsgCKeepAlive messages that indicates a problem +static const double kKeepAlivesUntilDeath = 3.0; + +// obsolete heartbeat stuff +static const double kHeartRate = -1.0; +static const double kHeartBeatsUntilDeath = 3.0; + +// direction constants +enum EDirection { + kNoDirection, + kLeft, + kRight, + kTop, + kBottom, + kFirstDirection = kLeft, + kLastDirection = kBottom, + kNumDirections = kLastDirection - kFirstDirection + 1 +}; +enum EDirectionMask { + kNoDirMask = 0, + kLeftMask = 1 << kLeft, + kRightMask = 1 << kRight, + kTopMask = 1 << kTop, + kBottomMask = 1 << kBottom +}; + + +// +// message codes (trailing NUL is not part of code). in comments, $n +// refers to the n'th argument (counting from one). message codes are +// always 4 bytes optionally followed by message specific parameters +// except those for the greeting handshake. +// + +// +// positions and sizes are signed 16 bit integers. +// + +// +// greeting handshake messages +// + +// say hello to client; primary -> secondary +// $1 = protocol major version number supported by server. $2 = +// protocol minor version number supported by server. +extern const char* kMsgHello; + +// respond to hello from server; secondary -> primary +// $1 = protocol major version number supported by client. $2 = +// protocol minor version number supported by client. $3 = client +// name. +extern const char* kMsgHelloBack; + + +// +// command codes +// + +// no operation; secondary -> primary +extern const char* kMsgCNoop; + +// close connection; primary -> secondary +extern const char* kMsgCClose; + +// enter screen: primary -> secondary +// entering screen at screen position $1 = x, $2 = y. x,y are +// absolute screen coordinates. $3 = sequence number, which is +// used to order messages between screens. the secondary screen +// must return this number with some messages. $4 = modifier key +// mask. this will have bits set for each toggle modifier key +// that is activated on entry to the screen. the secondary screen +// should adjust its toggle modifiers to reflect that state. +extern const char* kMsgCEnter; + +// leave screen: primary -> secondary +// leaving screen. the secondary screen should send clipboard +// data in response to this message for those clipboards that +// it has grabbed (i.e. has sent a kMsgCClipboard for and has +// not received a kMsgCClipboard for with a greater sequence +// number) and that were grabbed or have changed since the +// last leave. +extern const char* kMsgCLeave; + +// grab clipboard: primary <-> secondary +// sent by screen when some other app on that screen grabs a +// clipboard. $1 = the clipboard identifier, $2 = sequence number. +// secondary screens must use the sequence number passed in the +// most recent kMsgCEnter. the primary always sends 0. +extern const char* kMsgCClipboard; + +// screensaver change: primary -> secondary +// screensaver on primary has started ($1 == 1) or closed ($1 == 0) +extern const char* kMsgCScreenSaver; + +// reset options: primary -> secondary +// client should reset all of its options to their defaults. +extern const char* kMsgCResetOptions; + +// resolution change acknowledgment: primary -> secondary +// sent by primary in response to a secondary screen's kMsgDInfo. +// this is sent for every kMsgDInfo, whether or not the primary +// had sent a kMsgQInfo. +extern const char* kMsgCInfoAck; + +// keep connection alive: primary <-> secondary +// sent by the server periodically to verify that connections are still +// up and running. clients must reply in kind on receipt. if the server +// gets an error sending the message or does not receive a reply within +// a reasonable time then the server disconnects the client. if the +// client doesn't receive these (or any message) periodically then it +// should disconnect from the server. the appropriate interval is +// defined by an option. +extern const char* kMsgCKeepAlive; + + +// +// data codes +// + +// key pressed: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +// the KeyButton identifies the physical key on the primary used to +// generate this key. the secondary should note the KeyButton along +// with the physical key it uses to generate the key press. on +// release, the secondary can then use the primary's KeyButton to +// find its corresponding physical key and release it. this is +// necessary because the KeyID on release may not be the KeyID of +// the press. this can happen with combining (dead) keys or if +// the keyboard layouts are not identical and the user releases +// a modifier key before releasing the modified key. +extern const char* kMsgDKeyDown; + +// key pressed 1.0: same as above but without KeyButton +extern const char* kMsgDKeyDown1_0; + +// key auto-repeat: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats, $4 = KeyButton +extern const char* kMsgDKeyRepeat; + +// key auto-repeat 1.0: same as above but without KeyButton +extern const char* kMsgDKeyRepeat1_0; + +// key released: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +extern const char* kMsgDKeyUp; + +// key released 1.0: same as above but without KeyButton +extern const char* kMsgDKeyUp1_0; + +// mouse button pressed: primary -> secondary +// $1 = ButtonID +extern const char* kMsgDMouseDown; + +// mouse button released: primary -> secondary +// $1 = ButtonID +extern const char* kMsgDMouseUp; + +// mouse moved: primary -> secondary +// $1 = x, $2 = y. x,y are absolute screen coordinates. +extern const char* kMsgDMouseMove; + +// relative mouse move: primary -> secondary +// $1 = dx, $2 = dy. dx,dy are motion deltas. +extern const char* kMsgDMouseRelMove; + +// mouse scroll: primary -> secondary +// $1 = xDelta, $2 = yDelta. the delta should be +120 for one tick forward +// (away from the user) or right and -120 for one tick backward (toward +// the user) or left. +extern const char* kMsgDMouseWheel; + +// mouse vertical scroll: primary -> secondary +// like as kMsgDMouseWheel except only sends $1 = yDelta. +extern const char* kMsgDMouseWheel1_0; + +// clipboard data: primary <-> secondary +// $2 = sequence number, $3 = clipboard data. the sequence number +// is 0 when sent by the primary. secondary screens should use the +// sequence number from the most recent kMsgCEnter. $1 = clipboard +// identifier. +extern const char* kMsgDClipboard; + +// client data: secondary -> primary +// $1 = coordinate of leftmost pixel on secondary screen, +// $2 = coordinate of topmost pixel on secondary screen, +// $3 = width of secondary screen in pixels, +// $4 = height of secondary screen in pixels, +// $5 = size of warp zone, (obsolete) +// $6, $7 = the x,y position of the mouse on the secondary screen. +// +// the secondary screen must send this message in response to the +// kMsgQInfo message. it must also send this message when the +// screen's resolution changes. in this case, the secondary screen +// should ignore any kMsgDMouseMove messages until it receives a +// kMsgCInfoAck in order to prevent attempts to move the mouse off +// the new screen area. +extern const char* kMsgDInfo; + +// set options: primary -> secondary +// client should set the given option/value pairs. $1 = option/value +// pairs. +extern const char* kMsgDSetOptions; + + +// +// query codes +// + +// query screen info: primary -> secondary +// client should reply with a kMsgDInfo. +extern const char* kMsgQInfo; + + +// +// error codes +// + +// incompatible versions: primary -> secondary +// $1 = major version of primary, $2 = minor version of primary. +extern const char* kMsgEIncompatible; + +// name provided when connecting is already in use: primary -> secondary +extern const char* kMsgEBusy; + +// unknown client: primary -> secondary +// name provided when connecting is not in primary's screen +// configuration map. +extern const char* kMsgEUnknown; + +// protocol violation: primary -> secondary +// primary should disconnect after sending this message. +extern const char* kMsgEBad; + + +// +// structures +// + +//! Screen information +/*! +This class contains information about a screen. +*/ +class CClientInfo { +public: + //! Screen position + /*! + The position of the upper-left corner of the screen. This is + typically 0,0. + */ + SInt32 m_x, m_y; + + //! Screen size + /*! + The size of the screen in pixels. + */ + SInt32 m_w, m_h; + + //! Obsolete (jump zone size) + SInt32 obsolete1; + + //! Mouse position + /*! + The current location of the mouse cursor. + */ + SInt32 m_mx, m_my; +}; + +#endif + diff --git a/lib/synergy/XScreen.cpp b/lib/synergy/XScreen.cpp new file mode 100644 index 00000000..8a694bfa --- /dev/null +++ b/lib/synergy/XScreen.cpp @@ -0,0 +1,53 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XScreen.h" + +// +// XScreenOpenFailure +// + +CString +XScreenOpenFailure::getWhat() const throw() +{ + return format("XScreenOpenFailure", "unable to open screen"); +} + + +// +// XScreenUnavailable +// + +XScreenUnavailable::XScreenUnavailable(double timeUntilRetry) : + m_timeUntilRetry(timeUntilRetry) +{ + // do nothing +} + +XScreenUnavailable::~XScreenUnavailable() +{ + // do nothing +} + +double +XScreenUnavailable::getRetryTime() const +{ + return m_timeUntilRetry; +} + +CString +XScreenUnavailable::getWhat() const throw() +{ + return format("XScreenUnavailable", "unable to open screen"); +} diff --git a/lib/synergy/XScreen.h b/lib/synergy/XScreen.h new file mode 100644 index 00000000..9966e6cf --- /dev/null +++ b/lib/synergy/XScreen.h @@ -0,0 +1,61 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XSCREEN_H +#define XSCREEN_H + +#include "XBase.h" + +//! Generic screen exception +XBASE_SUBCLASS(XScreen, XBase); + +//! Cannot open screen exception +/*! +Thrown when a screen cannot be opened or initialized. +*/ +XBASE_SUBCLASS_WHAT(XScreenOpenFailure, XScreen); + +//! Screen unavailable exception +/*! +Thrown when a screen cannot be opened or initialized but retrying later +may be successful. +*/ +class XScreenUnavailable : public XScreenOpenFailure { +public: + /*! + \c timeUntilRetry is the suggested time the caller should wait until + trying to open the screen again. + */ + XScreenUnavailable(double timeUntilRetry); + virtual ~XScreenUnavailable(); + + //! @name manipulators + //@{ + + //! Get retry time + /*! + Returns the suggested time to wait until retrying to open the screen. + */ + double getRetryTime() const; + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + double m_timeUntilRetry; +}; + +#endif diff --git a/lib/synergy/XSynergy.cpp b/lib/synergy/XSynergy.cpp new file mode 100644 index 00000000..1e19945f --- /dev/null +++ b/lib/synergy/XSynergy.cpp @@ -0,0 +1,104 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "XSynergy.h" +#include "CStringUtil.h" + +// +// XBadClient +// + +CString +XBadClient::getWhat() const throw() +{ + return "XBadClient"; +} + + +// +// XIncompatibleClient +// + +XIncompatibleClient::XIncompatibleClient(int major, int minor) : + m_major(major), + m_minor(minor) +{ + // do nothing +} + +int +XIncompatibleClient::getMajor() const throw() +{ + return m_major; +} + +int +XIncompatibleClient::getMinor() const throw() +{ + return m_minor; +} + +CString +XIncompatibleClient::getWhat() const throw() +{ + return format("XIncompatibleClient", "incompatible client %{1}.%{2}", + CStringUtil::print("%d", m_major).c_str(), + CStringUtil::print("%d", m_minor).c_str()); +} + + +// +// XDuplicateClient +// + +XDuplicateClient::XDuplicateClient(const CString& name) : + m_name(name) +{ + // do nothing +} + +const CString& +XDuplicateClient::getName() const throw() +{ + return m_name; +} + +CString +XDuplicateClient::getWhat() const throw() +{ + return format("XDuplicateClient", "duplicate client %{1}", m_name.c_str()); +} + + +// +// XUnknownClient +// + +XUnknownClient::XUnknownClient(const CString& name) : + m_name(name) +{ + // do nothing +} + +const CString& +XUnknownClient::getName() const throw() +{ + return m_name; +} + +CString +XUnknownClient::getWhat() const throw() +{ + return format("XUnknownClient", "unknown client %{1}", m_name.c_str()); +} diff --git a/lib/synergy/XSynergy.h b/lib/synergy/XSynergy.h new file mode 100644 index 00000000..23131194 --- /dev/null +++ b/lib/synergy/XSynergy.h @@ -0,0 +1,105 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XSYNERGY_H +#define XSYNERGY_H + +#include "XBase.h" + +//! Generic synergy exception +XBASE_SUBCLASS(XSynergy, XBase); + +//! Client error exception +/*! +Thrown when the client fails to follow the protocol. +*/ +XBASE_SUBCLASS_WHAT(XBadClient, XSynergy); + +//! Incompatible client exception +/*! +Thrown when a client attempting to connect has an incompatible version. +*/ +class XIncompatibleClient : public XSynergy { +public: + XIncompatibleClient(int major, int minor); + + //! @name accessors + //@{ + + //! Get client's major version number + int getMajor() const throw(); + //! Get client's minor version number + int getMinor() const throw(); + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + int m_major; + int m_minor; +}; + +//! Client already connected exception +/*! +Thrown when a client attempting to connect is using the same name as +a client that is already connected. +*/ +class XDuplicateClient : public XSynergy { +public: + XDuplicateClient(const CString& name); + + //! @name accessors + //@{ + + //! Get client's name + virtual const CString& + getName() const throw(); + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + CString m_name; +}; + +//! Client not in map exception +/*! +Thrown when a client attempting to connect is using a name that is +unknown to the server. +*/ +class XUnknownClient : public XSynergy { +public: + XUnknownClient(const CString& name); + + //! @name accessors + //@{ + + //! Get the client's name + virtual const CString& + getName() const throw(); + + //@} + +protected: + virtual CString getWhat() const throw(); + +private: + CString m_name; +}; + +#endif diff --git a/win32util/autodep.cpp b/win32util/autodep.cpp new file mode 100644 index 00000000..2a2e9d50 --- /dev/null +++ b/win32util/autodep.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include + +using namespace std; + +static +string +baseName(const string& filename) +{ + return filename.substr(0, filename.rfind('.')); +} + +static +int +writeMakefile(const string& dstdir, const set& depFilenames) +{ + string makeFilename = dstdir + "\\deps.mak"; + ofstream makeFile(makeFilename.c_str()); + if (!makeFile) { + cerr << "Can't open '" << makeFilename << "' for writing" << endl; + return 1; + } + + for (set::const_iterator i = depFilenames.begin(); + i != depFilenames.end(); ++i) { + makeFile << "!if EXIST(\"" << *i << "\")" << endl; + makeFile << "!include \"" << *i << "\"" << endl; + makeFile << "!endif" << endl; + } + + return 0; +} + +static +void +writeDependencies( + const string& filename, + const string& srcdir, + const string& dstdir, + const set& paths) +{ + string basename = baseName(filename); + string depFilename = dstdir + "\\" + basename + ".d"; + ofstream depFile(depFilename.c_str()); + if (!depFile) { + cerr << "Can't open '" << depFilename << "' for writing" << endl; + return; + } + + // Write dependencies rule for filename + depFile << "\"" << dstdir << "\\" << basename << ".obj\": \"" << + srcdir << "\\" << filename << "\" \\" << endl; + for (set::const_iterator i = paths.begin(); i != paths.end(); ++i) { + depFile << "\t\"" << *i << "\" \\" << endl; + } + depFile << "\t$(NULL)" << endl; +} + +static +int +writeDepfiles(const string& srcdir, const string& dstdir) +{ + const string includeLine = "Note: including file:"; + + // Parse stdin + string line; + string filename; + set paths; + locale loc = locale::classic(); + const ctype& ct = use_facet >(loc); + while (getline(cin, line)) { + bool echo = true; + + // Check for include line + if (line.compare(0, includeLine.length(), includeLine) == 0) { + // Strip includeLine and leading spaces + line.erase(0, line.find_first_not_of(" ", includeLine.length())); + if (line.length() == 0) { + continue; + } + + // Uppercase all drive letters + if (line.length() > 2 && line[1] == ':') { + line[0] = ct.toupper(line[0]); + } + + // Record path + paths.insert(line); + echo = false; + } + + // Maybe a source filename + else if (line.rfind(".cpp") == line.length() - 4) { + // Write dependencies for previous source file + if (filename.length() != 0) { + writeDependencies(filename, srcdir, dstdir, paths); + paths.clear(); + } + filename = line; + } + + // Otherwise other output + else { + // do nothing + } + + if (echo) { + cout << line << endl; + } + } + + // Write dependencies for last source file + if (filename.length() != 0) { + writeDependencies(filename, srcdir, dstdir, paths); + paths.clear(); + } + + return 0; +} + +int +main(int argc, char** argv) +{ + if (argc < 3) { + cerr << "usage: " << argv[0] << + " []" << endl; + return 1; + } + string srcdir = argv[1]; + string dstdir = argv[2]; + + // If depfiles were supplied then create a makefile in outdir to load + // all of them. + int result; + if (argc > 3) { + set depFilenames(argv + 3, argv + argc); + result = writeMakefile(dstdir, depFilenames); + } + + // Otherwise parse stdin and create a depfile for each listed file + else { + result = writeDepfiles(srcdir, dstdir); + } + + return result; +}