From 47ca409ff987fe6af518234861aff0bf67363bc6 Mon Sep 17 00:00:00 2001 From: crs Date: Tue, 2 Sep 2003 22:05:47 +0000 Subject: [PATCH] Merged primary and secondary screens into one class. --- cmd/synergyc/synergyc.cpp | 33 +- cmd/synergys/synergys.cpp | 30 +- configure.in | 2 +- lib/arch/CArch.cpp | 5 + lib/arch/CArchConsoleWindows.cpp | 7 + lib/arch/CArchMiscWindows.cpp | 27 +- lib/arch/CArchMiscWindows.h | 3 + lib/arch/CArchMultithreadPosix.cpp | 10 +- lib/arch/IArchMultithread.h | 8 +- lib/base/CPriorityQueue.h | 125 ++ lib/base/Makefile.am | 1 + lib/client/CClient.cpp | 31 +- lib/client/CClient.h | 21 +- lib/platform/CMSWindowsDesktop.cpp | 75 + lib/platform/CMSWindowsDesktop.h | 52 + lib/platform/CMSWindowsKeyMapper.cpp | 1495 +++++++++++++ lib/platform/CMSWindowsKeyMapper.h | 142 ++ lib/platform/CMSWindowsPrimaryScreen.cpp | 1816 --------------- lib/platform/CMSWindowsPrimaryScreen.h | 140 -- lib/platform/CMSWindowsScreen.cpp | 1952 +++++++++++++---- lib/platform/CMSWindowsScreen.h | 249 ++- lib/platform/CMSWindowsSecondaryScreen.cpp | 1338 ----------- lib/platform/CMSWindowsSecondaryScreen.h | 137 -- lib/platform/CSynergyHook.cpp | 24 +- lib/platform/CSynergyHook.h | 9 +- lib/platform/CXWindowsKeyMapper.cpp | 910 ++++++++ lib/platform/CXWindowsKeyMapper.h | 153 ++ lib/platform/CXWindowsPrimaryScreen.cpp | 1044 --------- lib/platform/CXWindowsPrimaryScreen.h | 126 -- lib/platform/CXWindowsScreen.cpp | 1590 +++++++++++--- lib/platform/CXWindowsScreen.h | 294 ++- lib/platform/CXWindowsSecondaryScreen.cpp | 1780 --------------- lib/platform/CXWindowsSecondaryScreen.h | 195 -- lib/platform/CXWindowsUtil.cpp | 249 +++ lib/platform/CXWindowsUtil.h | 13 + lib/platform/IMSWindowsScreenEventHandler.h | 55 - lib/platform/Makefile.am | 15 +- lib/platform/platform.dsp | 24 +- lib/server/CPrimaryClient.cpp | 31 +- lib/server/CPrimaryClient.h | 11 +- lib/server/CServer.cpp | 16 +- lib/server/CServer.h | 15 +- lib/synergy/CPrimaryScreen.cpp | 281 --- lib/synergy/CPrimaryScreen.h | 349 --- lib/synergy/CScreen.cpp | 1001 +++++++++ lib/synergy/CScreen.h | 357 +++ lib/synergy/CSecondaryScreen.cpp | 781 ------- lib/synergy/CSecondaryScreen.h | 446 ---- lib/synergy/IKeyState.h | 156 ++ lib/synergy/{IScreen.h => IPlatformScreen.h} | 110 +- lib/synergy/IPrimaryScreen.h | 84 + lib/synergy/IScreenEventHandler.h | 69 - ...rimaryScreenFactory.h => IScreenFactory.h} | 20 +- lib/synergy/ISecondaryScreen.h | 82 + lib/synergy/ISecondaryScreenFactory.h | 38 - lib/synergy/Makefile.am | 15 +- lib/synergy/libsynergy.dsp | 30 +- 57 files changed, 8263 insertions(+), 9809 deletions(-) create mode 100644 lib/base/CPriorityQueue.h create mode 100644 lib/platform/CMSWindowsDesktop.cpp create mode 100644 lib/platform/CMSWindowsDesktop.h create mode 100644 lib/platform/CMSWindowsKeyMapper.cpp create mode 100644 lib/platform/CMSWindowsKeyMapper.h delete mode 100644 lib/platform/CMSWindowsPrimaryScreen.cpp delete mode 100644 lib/platform/CMSWindowsPrimaryScreen.h delete mode 100644 lib/platform/CMSWindowsSecondaryScreen.cpp delete mode 100644 lib/platform/CMSWindowsSecondaryScreen.h create mode 100644 lib/platform/CXWindowsKeyMapper.cpp create mode 100644 lib/platform/CXWindowsKeyMapper.h delete mode 100644 lib/platform/CXWindowsPrimaryScreen.cpp delete mode 100644 lib/platform/CXWindowsPrimaryScreen.h delete mode 100644 lib/platform/CXWindowsSecondaryScreen.cpp delete mode 100644 lib/platform/CXWindowsSecondaryScreen.h delete mode 100644 lib/platform/IMSWindowsScreenEventHandler.h delete mode 100644 lib/synergy/CPrimaryScreen.cpp delete mode 100644 lib/synergy/CPrimaryScreen.h create mode 100644 lib/synergy/CScreen.cpp create mode 100644 lib/synergy/CScreen.h delete mode 100644 lib/synergy/CSecondaryScreen.cpp delete mode 100644 lib/synergy/CSecondaryScreen.h create mode 100644 lib/synergy/IKeyState.h rename lib/synergy/{IScreen.h => IPlatformScreen.h} (58%) create mode 100644 lib/synergy/IPrimaryScreen.h delete mode 100644 lib/synergy/IScreenEventHandler.h rename lib/synergy/{IPrimaryScreenFactory.h => IScreenFactory.h} (64%) create mode 100644 lib/synergy/ISecondaryScreen.h delete mode 100644 lib/synergy/ISecondaryScreenFactory.h diff --git a/cmd/synergyc/synergyc.cpp b/cmd/synergyc/synergyc.cpp index ce2dca07..02f84c3d 100644 --- a/cmd/synergyc/synergyc.cpp +++ b/cmd/synergyc/synergyc.cpp @@ -13,7 +13,7 @@ */ #include "CClient.h" -#include "ISecondaryScreenFactory.h" +#include "IScreenFactory.h" #include "ProtocolTypes.h" #include "Version.h" #include "XScreen.h" @@ -35,14 +35,13 @@ #define DAEMON_RUNNING(running_) #if WINDOWS_LIKE #include "CMSWindowsScreen.h" -#include "CMSWindowsSecondaryScreen.h" #include "CArchMiscWindows.h" #include "CMSWindowsClientTaskBarReceiver.h" #include "resource.h" #undef DAEMON_RUNNING #define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) #elif UNIX_LIKE -#include "CXWindowsSecondaryScreen.h" +#include "CXWindowsScreen.h" #include "CXWindowsClientTaskBarReceiver.h" #endif @@ -88,28 +87,28 @@ CArgs* CArgs::s_instance = NULL; // platform dependent factories // -//! Factory for creating secondary screens +//! Factory for creating screens /*! -Objects of this type create secondary screens appropriate for the -platform. +Objects of this type create screens appropriate for the platform. */ -class CSecondaryScreenFactory : public ISecondaryScreenFactory { +class CScreenFactory : public IScreenFactory { public: - CSecondaryScreenFactory() { } - virtual ~CSecondaryScreenFactory() { } + CScreenFactory() { } + virtual ~CScreenFactory() { } - // ISecondaryScreenFactory overrides - virtual CSecondaryScreen* - create(IScreenReceiver*); + // IScreenFactory overrides + virtual IPlatformScreen* + create(IScreenReceiver*, IPrimaryScreenReceiver*); }; -CSecondaryScreen* -CSecondaryScreenFactory::create(IScreenReceiver* receiver) +IPlatformScreen* +CScreenFactory::create(IScreenReceiver* receiver, + IPrimaryScreenReceiver* primaryReceiver) { #if WINDOWS_LIKE - return new CMSWindowsSecondaryScreen(receiver); + return new CMSWindowsScreen(receiver, primaryReceiver); #elif UNIX_LIKE - return new CXWindowsSecondaryScreen(receiver); + return new CXWindowsScreen(receiver, primaryReceiver); #endif } @@ -167,7 +166,7 @@ realMain(void) // create client s_client = new CClient(ARG->m_name); s_client->setAddress(ARG->m_serverAddress); - s_client->setScreenFactory(new CSecondaryScreenFactory); + s_client->setScreenFactory(new CScreenFactory); s_client->setSocketFactory(new CTCPSocketFactory); s_client->setStreamFilterFactory(NULL); diff --git a/cmd/synergys/synergys.cpp b/cmd/synergys/synergys.cpp index e3fb74de..59b76fce 100644 --- a/cmd/synergys/synergys.cpp +++ b/cmd/synergys/synergys.cpp @@ -14,7 +14,7 @@ #include "CServer.h" #include "CConfig.h" -#include "IPrimaryScreenFactory.h" +#include "IScreenFactory.h" #include "ProtocolTypes.h" #include "Version.h" #include "XScreen.h" @@ -34,14 +34,13 @@ #define DAEMON_RUNNING(running_) #if WINDOWS_LIKE #include "CMSWindowsScreen.h" -#include "CMSWindowsPrimaryScreen.h" #include "CArchMiscWindows.h" #include "CMSWindowsServerTaskBarReceiver.h" #include "resource.h" #undef DAEMON_RUNNING #define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_) #elif UNIX_LIKE -#include "CXWindowsPrimaryScreen.h" +#include "CXWindowsScreen.h" #include "CXWindowsServerTaskBarReceiver.h" #endif @@ -100,29 +99,28 @@ CArgs* CArgs::s_instance = NULL; // platform dependent factories // -//! Factory for creating primary screens +//! Factory for creating screens /*! -Objects of this type create primary screens appropriate for the -platform. +Objects of this type create screens appropriate for the platform. */ -class CPrimaryScreenFactory : public IPrimaryScreenFactory { +class CScreenFactory : public IScreenFactory { public: - CPrimaryScreenFactory() { } - virtual ~CPrimaryScreenFactory() { } + CScreenFactory() { } + virtual ~CScreenFactory() { } - // IPrimaryScreenFactory overrides - virtual CPrimaryScreen* + // IScreenFactory overrides + virtual IPlatformScreen* create(IScreenReceiver*, IPrimaryScreenReceiver*); }; -CPrimaryScreen* -CPrimaryScreenFactory::create(IScreenReceiver* receiver, +IPlatformScreen* +CScreenFactory::create(IScreenReceiver* receiver, IPrimaryScreenReceiver* primaryReceiver) { #if WINDOWS_LIKE - return new CMSWindowsPrimaryScreen(receiver, primaryReceiver); + return new CMSWindowsScreen(receiver, primaryReceiver); #elif UNIX_LIKE - return new CXWindowsPrimaryScreen(receiver, primaryReceiver); + return new CXWindowsScreen(receiver, primaryReceiver); #endif } @@ -201,7 +199,7 @@ realMain(void) // create server s_server = new CServer(ARG->m_name); s_server->setConfig(ARG->m_config); - s_server->setScreenFactory(new CPrimaryScreenFactory); + s_server->setScreenFactory(new CScreenFactory); s_server->setSocketFactory(new CTCPSocketFactory); s_server->setStreamFilterFactory(NULL); diff --git a/configure.in b/configure.in index a8beba9e..cb0e2a6c 100644 --- a/configure.in +++ b/configure.in @@ -96,7 +96,7 @@ dnl checks for system services dnl adjust variables for X11 and pthreads -CXXFLAGS="$CXXFLAGS $SYNERGY_CXXFLAGS $X_CFLAGS $PTHREAD_CFLAGS" +CXXFLAGS="$CXXFLAGS $SYNERGY_CXXFLAGS $X_CFLAGS $PTHREAD_CFLAGS -D_BSD_SOURCE -D_XOPEN_SOURCE=500" LIBS="$NANOSLEEP_LIBS $INET_ATON_LIBS $PTHREAD_LIBS $LIBS" AC_OUTPUT([ diff --git a/lib/arch/CArch.cpp b/lib/arch/CArch.cpp index b8322c33..30bfb38c 100644 --- a/lib/arch/CArch.cpp +++ b/lib/arch/CArch.cpp @@ -32,6 +32,7 @@ # include "CArchDaemonWindows.h" # include "CArchFileWindows.h" # include "CArchLogWindows.h" +# include "CArchMiscWindows.h" # include "CArchMultithreadWindows.h" # include "CArchNetworkWinsock.h" # include "CArchSleepWindows.h" @@ -116,6 +117,10 @@ CArch::CArch(ARCH_ARGS* args) m_console = new ARCH_CONSOLE; m_daemon = new ARCH_DAEMON; m_taskbar = new ARCH_TASKBAR(args); + +#if WINDOWS_LIKE + CArchMiscWindows::init(); +#endif } CArch::~CArch() diff --git a/lib/arch/CArchConsoleWindows.cpp b/lib/arch/CArchConsoleWindows.cpp index a89a42f4..f479b378 100644 --- a/lib/arch/CArchConsoleWindows.cpp +++ b/lib/arch/CArchConsoleWindows.cpp @@ -29,6 +29,13 @@ CArchConsoleWindows::CArchConsoleWindows() : s_thread = ARCH->newCurrentThread(); m_mutex = ARCH->newMutex(); + + // dummy write to stderr to create locks in stdio from the main + // thread. if we open the console from another thread then we + // can deadlock in stdio when trying to write from a 3rd thread. + // writes to stderr without a console don't go anywhere so the + // user won't notice this. + fprintf(stderr, "\n"); } CArchConsoleWindows::~CArchConsoleWindows() diff --git a/lib/arch/CArchMiscWindows.cpp b/lib/arch/CArchMiscWindows.cpp index f76b42f1..1fb3d7bd 100644 --- a/lib/arch/CArchMiscWindows.cpp +++ b/lib/arch/CArchMiscWindows.cpp @@ -19,16 +19,31 @@ // CArchMiscWindows // +void +CArchMiscWindows::init() +{ + isWindows95Family(); +} + bool CArchMiscWindows::isWindows95Family() { - OSVERSIONINFO version; - version.dwOSVersionInfoSize = sizeof(version); - if (GetVersionEx(&version) == 0) { - // cannot determine OS; assume windows 95 family - return true; + 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 (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + return result; } int diff --git a/lib/arch/CArchMiscWindows.h b/lib/arch/CArchMiscWindows.h index dc993362..766553ff 100644 --- a/lib/arch/CArchMiscWindows.h +++ b/lib/arch/CArchMiscWindows.h @@ -26,6 +26,9 @@ class CArchMiscWindows { public: typedef int (*RunFunc)(void); + //! Initialize + static void init(); + //! Test if windows 95, et al. /*! Returns true iff the platform is win95/98/me. diff --git a/lib/arch/CArchMultithreadPosix.cpp b/lib/arch/CArchMultithreadPosix.cpp index bef806bf..67e5fcca 100644 --- a/lib/arch/CArchMultithreadPosix.cpp +++ b/lib/arch/CArchMultithreadPosix.cpp @@ -252,14 +252,12 @@ CArchMultithreadPosix::waitCondVar(CArchCond cond, CArchMutex CArchMultithreadPosix::newMutex() { + pthread_mutexattr_t attr; + int status = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + assert(status == 0); CArchMutexImpl* mutex = new CArchMutexImpl; - int status = pthread_mutex_init(&mutex->m_mutex, NULL); + status = pthread_mutex_init(&mutex->m_mutex, &attr); assert(status == 0); -/* - status = pthread_mutexattr_settype(&mutex->m_mutex, - PTHREAD_MUTEX_RECURSIVE); - assert(status == 0); -*/ return mutex; } diff --git a/lib/arch/IArchMultithread.h b/lib/arch/IArchMultithread.h index ee376771..69d88694 100644 --- a/lib/arch/IArchMultithread.h +++ b/lib/arch/IArchMultithread.h @@ -124,11 +124,11 @@ public: // mutex methods // - //! Create a non-recursive mutex + //! Create a recursive mutex /*! - Creates a non-recursive mutex. A thread must not lock a - non-recursive mutex when it already holds a lock on that mutex. - If it does it will deadlock. The mutex is an opaque data type. + 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; diff --git a/lib/base/CPriorityQueue.h b/lib/base/CPriorityQueue.h new file mode 100644 index 00000000..a03024b1 --- /dev/null +++ b/lib/base/CPriorityQueue.h @@ -0,0 +1,125 @@ +/* + * 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 , + class Compare = std::greater > +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(); + } + + //! 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/Makefile.am b/lib/base/Makefile.am index f750ba7f..f9006ab2 100644 --- a/lib/base/Makefile.am +++ b/lib/base/Makefile.am @@ -36,6 +36,7 @@ libbase_a_SOURCES = \ CFunctionJob.h \ CJobList.h \ CLog.h \ + CPriorityQueue.h \ CStopwatch.h \ CString.h \ CStringUtil.h \ diff --git a/lib/client/CClient.cpp b/lib/client/CClient.cpp index 8ada1111..1cc8a688 100644 --- a/lib/client/CClient.cpp +++ b/lib/client/CClient.cpp @@ -14,12 +14,13 @@ #include "CClient.h" #include "CServerProxy.h" -#include "ISecondaryScreenFactory.h" +#include "CScreen.h" +#include "IScreenFactory.h" #include "CClipboard.h" #include "CInputPacketStream.h" #include "COutputPacketStream.h" #include "CProtocolUtil.h" -#include "CSecondaryScreen.h" +#include "IPlatformScreen.h" #include "IServer.h" #include "ProtocolTypes.h" #include "XScreen.h" @@ -72,7 +73,7 @@ CClient::setAddress(const CNetworkAddress& serverAddress) } void -CClient::setScreenFactory(ISecondaryScreenFactory* adopted) +CClient::setScreenFactory(IScreenFactory* adopted) { CLock lock(&m_mutex); delete m_screenFactory; @@ -305,8 +306,9 @@ CClient::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool) CLock lock(&m_mutex); m_active = true; } - - m_screen->enter(xAbs, yAbs, mask); + m_screen->mouseMove(xAbs, yAbs); + m_screen->enter(); + m_screen->setToggleState(mask); } bool @@ -464,7 +466,10 @@ CClient::openSecondaryScreen() // create screen LOG((CLOG_DEBUG1 "creating secondary screen")); if (m_screenFactory != NULL) { - m_screen = m_screenFactory->create(this); + IPlatformScreen* platformScreen = m_screenFactory->create(this, NULL); + if (platformScreen != NULL) { + m_screen = new CScreen(platformScreen, this); + } } if (m_screen == NULL) { throw XScreenOpenFailure(); @@ -634,9 +639,11 @@ CClient::runServer() m_server = proxy; } + bool enabled = false; try { - // prepare for remote control - m_screen->remoteControl(); + // enable the screen + m_screen->enable(); + enabled = true; // process messages bool rejected = true; @@ -647,8 +654,8 @@ CClient::runServer() setStatus(kNotRunning); } - // prepare for local control - m_screen->localControl(); + // disable the screen + m_screen->disable(); // clean up CLock lock(&m_mutex); @@ -661,7 +668,9 @@ CClient::runServer() } catch (...) { setStatus(kNotRunning); - m_screen->localControl(); + if (enabled) { + m_screen->disable(); + } CLock lock(&m_mutex); m_rejected = false; m_server = NULL; diff --git a/lib/client/CClient.h b/lib/client/CClient.h index 5647e7e3..4a802a5a 100644 --- a/lib/client/CClient.h +++ b/lib/client/CClient.h @@ -22,12 +22,12 @@ #include "CMutex.h" #include "CJobList.h" -class CSecondaryScreen; +class CScreen; class CServerProxy; class CThread; class IDataSocket; class IScreenReceiver; -class ISecondaryScreenFactory; +class IScreenFactory; class ISocketFactory; class IStreamFilterFactory; @@ -60,13 +60,12 @@ public: */ void setAddress(const CNetworkAddress& serverAddress); - //! Set secondary screen factory + //! Set screen factory /*! - Sets the factory for creating secondary screens. This must be - set before calling open(). This object takes ownership of the - factory. + Sets the factory for creating screens. This must be set before + calling open(). This object takes ownership of the factory. */ - void setScreenFactory(ISecondaryScreenFactory*); + void setScreenFactory(IScreenFactory*); //! Set socket factory /*! @@ -184,12 +183,12 @@ private: private: CMutex m_mutex; CString m_name; - CSecondaryScreen* m_screen; + CScreen* m_screen; IScreenReceiver* m_server; CNetworkAddress m_serverAddress; - ISecondaryScreenFactory* m_screenFactory; - ISocketFactory* m_socketFactory; - IStreamFilterFactory* m_streamFilterFactory; + IScreenFactory* m_screenFactory; + ISocketFactory* m_socketFactory; + IStreamFilterFactory* m_streamFilterFactory; CThread* m_session; bool m_active; bool m_rejected; diff --git a/lib/platform/CMSWindowsDesktop.cpp b/lib/platform/CMSWindowsDesktop.cpp new file mode 100644 index 00000000..f62b6b1b --- /dev/null +++ b/lib/platform/CMSWindowsDesktop.cpp @@ -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. + */ + +#include "CMSWindowsDesktop.h" +#include "CLog.h" +#include "CArchMiscWindows.h" +#include + +// +// CMSWindowsDesktop +// + +HDESK +CMSWindowsDesktop::openInputDesktop() +{ + if (CArchMiscWindows::isWindows95Family()) { + // 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 +CMSWindowsDesktop::closeDesktop(HDESK desk) +{ + // on 95/98/me we don't need to close the desktop returned by + // openInputDesktop(). + if (desk != NULL && !CArchMiscWindows::isWindows95Family()) { + CloseDesktop(desk); + } +} + +bool +CMSWindowsDesktop::setDesktop(HDESK desk) +{ + // 95/98/me doesn't support multiple desktops so just return + // true on those platforms. + return (CArchMiscWindows::isWindows95Family() || + SetThreadDesktop(desk) != 0); +} + +CString +CMSWindowsDesktop::getDesktopName(HDESK desk) +{ + if (desk == NULL) { + return CString(); + } + else if (CArchMiscWindows::isWindows95Family()) { + 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; + } +} diff --git a/lib/platform/CMSWindowsDesktop.h b/lib/platform/CMSWindowsDesktop.h new file mode 100644 index 00000000..6f1d6afe --- /dev/null +++ b/lib/platform/CMSWindowsDesktop.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 CMSWINDOWSDESKTOP_H +#define CMSWINDOWSDESKTOP_H + +#include "CString.h" +#include + +//! Encapsulate Microsoft Windows desktop +class CMSWindowsDesktop { +public: + //! Open the input desktop + /*! + Opens the input desktop. The caller must close the desktop. + */ + static HDESK openInputDesktop(); + + //! Close a desktop + /*! + Closes the given desktop. + */ + static void closeDesktop(HDESK); + + //! Change current desktop + /*! + Changes the calling thread's desktop, return true iff successful. + The call will fail if the calling thread has any windows or a hooks + on the current desktop. + */ + static bool setDesktop(HDESK); + + //! Get the desktop's name. + /*! + Returns the current desktop's name. Returns a constant string + on 95/98/Me. + */ + static CString getDesktopName(HDESK); +}; + +#endif diff --git a/lib/platform/CMSWindowsKeyMapper.cpp b/lib/platform/CMSWindowsKeyMapper.cpp new file mode 100644 index 00000000..4e4fc957 --- /dev/null +++ b/lib/platform/CMSWindowsKeyMapper.cpp @@ -0,0 +1,1495 @@ +/* + * 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 "CMSWindowsKeyMapper.h" +#include "CLog.h" + +// multimedia keys +#if !defined(VK_BROWSER_BACK) +#define VK_BROWSER_BACK 0xA6 +#define VK_BROWSER_FORWARD 0xA7 +#define VK_BROWSER_REFRESH 0xA8 +#define VK_BROWSER_STOP 0xA9 +#define VK_BROWSER_SEARCH 0xAA +#define VK_BROWSER_FAVORITES 0xAB +#define VK_BROWSER_HOME 0xAC +#define VK_VOLUME_MUTE 0xAD +#define VK_VOLUME_DOWN 0xAE +#define VK_VOLUME_UP 0xAF +#define VK_MEDIA_NEXT_TRACK 0xB0 +#define VK_MEDIA_PREV_TRACK 0xB1 +#define VK_MEDIA_STOP 0xB2 +#define VK_MEDIA_PLAY_PAUSE 0xB3 +#define VK_LAUNCH_MAIL 0xB4 +#define VK_LAUNCH_MEDIA_SELECT 0xB5 +#define VK_LAUNCH_APP1 0xB6 +#define VK_LAUNCH_APP2 0xB7 +#endif + +// +// CMSWindowsKeyMapper +// + +// table of modifier keys +const CMSWindowsKeyMapper::CModifierKeys + CMSWindowsKeyMapper::s_modifiers[] = +{ + KeyModifierShift, { VK_LSHIFT, VK_RSHIFT }, + KeyModifierControl, { VK_LCONTROL, VK_RCONTROL | 0x100 }, + KeyModifierAlt, { VK_LMENU, VK_RMENU | 0x100 }, + KeyModifierSuper, { VK_LWIN | 0x100, VK_RWIN | 0x100 }, + KeyModifierCapsLock, { VK_CAPITAL, 0 }, + KeyModifierNumLock, { VK_NUMLOCK | 0x100, 0 }, + KeyModifierScrollLock, { VK_SCROLL, 0 } +}; + +const char* CMSWindowsKeyMapper::s_vkToName[] = +{ + "vk 0x00", + "Left Button", + "Right Button", + "VK_CANCEL", + "Middle Button", + "vk 0x05", + "vk 0x06", + "vk 0x07", + "VK_BACK", + "VK_TAB", + "vk 0x0a", + "vk 0x0b", + "VK_CLEAR", + "VK_RETURN", + "vk 0x0e", + "vk 0x0f", + "VK_SHIFT", + "VK_CONTROL", + "VK_MENU", + "VK_PAUSE", + "VK_CAPITAL", + "VK_KANA", + "vk 0x16", + "VK_JUNJA", + "VK_FINAL", + "VK_KANJI", + "vk 0x1a", + "VK_ESCAPE", + "VK_CONVERT", + "VK_NONCONVERT", + "VK_ACCEPT", + "VK_MODECHANGE", + "VK_SPACE", + "VK_PRIOR", + "VK_NEXT", + "VK_END", + "VK_HOME", + "VK_LEFT", + "VK_UP", + "VK_RIGHT", + "VK_DOWN", + "VK_SELECT", + "VK_PRINT", + "VK_EXECUTE", + "VK_SNAPSHOT", + "VK_INSERT", + "VK_DELETE", + "VK_HELP", + "VK_0", + "VK_1", + "VK_2", + "VK_3", + "VK_4", + "VK_5", + "VK_6", + "VK_7", + "VK_8", + "VK_9", + "vk 0x3a", + "vk 0x3b", + "vk 0x3c", + "vk 0x3d", + "vk 0x3e", + "vk 0x3f", + "vk 0x40", + "VK_A", + "VK_B", + "VK_C", + "VK_D", + "VK_E", + "VK_F", + "VK_G", + "VK_H", + "VK_I", + "VK_J", + "VK_K", + "VK_L", + "VK_M", + "VK_N", + "VK_O", + "VK_P", + "VK_Q", + "VK_R", + "VK_S", + "VK_T", + "VK_U", + "VK_V", + "VK_W", + "VK_X", + "VK_Y", + "VK_Z", + "VK_LWIN", + "VK_RWIN", + "VK_APPS", + "vk 0x5e", + "vk 0x5f", + "VK_NUMPAD0", + "VK_NUMPAD1", + "VK_NUMPAD2", + "VK_NUMPAD3", + "VK_NUMPAD4", + "VK_NUMPAD5", + "VK_NUMPAD6", + "VK_NUMPAD7", + "VK_NUMPAD8", + "VK_NUMPAD9", + "VK_MULTIPLY", + "VK_ADD", + "VK_SEPARATOR", + "VK_SUBTRACT", + "VK_DECIMAL", + "VK_DIVIDE", + "VK_F1", + "VK_F2", + "VK_F3", + "VK_F4", + "VK_F5", + "VK_F6", + "VK_F7", + "VK_F8", + "VK_F9", + "VK_F10", + "VK_F11", + "VK_F12", + "VK_F13", + "VK_F14", + "VK_F15", + "VK_F16", + "VK_F17", + "VK_F18", + "VK_F19", + "VK_F20", + "VK_F21", + "VK_F22", + "VK_F23", + "VK_F24", + "vk 0x88", + "vk 0x89", + "vk 0x8a", + "vk 0x8b", + "vk 0x8c", + "vk 0x8d", + "vk 0x8e", + "vk 0x8f", + "VK_NUMLOCK", + "VK_SCROLL", + "vk 0x92", + "vk 0x93", + "vk 0x94", + "vk 0x95", + "vk 0x96", + "vk 0x97", + "vk 0x98", + "vk 0x99", + "vk 0x9a", + "vk 0x9b", + "vk 0x9c", + "vk 0x9d", + "vk 0x9e", + "vk 0x9f", + "VK_LSHIFT", + "VK_RSHIFT", + "VK_LCONTROL", + "VK_RCONTROL", + "VK_LMENU", + "VK_RMENU", + "VK_BROWSER_BACK", + "VK_BROWSER_FORWARD", + "VK_BROWSER_REFRESH", + "VK_BROWSER_STOP", + "VK_BROWSER_SEARCH", + "VK_BROWSER_FAVORITES", + "VK_BROWSER_HOME", + "VK_VOLUME_MUTE", + "VK_VOLUME_DOWN", + "VK_VOLUME_UP", + "VK_MEDIA_NEXT_TRACK", + "VK_MEDIA_PREV_TRACK", + "VK_MEDIA_STOP", + "VK_MEDIA_PLAY_PAUSE", + "VK_LAUNCH_MAIL", + "VK_LAUNCH_MEDIA_SELECT", + "VK_LAUNCH_APP1", + "VK_LAUNCH_APP2", + "vk 0xb8", + "vk 0xb9", + "vk 0xba", + "vk 0xbb", + "vk 0xbc", + "vk 0xbd", + "vk 0xbe", + "vk 0xbf", + "vk 0xc0", + "vk 0xc1", + "vk 0xc2", + "vk 0xc3", + "vk 0xc4", + "vk 0xc5", + "vk 0xc6", + "vk 0xc7", + "vk 0xc8", + "vk 0xc9", + "vk 0xca", + "vk 0xcb", + "vk 0xcc", + "vk 0xcd", + "vk 0xce", + "vk 0xcf", + "vk 0xd0", + "vk 0xd1", + "vk 0xd2", + "vk 0xd3", + "vk 0xd4", + "vk 0xd5", + "vk 0xd6", + "vk 0xd7", + "vk 0xd8", + "vk 0xd9", + "vk 0xda", + "vk 0xdb", + "vk 0xdc", + "vk 0xdd", + "vk 0xde", + "vk 0xdf", + "vk 0xe0", + "vk 0xe1", + "vk 0xe2", + "vk 0xe3", + "vk 0xe4", + "VK_PROCESSKEY", + "vk 0xe6", + "vk 0xe7", + "vk 0xe8", + "vk 0xe9", + "vk 0xea", + "vk 0xeb", + "vk 0xec", + "vk 0xed", + "vk 0xee", + "vk 0xef", + "vk 0xf0", + "vk 0xf1", + "vk 0xf2", + "vk 0xf3", + "vk 0xf4", + "vk 0xf5", + "VK_ATTN", + "VK_CRSEL", + "VK_EXSEL", + "VK_EREOF", + "VK_PLAY", + "VK_ZOOM", + "VK_NONAME", + "VK_PA1", + "VK_OEM_CLEAR", + "vk 0xff" +}; + +// map virtual keys to synergy key enumeration +const KeyID CMSWindowsKeyMapper::s_virtualKey[][2] = +{ + /* 0x00 */ kKeyNone, kKeyNone, // reserved + /* 0x01 */ kKeyNone, kKeyNone, // VK_LBUTTON + /* 0x02 */ kKeyNone, kKeyNone, // VK_RBUTTON + /* 0x03 */ kKeyNone, kKeyBreak, // VK_CANCEL + /* 0x04 */ kKeyNone, kKeyNone, // VK_MBUTTON + /* 0x05 */ kKeyNone, kKeyNone, // undefined + /* 0x06 */ kKeyNone, kKeyNone, // undefined + /* 0x07 */ kKeyNone, kKeyNone, // undefined + /* 0x08 */ kKeyBackSpace, kKeyNone, // VK_BACK + /* 0x09 */ kKeyTab, kKeyNone, // VK_TAB + /* 0x0a */ kKeyNone, kKeyNone, // undefined + /* 0x0b */ kKeyNone, kKeyNone, // undefined + /* 0x0c */ kKeyClear, kKeyClear, // VK_CLEAR + /* 0x0d */ kKeyReturn, kKeyKP_Enter, // VK_RETURN + /* 0x0e */ kKeyNone, kKeyNone, // undefined + /* 0x0f */ kKeyNone, kKeyNone, // undefined + /* 0x10 */ kKeyShift_L, kKeyShift_R, // VK_SHIFT + /* 0x11 */ kKeyControl_L, kKeyControl_R, // VK_CONTROL + /* 0x12 */ kKeyAlt_L, kKeyAlt_R, // VK_MENU + /* 0x13 */ kKeyPause, kKeyNone, // VK_PAUSE + /* 0x14 */ kKeyCapsLock, kKeyNone, // VK_CAPITAL + /* 0x15 */ kKeyNone, kKeyNone, // VK_KANA + /* 0x16 */ kKeyNone, kKeyNone, // VK_HANGUL + /* 0x17 */ kKeyNone, kKeyNone, // VK_JUNJA + /* 0x18 */ kKeyNone, kKeyNone, // VK_FINAL + /* 0x19 */ kKeyNone, kKeyNone, // VK_KANJI + /* 0x1a */ kKeyNone, kKeyNone, // undefined + /* 0x1b */ kKeyEscape, kKeyNone, // VK_ESCAPE + /* 0x1c */ kKeyNone, kKeyNone, // VK_CONVERT + /* 0x1d */ kKeyNone, kKeyNone, // VK_NONCONVERT + /* 0x1e */ kKeyNone, kKeyNone, // VK_ACCEPT + /* 0x1f */ kKeyNone, kKeyNone, // VK_MODECHANGE + /* 0x20 */ kKeyNone, kKeyNone, // VK_SPACE + /* 0x21 */ kKeyKP_PageUp, kKeyPageUp, // VK_PRIOR + /* 0x22 */ kKeyKP_PageDown, kKeyPageDown, // VK_NEXT + /* 0x23 */ kKeyKP_End, kKeyEnd, // VK_END + /* 0x24 */ kKeyKP_Home, kKeyHome, // VK_HOME + /* 0x25 */ kKeyKP_Left, kKeyLeft, // VK_LEFT + /* 0x26 */ kKeyKP_Up, kKeyUp, // VK_UP + /* 0x27 */ kKeyKP_Right, kKeyRight, // VK_RIGHT + /* 0x28 */ kKeyKP_Down, kKeyDown, // VK_DOWN + /* 0x29 */ kKeySelect, kKeySelect, // VK_SELECT + /* 0x2a */ kKeyNone, kKeyNone, // VK_PRINT + /* 0x2b */ kKeyExecute, kKeyExecute, // VK_EXECUTE + /* 0x2c */ kKeyPrint, kKeyPrint, // VK_SNAPSHOT + /* 0x2d */ kKeyKP_Insert, kKeyInsert, // VK_INSERT + /* 0x2e */ kKeyKP_Delete, kKeyDelete, // VK_DELETE + /* 0x2f */ kKeyHelp, kKeyHelp, // VK_HELP + /* 0x30 */ kKeyNone, kKeyNone, // VK_0 + /* 0x31 */ kKeyNone, kKeyNone, // VK_1 + /* 0x32 */ kKeyNone, kKeyNone, // VK_2 + /* 0x33 */ kKeyNone, kKeyNone, // VK_3 + /* 0x34 */ kKeyNone, kKeyNone, // VK_4 + /* 0x35 */ kKeyNone, kKeyNone, // VK_5 + /* 0x36 */ kKeyNone, kKeyNone, // VK_6 + /* 0x37 */ kKeyNone, kKeyNone, // VK_7 + /* 0x38 */ kKeyNone, kKeyNone, // VK_8 + /* 0x39 */ kKeyNone, kKeyNone, // VK_9 + /* 0x3a */ kKeyNone, kKeyNone, // undefined + /* 0x3b */ kKeyNone, kKeyNone, // undefined + /* 0x3c */ kKeyNone, kKeyNone, // undefined + /* 0x3d */ kKeyNone, kKeyNone, // undefined + /* 0x3e */ kKeyNone, kKeyNone, // undefined + /* 0x3f */ kKeyNone, kKeyNone, // undefined + /* 0x40 */ kKeyNone, kKeyNone, // undefined + /* 0x41 */ kKeyNone, kKeyNone, // VK_A + /* 0x42 */ kKeyNone, kKeyNone, // VK_B + /* 0x43 */ kKeyNone, kKeyNone, // VK_C + /* 0x44 */ kKeyNone, kKeyNone, // VK_D + /* 0x45 */ kKeyNone, kKeyNone, // VK_E + /* 0x46 */ kKeyNone, kKeyNone, // VK_F + /* 0x47 */ kKeyNone, kKeyNone, // VK_G + /* 0x48 */ kKeyNone, kKeyNone, // VK_H + /* 0x49 */ kKeyNone, kKeyNone, // VK_I + /* 0x4a */ kKeyNone, kKeyNone, // VK_J + /* 0x4b */ kKeyNone, kKeyNone, // VK_K + /* 0x4c */ kKeyNone, kKeyNone, // VK_L + /* 0x4d */ kKeyNone, kKeyNone, // VK_M + /* 0x4e */ kKeyNone, kKeyNone, // VK_N + /* 0x4f */ kKeyNone, kKeyNone, // VK_O + /* 0x50 */ kKeyNone, kKeyNone, // VK_P + /* 0x51 */ kKeyNone, kKeyNone, // VK_Q + /* 0x52 */ kKeyNone, kKeyNone, // VK_R + /* 0x53 */ kKeyNone, kKeyNone, // VK_S + /* 0x54 */ kKeyNone, kKeyNone, // VK_T + /* 0x55 */ kKeyNone, kKeyNone, // VK_U + /* 0x56 */ kKeyNone, kKeyNone, // VK_V + /* 0x57 */ kKeyNone, kKeyNone, // VK_W + /* 0x58 */ kKeyNone, kKeyNone, // VK_X + /* 0x59 */ kKeyNone, kKeyNone, // VK_Y + /* 0x5a */ kKeyNone, kKeyNone, // VK_Z + /* 0x5b */ kKeyNone, kKeySuper_L, // VK_LWIN + /* 0x5c */ kKeyNone, kKeySuper_R, // VK_RWIN + /* 0x5d */ kKeyMenu, kKeyMenu, // VK_APPS + /* 0x5e */ kKeyNone, kKeyNone, // undefined + /* 0x5f */ kKeyNone, kKeyNone, // undefined + /* 0x60 */ kKeyKP_0, kKeyNone, // VK_NUMPAD0 + /* 0x61 */ kKeyKP_1, kKeyNone, // VK_NUMPAD1 + /* 0x62 */ kKeyKP_2, kKeyNone, // VK_NUMPAD2 + /* 0x63 */ kKeyKP_3, kKeyNone, // VK_NUMPAD3 + /* 0x64 */ kKeyKP_4, kKeyNone, // VK_NUMPAD4 + /* 0x65 */ kKeyKP_5, kKeyNone, // VK_NUMPAD5 + /* 0x66 */ kKeyKP_6, kKeyNone, // VK_NUMPAD6 + /* 0x67 */ kKeyKP_7, kKeyNone, // VK_NUMPAD7 + /* 0x68 */ kKeyKP_8, kKeyNone, // VK_NUMPAD8 + /* 0x69 */ kKeyKP_9, kKeyNone, // VK_NUMPAD9 + /* 0x6a */ kKeyKP_Multiply, kKeyNone, // VK_MULTIPLY + /* 0x6b */ kKeyKP_Add, kKeyNone, // VK_ADD + /* 0x6c */ kKeyKP_Separator,kKeyKP_Separator,// VK_SEPARATOR + /* 0x6d */ kKeyKP_Subtract, kKeyNone, // VK_SUBTRACT + /* 0x6e */ kKeyKP_Decimal, kKeyNone, // VK_DECIMAL + /* 0x6f */ kKeyNone, kKeyKP_Divide, // VK_DIVIDE + /* 0x70 */ kKeyF1, kKeyNone, // VK_F1 + /* 0x71 */ kKeyF2, kKeyNone, // VK_F2 + /* 0x72 */ kKeyF3, kKeyNone, // VK_F3 + /* 0x73 */ kKeyF4, kKeyNone, // VK_F4 + /* 0x74 */ kKeyF5, kKeyNone, // VK_F5 + /* 0x75 */ kKeyF6, kKeyNone, // VK_F6 + /* 0x76 */ kKeyF7, kKeyNone, // VK_F7 + /* 0x77 */ kKeyF8, kKeyNone, // VK_F8 + /* 0x78 */ kKeyF9, kKeyNone, // VK_F9 + /* 0x79 */ kKeyF10, kKeyNone, // VK_F10 + /* 0x7a */ kKeyF11, kKeyNone, // VK_F11 + /* 0x7b */ kKeyF12, kKeyNone, // VK_F12 + /* 0x7c */ kKeyF13, kKeyF13, // VK_F13 + /* 0x7d */ kKeyF14, kKeyF14, // VK_F14 + /* 0x7e */ kKeyF15, kKeyF15, // VK_F15 + /* 0x7f */ kKeyF16, kKeyF16, // VK_F16 + /* 0x80 */ kKeyF17, kKeyF17, // VK_F17 + /* 0x81 */ kKeyF18, kKeyF18, // VK_F18 + /* 0x82 */ kKeyF19, kKeyF19, // VK_F19 + /* 0x83 */ kKeyF20, kKeyF20, // VK_F20 + /* 0x84 */ kKeyF21, kKeyF21, // VK_F21 + /* 0x85 */ kKeyF22, kKeyF22, // VK_F22 + /* 0x86 */ kKeyF23, kKeyF23, // VK_F23 + /* 0x87 */ kKeyF24, kKeyF24, // VK_F24 + /* 0x88 */ kKeyNone, kKeyNone, // unassigned + /* 0x89 */ kKeyNone, kKeyNone, // unassigned + /* 0x8a */ kKeyNone, kKeyNone, // unassigned + /* 0x8b */ kKeyNone, kKeyNone, // unassigned + /* 0x8c */ kKeyNone, kKeyNone, // unassigned + /* 0x8d */ kKeyNone, kKeyNone, // unassigned + /* 0x8e */ kKeyNone, kKeyNone, // unassigned + /* 0x8f */ kKeyNone, kKeyNone, // unassigned + /* 0x90 */ kKeyNumLock, kKeyNumLock, // VK_NUMLOCK + /* 0x91 */ kKeyScrollLock, kKeyNone, // VK_SCROLL + /* 0x92 */ kKeyNone, kKeyNone, // unassigned + /* 0x93 */ kKeyNone, kKeyNone, // unassigned + /* 0x94 */ kKeyNone, kKeyNone, // unassigned + /* 0x95 */ kKeyNone, kKeyNone, // unassigned + /* 0x96 */ kKeyNone, kKeyNone, // unassigned + /* 0x97 */ kKeyNone, kKeyNone, // unassigned + /* 0x98 */ kKeyNone, kKeyNone, // unassigned + /* 0x99 */ kKeyNone, kKeyNone, // unassigned + /* 0x9a */ kKeyNone, kKeyNone, // unassigned + /* 0x9b */ kKeyNone, kKeyNone, // unassigned + /* 0x9c */ kKeyNone, kKeyNone, // unassigned + /* 0x9d */ kKeyNone, kKeyNone, // unassigned + /* 0x9e */ kKeyNone, kKeyNone, // unassigned + /* 0x9f */ kKeyNone, kKeyNone, // unassigned + /* 0xa0 */ kKeyShift_L, kKeyShift_L, // VK_LSHIFT + /* 0xa1 */ kKeyShift_R, kKeyShift_R, // VK_RSHIFT + /* 0xa2 */ kKeyControl_L, kKeyControl_L, // VK_LCONTROL + /* 0xa3 */ kKeyControl_R, kKeyControl_R, // VK_RCONTROL + /* 0xa4 */ kKeyAlt_L, kKeyAlt_L, // VK_LMENU + /* 0xa5 */ kKeyAlt_R, kKeyAlt_R, // VK_RMENU + /* 0xa6 */ kKeyNone, kKeyWWWBack, // VK_BROWSER_BACK + /* 0xa7 */ kKeyNone, kKeyWWWForward, // VK_BROWSER_FORWARD + /* 0xa8 */ kKeyNone, kKeyWWWRefresh, // VK_BROWSER_REFRESH + /* 0xa9 */ kKeyNone, kKeyWWWStop, // VK_BROWSER_STOP + /* 0xaa */ kKeyNone, kKeyWWWSearch, // VK_BROWSER_SEARCH + /* 0xab */ kKeyNone, kKeyWWWFavorites, // VK_BROWSER_FAVORITES + /* 0xac */ kKeyNone, kKeyWWWHome, // VK_BROWSER_HOME + /* 0xad */ kKeyNone, kKeyAudioMute, // VK_VOLUME_MUTE + /* 0xae */ kKeyNone, kKeyAudioDown, // VK_VOLUME_DOWN + /* 0xaf */ kKeyNone, kKeyAudioUp, // VK_VOLUME_UP + /* 0xb0 */ kKeyNone, kKeyAudioNext, // VK_MEDIA_NEXT_TRACK + /* 0xb1 */ kKeyNone, kKeyAudioPrev, // VK_MEDIA_PREV_TRACK + /* 0xb2 */ kKeyNone, kKeyAudioStop, // VK_MEDIA_STOP + /* 0xb3 */ kKeyNone, kKeyAudioPlay, // VK_MEDIA_PLAY_PAUSE + /* 0xb4 */ kKeyNone, kKeyAppMail, // VK_LAUNCH_MAIL + /* 0xb5 */ kKeyNone, kKeyAppMedia, // VK_LAUNCH_MEDIA_SELECT + /* 0xb6 */ kKeyNone, kKeyAppUser1, // VK_LAUNCH_APP1 + /* 0xb7 */ kKeyNone, kKeyAppUser2, // VK_LAUNCH_APP2 + /* 0xb8 */ kKeyNone, kKeyNone, // unassigned + /* 0xb9 */ kKeyNone, kKeyNone, // unassigned + /* 0xba */ kKeyNone, kKeyNone, // OEM specific + /* 0xbb */ kKeyNone, kKeyNone, // OEM specific + /* 0xbc */ kKeyNone, kKeyNone, // OEM specific + /* 0xbd */ kKeyNone, kKeyNone, // OEM specific + /* 0xbe */ kKeyNone, kKeyNone, // OEM specific + /* 0xbf */ kKeyNone, kKeyNone, // OEM specific + /* 0xc0 */ kKeyNone, kKeyNone, // OEM specific + /* 0xc1 */ kKeyNone, kKeyNone, // unassigned + /* 0xc2 */ kKeyNone, kKeyNone, // unassigned + /* 0xc3 */ kKeyNone, kKeyNone, // unassigned + /* 0xc4 */ kKeyNone, kKeyNone, // unassigned + /* 0xc5 */ kKeyNone, kKeyNone, // unassigned + /* 0xc6 */ kKeyNone, kKeyNone, // unassigned + /* 0xc7 */ kKeyNone, kKeyNone, // unassigned + /* 0xc8 */ kKeyNone, kKeyNone, // unassigned + /* 0xc9 */ kKeyNone, kKeyNone, // unassigned + /* 0xca */ kKeyNone, kKeyNone, // unassigned + /* 0xcb */ kKeyNone, kKeyNone, // unassigned + /* 0xcc */ kKeyNone, kKeyNone, // unassigned + /* 0xcd */ kKeyNone, kKeyNone, // unassigned + /* 0xce */ kKeyNone, kKeyNone, // unassigned + /* 0xcf */ kKeyNone, kKeyNone, // unassigned + /* 0xd0 */ kKeyNone, kKeyNone, // unassigned + /* 0xd1 */ kKeyNone, kKeyNone, // unassigned + /* 0xd2 */ kKeyNone, kKeyNone, // unassigned + /* 0xd3 */ kKeyNone, kKeyNone, // unassigned + /* 0xd4 */ kKeyNone, kKeyNone, // unassigned + /* 0xd5 */ kKeyNone, kKeyNone, // unassigned + /* 0xd6 */ kKeyNone, kKeyNone, // unassigned + /* 0xd7 */ kKeyNone, kKeyNone, // unassigned + /* 0xd8 */ kKeyNone, kKeyNone, // unassigned + /* 0xd9 */ kKeyNone, kKeyNone, // unassigned + /* 0xda */ kKeyNone, kKeyNone, // unassigned + /* 0xdb */ kKeyNone, kKeyNone, // OEM specific + /* 0xdc */ kKeyNone, kKeyNone, // OEM specific + /* 0xdd */ kKeyNone, kKeyNone, // OEM specific + /* 0xde */ kKeyNone, kKeyNone, // OEM specific + /* 0xdf */ kKeyNone, kKeyNone, // OEM specific + /* 0xe0 */ kKeyNone, kKeyNone, // OEM specific + /* 0xe1 */ kKeyNone, kKeyNone, // OEM specific + /* 0xe2 */ kKeyNone, kKeyNone, // OEM specific + /* 0xe3 */ kKeyNone, kKeyNone, // OEM specific + /* 0xe4 */ kKeyNone, kKeyNone, // OEM specific + /* 0xe5 */ kKeyNone, kKeyNone, // unassigned + /* 0xe6 */ kKeyNone, kKeyNone, // OEM specific + /* 0xe7 */ kKeyNone, kKeyNone, // unassigned + /* 0xe8 */ kKeyNone, kKeyNone, // unassigned + /* 0xe9 */ kKeyNone, kKeyNone, // OEM specific + /* 0xea */ kKeyNone, kKeyNone, // OEM specific + /* 0xeb */ kKeyNone, kKeyNone, // OEM specific + /* 0xec */ kKeyNone, kKeyNone, // OEM specific + /* 0xed */ kKeyNone, kKeyNone, // OEM specific + /* 0xee */ kKeyNone, kKeyNone, // OEM specific + /* 0xef */ kKeyNone, kKeyNone, // OEM specific + /* 0xf0 */ kKeyNone, kKeyNone, // OEM specific + /* 0xf1 */ kKeyNone, kKeyNone, // OEM specific + /* 0xf2 */ kKeyNone, kKeyNone, // OEM specific + /* 0xf3 */ kKeyNone, kKeyNone, // OEM specific + /* 0xf4 */ kKeyNone, kKeyNone, // OEM specific + /* 0xf5 */ kKeyNone, kKeyNone, // OEM specific + /* 0xf6 */ kKeyNone, kKeyNone, // VK_ATTN + /* 0xf7 */ kKeyNone, kKeyNone, // VK_CRSEL + /* 0xf8 */ kKeyNone, kKeyNone, // VK_EXSEL + /* 0xf9 */ kKeyNone, kKeyNone, // VK_EREOF + /* 0xfa */ kKeyNone, kKeyNone, // VK_PLAY + /* 0xfb */ kKeyNone, kKeyNone, // VK_ZOOM + /* 0xfc */ kKeyNone, kKeyNone, // reserved + /* 0xfd */ kKeyNone, kKeyNone, // VK_PA1 + /* 0xfe */ kKeyNone, kKeyNone, // VK_OEM_CLEAR + /* 0xff */ kKeyNone, kKeyNone // reserved +}; + +// map special KeyID keys to virtual key codes. if the key is an +// extended key then the entry is the virtual key code | 0x100. +// unmapped keys have a 0 entry. +const KeyButton CMSWindowsKeyMapper::s_mapE000[] = +{ + /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ 0, 0, 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, + /* 0xa4 */ 0, 0, VK_BROWSER_BACK|0x100, VK_BROWSER_FORWARD|0x100, + /* 0xa8 */ VK_BROWSER_REFRESH|0x100, VK_BROWSER_STOP|0x100, + /* 0xaa */ VK_BROWSER_SEARCH|0x100, VK_BROWSER_FAVORITES|0x100, + /* 0xac */ VK_BROWSER_HOME|0x100, VK_VOLUME_MUTE|0x100, + /* 0xae */ VK_VOLUME_DOWN|0x100, VK_VOLUME_UP|0x100, + /* 0xb0 */ VK_MEDIA_NEXT_TRACK|0x100, VK_MEDIA_PREV_TRACK|0x100, + /* 0xb2 */ VK_MEDIA_STOP|0x100, VK_MEDIA_PLAY_PAUSE|0x100, + /* 0xb4 */ VK_LAUNCH_MAIL|0x100, VK_LAUNCH_MEDIA_SELECT|0x100, + /* 0xb6 */ VK_LAUNCH_APP1|0x100, VK_LAUNCH_APP2|0x100, + /* 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 +}; +const KeyButton CMSWindowsKeyMapper::s_mapEE00[] = +{ + /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x20 */ VK_TAB, 0, 0, 0, 0, 0, 0, 0, + /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ 0, 0, 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 +}; +const KeyButton CMSWindowsKeyMapper::s_mapEF00[] = +{ + /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x08 */ VK_BACK, VK_TAB, 0, VK_CLEAR, 0, VK_RETURN, 0, 0, + /* 0x10 */ 0, 0, 0, VK_PAUSE, VK_SCROLL, 0/*sys-req*/, 0, 0, + /* 0x18 */ 0, 0, 0, VK_ESCAPE, 0, 0, 0, 0, + /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x50 */ VK_HOME|0x100, VK_LEFT|0x100, VK_UP|0x100, VK_RIGHT|0x100, + /* 0x54 */ VK_DOWN|0x100, VK_PRIOR|0x100, VK_NEXT|0x100, VK_END|0x100, + /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x60 */ VK_SELECT|0x100, VK_SNAPSHOT|0x100, VK_EXECUTE|0x100, VK_INSERT|0x100, + /* 0x64 */ 0, 0, 0, VK_APPS|0x100, + /* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0, + /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, VK_NUMLOCK|0x100, + /* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0, + /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN|0x100, 0, 0, + /* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP, + /* 0x98 */ VK_RIGHT, VK_DOWN, VK_PRIOR, VK_NEXT, + /* 0x9c */ VK_END, 0, VK_INSERT, VK_DELETE, + /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa8 */ 0, 0, VK_MULTIPLY, VK_ADD, + /* 0xac */ VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE|0x100, + /* 0xb0 */ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, + /* 0xb4 */ VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, + /* 0xb8 */ VK_NUMPAD8, VK_NUMPAD9, 0, 0, 0, 0, VK_F1, VK_F2, + /* 0xc0 */ VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, + /* 0xc8 */ VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, + /* 0xd0 */ VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, 0, 0, + /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, + /* 0xe4 */ VK_RCONTROL|0x100, VK_CAPITAL, 0, 0, + /* 0xe8 */ 0, VK_LMENU, VK_RMENU|0x100, VK_LWIN|0x100, + /* 0xec */ VK_RWIN|0x100, 0, 0, 0, + /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE|0x100 +}; + +CMSWindowsKeyMapper::CMSWindowsKeyMapper() +{ + // do nothing +} + +CMSWindowsKeyMapper::~CMSWindowsKeyMapper() +{ + // do nothing +} + +void +CMSWindowsKeyMapper::update(IKeyState* keyState) +{ + static const size_t numModifiers = sizeof(s_modifiers) / + sizeof(s_modifiers[0]); + + // clear shadow state + memset(m_keys, 0, sizeof(m_keys)); + + // add modifiers + if (keyState != NULL) { + for (size_t i = 0; i < numModifiers; ++i) { + IKeyState::KeyButtons keys; + for (size_t j = 0; j < CModifierKeys::s_maxKeys; ++j) { + if (s_modifiers[i].m_keys[j] != 0) { + keys.push_back(s_modifiers[i].m_keys[j]); + } + } + keyState->addModifier(s_modifiers[i].m_mask, keys); + } + } + + // save current state of modifiers + for (size_t i = 0; i < numModifiers; ++i) { + for (size_t j = 0; j < CModifierKeys::s_maxKeys; ++j) { + if (s_modifiers[i].m_keys[j] != 0) { + SHORT s = GetKeyState(s_modifiers[i].m_keys[j]); + m_keys[s_modifiers[i].m_keys[j]] = static_cast(s); + if (keyState != NULL) { + if ((s & 0x01) != 0) { + keyState->setToggled(s_modifiers[i].m_mask); + } + if ((s & 0x80) != 0) { + keyState->setKeyDown(s_modifiers[i].m_keys[j]); + } + } + } + } + } +} + +void +CMSWindowsKeyMapper::updateKey(KeyButton key, bool pressed) +{ + if (pressed) { + switch (key) { + case 0: + case VK_LBUTTON: + case VK_MBUTTON: + case VK_RBUTTON: + // ignore bogus key + break; + + case VK_LSHIFT: + case VK_RSHIFT: + case VK_SHIFT: + m_keys[key] |= 0x80; + m_keys[VK_SHIFT] |= 0x80; + break; + + case VK_LCONTROL: + case VK_RCONTROL: + case VK_CONTROL: + m_keys[key] |= 0x80; + m_keys[VK_CONTROL] |= 0x80; + break; + + case VK_LMENU: + case VK_RMENU: + case VK_MENU: + m_keys[key] |= 0x80; + m_keys[VK_MENU] |= 0x80; + break; + + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // toggle keys + m_keys[key] |= 0x80; + break; + + default: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: + m_keys[key] |= 0x80; + break; + } + + // special case: we detect ctrl+alt+del being pressed on some + // systems but we don't detect the release of those keys. so + // if ctrl, alt, and del are down then mark them up. + if ((m_keys[VK_CONTROL] & 0x80) != 0 && + (m_keys[VK_MENU] & 0x80) != 0 && + (m_keys[VK_DELETE] & 0x80) != 0) { + m_keys[VK_LCONTROL] &= ~0x80; + m_keys[VK_RCONTROL] &= ~0x80; + m_keys[VK_CONTROL] &= ~0x80; + m_keys[VK_LMENU] &= ~0x80; + m_keys[VK_RMENU] &= ~0x80; + m_keys[VK_MENU] &= ~0x80; + m_keys[VK_DELETE] &= ~0x80; + } + } + else { + switch (key) { + case 0: + case VK_LBUTTON: + case VK_MBUTTON: + case VK_RBUTTON: + // ignore bogus key + break; + + case VK_LSHIFT: + case VK_RSHIFT: + case VK_SHIFT: + m_keys[key] &= ~0x80; + if (((m_keys[VK_LSHIFT] | m_keys[VK_RSHIFT]) & 0x80) == 0) { + m_keys[VK_SHIFT] &= ~0x80; + } + break; + + case VK_LCONTROL: + case VK_RCONTROL: + case VK_CONTROL: + m_keys[key] &= ~0x80; + if (((m_keys[VK_LCONTROL] | m_keys[VK_RCONTROL]) & 0x80) == 0) { + m_keys[VK_CONTROL] &= ~0x80; + } + break; + + case VK_LMENU: + case VK_RMENU: + case VK_MENU: + m_keys[key] &= ~0x80; + if (((m_keys[VK_LMENU] | m_keys[VK_RMENU]) & 0x80) == 0) { + m_keys[VK_MENU] &= ~0x80; + } + break; + + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // toggle keys + m_keys[key] &= ~0x80; + m_keys[key] ^= 0x01; + break; + + default: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: + m_keys[key] &= ~0x80; + break; + } + } +} + +KeyButton +CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, + const IKeyState& keyState, KeyID id, + KeyModifierMask, bool isAutoRepeat) const +{ + KeyButton virtualKey = 0; + + // check for special keys + if ((id & 0xfffff000u) == 0xe000u) { + if ((id & 0xff00u) == 0xe000u) { + virtualKey = s_mapE000[id & 0xffu]; + } + else if ((id & 0xff00) == 0xee00) { + virtualKey = s_mapEE00[id & 0xffu]; + } + else if ((id & 0xff00) == 0xef00) { + virtualKey = s_mapEF00[id & 0xffu]; + } + if (virtualKey == 0) { + LOG((CLOG_DEBUG2 "unknown special key")); + return virtualKey; + } + } + +/* XXX + // special handling of VK_SNAPSHOT + if ((virtualKey & 0xff) == VK_SNAPSHOT) { + // ignore key repeats on print screen + if (!isAutoRepeat) { + // get event flags + DWORD flags = 0; + if (isExtendedKey(virtualKey)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (action != kPress) { + flags |= KEYEVENTF_KEYUP; + } + + // active window or fullscreen? + BYTE scan = 0; + if ((mask & KeyModifierAlt) == 0) { + scan = 1; + } + + // send event + keybd_event(static_cast(virtualKey & 0xff), scan, flags, 0); + } + return 0; + } +*/ + + // handle other special keys + if (virtualKey != 0) { + // compute required modifiers + KeyModifierMask requiredMask = 0; + KeyModifierMask outMask = 0; + + // strip out extended key flag + UINT virtualKey2 = (virtualKey & 0xffu); + + // check numeric keypad. note that virtual keys do not distinguish + // between the keypad and non-keypad movement keys. however, the + // virtual keys do distinguish between keypad numbers and operators + // (e.g. add, multiply) and their main keyboard counterparts. + // therefore, we can ignore the num-lock state for movement virtual + // keys but not for numeric keys. + if (virtualKey2 >= VK_NUMPAD0 && virtualKey2 <= VK_DIVIDE) { + requiredMask |= KeyModifierNumLock; + if (!keyState.isModifierActive(KeyModifierNumLock)) { + LOG((CLOG_DEBUG2 "turn on num lock for keypad key")); + outMask |= KeyModifierNumLock; + } + } + + // check for left tab. that requires the shift key. + if (id == kKeyLeftTab) { + requiredMask |= KeyModifierShift; + outMask |= KeyModifierShift; + } + + // now generate the keystrokes and return the resulting modifier mask + LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d mask 0x%04x", id, virtualKey2, outMask)); + return mapToKeystrokes(keys, keyState, virtualKey, + outMask, requiredMask, isAutoRepeat); + } + + // determine the thread that'll receive this event + // FIXME -- we can't be sure we'll get the right thread here + HWND targetWindow = GetForegroundWindow(); + DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL); + + // figure out the code page for the target thread. i'm just + // guessing here. get the target thread's keyboard layout, + // extract the language id from that, and choose the code page + // based on that language. + HKL hkl = GetKeyboardLayout(targetThread); + LANGID langID = static_cast(LOWORD(hkl)); + UINT codePage = getCodePageFromLangID(langID); + LOG((CLOG_DEBUG2 "using code page %d and language id 0x%04x for thread 0x%08x", codePage, langID, targetThread)); + + // regular characters are complicated by dead keys. it may not be + // possible to generate a desired character directly. we may need + // to generate a dead key first then some other character. the + // app receiving the events will compose these two characters into + // a single precomposed character. + // + // as best as i can tell this is the simplest way to convert a + // character into its uncomposed version. along the way we'll + // discover if the key cannot be handled at all. we convert + // from wide char to multibyte, then from multibyte to wide char + // forcing conversion to composite characters, then from wide + // char back to multibyte without making precomposed characters. + // + // after the first conversion to multibyte we see if we can map + // the key. if so then we don't bother trying to decompose dead + // keys. + BOOL error; + char multiByte[2 * MB_LEN_MAX]; + wchar_t unicode[2]; + unicode[0] = static_cast(id & 0x0000ffffu); + int nChars = WideCharToMultiByte(codePage, + WC_COMPOSITECHECK | WC_DEFAULTCHAR, + unicode, 1, + multiByte, sizeof(multiByte), + NULL, &error); + if (nChars == 0 || error) { + LOG((CLOG_DEBUG2 "KeyID 0x%08x not in code page", id)); + return 0; + } + virtualKey = mapCharacter(keys, keyState, multiByte[0], hkl, isAutoRepeat); + if (virtualKey != 0) { + LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); + if ((MapVirtualKey(virtualKey, 2) & 0x80000000u) != 0) { + // it looks like this character is a dead key but + // MapVirtualKey() will claim it's a dead key even if it's + // not (though i don't think it ever claims it's not when + // it is). we need a backup test to ensure that this is + // really a dead key. we could use ToAscii() for this but + // that keeps state and it's a hassle to restore that state. + // OemKeyScan() appears to do the trick. if the character + // cannot be generated with a single keystroke then it + // returns 0xffffffff. + if (OemKeyScan(multiByte[0]) != 0xffffffffu) { + // character mapped to a dead key but we want the + // character for real so send a space key afterwards. + LOG((CLOG_DEBUG2 "character mapped to dead key")); + IKeyState::Keystroke keystroke; + keystroke.m_key = VK_SPACE; + keystroke.m_press = true; + keystroke.m_repeat = false; + keys.push_back(keystroke); + keystroke.m_press = false; + keys.push_back(keystroke); + + // ignore the release of this key since we already + // handled it. + virtualKey = 0; + } + } + return virtualKey; + } + nChars = MultiByteToWideChar(codePage, + MB_COMPOSITE | MB_ERR_INVALID_CHARS, + multiByte, nChars, + unicode, 2); + if (nChars == 0) { + LOG((CLOG_DEBUG2 "KeyID 0x%08x mb->wc mapping failed", id)); + return 0; + } + nChars = WideCharToMultiByte(codePage, + 0, + unicode, nChars, + multiByte, sizeof(multiByte), + NULL, &error); + if (nChars == 0 || error) { + LOG((CLOG_DEBUG2 "KeyID 0x%08x wc->mb mapping failed", id)); + return 0; + } + + // we expect one or two characters in multiByte. if there are two + // then the *second* is a dead key. process the dead key if there. + // FIXME -- we assume each character is one byte here + if (nChars > 2) { + LOG((CLOG_DEBUG2 "multibyte characters not supported for character 0x%04x", id)); + return 0; + } + if (nChars == 2) { + LOG((CLOG_DEBUG2 "KeyID 0x%08x needs dead key %u", id, (unsigned char)multiByte[1])); + mapCharacter(keys, keyState, multiByte[1], hkl, isAutoRepeat); + } + + // process character + LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); + virtualKey = mapCharacter(keys, keyState, multiByte[0], hkl, isAutoRepeat); + + return virtualKey; +} + +KeyID +CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info, + KeyModifierMask* maskOut, bool* altgr) const +{ + // note: known microsoft bugs + // Q72583 -- MapVirtualKey() maps keypad keys incorrectly + // 95,98: num pad vk code -> invalid scan code + // 95,98,NT4: num pad scan code -> bad vk code except + // SEPARATOR, MULTIPLY, SUBTRACT, ADD + + // get the scan code and the extended keyboard flag + UINT scanCode = static_cast((info & 0x00ff0000u) >> 16); + int extended = ((info & 0x01000000) == 0) ? 0 : 1; + LOG((CLOG_DEBUG1 "key vk=%d info=0x%08x ext=%d scan=%d", vkCode, info, extended, scanCode)); + + // handle some keys via table lookup + char c = 0; + KeyID id = s_virtualKey[vkCode][extended]; +LOG((CLOG_NOTE "code=%d, info=0x%08x -> id=%d", vkCode, info, id)); + if (id == kKeyNone) { + // not in table + + // save the control state then clear it. ToAscii() maps ctrl+letter + // to the corresponding control code and ctrl+backspace to delete. + // we don't want that translation so we clear the control modifier + // state. however, if we want to simulate AltGr (which is ctrl+alt) + // then we must not clear it. + BYTE keys[256]; + memcpy(keys, m_keys, sizeof(keys)); + BYTE control = keys[VK_CONTROL]; + BYTE menu = keys[VK_MENU]; + 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_CONTROL] = 0x80; + keys[VK_LMENU] = 0x80; + keys[VK_MENU] = 0x80; + } + + // convert to ascii + WORD ascii; + int result = ToAscii(vkCode, scanCode, keys, &ascii, + ((menu & 0x80) == 0) ? 0 : 1); + + // if result is less than zero then it was a dead key. leave it + // there. + if (result < 0) { + id = kKeyMultiKey; + } + + // if result is 1 then the key was succesfully converted + else if (result == 1) { + c = static_cast(ascii & 0xff); + if (ascii >= 0x80) { + // 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(ascii & 0x00ff); + } + } + else { + id = static_cast(ascii & 0x00ff); + } + } + + // if result is 2 then a previous dead key could not be composed. + else if (result == 2) { + // if the two characters are the same and this is a key release + // then this event is the dead key being released. we put the + // dead key back in that case, otherwise we discard both key + // events because we can't compose the character. alternatively + // we could generate key events for both keys. + if (((ascii & 0xff00) >> 8) != (ascii & 0x00ff) || + (info & 0x80000000) == 0) { + // cannot compose key + return kKeyNone; + } + + // get the scan code of the dead key and the shift state + // required to generate it. + vkCode = VkKeyScan(static_cast(ascii & 0x00ff)); + + // set shift state required to generate key + BYTE keys[256]; + memset(keys, 0, sizeof(keys)); + if (vkCode & 0x0100) { + keys[VK_SHIFT] = 0x80; + } + if (vkCode & 0x0200) { + keys[VK_CONTROL] = 0x80; + } + if (vkCode & 0x0400) { + keys[VK_MENU] = 0x80; + } + + // strip shift state off of virtual key code + vkCode &= 0x00ff; + + // get the scan code for the key + scanCode = MapVirtualKey(vkCode, 0); + + // put it back + ToAscii(vkCode, scanCode, keys, &ascii, 0); + id = kKeyMultiKey; + } + } + + // set mask + bool needAltGr = false; + if (id != kKeyNone && id != kKeyMultiKey && c != 0) { + // note if key requires AltGr. VkKeyScan() can have a problem + // with some characters. there are two problems in particular. + // first, typing a dead key then pressing space will cause + // VkKeyScan() to return 0xffff. second, certain characters + // may map to multiple virtual keys and we might get the wrong + // one. if that happens then we might not get the right + // modifier mask. AltGr+9 on the french keyboard layout (^) + // has this problem. in the first case, we'll assume AltGr is + // required (only because it solves the problems we've seen + // so far). in the second, we'll use whatever the keyboard + // state says. + WORD virtualKeyAndModifierState = VkKeyScan(c); + if (virtualKeyAndModifierState == 0xffff) { + // there is no mapping. assume AltGr. + LOG((CLOG_DEBUG1 "no VkKeyScan() mapping")); + needAltGr = true; + } + else if (LOBYTE(virtualKeyAndModifierState) != vkCode) { + // we didn't get the key that was actually pressed + LOG((CLOG_DEBUG1 "VkKeyScan() mismatch")); + if ((m_keys[VK_CONTROL] & 0x80) != 0 && + (m_keys[VK_MENU] & 0x80) != 0) { + needAltGr = true; + } + } + else { + BYTE modifierState = HIBYTE(virtualKeyAndModifierState); + if ((modifierState & 6) == 6) { + // key requires ctrl and alt == AltGr + needAltGr = true; + } + } + } + if (altgr != NULL) { + *altgr = needAltGr; + } + + // map modifier key + // map modifier key + KeyModifierMask mask = 0; + if (((m_keys[VK_LSHIFT] | + m_keys[VK_RSHIFT] | + m_keys[VK_SHIFT]) & 0x80) != 0) { + mask |= KeyModifierShift; + } + if (needAltGr) { + mask |= KeyModifierModeSwitch; + } + else { + if (((m_keys[VK_LCONTROL] | + m_keys[VK_RCONTROL] | + m_keys[VK_CONTROL]) & 0x80) != 0) { + mask |= KeyModifierControl; + } + if (((m_keys[VK_LMENU] | + m_keys[VK_RMENU] | + m_keys[VK_MENU]) & 0x80) != 0) { + mask |= KeyModifierAlt; + } + } + if (((m_keys[VK_LWIN] | + m_keys[VK_RWIN]) & 0x80) != 0) { + mask |= KeyModifierSuper; + } + if ((m_keys[VK_CAPITAL] & 0x01) != 0) { + mask |= KeyModifierCapsLock; + } + if ((m_keys[VK_NUMLOCK] & 0x01) != 0) { + mask |= KeyModifierNumLock; + } + if ((m_keys[VK_SCROLL] & 0x01) != 0) { + mask |= KeyModifierScrollLock; + } + if (maskOut != NULL) { + *maskOut = mask; + } + + return id; +} + +bool +CMSWindowsKeyMapper::isPressed(KeyButton key) const +{ + return ((m_keys[key & 0xffu] & 0x80) != 0); +} + +UINT +CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const +{ + // try mapping given virtual key + UINT code = MapVirtualKey((*virtualKey) & 0xffu, 0); + if (code != 0) { + return code; + } + + // no dice. if the virtual key distinguishes between left/right + // then try the one that doesn't distinguish sides. windows (or + // keyboard drivers) are inconsistent in their treatment of these + // virtual keys. the following behaviors have been observed: + // + // win2k (gateway desktop): + // MapVirtualKey(vk, 0): + // VK_SHIFT == VK_LSHIFT != VK_RSHIFT + // VK_CONTROL == VK_LCONTROL == VK_RCONTROL + // VK_MENU == VK_LMENU == VK_RMENU + // MapVirtualKey(sc, 3): + // VK_LSHIFT and VK_RSHIFT mapped independently + // VK_LCONTROL is mapped but not VK_RCONTROL + // VK_LMENU is mapped but not VK_RMENU + // + // win me (sony vaio laptop): + // MapVirtualKey(vk, 0): + // VK_SHIFT mapped; VK_LSHIFT, VK_RSHIFT not mapped + // VK_CONTROL mapped; VK_LCONTROL, VK_RCONTROL not mapped + // VK_MENU mapped; VK_LMENU, VK_RMENU not mapped + // MapVirtualKey(sc, 3): + // all scan codes unmapped (function apparently unimplemented) + switch ((*virtualKey) & 0xffu) { + case VK_LSHIFT: + case VK_RSHIFT: + *virtualKey = VK_SHIFT; + return MapVirtualKey(VK_SHIFT, 0); + + case VK_LCONTROL: + case VK_RCONTROL: + *virtualKey = VK_CONTROL; + return MapVirtualKey(VK_CONTROL, 0); + + case VK_LMENU: + case VK_RMENU: + *virtualKey = VK_MENU; + return MapVirtualKey(VK_MENU, 0); + + default: + return 0; + } +} + +bool +CMSWindowsKeyMapper::isExtendedKey(KeyButton virtualKey) const +{ + // see if we've already encoded the extended flag + if ((virtualKey & 0x100u) != 0) { + return true; + } + + // check known virtual keys + switch (virtualKey & 0xffu) { + case VK_NUMLOCK: + case VK_RCONTROL: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: + return true; + + default: + return false; + } +} + +const char* +CMSWindowsKeyMapper::getKeyName(KeyButton key) const +{ + return s_vkToName[key & 0xffu]; +} + +UINT +CMSWindowsKeyMapper::getCodePageFromLangID(LANGID langid) const +{ + // construct a locale id from the language id + LCID lcid = MAKELCID(langid, SORT_DEFAULT); + + // get the ANSI code page for this locale + char data[6]; + if (GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE, data, 6) == 0) { + // can't get code page + LOG((CLOG_DEBUG1 "can't find code page for langid 0x%04x", langid)); + return CP_ACP; + } + + // convert stringified code page into a number + UINT codePage = static_cast(atoi(data)); + if (codePage == 0) { + // parse failed + LOG((CLOG_DEBUG1 "can't parse code page %s for langid 0x%04x", data, langid)); + return CP_ACP; + } + + return codePage; +} + +KeyButton +CMSWindowsKeyMapper::mapCharacter(IKeyState::Keystrokes& keys, + const IKeyState& keyState, char c, HKL hkl, + bool isAutoRepeat) const +{ + // translate the character into its virtual key and its required + // modifier state. + SHORT virtualKeyAndModifierState = VkKeyScanEx(c, hkl); + + // get virtual key + KeyButton virtualKey = LOBYTE(virtualKeyAndModifierState); + if (virtualKey == 0xffu) { + LOG((CLOG_DEBUG2 "cannot map character %d", static_cast(c))); + return 0; + } + + // get the required modifier state + BYTE modifierState = HIBYTE(virtualKeyAndModifierState); + + // see what modifiers are needed. we only check for shift and + // AltGr. we must always match the desired shift state but only + // the desired AltGr state if AltGr is required. AltGr is actually + // ctrl + alt so we can't require that ctrl and alt not be pressed + // otherwise users couldn't do, say, ctrl+z.f + KeyModifierMask desiredMask = 0; + KeyModifierMask requiredMask = KeyModifierShift; + if ((modifierState & 0x01u) == 1) { + desiredMask |= KeyModifierShift; + } + if ((modifierState & 0x06u) == 6) { + // add ctrl and alt, which must be matched + desiredMask |= KeyModifierControl | KeyModifierAlt; + requiredMask |= KeyModifierControl | KeyModifierAlt; + } + + // handle combination of caps-lock and shift. if caps-lock is + // off locally then use shift as necessary. if caps-lock is on + // locally then it reverses the meaning of shift for keys that + // are subject to case conversion. + if (keyState.isModifierActive(KeyModifierCapsLock)) { + // there doesn't seem to be a simple way to test if a + // character respects the caps lock key. for normal + // characters it's easy enough but CharLower() and + // CharUpper() don't map dead keys even though they + // do respect caps lock for some unfathomable reason. + // first check the easy way. if that doesn't work + // then see if it's a dead key. + unsigned char uc = static_cast(c); + if (CharLower((LPTSTR)uc) != CharUpper((LPTSTR)uc) || + (MapVirtualKey(virtualKey, 2) & 0x80000000lu) != 0) { + LOG((CLOG_DEBUG2 "flip shift")); + desiredMask ^= KeyModifierShift; + } + } + + // now generate the keystrokes. ignore the resulting modifier + // mask since it can't have changed (because we don't call this + // method for modifier keys). + LOG((CLOG_DEBUG2 "character %d to virtual key %d mask 0x%08x", (unsigned char)c, virtualKey, desiredMask)); + mapToKeystrokes(keys, keyState, virtualKey, + desiredMask, requiredMask, isAutoRepeat); + + return virtualKey; +} + +KeyButton +CMSWindowsKeyMapper::mapToKeystrokes(IKeyState::Keystrokes& keys, + const IKeyState& keyState, KeyButton virtualKey, + KeyModifierMask desiredMask, KeyModifierMask requiredMask, + bool isAutoRepeat) const +{ + // adjust the modifiers to match the desired modifiers + IKeyState::Keystrokes undo; + if (!adjustModifiers(keys, undo, keyState, desiredMask, requiredMask)) { + LOG((CLOG_DEBUG2 "failed to adjust modifiers")); + return 0; + } + + // add the key event + IKeyState::Keystroke keystroke; + keystroke.m_key = virtualKey; + keystroke.m_press = true; + keystroke.m_repeat = isAutoRepeat; + keys.push_back(keystroke); + + // put undo keystrokes at end of keystrokes in reverse order + while (!undo.empty()) { + keys.push_back(undo.back()); + undo.pop_back(); + } + + return virtualKey; +} + +bool +CMSWindowsKeyMapper::adjustModifiers(IKeyState::Keystrokes& keys, + IKeyState::Keystrokes& undo, + const IKeyState& keyState, + KeyModifierMask desiredMask, + KeyModifierMask requiredMask) const +{ + // for each modifier in requiredMask make sure the current state + // of that modifier matches the bit in desiredMask. + for (KeyModifierMask mask = 1u; requiredMask != 0; mask <<= 1) { + if ((mask & requiredMask) != 0) { + bool active = ((desiredMask & mask) != 0); + if (!keyState.mapModifier(keys, undo, mask, active)) { + return false; + } + requiredMask ^= mask; + } + } + + return true; +} diff --git a/lib/platform/CMSWindowsKeyMapper.h b/lib/platform/CMSWindowsKeyMapper.h new file mode 100644 index 00000000..ae26dcdb --- /dev/null +++ b/lib/platform/CMSWindowsKeyMapper.h @@ -0,0 +1,142 @@ +/* + * 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 CMSWINDOWSKEYMAPPER_H +#define CMSWINDOWSKEYMAPPER_H + +#include "IKeyState.h" +#define WIN32_LEAN_AND_MEAN +#include + +//! Microsoft Windows key mapper +/*! +This class maps KeyIDs to keystrokes. +*/ +class CMSWindowsKeyMapper { +public: + CMSWindowsKeyMapper(); + ~CMSWindowsKeyMapper(); + + //! @name manipulators + //@{ + + //! Update key mapper + /*! + Updates the key mapper's internal tables according to the + current keyboard mapping and updates \c keyState. + */ + void update(IKeyState* keyState); + + //! Update shadow key state + /*! + Updates the shadow keyboard state. + */ + void updateKey(KeyButton key, bool pressed); + + //@} + //! @name accessors + //@{ + + //! Map key press/repeat to keystrokes + /*! + Converts a press/repeat of key \c id with the modifiers as given + in \c desiredMask into the keystrokes necessary to synthesize + that key event. Returns the platform specific code of the key + being pressed, or 0 if the key cannot be mapped or \c isAutoRepeat + is true and the key does not auto-repeat. + */ + KeyButton mapKey(IKeyState::Keystrokes&, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + //! Map key event to a key + /*! + Converts a key event into a KeyID and the shadow modifier state + to a modifier mask. If \c altgr is non-NULL it's set to true if + the key requires AltGr and false otherwise. + */ + KeyID mapKeyFromEvent(WPARAM vkCode, LPARAM info, + KeyModifierMask* maskOut, bool* altgr) const; + + //! Test shadow key state + /*! + Returns true iff the shadow state indicates the key is pressed. + */ + bool isPressed(KeyButton key) const; + + //! Map key to a scan code + /*! + Returns the scan code for \c key and possibly adjusts \c key. + */ + UINT keyToScanCode(KeyButton* key) const; + + //! Check for extended key + /*! + Returns true iff \c key is an extended key + */ + bool isExtendedKey(KeyButton key) const; + + //! Get name of key + /*! + Return a string describing the given key. + */ + const char* getKeyName(KeyButton) const; + + //@} + +private: + // convert a language ID to a code page + UINT getCodePageFromLangID(LANGID langid) const; + + // map character \c c given keyboard layout \c hkl to the keystrokes + // to generate it. + KeyButton mapCharacter(IKeyState::Keystrokes& keys, + const IKeyState& keyState, char c, HKL hkl, + bool isAutoRepeat) const; + + // map \c virtualKey to the keystrokes to generate it, along with + // keystrokes to update and restore the modifier state. + KeyButton mapToKeystrokes(IKeyState::Keystrokes& keys, + const IKeyState& keyState, KeyButton virtualKey, + KeyModifierMask desiredMask, + KeyModifierMask requiredMask, + bool isAutoRepeat) const; + + // get keystrokes to get modifiers in a desired state + bool adjustModifiers(IKeyState::Keystrokes& keys, + IKeyState::Keystrokes& undo, + const IKeyState& keyState, + KeyModifierMask desiredMask, + KeyModifierMask requiredMask) const; + +private: + class CModifierKeys { + public: + enum { s_maxKeys = 2 }; + KeyModifierMask m_mask; + KeyButton m_keys[s_maxKeys]; + }; + + BYTE m_keys[256]; + + static const CModifierKeys s_modifiers[]; + static const char* s_vkToName[]; + static const KeyID s_virtualKey[][2]; + static const KeyButton s_mapE000[]; + static const KeyButton s_mapEE00[]; + static const KeyButton s_mapEF00[]; +}; + +#endif diff --git a/lib/platform/CMSWindowsPrimaryScreen.cpp b/lib/platform/CMSWindowsPrimaryScreen.cpp deleted file mode 100644 index d68612dc..00000000 --- a/lib/platform/CMSWindowsPrimaryScreen.cpp +++ /dev/null @@ -1,1816 +0,0 @@ -/* - * 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 "CMSWindowsPrimaryScreen.h" -#include "CMSWindowsScreen.h" -#include "IPrimaryScreenReceiver.h" -#include "XScreen.h" -#include "CLog.h" -#include "CArch.h" -#include "CArchMiscWindows.h" -#include - -// 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 0x0100 -#define MOUSEEVENTF_XUP 0x0200 -#define XBUTTON1 0x0001 -#define XBUTTON2 0x0002 -#endif - -// -// map virtual key id to a name -// - -static const char* g_buttonToName[] = { - "button 0", - "Left Button", - "Middle Button", - "Right Button", - "X Button 1", - "X Button 2" -}; -static const char* g_vkToName[] = { - "vk 0x00", - "Left Button", - "Right Button", - "VK_CANCEL", - "Middle Button", - "vk 0x05", - "vk 0x06", - "vk 0x07", - "VK_BACK", - "VK_TAB", - "vk 0x0a", - "vk 0x0b", - "VK_CLEAR", - "VK_RETURN", - "vk 0x0e", - "vk 0x0f", - "VK_SHIFT", - "VK_CONTROL", - "VK_MENU", - "VK_PAUSE", - "VK_CAPITAL", - "VK_KANA", - "vk 0x16", - "VK_JUNJA", - "VK_FINAL", - "VK_KANJI", - "vk 0x1a", - "VK_ESCAPE", - "VK_CONVERT", - "VK_NONCONVERT", - "VK_ACCEPT", - "VK_MODECHANGE", - "VK_SPACE", - "VK_PRIOR", - "VK_NEXT", - "VK_END", - "VK_HOME", - "VK_LEFT", - "VK_UP", - "VK_RIGHT", - "VK_DOWN", - "VK_SELECT", - "VK_PRINT", - "VK_EXECUTE", - "VK_SNAPSHOT", - "VK_INSERT", - "VK_DELETE", - "VK_HELP", - "VK_0", - "VK_1", - "VK_2", - "VK_3", - "VK_4", - "VK_5", - "VK_6", - "VK_7", - "VK_8", - "VK_9", - "vk 0x3a", - "vk 0x3b", - "vk 0x3c", - "vk 0x3d", - "vk 0x3e", - "vk 0x3f", - "vk 0x40", - "VK_A", - "VK_B", - "VK_C", - "VK_D", - "VK_E", - "VK_F", - "VK_G", - "VK_H", - "VK_I", - "VK_J", - "VK_K", - "VK_L", - "VK_M", - "VK_N", - "VK_O", - "VK_P", - "VK_Q", - "VK_R", - "VK_S", - "VK_T", - "VK_U", - "VK_V", - "VK_W", - "VK_X", - "VK_Y", - "VK_Z", - "VK_LWIN", - "VK_RWIN", - "VK_APPS", - "vk 0x5e", - "vk 0x5f", - "VK_NUMPAD0", - "VK_NUMPAD1", - "VK_NUMPAD2", - "VK_NUMPAD3", - "VK_NUMPAD4", - "VK_NUMPAD5", - "VK_NUMPAD6", - "VK_NUMPAD7", - "VK_NUMPAD8", - "VK_NUMPAD9", - "VK_MULTIPLY", - "VK_ADD", - "VK_SEPARATOR", - "VK_SUBTRACT", - "VK_DECIMAL", - "VK_DIVIDE", - "VK_F1", - "VK_F2", - "VK_F3", - "VK_F4", - "VK_F5", - "VK_F6", - "VK_F7", - "VK_F8", - "VK_F9", - "VK_F10", - "VK_F11", - "VK_F12", - "VK_F13", - "VK_F14", - "VK_F15", - "VK_F16", - "VK_F17", - "VK_F18", - "VK_F19", - "VK_F20", - "VK_F21", - "VK_F22", - "VK_F23", - "VK_F24", - "vk 0x88", - "vk 0x89", - "vk 0x8a", - "vk 0x8b", - "vk 0x8c", - "vk 0x8d", - "vk 0x8e", - "vk 0x8f", - "VK_NUMLOCK", - "VK_SCROLL", - "vk 0x92", - "vk 0x93", - "vk 0x94", - "vk 0x95", - "vk 0x96", - "vk 0x97", - "vk 0x98", - "vk 0x99", - "vk 0x9a", - "vk 0x9b", - "vk 0x9c", - "vk 0x9d", - "vk 0x9e", - "vk 0x9f", - "VK_LSHIFT", - "VK_RSHIFT", - "VK_LCONTROL", - "VK_RCONTROL", - "VK_LMENU", - "VK_RMENU", - "VK_BROWSER_BACK", - "VK_BROWSER_FORWARD", - "VK_BROWSER_REFRESH", - "VK_BROWSER_STOP", - "VK_BROWSER_SEARCH", - "VK_BROWSER_FAVORITES", - "VK_BROWSER_HOME", - "VK_VOLUME_MUTE", - "VK_VOLUME_DOWN", - "VK_VOLUME_UP", - "VK_MEDIA_NEXT_TRACK", - "VK_MEDIA_PREV_TRACK", - "VK_MEDIA_STOP", - "VK_MEDIA_PLAY_PAUSE", - "VK_LAUNCH_MAIL", - "VK_LAUNCH_MEDIA_SELECT", - "VK_LAUNCH_APP1", - "VK_LAUNCH_APP2", - "vk 0xb8", - "vk 0xb9", - "vk 0xba", - "vk 0xbb", - "vk 0xbc", - "vk 0xbd", - "vk 0xbe", - "vk 0xbf", - "vk 0xc0", - "vk 0xc1", - "vk 0xc2", - "vk 0xc3", - "vk 0xc4", - "vk 0xc5", - "vk 0xc6", - "vk 0xc7", - "vk 0xc8", - "vk 0xc9", - "vk 0xca", - "vk 0xcb", - "vk 0xcc", - "vk 0xcd", - "vk 0xce", - "vk 0xcf", - "vk 0xd0", - "vk 0xd1", - "vk 0xd2", - "vk 0xd3", - "vk 0xd4", - "vk 0xd5", - "vk 0xd6", - "vk 0xd7", - "vk 0xd8", - "vk 0xd9", - "vk 0xda", - "vk 0xdb", - "vk 0xdc", - "vk 0xdd", - "vk 0xde", - "vk 0xdf", - "vk 0xe0", - "vk 0xe1", - "vk 0xe2", - "vk 0xe3", - "vk 0xe4", - "VK_PROCESSKEY", - "vk 0xe6", - "vk 0xe7", - "vk 0xe8", - "vk 0xe9", - "vk 0xea", - "vk 0xeb", - "vk 0xec", - "vk 0xed", - "vk 0xee", - "vk 0xef", - "vk 0xf0", - "vk 0xf1", - "vk 0xf2", - "vk 0xf3", - "vk 0xf4", - "vk 0xf5", - "VK_ATTN", - "VK_CRSEL", - "VK_EXSEL", - "VK_EREOF", - "VK_PLAY", - "VK_ZOOM", - "VK_NONAME", - "VK_PA1", - "VK_OEM_CLEAR", - "vk 0xff" -}; - -// -// CMSWindowsPrimaryScreen -// - -CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen( - IScreenReceiver* receiver, - IPrimaryScreenReceiver* primaryReceiver) : - CPrimaryScreen(receiver), - m_receiver(primaryReceiver), - m_is95Family(CArchMiscWindows::isWindows95Family()), - m_threadID(0), - m_mark(0), - m_markReceived(0), - m_lowLevel(false), - m_cursorThread(0) -{ - assert(m_receiver != NULL); - - // load the hook library - m_hookLibrary = LoadLibrary("synrgyhk"); - if (m_hookLibrary == NULL) { - LOG((CLOG_ERR "Failed to load hook library; synrgyhk.dll is missing")); - throw XScreenOpenFailure(); - } - m_setSides = (SetSidesFunc)GetProcAddress(m_hookLibrary, "setSides"); - m_setZone = (SetZoneFunc)GetProcAddress(m_hookLibrary, "setZone"); - m_setRelay = (SetRelayFunc)GetProcAddress(m_hookLibrary, "setRelay"); - m_install = (InstallFunc)GetProcAddress(m_hookLibrary, "install"); - m_uninstall = (UninstallFunc)GetProcAddress(m_hookLibrary, "uninstall"); - m_init = (InitFunc)GetProcAddress(m_hookLibrary, "init"); - m_cleanup = (CleanupFunc)GetProcAddress(m_hookLibrary, "cleanup"); - if (m_setSides == NULL || - m_setZone == NULL || - m_setRelay == NULL || - m_install == NULL || - m_uninstall == NULL || - m_init == NULL || - m_cleanup == NULL) { - LOG((CLOG_ERR "Invalid hook library; use a newer synrgyhk.dll")); - FreeLibrary(m_hookLibrary); - throw XScreenOpenFailure(); - } - - // create screen - m_screen = new CMSWindowsScreen(receiver, this); -} - -CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen() -{ - assert(m_hookLibrary != NULL); - - delete m_screen; - FreeLibrary(m_hookLibrary); -} - -void -CMSWindowsPrimaryScreen::reconfigure(UInt32 activeSides) -{ - m_setSides(activeSides); -} - -void -CMSWindowsPrimaryScreen::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_x = x; - m_y = y; -} - -void -CMSWindowsPrimaryScreen::resetOptions() -{ - // no options -} - -void -CMSWindowsPrimaryScreen::setOptions(const COptionsList& /*options*/) -{ - // no options -} - -UInt32 -CMSWindowsPrimaryScreen::addOneShotTimer(double timeout) -{ - return m_screen->addOneShotTimer(timeout); -} - -KeyModifierMask -CMSWindowsPrimaryScreen::getToggleMask() const -{ - KeyModifierMask mask = 0; - - // get key state from our shadow state - if ((m_keys[VK_CAPITAL] & 0x01) != 0) - mask |= KeyModifierCapsLock; - if ((m_keys[VK_NUMLOCK] & 0x01) != 0) - mask |= KeyModifierNumLock; - if ((m_keys[VK_SCROLL] & 0x01) != 0) - mask |= KeyModifierScrollLock; - - return mask; -} - -bool -CMSWindowsPrimaryScreen::isLockedToScreen() const -{ - // use shadow keyboard state in m_keys and m_buttons - for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { - if ((m_buttons[i] & 0x80) != 0) { - LOG((CLOG_DEBUG "locked by \"%s\"", g_buttonToName[i])); - return true; - } - } - for (UInt32 i = 0; i < sizeof(m_keys) / sizeof(m_keys[0]); ++i) { - if ((m_keys[i] & 0x80) != 0) { - LOG((CLOG_DEBUG "locked by \"%s\"", g_vkToName[i])); - return true; - } - } - - // not locked - return false; -} - -IScreen* -CMSWindowsPrimaryScreen::getScreen() const -{ - return m_screen; -} - -void -CMSWindowsPrimaryScreen::onScreensaver(bool activated) -{ - m_receiver->onScreensaver(activated); -} - -bool -CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) -{ - assert(event != NULL); - - const MSG* msg = &event->m_msg; - - // check if windows key is up but we think it's down. if so then - // synthesize a key release for it. we have to do this because - // if the user presses and releases a windows key without pressing - // any other key when its down then windows will eat the key - // release. if we don't detect that an synthesize the release - // then the user will be locked to the screen and the client won't - // take the usual windows key release action (which on windows is - // to show the start menu). - // - // we can use GetKeyState() to check the state of the windows keys - // because, event though the key release is not reported to us, - // the event is processed and the keyboard state updated by the - // system. since the key could go up at any time we'll check the - // state on every event. only check on windows 95 family since - // NT family reports the key release as usual. obviously we skip - // this if the event is for the windows key itself. - if (m_is95Family) { - if ((m_keys[VK_LWIN] & 0x80) != 0 && - (GetAsyncKeyState(VK_LWIN) & 0x8000) == 0 && - !(msg->message == SYNERGY_MSG_KEY && msg->wParam == VK_LWIN)) { - // compute appropriate parameters for fake event - WPARAM wParam = VK_LWIN; - LPARAM lParam = 0xc1000000; - lParam |= (0x00ff0000 & (MapVirtualKey(wParam, 0) << 24)); - - // process as if it were a key up - bool altgr; - KeyModifierMask mask; - KeyButton button = static_cast( - (lParam & 0x00ff0000u) >> 16); - const KeyID key = mapKey(wParam, lParam, &mask, &altgr); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); - m_receiver->onKeyUp(key, mask, button); - updateKey(wParam, false); - } - if ((m_keys[VK_RWIN] & 0x80) != 0 && - (GetAsyncKeyState(VK_RWIN) & 0x8000) == 0 && - !(msg->message == SYNERGY_MSG_KEY && msg->wParam == VK_RWIN)) { - // compute appropriate parameters for fake event - WPARAM wParam = VK_RWIN; - LPARAM lParam = 0xc1000000; - lParam |= (0x00ff0000 & (MapVirtualKey(wParam, 0) << 24)); - - // process as if it were a key up - bool altgr; - KeyModifierMask mask; - KeyButton button = static_cast( - (lParam & 0x00ff0000u) >> 16); - const KeyID key = mapKey(wParam, lParam, &mask, &altgr); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); - m_receiver->onKeyUp(key, mask, button); - updateKey(wParam, false); - } - } - - // handle event - switch (msg->message) { - case SYNERGY_MSG_MARK: - m_markReceived = msg->wParam; - return true; - - case SYNERGY_MSG_KEY: - // ignore message if posted prior to last mark change - if (!ignore()) { - WPARAM wParam = msg->wParam; - LPARAM lParam = msg->lParam; - - // check for ctrl+alt+del emulation - if ((wParam == VK_PAUSE || wParam == VK_CANCEL) && - (m_keys[VK_CONTROL] & 0x80) != 0 && - (m_keys[VK_MENU] & 0x80) != 0) { - LOG((CLOG_DEBUG "emulate ctrl+alt+del")); - wParam = VK_DELETE; - lParam &= 0xffff0000; - lParam |= 0x00000001; - } - - // process key normally - bool altgr; - KeyModifierMask mask; - const KeyID key = mapKey(wParam, lParam, &mask, &altgr); - KeyButton button = static_cast( - (lParam & 0x00ff0000u) >> 16); - if (key != kKeyNone && key != kKeyMultiKey) { - if ((lParam & 0x80000000) == 0) { - // key press - - // if AltGr required for this key then make sure - // the ctrl and alt keys are *not* down on the - // client. windows simulates AltGr with ctrl and - // alt for some inexplicable reason and clients - // will get confused if they see mode switch and - // ctrl and alt. we'll also need to put ctrl and - // alt back the way there were after we simulate - // the key. - bool ctrlL = ((m_keys[VK_LCONTROL] & 0x80) != 0); - bool ctrlR = ((m_keys[VK_RCONTROL] & 0x80) != 0); - bool altL = ((m_keys[VK_LMENU] & 0x80) != 0); - bool altR = ((m_keys[VK_RMENU] & 0x80) != 0); - if (altgr) { - KeyID key; - KeyButton button; - KeyModifierMask mask2 = (mask & - ~(KeyModifierControl | - KeyModifierAlt | - KeyModifierModeSwitch)); - if (ctrlL) { - key = kKeyControl_L; - button = mapKeyToScanCode(VK_LCONTROL, VK_CONTROL); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyUp(key, mask2, button); - } - if (ctrlR) { - key = kKeyControl_R; - button = mapKeyToScanCode(VK_RCONTROL, VK_CONTROL); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyUp(key, mask2, button); - } - if (altL) { - key = kKeyAlt_L; - button = mapKeyToScanCode(VK_LMENU, VK_MENU); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyUp(key, mask2, button); - } - if (altR) { - key = kKeyAlt_R; - button = mapKeyToScanCode(VK_RMENU, VK_MENU); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyUp(key, mask2, button); - } - } - - // send key - const bool wasDown = ((lParam & 0x40000000) != 0); - SInt32 repeat = (SInt32)(lParam & 0xffff); - if (!wasDown) { - LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); - m_receiver->onKeyDown(key, mask, button); - if (repeat > 0) { - --repeat; - } - } - if (repeat >= 1) { - LOG((CLOG_DEBUG1 "event: key repeat key=%d mask=0x%04x count=%d button=0x%04x", key, mask, repeat, button)); - m_receiver->onKeyRepeat(key, mask, repeat, button); - } - - // restore ctrl and alt state - if (altgr) { - KeyID key; - KeyButton button; - KeyModifierMask mask2 = (mask & - ~(KeyModifierControl | - KeyModifierAlt | - KeyModifierModeSwitch)); - if (ctrlL) { - key = kKeyControl_L; - button = mapKeyToScanCode(VK_LCONTROL, VK_CONTROL); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyDown(key, mask2, button); - mask2 |= KeyModifierControl; - } - if (ctrlR) { - key = kKeyControl_R; - button = mapKeyToScanCode(VK_RCONTROL, VK_CONTROL); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyDown(key, mask2, button); - mask2 |= KeyModifierControl; - } - if (altL) { - key = kKeyAlt_L; - button = mapKeyToScanCode(VK_LMENU, VK_MENU); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyDown(key, mask2, button); - mask2 |= KeyModifierAlt; - } - if (altR) { - key = kKeyAlt_R; - button = mapKeyToScanCode(VK_RMENU, VK_MENU); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - m_receiver->onKeyDown(key, mask2, button); - mask2 |= KeyModifierAlt; - } - } - } - else { - // key release. 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 && !isModifier(msg->wParam) && - (m_keys[msg->wParam] & 0x80) == 0) { - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); - m_receiver->onKeyDown(key, mask, button); - updateKey(msg->wParam, true); - } - - // do key up - LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); - m_receiver->onKeyUp(key, mask, button); - } - } - else { - LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", msg->wParam, msg->lParam)); - } - } - - // keep our shadow key state up to date - updateKey(msg->wParam, ((msg->lParam & 0x80000000) == 0)); - - return true; - - case SYNERGY_MSG_MOUSE_BUTTON: { - // get which button - bool pressed = false; - const ButtonID button = mapButton(msg->wParam, msg->lParam); - - // ignore message if posted prior to last mark change - if (!ignore()) { - switch (msg->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_NCLBUTTONDOWN: - case WM_NCMBUTTONDOWN: - case WM_NCRBUTTONDOWN: - case WM_NCXBUTTONDOWN: - case WM_NCLBUTTONDBLCLK: - case WM_NCMBUTTONDBLCLK: - case WM_NCRBUTTONDBLCLK: - case WM_NCXBUTTONDBLCLK: - LOG((CLOG_DEBUG1 "event: button press button=%d", button)); - if (button != kButtonNone) { - m_receiver->onMouseDown(button); - } - pressed = true; - break; - - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - case WM_XBUTTONUP: - case WM_NCLBUTTONUP: - case WM_NCMBUTTONUP: - case WM_NCRBUTTONUP: - case WM_NCXBUTTONUP: - LOG((CLOG_DEBUG1 "event: button release button=%d", button)); - if (button != kButtonNone) { - m_receiver->onMouseUp(button); - } - pressed = false; - break; - } - } - - // keep our shadow key state up to date - if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { - if (pressed) { - m_buttons[button] |= 0x80; - } - else { - m_buttons[button] &= ~0x80; - } - } - - return true; - } - - case SYNERGY_MSG_MOUSE_WHEEL: - // ignore message if posted prior to last mark change - if (!ignore()) { - LOG((CLOG_DEBUG1 "event: button wheel delta=%d %d", msg->wParam, msg->lParam)); - m_receiver->onMouseWheel(msg->wParam); - } - return true; - - case SYNERGY_MSG_PRE_WARP: - { - // save position to compute delta of next motion - m_x = static_cast(msg->wParam); - m_y = static_cast(msg->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 SYNERGY_MSG_MOUSE_MOVE: - // ignore message if posted prior to last mark change - if (!ignore()) { - // compute motion delta (relative to the last known - // mouse position) - SInt32 x = static_cast(msg->wParam) - m_x; - SInt32 y = static_cast(msg->lParam) - m_y; - - // save position to compute delta of next motion - m_x = static_cast(msg->wParam); - m_y = static_cast(msg->lParam); - - if (!isActive()) { - // motion on primary screen - if (x != 0 || y != 0) { - m_receiver->onMouseMovePrimary(m_x, m_y); - } - } - else { - // motion on secondary screen. warp mouse back to - // center. - if (x != 0 || y != 0) { - // 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; - SInt32 x0, y0, w0, h0; - m_screen->getShape(x0, y0, w0, h0); - if (-x + bogusZoneSize > m_xCenter - x0 || - x + bogusZoneSize > x0 + w0 - m_xCenter || - -y + bogusZoneSize > m_yCenter - y0 || - y + bogusZoneSize > y0 + h0 - m_yCenter) { - LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); - } - else { - // send motion - m_receiver->onMouseMoveSecondary(x, y); - } - } - } - } - return true; - } - - return false; -} - -bool -CMSWindowsPrimaryScreen::onEvent(CEvent* event) -{ - assert(event != NULL); - - const MSG& msg = event->m_msg; - switch (msg.message) { - case WM_DISPLAYCHANGE: - // recompute center pixel of primary screen - m_screen->getCursorCenter(m_xCenter, m_yCenter); - - // warp mouse to center if active - if (isActive()) { - warpCursorToCenter(); - } - - // tell hook about resize if not active - else { - SInt32 x, y, w, h; - m_screen->getShape(x, y, w, h); - m_setZone(x, y, w, h, getJumpZoneSize()); - } - return true; - } - - return false; -} - -void -CMSWindowsPrimaryScreen::onOneShotTimerExpired(UInt32 id) -{ - m_receiver->onOneShotTimerExpired(id); -} - -SInt32 -CMSWindowsPrimaryScreen::getJumpZoneSize() const -{ - return 1; -} - -void -CMSWindowsPrimaryScreen::postCreateWindow(HWND) -{ - // install hooks - switch (m_install()) { - case kHOOK_FAILED: - // FIXME -- can't install hook so we won't work; report error - m_lowLevel = false; - break; - - case kHOOK_OKAY: - m_lowLevel = false; - break; - - case kHOOK_OKAY_LL: - m_lowLevel = true; - break; - } - - if (!isActive()) { - // watch jump zones - m_setRelay(false); - - // all messages prior to now are invalid - nextMark(); - } -} - -void -CMSWindowsPrimaryScreen::preDestroyWindow(HWND) -{ - // uninstall hooks - m_uninstall(); -} - -void -CMSWindowsPrimaryScreen::onAccessibleDesktop() -{ - // get the current keyboard state - updateKeys(); -} - -void -CMSWindowsPrimaryScreen::onPreMainLoop() -{ - // must call mainLoop() from same thread as open() - assert(m_threadID == GetCurrentThreadId()); -} - -void -CMSWindowsPrimaryScreen::onPreOpen() -{ - // initialize hook library - m_threadID = GetCurrentThreadId(); - if (m_init(m_threadID) == 0) { - LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?")); - throw XScreenOpenFailure(); - } -} - -void -CMSWindowsPrimaryScreen::onPostOpen() -{ - // get cursor info - m_screen->getCursorPos(m_x, m_y); - m_screen->getCursorCenter(m_xCenter, m_yCenter); - - // set jump zones - SInt32 x, y, w, h; - m_screen->getShape(x, y, w, h); - m_setZone(x, y, w, h, getJumpZoneSize()); - - // initialize marks - m_mark = 0; - m_markReceived = 0; - nextMark(); -} - -void -CMSWindowsPrimaryScreen::onPostClose() -{ - m_cleanup(); - m_threadID = 0; -} - -void -CMSWindowsPrimaryScreen::onPreEnter() -{ - // show cursor if we hid it - if (m_cursorThread != 0) { - if (m_threadID != m_cursorThread) { - AttachThreadInput(m_threadID, m_cursorThread, TRUE); - } - ShowCursor(TRUE); - if (m_threadID != m_cursorThread) { - AttachThreadInput(m_threadID, m_cursorThread, FALSE); - } - m_cursorThread = 0; - } - - // enable ctrl+alt+del, alt+tab, etc - if (m_is95Family) { - DWORD dummy = 0; - SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, &dummy, 0); - } - - // watch jump zones - m_setRelay(false); -} - -void -CMSWindowsPrimaryScreen::onPostEnter() -{ - // all messages prior to now are invalid - nextMark(); -} - -void -CMSWindowsPrimaryScreen::onPreLeave() -{ - // all messages prior to now are invalid - nextMark(); -} - -void -CMSWindowsPrimaryScreen::onPostLeave(bool success) -{ - if (success) { - // relay all mouse and keyboard events - m_setRelay(true); - - // disable ctrl+alt+del, alt+tab, etc - if (m_is95Family) { - DWORD dummy = 0; - SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, &dummy, 0); - } - - // hide the cursor if using low level hooks - if (m_lowLevel) { - HWND hwnd = GetForegroundWindow(); - m_cursorThread = GetWindowThreadProcessId(hwnd, NULL); - if (m_threadID != m_cursorThread) { - AttachThreadInput(m_threadID, m_cursorThread, TRUE); - } - ShowCursor(FALSE); - if (m_threadID != m_cursorThread) { - AttachThreadInput(m_threadID, m_cursorThread, FALSE); - } - } - } -} - -void -CMSWindowsPrimaryScreen::createWindow() -{ - // open the desktop and the window - m_window = m_screen->openDesktop(); - if (m_window == NULL) { - throw XScreenOpenFailure(); - } - - // we don't ever want our window to activate - EnableWindow(m_window, FALSE); -} - -void -CMSWindowsPrimaryScreen::destroyWindow() -{ - // close the desktop and the window - m_screen->closeDesktop(); -} - -bool -CMSWindowsPrimaryScreen::showWindow() -{ - // we don't need a window to capture input but we need a window - // to hide the cursor when using low-level hooks. do not try to - // take the activation; we want the currently active window to - // stay active. - if (m_lowLevel) { - SetWindowPos(m_window, HWND_TOPMOST, m_xCenter, m_yCenter, 1, 1, - SWP_NOACTIVATE); - ShowWindow(m_window, SW_SHOWNA); - } - return true; -} - -void -CMSWindowsPrimaryScreen::hideWindow() -{ - // hide our window - if (m_lowLevel) { - ShowWindow(m_window, SW_HIDE); - } -} - -void -CMSWindowsPrimaryScreen::warpCursorToCenter() -{ - warpCursor(m_xCenter, m_yCenter); -} - -void -CMSWindowsPrimaryScreen::warpCursorNoFlush(SInt32 x, SInt32 y) -{ - // send an event that we can recognize before the mouse warp - PostThreadMessage(m_threadID, 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(m_threadID, SYNERGY_MSG_POST_WARP, 0, 0); -} - -void -CMSWindowsPrimaryScreen::nextMark() -{ - // next mark - ++m_mark; - - // mark point in message queue where the mark was changed - PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0); -} - -bool -CMSWindowsPrimaryScreen::ignore() const -{ - return (m_mark != m_markReceived); -} - -// map virtual keys to synergy key enumeration. use extended keyboard -// bit to distinguish some keys. -static const KeyID g_virtualKey[][2] = -{ - /* 0x00 */ kKeyNone, kKeyNone, // reserved - /* 0x01 */ kKeyNone, kKeyNone, // VK_LBUTTON - /* 0x02 */ kKeyNone, kKeyNone, // VK_RBUTTON - /* 0x03 */ kKeyNone, kKeyBreak, // VK_CANCEL - /* 0x04 */ kKeyNone, kKeyNone, // VK_MBUTTON - /* 0x05 */ kKeyNone, kKeyNone, // undefined - /* 0x06 */ kKeyNone, kKeyNone, // undefined - /* 0x07 */ kKeyNone, kKeyNone, // undefined - /* 0x08 */ kKeyBackSpace, kKeyNone, // VK_BACK - /* 0x09 */ kKeyTab, kKeyNone, // VK_TAB - /* 0x0a */ kKeyNone, kKeyNone, // undefined - /* 0x0b */ kKeyNone, kKeyNone, // undefined - /* 0x0c */ kKeyClear, kKeyClear, // VK_CLEAR - /* 0x0d */ kKeyReturn, kKeyKP_Enter, // VK_RETURN - /* 0x0e */ kKeyNone, kKeyNone, // undefined - /* 0x0f */ kKeyNone, kKeyNone, // undefined - /* 0x10 */ kKeyShift_L, kKeyShift_R, // VK_SHIFT - /* 0x11 */ kKeyControl_L, kKeyControl_R, // VK_CONTROL - /* 0x12 */ kKeyAlt_L, kKeyAlt_R, // VK_MENU - /* 0x13 */ kKeyPause, kKeyNone, // VK_PAUSE - /* 0x14 */ kKeyCapsLock, kKeyNone, // VK_CAPITAL - /* 0x15 */ kKeyNone, kKeyNone, // VK_KANA - /* 0x16 */ kKeyNone, kKeyNone, // VK_HANGUL - /* 0x17 */ kKeyNone, kKeyNone, // VK_JUNJA - /* 0x18 */ kKeyNone, kKeyNone, // VK_FINAL - /* 0x19 */ kKeyNone, kKeyNone, // VK_KANJI - /* 0x1a */ kKeyNone, kKeyNone, // undefined - /* 0x1b */ kKeyEscape, kKeyNone, // VK_ESCAPE - /* 0x1c */ kKeyNone, kKeyNone, // VK_CONVERT - /* 0x1d */ kKeyNone, kKeyNone, // VK_NONCONVERT - /* 0x1e */ kKeyNone, kKeyNone, // VK_ACCEPT - /* 0x1f */ kKeyNone, kKeyNone, // VK_MODECHANGE - /* 0x20 */ kKeyNone, kKeyNone, // VK_SPACE - /* 0x21 */ kKeyKP_PageUp, kKeyPageUp, // VK_PRIOR - /* 0x22 */ kKeyKP_PageDown, kKeyPageDown, // VK_NEXT - /* 0x23 */ kKeyKP_End, kKeyEnd, // VK_END - /* 0x24 */ kKeyKP_Home, kKeyHome, // VK_HOME - /* 0x25 */ kKeyKP_Left, kKeyLeft, // VK_LEFT - /* 0x26 */ kKeyKP_Up, kKeyUp, // VK_UP - /* 0x27 */ kKeyKP_Right, kKeyRight, // VK_RIGHT - /* 0x28 */ kKeyKP_Down, kKeyDown, // VK_DOWN - /* 0x29 */ kKeySelect, kKeySelect, // VK_SELECT - /* 0x2a */ kKeyNone, kKeyNone, // VK_PRINT - /* 0x2b */ kKeyExecute, kKeyExecute, // VK_EXECUTE - /* 0x2c */ kKeyPrint, kKeyPrint, // VK_SNAPSHOT - /* 0x2d */ kKeyKP_Insert, kKeyInsert, // VK_INSERT - /* 0x2e */ kKeyKP_Delete, kKeyDelete, // VK_DELETE - /* 0x2f */ kKeyHelp, kKeyHelp, // VK_HELP - /* 0x30 */ kKeyNone, kKeyNone, // VK_0 - /* 0x31 */ kKeyNone, kKeyNone, // VK_1 - /* 0x32 */ kKeyNone, kKeyNone, // VK_2 - /* 0x33 */ kKeyNone, kKeyNone, // VK_3 - /* 0x34 */ kKeyNone, kKeyNone, // VK_4 - /* 0x35 */ kKeyNone, kKeyNone, // VK_5 - /* 0x36 */ kKeyNone, kKeyNone, // VK_6 - /* 0x37 */ kKeyNone, kKeyNone, // VK_7 - /* 0x38 */ kKeyNone, kKeyNone, // VK_8 - /* 0x39 */ kKeyNone, kKeyNone, // VK_9 - /* 0x3a */ kKeyNone, kKeyNone, // undefined - /* 0x3b */ kKeyNone, kKeyNone, // undefined - /* 0x3c */ kKeyNone, kKeyNone, // undefined - /* 0x3d */ kKeyNone, kKeyNone, // undefined - /* 0x3e */ kKeyNone, kKeyNone, // undefined - /* 0x3f */ kKeyNone, kKeyNone, // undefined - /* 0x40 */ kKeyNone, kKeyNone, // undefined - /* 0x41 */ kKeyNone, kKeyNone, // VK_A - /* 0x42 */ kKeyNone, kKeyNone, // VK_B - /* 0x43 */ kKeyNone, kKeyNone, // VK_C - /* 0x44 */ kKeyNone, kKeyNone, // VK_D - /* 0x45 */ kKeyNone, kKeyNone, // VK_E - /* 0x46 */ kKeyNone, kKeyNone, // VK_F - /* 0x47 */ kKeyNone, kKeyNone, // VK_G - /* 0x48 */ kKeyNone, kKeyNone, // VK_H - /* 0x49 */ kKeyNone, kKeyNone, // VK_I - /* 0x4a */ kKeyNone, kKeyNone, // VK_J - /* 0x4b */ kKeyNone, kKeyNone, // VK_K - /* 0x4c */ kKeyNone, kKeyNone, // VK_L - /* 0x4d */ kKeyNone, kKeyNone, // VK_M - /* 0x4e */ kKeyNone, kKeyNone, // VK_N - /* 0x4f */ kKeyNone, kKeyNone, // VK_O - /* 0x50 */ kKeyNone, kKeyNone, // VK_P - /* 0x51 */ kKeyNone, kKeyNone, // VK_Q - /* 0x52 */ kKeyNone, kKeyNone, // VK_R - /* 0x53 */ kKeyNone, kKeyNone, // VK_S - /* 0x54 */ kKeyNone, kKeyNone, // VK_T - /* 0x55 */ kKeyNone, kKeyNone, // VK_U - /* 0x56 */ kKeyNone, kKeyNone, // VK_V - /* 0x57 */ kKeyNone, kKeyNone, // VK_W - /* 0x58 */ kKeyNone, kKeyNone, // VK_X - /* 0x59 */ kKeyNone, kKeyNone, // VK_Y - /* 0x5a */ kKeyNone, kKeyNone, // VK_Z - /* 0x5b */ kKeyNone, kKeySuper_L, // VK_LWIN - /* 0x5c */ kKeyNone, kKeySuper_R, // VK_RWIN - /* 0x5d */ kKeyMenu, kKeyMenu, // VK_APPS - /* 0x5e */ kKeyNone, kKeyNone, // undefined - /* 0x5f */ kKeyNone, kKeyNone, // undefined - /* 0x60 */ kKeyKP_0, kKeyNone, // VK_NUMPAD0 - /* 0x61 */ kKeyKP_1, kKeyNone, // VK_NUMPAD1 - /* 0x62 */ kKeyKP_2, kKeyNone, // VK_NUMPAD2 - /* 0x63 */ kKeyKP_3, kKeyNone, // VK_NUMPAD3 - /* 0x64 */ kKeyKP_4, kKeyNone, // VK_NUMPAD4 - /* 0x65 */ kKeyKP_5, kKeyNone, // VK_NUMPAD5 - /* 0x66 */ kKeyKP_6, kKeyNone, // VK_NUMPAD6 - /* 0x67 */ kKeyKP_7, kKeyNone, // VK_NUMPAD7 - /* 0x68 */ kKeyKP_8, kKeyNone, // VK_NUMPAD8 - /* 0x69 */ kKeyKP_9, kKeyNone, // VK_NUMPAD9 - /* 0x6a */ kKeyKP_Multiply, kKeyNone, // VK_MULTIPLY - /* 0x6b */ kKeyKP_Add, kKeyNone, // VK_ADD - /* 0x6c */ kKeyKP_Separator,kKeyKP_Separator,// VK_SEPARATOR - /* 0x6d */ kKeyKP_Subtract, kKeyNone, // VK_SUBTRACT - /* 0x6e */ kKeyKP_Decimal, kKeyNone, // VK_DECIMAL - /* 0x6f */ kKeyNone, kKeyKP_Divide, // VK_DIVIDE - /* 0x70 */ kKeyF1, kKeyNone, // VK_F1 - /* 0x71 */ kKeyF2, kKeyNone, // VK_F2 - /* 0x72 */ kKeyF3, kKeyNone, // VK_F3 - /* 0x73 */ kKeyF4, kKeyNone, // VK_F4 - /* 0x74 */ kKeyF5, kKeyNone, // VK_F5 - /* 0x75 */ kKeyF6, kKeyNone, // VK_F6 - /* 0x76 */ kKeyF7, kKeyNone, // VK_F7 - /* 0x77 */ kKeyF8, kKeyNone, // VK_F8 - /* 0x78 */ kKeyF9, kKeyNone, // VK_F9 - /* 0x79 */ kKeyF10, kKeyNone, // VK_F10 - /* 0x7a */ kKeyF11, kKeyNone, // VK_F11 - /* 0x7b */ kKeyF12, kKeyNone, // VK_F12 - /* 0x7c */ kKeyF13, kKeyF13, // VK_F13 - /* 0x7d */ kKeyF14, kKeyF14, // VK_F14 - /* 0x7e */ kKeyF15, kKeyF15, // VK_F15 - /* 0x7f */ kKeyF16, kKeyF16, // VK_F16 - /* 0x80 */ kKeyF17, kKeyF17, // VK_F17 - /* 0x81 */ kKeyF18, kKeyF18, // VK_F18 - /* 0x82 */ kKeyF19, kKeyF19, // VK_F19 - /* 0x83 */ kKeyF20, kKeyF20, // VK_F20 - /* 0x84 */ kKeyF21, kKeyF21, // VK_F21 - /* 0x85 */ kKeyF22, kKeyF22, // VK_F22 - /* 0x86 */ kKeyF23, kKeyF23, // VK_F23 - /* 0x87 */ kKeyF24, kKeyF24, // VK_F24 - /* 0x88 */ kKeyNone, kKeyNone, // unassigned - /* 0x89 */ kKeyNone, kKeyNone, // unassigned - /* 0x8a */ kKeyNone, kKeyNone, // unassigned - /* 0x8b */ kKeyNone, kKeyNone, // unassigned - /* 0x8c */ kKeyNone, kKeyNone, // unassigned - /* 0x8d */ kKeyNone, kKeyNone, // unassigned - /* 0x8e */ kKeyNone, kKeyNone, // unassigned - /* 0x8f */ kKeyNone, kKeyNone, // unassigned - /* 0x90 */ kKeyNumLock, kKeyNumLock, // VK_NUMLOCK - /* 0x91 */ kKeyScrollLock, kKeyNone, // VK_SCROLL - /* 0x92 */ kKeyNone, kKeyNone, // unassigned - /* 0x93 */ kKeyNone, kKeyNone, // unassigned - /* 0x94 */ kKeyNone, kKeyNone, // unassigned - /* 0x95 */ kKeyNone, kKeyNone, // unassigned - /* 0x96 */ kKeyNone, kKeyNone, // unassigned - /* 0x97 */ kKeyNone, kKeyNone, // unassigned - /* 0x98 */ kKeyNone, kKeyNone, // unassigned - /* 0x99 */ kKeyNone, kKeyNone, // unassigned - /* 0x9a */ kKeyNone, kKeyNone, // unassigned - /* 0x9b */ kKeyNone, kKeyNone, // unassigned - /* 0x9c */ kKeyNone, kKeyNone, // unassigned - /* 0x9d */ kKeyNone, kKeyNone, // unassigned - /* 0x9e */ kKeyNone, kKeyNone, // unassigned - /* 0x9f */ kKeyNone, kKeyNone, // unassigned - /* 0xa0 */ kKeyShift_L, kKeyShift_L, // VK_LSHIFT - /* 0xa1 */ kKeyShift_R, kKeyShift_R, // VK_RSHIFT - /* 0xa2 */ kKeyControl_L, kKeyControl_L, // VK_LCONTROL - /* 0xa3 */ kKeyControl_R, kKeyControl_R, // VK_RCONTROL - /* 0xa4 */ kKeyAlt_L, kKeyAlt_L, // VK_LMENU - /* 0xa5 */ kKeyAlt_R, kKeyAlt_R, // VK_RMENU - /* 0xa6 */ kKeyNone, kKeyWWWBack, // VK_BROWSER_BACK - /* 0xa7 */ kKeyNone, kKeyWWWForward, // VK_BROWSER_FORWARD - /* 0xa8 */ kKeyNone, kKeyWWWRefresh, // VK_BROWSER_REFRESH - /* 0xa9 */ kKeyNone, kKeyWWWStop, // VK_BROWSER_STOP - /* 0xaa */ kKeyNone, kKeyWWWSearch, // VK_BROWSER_SEARCH - /* 0xab */ kKeyNone, kKeyWWWFavorites, // VK_BROWSER_FAVORITES - /* 0xac */ kKeyNone, kKeyWWWHome, // VK_BROWSER_HOME - /* 0xad */ kKeyNone, kKeyAudioMute, // VK_VOLUME_MUTE - /* 0xae */ kKeyNone, kKeyAudioDown, // VK_VOLUME_DOWN - /* 0xaf */ kKeyNone, kKeyAudioUp, // VK_VOLUME_UP - /* 0xb0 */ kKeyNone, kKeyAudioNext, // VK_MEDIA_NEXT_TRACK - /* 0xb1 */ kKeyNone, kKeyAudioPrev, // VK_MEDIA_PREV_TRACK - /* 0xb2 */ kKeyNone, kKeyAudioStop, // VK_MEDIA_STOP - /* 0xb3 */ kKeyNone, kKeyAudioPlay, // VK_MEDIA_PLAY_PAUSE - /* 0xb4 */ kKeyNone, kKeyAppMail, // VK_LAUNCH_MAIL - /* 0xb5 */ kKeyNone, kKeyAppMedia, // VK_LAUNCH_MEDIA_SELECT - /* 0xb6 */ kKeyNone, kKeyAppUser1, // VK_LAUNCH_APP1 - /* 0xb7 */ kKeyNone, kKeyAppUser2, // VK_LAUNCH_APP2 - /* 0xb8 */ kKeyNone, kKeyNone, // unassigned - /* 0xb9 */ kKeyNone, kKeyNone, // unassigned - /* 0xba */ kKeyNone, kKeyNone, // OEM specific - /* 0xbb */ kKeyNone, kKeyNone, // OEM specific - /* 0xbc */ kKeyNone, kKeyNone, // OEM specific - /* 0xbd */ kKeyNone, kKeyNone, // OEM specific - /* 0xbe */ kKeyNone, kKeyNone, // OEM specific - /* 0xbf */ kKeyNone, kKeyNone, // OEM specific - /* 0xc0 */ kKeyNone, kKeyNone, // OEM specific - /* 0xc1 */ kKeyNone, kKeyNone, // unassigned - /* 0xc2 */ kKeyNone, kKeyNone, // unassigned - /* 0xc3 */ kKeyNone, kKeyNone, // unassigned - /* 0xc4 */ kKeyNone, kKeyNone, // unassigned - /* 0xc5 */ kKeyNone, kKeyNone, // unassigned - /* 0xc6 */ kKeyNone, kKeyNone, // unassigned - /* 0xc7 */ kKeyNone, kKeyNone, // unassigned - /* 0xc8 */ kKeyNone, kKeyNone, // unassigned - /* 0xc9 */ kKeyNone, kKeyNone, // unassigned - /* 0xca */ kKeyNone, kKeyNone, // unassigned - /* 0xcb */ kKeyNone, kKeyNone, // unassigned - /* 0xcc */ kKeyNone, kKeyNone, // unassigned - /* 0xcd */ kKeyNone, kKeyNone, // unassigned - /* 0xce */ kKeyNone, kKeyNone, // unassigned - /* 0xcf */ kKeyNone, kKeyNone, // unassigned - /* 0xd0 */ kKeyNone, kKeyNone, // unassigned - /* 0xd1 */ kKeyNone, kKeyNone, // unassigned - /* 0xd2 */ kKeyNone, kKeyNone, // unassigned - /* 0xd3 */ kKeyNone, kKeyNone, // unassigned - /* 0xd4 */ kKeyNone, kKeyNone, // unassigned - /* 0xd5 */ kKeyNone, kKeyNone, // unassigned - /* 0xd6 */ kKeyNone, kKeyNone, // unassigned - /* 0xd7 */ kKeyNone, kKeyNone, // unassigned - /* 0xd8 */ kKeyNone, kKeyNone, // unassigned - /* 0xd9 */ kKeyNone, kKeyNone, // unassigned - /* 0xda */ kKeyNone, kKeyNone, // unassigned - /* 0xdb */ kKeyNone, kKeyNone, // OEM specific - /* 0xdc */ kKeyNone, kKeyNone, // OEM specific - /* 0xdd */ kKeyNone, kKeyNone, // OEM specific - /* 0xde */ kKeyNone, kKeyNone, // OEM specific - /* 0xdf */ kKeyNone, kKeyNone, // OEM specific - /* 0xe0 */ kKeyNone, kKeyNone, // OEM specific - /* 0xe1 */ kKeyNone, kKeyNone, // OEM specific - /* 0xe2 */ kKeyNone, kKeyNone, // OEM specific - /* 0xe3 */ kKeyNone, kKeyNone, // OEM specific - /* 0xe4 */ kKeyNone, kKeyNone, // OEM specific - /* 0xe5 */ kKeyNone, kKeyNone, // unassigned - /* 0xe6 */ kKeyNone, kKeyNone, // OEM specific - /* 0xe7 */ kKeyNone, kKeyNone, // unassigned - /* 0xe8 */ kKeyNone, kKeyNone, // unassigned - /* 0xe9 */ kKeyNone, kKeyNone, // OEM specific - /* 0xea */ kKeyNone, kKeyNone, // OEM specific - /* 0xeb */ kKeyNone, kKeyNone, // OEM specific - /* 0xec */ kKeyNone, kKeyNone, // OEM specific - /* 0xed */ kKeyNone, kKeyNone, // OEM specific - /* 0xee */ kKeyNone, kKeyNone, // OEM specific - /* 0xef */ kKeyNone, kKeyNone, // OEM specific - /* 0xf0 */ kKeyNone, kKeyNone, // OEM specific - /* 0xf1 */ kKeyNone, kKeyNone, // OEM specific - /* 0xf2 */ kKeyNone, kKeyNone, // OEM specific - /* 0xf3 */ kKeyNone, kKeyNone, // OEM specific - /* 0xf4 */ kKeyNone, kKeyNone, // OEM specific - /* 0xf5 */ kKeyNone, kKeyNone, // OEM specific - /* 0xf6 */ kKeyNone, kKeyNone, // VK_ATTN - /* 0xf7 */ kKeyNone, kKeyNone, // VK_CRSEL - /* 0xf8 */ kKeyNone, kKeyNone, // VK_EXSEL - /* 0xf9 */ kKeyNone, kKeyNone, // VK_EREOF - /* 0xfa */ kKeyNone, kKeyNone, // VK_PLAY - /* 0xfb */ kKeyNone, kKeyNone, // VK_ZOOM - /* 0xfc */ kKeyNone, kKeyNone, // reserved - /* 0xfd */ kKeyNone, kKeyNone, // VK_PA1 - /* 0xfe */ kKeyNone, kKeyNone, // VK_OEM_CLEAR - /* 0xff */ kKeyNone, kKeyNone // reserved -}; - -KeyID -CMSWindowsPrimaryScreen::mapKey( - WPARAM vkCode, - LPARAM info, - KeyModifierMask* maskOut, - bool* altgr) -{ - // note: known microsoft bugs - // Q72583 -- MapVirtualKey() maps keypad keys incorrectly - // 95,98: num pad vk code -> invalid scan code - // 95,98,NT4: num pad scan code -> bad vk code except - // SEPARATOR, MULTIPLY, SUBTRACT, ADD - - assert(maskOut != NULL); - assert(altgr != NULL); - - // get the scan code and the extended keyboard flag - UINT scanCode = static_cast((info & 0x00ff0000u) >> 16); - int extended = ((info & 0x01000000) == 0) ? 0 : 1; - LOG((CLOG_DEBUG1 "key vk=%d info=0x%08x ext=%d scan=%d", vkCode, info, extended, scanCode)); - - // handle some keys via table lookup - char c = 0; - KeyID id = g_virtualKey[vkCode][extended]; - if (id == kKeyNone) { - // not in table - - // save the control state then clear it. ToAscii() maps ctrl+letter - // to the corresponding control code and ctrl+backspace to delete. - // we don't want that translation so we clear the control modifier - // state. however, if we want to simulate AltGr (which is ctrl+alt) - // then we must not clear it. - BYTE lControl = m_keys[VK_LCONTROL]; - BYTE rControl = m_keys[VK_RCONTROL]; - BYTE control = m_keys[VK_CONTROL]; - BYTE lMenu = m_keys[VK_LMENU]; - BYTE menu = m_keys[VK_MENU]; - if ((control & 0x80) == 0 || (menu & 0x80) == 0) { - m_keys[VK_LCONTROL] = 0; - m_keys[VK_RCONTROL] = 0; - m_keys[VK_CONTROL] = 0; - } - else { - m_keys[VK_LCONTROL] = 0x80; - m_keys[VK_CONTROL] = 0x80; - m_keys[VK_LMENU] = 0x80; - m_keys[VK_MENU] = 0x80; - } - - // convert to ascii - WORD ascii; - int result = ToAscii(vkCode, scanCode, m_keys, &ascii, - ((menu & 0x80) == 0) ? 0 : 1); - - // restore control state - m_keys[VK_LCONTROL] = lControl; - m_keys[VK_RCONTROL] = rControl; - m_keys[VK_CONTROL] = control; - m_keys[VK_LMENU] = lMenu; - m_keys[VK_MENU] = menu; - - // if result is less than zero then it was a dead key. leave it - // there. - if (result < 0) { - id = kKeyMultiKey; - } - - // if result is 1 then the key was succesfully converted - else if (result == 1) { - c = static_cast(ascii & 0xff); - if (ascii >= 0x80) { - // 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(ascii & 0x00ff); - } - } - else { - id = static_cast(ascii & 0x00ff); - } - } - - // if result is 2 then a previous dead key could not be composed. - else if (result == 2) { - // if the two characters are the same and this is a key release - // then this event is the dead key being released. we put the - // dead key back in that case, otherwise we discard both key - // events because we can't compose the character. alternatively - // we could generate key events for both keys. - if (((ascii & 0xff00) >> 8) != (ascii & 0x00ff) || - (info & 0x80000000) == 0) { - // cannot compose key - return kKeyNone; - } - - // get the scan code of the dead key and the shift state - // required to generate it. - vkCode = VkKeyScan(static_cast(ascii & 0x00ff)); - - // set shift state required to generate key - BYTE keys[256]; - memset(keys, 0, sizeof(keys)); - if (vkCode & 0x0100) { - keys[VK_SHIFT] = 0x80; - } - if (vkCode & 0x0200) { - keys[VK_CONTROL] = 0x80; - } - if (vkCode & 0x0400) { - keys[VK_MENU] = 0x80; - } - - // strip shift state off of virtual key code - vkCode &= 0x00ff; - - // get the scan code for the key - scanCode = MapVirtualKey(vkCode, 0); - - // put it back - ToAscii(vkCode, scanCode, keys, &ascii, 0); - id = kKeyMultiKey; - } - } - - // set mask - *altgr = false; - if (id != kKeyNone && id != kKeyMultiKey && c != 0) { - // note if key requires AltGr. VkKeyScan() can have a problem - // with some characters. there are two problems in particular. - // first, typing a dead key then pressing space will cause - // VkKeyScan() to return 0xffff. second, certain characters - // may map to multiple virtual keys and we might get the wrong - // one. if that happens then we might not get the right - // modifier mask. AltGr+9 on the french keyboard layout (^) - // has this problem. in the first case, we'll assume AltGr is - // required (only because it solves the problems we've seen - // so far). in the second, we'll use whatever the keyboard - // state says. - WORD virtualKeyAndModifierState = VkKeyScan(c); - if (virtualKeyAndModifierState == 0xffff) { - // there is no mapping. assume AltGr. - LOG((CLOG_DEBUG1 "no VkKeyScan() mapping")); - *altgr = true; - } - else if (LOBYTE(virtualKeyAndModifierState) != vkCode) { - // we didn't get the key that was actually pressed - LOG((CLOG_DEBUG1 "VkKeyScan() mismatch")); - if ((m_keys[VK_CONTROL] & 0x80) != 0 && - (m_keys[VK_MENU] & 0x80) != 0) { - *altgr = true; - } - } - else { - BYTE modifierState = HIBYTE(virtualKeyAndModifierState); - if ((modifierState & 6) == 6) { - // key requires ctrl and alt == AltGr - *altgr = true; - } - } - } - - // map modifier key - KeyModifierMask mask = 0; - if (((m_keys[VK_LSHIFT] | - m_keys[VK_RSHIFT] | - m_keys[VK_SHIFT]) & 0x80) != 0) { - mask |= KeyModifierShift; - } - if (*altgr) { - mask |= KeyModifierModeSwitch; - } - else { - if (((m_keys[VK_LCONTROL] | - m_keys[VK_RCONTROL] | - m_keys[VK_CONTROL]) & 0x80) != 0) { - mask |= KeyModifierControl; - } - if (((m_keys[VK_LMENU] | - m_keys[VK_RMENU] | - m_keys[VK_MENU]) & 0x80) != 0) { - mask |= KeyModifierAlt; - } - } - if (((m_keys[VK_LWIN] | - m_keys[VK_RWIN]) & 0x80) != 0) { - mask |= KeyModifierSuper; - } - if ((m_keys[VK_CAPITAL] & 0x01) != 0) { - mask |= KeyModifierCapsLock; - } - if ((m_keys[VK_NUMLOCK] & 0x01) != 0) { - mask |= KeyModifierNumLock; - } - if ((m_keys[VK_SCROLL] & 0x01) != 0) { - mask |= KeyModifierScrollLock; - } - *maskOut = mask; - - return id; -} - -ButtonID -CMSWindowsPrimaryScreen::mapButton(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: - return kButtonExtra0 + 0; - - case XBUTTON2: - return kButtonExtra0 + 1; - } - return kButtonNone; - - default: - return kButtonNone; - } -} - -void -CMSWindowsPrimaryScreen::updateKeys() -{ - // not using GetKeyboardState() because that doesn't seem to give - // up-to-date results. i don't know why that is or why GetKeyState() - // should give different results. - - // clear key and button state - memset(m_keys, 0, sizeof(m_keys)); - memset(m_buttons, 0, sizeof(m_buttons)); - - // we only care about the modifier key states. other keys and the - // mouse buttons should be up. - // sometimes these seem to be out of date so, to avoid getting - // locked to a screen unexpectedly, just assume the non-toggle - // modifier keys are up. -/* - m_keys[VK_LSHIFT] = static_cast(GetKeyState(VK_LSHIFT) & 0x80); - m_keys[VK_RSHIFT] = static_cast(GetKeyState(VK_RSHIFT) & 0x80); - m_keys[VK_SHIFT] = static_cast(GetKeyState(VK_SHIFT) & 0x80); - m_keys[VK_LCONTROL] = static_cast(GetKeyState(VK_LCONTROL) & 0x80); - m_keys[VK_RCONTROL] = static_cast(GetKeyState(VK_RCONTROL) & 0x80); - m_keys[VK_CONTROL] = static_cast(GetKeyState(VK_CONTROL) & 0x80); - m_keys[VK_LMENU] = static_cast(GetKeyState(VK_LMENU) & 0x80); - m_keys[VK_RMENU] = static_cast(GetKeyState(VK_RMENU) & 0x80); - m_keys[VK_MENU] = static_cast(GetKeyState(VK_MENU) & 0x80); - m_keys[VK_LWIN] = static_cast(GetKeyState(VK_LWIN) & 0x80); - m_keys[VK_RWIN] = static_cast(GetKeyState(VK_RWIN) & 0x80); - m_keys[VK_APPS] = static_cast(GetKeyState(VK_APPS) & 0x80); -*/ - m_keys[VK_CAPITAL] = static_cast(GetKeyState(VK_CAPITAL)); - m_keys[VK_NUMLOCK] = static_cast(GetKeyState(VK_NUMLOCK)); - m_keys[VK_SCROLL] = static_cast(GetKeyState(VK_SCROLL)); -} - -void -CMSWindowsPrimaryScreen::updateKey(UINT vkCode, bool press) -{ - if (press) { - switch (vkCode) { - case 0: - case VK_LBUTTON: - case VK_MBUTTON: - case VK_RBUTTON: - // ignore bogus key - break; - - case VK_LSHIFT: - case VK_RSHIFT: - case VK_SHIFT: - m_keys[vkCode] |= 0x80; - m_keys[VK_SHIFT] |= 0x80; - break; - - case VK_LCONTROL: - case VK_RCONTROL: - case VK_CONTROL: - m_keys[vkCode] |= 0x80; - m_keys[VK_CONTROL] |= 0x80; - break; - - case VK_LMENU: - case VK_RMENU: - case VK_MENU: - m_keys[vkCode] |= 0x80; - m_keys[VK_MENU] |= 0x80; - break; - - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - // toggle keys - m_keys[vkCode] |= 0x80; - break; - - default: - case VK_LWIN: - case VK_RWIN: - case VK_APPS: - m_keys[vkCode] |= 0x80; - break; - } - - // special case: we detect ctrl+alt+del being pressed on some - // systems but we don't detect the release of those keys. so - // if ctrl, alt, and del are down then mark them up. - if ((m_keys[VK_CONTROL] & 0x80) != 0 && - (m_keys[VK_MENU] & 0x80) != 0 && - (m_keys[VK_DELETE] & 0x80) != 0) { - m_keys[VK_LCONTROL] &= ~0x80; - m_keys[VK_RCONTROL] &= ~0x80; - m_keys[VK_CONTROL] &= ~0x80; - m_keys[VK_LMENU] &= ~0x80; - m_keys[VK_RMENU] &= ~0x80; - m_keys[VK_MENU] &= ~0x80; - m_keys[VK_DELETE] &= ~0x80; - } - } - else { - switch (vkCode) { - case 0: - case VK_LBUTTON: - case VK_MBUTTON: - case VK_RBUTTON: - // ignore bogus key - break; - - case VK_LSHIFT: - case VK_RSHIFT: - case VK_SHIFT: - m_keys[vkCode] &= ~0x80; - if (((m_keys[VK_LSHIFT] | m_keys[VK_RSHIFT]) & 0x80) == 0) { - m_keys[VK_SHIFT] &= ~0x80; - } - break; - - case VK_LCONTROL: - case VK_RCONTROL: - case VK_CONTROL: - m_keys[vkCode] &= ~0x80; - if (((m_keys[VK_LCONTROL] | m_keys[VK_RCONTROL]) & 0x80) == 0) { - m_keys[VK_CONTROL] &= ~0x80; - } - break; - - case VK_LMENU: - case VK_RMENU: - case VK_MENU: - m_keys[vkCode] &= ~0x80; - if (((m_keys[VK_LMENU] | m_keys[VK_RMENU]) & 0x80) == 0) { - m_keys[VK_MENU] &= ~0x80; - } - break; - - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - // toggle keys - m_keys[vkCode] &= ~0x80; - m_keys[vkCode] ^= 0x01; - break; - - default: - case VK_LWIN: - case VK_RWIN: - case VK_APPS: - m_keys[vkCode] &= ~0x80; - break; - } - } -} - -bool -CMSWindowsPrimaryScreen::isModifier(UINT vkCode) const - -{ - switch (vkCode) { - case VK_LSHIFT: - case VK_RSHIFT: - case VK_SHIFT: - case VK_LCONTROL: - case VK_RCONTROL: - case VK_CONTROL: - case VK_LMENU: - case VK_RMENU: - case VK_MENU: - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - case VK_LWIN: - case VK_RWIN: - return true; - - default: - return false; - } -} - -KeyButton -CMSWindowsPrimaryScreen::mapKeyToScanCode(UINT vk1, UINT vk2) const -{ - KeyButton button = static_cast(MapVirtualKey(vk1, 0)); - if (button == 0) { - button = static_cast(MapVirtualKey(vk2, 0)); - } - return button; -} diff --git a/lib/platform/CMSWindowsPrimaryScreen.h b/lib/platform/CMSWindowsPrimaryScreen.h deleted file mode 100644 index c318d12c..00000000 --- a/lib/platform/CMSWindowsPrimaryScreen.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 CMSWINDOWSPRIMARYSCREEN_H -#define CMSWINDOWSPRIMARYSCREEN_H - -#include "CPrimaryScreen.h" -#include "IMSWindowsScreenEventHandler.h" -#include "CSynergyHook.h" -#include "MouseTypes.h" -#include "CString.h" - -class CMSWindowsScreen; -class IScreenReceiver; -class IPrimaryScreenReceiver; - -//! Microsoft windows primary screen implementation -class CMSWindowsPrimaryScreen : - public CPrimaryScreen, public IMSWindowsScreenEventHandler { -public: - typedef bool (CMSWindowsPrimaryScreen::*HookMethod)(int, WPARAM, LPARAM); - - CMSWindowsPrimaryScreen(IScreenReceiver*, IPrimaryScreenReceiver*); - virtual ~CMSWindowsPrimaryScreen(); - - // CPrimaryScreen overrides - virtual void reconfigure(UInt32 activeSides); - virtual void warpCursor(SInt32 x, SInt32 y); - virtual void resetOptions(); - virtual void setOptions(const COptionsList& options); - virtual UInt32 addOneShotTimer(double timeout); - virtual KeyModifierMask getToggleMask() const; - virtual bool isLockedToScreen() const; - virtual IScreen* getScreen() const; - - // IMSWindowsScreenEventHandler overrides - virtual void onScreensaver(bool activated); - virtual bool onPreDispatch(const CEvent* event); - virtual bool onEvent(CEvent* event); - virtual void onOneShotTimerExpired(UInt32 id); - virtual SInt32 getJumpZoneSize() const; - virtual void postCreateWindow(HWND); - virtual void preDestroyWindow(HWND); - virtual void onAccessibleDesktop(); - -protected: - // CPrimaryScreen overrides - virtual void onPreMainLoop(); - virtual void onPreOpen(); - virtual void onPostOpen(); - virtual void onPostClose(); - virtual void onPreEnter(); - virtual void onPostEnter(); - virtual void onPreLeave(); - virtual void onPostLeave(bool); - - virtual void createWindow(); - virtual void destroyWindow(); - virtual bool showWindow(); - virtual void hideWindow(); - virtual void warpCursorToCenter(); - - virtual void updateKeys(); - -private: - void enterNoWarp(); - - // 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; - - // key and button queries - KeyID mapKey(WPARAM keycode, LPARAM info, - KeyModifierMask* maskOut, bool* altgr); - ButtonID mapButton(WPARAM msg, LPARAM button) const; - void updateKey(UINT vkCode, bool press); - bool isModifier(UINT vkCode) const; - KeyButton mapKeyToScanCode(UINT vk1, UINT vk2) const; - -private: - IPrimaryScreenReceiver* m_receiver; - CMSWindowsScreen* m_screen; - - // true if windows 95/98/me - bool m_is95Family; - - // the main loop's thread id - DWORD m_threadID; - - // my window - HWND m_window; - - // used to discard queued messages that are no longer needed - UInt32 m_mark; - UInt32 m_markReceived; - - // map of key state - BYTE m_keys[256]; - - // map of button state - BYTE m_buttons[1 + kButtonExtra0 + 1]; - - // last mouse position - SInt32 m_x, m_y; - - // position of center pixel of screen - SInt32 m_xCenter, m_yCenter; - - // hook library stuff - HINSTANCE m_hookLibrary; - InitFunc m_init; - CleanupFunc m_cleanup; - InstallFunc m_install; - UninstallFunc m_uninstall; - SetSidesFunc m_setSides; - SetZoneFunc m_setZone; - SetRelayFunc m_setRelay; - bool m_lowLevel; - - // stuff for hiding the cursor - DWORD m_cursorThread; -}; - -#endif diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp index e42e91b1..7a272c46 100644 --- a/lib/platform/CMSWindowsScreen.cpp +++ b/lib/platform/CMSWindowsScreen.cpp @@ -14,16 +14,20 @@ #include "CMSWindowsScreen.h" #include "CMSWindowsClipboard.h" +#include "CMSWindowsDesktop.h" #include "CMSWindowsScreenSaver.h" #include "CClipboard.h" -#include "IMSWindowsScreenEventHandler.h" #include "IScreenReceiver.h" +#include "IPrimaryScreenReceiver.h" +#include "XScreen.h" #include "CThread.h" #include "CLock.h" -#include "TMethodJob.h" +#include "CFunctionJob.h" #include "CLog.h" #include "CString.h" #include "CStringUtil.h" +#include "TMethodJob.h" +#include "CArch.h" #include "CArchMiscWindows.h" #include #include @@ -38,6 +42,50 @@ #include #pragma warning(pop) +// 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 + +// 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 0x0100 +#define MOUSEEVENTF_XUP 0x0200 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif + +// multimedia keys +#if !defined(VK_BROWSER_BACK) +#define VK_BROWSER_BACK 0xA6 +#define VK_BROWSER_FORWARD 0xA7 +#define VK_BROWSER_REFRESH 0xA8 +#define VK_BROWSER_STOP 0xA9 +#define VK_BROWSER_SEARCH 0xAA +#define VK_BROWSER_FAVORITES 0xAB +#define VK_BROWSER_HOME 0xAC +#define VK_VOLUME_MUTE 0xAD +#define VK_VOLUME_DOWN 0xAE +#define VK_VOLUME_UP 0xAF +#define VK_MEDIA_NEXT_TRACK 0xB0 +#define VK_MEDIA_PREV_TRACK 0xB1 +#define VK_MEDIA_STOP 0xB2 +#define VK_MEDIA_PLAY_PAUSE 0xB3 +#define VK_LAUNCH_MAIL 0xB4 +#define VK_LAUNCH_MEDIA_SELECT 0xB5 +#define VK_LAUNCH_APP1 0xB6 +#define VK_LAUNCH_APP2 0xB7 +#endif + // // CMSWindowsScreen // @@ -45,35 +93,41 @@ HINSTANCE CMSWindowsScreen::s_instance = NULL; CMSWindowsScreen* CMSWindowsScreen::s_screen = NULL; -CMSWindowsScreen::CMSWindowsScreen(IScreenReceiver* receiver, - IMSWindowsScreenEventHandler* eventHandler) : - m_receiver(receiver), - m_eventHandler(eventHandler), - m_class(NULL), - m_icon(NULL), - m_cursor(NULL), +CMSWindowsScreen::CMSWindowsScreen( + IScreenReceiver* receiver, + IPrimaryScreenReceiver* primaryReceiver) : + m_isPrimary(primaryReceiver != NULL), m_is95Family(CArchMiscWindows::isWindows95Family()), + m_receiver(receiver), + m_primaryReceiver(primaryReceiver), + m_isOnScreen(m_isPrimary), + m_class(0), + m_cursor(NULL), m_window(NULL), 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_mark(0), + m_markReceived(0), m_threadID(0), m_lastThreadID(0), - m_nextClipboardWindow(NULL), - m_ownClipboard(false), m_timer(0), - m_desk(NULL), - m_deskName(), - m_hookLibrary(NULL), - m_installScreensaver(NULL), - m_uninstallScreensaver(NULL), + m_oneShotTimer(0), m_screensaver(NULL), m_screensaverNotify(false), - m_inaccessibleDesktop(false) + m_nextClipboardWindow(NULL), + m_ownClipboard(false), + m_desk(NULL), + m_deskName(), + m_inaccessibleDesktop(false), + m_hookLibrary(NULL), + m_lowLevel(false), + m_keyState(NULL) { - assert(s_screen == NULL); - assert(m_receiver != NULL); - assert(m_eventHandler != NULL); + assert(s_screen == NULL); + assert(m_receiver != NULL); s_screen = this; @@ -96,66 +150,6 @@ CMSWindowsScreen::init(HINSTANCE instance) s_instance = instance; } -HWND -CMSWindowsScreen::openDesktop() -{ - // save thread id - m_threadID = GetCurrentThreadId(); - - // get the input desktop and switch to it - if (!switchDesktop(openInputDesktop())) { - return NULL; - } - - // poll input desktop to see if it changes (onPreDispatch() - // handles WM_TIMER). this is also used for polling other - // stuff. - m_timer = SetTimer(NULL, 0, 200, NULL); - - return m_window; -} - -void -CMSWindowsScreen::closeDesktop() -{ - // remove timer - if (m_timer != 0) { - KillTimer(NULL, m_timer); - m_timer = 0; - } - if (m_oneShotTimer != 0) { - KillTimer(NULL, m_oneShotTimer); - m_oneShotTimer = 0; - } - - // disconnect from desktop - switchDesktop(NULL); - - // clear thread id - m_threadID = 0; - - assert(m_window == NULL); - assert(m_desk == NULL); -} - -UInt32 -CMSWindowsScreen::addOneShotTimer(double timeout) -{ - // FIXME -- support multiple one-shot timers - if (m_oneShotTimer != 0) { - KillTimer(NULL, m_oneShotTimer); - } - m_oneShotTimer = SetTimer(NULL, 0, - static_cast(1000.0 * timeout), NULL); - return 0; -} - -bool -CMSWindowsScreen::isMultimon() const -{ - return m_multimon; -} - HINSTANCE CMSWindowsScreen::getInstance() { @@ -163,51 +157,178 @@ CMSWindowsScreen::getInstance() } void -CMSWindowsScreen::open() +CMSWindowsScreen::open(IKeyState* keyState) +{ + assert(s_instance != NULL); + assert(m_class == 0); + assert(m_hookLibrary == NULL); + + try { + // load the hook library + m_hookLibrary = LoadLibrary("synrgyhk"); + if (m_hookLibrary == NULL) { + LOG((CLOG_ERR "Failed to load hook library; synrgyhk.dll is missing")); + throw XScreenOpenFailure(); + } + m_setSides = (SetSidesFunc)GetProcAddress(m_hookLibrary, "setSides"); + m_setZone = (SetZoneFunc)GetProcAddress(m_hookLibrary, "setZone"); + m_setMode = (SetModeFunc)GetProcAddress(m_hookLibrary, "setMode"); + m_install = (InstallFunc)GetProcAddress(m_hookLibrary, "install"); + m_uninstall = (UninstallFunc)GetProcAddress(m_hookLibrary, "uninstall"); + m_init = (InitFunc)GetProcAddress(m_hookLibrary, "init"); + m_cleanup = (CleanupFunc)GetProcAddress(m_hookLibrary, "cleanup"); + m_installScreensaver = + (InstallScreenSaverFunc)GetProcAddress( + m_hookLibrary, "installScreenSaver"); + m_uninstallScreensaver = + (UninstallScreenSaverFunc)GetProcAddress( + m_hookLibrary, "uninstallScreenSaver"); + if (m_setSides == NULL || + m_setZone == NULL || + m_setMode == NULL || + m_install == NULL || + m_uninstall == NULL || + m_init == NULL || + m_cleanup == NULL || + m_installScreensaver == NULL || + m_uninstallScreensaver == NULL) { + LOG((CLOG_ERR "Invalid hook library; use a newer synrgyhk.dll")); + throw XScreenOpenFailure(); + } + + // save thread id. this is mainly to ensure that mainLoop() + // is called by the same thread that called open(). these + // threads must be the same to get the right message queue. + m_threadID = GetCurrentThreadId(); + + // initialize hook library + if (m_isPrimary && m_init(m_threadID) == 0) { + LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?")); + throw XScreenOpenFailure(); + } + + // create the transparent cursor + m_cursor = createBlankCursor(); + + // register a window class + 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 = m_cursor; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "Synergy"; + classInfo.hIconSm = NULL; + m_class = RegisterClassEx(&classInfo); + + // get screen shape + updateScreenShape(); + + // initialize the screen saver + m_screensaver = new CMSWindowsScreenSaver(); + + // initialize marks + m_mark = 0; + m_markReceived = 0; + } + catch (...) { + close(); + throw; + } + + // save the IKeyState + m_keyState = keyState; +} + +void +CMSWindowsScreen::close() { assert(s_instance != NULL); - assert(m_class == 0); - LOG((CLOG_DEBUG "opening display")); + // done with m_keyState + m_keyState = NULL; - // create the transparent cursor - createBlankCursor(); + // done with screen saver + delete m_screensaver; - // register a window class - 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 = m_cursor; - classInfo.hbrBackground = NULL; - classInfo.lpszMenuName = NULL; - classInfo.lpszClassName = "Synergy"; - classInfo.hIconSm = NULL; - m_class = RegisterClassEx(&classInfo); - - // get screen shape - updateScreenShape(); - - // initialize the screen saver - m_screensaver = new CMSWindowsScreenSaver(); - - // load the hook library and get the screen saver functions - m_hookLibrary = LoadLibrary("synrgyhk"); - if (m_hookLibrary != NULL) { - m_installScreensaver = (InstallScreenSaverFunc)GetProcAddress( - m_hookLibrary, "installScreenSaver"); - m_uninstallScreensaver = (UninstallScreenSaverFunc)GetProcAddress( - m_hookLibrary, "uninstallScreenSaver"); - if (m_installScreensaver == NULL || m_uninstallScreensaver == NULL) { - // disable if either install or uninstall is unavailable - m_installScreensaver = NULL; - m_uninstallScreensaver = NULL; - } + // unregister the window class + if (m_class != 0) { + UnregisterClass((LPCTSTR)m_class, s_instance); } + + // done with cursor + if (m_cursor != NULL) { + DestroyCursor(m_cursor); + } + + // done with hook library + if (m_hookLibrary != NULL) { + if (m_isPrimary) { + m_cleanup(); + } + FreeLibrary(m_hookLibrary); + } + + // reset state + m_screensaver = NULL; + m_cursor = NULL; + m_class = 0; + m_hookLibrary = NULL; + m_threadID = 0; +} + +void +CMSWindowsScreen::enable() +{ + assert(m_isOnScreen == m_isPrimary); + + if (m_isPrimary) { + // update shadow key state + m_keyMapper.update(NULL); + + // set jump zones + m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + + // watch jump zones + m_setMode(kHOOK_WATCH_JUMP_ZONE); + } + + // create the window + if (!switchDesktop(CMSWindowsDesktop::openInputDesktop())) { + throw XScreenOpenFailure(); + } + + // poll input desktop to see if it changes. windows doesn't + // inform us when the desktop has changed but we need to + // open a new window when that happens so we poll. this is + // also used for polling other stuff. + m_timer = SetTimer(NULL, 0, 200, NULL); +} + +void +CMSWindowsScreen::disable() +{ + // remove timers + if (m_timer != 0) { + KillTimer(NULL, m_timer); + } + if (m_oneShotTimer != 0) { + KillTimer(NULL, m_oneShotTimer); + } + + // reset state + m_timer = 0; + m_oneShotTimer = 0; + + // done with window + switchDesktop(NULL); + assert(m_window == NULL); + assert(m_desk == NULL); } void @@ -217,20 +338,19 @@ CMSWindowsScreen::mainLoop() assert(m_threadID == GetCurrentThreadId()); // event loop - CEvent event; - event.m_result = 0; + MSG msg; for (;;) { // wait for an event in a cancellable way if (CThread::getCurrentThread().waitForEvent(-1.0) != CThread::kEvent) { continue; } - if (!PeekMessage(&event.m_msg, NULL, 0, 0, PM_REMOVE)) { + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { continue; } // handle quit message - if (event.m_msg.message == WM_QUIT) { - if (event.m_msg.wParam == 0) { + if (msg.message == WM_QUIT) { + if (msg.wParam == 0) { // force termination CThread::getCurrentThread().cancel(); } @@ -241,9 +361,9 @@ CMSWindowsScreen::mainLoop() } // dispatch message - if (!onPreDispatch(&event)) { - TranslateMessage(&event.m_msg); - DispatchMessage(&event.m_msg); + if (!onPreDispatch(msg.hwnd, msg.message, msg.wParam, msg.lParam)) { + TranslateMessage(&msg); + DispatchMessage(&msg); } } } @@ -256,35 +376,82 @@ CMSWindowsScreen::exitMainLoop() } void -CMSWindowsScreen::close() +CMSWindowsScreen::enter() { - assert(s_instance != NULL); + if (m_isPrimary) { + // show the cursor + showCursor(true); + m_cursorThread = 0; - // done with hook library - if (m_hookLibrary != NULL) { - FreeLibrary(m_hookLibrary); - m_installScreensaver = NULL; - m_uninstallScreensaver = NULL; - m_hookLibrary = NULL; + // 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(); + } + else { + // show the cursor + ShowWindow(m_window, SW_HIDE); } - // done with screen saver - delete m_screensaver; - m_screensaver = NULL; + // now on screen + m_isOnScreen = true; +} - // unregister the window class - if (m_class != 0) { - UnregisterClass((LPCTSTR)m_class, s_instance); - m_class = 0; +bool +CMSWindowsScreen::leave() +{ + if (m_isPrimary) { + // show window +/* XXX + if (m_lowLevel) { + SetWindowPos(m_window, HWND_TOPMOST, m_xCenter, m_yCenter, 1, 1, + SWP_NOACTIVATE); + ShowWindow(m_window, SW_SHOWNA); + } +*/ + // update keys +/* XXX + m_keyMapper.update(NULL); +*/ + + // 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(); + + // watch jump zones + m_setMode(kHOOK_RELAY_EVENTS); + + // hide the cursor if using low level hooks + if (m_lowLevel) { + HWND hwnd = GetForegroundWindow(); + m_cursorThread = GetWindowThreadProcessId(hwnd, NULL); + showCursor(false); + } + } + else { + // move hider window under the cursor center + MoveWindow(m_window, m_xCenter, m_yCenter, 1, 1, FALSE); + + // raise and show the hider window + ShowWindow(m_window, SW_SHOWNA); + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); } - // delete resources - if (m_cursor != NULL) { - DestroyCursor(m_cursor); - m_cursor = NULL; - } + // now off screen + m_isOnScreen = false; - LOG((CLOG_DEBUG "closed display")); + return true; } bool @@ -321,6 +488,7 @@ CMSWindowsScreen::checkClipboards() // 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; m_receiver->onGrabClipboard(kClipboardClipboard); m_receiver->onGrabClipboard(kClipboardSelection); @@ -334,9 +502,7 @@ CMSWindowsScreen::openScreensaver(bool notify) m_screensaverNotify = notify; if (m_screensaverNotify) { - if (m_installScreensaver != NULL) { - m_installScreensaver(); - } + m_installScreensaver(); } else { m_screensaver->disable(); @@ -348,9 +514,7 @@ CMSWindowsScreen::closeScreensaver() { if (m_screensaver != NULL) { if (m_screensaverNotify) { - if (m_uninstallScreensaver != NULL) { - m_uninstallScreensaver(); - } + m_uninstallScreensaver(); } else { m_screensaver->enable(); @@ -363,6 +527,7 @@ void CMSWindowsScreen::screensaver(bool activate) { assert(m_screensaver != NULL); + if (activate) { m_screensaver->activate(); } @@ -372,25 +537,29 @@ CMSWindowsScreen::screensaver(bool activate) } void -CMSWindowsScreen::syncDesktop() +CMSWindowsScreen::resetOptions() { - // change calling thread's desktop - if (!m_is95Family) { - if (SetThreadDesktop(m_desk) == 0) { -// LOG((CLOG_WARN "failed to set desktop: %d", GetLastError())); - } - } + // no options +} - // attach input queues if not already attached. this has a habit - // of sucking up more and more CPU each time it's called (even if - // the threads are already attached). since we only expect one - // thread to call this more than once we can save just the last - // attached thread. - DWORD threadID = GetCurrentThreadId(); - if (threadID != m_lastThreadID && threadID != m_threadID) { - m_lastThreadID = threadID; - AttachThreadInput(threadID, m_threadID, TRUE); - } +void +CMSWindowsScreen::setOptions(const COptionsList&) +{ + // no options +} + +void +CMSWindowsScreen::updateKeys() +{ + syncDesktop(); + m_keyMapper.update(m_keyState); + memset(m_buttons, 0, sizeof(m_buttons)); +} + +bool +CMSWindowsScreen::isPrimary() const +{ + return m_isPrimary; } bool @@ -402,8 +571,7 @@ CMSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const } void -CMSWindowsScreen::getShape(SInt32& x, SInt32& y, - SInt32& w, SInt32& h) const +CMSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { assert(m_class != 0); @@ -417,20 +585,243 @@ void CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const { POINT pos; + syncDesktop(); if (GetCursorPos(&pos)) { x = pos.x; y = pos.y; } else { - getCursorCenter(x, y); + x = m_xCenter; + y = m_yCenter; } } void -CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +CMSWindowsScreen::reconfigure(UInt32 activeSides) { - x = GetSystemMetrics(SM_CXSCREEN) >> 1; - y = GetSystemMetrics(SM_CYSCREEN) >> 1; + assert(m_isPrimary); + + 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::addOneShotTimer(double timeout) +{ + // FIXME -- support multiple one-shot timers + if (m_oneShotTimer != 0) { + KillTimer(NULL, m_oneShotTimer); + } + m_oneShotTimer = SetTimer(NULL, 0, + static_cast(1000.0 * timeout), NULL); + return 0; +} + +SInt32 +CMSWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +CMSWindowsScreen::isAnyMouseButtonDown() const +{ + static const char* buttonToName[] = { + "button 0", + "Left Button", + "Middle Button", + "Right Button", + "X Button 1", + "X Button 2" + }; + + for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + if ((m_buttons[i] & 0x80) != 0) { + LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i])); + return true; + } + } + + return false; +} + +const char* +CMSWindowsScreen::getKeyName(KeyButton virtualKey) const +{ + return m_keyMapper.getKeyName(virtualKey); +} + +void +CMSWindowsScreen::fakeKeyEvent(KeyButton virtualKey, bool press) const +{ + DWORD flags = 0; + if (m_keyMapper.isExtendedKey(virtualKey)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (!press) { + flags |= KEYEVENTF_KEYUP; + } + const UINT code = m_keyMapper.keyToScanCode(&virtualKey); + syncDesktop(); + keybd_event(static_cast(virtualKey & 0xffu), + static_cast(code), flags, 0); +} + +bool +CMSWindowsScreen::fakeCtrlAltDel() const +{ + 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, we + // 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(&CMSWindowsScreen::ctrlAltDelThread)); + cad.wait(); + } + else { + // get the sequence of keys to simulate ctrl+alt+del + IKeyState::Keystrokes keys; + KeyID key = kKeyDelete; + KeyModifierMask mask = KeyModifierControl | KeyModifierAlt; + if (mapKey(keys, *m_keyState, key, mask, false) == 0) { + keys.clear(); + } + + // do it + for (IKeyState::Keystrokes::const_iterator k = keys.begin(); + k != keys.end(); ++k) { + fakeKeyEvent(k->m_key, k->m_press); + } + } + return true; +} + +void +CMSWindowsScreen::fakeMouseButton(ButtonID id, bool press) const +{ + // map button id to button flag + DWORD data; + DWORD flags = mapButtonToEvent(id, press, &data); + + // send event + if (flags != 0) { + syncDesktop(); + mouse_event(flags, 0, 0, data, 0); + } +} + +void +CMSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + // motion is simple (i.e. it's on the primary monitor) if there + // is only one monitor. + bool simple = !m_multimon; + 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 + syncDesktop(); + 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)((65536.0 * x) / w), + (DWORD)((65536.0 * y) / h), + 0, 0); + } + + // windows 98 (and Me?) is 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 a 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. 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. + else { + // 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); + } + + // get current mouse position + POINT pos; + GetCursorPos(&pos); + + // move relative to mouse position + mouse_event(MOUSEEVENTF_MOVE, x - pos.x, y - pos.y, 0, 0); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } + } +} + +void +CMSWindowsScreen::fakeMouseWheel(SInt32 delta) const +{ + syncDesktop(); + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0); +} + +KeyButton +CMSWindowsScreen::mapKey(IKeyState::Keystrokes& keys, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + return m_keyMapper.mapKey(keys, keyState, id, desiredMask, isAutoRepeat); } void @@ -443,208 +834,15 @@ CMSWindowsScreen::updateScreenShape() m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d", m_x, m_y, m_w, m_h)); + // 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)); } -bool -CMSWindowsScreen::onPreDispatch(const CEvent* event) -{ - // handle event - const MSG* msg = &event->m_msg; - switch (msg->message) { - case SYNERGY_MSG_SCREEN_SAVER: - { - // activating or deactivating? - bool activate = (msg->wParam != 0); - - // 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)) { - if (activate) { - if (m_screensaver->checkStarted( - SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) { - m_eventHandler->onScreensaver(true); - } - } - else { - m_eventHandler->onScreensaver(false); - } - } - return true; - } - - case WM_TIMER: - if (msg->wParam == m_timer) { - // if current desktop is not the input desktop then switch to it. - // windows 95 doesn't support multiple desktops so don't bother - // to check under it. - if (!m_is95Family) { - HDESK desk = openInputDesktop(); - if (desk != NULL) { - if (isCurrentDesktop(desk)) { - CloseDesktop(desk); - } - else if (m_screensaver->isActive()) { - // don't switch desktops when the screensaver is - // active. we'd most likely switch to the - // screensaver desktop which would have the side - // effect of forcing the screensaver to stop. - CloseDesktop(desk); - } - else { - switchDesktop(desk); - } - - // if the desktop was inaccessible then notify the - // event handler of that. - if (m_inaccessibleDesktop) { - LOG((CLOG_DEBUG "desktop is now accessible")); - m_inaccessibleDesktop = false; - m_eventHandler->onAccessibleDesktop(); - } - } - else if (!m_inaccessibleDesktop) { - // the desktop has become inaccessible - m_inaccessibleDesktop = true; - LOG((CLOG_DEBUG "desktop is now inaccessible")); - } - } - - // let client do timer related stuff. ignore the return - // value though since the event has been handled here. - m_eventHandler->onPreDispatch(event); - } - else if (msg->wParam == m_oneShotTimer) { - // one shot timer expired - KillTimer(NULL, m_oneShotTimer); - m_oneShotTimer = 0; - m_eventHandler->onOneShotTimerExpired(0); - } - - return true; - } - - // let client handle the event - return m_eventHandler->onPreDispatch(event); -} - -bool -CMSWindowsScreen::onEvent(CEvent* event) -{ - assert(event != NULL); - - const MSG& msg = event->m_msg; - switch (msg.message) { - case WM_QUERYENDSESSION: - if (m_is95Family) { - event->m_result = TRUE; - return true; - } - break; - - case WM_ENDSESSION: - if (m_is95Family) { - if (msg.wParam == TRUE && msg.lParam == 0) { - exitMainLoop(); - } - return true; - } - break; - - case WM_PAINT: - ValidateRect(msg.hwnd, NULL); - return true; - - case WM_DRAWCLIPBOARD: - LOG((CLOG_DEBUG "clipboard was taken")); - - // first pass it on - if (m_nextClipboardWindow != NULL) { - SendMessage(m_nextClipboardWindow, - msg.message, msg.wParam, msg.lParam); - } - - // now notify client that somebody changed the clipboard (unless - // we're the owner). - if (!CMSWindowsClipboard::isOwnedBySynergy()) { - if (m_ownClipboard) { - m_ownClipboard = false; - m_receiver->onGrabClipboard(kClipboardClipboard); - m_receiver->onGrabClipboard(kClipboardSelection); - } - } - else { - m_ownClipboard = true; - } - return true; - - case WM_CHANGECBCHAIN: - if (m_nextClipboardWindow == (HWND)msg.wParam) { - m_nextClipboardWindow = (HWND)msg.lParam; - } - else if (m_nextClipboardWindow != NULL) { - SendMessage(m_nextClipboardWindow, - msg.message, msg.wParam, msg.lParam); - } - return true; - - case WM_DISPLAYCHANGE: - { - // screen resolution may have changed. get old shape. - SInt32 xOld, yOld, wOld, hOld; - getShape(xOld, yOld, wOld, hOld); - - // update shape - updateScreenShape(); - - // collect new screen info - CClientInfo info; - getShape(info.m_x, info.m_y, info.m_w, info.m_h); - getCursorPos(info.m_mx, info.m_my); - info.m_zoneSize = m_eventHandler->getJumpZoneSize(); - - // do nothing if resolution hasn't changed - if (info.m_x != xOld || info.m_y != yOld || - info.m_w != wOld || info.m_h != hOld) { - // forward event - m_eventHandler->onEvent(event); - - // send new screen info - m_receiver->onInfoChanged(info); - } - - return true; - } - } - - return m_eventHandler->onEvent(event); -} - -void -CMSWindowsScreen::createBlankCursor() -{ - // 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)); - m_cursor = CreateCursor(s_instance, 0, 0, cw, ch, cursorAND, cursorXOR); - delete[] cursorXOR; - delete[] cursorAND; -} - bool CMSWindowsScreen::switchDesktop(HDESK desk) { @@ -657,17 +855,19 @@ CMSWindowsScreen::switchDesktop(HDESK desk) ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; - // let client clean up before we destroy the window - m_eventHandler->preDestroyWindow(m_window); + // uninstall hooks. we can't change the thread desktop + // with hooks installed. + if (m_isPrimary) { + m_uninstall(); + } - // now destroy window + // now destroy window. we can't change the thread desktop + // with a window. DestroyWindow(m_window); m_window = NULL; // done with desk - if (!m_is95Family) { - CloseDesktop(m_desk); - } + CMSWindowsDesktop::closeDesktop(m_desk); m_desk = NULL; m_deskName = ""; } @@ -680,18 +880,14 @@ CMSWindowsScreen::switchDesktop(HDESK desk) // uninstall screen saver hooks if (m_screensaverNotify) { - if (m_uninstallScreensaver != NULL) { - m_uninstallScreensaver(); - } + m_uninstallScreensaver(); } // set the desktop. can only do this when there are no windows // and hooks on the current desktop owned by this thread. - if (SetThreadDesktop(desk) == 0) { + if (!CMSWindowsDesktop::setDesktop(desk)) { LOG((CLOG_ERR "failed to set desktop: %d", GetLastError())); - if (!m_is95Family) { - CloseDesktop(desk); - } + CMSWindowsDesktop::closeDesktop(desk); return false; } @@ -708,16 +904,55 @@ CMSWindowsScreen::switchDesktop(HDESK desk) NULL); if (m_window == NULL) { LOG((CLOG_ERR "failed to create window: %d", GetLastError())); - if (!m_is95Family) { - CloseDesktop(desk); - } + CMSWindowsDesktop::closeDesktop(desk); return false; } // reinstall screen saver hooks if (m_screensaverNotify) { - if (m_installScreensaver != NULL) { - m_installScreensaver(); + m_installScreensaver(); + } + + if (m_isPrimary) { + // we don't ever want our window to activate + EnableWindow(m_window, FALSE); + + // install hooks + switch (m_install()) { + case kHOOK_FAILED: + // FIXME -- can't install hook so we won't work; report error + m_lowLevel = false; + break; + + case kHOOK_OKAY: + m_lowLevel = false; + break; + + case kHOOK_OKAY_LL: + m_lowLevel = true; + break; + } + + if (m_isOnScreen) { + // all messages prior to now are invalid + // FIXME -- is this necessary; couldn't we lose key releases? + nextMark(); + } + } + else { + // update key state + updateKeys(); + + // hide cursor if this screen isn't active + if (!m_isOnScreen) { + // move hider window under the cursor center + MoveWindow(m_window, m_xCenter, m_yCenter, 1, 1, FALSE); + + // raise and show the hider window + ShowWindow(m_window, SW_SHOWNA); + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); } } @@ -729,68 +964,892 @@ CMSWindowsScreen::switchDesktop(HDESK desk) // save new desktop m_desk = desk; - m_deskName = getDesktopName(m_desk); - LOG((CLOG_DEBUG "switched to desktop \"%s\"", m_deskName.c_str())); - - // let client prepare the window - m_eventHandler->postCreateWindow(m_window); + m_deskName = CMSWindowsDesktop::getDesktopName(desk); + LOG((CLOG_DEBUG "switched to desktop \"%s\" with window 0x%08x", m_deskName.c_str(), (UInt32)m_window)); return true; } -HDESK -CMSWindowsScreen::openInputDesktop() const +void +CMSWindowsScreen::syncDesktop() const { - if (m_is95Family) { - // there's only one desktop on windows 95 et al. - return GetThreadDesktop(GetCurrentThreadId()); + // change calling thread's desktop + if (!CMSWindowsDesktop::setDesktop(m_desk)) { +// LOG((CLOG_WARN "failed to set desktop: %d", GetLastError())); } - else { - return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, - DESKTOP_CREATEWINDOW | - DESKTOP_HOOKCONTROL | - GENERIC_WRITE); - } -} -CString -CMSWindowsScreen::getDesktopName(HDESK desk) const -{ - 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; + // attach input queues if not already attached. this has a habit + // of sucking up more and more CPU each time it's called (even if + // the threads are already attached). since we only expect one + // thread to call this more than once we can save just the last + // attached thread. + DWORD threadID = GetCurrentThreadId(); + if (threadID != m_lastThreadID && threadID != m_threadID) { + CMSWindowsScreen* self = const_cast(this); + self->m_lastThreadID = threadID; + AttachThreadInput(threadID, m_threadID, TRUE); } } bool -CMSWindowsScreen::isCurrentDesktop(HDESK desk) const +CMSWindowsScreen::onPreDispatch(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) { - // don't allocate space for current desktop name on heap since - // we do this a lot and we never save the name. - TCHAR* name; - if (desk == NULL) { - name = _T(""); + // handle event + switch (message) { + case SYNERGY_MSG_SCREEN_SAVER: + return onScreensaver(wParam != 0); + + case WM_TIMER: + return onTimer(static_cast(wParam)); } - else if (m_is95Family) { - name = _T("desktop"); + + if (m_isPrimary) { + return onPreDispatchPrimary(hwnd, message, wParam, lParam); + } + + return false; +} + +bool +CMSWindowsScreen::onPreDispatchPrimary(HWND, + UINT message, WPARAM wParam, LPARAM lParam) +{ + // check if windows key is up but we think it's down. if so then + // synthesize a key release for it. 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 windows will eat the key + // release. if we don't detect that and synthesize the release + // then the user will be locked to the screen and the client won't + // take the usual windows key release action (which on windows is + // to show the start menu). + // + // we can use GetKeyState() to check the state of the windows keys + // because, event though the key release is not reported to us, + // the event is processed and the keyboard state updated by the + // system. since the key could go up at any time we'll check the + // state on every event. only check on windows 95 family since + // NT family reports the key release as usual. obviously we skip + // this if the event is for the windows key itself. + if (m_is95Family) { + if (m_keyMapper.isPressed(VK_LWIN) && + (GetAsyncKeyState(VK_LWIN) & 0x8000) == 0 && + !(message == SYNERGY_MSG_KEY && wParam == VK_LWIN)) { + // compute appropriate parameters for fake event + WPARAM wParam = VK_LWIN; + LPARAM lParam = 0xc1000000; + lParam |= (0x00ff0000 & (MapVirtualKey(wParam, 0) << 24)); + + // process as if it were a key up + KeyModifierMask mask; + KeyButton button = static_cast( + (lParam & 0x00ff0000u) >> 16); + KeyID key = m_keyMapper.mapKeyFromEvent(wParam, + lParam, &mask, NULL); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_primaryReceiver->onKeyUp(key, mask, button); + m_keyMapper.updateKey(static_cast(wParam), false); + } + if (m_keyMapper.isPressed(VK_RWIN) && + (GetAsyncKeyState(VK_RWIN) & 0x8000) == 0 && + !(message == SYNERGY_MSG_KEY && wParam == VK_RWIN)) { + // compute appropriate parameters for fake event + WPARAM wParam = VK_RWIN; + LPARAM lParam = 0xc1000000; + lParam |= (0x00ff0000 & (MapVirtualKey(wParam, 0) << 24)); + + // process as if it were a key up + KeyModifierMask mask; + KeyButton button = static_cast( + (lParam & 0x00ff0000u) >> 16); + KeyID key = m_keyMapper.mapKeyFromEvent(wParam, + lParam, &mask, NULL); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_primaryReceiver->onKeyUp(key, mask, button); + m_keyMapper.updateKey(static_cast(wParam), false); + } + } + + // 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: + return onMouseWheel(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; + } + + return false; +} + +bool +CMSWindowsScreen::onEvent(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam, LRESULT* result) +{ + switch (message) { + case WM_QUERYENDSESSION: + if (m_is95Family) { + *result = TRUE; + return true; + } + break; + + case WM_ENDSESSION: + if (m_is95Family) { + if (wParam == TRUE && lParam == 0) { + exitMainLoop(); + } + return true; + } + break; + + case WM_PAINT: + ValidateRect(hwnd, NULL); + return true; + + case WM_DRAWCLIPBOARD: + LOG((CLOG_DEBUG "clipboard was taken")); + + // first pass on the message + if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, message, 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) { + LOG((CLOG_DEBUG "clipboard chain: forward: %d 0x%08x 0x%08x", message, wParam, lParam)); + SendMessage(m_nextClipboardWindow, message, wParam, lParam); + } + return true; + + case WM_DISPLAYCHANGE: + return onDisplayChange(); + + case WM_ACTIVATEAPP: + return onActivate(wParam != FALSE); + } + + return false; +} + +bool +CMSWindowsScreen::onMark(UInt32 mark) +{ + m_markReceived = mark; + return true; +} + +bool +CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) +{ + // ignore message if posted prior to last mark change + if (!ignore()) { + // check for ctrl+alt+del emulation + if ((wParam == VK_PAUSE || wParam == VK_CANCEL) && + (m_keyMapper.isPressed(VK_CONTROL) && + m_keyMapper.isPressed(VK_MENU))) { + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + wParam = VK_DELETE; + lParam &= 0xffff0000; + lParam |= 0x00000001; + } + + // process key normally + bool altgr; + KeyModifierMask mask; + const KeyID key = m_keyMapper.mapKeyFromEvent(wParam, + lParam, &mask, &altgr); + KeyButton button = static_cast( + (lParam & 0x00ff0000u) >> 16); + if (key != kKeyNone && key != kKeyMultiKey) { + if ((lParam & 0x80000000) == 0) { + // key press + + // if AltGr required for this key then make sure + // the ctrl and alt keys are *not* down on the + // client. windows simulates AltGr with ctrl and + // alt for some inexplicable reason and clients + // will get confused if they see mode switch and + // ctrl and alt. we'll also need to put ctrl and + // alt back the way they were after we simulate + // the key. + bool ctrlL = m_keyMapper.isPressed(VK_LCONTROL); + bool ctrlR = m_keyMapper.isPressed(VK_RCONTROL); + bool altL = m_keyMapper.isPressed(VK_LMENU); + bool altR = m_keyMapper.isPressed(VK_RMENU); + if (altgr) { + KeyID key; + KeyButton button; + UINT scanCode; + KeyModifierMask mask2 = (mask & + ~(KeyModifierControl | + KeyModifierAlt | + KeyModifierModeSwitch)); + if (ctrlL) { + key = kKeyControl_L; + button = VK_LCONTROL; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyUp(key, mask2, button); + } + if (ctrlR) { + key = kKeyControl_R; + button = VK_RCONTROL; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyUp(key, mask2, button); + } + if (altL) { + key = kKeyAlt_L; + button = VK_LMENU; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyUp(key, mask2, button); + } + if (altR) { + key = kKeyAlt_R; + button = VK_RMENU; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyUp(key, mask2, button); + } + } + + // send key + const bool wasDown = ((lParam & 0x40000000) != 0); + SInt32 repeat = (SInt32)(lParam & 0xffff); + if (!wasDown) { + LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_primaryReceiver->onKeyDown(key, mask, button); + if (repeat > 0) { + --repeat; + } + } + if (repeat >= 1) { + LOG((CLOG_DEBUG1 "event: key repeat key=%d mask=0x%04x count=%d button=0x%04x", key, mask, repeat, button)); + m_primaryReceiver->onKeyRepeat(key, mask, repeat, button); + } + + // restore ctrl and alt state + if (altgr) { + KeyID key; + KeyButton button; + UINT scanCode; + KeyModifierMask mask2 = (mask & + ~(KeyModifierControl | + KeyModifierAlt | + KeyModifierModeSwitch)); + if (ctrlL) { + key = kKeyControl_L; + button = VK_LCONTROL; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyDown(key, mask2, button); + mask2 |= KeyModifierControl; + } + if (ctrlR) { + key = kKeyControl_R; + button = VK_RCONTROL; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyDown(key, mask2, button); + mask2 |= KeyModifierControl; + } + if (altL) { + key = kKeyAlt_L; + button = VK_LMENU; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyDown(key, mask2, button); + mask2 |= KeyModifierAlt; + } + if (altR) { + key = kKeyAlt_R; + button = VK_RMENU; + scanCode = m_keyMapper.keyToScanCode(&button); + button = static_cast(scanCode); + LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); + m_primaryReceiver->onKeyDown(key, mask2, button); + mask2 |= KeyModifierAlt; + } + } + } + else { + // key release. 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 && !isModifier(wParam) && + m_keyMapper.isPressed(static_cast(wParam))) { + LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_primaryReceiver->onKeyDown(key, mask, button); + m_keyMapper.updateKey(static_cast(wParam), true); + } + + // do key up + LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_primaryReceiver->onKeyUp(key, mask, button); + } + } + else { + LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", wParam, lParam)); + } + } + + // keep our shadow key state up to date + m_keyMapper.updateKey(static_cast(wParam), + ((lParam & 0x80000000) == 0)); + + return true; +} + +bool +CMSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam) +{ + // get which button + bool pressed = false; + const ButtonID button = mapButtonFromEvent(wParam, lParam); + + // ignore message if posted prior to last mark change + if (!ignore()) { + 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_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + m_primaryReceiver->onMouseDown(button); + } + pressed = true; + break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + m_primaryReceiver->onMouseUp(button); + } + pressed = false; + break; + } + } + + // keep our shadow key state up to date + if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { + if (pressed) { + m_buttons[button] |= 0x80; + } + else { + m_buttons[button] &= ~0x80; + } + } + + 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 + m_primaryReceiver->onMouseMovePrimary(m_xCursor, m_yCursor); } else { - DWORD size; - GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); - name = (TCHAR*)alloca(size + sizeof(TCHAR)); - GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + // 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 + m_primaryReceiver->onMouseMoveSecondary(x, y); + } + } + + return true; +} + +bool +CMSWindowsScreen::onMouseWheel(SInt32 delta) +{ + // ignore message if posted prior to last mark change + if (!ignore()) { + LOG((CLOG_DEBUG1 "event: button wheel delta=%d", delta)); + m_primaryReceiver->onMouseWheel(delta); + } + 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_screensaver->checkStarted(SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) { + m_primaryReceiver->onScreensaver(true); + } + } + else { + m_primaryReceiver->onScreensaver(false); + } + + return true; +} + +bool +CMSWindowsScreen::onTimer(UINT timerID) +{ + if (timerID == m_timer) { + // if current desktop is not the input desktop then switch to it. + HDESK desk = CMSWindowsDesktop::openInputDesktop(); + if (desk == m_desk || + CMSWindowsDesktop::getDesktopName(desk) == m_deskName || + m_screensaver->isActive()) { + // same desktop or screensaver is active. don't switch + // desktops when the screensaver is active. we'd most + // likely switch to the screensaver desktop which would + // have the side effect of forcing the screensaver to stop. + CMSWindowsDesktop::closeDesktop(desk); + } + else { + switchDesktop(desk); + } + + // if the desktop was inaccessible and isn't anymore then + // update our key state. + if (desk != NULL && m_inaccessibleDesktop) { + LOG((CLOG_DEBUG "desktop is now accessible")); + m_inaccessibleDesktop = false; + updateKeys(); + } + + // note if desktop was accessible but isn't anymore + else if (desk == NULL && !m_inaccessibleDesktop) { + m_inaccessibleDesktop = true; + LOG((CLOG_DEBUG "desktop is now inaccessible")); + } + } + + else if (timerID == m_oneShotTimer) { + // one shot timer expired + KillTimer(NULL, m_oneShotTimer); + m_oneShotTimer = 0; + m_primaryReceiver->onOneShotTimerExpired(0); + } + + 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()); + } + } + + // collect new screen info + CClientInfo info; + info.m_x = m_x; + info.m_y = m_y; + info.m_w = m_w; + info.m_h = m_h; + info.m_zoneSize = getJumpZoneSize(); + getCursorPos(info.m_mx, info.m_my); + + // send new screen info + m_receiver->onInfoChanged(info); + } + + return true; +} + +bool +CMSWindowsScreen::onClipboardChange() +{ + // now notify client that somebody changed the clipboard (unless + // we're the owner). + if (!CMSWindowsClipboard::isOwnedBySynergy()) { + LOG((CLOG_DEBUG "clipboard changed: foreign owned")); + if (m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership")); + m_ownClipboard = false; + m_receiver->onGrabClipboard(kClipboardClipboard); + m_receiver->onGrabClipboard(kClipboardSelection); + } + } + else { + LOG((CLOG_DEBUG "clipboard changed: synergy owned")); + m_ownClipboard = true; + } + + return true; +} + +bool +CMSWindowsScreen::onActivate(bool activated) +{ + if (!m_isPrimary && activated) { + // some other app activated. hide the hider window. + ShowWindow(m_window, SW_HIDE); + } + + return false; +} + +void +CMSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + // send an event that we can recognize before the mouse warp + PostThreadMessage(m_threadID, 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(m_threadID, SYNERGY_MSG_POST_WARP, 0, 0); +} + +void +CMSWindowsScreen::nextMark() +{ + // next mark + ++m_mark; + + // mark point in message queue where the mark was changed + PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0); +} + +bool +CMSWindowsScreen::ignore() const +{ + return (m_mark != m_markReceived); +} + +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::showCursor(bool show) const +{ + if (m_cursorThread != 0) { + if (m_threadID != m_cursorThread) { + AttachThreadInput(m_threadID, m_cursorThread, TRUE); + } + ShowCursor(show ? TRUE : FALSE); + if (m_threadID != m_cursorThread) { + AttachThreadInput(m_threadID, m_cursorThread, FALSE); + } + } +} + +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); + } +} + +DWORD +CMSWindowsScreen::mapButtonToEvent(ButtonID button, + bool press, DWORD* inData) const +{ + DWORD dummy; + DWORD* data = (inData != NULL) ? inData : &dummy; + + // 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 + *data = 0; + switch (button) { + case kButtonLeft: + return press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + + case kButtonMiddle: + return press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + + case kButtonRight: + return press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + + case kButtonExtra0 + 0: + *data = XBUTTON1; + return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + + case kButtonExtra0 + 1: + *data = XBUTTON2; + return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + + default: + return 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: + return kButtonExtra0 + 0; + + case XBUTTON2: + return kButtonExtra0 + 1; + } + return kButtonNone; + + default: + return kButtonNone; + } +} + +bool +CMSWindowsScreen::isModifier(UINT vkCode) const +{ + switch (vkCode) { + case VK_LSHIFT: + case VK_RSHIFT: + case VK_SHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_CONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_MENU: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + case VK_LWIN: + case VK_RWIN: + return true; + + default: + return false; + } +} + +void +CMSWindowsScreen::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())); } - return (_tcsicmp(name, m_deskName.c_str()) == 0); } LRESULT CALLBACK @@ -798,17 +1857,10 @@ CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { assert(s_screen != NULL); - CEvent event; - event.m_msg.hwnd = hwnd; - event.m_msg.message = msg; - event.m_msg.wParam = wParam; - event.m_msg.lParam = lParam; - event.m_result = 0; + LRESULT result = 0; + if (!s_screen->onEvent(hwnd, msg, wParam, lParam, &result)) { + result = DefWindowProc(hwnd, msg, wParam, lParam); + } - if (s_screen->onEvent(&event)) { - return event.m_result; - } - else { - return DefWindowProc(hwnd, msg, wParam, lParam); - } + return result; } diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h index f4af0cac..29be457b 100644 --- a/lib/platform/CMSWindowsScreen.h +++ b/lib/platform/CMSWindowsScreen.h @@ -15,7 +15,8 @@ #ifndef CMSWINDOWSSCREEN_H #define CMSWINDOWSSCREEN_H -#include "IScreen.h" +#include "IPlatformScreen.h" +#include "CMSWindowsKeyMapper.h" #include "CSynergyHook.h" #include "CMutex.h" #include "CString.h" @@ -23,22 +24,13 @@ #include class CMSWindowsScreenSaver; -class CThread; - -// Microsoft windows event -class CEvent { -public: - MSG m_msg; - LRESULT m_result; -}; - class IScreenReceiver; -class IMSWindowsScreenEventHandler; +class IPrimaryScreenReceiver; -//! Implementation of IScreen for Microsoft Windows -class CMSWindowsScreen : public IScreen { +//! Implementation of IPlatformScreen for Microsoft Windows +class CMSWindowsScreen : public IPlatformScreen { public: - CMSWindowsScreen(IScreenReceiver*, IMSWindowsScreenEventHandler*); + CMSWindowsScreen(IScreenReceiver*, IPrimaryScreenReceiver*); virtual ~CMSWindowsScreen(); //! @name manipulators @@ -51,37 +43,10 @@ public: */ static void init(HINSTANCE); - //! Open desktop - /*! - Open the desktop and create and return the window. Returns NULL - on failure. - */ - HWND openDesktop(); - - //! Close desktop - /*! - Close the window and desktop. - */ - void closeDesktop(); - - //! Install a one-shot timer - /*! - Installs a one-shot timer for \c timeout seconds and returns the - id of the timer (which will be passed to the receiver's - \c onTimerExpired()). - */ - UInt32 addOneShotTimer(double timeout); - //@} //! @name accessors //@{ - //! Test for multiple monitors - /*! - Returns true iff the system appears to have multiple monitors. - */ - bool isMultimon() const; - //! Get instance /*! Returns the application instance handle passed to init(). @@ -90,53 +55,116 @@ public: //@} - // IScreen overrides - // note -- this class expects the hook DLL to have been loaded - // and initialized before open() is called. - void open(); - void mainLoop(); - void exitMainLoop(); - void close(); - bool setClipboard(ClipboardID, const IClipboard*); - void checkClipboards(); - void openScreensaver(bool notify); - void closeScreensaver(); - void screensaver(bool activate); - void syncDesktop(); - bool getClipboard(ClipboardID, IClipboard*) const; - void getShape(SInt32&, SInt32&, SInt32&, SInt32&) const; - void getCursorPos(SInt32&, SInt32&) const; - void getCursorCenter(SInt32&, SInt32&) const; + // IPlatformScreen overrides + virtual void open(IKeyState*); + virtual void close(); + virtual void enable(); + virtual void disable(); + virtual void mainLoop(); + virtual void exitMainLoop(); + 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 updateKeys(); + virtual bool isPrimary() const; + virtual bool getClipboard(ClipboardID, IClipboard*) const; + virtual void getShape(SInt32&, SInt32&, SInt32&, SInt32&) const; + virtual void getCursorPos(SInt32&, SInt32&) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 addOneShotTimer(double timeout); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual const char* getKeyName(KeyButton) const; + + // ISecondaryScreen overrides + virtual void fakeKeyEvent(KeyButton id, bool press) const; + virtual bool fakeCtrlAltDel() const; + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseWheel(SInt32 delta) const; + virtual KeyButton mapKey(IKeyState::Keystrokes&, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; private: // update screen size cache void updateScreenShape(); - // internal pre-dispatch event processing - bool onPreDispatch(const CEvent* event); - - // internal (post-dispatch) event processing - bool onEvent(CEvent* event); - - // create the transparent cursor - void createBlankCursor(); - // switch to the given desktop. this destroys the window and unhooks // all hooks, switches the desktop, then creates the window and rehooks // all hooks (because you can't switch the thread's desktop if it has // any windows or hooks). bool switchDesktop(HDESK desk); - // get the input desktop. caller must CloseDesktop() the result. - // do not call under windows 95/98/me. - HDESK openInputDesktop() const; + // make sure we're on the expected desktop + void syncDesktop() const; - // get the desktop's name. do not call under windows 95/98/me. - CString getDesktopName(HDESK) const; + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatch(HWND, UINT, WPARAM, LPARAM); - // returns true iff desk is the current desk. do not call under - // windows 95/98/me. - bool isCurrentDesktop(HDESK desk) const; + // 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 onMouseButton(WPARAM, LPARAM); + bool onMouseMove(SInt32 x, SInt32 y); + bool onMouseWheel(SInt32 delta); + bool onScreensaver(bool activated); + bool onTimer(UINT timerID); + bool onDisplayChange(); + bool onClipboardChange(); + bool onActivate(bool activated); + +// XXX + // 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; +// XXX + + // create the transparent cursor + HCURSOR createBlankCursor() const; + + // show/hide the cursor + void showCursor(bool) const; + + // enable/disable special key combinations so we can catch/pass them + void enableSpecialKeys(bool) const; + + // map a button ID and action to a mouse event + DWORD mapButtonToEvent(ButtonID button, + bool press, DWORD* inData) const; + + // map a button event to a button ID + ButtonID mapButtonFromEvent(WPARAM msg, LPARAM button) const; + + // return true iff the given virtual key is a modifier + bool isModifier(UINT vkCode) const; + + // send ctrl+alt+del hotkey event + static void ctrlAltDelThread(void*); // our window proc static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); @@ -144,53 +172,63 @@ private: private: static HINSTANCE s_instance; - IScreenReceiver* m_receiver; - IMSWindowsScreenEventHandler* m_eventHandler; - - ATOM m_class; - HICON m_icon; - HCURSOR m_cursor; + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; // true if windows 95/98/me bool m_is95Family; - // our window + // receivers + IScreenReceiver* m_receiver; + IPrimaryScreenReceiver* m_primaryReceiver; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_class; + HCURSOR m_cursor; HWND m_window; - // screen shape + // 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; + + // 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; // the thread id of the last attached thread DWORD m_lastThreadID; - // clipboard stuff - HWND m_nextClipboardWindow; - bool m_ownClipboard; - // the timer used to check for desktop switching UINT m_timer; // the one shot timer UINT m_oneShotTimer; + // screen saver stuff + CMSWindowsScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // clipboard stuff + HWND m_nextClipboardWindow; + bool m_ownClipboard; + // the current desk and it's name HDESK m_desk; CString m_deskName; - // screen saver stuff - HINSTANCE m_hookLibrary; - InstallScreenSaverFunc m_installScreensaver; - UninstallScreenSaverFunc m_uninstallScreensaver; - CMSWindowsScreenSaver* m_screensaver; - bool m_screensaverNotify; - // true when the current desktop is inaccessible. while // the desktop is inaccessible we won't receive user input // and we'll lose track of the keyboard state. when the @@ -198,6 +236,29 @@ private: // handler of that. bool m_inaccessibleDesktop; + // hook library stuff + HINSTANCE m_hookLibrary; + InitFunc m_init; + CleanupFunc m_cleanup; + InstallFunc m_install; + UninstallFunc m_uninstall; + SetSidesFunc m_setSides; + SetZoneFunc m_setZone; + SetModeFunc m_setMode; + InstallScreenSaverFunc m_installScreensaver; + UninstallScreenSaverFunc m_uninstallScreensaver; + bool m_lowLevel; + + // keyboard stuff + IKeyState* m_keyState; + CMSWindowsKeyMapper m_keyMapper; + + // map of button state + BYTE m_buttons[1 + kButtonExtra0 + 1]; + + // stuff for hiding the cursor + DWORD m_cursorThread; + static CMSWindowsScreen* s_screen; }; diff --git a/lib/platform/CMSWindowsSecondaryScreen.cpp b/lib/platform/CMSWindowsSecondaryScreen.cpp deleted file mode 100644 index c04cfcb0..00000000 --- a/lib/platform/CMSWindowsSecondaryScreen.cpp +++ /dev/null @@ -1,1338 +0,0 @@ -/* - * 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 "CMSWindowsSecondaryScreen.h" -#include "CMSWindowsScreen.h" -#include "XScreen.h" -#include "CLock.h" -#include "CThread.h" -#include "CFunctionJob.h" -#include "CLog.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 - -// 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 0x0100 -#define MOUSEEVENTF_XUP 0x0200 -#define XBUTTON1 0x0001 -#define XBUTTON2 0x0002 -#endif - -// multimedia keys -#if !defined(VK_BROWSER_BACK) -#define VK_BROWSER_BACK 0xA6 -#define VK_BROWSER_FORWARD 0xA7 -#define VK_BROWSER_REFRESH 0xA8 -#define VK_BROWSER_STOP 0xA9 -#define VK_BROWSER_SEARCH 0xAA -#define VK_BROWSER_FAVORITES 0xAB -#define VK_BROWSER_HOME 0xAC -#define VK_VOLUME_MUTE 0xAD -#define VK_VOLUME_DOWN 0xAE -#define VK_VOLUME_UP 0xAF -#define VK_MEDIA_NEXT_TRACK 0xB0 -#define VK_MEDIA_PREV_TRACK 0xB1 -#define VK_MEDIA_STOP 0xB2 -#define VK_MEDIA_PLAY_PAUSE 0xB3 -#define VK_LAUNCH_MAIL 0xB4 -#define VK_LAUNCH_MEDIA_SELECT 0xB5 -#define VK_LAUNCH_APP1 0xB6 -#define VK_LAUNCH_APP2 0xB7 -#endif - -// -// CMSWindowsSecondaryScreen -// - -// a list of modifier key info -const CMSWindowsSecondaryScreen::CModifierInfo - CMSWindowsSecondaryScreen::s_modifier[] = { - { KeyModifierShift, VK_LSHIFT, VK_RSHIFT, false }, - { KeyModifierControl, VK_LCONTROL, VK_RCONTROL | 0x100,false }, - { KeyModifierAlt, VK_LMENU, VK_RMENU | 0x100, false }, - // note -- no keys for KeyModifierMeta - { KeyModifierSuper, VK_LWIN | 0x100, VK_RWIN | 0x100, false }, - { KeyModifierCapsLock, VK_CAPITAL, 0, true }, - { KeyModifierNumLock, VK_NUMLOCK | 0x100, 0, true }, - { KeyModifierScrollLock,VK_SCROLL, 0, true } -}; - -CMSWindowsSecondaryScreen::CMSWindowsSecondaryScreen( - IScreenReceiver* receiver) : - m_is95Family(CArchMiscWindows::isWindows95Family()), - m_window(NULL) -{ - m_screen = new CMSWindowsScreen(receiver, this); -} - -CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() -{ - assert(m_window == NULL); - - delete m_screen; -} - -CSecondaryScreen::SysKeyID -CMSWindowsSecondaryScreen::getUnhanded(SysKeyID sysKeyID) const -{ - switch (sysKeyID) { - case VK_LSHIFT: - case VK_RSHIFT: - return VK_SHIFT; - - case VK_LCONTROL: - case VK_RCONTROL: - return VK_CONTROL; - - case VK_LMENU: - case VK_RMENU: - return VK_MENU; - - default: - return 0; - } -} - -CSecondaryScreen::SysKeyID -CMSWindowsSecondaryScreen::getOtherHanded(SysKeyID sysKeyID) const -{ - switch (sysKeyID) { - case VK_LSHIFT: - return VK_RSHIFT; - - case VK_RSHIFT: - return VK_LSHIFT; - - case VK_LCONTROL: - return VK_RCONTROL; - - case VK_RCONTROL: - return VK_LCONTROL; - - case VK_LMENU: - return VK_RMENU; - - case VK_RMENU: - return VK_LMENU; - - default: - return 0; - } -} - -bool -CMSWindowsSecondaryScreen::isAutoRepeating(SysKeyID) const -{ - return true; -} - -void -CMSWindowsSecondaryScreen::sync() const -{ - m_screen->syncDesktop(); -} - -IScreen* -CMSWindowsSecondaryScreen::getScreen() const -{ - return m_screen; -} - -void -CMSWindowsSecondaryScreen::onScreensaver(bool) -{ - // ignore -} - -bool -CMSWindowsSecondaryScreen::onPreDispatch(const CEvent*) -{ - return false; -} - -bool -CMSWindowsSecondaryScreen::onEvent(CEvent* event) -{ - assert(event != NULL); - - const MSG& msg = event->m_msg; - switch (msg.message) { - case WM_ACTIVATEAPP: - if (msg.wParam == FALSE) { - // some other app activated. hide the hider window. - ShowWindow(m_window, SW_HIDE); - } - break; - } - - return false; -} - -void -CMSWindowsSecondaryScreen::onOneShotTimerExpired(UInt32) -{ - // ignore -} - -void -CMSWindowsSecondaryScreen::postCreateWindow(HWND window) -{ - m_window = window; - - // update key state - updateKeys(); - - // hide cursor if this screen isn't active - if (!isActive()) { - SInt32 x, y; - getScreen()->getCursorCenter(x, y); - showWindow(x, y); - } -} - -void -CMSWindowsSecondaryScreen::preDestroyWindow(HWND) -{ - // do nothing -} - -void -CMSWindowsSecondaryScreen::onAccessibleDesktop() -{ - // get the current keyboard state - updateKeys(); -} - -void -CMSWindowsSecondaryScreen::onPreMainLoop() -{ - assert(m_window != NULL); -} - -void -CMSWindowsSecondaryScreen::onPreOpen() -{ - assert(m_window == NULL); -} - -void -CMSWindowsSecondaryScreen::onPreEnter() -{ - assert(m_window != NULL); -} - -void -CMSWindowsSecondaryScreen::onPreLeave() -{ - assert(m_window != NULL); -} - -void -CMSWindowsSecondaryScreen::createWindow() -{ - // open the desktop and the window - m_window = m_screen->openDesktop(); - if (m_window == NULL) { - throw XScreenOpenFailure(); - } -} - -void -CMSWindowsSecondaryScreen::destroyWindow() -{ - // release keys that are logically pressed - releaseKeys(); - - // close the desktop and the window - m_screen->closeDesktop(); - m_window = NULL; -} - -void -CMSWindowsSecondaryScreen::showWindow(SInt32 x, SInt32 y) -{ - // move hider window under the given position - MoveWindow(m_window, x, y, 1, 1, FALSE); - - // raise and show the hider window - ShowWindow(m_window, SW_SHOWNA); - - // now warp the mouse - fakeMouseMove(x, y); -} - -void -CMSWindowsSecondaryScreen::hideWindow() -{ - ShowWindow(m_window, SW_HIDE); -} - -CSecondaryScreen::KeyState -CMSWindowsSecondaryScreen::getKeyState(UINT virtualKey) const -{ - BYTE sysState = static_cast(GetKeyState(virtualKey)); - KeyState state = 0; - if (sysState & 0x01u) { - state |= kToggled; - } - if (sysState & 0x80u) { - state |= kDown; - } - return state; -} - -void -CMSWindowsSecondaryScreen::updateKeys(KeyState* keys) -{ - // we only care about the modifier key states - keys[VK_LSHIFT] = getKeyState(VK_LSHIFT); - keys[VK_RSHIFT] = getKeyState(VK_RSHIFT); - keys[VK_SHIFT] = getKeyState(VK_SHIFT); - keys[VK_LCONTROL] = getKeyState(VK_LCONTROL); - keys[VK_RCONTROL] = getKeyState(VK_RCONTROL); - keys[VK_CONTROL] = getKeyState(VK_CONTROL); - keys[VK_LMENU] = getKeyState(VK_LMENU); - keys[VK_RMENU] = getKeyState(VK_RMENU); - keys[VK_MENU] = getKeyState(VK_MENU); - keys[VK_LWIN] = getKeyState(VK_LWIN); - keys[VK_RWIN] = getKeyState(VK_RWIN); - keys[VK_APPS] = getKeyState(VK_APPS); - keys[VK_CAPITAL] = getKeyState(VK_CAPITAL); - keys[VK_NUMLOCK] = getKeyState(VK_NUMLOCK); - keys[VK_SCROLL] = getKeyState(VK_SCROLL); -} - -KeyModifierMask -CMSWindowsSecondaryScreen::getModifiers() const -{ - // update active modifier mask - KeyModifierMask mask = 0; - if (isKeyDown(VK_LSHIFT) || isKeyDown(VK_RSHIFT)) { - mask |= KeyModifierShift; - } - if (isKeyDown(VK_LCONTROL) || isKeyDown(VK_RCONTROL)) { - mask |= KeyModifierControl; - } - if (isKeyDown(VK_LMENU) || isKeyDown(VK_RMENU)) { - mask |= KeyModifierAlt; - } - // note -- no keys for KeyModifierMeta - if (isKeyDown(VK_LWIN) || isKeyDown(VK_RWIN)) { - mask |= KeyModifierSuper; - } - if (isKeyToggled(VK_CAPITAL)) { - mask |= KeyModifierCapsLock; - } - if (isKeyToggled(VK_NUMLOCK)) { - mask |= KeyModifierNumLock; - } - if (isKeyToggled(VK_SCROLL)) { - mask |= KeyModifierScrollLock; - } - // note -- do not save KeyModifierModeSwitch in mask - return mask; -} - -// map special KeyID keys to virtual key codes. if the key is an -// extended key then the entry is the virtual key code | 0x100. -// unmapped keys have a 0 entry. -static const UINT g_mapE000[] = -{ - /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x40 */ 0, 0, 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, - /* 0xa4 */ 0, 0, VK_BROWSER_BACK|0x100, VK_BROWSER_FORWARD|0x100, - /* 0xa8 */ VK_BROWSER_REFRESH|0x100, VK_BROWSER_STOP|0x100, - /* 0xaa */ VK_BROWSER_SEARCH|0x100, VK_BROWSER_FAVORITES|0x100, - /* 0xac */ VK_BROWSER_HOME|0x100, VK_VOLUME_MUTE|0x100, - /* 0xae */ VK_VOLUME_DOWN|0x100, VK_VOLUME_UP|0x100, - /* 0xb0 */ VK_MEDIA_NEXT_TRACK|0x100, VK_MEDIA_PREV_TRACK|0x100, - /* 0xb2 */ VK_MEDIA_STOP|0x100, VK_MEDIA_PLAY_PAUSE|0x100, - /* 0xb4 */ VK_LAUNCH_MAIL|0x100, VK_LAUNCH_MEDIA_SELECT|0x100, - /* 0xb6 */ VK_LAUNCH_APP1|0x100, VK_LAUNCH_APP2|0x100, - /* 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 -}; -static const UINT g_mapEE00[] = -{ - /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x20 */ VK_TAB, 0, 0, 0, 0, 0, 0, 0, - /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x40 */ 0, 0, 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 -}; -static const UINT g_mapEF00[] = -{ - /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x08 */ VK_BACK, VK_TAB, 0, VK_CLEAR, 0, VK_RETURN, 0, 0, - /* 0x10 */ 0, 0, 0, VK_PAUSE, VK_SCROLL, 0/*sys-req*/, 0, 0, - /* 0x18 */ 0, 0, 0, VK_ESCAPE, 0, 0, 0, 0, - /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x50 */ VK_HOME|0x100, VK_LEFT|0x100, VK_UP|0x100, VK_RIGHT|0x100, - /* 0x54 */ VK_DOWN|0x100, VK_PRIOR|0x100, VK_NEXT|0x100, VK_END|0x100, - /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x60 */ VK_SELECT|0x100, VK_SNAPSHOT|0x100, VK_EXECUTE|0x100, VK_INSERT|0x100, - /* 0x64 */ 0, 0, 0, VK_APPS|0x100, - /* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0, - /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, VK_NUMLOCK|0x100, - /* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0, - /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN|0x100, 0, 0, - /* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP, - /* 0x98 */ VK_RIGHT, VK_DOWN, VK_PRIOR, VK_NEXT, - /* 0x9c */ VK_END, 0, VK_INSERT, VK_DELETE, - /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0xa8 */ 0, 0, VK_MULTIPLY, VK_ADD, - /* 0xac */ VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE|0x100, - /* 0xb0 */ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, - /* 0xb4 */ VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, - /* 0xb8 */ VK_NUMPAD8, VK_NUMPAD9, 0, 0, 0, 0, VK_F1, VK_F2, - /* 0xc0 */ VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, - /* 0xc8 */ VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, - /* 0xd0 */ VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, 0, 0, - /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, - /* 0xe4 */ VK_RCONTROL|0x100, VK_CAPITAL, 0, 0, - /* 0xe8 */ 0, VK_LMENU, VK_RMENU|0x100, VK_LWIN|0x100, - /* 0xec */ VK_RWIN|0x100, 0, 0, 0, - /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE|0x100 -}; - -DWORD -CMSWindowsSecondaryScreen::mapButton(ButtonID button, - bool press, DWORD* inData) const -{ - DWORD dummy; - DWORD* data = (inData != NULL) ? inData : &dummy; - - // 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 - *data = 0; - switch (button) { - case kButtonLeft: - return press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; - - case kButtonMiddle: - return press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; - - case kButtonRight: - return press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; - - case kButtonExtra0 + 0: - *data = XBUTTON1; - return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; - - case kButtonExtra0 + 1: - *data = XBUTTON2; - return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; - - default: - return 0; - } -} - -KeyModifierMask -CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, - SysKeyID& virtualKey, KeyID id, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, EKeyAction action) const -{ - virtualKey = 0; - - // check for special keys - if ((id & 0xfffff000) == 0xe000) { - if ((id & 0xff00) == 0xe000) { - virtualKey = g_mapE000[id & 0xff]; - } - else if ((id & 0xff00) == 0xee00) { - virtualKey = g_mapEE00[id & 0xff]; - } - else if ((id & 0xff00) == 0xef00) { - virtualKey = g_mapEF00[id & 0xff]; - } - if (virtualKey == 0) { - LOG((CLOG_DEBUG2 "unknown special key")); - return currentMask; - } - } - - // special handling of VK_SNAPSHOT - if ((virtualKey & 0xff) == VK_SNAPSHOT) { - // ignore key repeats on print screen - if (action != kRepeat) { - // get event flags - DWORD flags = 0; - if (isExtendedKey(virtualKey)) { - flags |= KEYEVENTF_EXTENDEDKEY; - } - if (action != kPress) { - flags |= KEYEVENTF_KEYUP; - } - - // active window or fullscreen? - BYTE scan = 0; - if ((desiredMask & KeyModifierAlt) == 0) { - scan = 1; - } - - // send event - keybd_event(static_cast(virtualKey & 0xff), scan, flags, 0); - } - return currentMask; - } - - // handle other special keys - if (virtualKey != 0) { - // compute the final desired modifier mask. special keys use - // the desired modifiers as given except we keep the caps lock, - // num lock, and scroll lock as is. - KeyModifierMask outMask = (currentMask & - (KeyModifierCapsLock | - KeyModifierNumLock | - KeyModifierScrollLock)); - outMask |= (desiredMask & - (KeyModifierShift | - KeyModifierControl | - KeyModifierAlt | - KeyModifierMeta | - KeyModifierSuper)); - - // strip out extended key flag - UINT virtualKey2 = (virtualKey & ~0x100); - - // check numeric keypad. note that virtual keys do not distinguish - // between the keypad and non-keypad movement keys. however, the - // virtual keys do distinguish between keypad numbers and operators - // (e.g. add, multiply) and their main keyboard counterparts. - // therefore, we can ignore the num-lock state for movement virtual - // keys but not for numeric keys. - if (virtualKey2 >= VK_NUMPAD0 && virtualKey2 <= VK_DIVIDE) { - // set required shift state based on current numlock state - if ((outMask & KeyModifierNumLock) == 0) { - if ((currentMask & KeyModifierNumLock) == 0) { - LOG((CLOG_DEBUG2 "turn on num lock for keypad key")); - outMask |= KeyModifierNumLock; - } - else { - LOG((CLOG_DEBUG2 "turn on shift for keypad key")); - outMask |= KeyModifierShift; - } - } - } - - // check for left tab. that requires the shift key. - if (id == kKeyLeftTab) { - // XXX -- this isn't used; outMask meant instead? - desiredMask |= KeyModifierShift; - } - - // now generate the keystrokes and return the resulting modifier mask - LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d mask 0x%04x", id, virtualKey2, outMask)); - return mapToKeystrokes(keys, virtualKey, currentMask, outMask, action); - } - - // determine the thread that'll receive this event - // FIXME -- we can't be sure we'll get the right thread here - HWND targetWindow = GetForegroundWindow(); - DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL); - - // figure out the code page for the target thread. i'm just - // guessing here. get the target thread's keyboard layout, - // extract the language id from that, and choose the code page - // based on that language. - HKL hkl = GetKeyboardLayout(targetThread); - LANGID langID = static_cast(LOWORD(hkl)); - UINT codePage = getCodePageFromLangID(langID); - LOG((CLOG_DEBUG2 "using code page %d and language id 0x%04x for thread 0x%08x", codePage, langID, targetThread)); - - // regular characters are complicated by dead keys. it may not be - // possible to generate a desired character directly. we may need - // to generate a dead key first then some other character. the - // app receiving the events will compose these two characters into - // a single precomposed character. - // - // as best as i can tell this is the simplest way to convert a - // character into its uncomposed version. along the way we'll - // discover if the key cannot be handled at all. we convert - // from wide char to multibyte, then from multibyte to wide char - // forcing conversion to composite characters, then from wide - // char back to multibyte without making precomposed characters. - // - // after the first conversion to multibyte we see if we can map - // the key. if so then we don't bother trying to decompose dead - // keys. - BOOL error; - char multiByte[2 * MB_LEN_MAX]; - wchar_t unicode[2]; - unicode[0] = static_cast(id & 0x0000ffff); - int nChars = WideCharToMultiByte(codePage, - WC_COMPOSITECHECK | WC_DEFAULTCHAR, - unicode, 1, - multiByte, sizeof(multiByte), - NULL, &error); - if (nChars == 0 || error) { - LOG((CLOG_DEBUG2 "KeyID 0x%08x not in code page", id)); - return currentMask; - } - virtualKey = mapCharacter(keys, multiByte[0], - hkl, currentMask, desiredMask, action); - if (virtualKey != static_cast(-1)) { - LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); - if ((MapVirtualKey(virtualKey, 2) & 0x80000000u) != 0) { - // it looks like this character is a dead key but - // MapVirtualKey() will claim it's a dead key even if it's - // not (though i don't think it ever claims it's not when - // it is). we need a backup test to ensure that this is - // really a dead key. we could use ToAscii() for this but - // that keeps state and it's a hassle to restore that state. - // OemKeyScan() appears to do the trick. if the character - // cannot be generated with a single keystroke then it - // returns 0xffffffff. - if (OemKeyScan(multiByte[0]) != 0xffffffffu) { - // character mapped to a dead key but we want the - // character for real so send a space key afterwards. - LOG((CLOG_DEBUG2 "character mapped to dead key")); - Keystroke keystroke; - keystroke.m_sysKeyID = VK_SPACE; - keystroke.m_press = true; - keystroke.m_repeat = false; - keys.push_back(keystroke); - keystroke.m_press = false; - keys.push_back(keystroke); - - // ignore the release of this key since we already - // handled it in mapCharacter(). - virtualKey = 0; - } - } - return currentMask; - } - nChars = MultiByteToWideChar(codePage, - MB_COMPOSITE | MB_ERR_INVALID_CHARS, - multiByte, nChars, - unicode, 2); - if (nChars == 0) { - LOG((CLOG_DEBUG2 "KeyID 0x%08x mb->wc mapping failed", id)); - return currentMask; - } - nChars = WideCharToMultiByte(codePage, - 0, - unicode, nChars, - multiByte, sizeof(multiByte), - NULL, &error); - if (nChars == 0 || error) { - LOG((CLOG_DEBUG2 "KeyID 0x%08x wc->mb mapping failed", id)); - return currentMask; - } - - // we expect one or two characters in multiByte. if there are two - // then the *second* is a dead key. process the dead key if there. - // FIXME -- we assume each character is one byte here - if (nChars > 2) { - LOG((CLOG_DEBUG2 "multibyte characters not supported for character 0x%04x", id)); - return currentMask; - } - if (nChars == 2) { - LOG((CLOG_DEBUG2 "KeyID 0x%08x needs dead key %u", id, (unsigned char)multiByte[1])); - mapCharacter(keys, multiByte[1], - hkl, currentMask, desiredMask, action); - } - - // process character - LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); - virtualKey = mapCharacter(keys, multiByte[0], - hkl, currentMask, desiredMask, action); - - // non-special key cannot modify the modifier mask - return currentMask; -} - -KeyModifierMask -CMSWindowsSecondaryScreen::getModifierKeyMask(SysKeyID virtualKey) const -{ - switch (virtualKey) { - case VK_CAPITAL: - return KeyModifierCapsLock; - - case VK_NUMLOCK: - return KeyModifierNumLock; - - case VK_SCROLL: - return KeyModifierScrollLock; - - default: - return 0; - } -} - -bool -CMSWindowsSecondaryScreen::isModifierActive(SysKeyID virtualKey) const -{ - // check if any virtual key for this modifier is down. return false - // for toggle modifiers. - const CModifierInfo* modifier = getModifierInfo(virtualKey); - if (modifier != NULL && !modifier->m_isToggle) { - if (isKeyDown(modifier->m_virtualKey & 0xff) || - isKeyDown(modifier->m_virtualKey2 & 0xff)) { - return true; - } - } - return false; -} - -UINT -CMSWindowsSecondaryScreen::mapCharacter(Keystrokes& keys, - char c, HKL hkl, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, EKeyAction action) const -{ - // translate the character into its virtual key and its required - // modifier state. - SHORT virtualKeyAndModifierState = VkKeyScanEx(c, hkl); - - // get virtual key - UINT virtualKey = LOBYTE(virtualKeyAndModifierState); - if (LOBYTE(virtualKeyAndModifierState) == 0xff) { - LOG((CLOG_DEBUG2 "cannot map character %d", static_cast(c))); - return static_cast(-1); - } - - // get the required modifier state - BYTE modifierState = HIBYTE(virtualKeyAndModifierState); - - // compute the final desired modifier mask. this is the - // desired modifier mask except that the system might require - // that certain modifiers be up or down in order to generate - // the character. to start with, we know that we want to keep - // the caps lock, num lock, scroll lock modifiers as is. also, - // the system never requires the meta or super modifiers so we - // can set those however we like. - KeyModifierMask outMask = (currentMask & - (KeyModifierCapsLock | - KeyModifierNumLock | - KeyModifierScrollLock)); - outMask |= (desiredMask & - (KeyModifierMeta | - KeyModifierSuper)); - - // win32 does not permit ctrl and alt used together to - // modify a character because ctrl and alt together mean - // AltGr. if the desired mask has both ctrl and alt then - // strip them both out. - if ((desiredMask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - desiredMask &= ~(KeyModifierControl | KeyModifierAlt); - } - - // strip out the desired shift state. we're forced to use - // a particular shift state to generate the desired character. - outMask &= ~KeyModifierShift; - - // use the required modifiers. if AltGr is required then - // modifierState will indicate control and alt. - if ((modifierState & 1) != 0) { - outMask |= KeyModifierShift; - } - if ((modifierState & 2) != 0) { - outMask |= KeyModifierControl; - } - if ((modifierState & 4) != 0) { - outMask |= KeyModifierAlt; - } - - // use desired modifiers - if ((desiredMask & KeyModifierControl) != 0) { - outMask |= KeyModifierControl; - } - if ((desiredMask & KeyModifierAlt) != 0) { - outMask |= KeyModifierAlt; - } - - // handle combination of caps-lock and shift. if caps-lock is - // off locally then use shift as necessary. if caps-lock is on - // locally then it reverses the meaning of shift for keys that - // are subject to case conversion. - if ((outMask & KeyModifierCapsLock) != 0) { - // there doesn't seem to be a simple way to test if a - // character respects the caps lock key. for normal - // characters it's easy enough but CharLower() and - // CharUpper() don't map dead keys even though they - // do respect caps lock for some unfathomable reason. - // first check the easy way. if that doesn't work - // then see if it's a dead key. - unsigned char uc = static_cast(c); - if (CharLower((LPTSTR)uc) != CharUpper((LPTSTR)uc) || - (MapVirtualKey(virtualKey, 2) & 0x80000000lu) != 0) { - LOG((CLOG_DEBUG2 "flip shift")); - outMask ^= KeyModifierShift; - } - } - - // now generate the keystrokes. ignore the resulting modifier - // mask since it can't have changed (because we don't call this - // method for modifier keys). - LOG((CLOG_DEBUG2 "character %d to virtual key %d mask 0x%04x", (unsigned char)c, virtualKey, outMask)); - mapToKeystrokes(keys, virtualKey, currentMask, outMask, action); - - return virtualKey; -} - -KeyModifierMask -CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, - UINT virtualKey, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, EKeyAction action) const -{ - const CModifierInfo* modifier = getModifierInfo(virtualKey); - - // add the key events required to get to the desired modifier state. - // also save the key events required to restore the current state. - // if the key is a modifier key then skip this because modifiers - // should not modify modifiers. - Keystrokes undo; - Keystroke keystroke; - if (desiredMask != currentMask && modifier == NULL) { - const unsigned int s_numModifiers = sizeof(s_modifier) / - sizeof(s_modifier[0]); - for (unsigned int i = 0; i < s_numModifiers; ++i) { - KeyModifierMask bit = s_modifier[i].m_mask; - if ((desiredMask & bit) != (currentMask & bit)) { - if ((desiredMask & bit) != 0) { - // modifier is not active but should be. if the - // modifier is a toggle then toggle it on with a - // press/release, otherwise activate it with a - // press. - keystroke.m_sysKeyID = s_modifier[i].m_virtualKey; - keystroke.m_press = true; - keystroke.m_repeat = false; - keys.push_back(keystroke); - if (s_modifier[i].m_isToggle) { - keystroke.m_press = false; - keys.push_back(keystroke); - undo.push_back(keystroke); - keystroke.m_press = true; - undo.push_back(keystroke); - } - else { - keystroke.m_press = false; - undo.push_back(keystroke); - } - } - - else { - // modifier is active but should not be. if the - // modifier is a toggle then toggle it off with a - // press/release, otherwise deactivate it with a - // release. we must check each keycode for the - // modifier if not a toggle. - if (s_modifier[i].m_isToggle) { - keystroke.m_sysKeyID = s_modifier[i].m_virtualKey; - keystroke.m_press = true; - keystroke.m_repeat = false; - keys.push_back(keystroke); - keystroke.m_press = false; - keys.push_back(keystroke); - undo.push_back(keystroke); - keystroke.m_press = true; - undo.push_back(keystroke); - } - else { - UINT key = s_modifier[i].m_virtualKey; - if (isKeyDown(key & 0xff)) { - keystroke.m_sysKeyID = key; - keystroke.m_press = false; - keystroke.m_repeat = false; - keys.push_back(keystroke); - keystroke.m_press = true; - undo.push_back(keystroke); - } - key = s_modifier[i].m_virtualKey2; - if (isKeyDown(key & 0xff)) { - keystroke.m_sysKeyID = key; - keystroke.m_press = false; - keystroke.m_repeat = false; - keys.push_back(keystroke); - keystroke.m_press = true; - undo.push_back(keystroke); - } - } - } - } - } - } - - // add the key event - keystroke.m_sysKeyID = virtualKey; - switch (action) { - case kPress: - keystroke.m_press = true; - keystroke.m_repeat = false; - keys.push_back(keystroke); - break; - - case kRelease: - keystroke.m_press = false; - keystroke.m_repeat = false; - keys.push_back(keystroke); - break; - - case kRepeat: - keystroke.m_press = true; - keystroke.m_repeat = true; - keys.push_back(keystroke); - break; - } - - // if this is a dead key press then send a release immediately. - // the dead key may not be processed correctly if its release - // event comes after we release the modifiers. - if (action == kPress && - (MapVirtualKey(virtualKey, 2) & 0x80000000lu) != 0) { - keystroke.m_press = false; - keys.push_back(keystroke); - } - - // add key events to restore the modifier state. apply events in - // the reverse order that they're stored in undo. - while (!undo.empty()) { - keys.push_back(undo.back()); - undo.pop_back(); - } - - // if the key is a modifier key then compute the modifier mask after - // this key is pressed. toggle keys modify the state on release. - // other keys set the modifier bit on press. - KeyModifierMask mask = currentMask; - if (action == kPress) { - if (modifier != NULL && !modifier->m_isToggle) { - mask |= modifier->m_mask; - } - } - - LOG((CLOG_DEBUG2 "previous modifiers 0x%04x, final modifiers 0x%04x", currentMask, mask)); - return mask; -} - - -const CMSWindowsSecondaryScreen::CModifierInfo* -CMSWindowsSecondaryScreen::getModifierInfo(UINT virtualKey) const -{ - // note if the key is a modifier. strip out extended key flag from - // virtual key before lookup. - switch (virtualKey & 0xffu) { - case VK_SHIFT: - case VK_LSHIFT: - case VK_RSHIFT: - return s_modifier + 0; - - case VK_CONTROL: - case VK_LCONTROL: - case VK_RCONTROL: - return s_modifier + 1; - - case VK_MENU: - case VK_LMENU: - case VK_RMENU: - return s_modifier + 2; - - case VK_LWIN: - case VK_RWIN: - return s_modifier + 3; - - case VK_CAPITAL: - return s_modifier + 4; - - case VK_NUMLOCK: - return s_modifier + 5; - - case VK_SCROLL: - return s_modifier + 6; - - default: - return NULL; - } -} - -CSecondaryScreen::SysKeyID -CMSWindowsSecondaryScreen::getToggleSysKey(KeyID keyID) const -{ - switch (keyID) { - case kKeyNumLock: - return VK_NUMLOCK | 0x100; - - case kKeyCapsLock: - return VK_CAPITAL; - - case kKeyScrollLock: - return VK_SCROLL; - - default: - return 0; - } -} - -UINT -CMSWindowsSecondaryScreen::virtualKeyToScanCode(UINT& virtualKey) const -{ - // try mapping given virtual key - UINT code = MapVirtualKey(virtualKey & 0xff, 0); - if (code != 0) { - return code; - } - - // no dice. if the virtual key distinguishes between left/right - // then try the one that doesn't distinguish sides. windows (or - // keyboard drivers) are inconsistent in their treatment of these - // virtual keys. the following behaviors have been observed: - // - // win2k (gateway desktop): - // MapVirtualKey(vk, 0): - // VK_SHIFT == VK_LSHIFT != VK_RSHIFT - // VK_CONTROL == VK_LCONTROL == VK_RCONTROL - // VK_MENU == VK_LMENU == VK_RMENU - // MapVirtualKey(sc, 3): - // VK_LSHIFT and VK_RSHIFT mapped independently - // VK_LCONTROL is mapped but not VK_RCONTROL - // VK_LMENU is mapped but not VK_RMENU - // - // win me (sony vaio laptop): - // MapVirtualKey(vk, 0): - // VK_SHIFT mapped; VK_LSHIFT, VK_RSHIFT not mapped - // VK_CONTROL mapped; VK_LCONTROL, VK_RCONTROL not mapped - // VK_MENU mapped; VK_LMENU, VK_RMENU not mapped - // MapVirtualKey(sc, 3): - // all scan codes unmapped (function apparently unimplemented) - switch (virtualKey & 0xff) { - case VK_LSHIFT: - case VK_RSHIFT: - virtualKey = VK_SHIFT; - return MapVirtualKey(VK_SHIFT, 0); - - case VK_LCONTROL: - case VK_RCONTROL: - virtualKey = VK_CONTROL; - return MapVirtualKey(VK_CONTROL, 0); - - case VK_LMENU: - case VK_RMENU: - virtualKey = VK_MENU; - return MapVirtualKey(VK_MENU, 0); - - default: - return 0; - } -} - -bool -CMSWindowsSecondaryScreen::isExtendedKey(UINT virtualKey) const -{ - // see if we've already encoded the extended flag - if ((virtualKey & 0x100) != 0) { - return true; - } - - // check known virtual keys - switch (virtualKey & 0xffu) { - case VK_NUMLOCK: - case VK_RCONTROL: - case VK_RMENU: - case VK_LWIN: - case VK_RWIN: - case VK_APPS: - return true; - - default: - return false; - } -} - -void -CMSWindowsSecondaryScreen::fakeKeyEvent(UINT virtualKey, bool press) const -{ - DWORD flags = 0; - if (isExtendedKey(virtualKey)) { - flags |= KEYEVENTF_EXTENDEDKEY; - } - if (!press) { - flags |= KEYEVENTF_KEYUP; - } - const UINT code = virtualKeyToScanCode(virtualKey); - keybd_event(static_cast(virtualKey & 0xff), - static_cast(code), flags, 0); -} - -void -CMSWindowsSecondaryScreen::fakeMouseButton(ButtonID button, bool press) const -{ - // map button id to button flag - DWORD data; - DWORD flags = mapButton(button, press, &data); - - // send event - if (flags != 0) { - mouse_event(flags, 0, 0, data, 0); - } -} - -void -CMSWindowsSecondaryScreen::fakeMouseMove(SInt32 x, SInt32 y) const -{ - // motion is simple (i.e. it's on the primary monitor) if there - // is only one monitor. - bool simple = !m_screen->isMultimon(); - 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)((65536.0 * x) / w), - (DWORD)((65536.0 * y) / h), - 0, 0); - } - - // windows 98 (and Me?) is 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 a 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. 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. - else { - // 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); - } - - // get current mouse position - POINT pos; - GetCursorPos(&pos); - - // move relative to mouse position - mouse_event(MOUSEEVENTF_MOVE, x - pos.x, y - pos.y, 0, 0); - - // restore mouse speed & acceleration - if (accelChanged) { - SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); - SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); - } - } -} - -void -CMSWindowsSecondaryScreen::fakeMouseWheel(SInt32 delta) -{ - mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0); -} - -UINT -CMSWindowsSecondaryScreen::getCodePageFromLangID(LANGID langid) const -{ - // construct a locale id from the language id - LCID lcid = MAKELCID(langid, SORT_DEFAULT); - - // get the ANSI code page for this locale - char data[6]; - if (GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE, data, 6) == 0) { - // can't get code page - LOG((CLOG_DEBUG1 "can't find code page for langid 0x%04x", langid)); - return CP_ACP; - } - - // convert stringified code page into a number - UINT codePage = static_cast(atoi(data)); - if (codePage == 0) { - // parse failed - LOG((CLOG_DEBUG1 "can't parse code page %s for langid 0x%04x", data, langid)); - return CP_ACP; - } - - return codePage; -} - -bool -CMSWindowsSecondaryScreen::synthesizeCtrlAltDel(EKeyAction action) -{ - // ignore except for key press - if (action != kPress) { - return true; - } - - 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, we - // 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( - &CMSWindowsSecondaryScreen::ctrlAltDelThread)); - cad.wait(); - } - else { - Keystrokes keys; - UINT virtualKey; - KeyID key = kKeyDelete; - KeyModifierMask mask = KeyModifierControl | KeyModifierAlt; - - // get the sequence of keys to simulate ctrl+alt+del - mapKey(keys, virtualKey, key, mask, kPress); - if (!keys.empty()) { - // generate key events - doKeystrokes(keys, 1); - } - } - return true; -} - -void -CMSWindowsSecondaryScreen::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())); - } -} diff --git a/lib/platform/CMSWindowsSecondaryScreen.h b/lib/platform/CMSWindowsSecondaryScreen.h deleted file mode 100644 index c402a9ba..00000000 --- a/lib/platform/CMSWindowsSecondaryScreen.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 CMSWINDOWSSECONDARYSCREEN_H -#define CMSWINDOWSSECONDARYSCREEN_H - -// ensure that we get SendInput() -#if _WIN32_WINNT <= 0x400 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x401 -#endif - -#include "CSecondaryScreen.h" -#include "IMSWindowsScreenEventHandler.h" -#include "CMutex.h" -#include "CString.h" - -class CMSWindowsScreen; -class IScreenReceiver; - -//! Microsoft windows secondary screen implementation -class CMSWindowsSecondaryScreen : - public CSecondaryScreen, public IMSWindowsScreenEventHandler { -public: - CMSWindowsSecondaryScreen(IScreenReceiver*); - virtual ~CMSWindowsSecondaryScreen(); - - // CSecondaryScreen overrides - virtual IScreen* getScreen() const; - - // IMSWindowsScreenEventHandler overrides - virtual void onScreensaver(bool activated); - virtual bool onPreDispatch(const CEvent* event); - virtual bool onEvent(CEvent* event); - virtual void onOneShotTimerExpired(UInt32 id); - virtual void postCreateWindow(HWND); - virtual void preDestroyWindow(HWND); - virtual void onAccessibleDesktop(); - -protected: - // CSecondaryScreen overrides - virtual void onPreMainLoop(); - virtual void onPreOpen(); - virtual void onPreEnter(); - virtual void onPreLeave(); - virtual void createWindow(); - virtual void destroyWindow(); - virtual void showWindow(SInt32 x, SInt32 y); - virtual void hideWindow(); - virtual void updateKeys(KeyState* sysKeyStates); - virtual KeyModifierMask getModifiers() const; - - virtual SysKeyID getUnhanded(SysKeyID) const; - virtual SysKeyID getOtherHanded(SysKeyID) const; - virtual bool isAutoRepeating(SysKeyID) const; - virtual KeyModifierMask getModifierKeyMask(SysKeyID) const; - virtual bool isModifierActive(SysKeyID) const; - virtual SysKeyID getToggleSysKey(KeyID keyID) const; - virtual bool synthesizeCtrlAltDel(EKeyAction); - virtual void sync() const; - virtual KeyModifierMask - mapKey(Keystrokes&, SysKeyID& sysKeyID, KeyID, - KeyModifierMask, KeyModifierMask, EKeyAction) const; - virtual void fakeKeyEvent(SysKeyID, bool press) const; - virtual void fakeMouseButton(ButtonID, bool press) const; - virtual void fakeMouseMove(SInt32 x, SInt32 y) const; - virtual void fakeMouseWheel(SInt32 delta) const; - -private: - class CModifierInfo { - public: - KeyModifierMask m_mask; - UINT m_virtualKey; - UINT m_virtualKey2; - bool m_isToggle; - }; - - // open/close desktop (for windows 95/98/me) - bool openDesktop(); - void closeDesktop(); - - // make desk the thread desktop (for windows NT/2000/XP) - bool switchDesktop(HDESK desk); - - // returns true iff there appear to be multiple monitors - bool isMultimon() const; - - // key and button queries and operations - DWORD mapButton(ButtonID button, - bool press, DWORD* data) const; - UINT mapCharacter(Keystrokes& keys, - char c, HKL hkl, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, - EKeyAction action) const; - KeyModifierMask mapToKeystrokes(Keystrokes& keys, - UINT virtualKey, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, - EKeyAction action) const; - const CModifierInfo* getModifierInfo(UINT virtualKey) const; - - KeyState getKeyState(UINT virtualKey) const; - UINT virtualKeyToScanCode(UINT& virtualKey) const; - bool isExtendedKey(UINT virtualKey) const; - - UINT getCodePageFromLangID(LANGID) const; - - // thread that generates fake ctrl+alt+del - static void ctrlAltDelThread(void*); - -private: - CMutex m_mutex; - CMSWindowsScreen* m_screen; - - // true if windows 95/98/me - bool m_is95Family; - - // our window - HWND m_window; - - // modifier table - static const CModifierInfo s_modifier[]; -}; - -#endif diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp index 904d46bd..33022070 100644 --- a/lib/platform/CSynergyHook.cpp +++ b/lib/platform/CSynergyHook.cpp @@ -82,7 +82,7 @@ static HANDLE g_hookEventLL = NULL; static HHOOK g_keyboardLL = NULL; static HHOOK g_mouseLL = NULL; static bool g_screenSaver = false; -static bool g_relay = false; +static EHookMode g_mode = kHOOK_WATCH_JUMP_ZONE; static UInt32 g_zoneSides = 0; static SInt32 g_zoneSize = 0; static SInt32 g_xScreen = 0; @@ -151,7 +151,7 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // the scroll lock toggle state. PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); - if (g_relay) { + if (g_mode == kHOOK_RELAY_EVENTS) { // let certain keys pass through switch (wParam) { case VK_CAPITAL: @@ -202,18 +202,18 @@ mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) case WM_NCXBUTTONUP: // always relay the event. eat it if relaying. PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, data); - return g_relay; + return (g_mode == kHOOK_RELAY_EVENTS); case WM_MOUSEWHEEL: - if (g_relay) { + if (g_mode == kHOOK_RELAY_EVENTS) { // relay event PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, data, 0); } - return g_relay; + return (g_mode == kHOOK_RELAY_EVENTS); case WM_NCMOUSEMOVE: case WM_MOUSEMOVE: - if (g_relay) { + if (g_mode == kHOOK_RELAY_EVENTS) { // we want the cursor to be hidden at all times so we // hide the cursor on whatever window has it. but then // we have to show the cursor whenever we leave that @@ -329,7 +329,7 @@ getMessageHook(int code, WPARAM wParam, LPARAM lParam) SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); } } - if (g_relay) { + if (g_mode == kHOOK_RELAY_EVENTS) { MSG* msg = reinterpret_cast(lParam); if (msg->message == g_wmMouseWheel) { // post message to our window @@ -614,7 +614,7 @@ init(DWORD threadID) g_threadID = threadID; // set defaults - g_relay = false; + g_mode = kHOOK_WATCH_JUMP_ZONE; g_zoneSides = 0; g_zoneSize = 0; g_xScreen = 0; @@ -827,14 +827,14 @@ setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize) } void -setRelay(int enable) +setMode(EHookMode mode) { - if ((enable != 0) == g_relay) { + if (mode == g_mode) { // no change return; } - g_relay = (enable != 0); - if (!g_relay) { + g_mode = mode; + if (g_mode != kHOOK_RELAY_EVENTS) { restoreCursor(); } } diff --git a/lib/platform/CSynergyHook.h b/lib/platform/CSynergyHook.h index 03e72fa7..6953fd70 100644 --- a/lib/platform/CSynergyHook.h +++ b/lib/platform/CSynergyHook.h @@ -49,6 +49,11 @@ enum EHookResult { kHOOK_OKAY_LL }; +enum EHookMode { + kHOOK_WATCH_JUMP_ZONE, + kHOOK_RELAY_EVENTS +}; + typedef int (*InitFunc)(DWORD targetQueueThreadID); typedef int (*CleanupFunc)(void); typedef EHookResult (*InstallFunc)(void); @@ -57,7 +62,7 @@ typedef int (*InstallScreenSaverFunc)(void); typedef int (*UninstallScreenSaverFunc)(void); typedef void (*SetSidesFunc)(UInt32); typedef void (*SetZoneFunc)(SInt32, SInt32, SInt32, SInt32, SInt32); -typedef void (*SetRelayFunc)(int); +typedef void (*SetModeFunc)(int); CSYNERGYHOOK_API int init(DWORD); CSYNERGYHOOK_API int cleanup(void); @@ -68,7 +73,7 @@ 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 setRelay(int enable); // relay iff enable != 0 +CSYNERGYHOOK_API void setMode(EHookMode mode); } diff --git a/lib/platform/CXWindowsKeyMapper.cpp b/lib/platform/CXWindowsKeyMapper.cpp new file mode 100644 index 00000000..80ecbc37 --- /dev/null +++ b/lib/platform/CXWindowsKeyMapper.cpp @@ -0,0 +1,910 @@ +/* + * 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 "CXWindowsKeyMapper.h" +#include "CXWindowsUtil.h" +#include "CLog.h" +#if defined(X_DISPLAY_MISSING) +# error X11 is required to build synergy +#else +# include +# include +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include +# if defined(HAVE_X11_XF86KEYSYM_H) +# include +# endif +# if !defined(XF86XK_Launch0) +# define XF86XK_Launch0 0x1008FF40 +# endif +# if !defined(XF86XK_Launch1) +# define XF86XK_Launch1 0x1008FF41 +# endif +#endif + +// map special KeyID keys to KeySyms +#if defined(HAVE_X11_XF86KEYSYM_H) +static const KeySym g_mapE000[] = +{ + /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ 0, 0, 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, + /* 0xa4 */ 0, 0, + /* 0xa6 */ XF86XK_Back, XF86XK_Forward, + /* 0xa8 */ XF86XK_Refresh, XF86XK_Stop, + /* 0xaa */ XF86XK_Search, XF86XK_Favorites, + /* 0xac */ XF86XK_HomePage, XF86XK_AudioMute, + /* 0xae */ XF86XK_AudioLowerVolume, XF86XK_AudioRaiseVolume, + /* 0xb0 */ XF86XK_AudioNext, XF86XK_AudioPrev, + /* 0xb2 */ XF86XK_AudioStop, XF86XK_AudioPlay, + /* 0xb4 */ XF86XK_Mail, XF86XK_AudioMedia, + /* 0xb6 */ XF86XK_Launch0, XF86XK_Launch1, + /* 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 +}; +#endif + +CXWindowsKeyMapper::CXWindowsKeyMapper() +{ + // do nothing +} + +CXWindowsKeyMapper::~CXWindowsKeyMapper() +{ + // do nothing +} + +void +CXWindowsKeyMapper::update(Display* display, IKeyState* keyState) +{ + // query which keys are pressed + char keys[32]; + XQueryKeymap(display, keys); + + // query the pointer to get the keyboard state + Window root = DefaultRootWindow(display), window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (!XQueryPointer(display, root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + state = 0; + } + + // update mappings + updateKeysymMap(display, keyState); + updateModifiers(); + + // transfer to our state + for (UInt32 i = 0, j = 0; i < 32; j += 8, ++i) { + if ((keys[i] & 0x01) != 0) + keyState->setKeyDown(j + 0); + if ((keys[i] & 0x02) != 0) + keyState->setKeyDown(j + 1); + if ((keys[i] & 0x04) != 0) + keyState->setKeyDown(j + 2); + if ((keys[i] & 0x08) != 0) + keyState->setKeyDown(j + 3); + if ((keys[i] & 0x10) != 0) + keyState->setKeyDown(j + 4); + if ((keys[i] & 0x20) != 0) + keyState->setKeyDown(j + 5); + if ((keys[i] & 0x40) != 0) + keyState->setKeyDown(j + 6); + if ((keys[i] & 0x80) != 0) + keyState->setKeyDown(j + 7); + } + + // set toggle modifier states + if ((state & LockMask) != 0) + keyState->setToggled(KeyModifierCapsLock); + if ((state & m_numLockMask) != 0) + keyState->setToggled(KeyModifierNumLock); + if ((state & m_scrollLockMask) != 0) + keyState->setToggled(KeyModifierScrollLock); +} + +KeyButton +CXWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + // the system translates key events into characters depending + // on the modifier key state at the time of the event. to + // generate the right keysym we need to set the modifier key + // states appropriately. + // + // desiredMask is the mask desired by the caller. however, there + // may not be a keycode mapping to generate the desired keysym + // with that mask. we override the bits in the mask that cannot + // be accomodated. + + // convert KeyID to a KeySym + KeySym keysym = keyIDToKeySym(id, desiredMask); + if (keysym == NoSymbol) { + // unknown key + return 0; + } + + // get the mapping for this keysym + KeySymIndex keyIndex = m_keysymMap.find(keysym); + + // if the mapping isn't found and keysym is caps lock sensitive + // then convert the case of the keysym and try again. + if (keyIndex == m_keysymMap.end()) { + KeySym lKey, uKey; + XConvertCase(keysym, &lKey, &uKey); + if (lKey != uKey) { + if (lKey == keysym) { + keyIndex = m_keysymMap.find(uKey); + } + else { + keyIndex = m_keysymMap.find(lKey); + } + } + } + + if (keyIndex != m_keysymMap.end()) { + // the keysym is mapped to some keycode. create the keystrokes + // for this keysym. + return mapToKeystrokes(keys, keyState, keyIndex, isAutoRepeat); + } + + // we can't find the keysym mapped to any keycode. this doesn't + // necessarily mean we can't generate the keysym, though. if the + // keysym can be created by combining keysyms then we may still + // be okay. + CXWindowsUtil::KeySyms decomposition; + if (!CXWindowsUtil::decomposeKeySym(keysym, decomposition)) { + return 0; + } + LOG((CLOG_DEBUG2 "decomposed keysym 0x%08x into %d keysyms", keysym, decomposition.size())); + + // map each decomposed keysym to keystrokes. we want the mask + // and the keycode from the last keysym (which should be the + // only non-dead key). the dead keys are not sensitive to + // anything but shift and mode switch. + KeyButton keycode = 0; + for (CXWindowsUtil::KeySyms::const_iterator i = decomposition.begin(); + i != decomposition.end(); ++i) { + // lookup the key + keysym = *i; + keyIndex = m_keysymMap.find(keysym); + if (keyIndex == m_keysymMap.end()) { + // missing a required keysym + return 0; + } + + // the keysym is mapped to some keycode + keycode = mapToKeystrokes(keys, keyState, keyIndex, isAutoRepeat); + if (keycode == 0) { + return 0; + } + } + + return keycode; +} + +KeyModifierMask +CXWindowsKeyMapper::mapModifier(unsigned int state) const +{ + KeyModifierMask mask = 0; + if (state & ShiftMask) + mask |= KeyModifierShift; + if (state & LockMask) + mask |= KeyModifierCapsLock; + if (state & ControlMask) + mask |= KeyModifierControl; + if (state & m_altMask) + mask |= KeyModifierAlt; + if (state & m_metaMask) + mask |= KeyModifierMeta; + if (state & m_superMask) + mask |= KeyModifierSuper; + if (state & m_modeSwitchMask) + mask |= KeyModifierModeSwitch; + if (state & m_numLockMask) + mask |= KeyModifierNumLock; + if (state & m_scrollLockMask) + mask |= KeyModifierScrollLock; + return mask; +} + +void +CXWindowsKeyMapper::updateKeysymMap(Display* display, IKeyState* keyState) +{ + // there are up to 4 keysyms per keycode + static const unsigned int maxKeysyms = 4; + + // get the number of keycodes + int minKeycode, maxKeycode; + XDisplayKeycodes(display, &minKeycode, &maxKeycode); + const int numKeycodes = maxKeycode - minKeycode + 1; + + // get the keyboard mapping for all keys + int keysymsPerKeycode; + KeySym* keysyms = XGetKeyboardMapping(display, + minKeycode, numKeycodes, + &keysymsPerKeycode); + + // we only understand up to maxKeysyms keysyms per keycodes + unsigned int numKeysyms = keysymsPerKeycode; + if (numKeysyms > maxKeysyms) { + numKeysyms = maxKeysyms; + } + + // determine shift and mode switch sensitivity. a keysym is shift + // or mode switch sensitive if its keycode is. a keycode is mode + // mode switch sensitive if it has keysyms for indices 2 or 3. + // it's shift sensitive if the keysym for index 1 (if any) is + // different from the keysym for index 0 and, if the keysym for + // for index 3 (if any) is different from the keysym for index 2. + // that is, if shift changes the generated keysym for the keycode. + std::vector usesShift(numKeycodes); + std::vector usesModeSwitch(numKeycodes); + for (int i = 0; i < numKeycodes; ++i) { + // check mode switch first + if (numKeysyms > 2 && + keysyms[i * keysymsPerKeycode + 2] != NoSymbol || + keysyms[i * keysymsPerKeycode + 3] != NoSymbol) { + usesModeSwitch[i] = true; + } + + // check index 0 with index 1 keysyms + if (keysyms[i * keysymsPerKeycode + 0] != NoSymbol && + keysyms[i * keysymsPerKeycode + 1] != NoSymbol && + keysyms[i * keysymsPerKeycode + 1] != + keysyms[i * keysymsPerKeycode + 0]) { + usesShift[i] = true; + } + + else if (numKeysyms >= 4 && + keysyms[i * keysymsPerKeycode + 2] != NoSymbol && + keysyms[i * keysymsPerKeycode + 3] != NoSymbol && + keysyms[i * keysymsPerKeycode + 3] != + keysyms[i * keysymsPerKeycode + 2]) { + usesShift[i] = true; + } + } + + // get modifier map from server + XModifierKeymap* modifiers = XGetModifierMapping(display); + int keysPerModifier = modifiers->max_keypermod; + + // clear state + m_keysymMap.clear(); + m_modeSwitchKeysym = NoSymbol; + m_altMask = 0; + m_metaMask = 0; + m_superMask = 0; + m_modeSwitchMask = 0; + m_numLockMask = 0; + m_scrollLockMask = 0; + + // work around for my system, which reports this state bit when + // mode switch is down, instead of the appropriate modifier bit. + // should have no effect on other systems. -crs 9/02. + m_modeSwitchMask |= (1 << 13); + + // for each modifier keycode, get the index 0 keycode and add it to + // the keysym map. also collect all keycodes for each modifier. + for (unsigned int i = 0; i < 8; ++i) { + // no keycodes for this modifier yet + bool hasKeycode = false; + KeyModifierMask mask = 0; + IKeyState::KeyButtons modifierKeys; + + // add each keycode for modifier + for (unsigned int j = 0; j < keysPerModifier; ++j) { + // get keycode and ignore unset keycodes + KeyCode keycode = modifiers->modifiermap[i * keysPerModifier + j]; + if (keycode == 0) { + continue; + } + + // get keysym and get/create key mapping + const int keycodeIndex = keycode - minKeycode; + const KeySym keysym = keysyms[keycodeIndex * + keysymsPerKeycode + 0]; + + // get modifier mask if we haven't yet. this has the side + // effect of setting the m_*Mask members. + if (mask == 0) { + mask = mapToModifierMask(i, keysym); + if (mask == 0) { + continue; + } + } + + // save keycode for modifier + modifierKeys.push_back(keycode); + + // skip if we already have a keycode for this index + KeyMapping& mapping = m_keysymMap[keysym]; + if (mapping.m_keycode[0] != 0) { + continue; + } + + // fill in keysym info + mapping.m_keycode[0] = keycode; + mapping.m_shiftSensitive[0] = usesShift[keycodeIndex]; + mapping.m_modeSwitchSensitive[0] = usesModeSwitch[keycodeIndex]; + mapping.m_modifierMask = mask; + mapping.m_capsLockSensitive = false; + mapping.m_numLockSensitive = false; + } + + // tell keyState about this modifier + if (mask != 0 && keyState != NULL) { + keyState->addModifier(mask, modifierKeys); + } + } + + // create a convenient NoSymbol entry (if it doesn't exist yet). + // sometimes it's useful to handle NoSymbol like a normal keysym. + // remove any entry for NoSymbol. that keysym doesn't count. + { + KeyMapping& mapping = m_keysymMap[NoSymbol]; + for (unsigned int i = 0; i < numKeysyms; ++i) { + mapping.m_keycode[i] = 0; + mapping.m_shiftSensitive[i] = false; + mapping.m_modeSwitchSensitive[i] = false; + } + mapping.m_modifierMask = 0; + mapping.m_capsLockSensitive = false; + mapping.m_numLockSensitive = false; + } + + // add each keysym to the map, unless we've already inserted a key + // for that keysym index. + for (int i = 0; i < numKeycodes; ++i) { + for (unsigned int j = 0; j < numKeysyms; ++j) { + // lookup keysym + const KeySym keysym = keysyms[i * keysymsPerKeycode + j]; + if (keysym == NoSymbol) { + continue; + } + KeyMapping& mapping = m_keysymMap[keysym]; + + // skip if we already have a keycode for this index + if (mapping.m_keycode[j] != 0) { + continue; + } + + // fill in keysym info + if (mapping.m_keycode[0] == 0) { + mapping.m_modifierMask = 0; + } + mapping.m_keycode[j] = static_cast( + minKeycode + i); + mapping.m_shiftSensitive[j] = usesShift[i]; + mapping.m_modeSwitchSensitive[j] = usesModeSwitch[i]; + mapping.m_numLockSensitive = isNumLockSensitive(keysym); + mapping.m_capsLockSensitive = isCapsLockSensitive(keysym); + } + } + + // clean up + XFreeModifiermap(modifiers); + XFree(keysyms); +} + +KeyModifierMask +CXWindowsKeyMapper::mapToModifierMask(unsigned int i, KeySym keysym) +{ + // some modifier indices (0,1,2) are dedicated to particular uses, + // the rest depend on the keysyms bound. + switch (i) { + case 0: + return KeyModifierShift; + + case 1: + return KeyModifierCapsLock; + + case 2: + return KeyModifierControl; + + default: + switch (keysym) { + case XK_Shift_L: + case XK_Shift_R: + return KeyModifierShift; + + case XK_Control_L: + case XK_Control_R: + return KeyModifierControl; + + case XK_Alt_L: + case XK_Alt_R: + m_altMask = (1 << i); + return KeyModifierAlt; + + case XK_Meta_L: + case XK_Meta_R: + m_metaMask = (1 << i); + return KeyModifierMeta; + + case XK_Super_L: + case XK_Super_R: + m_superMask = (1 << i); + return KeyModifierSuper; + + case XK_Mode_switch: + m_modeSwitchMask = (1 << i); + return KeyModifierModeSwitch; + + case XK_Caps_Lock: + return KeyModifierCapsLock; + + case XK_Num_Lock: + m_numLockMask = (1 << i); + return KeyModifierNumLock; + + case XK_Scroll_Lock: + m_scrollLockMask = (1 << i); + return KeyModifierScrollLock; + + default: + return 0; + } + } +} + +void +CXWindowsKeyMapper::updateModifiers() +{ + struct CModifierBitInfo { + public: + KeySym CXWindowsKeyMapper::*m_keysym; + KeySym m_left; + KeySym m_right; + }; + static const CModifierBitInfo s_modifierBitTable[] = { + { &CXWindowsKeyMapper::m_modeSwitchKeysym, XK_Mode_switch, NoSymbol }, + }; + + // choose the keysym to use for some modifiers. if a modifier has + // both left and right versions then (arbitrarily) prefer the left. + for (size_t i = 0; i < sizeof(s_modifierBitTable) / + sizeof(s_modifierBitTable[0]); ++i) { + const CModifierBitInfo& info = s_modifierBitTable[i]; + + // find available keysym + KeySymIndex keyIndex = m_keysymMap.find(info.m_left); + if (keyIndex == m_keysymMap.end() && info.m_right != NoSymbol) { + keyIndex = m_keysymMap.find(info.m_right); + } + + // save modifier info + if (keyIndex != m_keysymMap.end() && + keyIndex->second.m_modifierMask != 0) { + this->*(info.m_keysym) = keyIndex->first; + } + } + + // if there's no mode switch key mapped then remove all keycodes + // that depend on it and no keycode can be mode switch sensitive. + if (m_modeSwitchKeysym == NoSymbol) { + LOG((CLOG_DEBUG2 "no mode switch in keymap")); + for (KeySymMap::iterator i = m_keysymMap.begin(); + i != m_keysymMap.end(); ) { + i->second.m_keycode[2] = 0; + i->second.m_keycode[3] = 0; + i->second.m_modeSwitchSensitive[0] = false; + i->second.m_modeSwitchSensitive[1] = false; + i->second.m_modeSwitchSensitive[2] = false; + i->second.m_modeSwitchSensitive[3] = false; + + // if this keysym no has no keycodes then remove it + // except for the NoSymbol keysym mapping. + if (i->second.m_keycode[0] == 0 && i->second.m_keycode[1] == 0) { + m_keysymMap.erase(i++); + } + else { + ++i; + } + } + } +} + +KeySym +CXWindowsKeyMapper::keyIDToKeySym(KeyID id, KeyModifierMask mask) const +{ + // convert id to keysym + KeySym keysym = NoSymbol; + if ((id & 0xfffff000) == 0xe000) { + // special character + switch (id & 0x0000ff00) { +#if defined(HAVE_X11_XF86KEYSYM_H) + case 0xe000: + return g_mapE000[id & 0xff]; +#endif + + case 0xee00: + // ISO 9995 Function and Modifier Keys + if (id == kKeyLeftTab) { + keysym = XK_ISO_Left_Tab; + } + break; + + case 0xef00: + // MISCELLANY + keysym = static_cast(id - 0xef00 + 0xff00); + break; + } + } + else if ((id >= 0x0020 && id <= 0x007e) || + (id >= 0x00a0 && id <= 0x00ff)) { + // Latin-1 maps directly + return static_cast(id); + } + else { + // lookup keysym in table + return CXWindowsUtil::mapUCS4ToKeySym(id); + } + + // fail if unknown key + if (keysym == NoSymbol) { + return keysym; + } + + // if kKeyTab is requested with shift active then try XK_ISO_Left_Tab + // instead. if that doesn't work, we'll fall back to XK_Tab with + // shift active. this is to handle primary screens that don't map + // XK_ISO_Left_Tab sending events to secondary screens that do. + if (keysym == XK_Tab && (mask & KeyModifierShift) != 0) { + keysym = XK_ISO_Left_Tab; + } + + // some keysyms have emergency backups (particularly the numpad + // keys since most laptops don't have a separate numpad and the + // numpad overlaying the main keyboard may not have movement + // key bindings). figure out the emergency backup. + KeySym backupKeysym; + switch (keysym) { + case XK_KP_Home: + backupKeysym = XK_Home; + break; + + case XK_KP_Left: + backupKeysym = XK_Left; + break; + + case XK_KP_Up: + backupKeysym = XK_Up; + break; + + case XK_KP_Right: + backupKeysym = XK_Right; + break; + + case XK_KP_Down: + backupKeysym = XK_Down; + break; + + case XK_KP_Prior: + backupKeysym = XK_Prior; + break; + + case XK_KP_Next: + backupKeysym = XK_Next; + break; + + case XK_KP_End: + backupKeysym = XK_End; + break; + + case XK_KP_Insert: + backupKeysym = XK_Insert; + break; + + case XK_KP_Delete: + backupKeysym = XK_Delete; + break; + + case XK_ISO_Left_Tab: + backupKeysym = XK_Tab; + break; + + default: + backupKeysym = keysym; + break; + } + + // see if the keysym is assigned to any keycode. if not and the + // backup keysym is then use the backup keysym. + if (backupKeysym != keysym && + m_keysymMap.find(keysym) == m_keysymMap.end() && + m_keysymMap.find(backupKeysym) != m_keysymMap.end()) { + keysym = backupKeysym; + } + + return keysym; +} + +KeyButton +CXWindowsKeyMapper::mapToKeystrokes(IKeyState::Keystrokes& keys, + const IKeyState& keyState, + KeySymIndex keyIndex, + bool isAutoRepeat) const +{ + // keyIndex must be valid + assert(keyIndex != m_keysymMap.end()); + + KeyModifierMask currentMask = keyState.getActiveModifiers(); + + // get the keysym we're trying to generate and possible keycodes + const KeySym keysym = keyIndex->first; + const KeyMapping& mapping = keyIndex->second; + LOG((CLOG_DEBUG2 "keysym = 0x%08x", keysym)); + + // get the best keycode index for the keysym and modifiers. note + // that (bestIndex & 1) == 0 if the keycode is a shift modifier + // and (bestIndex & 2) == 0 if the keycode is a mode switch + // modifier. this is important later because we don't want + // adjustModifiers() to adjust a modifier if that's the key we're + // mapping. + unsigned int bestIndex = findBestKeyIndex(keyIndex, currentMask); + + // get the keycode + KeyButton keycode = mapping.m_keycode[bestIndex]; + + // flip low bit of bestIndex if shift is inverted. if there's a + // keycode for this new index then use it. otherwise use the old + // keycode. you'd think we should fail if there isn't a keycode + // for the new index but some keymaps only include the upper case + // keysyms (notably those on Sun Solaris) so to handle the missing + // lower case keysyms we just use the old keycode. note that + // isShiftInverted() will always return false for a shift modifier. + if (isShiftInverted(keyIndex, currentMask)) { + LOG((CLOG_DEBUG2 "shift is inverted")); + bestIndex ^= 1; + if (mapping.m_keycode[bestIndex] != 0) { + keycode = mapping.m_keycode[bestIndex]; + } + } + LOG((CLOG_DEBUG2 "bestIndex = %d, keycode = %d", bestIndex, keycode)); + + // if this for auto-repeat and this key does not auto-repeat + // then return 0. + if (isAutoRepeat && + (m_keyControl.auto_repeats[keycode >> 3] & + static_cast(1 << (keycode & 7))) == 0) { + return 0; + } + + // compute desired mask. the desired mask is the one that matches + // bestIndex, except if the key being synthesized is a shift key + // where we desire what we already have or if it's the mode switch + // key where we only desire to adjust shift. also, if the keycode + // is not sensitive to shift then don't adjust it, otherwise + // something like shift+home would become just home. similiarly + // for mode switch. + KeyModifierMask desiredMask = currentMask; + if (keyIndex->second.m_modifierMask != KeyModifierShift) { + if (keyIndex->second.m_shiftSensitive[bestIndex]) { + if ((bestIndex & 1) != 0) { + desiredMask |= KeyModifierShift; + } + else { + desiredMask &= ~KeyModifierShift; + } + } + if (keyIndex->second.m_modifierMask != KeyModifierModeSwitch) { + if (keyIndex->second.m_modeSwitchSensitive[bestIndex]) { + if ((bestIndex & 2) != 0) { + desiredMask |= KeyModifierModeSwitch; + } + else { + desiredMask &= ~KeyModifierModeSwitch; + } + } + } + } + + // adjust the modifiers to match the desired modifiers + IKeyState::Keystrokes undo; + if (!adjustModifiers(keys, undo, keyState, desiredMask)) { + LOG((CLOG_DEBUG2 "failed to adjust modifiers")); + return 0; + } + + // add the key event + IKeyState::Keystroke keystroke; + keystroke.m_key = keycode; + if (!isAutoRepeat) { + keystroke.m_press = true; + keystroke.m_repeat = false; + keys.push_back(keystroke); + } + else { + keystroke.m_press = false; + keystroke.m_repeat = true; + keys.push_back(keystroke); + keystroke.m_press = true; + keys.push_back(keystroke); + } + + // put undo keystrokes at end of keystrokes in reverse order + while (!undo.empty()) { + keys.push_back(undo.back()); + undo.pop_back(); + } + + return keycode; +} + +unsigned int +CXWindowsKeyMapper::findBestKeyIndex(KeySymIndex keyIndex, + KeyModifierMask /*currentMask*/) const +{ + // there are up to 4 keycodes per keysym to choose from. the + // best choice is the one that requires the fewest adjustments + // to the modifier state. for example, the letter A normally + // requires shift + a. if shift isn't already down we'd have + // to synthesize a shift press before the a press. however, + // if A could also be created with some other keycode without + // shift then we'd prefer that when shift wasn't down. + // + // if the action is an auto-repeat then we don't call this + // method since we just need to synthesize a key repeat on the + // same keycode that we pressed. + // XXX -- do this right + for (unsigned int i = 0; i < 4; ++i) { + if (keyIndex->second.m_keycode[i] != 0) { + return i; + } + } + + assert(0 && "no keycode found for keysym"); + return 0; +} + +bool +CXWindowsKeyMapper::isShiftInverted(KeySymIndex keyIndex, + KeyModifierMask currentMask) const +{ + // each keycode has up to 4 keysym associated with it, one each for: + // no modifiers, shift, mode switch, and shift and mode switch. if + // a keysym is modified by num lock and num lock is active then you + // get the shifted keysym when shift is not down and the unshifted + // keysym when it is. that is, num lock inverts the sense of the + // shift modifier when active. similarly for caps lock. this + // method returns true iff the sense of shift should be inverted + // for this key given a modifier state. + if (keyIndex->second.m_numLockSensitive) { + if ((currentMask & KeyModifierNumLock) != 0) { + return true; + } + } + + // if a keysym is num lock sensitive it is never caps lock + // sensitive, thus the else here. + else if (keyIndex->second.m_capsLockSensitive) { + if ((currentMask & KeyModifierCapsLock) != 0) { + return true; + } + } + + return false; +} + +bool +CXWindowsKeyMapper::adjustModifiers(IKeyState::Keystrokes& keys, + IKeyState::Keystrokes& undo, + const IKeyState& keyState, + KeyModifierMask desiredMask) const +{ + KeyModifierMask currentMask = keyState.getActiveModifiers(); + + // get mode switch set correctly. do this before shift because + // mode switch may be sensitive to the shift modifier and will + // set/reset it as necessary. + const bool wantModeSwitch = ((desiredMask & KeyModifierModeSwitch) != 0); + const bool haveModeSwitch = ((currentMask & KeyModifierModeSwitch) != 0); + if (wantModeSwitch != haveModeSwitch) { + LOG((CLOG_DEBUG2 "fix mode switch")); + + // adjust shift if necessary + KeySymIndex modeSwitchIndex = m_keysymMap.find(m_modeSwitchKeysym); + assert(modeSwitchIndex != m_keysymMap.end()); + if (modeSwitchIndex->second.m_shiftSensitive[0]) { + const bool wantShift = false; + const bool haveShift = ((currentMask & KeyModifierShift) != 0); + if (wantShift != haveShift) { + // add shift keystrokes + LOG((CLOG_DEBUG2 "fix shift for mode switch")); + if (!keyState.mapModifier(keys, undo, + KeyModifierShift, wantShift)) { + return false; + } + currentMask ^= KeyModifierShift; + } + } + + // add mode switch keystrokes + if (!keyState.mapModifier(keys, undo, + KeyModifierModeSwitch, wantModeSwitch)) { + return false; + } + currentMask ^= KeyModifierModeSwitch; + } + + // get shift set correctly + const bool wantShift = ((desiredMask & KeyModifierShift) != 0); + const bool haveShift = ((currentMask & KeyModifierShift) != 0); + if (wantShift != haveShift) { + // add shift keystrokes + LOG((CLOG_DEBUG2 "fix shift")); + if (!keyState.mapModifier(keys, undo, KeyModifierShift, wantShift)) { + return false; + } + currentMask ^= KeyModifierShift; + } + + return true; +} + +bool +CXWindowsKeyMapper::isNumLockSensitive(KeySym keysym) const +{ + return (IsKeypadKey(keysym) || IsPrivateKeypadKey(keysym)); +} + +bool +CXWindowsKeyMapper::isCapsLockSensitive(KeySym keysym) const +{ + KeySym lKey, uKey; + XConvertCase(keysym, &lKey, &uKey); + return (lKey != uKey); +} + + +// +// CXWindowsKeyMapper::KeyMapping +// + +CXWindowsKeyMapper::KeyMapping::KeyMapping() +{ + m_keycode[0] = 0; + m_keycode[1] = 0; + m_keycode[2] = 0; + m_keycode[3] = 0; +} diff --git a/lib/platform/CXWindowsKeyMapper.h b/lib/platform/CXWindowsKeyMapper.h new file mode 100644 index 00000000..0c707aa5 --- /dev/null +++ b/lib/platform/CXWindowsKeyMapper.h @@ -0,0 +1,153 @@ +/* + * 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 CXWINDOWSKEYMAPPER_H +#define CXWINDOWSKEYMAPPER_H + +#include "IKeyState.h" +#include "stdmap.h" +#if defined(X_DISPLAY_MISSING) +# error X11 is required to build synergy +#else +# include +#endif + +//! X Windows key mapper +/*! +This class maps KeyIDs to keystrokes. +*/ +class CXWindowsKeyMapper { +public: + CXWindowsKeyMapper(); + ~CXWindowsKeyMapper(); + + //! @name manipulators + //@{ + + //! Update key mapper + /*! + Updates the key mapper's internal tables according to the display's + current keyboard mapping and updates \c keyState. + */ + void update(Display*, IKeyState* keyState); + + //@} + //! @name accessors + //@{ + + //! Map key press/repeat to keystrokes + /*! + Converts a press/repeat of key \c id with the modifiers as given + in \c desiredMask into the keystrokes necessary to synthesize + that key event. Returns the platform specific code of the key + being pressed, or 0 if the key cannot be mapped or \c isAutoRepeat + is true and the key does not auto-repeat. + */ + KeyButton mapKey(IKeyState::Keystrokes&, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + //! Convert X modifier mask to synergy mask + /*! + Returns the synergy modifier mask corresponding to the given X + modifier mask. + */ + KeyModifierMask mapModifier(unsigned int state) const; + + //@} + +private: + class KeyMapping { + public: + KeyMapping(); + + public: + // KeyCode to generate keysym and whether keycode[i] is + // sensitive to shift and mode switch. + KeyCode m_keycode[4]; + bool m_shiftSensitive[4]; + bool m_modeSwitchSensitive[4]; + + // the modifier mask of keysym or 0 if not a modifier + KeyModifierMask m_modifierMask; + + // whether keysym is sensitive to caps and num lock + bool m_numLockSensitive; + bool m_capsLockSensitive; + }; + typedef std::map KeySymMap; + typedef KeySymMap::const_iterator KeySymIndex; + + // save the current keyboard mapping and note the currently + // pressed keys in \c keyState. + void updateKeysymMap(Display* display, IKeyState* keyState); + + // note interesting modifier KeySyms + void updateModifiers(); + + // map a modifier index and its KeySym to a modifier mask. also + // save the modifier mask in one of m_*Mask. + KeyModifierMask mapToModifierMask(unsigned int, KeySym); + + // convert a KeyID to a KeySym + KeySym keyIDToKeySym(KeyID id, KeyModifierMask mask) const; + + // map a KeySym into the keystrokes to produce it + KeyButton mapToKeystrokes(IKeyState::Keystrokes& keys, + const IKeyState& keyState, + KeySymIndex keyIndex, + bool isAutoRepeat) const; + + // choose the best set of modifiers to generate the KeySym + unsigned int findBestKeyIndex(KeySymIndex keyIndex, + KeyModifierMask currentMask) const; + + // returns true if the sense of shift is inverted for KeySym + bool isShiftInverted(KeySymIndex keyIndex, + KeyModifierMask currentMask) const; + + // returns the keystrokes to adjust the modifiers into the desired + // state the keystrokes to get back to the current state. + bool adjustModifiers(IKeyState::Keystrokes& keys, + IKeyState::Keystrokes& undo, + const IKeyState& keyState, + KeyModifierMask desiredMask) const; + + // returns true if keysym is sensitive to the NumLock state + bool isNumLockSensitive(KeySym keysym) const; + + // returns true if keysym is sensitive to the CapsLock state + bool isCapsLockSensitive(KeySym keysym) const; + +private: + // keysym to keycode mapping + KeySymMap m_keysymMap; + + // the keyboard control state the last time this screen was entered + XKeyboardState m_keyControl; + + // modifier keysyms + KeySym m_modeSwitchKeysym; + + // modifier masks + unsigned int m_altMask; + unsigned int m_metaMask; + unsigned int m_superMask; + unsigned int m_modeSwitchMask; + unsigned int m_numLockMask; + unsigned int m_scrollLockMask; +}; + +#endif diff --git a/lib/platform/CXWindowsPrimaryScreen.cpp b/lib/platform/CXWindowsPrimaryScreen.cpp deleted file mode 100644 index 360bea85..00000000 --- a/lib/platform/CXWindowsPrimaryScreen.cpp +++ /dev/null @@ -1,1044 +0,0 @@ -/* - * 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 "CXWindowsPrimaryScreen.h" -#include "CXWindowsScreen.h" -#include "CXWindowsUtil.h" -#include "IPrimaryScreenReceiver.h" -#include "XScreen.h" -#include "CThread.h" -#include "CLog.h" -#include "CStopwatch.h" -#if defined(X_DISPLAY_MISSING) -# error X11 is required to build synergy -#else -# include -# include -# define XK_MISCELLANY -# define XK_XKB_KEYS -# include -#endif -#include "CArch.h" - -// -// CXWindowsPrimaryScreen -// - -CXWindowsPrimaryScreen::CXWindowsPrimaryScreen( - IScreenReceiver* receiver, - IPrimaryScreenReceiver* primaryReceiver) : - CPrimaryScreen(receiver), - m_receiver(primaryReceiver), - m_window(None), - m_im(NULL), - m_ic(NULL), - m_lastKeycode(0) -{ - m_screen = new CXWindowsScreen(receiver, this); -} - -CXWindowsPrimaryScreen::~CXWindowsPrimaryScreen() -{ - assert(m_window == None); - delete m_screen; -} - -void -CXWindowsPrimaryScreen::reconfigure(UInt32) -{ - // do nothing -} - -void -CXWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) -{ - CDisplayLock display(m_screen); - - // warp mouse - warpCursorNoFlush(display, x, y); - - // remove all input events before and including warp - XEvent event; - while (XCheckMaskEvent(display, PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | - KeyPressMask | KeyReleaseMask | - KeymapStateMask, - &event)) { - // do nothing - } - - // save position as last position - m_x = x; - m_y = y; -} - -void -CXWindowsPrimaryScreen::resetOptions() -{ - m_numLockHalfDuplex = false; - m_capsLockHalfDuplex = false; -} - -void -CXWindowsPrimaryScreen::setOptions(const COptionsList& options) -{ - for (UInt32 i = 0, n = options.size(); i < n; i += 2) { - if (options[i] == kOptionHalfDuplexCapsLock) { - m_capsLockHalfDuplex = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off")); - } - else if (options[i] == kOptionHalfDuplexNumLock) { - m_numLockHalfDuplex = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off")); - } - } -} - -UInt32 -CXWindowsPrimaryScreen::addOneShotTimer(double timeout) -{ - return m_screen->addOneShotTimer(timeout); -} - -KeyModifierMask -CXWindowsPrimaryScreen::getToggleMask() const -{ - CDisplayLock display(m_screen); - - // query the pointer to get the keyboard state - Window root, window; - int xRoot, yRoot, xWindow, yWindow; - unsigned int state; - if (!XQueryPointer(display, m_window, &root, &window, - &xRoot, &yRoot, &xWindow, &yWindow, &state)) { - return 0; - } - - // convert to KeyModifierMask - KeyModifierMask mask = 0; - if (state & m_numLockMask) { - mask |= KeyModifierNumLock; - } - if (state & m_capsLockMask) { - mask |= KeyModifierCapsLock; - } - if (state & m_scrollLockMask) { - mask |= KeyModifierScrollLock; - } - - return mask; -} - -bool -CXWindowsPrimaryScreen::isLockedToScreen() const -{ - CDisplayLock display(m_screen); - - // query the pointer to get the button state - Window root, window; - int xRoot, yRoot, xWindow, yWindow; - unsigned int state; - if (XQueryPointer(display, m_window, &root, &window, - &xRoot, &yRoot, &xWindow, &yWindow, &state)) { - if ((state & (Button1Mask | Button2Mask | Button3Mask | - Button4Mask | Button5Mask)) != 0) { - LOG((CLOG_DEBUG "locked by mouse button")); - return true; - } - } - - // get logical keyboard state - char keyMap[32]; - memset(keyMap, 0, sizeof(keyMap)); - XQueryKeymap(display, keyMap); - - // locked if any key is down - for (unsigned int i = 0; i < sizeof(keyMap); ++i) { - if (keyMap[i] != 0) { - for (unsigned int j = 0; j < 8; ++j) { - if ((keyMap[i] & (1 << j)) != 0) { - const KeyCode keycode = 8 * i + j; - const KeySym keysym = XKeycodeToKeysym(display, - keycode, 0); - // if any key is half-duplex then it'll be down when - // toggled on but shouldn't count as a reason to lock - // to the screen. - if (m_numLockHalfDuplex && keysym == XK_Num_Lock) { - continue; - } - if (m_capsLockHalfDuplex && keysym == XK_Caps_Lock) { - continue; - } - - // non-half-duplex key down - char* name = XKeysymToString(keysym); - if (name == NULL) { - LOG((CLOG_DEBUG "locked by keycode %d", keycode)); - } - else { - LOG((CLOG_DEBUG "locked by \"%s\"", name)); - } - return true; - } - } - } - } - - // not locked - return false; -} - -IScreen* -CXWindowsPrimaryScreen::getScreen() const -{ - return m_screen; -} - -void -CXWindowsPrimaryScreen::onScreensaver(bool activated) -{ - m_receiver->onScreensaver(activated); -} - -bool -CXWindowsPrimaryScreen::onPreDispatch(const CEvent*) -{ - return false; -} - -bool -CXWindowsPrimaryScreen::onEvent(CEvent* event) -{ - assert(event != NULL); - XEvent& xevent = event->m_event; - - // 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)) { - return true; - } - } - - // handle event - switch (xevent.type) { - case CreateNotify: - { - // select events on new window - CDisplayLock display(m_screen); - selectEvents(display, xevent.xcreatewindow.window); - } - return true; - - case MappingNotify: - // keyboard mapping changed - updateKeys(); - return true; - - case KeyPress: - { - LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); - const KeyModifierMask mask = mapModifier(xevent.xkey.state); - KeyID key = mapKey(&xevent.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() above - // for more info. - KeyCode keycode = xevent.xkey.keycode; - if (keycode == 0) { - keycode = m_lastKeycode; - } - - // handle key - m_receiver->onKeyDown(key, mask, - static_cast(keycode)); - if (key == kKeyCapsLock && m_capsLockHalfDuplex) { - m_receiver->onKeyUp(key, mask | KeyModifierCapsLock, - static_cast(keycode)); - } - else if (key == kKeyNumLock && m_numLockHalfDuplex) { - m_receiver->onKeyUp(key, mask | KeyModifierNumLock, - static_cast(keycode)); - } - } - } - return true; - - case KeyRelease: - { - const KeyModifierMask mask = mapModifier(xevent.xkey.state); - KeyID key = mapKey(&xevent.xkey); - if (key != kKeyNone) { - // 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. - CKeyEventInfo filter; - filter.m_event = KeyPress; - filter.m_window = xevent.xkey.window; - filter.m_time = xevent.xkey.time; - filter.m_keycode = xevent.xkey.keycode; - - // now check for event - bool hasPress; - { - XEvent xevent2; - CDisplayLock display(m_screen); - hasPress = (XCheckIfEvent(display, &xevent2, - &CXWindowsPrimaryScreen::findKeyEvent, - (XPointer)&filter) == True); - } - - // 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; - hasPress = false; - } - - - if (!hasPress) { - // no press event follows so it's a plain release - LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); - if (key == kKeyCapsLock && m_capsLockHalfDuplex) { - m_receiver->onKeyDown(key, mask, - static_cast(xevent.xkey.keycode)); - } - else if (key == kKeyNumLock && m_numLockHalfDuplex) { - m_receiver->onKeyDown(key, mask, - static_cast(xevent.xkey.keycode)); - } - m_receiver->onKeyUp(key, mask, - static_cast(xevent.xkey.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", xevent.xkey.keycode, xevent.xkey.state)); - m_receiver->onKeyRepeat(key, mask, 1, - static_cast(xevent.xkey.keycode)); - } - } - } - return true; - - case ButtonPress: - { - LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xevent.xbutton.button)); - const ButtonID button = mapButton(xevent.xbutton.button); - if (button != kButtonNone) { - m_receiver->onMouseDown(button); - } - } - return true; - - case ButtonRelease: - { - LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xevent.xbutton.button)); - const ButtonID button = mapButton(xevent.xbutton.button); - if (button != kButtonNone) { - m_receiver->onMouseUp(button); - } - else if (xevent.xbutton.button == 4) { - // wheel forward (away from user) - m_receiver->onMouseWheel(120); - } - else if (xevent.xbutton.button == 5) { - // wheel backward (toward user) - m_receiver->onMouseWheel(-120); - } - } - return true; - - case MotionNotify: - { - LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xevent.xmotion.x_root, xevent.xmotion.y_root)); - - // compute motion delta (relative to the last known - // mouse position) - SInt32 x = xevent.xmotion.x_root - m_x; - SInt32 y = xevent.xmotion.y_root - m_y; - - // save position to compute delta of next motion - m_x = xevent.xmotion.x_root; - m_y = xevent.xmotion.y_root; - - if (xevent.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. - CDisplayLock display(m_screen); - do { - XMaskEvent(display, PointerMotionMask, &xevent); - } while (!xevent.xmotion.send_event); - } - else if (!isActive()) { - // motion on primary screen - m_receiver->onMouseMovePrimary(m_x, m_y); - } - 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 (xevent.xmotion.x_root - m_xCenter < -s_size || - xevent.xmotion.x_root - m_xCenter > s_size || - xevent.xmotion.y_root - m_yCenter < -s_size || - xevent.xmotion.y_root - m_yCenter > s_size) { - CDisplayLock display(m_screen); - warpCursorNoFlush(display, 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) { - m_receiver->onMouseMoveSecondary(x, y); - } - } - } - return true; - } - - return false; -} - -void -CXWindowsPrimaryScreen::onOneShotTimerExpired(UInt32 id) -{ - m_receiver->onOneShotTimerExpired(id); -} - -SInt32 -CXWindowsPrimaryScreen::getJumpZoneSize() const -{ - return 1; -} - -void -CXWindowsPrimaryScreen::onPreMainLoop() -{ - assert(m_window != None); -} - -void -CXWindowsPrimaryScreen::onPreOpen() -{ - assert(m_window == None); -} - -void -CXWindowsPrimaryScreen::onPostOpen() -{ - assert(m_window != None); - - // get cursor info - m_screen->getCursorPos(m_x, m_y); - m_screen->getCursorCenter(m_xCenter, m_yCenter); - - // get the input method - CDisplayLock display(m_screen); - m_im = XOpenIM(display, NULL, NULL, NULL); - if (m_im == NULL) { - return; - } - - // find the appropriate style. synergy supports XIMPreeditNothing - // only at the moment. - XIMStyles* styles; - if (XGetIMValues(m_im, XNQueryInputStyle, &styles, NULL) != NULL || - styles == NULL) { - LOG((CLOG_WARN "cannot get IM styles")); - 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_WARN "no supported IM styles")); - return; - } - - // create an input context for the style and tell it about our window - m_ic = XCreateIC(m_im, XNInputStyle, style, XNClientWindow, m_window, NULL); - if (m_ic == NULL) { - LOG((CLOG_WARN "cannot create IC")); - return; - } - - // find out the events we must select for and do so - unsigned long mask; - if (XGetICValues(m_ic, XNFilterEvents, &mask, NULL) != NULL) { - LOG((CLOG_WARN "cannot get IC filter events")); - return; - } - XWindowAttributes attr; - XGetWindowAttributes(display, m_window, &attr); - XSelectInput(display, m_window, attr.your_event_mask | mask); - - // no previous keycode - m_lastKeycode = 0; -} - -void -CXWindowsPrimaryScreen::onPreClose() -{ - CDisplayLock display(m_screen); - if (m_ic != NULL) { - XDestroyIC(m_ic); - m_ic = NULL; - } - if (m_im != NULL) { - XCloseIM(m_im); - m_im = NULL; - } - m_lastKeycode = 0; -} - -void -CXWindowsPrimaryScreen::onPreEnter() -{ - assert(m_window != None); - - if (m_ic != NULL) { - XUnsetICFocus(m_ic); - } -} - -void -CXWindowsPrimaryScreen::onPreLeave() -{ - assert(m_window != None); - - if (m_ic != NULL) { - XmbResetIC(m_ic); - XSetICFocus(m_ic); - } -} - -void -CXWindowsPrimaryScreen::onEnterScreenSaver() -{ - CDisplayLock display(m_screen); - - // set keyboard focus to root window. the screensaver should then - // pick up key events for when the user enters a password to unlock. - XSetInputFocus(display, PointerRoot, PointerRoot, CurrentTime); -} - -void -CXWindowsPrimaryScreen::createWindow() -{ - assert(m_window == None); - - // get size of screen - SInt32 x, y, w, h; - m_screen->getShape(x, y, w, h); - - // grab window attributes. this window is used to capture user - // input when the user is focused on another client. don't let - // the window manager mess with it. - XSetWindowAttributes attr; - attr.event_mask = PointerMotionMask | - ButtonPressMask | ButtonReleaseMask | - KeyPressMask | KeyReleaseMask | - KeymapStateMask | PropertyChangeMask; - attr.do_not_propagate_mask = 0; - attr.override_redirect = True; - attr.cursor = m_screen->getBlankCursor(); - - { - // create the grab window - CDisplayLock display(m_screen); - m_window = XCreateWindow(display, m_screen->getRoot(), - x, y, w, h, 0, 0, - InputOnly, CopyFromParent, - CWDontPropagate | CWEventMask | - CWOverrideRedirect | CWCursor, - &attr); - if (m_window == None) { - throw XScreenOpenFailure(); - } - LOG((CLOG_DEBUG "window is 0x%08x", m_window)); - - // start watching for events on other windows - selectEvents(display, m_screen->getRoot()); - } - - // tell generic screen about the window - m_screen->setWindow(m_window); -} - -void -CXWindowsPrimaryScreen::destroyWindow() -{ - // display can be NULL if the server unexpectedly disconnected - if (m_window != None) { - m_screen->setWindow(None); - CDisplayLock display(m_screen); - if (display != NULL) { - XDestroyWindow(display, m_window); - } - m_window = None; - } -} - -bool -CXWindowsPrimaryScreen::showWindow() -{ - assert(m_window != None); - - CDisplayLock display(m_screen); - - // raise and show the input window - XMapRaised(display, m_window); - - // 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(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")); - XUnmapWindow(display, m_window); - return false; - } - } - } while (result != GrabSuccess); - LOG((CLOG_DEBUG2 "grabbed keyboard")); - - // now the mouse - result = XGrabPointer(display, m_window, True, 0, - GrabModeAsync, GrabModeAsync, - m_window, None, CurrentTime); - assert(result != GrabNotViewable); - if (result != GrabSuccess) { - // back off to avoid grab deadlock - XUngrabKeyboard(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")); - XUnmapWindow(display, m_window); - return false; - } - } - } while (result != GrabSuccess); - LOG((CLOG_DEBUG1 "grabbed pointer and keyboard")); - - return true; -} - -void -CXWindowsPrimaryScreen::hideWindow() -{ - CDisplayLock display(m_screen); - - // unmap the grab window. this also ungrabs the mouse and keyboard. - XUnmapWindow(display, m_window); -} - -void -CXWindowsPrimaryScreen::warpCursorToCenter() -{ - warpCursor(m_xCenter, m_yCenter); -} - -void -CXWindowsPrimaryScreen::warpCursorNoFlush( - Display* display, SInt32 x, SInt32 y) -{ - assert(display != NULL); - assert(m_window != None); - - // send an event that we can recognize before the mouse warp - XEvent eventBefore; - eventBefore.type = MotionNotify; - eventBefore.xmotion.display = display; - eventBefore.xmotion.window = m_window; - eventBefore.xmotion.root = m_screen->getRoot(); - 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(display, m_window, False, 0, &eventBefore); - - // warp mouse - XWarpPointer(display, None, m_screen->getRoot(), 0, 0, 0, 0, x, y); - - // send an event that we can recognize after the mouse warp - XSendEvent(display, m_window, False, 0, &eventAfter); - XSync(display, False); - - LOG((CLOG_DEBUG2 "warped to %d,%d", x, y)); -} - -void -CXWindowsPrimaryScreen::selectEvents(Display* display, 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(display); - - // adjust event masks - doSelectEvents(display, w); -} - -void -CXWindowsPrimaryScreen::doSelectEvents(Display* display, 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(display, w, PointerMotionMask | SubstructureNotifyMask); - - // recurse on child windows - Window rw, pw, *cw; - unsigned int nc; - if (XQueryTree(display, w, &rw, &pw, &cw, &nc)) { - for (unsigned int i = 0; i < nc; ++i) { - doSelectEvents(display, cw[i]); - } - XFree(cw); - } -} - -KeyModifierMask -CXWindowsPrimaryScreen::mapModifier(unsigned int state) const -{ - KeyModifierMask mask = 0; - if (state & ShiftMask) - mask |= KeyModifierShift; - if (state & LockMask) - mask |= KeyModifierCapsLock; - if (state & ControlMask) - mask |= KeyModifierControl; - if (state & m_altMask) - mask |= KeyModifierAlt; - if (state & m_metaMask) - mask |= KeyModifierMeta; - if (state & m_superMask) - mask |= KeyModifierSuper; - if (state & m_modeSwitchMask) - mask |= KeyModifierModeSwitch; - if (state & m_numLockMask) - mask |= KeyModifierNumLock; - if (state & m_scrollLockMask) - mask |= KeyModifierScrollLock; - return mask; -} - -// map "Internet" keys to KeyIDs -static const KeySym g_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, 0, 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 -}; - -KeyID -CXWindowsPrimaryScreen::mapKey(XKeyEvent* event) const -{ - CDisplayLock display(m_screen); - - // 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 - switch (keysym & 0xffffff00) { - case 0x0000: - // Latin-1 - return static_cast(keysym); - - case 0xfe00: - // ISO 9995 Function and Modifier Keys - if (keysym == XK_ISO_Left_Tab) { - return kKeyLeftTab; - } - return kKeyNone; - - case 0xff00: - // MISCELLANY - return static_cast(keysym - 0xff00 + 0xef00); - - case 0x1008ff00: - // "Internet" keys - return g_map1008FF[keysym & 0xff]; - - default: { - // lookup character in table - UInt32 key = CXWindowsUtil::mapKeySymToUCS4(keysym); - if (key != 0x0000ffff) { - return static_cast(key); - } - - // unknown character - return kKeyNone; - } - } -} - -ButtonID -CXWindowsPrimaryScreen::mapButton(unsigned int button) const -{ - // 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; - } -} - -void -CXWindowsPrimaryScreen::updateKeys() -{ - CDisplayLock display(m_screen); - - // get modifier map from server - XModifierKeymap* keymap = XGetModifierMapping(display); - - // initialize - m_altMask = 0; - m_metaMask = 0; - m_superMask = 0; - m_modeSwitchMask = 0; - m_numLockMask = 0; - m_capsLockMask = 0; - m_scrollLockMask = 0; - - // work around for my system, which reports this state bit when - // mode switch is down, instead of the appropriate modifier bit. - // should have no effect on other systems. -crs 9/02. - m_modeSwitchMask |= (1 << 13); - - // set keycodes and masks - for (unsigned int i = 0; i < 8; ++i) { - const unsigned int bit = (1 << i); - for (int j = 0; j < keymap->max_keypermod; ++j) { - KeyCode keycode = keymap->modifiermap[i * - keymap->max_keypermod + j]; - - // note mask for particular modifiers - const KeySym keysym = XKeycodeToKeysym(display, keycode, 0); - switch (keysym) { - case XK_Alt_L: - case XK_Alt_R: - m_altMask |= bit; - break; - - case XK_Meta_L: - case XK_Meta_R: - m_metaMask |= bit; - break; - - case XK_Super_L: - case XK_Super_R: - m_superMask |= bit; - break; - - case XK_Mode_switch: - m_modeSwitchMask |= bit; - break; - - case XK_Num_Lock: - m_numLockMask |= bit; - break; - - case XK_Caps_Lock: - m_capsLockMask |= bit; - break; - - case XK_Scroll_Lock: - m_scrollLockMask |= bit; - break; - } - } - } - - XFreeModifiermap(keymap); -} - -Bool -CXWindowsPrimaryScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg) -{ - CKeyEventInfo* 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; -} diff --git a/lib/platform/CXWindowsPrimaryScreen.h b/lib/platform/CXWindowsPrimaryScreen.h deleted file mode 100644 index 2343ca07..00000000 --- a/lib/platform/CXWindowsPrimaryScreen.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 CXWINDOWSPRIMARYSCREEN_H -#define CXWINDOWSPRIMARYSCREEN_H - -#include "CPrimaryScreen.h" -#include "IScreenEventHandler.h" -#include "MouseTypes.h" -#if defined(X_DISPLAY_MISSING) -# error X11 is required to build synergy -#else -# include -#endif - -class CXWindowsScreen; -class IScreenReceiver; -class IPrimaryScreenReceiver; - -//! X11 primary screen implementation -class CXWindowsPrimaryScreen : - public CPrimaryScreen, public IScreenEventHandler { -public: - CXWindowsPrimaryScreen(IScreenReceiver*, IPrimaryScreenReceiver*); - virtual ~CXWindowsPrimaryScreen(); - - // CPrimaryScreen overrides - virtual void reconfigure(UInt32 activeSides); - virtual void warpCursor(SInt32 x, SInt32 y); - virtual void resetOptions(); - virtual void setOptions(const COptionsList& options); - virtual UInt32 addOneShotTimer(double timeout); - virtual KeyModifierMask getToggleMask() const; - virtual bool isLockedToScreen() const; - virtual IScreen* getScreen() const; - - // IScreenEventHandler overrides - virtual void onScreensaver(bool activated); - virtual bool onPreDispatch(const CEvent* event); - virtual bool onEvent(CEvent* event); - virtual void onOneShotTimerExpired(UInt32 id); - virtual SInt32 getJumpZoneSize() const; - -protected: - // CPrimaryScreen overrides - virtual void onPreMainLoop(); - virtual void onPreOpen(); - virtual void onPostOpen(); - virtual void onPreClose(); - virtual void onPreEnter(); - virtual void onPreLeave(); - virtual void onEnterScreenSaver(); - - virtual void createWindow(); - virtual void destroyWindow(); - virtual bool showWindow(); - virtual void hideWindow(); - virtual void warpCursorToCenter(); - - virtual void updateKeys(); - -private: - void warpCursorNoFlush(Display*, - SInt32 xAbsolute, SInt32 yAbsolute); - - void selectEvents(Display*, Window) const; - void doSelectEvents(Display*, Window) const; - - KeyModifierMask mapModifier(unsigned int state) const; - KeyID mapKey(XKeyEvent*) const; - ButtonID mapButton(unsigned int button) const; - - class CKeyEventInfo { - public: - int m_event; - Window m_window; - Time m_time; - KeyCode m_keycode; - }; - static Bool findKeyEvent(Display*, XEvent* xevent, XPointer arg); - -private: - CXWindowsScreen* m_screen; - IPrimaryScreenReceiver* m_receiver; - - // our window - Window m_window; - - // note toggle keys that toggle on up/down (false) or on - // transition (true) - bool m_numLockHalfDuplex; - bool m_capsLockHalfDuplex; - - // modifier masks - unsigned int m_altMask; - unsigned int m_metaMask; - unsigned int m_superMask; - unsigned int m_modeSwitchMask; - unsigned int m_numLockMask; - unsigned int m_capsLockMask; - unsigned int m_scrollLockMask; - - // last mouse position - SInt32 m_x, m_y; - - // position of center pixel of screen - SInt32 m_xCenter, m_yCenter; - - // input method stuff - XIM m_im; - XIC m_ic; - KeyCode m_lastKeycode; -}; - -#endif diff --git a/lib/platform/CXWindowsScreen.cpp b/lib/platform/CXWindowsScreen.cpp index 870da6dc..2dd1949d 100644 --- a/lib/platform/CXWindowsScreen.cpp +++ b/lib/platform/CXWindowsScreen.cpp @@ -17,12 +17,14 @@ #include "CXWindowsScreenSaver.h" #include "CXWindowsUtil.h" #include "CClipboard.h" -#include "IScreenEventHandler.h" #include "IScreenReceiver.h" +#include "IPrimaryScreenReceiver.h" #include "XScreen.h" #include "CLock.h" #include "CThread.h" #include "CLog.h" +#include "CStopwatch.h" +#include "CStringUtil.h" #include "IJob.h" #include #if defined(X_DISPLAY_MISSING) @@ -30,6 +32,13 @@ #else # include # include +# define XK_XKB_KEYS +# include +# if defined(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" { @@ -55,58 +64,45 @@ # endif # endif #endif +#include "CArch.h" -// -// CXWindowsScreen::CTimer -// - -CXWindowsScreen::CTimer::CTimer(IJob* job, double startTime, double resetTime) : - m_job(job), - m_timeout(resetTime), - m_time(resetTime), - m_startTime(startTime) +// map "Internet" keys to KeyIDs +static const KeySym g_map1008FF[] = { - assert(m_timeout > 0.0); -} - -CXWindowsScreen::CTimer::~CTimer() -{ - // do nothing -} - -void -CXWindowsScreen::CTimer::run() -{ - if (m_job != NULL) { - m_job->run(); - } -} - -void -CXWindowsScreen::CTimer::reset() -{ - m_time = m_timeout; - m_startTime = 0.0; -} - -CXWindowsScreen::CTimer::CTimer& -CXWindowsScreen::CTimer::operator-=(double dt) -{ - m_time -= dt - m_startTime; - m_startTime = 0.0; - return *this; -} - -CXWindowsScreen::CTimer::operator double() const -{ - return m_time; -} - -bool -CXWindowsScreen::CTimer::operator<(const CTimer& t) const -{ - return m_time < t.m_time; -} + /* 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, 0, 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 +}; // @@ -116,24 +112,32 @@ CXWindowsScreen::CTimer::operator<(const CTimer& t) const CXWindowsScreen* CXWindowsScreen::s_screen = NULL; CXWindowsScreen::CXWindowsScreen(IScreenReceiver* receiver, - IScreenEventHandler* eventHandler) : + IPrimaryScreenReceiver* primaryReceiver) : + m_isPrimary(primaryReceiver != NULL), m_display(NULL), m_root(None), - m_stop(false), - m_receiver(receiver), - m_eventHandler(eventHandler), m_window(None), + m_receiver(receiver), + m_primaryReceiver(primaryReceiver), + m_isOnScreen(m_isPrimary), m_x(0), m_y(0), m_w(0), m_h(0), + m_xCursor(0), m_yCursor(0), m_xCenter(0), m_yCenter(0), + m_keyState(NULL), + m_keyMapper(), + m_im(NULL), + m_ic(NULL), + m_lastKeycode(0), + m_atomQuit(None), m_screensaver(NULL), m_screensaverNotify(false), m_atomScreensaver(None), - m_oneShotTimer(NULL) + m_oneShotTimer(NULL), + m_xtestIsXineramaUnaware(true) { - assert(s_screen == NULL); - assert(m_receiver != NULL); - assert(m_eventHandler != NULL); + assert(s_screen == NULL); + assert(m_receiver != NULL); s_screen = this; @@ -153,115 +157,209 @@ CXWindowsScreen::~CXWindowsScreen() } void -CXWindowsScreen::addTimer(IJob* job, double timeout) +CXWindowsScreen::open(IKeyState* keyState) { - CLock lock(&m_timersMutex); - removeTimerNoLock(job); - m_timers.push(CTimer(job, m_time.getTime(), timeout)); -} + assert(m_display == NULL); -void -CXWindowsScreen::removeTimer(IJob* job) -{ - CLock lock(&m_timersMutex); - removeTimerNoLock(job); -} + try { + // set the X I/O error handler so we catch the display disconnecting + XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler); -void -CXWindowsScreen::removeTimerNoLock(IJob* job) -{ - // do it the hard way. first collect all jobs that are not - // the removed job. - CTimerPriorityQueue::container_type tmp; - for (CTimerPriorityQueue::iterator index = m_timers.begin(); - index != m_timers.end(); ++index) { - if (index->getJob() != job) { - tmp.push_back(*index); + // get the DISPLAY + const char* display = getenv("DISPLAY"); + if (display == NULL) { + display = ":0.0"; } + + // open the display + LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", display)); + m_display = XOpenDisplay(display); + if (m_display == NULL) { + throw XScreenUnavailable(60.0); + } + + // verify the availability of the XTest extension + if (!m_isPrimary) { + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(m_display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + LOG((CLOG_ERR "XTEST extension not available")); + throw XScreenOpenFailure(); + } + } + + // get root window + m_root = DefaultRootWindow(m_display); + + // get shape of default screen + m_x = 0; + m_y = 0; + m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); + m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d", m_x, m_y, m_w, m_h)); + + // 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 + + // create the window + m_window = createWindow(); + if (m_window == None) { + throw XScreenOpenFailure(); + } + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + + if (m_isPrimary) { + // start watching for events on other windows + selectEvents(m_root); + + // prepare to use input methods + openIM(); + } + + // initialize the clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id); + } + + // initialize the screen saver + m_atomScreensaver = XInternAtom(m_display, + "SYNERGY_SCREENSAVER", False); + m_screensaver = new CXWindowsScreenSaver(this, m_display); + } + catch (...) { + close(); + throw; } - // now swap in the new list - m_timers.swap(tmp); -} + // save the IKeyState + m_keyState = keyState; -UInt32 -CXWindowsScreen::addOneShotTimer(double timeout) -{ - CLock lock(&m_timersMutex); - // FIXME -- support multiple one-shot timers - m_oneShotTimer = new CTimer(NULL, m_time.getTime(), timeout); - return 0; + // we'll send ourself an event of this type to exit the main loop + m_atomQuit = XInternAtom(m_display, "SYNERGY_QUIT", False); + + if (!m_isPrimary) { + // become impervious to server grabs + XTestGrabControl(m_display, True); + } } void -CXWindowsScreen::setWindow(Window window) +CXWindowsScreen::close() { - CLock lock(&m_mutex); - assert(m_display != NULL); + // done with m_keyState + m_keyState = NULL; - // destroy the clipboards + // done with screen saver + delete m_screensaver; + + // destroy clipboards for (ClipboardID id = 0; id < kClipboardEnd; ++id) { delete m_clipboard[id]; m_clipboard[id] = NULL; } - // save the new window - m_window = window; - - // initialize the clipboards - if (m_window != None) { - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - m_clipboard[id] = new CXWindowsClipboard(m_display, m_window, id); - } + // done with input methods + if (m_ic != NULL) { + XDestroyIC(m_ic); + } + if (m_im != NULL) { + XCloseIM(m_im); } -} -Window -CXWindowsScreen::getRoot() const -{ - assert(m_display != NULL); - return m_root; -} + // done with window + if (m_window != None) { + XDestroyWindow(m_display, m_window); + } -Cursor -CXWindowsScreen::getBlankCursor() const -{ - return m_cursor; + // close the display + if (m_display != NULL) { + XCloseDisplay(m_display); + } + + // restore error handler + XSetIOErrorHandler(NULL); + + // reset state + m_atomQuit = None; + m_screensaver = NULL; + m_atomScreensaver = None; + m_ic = NULL; + m_im = NULL; + m_window = None; + m_root = None; + m_display = NULL; } void -CXWindowsScreen::open() +CXWindowsScreen::enable() { - assert(m_display == NULL); + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); - // set the X I/O error handler so we catch the display disconnecting - XSetIOErrorHandler(&CXWindowsScreen::ioErrorHandler); + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); - // get the DISPLAY - const char* display = getenv("DISPLAY"); - if (display == NULL) { - display = ":0.0"; + // 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); } - // open the display - LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", display)); - m_display = XOpenDisplay(display); - if (m_display == NULL) { - throw XScreenUnavailable(60.0); + // 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); } - - // get root window - m_root = DefaultRootWindow(m_display); - - // create the transparent cursor - createBlankCursor(); - - // get screen shape - updateScreenShape(); - - // initialize the screen saver - m_atomScreensaver = XInternAtom(m_display, "SYNERGY_SCREENSAVER", False); - m_screensaver = new CXWindowsScreenSaver(this, m_display); } void @@ -269,19 +367,13 @@ CXWindowsScreen::mainLoop() { // wait for an event in a cancellable way and don't lock the // display while we're waiting. - CEvent event; + XEvent event; m_mutex.lock(); + for (;;) { + #if UNIX_LIKE - // 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; -#endif - while (!m_stop) { // compute timeout to next timer double dtimeout; { @@ -292,8 +384,14 @@ CXWindowsScreen::mainLoop() dtimeout = *m_oneShotTimer; } } + + // 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 - int timeout = static_cast(1000.0 * dtimeout); + struct pollfd pfds[1]; + pfds[0].fd = ConnectionNumber(m_display); + pfds[0].events = POLLIN; + int timeout = static_cast(1000.0 * dtimeout); #else struct timeval timeout; struct timeval* timeoutPtr; @@ -333,27 +431,10 @@ CXWindowsScreen::mainLoop() // process timers processTimers(); - // handle pending events - while (!m_stop && XPending(m_display) > 0) { - // get the event - XNextEvent(m_display, &event.m_event); - - // process the event. if unhandled then let the subclass - // have a go at it. - m_mutex.unlock(); - if (!onPreDispatch(&event)) { - m_eventHandler->onEvent(&event); - } - m_mutex.lock(); - } - } - #else // !UNIX_LIKE - // poll and sleep - while (!m_stop) { // poll for pending events and process timers - while (!m_stop && XPending(m_display) == 0) { + while (XPending(m_display) == 0) { // check timers if (processTimers()) { continue; @@ -365,75 +446,130 @@ CXWindowsScreen::mainLoop() m_mutex.lock(); } - // process events - while (!m_stop && XPending(m_display) > 0) { - // get the event - XNextEvent(m_display, &event.m_event); - - // process the event. if unhandled then let the subclass - // have a go at it. - m_mutex.unlock(); - if (!onPreDispatch(&event)) { - m_eventHandler->onEvent(&event); - } - m_mutex.lock(); - } - } - #endif // !UNIX_LIKE - m_mutex.unlock(); + // process events + while (XPending(m_display) > 0) { + // get the event + XNextEvent(m_display, &event); + if (isQuitEvent(&event)) { + return; + } + + // process the event + onEvent(&event); + } + } } void CXWindowsScreen::exitMainLoop() { - // m_stop should be a condition variable that we signal here - // but we can't wait on both the condition variable and the - // X connection so there's no point. however, we do need - // to wake up the X connection so send ourself some event. - CLock lock(&m_mutex); - m_stop = true; - - if (m_display != NULL && m_window != None) { + // send ourself a quit event. this will wake up the event loop + // and cause it to exit. + if (m_atomQuit != None) { XEvent event; event.xclient.type = ClientMessage; event.xclient.display = m_display; event.xclient.window = m_window; - event.xclient.message_type = XInternAtom(m_display, "ATOM", False); + event.xclient.message_type = m_atomQuit; event.xclient.format = 32; event.xclient.data.l[0] = 0; event.xclient.data.l[1] = 0; event.xclient.data.l[2] = 0; event.xclient.data.l[3] = 0; event.xclient.data.l[4] = 0; - CXWindowsUtil::CErrorLock lock(m_display); + CLock lock(&m_mutex); + CXWindowsUtil::CErrorLock errorLock(m_display); XSendEvent(m_display, m_window, False, 0, &event); } } void -CXWindowsScreen::close() +CXWindowsScreen::enter() { CLock lock(&m_mutex); - // done with screen saver - delete m_screensaver; - m_screensaver = NULL; - - // destroy clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - delete m_clipboard[id]; - m_clipboard[id] = NULL; + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); } - // close the display - if (m_display != NULL) { - XCloseDisplay(m_display); - m_display = NULL; - LOG((CLOG_DEBUG "closed display")); + // 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); + + // 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); } - XSetIOErrorHandler(NULL); + + // now on screen + m_isOnScreen = true; +} + +bool +CXWindowsScreen::leave() +{ + CLock lock(&m_mutex); + + 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 + // FIXME -- take focus? + 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; + } + + // 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); + } + + // now off screen + m_isOnScreen = false; + + return true; } bool @@ -515,9 +651,36 @@ CXWindowsScreen::screensaver(bool activate) } void -CXWindowsScreen::syncDesktop() +CXWindowsScreen::resetOptions() { - // do nothing; X doesn't suffer from this bogosity + 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::updateKeys() +{ + CLock lock(&m_mutex); + + // update keyboard and mouse button mappings + m_keyMapper.update(m_display, m_keyState); + updateButtons(); +} + +bool +CXWindowsScreen::isPrimary() const +{ + return m_isPrimary; } bool @@ -544,9 +707,6 @@ CXWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const void CXWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { - CLock lock(&m_mutex); - assert(m_display != NULL); - x = m_x; y = m_y; w = m_w; @@ -562,7 +722,7 @@ CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const Window root, window; int mx, my, xWindow, yWindow; unsigned int mask; - if (XQueryPointer(m_display, getRoot(), &root, &window, + if (XQueryPointer(m_display, m_root, &root, &window, &mx, &my, &xWindow, &yWindow, &mask)) { x = mx; y = my; @@ -574,84 +734,368 @@ CXWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const } void -CXWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +CXWindowsScreen::reconfigure(UInt32) { - CLock lock(&m_mutex); - - x = m_xCenter; - y = m_yCenter; + // do nothing } void -CXWindowsScreen::updateScreenShape() +CXWindowsScreen::warpCursor(SInt32 x, SInt32 y) { - // get shape of default screen - m_x = 0; - m_y = 0; - m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); - m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); - LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d", m_x, m_y, m_w, m_h)); + CLock lock(&m_mutex); - // get center of default screen - m_xCenter = m_x + (m_w >> 1); - m_yCenter = m_y + (m_h >> 1); + // warp mouse + warpCursorNoFlush(x, y); -#if HAVE_X11_EXTENSIONS_XINERAMA_H - // 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. - int eventBase, errorBase; - if (XineramaQueryExtension(m_display, &eventBase, &errorBase)) { - if (XineramaIsActive(m_display)) { - int numScreens; - XineramaScreenInfo* screens; - screens = XineramaQueryScreens(m_display, &numScreens); - if (screens != NULL) { - if (numScreens > 1) { - m_xCenter = screens[0].x_org + (screens[0].width >> 1); - m_yCenter = screens[0].y_org + (screens[0].height >> 1); - } - XFree(screens); - } - } + // remove all input events before and including warp + XEvent event; + while (XCheckMaskEvent(m_display, PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask, + &event)) { + // do nothing } -#endif + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +CXWindowsScreen::addOneShotTimer(double timeout) +{ + CLock lock(&m_timersMutex); + // FIXME -- support multiple one-shot timers + m_oneShotTimer = new CTimer(NULL, m_time.getTime(), timeout); + return 0; +} + +SInt32 +CXWindowsScreen::getJumpZoneSize() const +{ + return 1; } bool -CXWindowsScreen::onPreDispatch(CEvent* event) +CXWindowsScreen::isAnyMouseButtonDown() const { - assert(event != NULL); - XEvent* xevent = &event->m_event; + CLock lock(&m_mutex); + + // 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; +} + +const char* +CXWindowsScreen::getKeyName(KeyButton keycode) const +{ + CLock lock(&m_mutex); + + KeySym keysym = XKeycodeToKeysym(m_display, keycode, 0); + char* name = XKeysymToString(keysym); + if (name != NULL) { + return name; + } + else { + static char buffer[20]; + return strcpy(buffer, + CStringUtil::print("keycode %d", keycode).c_str()); + } +} + +void +CXWindowsScreen::fakeKeyEvent(KeyButton keycode, bool press) const +{ + CLock lock(&m_mutex); + XTestFakeKeyEvent(m_display, keycode, press ? True : False, CurrentTime); + XFlush(m_display); +} + +bool +CXWindowsScreen::fakeCtrlAltDel() const +{ + // pass keys through unchanged + return false; +} + +void +CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const +{ + const unsigned int xButton = mapButtonToX(button); + if (xButton != 0) { + CLock lock(&m_mutex); + XTestFakeButtonEvent(m_display, xButton, + press ? True : False, CurrentTime); + XFlush(m_display); + } +} + +void +CXWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + CLock lock(&m_mutex); + 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::fakeMouseWheel(SInt32 delta) const +{ + // choose button depending on rotation direction + const unsigned int xButton = mapButtonToX(static_cast( + (delta >= 0) ? -1 : -2)); + if (xButton == 0) { + return; + } + + // now use absolute value of delta + if (delta < 0) { + delta = -delta; + } + + // send as many clicks as necessary + CLock lock(&m_mutex); + for (; delta >= 120; delta -= 120) { + XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); + XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); + } + XFlush(m_display); +} + +KeyButton +CXWindowsScreen::mapKey(IKeyState::Keystrokes& keys, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + return m_keyMapper.mapKey(keys, keyState, id, desiredMask, isAutoRepeat); +} + +bool +CXWindowsScreen::isQuitEvent(XEvent* event) const +{ + return (m_atomQuit != None && + event->type == ClientMessage && + event->xclient.window == m_window && + event->xclient.message_type == m_atomQuit); +} + +Window +CXWindowsScreen::createWindow() 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 + return XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); +} + +void +CXWindowsScreen::openIM() +{ + // open the input methods + XIM im = XOpenIM(m_display, NULL, NULL, NULL); + if (im == NULL) { + 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_WARN "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::addTimer(IJob* job, double timeout) +{ + CLock lock(&m_timersMutex); + removeTimerNoLock(job); + m_timers.push(CTimer(job, m_time.getTime(), timeout)); +} + +void +CXWindowsScreen::removeTimer(IJob* job) +{ + CLock lock(&m_timersMutex); + removeTimerNoLock(job); +} + +void +CXWindowsScreen::removeTimerNoLock(IJob* job) +{ + // do it the hard way. first collect all jobs that are not + // the removed job. + CTimerPriorityQueue::container_type tmp; + for (CTimerPriorityQueue::iterator index = m_timers.begin(); + index != m_timers.end(); ++index) { + if (index->getJob() != job) { + tmp.push_back(*index); + } + } + + // now swap in the new list + m_timers.swap(tmp); +} + +void +CXWindowsScreen::onEvent(XEvent* xevent) +{ + assert(xevent != NULL); + + // 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)) { + return; + } + } switch (xevent->type) { + case CreateNotify: + if (m_isPrimary) { + // select events on new window + selectEvents(xevent->xcreatewindow.window); + } + return; + case MappingNotify: - { - CLock lock(&m_mutex); - if (XPending(m_display) > 0) { - XEvent tmpEvent; - XPeekEvent(m_display, &tmpEvent); - if (tmpEvent.type == MappingNotify) { - // discard this MappingNotify since another follows. - // we tend to get a bunch of these in a row. - return true; - } + if (XPending(m_display) > 0) { + XEvent tmpEvent; + XPeekEvent(m_display, &tmpEvent); + if (tmpEvent.type == MappingNotify) { + // discard this MappingNotify since another follows. + // we tend to get a bunch of these in a row. + return; } } // keyboard mapping changed XRefreshKeyboardMapping(&xevent->xmapping); + m_keyState->updateKeys(); + break; - // pass event on + case LeaveNotify: + if (!m_isPrimary) { + // mouse moved out of hider window somehow. hide the window. + XUnmapWindow(m_display, m_window); + } break; case SelectionClear: @@ -664,7 +1108,7 @@ CXWindowsScreen::onPreDispatch(CEvent* event) LOG((CLOG_DEBUG "lost clipboard %d ownership at time %d", id, xevent->xselectionclear.time)); m_clipboard[id]->lost(xevent->xselectionclear.time); m_receiver->onGrabClipboard(id); - return true; + return; } } break; @@ -675,12 +1119,11 @@ CXWindowsScreen::onPreDispatch(CEvent* event) // retrieval methods. we'll just delete the property // with the data (satisfying the usual ICCCM protocol). if (xevent->xselection.property != None) { - CLock lock(&m_mutex); XDeleteProperty(m_display, xevent->xselection.requestor, xevent->xselection.property); } - return true; + return; case SelectionRequest: { @@ -688,14 +1131,13 @@ CXWindowsScreen::onPreDispatch(CEvent* event) ClipboardID id = getClipboardID( xevent->xselectionrequest.selection); if (id != kClipboardEnd) { - CLock lock(&m_mutex); m_clipboard[id]->addRequest( xevent->xselectionrequest.owner, xevent->xselectionrequest.requestor, xevent->xselectionrequest.target, xevent->xselectionrequest.time, xevent->xselectionrequest.property); - return true; + return; } } break; @@ -706,16 +1148,17 @@ CXWindowsScreen::onPreDispatch(CEvent* event) processClipboardRequest(xevent->xproperty.window, xevent->xproperty.time, xevent->xproperty.atom); - return true; + return; } break; case ClientMessage: - if (xevent->xclient.message_type == m_atomScreensaver && + if (m_isPrimary && + xevent->xclient.message_type == m_atomScreensaver && xevent->xclient.format == 32) { // screen saver activation/deactivation event - m_eventHandler->onScreensaver(xevent->xclient.data.l[0] != 0); - return true; + m_primaryReceiver->onScreensaver(xevent->xclient.data.l[0] != 0); + return; } break; @@ -723,22 +1166,236 @@ CXWindowsScreen::onPreDispatch(CEvent* event) // looks like one of the windows that requested a clipboard // transfer has gone bye-bye. destroyClipboardRequest(xevent->xdestroywindow.window); + break; - // we don't know if the event was handled or not so continue + case KeyPress: + if (m_isPrimary) { + onKeyPress(xevent->xkey); + } + return; + + case KeyRelease: + if (m_isPrimary) { + onKeyRelease(xevent->xkey); + } + 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: break; } // let screen saver have a go - { - CLock lock(&m_mutex); - m_screensaver->onPreDispatch(xevent); - } - - return m_eventHandler->onPreDispatch(event); + m_screensaver->onPreDispatch(xevent); } void -CXWindowsScreen::createBlankCursor() +CXWindowsScreen::onKeyPress(XKeyEvent& xkey) +{ + LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state)); + const KeyModifierMask mask = m_keyMapper.mapModifier(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. + KeyButton keycode = static_cast(xkey.keycode); + if (keycode == 0) { + keycode = static_cast(m_lastKeycode); + } + + // handle key + m_primaryReceiver->onKeyDown(key, mask, keycode); + KeyModifierMask keyMask = m_keyState->getMaskForKey(keycode); + if (m_keyState->isHalfDuplex(keyMask)) { + m_primaryReceiver->onKeyUp(key, mask | keyMask, keycode); + } + } +} + +Bool +CXWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg) +{ + CKeyEventInfo* 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::onKeyRelease(XKeyEvent& xkey) +{ + const KeyModifierMask mask = m_keyMapper.mapModifier(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // 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. + CKeyEventInfo filter; + filter.m_event = KeyPress; + filter.m_window = xkey.window; + filter.m_time = xkey.time; + filter.m_keycode = xkey.keycode; + + // now check for event + bool hasPress; + { + XEvent xevent2; + hasPress = (XCheckIfEvent(m_display, &xevent2, + &CXWindowsScreen::findKeyEvent, + (XPointer)&filter) == True); + } + + // 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; + hasPress = false; + } + + KeyButton keycode = static_cast(xkey.keycode); + if (!hasPress) { + // no press event follows so it's a plain release + LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state)); + KeyModifierMask keyMask = m_keyState->getMaskForKey(keycode); + if (m_keyState->isHalfDuplex(keyMask)) { + m_primaryReceiver->onKeyDown(key, mask, keycode); + } + m_primaryReceiver->onKeyUp(key, mask, 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_primaryReceiver->onKeyRepeat(key, mask, 1, keycode); + } + } +} + +void +CXWindowsScreen::onMousePress(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button)); + const ButtonID button = mapButtonFromX(&xbutton); + if (button != kButtonNone) { + m_primaryReceiver->onMouseDown(button); + } +} + +void +CXWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button)); + const ButtonID button = mapButtonFromX(&xbutton); + if (button != kButtonNone) { + m_primaryReceiver->onMouseUp(button); + } + else if (xbutton.button == 4) { + // wheel forward (away from user) + m_primaryReceiver->onMouseWheel(120); + } + else if (xbutton.button == 5) { + // wheel backward (toward user) + m_primaryReceiver->onMouseWheel(-120); + } +} + +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 + m_primaryReceiver->onMouseMovePrimary(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) { + m_primaryReceiver->onMouseMoveSecondary(x, y); + } + } +} + +Cursor +CXWindowsScreen::createBlankCursor() const { // this seems just a bit more complicated than really necessary @@ -763,12 +1420,14 @@ CXWindowsScreen::createBlankCursor() color.flags = DoRed | DoGreen | DoBlue; // make cursor from bitmap - m_cursor = XCreatePixmapCursor(m_display, bitmap, 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; } bool @@ -824,7 +1483,7 @@ CXWindowsScreen::processTimers() // now notify of the one shot timers if (oneShot) { m_mutex.unlock(); - m_eventHandler->onOneShotTimerExpired(0); + m_primaryReceiver->onOneShotTimerExpired(0); m_mutex.lock(); } @@ -853,8 +1512,6 @@ void CXWindowsScreen::processClipboardRequest(Window requestor, Time time, Atom property) { - CLock lock(&m_mutex); - // check every clipboard until one returns success for (ClipboardID id = 0; id < kClipboardEnd; ++id) { if (m_clipboard[id] != NULL && @@ -867,8 +1524,6 @@ CXWindowsScreen::processClipboardRequest(Window requestor, void CXWindowsScreen::destroyClipboardRequest(Window requestor) { - CLock lock(&m_mutex); - // check every clipboard until one returns success for (ClipboardID id = 0; id < kClipboardEnd; ++id) { if (m_clipboard[id] != NULL && @@ -894,29 +1549,348 @@ CXWindowsScreen::ioErrorHandler(Display*) exit(17); } +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 + switch (keysym & 0xffffff00) { + case 0x0000: + // Latin-1 + return static_cast(keysym); + + case 0xfe00: + // ISO 9995 Function and Modifier Keys + if (keysym == XK_ISO_Left_Tab) { + return kKeyLeftTab; + } + return kKeyNone; + + case 0xff00: + // MISCELLANY + return static_cast(keysym - 0xff00 + 0xef00); + + case 0x1008ff00: + // "Internet" keys + return g_map1008FF[keysym & 0xff]; + + default: { + // lookup character in table + UInt32 key = CXWindowsUtil::mapKeySymToUCS4(keysym); + if (key != 0x0000ffff) { + return static_cast(key); + } + + // unknown character + return kKeyNone; + } + } +} + +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(m_buttons[id - 1]); +} + +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; +} + // -// CDisplayLock +// CXWindowsScreen::CTimer // -CDisplayLock::CDisplayLock(const CXWindowsScreen* screen) : - m_mutex(&screen->m_mutex), - m_display(screen->m_display) +CXWindowsScreen::CTimer::CTimer(IJob* job, double startTime, double resetTime) : + m_job(job), + m_timeout(resetTime), + m_time(resetTime), + m_startTime(startTime) { - // note -- it's permitted for m_display to be NULL. that might - // happen if we couldn't connect to the display or if the - // display unexpectedly disconnected. the caller is expected - // to check for NULL as necessary. - - m_mutex->lock(); + assert(m_timeout > 0.0); } -CDisplayLock::~CDisplayLock() +CXWindowsScreen::CTimer::~CTimer() { - m_mutex->unlock(); + // do nothing } -CDisplayLock::operator Display*() const +void +CXWindowsScreen::CTimer::run() { - return m_display; + if (m_job != NULL) { + m_job->run(); + } +} + +void +CXWindowsScreen::CTimer::reset() +{ + m_time = m_timeout; + m_startTime = 0.0; +} + +CXWindowsScreen::CTimer::CTimer& +CXWindowsScreen::CTimer::operator-=(double dt) +{ + m_time -= dt - m_startTime; + m_startTime = 0.0; + return *this; +} + +CXWindowsScreen::CTimer::operator double() const +{ + return m_time; +} + +bool +CXWindowsScreen::CTimer::operator<(const CTimer& t) const +{ + return m_time < t.m_time; } diff --git a/lib/platform/CXWindowsScreen.h b/lib/platform/CXWindowsScreen.h index ce37a004..36d5be4b 100644 --- a/lib/platform/CXWindowsScreen.h +++ b/lib/platform/CXWindowsScreen.h @@ -15,40 +15,28 @@ #ifndef CXWINDOWSSCREEN_H #define CXWINDOWSSCREEN_H -#include "IScreen.h" +#include "IPlatformScreen.h" +#include "CXWindowsKeyMapper.h" #include "CMutex.h" #include "CStopwatch.h" +#include "CPriorityQueue.h" #include "stdvector.h" #if defined(X_DISPLAY_MISSING) # error X11 is required to build synergy #else # include #endif -#include -#include -class IJob; -class IScreenEventHandler; -class IScreenReceiver; class CXWindowsClipboard; class CXWindowsScreenSaver; +class IJob; +class IScreenReceiver; +class IPrimaryScreenReceiver; -/*! -\class CEvent -\brief User event data -An architecture dependent type holding user event data. -*/ -// X11 event -class CEvent { +//! Implementation of IPlatformScreen for X11 +class CXWindowsScreen : public IPlatformScreen { public: - XEvent m_event; - SInt32 m_result; -}; - -//! Implementation of IScreen for X11 -class CXWindowsScreen : public IScreen { -public: - CXWindowsScreen(IScreenReceiver*, IScreenEventHandler*); + CXWindowsScreen(IScreenReceiver*, IPrimaryScreenReceiver*); virtual ~CXWindowsScreen(); //! @name manipulators @@ -56,10 +44,10 @@ public: //! Add timer /*! - Add a job to invoke every timeout seconds. The job is - called with the display locked. If a job timeout expires twice - or more before the job can be called then the job is called - just once. The caller retains ownership of the job. + Add a job to invoke every timeout seconds. The job is called + with the display locked. If a job timeout expires twice or + more before the job can be called then the job is called just + once. The caller retains ownership of the job. */ void addTimer(IJob*, double timeout); @@ -69,64 +57,55 @@ public: */ void removeTimer(IJob*); - //! Install a one-shot timer - /*! - Installs a one-shot timer for \c timeout seconds and returns the - id of the timer (which will be passed to the receiver's - \c onTimerExpired()). - */ - UInt32 addOneShotTimer(double timeout); - - //! Set window - /*! - Set the window (created by the subclass). This performs some - initialization and saves the window in case it's needed later. - */ - void setWindow(Window); - - //@} - //! @name accessors - //@{ - - //! Get window - /*! - Returns the root window of the screen. - */ - Window getRoot() const; - - //! Get transparent cursor - /*! - Returns a cursor that is transparent everywhere. - */ - Cursor getBlankCursor() const; - //@} - // IScreen overrides - void open(); - void mainLoop(); - void exitMainLoop(); - void close(); - bool setClipboard(ClipboardID, const IClipboard*); - void checkClipboards(); - void openScreensaver(bool notify); - void closeScreensaver(); - void screensaver(bool activate); - void syncDesktop(); - bool getClipboard(ClipboardID, IClipboard*) const; - void getShape(SInt32&, SInt32&, SInt32&, SInt32&) const; - void getCursorPos(SInt32&, SInt32&) const; - void getCursorCenter(SInt32&, SInt32&) const; + // IPlatformScreen overrides + virtual void open(IKeyState*); + virtual void close(); + virtual void enable(); + virtual void disable(); + virtual void mainLoop(); + virtual void exitMainLoop(); + 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 updateKeys(); + virtual bool isPrimary() const; + virtual bool getClipboard(ClipboardID, IClipboard*) const; + virtual void getShape(SInt32&, SInt32&, SInt32&, SInt32&) const; + virtual void getCursorPos(SInt32&, SInt32&) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 addOneShotTimer(double timeout); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown() const; + virtual const char* getKeyName(KeyButton) const; + + // ISecondaryScreen overrides + virtual void fakeKeyEvent(KeyButton id, bool press) const; + virtual bool fakeCtrlAltDel() const; + virtual void fakeMouseButton(ButtonID id, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseWheel(SInt32 delta) const; + virtual KeyButton mapKey(IKeyState::Keystrokes&, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; private: - // update screen size cache - void updateScreenShape(); - // process events before dispatching to receiver - bool onPreDispatch(CEvent* event); + void onEvent(XEvent* event); // create the transparent cursor - void createBlankCursor(); + Cursor createBlankCursor() const; // remove a timer without locking void removeTimerNoLock(IJob*); @@ -149,78 +128,6 @@ private: static int ioErrorHandler(Display*); private: - // a priority queue will direct access to the elements - template , - class Compare = std::greater > - class CPriorityQueue { - public: - typedef typename Container::value_type value_type; - typedef typename Container::size_type size_type; - typedef typename Container::iterator iterator; - typedef Container container_type; - - CPriorityQueue() { } - CPriorityQueue(Container& swappedIn); - ~CPriorityQueue() { } - - // manipulators - - void push(const value_type& v) - { - c.push_back(v); - std::push_heap(c.begin(), c.end(), comp); - } - - void pop() - { - std::pop_heap(c.begin(), c.end(), comp); - c.pop_back(); - } - - iterator begin() - { - return c.begin(); - } - - iterator end() - { - return c.end(); - } - - void swap(CPriorityQueue& q) - { - c.swap(q.c); - } - - void swap(Container& c2) - { - c.swap(c2); - std::make_heap(c.begin(), c.end(), comp); - } - - // accessors - - bool empty() const - { - return c.empty(); - } - - size_type size() const - { - return c.size(); - } - - const value_type& - top() const - { - return c.front(); - } - - private: - Container c; - Compare comp; - }; - // a timer priority queue element class CTimer { public: @@ -252,32 +159,80 @@ private: double m_time; double m_startTime; }; + class CKeyEventInfo { + public: + int m_event; + Window m_window; + Time m_time; + KeyCode m_keycode; + }; + + bool isQuitEvent(XEvent*) const; + + Window createWindow() const; + void openIM(); + + bool grabMouseAndKeyboard(); + void onKeyPress(XKeyEvent&); + void onKeyRelease(XKeyEvent&); + 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 updateButtons(); + + static Bool findKeyEvent(Display*, XEvent* xevent, XPointer arg); private: - friend class CDisplayLock; - typedef CPriorityQueue CTimerPriorityQueue; + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + // X is not thread safe CMutex m_mutex; Display* m_display; Window m_root; - bool m_stop; - - IScreenReceiver* m_receiver; - IScreenEventHandler* m_eventHandler; Window m_window; + IScreenReceiver* m_receiver; + IPrimaryScreenReceiver* m_primaryReceiver; + + // 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 + IKeyState* m_keyState; + CXWindowsKeyMapper m_keyMapper; + + // input method stuff + XIM m_im; + XIC m_ic; + KeyCode m_lastKeycode; + // clipboards CXWindowsClipboard* m_clipboard[kClipboardEnd]; - // the transparent cursor - Cursor m_cursor; + // the quit message + Atom m_atomQuit; // screen saver stuff CXWindowsScreenSaver* m_screensaver; @@ -290,22 +245,23 @@ private: CMutex m_timersMutex; CTimer* m_oneShotTimer; + // 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; + // pointer to (singleton) screen. this is only needed by // ioErrorHandler(). static CXWindowsScreen* s_screen; }; -//! Convenience object to lock/unlock a CXWindowsScreen -class CDisplayLock { -public: - CDisplayLock(const CXWindowsScreen*); - ~CDisplayLock(); - - operator Display*() const; - -private: - const CMutex* m_mutex; - Display* m_display; -}; - #endif diff --git a/lib/platform/CXWindowsSecondaryScreen.cpp b/lib/platform/CXWindowsSecondaryScreen.cpp deleted file mode 100644 index 11096b3a..00000000 --- a/lib/platform/CXWindowsSecondaryScreen.cpp +++ /dev/null @@ -1,1780 +0,0 @@ -/* - * 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 "CXWindowsSecondaryScreen.h" -#include "CXWindowsClipboard.h" -#include "CXWindowsScreen.h" -#include "CXWindowsScreenSaver.h" -#include "CXWindowsUtil.h" -#include "IScreenReceiver.h" -#include "XScreen.h" -#include "CThread.h" -#include "CLog.h" -#if defined(X_DISPLAY_MISSING) -# error X11 is required to build synergy -#else -# include -# include -# define XK_MISCELLANY -# define XK_XKB_KEYS -# define XK_LATIN1 -# define XK_LATIN2 -# define XK_LATIN3 -# define XK_LATIN4 -# define XK_LATIN8 -# define XK_LATIN9 -# include -# if defined(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 defined(HAVE_X11_XF86KEYSYM_H) -# include -# endif -# if !defined(XF86XK_Launch0) -# define XF86XK_Launch0 0x1008FF40 -# endif -# if !defined(XF86XK_Launch1) -# define XF86XK_Launch1 0x1008FF41 -# endif -#endif - - -// -// CXWindowsSecondaryScreen -// - -CXWindowsSecondaryScreen::KeySymsMap - CXWindowsSecondaryScreen::s_decomposedKeySyms; - -CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) : - CSecondaryScreen(), - m_window(None), - m_xtestIsXineramaUnaware(true) -{ - m_screen = new CXWindowsScreen(receiver, this); - - // make sure decomposed keysym table is prepared - getDecomposedKeySymTable(); -} - -CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen() -{ - assert(m_window == None); - delete m_screen; -} - -bool -CXWindowsSecondaryScreen::isAutoRepeating(SysKeyID sysKeyID) const -{ - char bit = static_cast(1 << (sysKeyID & 7)); - return ((m_keyControl.auto_repeats[sysKeyID >> 3] & bit) != 0); -} - -void -CXWindowsSecondaryScreen::flush() -{ - CDisplayLock display(m_screen); - if (display != NULL) { - XFlush(display); - } -} - -void -CXWindowsSecondaryScreen::resetOptions() -{ - CSecondaryScreen::resetOptions(); - m_xtestIsXineramaUnaware = true; -} - -void -CXWindowsSecondaryScreen::setOptions(const COptionsList& options) -{ - CSecondaryScreen::setOptions(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")); - } - } -} - -IScreen* -CXWindowsSecondaryScreen::getScreen() const -{ - return m_screen; -} - -void -CXWindowsSecondaryScreen::onScreensaver(bool) -{ - // ignore -} - -bool -CXWindowsSecondaryScreen::onPreDispatch(const CEvent*) -{ - return false; -} - -bool -CXWindowsSecondaryScreen::onEvent(CEvent* event) -{ - assert(event != NULL); - XEvent& xevent = event->m_event; - - // handle event - switch (xevent.type) { - case MappingNotify: { - // keyboard mapping changed - CDisplayLock display(m_screen); - doUpdateKeys(display); - return true; - } - - case LeaveNotify: - // mouse moved out of hider window somehow. hide the window. - hideWindow(); - return true; - } -} - -void -CXWindowsSecondaryScreen::onOneShotTimerExpired(UInt32) -{ - // ignore -} - -void -CXWindowsSecondaryScreen::onPreMainLoop() -{ - assert(m_window != None); -} - -void -CXWindowsSecondaryScreen::onPreOpen() -{ - assert(m_window == None); -} - -void -CXWindowsSecondaryScreen::onPostOpen() -{ - assert(m_window != None); - - // get the keyboard control state - CDisplayLock display(m_screen); - XGetKeyboardControl(display, &m_keyControl); - - // check if xinerama is enabled and there is more than one screen - m_xinerama = false; -#if HAVE_X11_EXTENSIONS_XINERAMA_H - int eventBase, errorBase; - if (XineramaQueryExtension(display, &eventBase, &errorBase)) { - if (XineramaIsActive(display)) { - int numScreens; - XineramaScreenInfo* screens; - screens = XineramaQueryScreens(display, &numScreens); - if (screens != NULL) { - m_xinerama = (numScreens > 1); - XFree(screens); - } - } - } -#endif -} - -void -CXWindowsSecondaryScreen::onPreClose() -{ - if (m_keyControl.global_auto_repeat == AutoRepeatModeOn) { - CDisplayLock display(m_screen); - XAutoRepeatOn(display); - } -} - -void -CXWindowsSecondaryScreen::onPreEnter() -{ - assert(m_window != None); -} - -void -CXWindowsSecondaryScreen::onPostEnter() -{ - assert(m_window != None); - - // get the keyboard control state - CDisplayLock display(m_screen); - XGetKeyboardControl(display, &m_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(display); -} - -void -CXWindowsSecondaryScreen::onPreLeave() -{ - assert(m_window != None); - - // 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_keyControl.global_auto_repeat == AutoRepeatModeOn) { - CDisplayLock display(m_screen); - XAutoRepeatOn(display); - } -} - -void -CXWindowsSecondaryScreen::createWindow() -{ - { - CDisplayLock display(m_screen); - - // verify the availability of the XTest extension - int majorOpcode, firstEvent, firstError; - if (!XQueryExtension(display, XTestExtensionName, - &majorOpcode, &firstEvent, &firstError)) { - LOG((CLOG_ERR "XTEST extension not available")); - throw XScreenOpenFailure(); - } - - // 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 cursor is - // moved. - XSetWindowAttributes attr; - attr.event_mask = LeaveWindowMask; - attr.do_not_propagate_mask = 0; - attr.override_redirect = True; - attr.cursor = m_screen->getBlankCursor(); - - // create the cursor hider window - m_window = XCreateWindow(display, m_screen->getRoot(), - 0, 0, 1, 1, 0, 0, - InputOnly, CopyFromParent, - CWDontPropagate | CWEventMask | - CWOverrideRedirect | CWCursor, - &attr); - if (m_window == None) { - throw XScreenOpenFailure(); - } - LOG((CLOG_DEBUG "window is 0x%08x", m_window)); - - // become impervious to server grabs - XTestGrabControl(display, True); - } - - // tell generic screen about the window - m_screen->setWindow(m_window); -} - -void -CXWindowsSecondaryScreen::destroyWindow() -{ - { - // release keys that are still pressed - releaseKeys(); - - CDisplayLock display(m_screen); - if (display != NULL) { - // no longer impervious to server grabs - XTestGrabControl(display, False); - } - } - - // destroy window - if (m_window != None) { - m_screen->setWindow(None); - CDisplayLock display(m_screen); - if (display != NULL) { - XDestroyWindow(display, m_window); - } - m_window = None; - } -} - -void -CXWindowsSecondaryScreen::showWindow(SInt32 x, SInt32 y) -{ - { - CDisplayLock display(m_screen); - - // move hider window under the given position - XMoveWindow(display, m_window, x, y); - - // raise and show the hider window. take activation. - // FIXME -- take focus? - XMapRaised(display, m_window); - } - - // 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. - fakeMouseMove(x, y); -} - -void -CXWindowsSecondaryScreen::hideWindow() -{ - assert(m_window != None); - - CDisplayLock display(m_screen); - XUnmapWindow(display, m_window); -} - -unsigned int -CXWindowsSecondaryScreen::mapButton(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(m_buttons[id - 1]); -} - -KeyModifierMask -CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, - SysKeyID& keycode, KeyID id, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, EKeyAction action) const -{ - // note -- must have display locked on entry - - // the system translates key events into characters depending - // on the modifier key state at the time of the event. to - // generate the right keysym we need to set the modifier key - // states appropriately. - // - // desiredMask is the mask desired by the caller. however, there - // may not be a keycode mapping to generate the desired keysym - // with that mask. we override the bits in the mask that cannot - // be accomodated. - - // ignore releases and repeats for half-duplex keys - const bool isHalfDuplex = isKeyHalfDuplex(id); - if (isHalfDuplex && action != kPress) { - return currentMask; - } - - // convert KeyID to a KeySym - KeySym keysym = keyIDToKeySym(id, desiredMask); - if (keysym == NoSymbol) { - // unknown key - LOG((CLOG_DEBUG2 "no keysym for id 0x%08x", id)); - return currentMask; - } - - // get the mapping for this keysym - KeySymIndex keyIndex = m_keysymMap.find(keysym); - - // if the mapping isn't found and keysym is caps lock sensitive - // then convert the case of the keysym and try again. - if (keyIndex == m_keysymMap.end()) { - KeySym lKey, uKey; - XConvertCase(keysym, &lKey, &uKey); - if (lKey != uKey) { - if (lKey == keysym) { - keyIndex = m_keysymMap.find(uKey); - } - else { - keyIndex = m_keysymMap.find(lKey); - } - } - } - - if (keyIndex != m_keysymMap.end()) { - // the keysym is mapped to some keycode. if it's a modifier - // and that modifier is already in the desired state then - // ignore the request since there's nothing to do. never - // ignore a toggle modifier on press or release, though. - const KeyMapping& keyMapping = keyIndex->second; - const KeyModifierMask modifierBit = keyMapping.m_modifierMask; - if (modifierBit != 0) { - if (action == kRepeat) { - LOG((CLOG_DEBUG2 "ignore repeating modifier")); - return currentMask; - } - if ((m_toggleModifierMask & modifierBit) == 0) { - if ((action == kPress && (currentMask & modifierBit) != 0) || - (action == kRelease && (currentMask & modifierBit) == 0)) { - LOG((CLOG_DEBUG2 "modifier in proper state: 0x%04x", currentMask)); - return currentMask; - } - } - } - - // create the keystrokes for this keysym - KeyModifierMask mask; - if (!mapToKeystrokes(keys, keycode, mask, - keyIndex, currentMask, action, isHalfDuplex)) { - // failed to generate keystrokes - keys.clear(); - return currentMask; - } - else { - // success - LOG((CLOG_DEBUG2 "new mask: 0x%04x", mask)); - return mask; - } - } - - // we can't find the keysym mapped to any keycode. this doesn't - // necessarily mean we can't generate the keysym, though. if the - // keysym can be created by combining keysyms then we may still - // be okay. - KeySyms decomposition; - if (decomposeKeySym(keysym, decomposition)) { - LOG((CLOG_DEBUG2 "decomposed keysym 0x%08x into %d keysyms", keysym, decomposition.size())); - - // map each decomposed keysym to keystrokes. we want the mask - // and the keycode from the last keysym (which should be the - // only non-dead key). the dead keys are not sensitive to - // anything but shift and mode switch. - KeyModifierMask mask; - for (KeySyms::const_iterator i = decomposition.begin(); - i != decomposition.end();) { - // increment the iterator - KeySyms::const_iterator next = i; - ++next; - - // lookup the key - keysym = *i; - keyIndex = m_keysymMap.find(keysym); - if (keyIndex == m_keysymMap.end()) { - // missing a required keysym - LOG((CLOG_DEBUG2 "no keycode for decomposed keysym 0x%08x", keysym)); - keys.clear(); - return currentMask; - } - - // the keysym is mapped to some keycode - if (!mapToKeystrokes(keys, keycode, mask, - keyIndex, currentMask, action, isHalfDuplex)) { - // failed to generate keystrokes - keys.clear(); - return currentMask; - } - - // on to the next keysym - i = next; - } - LOG((CLOG_DEBUG2 "new mask: 0x%04x", mask)); - return mask; - } - - LOG((CLOG_DEBUG2 "no keycode for keysym")); - return currentMask; -} - -KeyModifierMask -CXWindowsSecondaryScreen::getModifierKeyMask(SysKeyID keycode) const -{ - KeyCodeToModifierMap::const_iterator i = m_keycodeToModifier.find(keycode); - if (i == m_keycodeToModifier.end()) { - return 0; - } - return m_modifierIndexToMask[i->second]; -} - -bool -CXWindowsSecondaryScreen::isModifierActive(SysKeyID keycode) const -{ - // check if any keycode for this modifier is down. return false - // for toggle modifiers. - KeyCodeToModifierMap::const_iterator i = m_keycodeToModifier.find(keycode); - if (i != m_keycodeToModifier.end() && - (m_modifierIndexToMask[i->second] & m_toggleModifierMask) != 0) { - const KeyCodes& keycodes = m_modifierKeycodes[i->second]; - for (KeyCodes::const_iterator j = keycodes.begin(); - j != keycodes.end(); ++j) { - if (isKeyDown(*j)) { - return true; - } - } - } - return false; -} - -unsigned int -CXWindowsSecondaryScreen::findBestKeyIndex(KeySymIndex keyIndex, - KeyModifierMask /*currentMask*/) const -{ - // there are up to 4 keycodes per keysym to choose from. the - // best choice is the one that requires the fewest adjustments - // to the modifier state. for example, the letter A normally - // requires shift + a. if shift isn't already down we'd have - // to synthesize a shift press before the a press. however, - // if A could also be created with some other keycode without - // shift then we'd prefer that when shift wasn't down. - // - // if the action is kRepeat or kRelease then we don't call this - // method since we just need to synthesize a key repeat/release - // on the same keycode that we pressed. - // XXX -- do this right - for (unsigned int i = 0; i < 4; ++i) { - if (keyIndex->second.m_keycode[i] != 0) { - return i; - } - } - - assert(0 && "no keycode found for keysym"); - return 0; -} - -bool -CXWindowsSecondaryScreen::isShiftInverted(KeySymIndex keyIndex, - KeyModifierMask currentMask) const -{ - // each keycode has up to 4 keysym associated with it, one each for: - // no modifiers, shift, mode switch, and shift and mode switch. if - // a keysym is modified by num lock and num lock is active then you - // get the shifted keysym when shift is not down and the unshifted - // keysym when it is. that is, num lock inverts the sense of the - // shift modifier when active. similarly for caps lock. this - // method returns true iff the sense of shift should be inverted - // for this key given a modifier state. - if (keyIndex->second.m_numLockSensitive) { - if ((currentMask & KeyModifierNumLock) != 0) { - return true; - } - } - - // if a keysym is num lock sensitive it is never caps lock - // sensitive, thus the else here. - else if (keyIndex->second.m_capsLockSensitive) { - if ((currentMask & KeyModifierCapsLock) != 0) { - return true; - } - } - - return false; -} - -bool -CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, - SysKeyID& keycode, - KeyModifierMask& finalMask, - KeySymIndex keyIndex, - KeyModifierMask currentMask, - EKeyAction action, - bool isHalfDuplex) const -{ - // keyIndex must be valid - assert(keyIndex != m_keysymMap.end()); - - // get the keysym we're trying to generate and possible keycodes - const KeySym keysym = keyIndex->first; - const KeyMapping& mapping = keyIndex->second; - LOG((CLOG_DEBUG2 "keysym = 0x%08x", keysym)); - - // get the best keycode index for the keysym and modifiers. note - // that (bestIndex & 1) == 0 if the keycode is a shift modifier - // and (bestIndex & 2) == 0 if the keycode is a mode switch - // modifier. this is important later because we don't want - // adjustModifiers() to adjust a modifier if that's the key we're - // mapping. - unsigned int bestIndex = findBestKeyIndex(keyIndex, currentMask); - - // get the keycode - keycode = mapping.m_keycode[bestIndex]; - - // flip low bit of bestIndex if shift is inverted. if there's a - // keycode for this new index then use it. otherwise use the old - // keycode. you'd think we should fail if there isn't a keycode - // for the new index but some keymaps only include the upper case - // keysyms (notably those on Sun Solaris) so to handle the missing - // lower case keysyms we just use the old keycode. note that - // isShiftInverted() will always return false for a shift modifier. - if (isShiftInverted(keyIndex, currentMask)) { - LOG((CLOG_DEBUG2 "shift is inverted")); - bestIndex ^= 1; - if (mapping.m_keycode[bestIndex] != 0) { - keycode = mapping.m_keycode[bestIndex]; - } - } - LOG((CLOG_DEBUG2 "bestIndex = %d, keycode = %d", bestIndex, keycode)); - - // compute desired mask. the desired mask is the one that matches - // bestIndex, except if the key being synthesized is a shift key - // where we desire what we already have or if it's the mode switch - // key where we only desire to adjust shift. also, if the keycode - // is not sensitive to shift then don't adjust it, otherwise - // something like shift+home would become just home. similiarly - // for mode switch. - KeyModifierMask desiredMask = currentMask; - if (keyIndex->second.m_modifierMask != KeyModifierShift) { - if (keyIndex->second.m_shiftSensitive[bestIndex]) { - if ((bestIndex & 1) != 0) { - desiredMask |= KeyModifierShift; - } - else { - desiredMask &= ~KeyModifierShift; - } - } - if (keyIndex->second.m_modifierMask != KeyModifierModeSwitch) { - if (keyIndex->second.m_modeSwitchSensitive[bestIndex]) { - if ((bestIndex & 2) != 0) { - desiredMask |= KeyModifierModeSwitch; - } - else { - desiredMask &= ~KeyModifierModeSwitch; - } - } - } - } - - // adjust the modifiers to match the desired modifiers - Keystrokes undo; - KeyModifierMask tmpMask = currentMask; - if (!adjustModifiers(keys, undo, tmpMask, desiredMask)) { - LOG((CLOG_DEBUG2 "failed to adjust modifiers")); - return false; - } - - // note if the press of a half-duplex key should be treated as a release - if (isHalfDuplex && (currentMask & mapping.m_modifierMask) != 0) { - action = kRelease; - } - - // add the key event - Keystroke keystroke; - keystroke.m_sysKeyID = keycode; - switch (action) { - case kPress: - keystroke.m_press = true; - keystroke.m_repeat = false; - keys.push_back(keystroke); - break; - - case kRelease: - keystroke.m_press = false; - keystroke.m_repeat = false; - keys.push_back(keystroke); - break; - - case kRepeat: - keystroke.m_press = false; - keystroke.m_repeat = true; - keys.push_back(keystroke); - keystroke.m_press = true; - keys.push_back(keystroke); - break; - } - - // put undo keystrokes at end of keystrokes in reverse order - while (!undo.empty()) { - keys.push_back(undo.back()); - undo.pop_back(); - } - - // if the key is a modifier key then compute the modifier map after - // this key is pressed or released. - finalMask = currentMask; - if (mapping.m_modifierMask != 0) { - // can't be repeating if we've gotten here - assert(action != kRepeat); - - // toggle keys modify the state on release. other keys set the - // bit on press and clear the bit on release. if half-duplex - // then toggle each time we get here. - if ((m_toggleModifierMask & mapping.m_modifierMask) != 0) { - if (isHalfDuplex) { - finalMask ^= mapping.m_modifierMask; - } - } - else if (action == kPress) { - finalMask |= mapping.m_modifierMask; - } - } - - return true; -} - -bool -CXWindowsSecondaryScreen::adjustModifiers(Keystrokes& keys, - Keystrokes& undo, - KeyModifierMask& inOutMask, - KeyModifierMask desiredMask) const -{ - // get mode switch set correctly. do this before shift because - // mode switch may be sensitive to the shift modifier and will - // set/reset it as necessary. - const bool wantModeSwitch = ((desiredMask & KeyModifierModeSwitch) != 0); - const bool haveModeSwitch = ((inOutMask & KeyModifierModeSwitch) != 0); - if (wantModeSwitch != haveModeSwitch) { - LOG((CLOG_DEBUG2 "fix mode switch")); - - // adjust shift if necessary - KeySymIndex modeSwitchIndex = m_keysymMap.find(m_modeSwitchKeysym); - assert(modeSwitchIndex != m_keysymMap.end()); - if (modeSwitchIndex->second.m_shiftSensitive[0]) { - const bool wantShift = false; - const bool haveShift = ((inOutMask & KeyModifierShift) != 0); - if (wantShift != haveShift) { - // add shift keystrokes - LOG((CLOG_DEBUG2 "fix shift for mode switch")); - if (!adjustModifier(keys, undo, m_shiftKeysym, wantShift)) { - return false; - } - inOutMask ^= KeyModifierShift; - } - } - - // add mode switch keystrokes - if (!adjustModifier(keys, undo, m_modeSwitchKeysym, wantModeSwitch)) { - return false; - } - inOutMask ^= KeyModifierModeSwitch; - } - - // get shift set correctly - const bool wantShift = ((desiredMask & KeyModifierShift) != 0); - const bool haveShift = ((inOutMask & KeyModifierShift) != 0); - if (wantShift != haveShift) { - // add shift keystrokes - LOG((CLOG_DEBUG2 "fix shift")); - if (!adjustModifier(keys, undo, m_shiftKeysym, wantShift)) { - return false; - } - inOutMask ^= KeyModifierShift; - } - - return true; -} - -bool -CXWindowsSecondaryScreen::adjustModifier(Keystrokes& keys, - Keystrokes& undo, KeySym keysym, bool desireActive) const -{ - // this method generates keystrokes to change a modifier into the - // desired state. under X11, we only expect to adjust the shift - // and mode switch states. other modifiers don't affect keysym - // generation, except num lock and caps lock and we don't change - // those but instead just invert the handling of the shift key. - // we don't check here if the modifier is already in the desired - // state; the caller should do that. - - // get the key mapping for keysym - KeySymIndex keyIndex = m_keysymMap.find(keysym); - if (keyIndex == m_keysymMap.end() || keyIndex->second.m_keycode[0] == 0) { - // no keycode for keysym or keycode is not a modifier - LOG((CLOG_DEBUG2 "no modifier for 0x%08x", keysym)); - return false; - } - - // this had better be a modifier - assert(keyIndex->second.m_modifierMask != 0); - - // we do not handle toggle modifiers here. they never need to be - // adjusted - assert((keyIndex->second.m_modifierMask & m_toggleModifierMask) == 0); - - // initialize keystroke - Keystroke keystroke; - keystroke.m_repeat = false; - - // releasing a modifier is quite different from pressing one. - // when we release a modifier we have to release every keycode that - // is assigned to the modifier since the modifier is active if any - // one of them is down. when we press a modifier we just have to - // press one of those keycodes. - if (desireActive) { - // press - keystroke.m_sysKeyID = keyIndex->second.m_keycode[0]; - keystroke.m_press = true; - keys.push_back(keystroke); - keystroke.m_press = false; - undo.push_back(keystroke); - } - else { - // release - KeyCodeToModifierMap::const_iterator index = - m_keycodeToModifier.find(keyIndex->second.m_keycode[0]); - if (index != m_keycodeToModifier.end()) { - const KeyCodes& keycodes = m_modifierKeycodes[index->second]; - for (KeyCodes::const_iterator j = keycodes.begin(); - j != keycodes.end(); ++j) { - if (isKeyDown(*j)) { - keystroke.m_sysKeyID = *j; - keystroke.m_press = false; - keys.push_back(keystroke); - keystroke.m_press = true; - undo.push_back(keystroke); - } - } - } - } - - return true; -} - -void -CXWindowsSecondaryScreen::fakeKeyEvent(SysKeyID keycode, bool press) const -{ - CDisplayLock display(m_screen); - if (display != NULL) { - XTestFakeKeyEvent(display, keycode, press ? True : False, CurrentTime); - } -} - -void -CXWindowsSecondaryScreen::fakeMouseButton(ButtonID button, bool press) const -{ - const unsigned int xButton = mapButton(button); - if (xButton != 0) { - CDisplayLock display(m_screen); - if (display != NULL) { - XTestFakeButtonEvent(display, xButton, - press ? True : False, CurrentTime); - } - } -} - -void -CXWindowsSecondaryScreen::fakeMouseMove(SInt32 x, SInt32 y) const -{ - CDisplayLock display(m_screen); - if (m_xinerama && m_xtestIsXineramaUnaware) { - XWarpPointer(display, None, m_screen->getRoot(), 0, 0, 0, 0, x, y); - } - else { - Display* pDisplay = display; - XTestFakeMotionEvent(display, DefaultScreen(pDisplay), - x, y, CurrentTime); - } -} - -void -CXWindowsSecondaryScreen::fakeMouseWheel(SInt32 delta) const -{ - // choose button depending on rotation direction - const unsigned int xButton = mapButton(static_cast( - (delta >= 0) ? -1 : -2)); - if (xButton == 0) { - return; - } - - // now use absolute value of delta - if (delta < 0) { - delta = -delta; - } - - // send as many clicks as necessary - CDisplayLock display(m_screen); - for (; delta >= 120; delta -= 120) { - XTestFakeButtonEvent(display, xButton, True, CurrentTime); - XTestFakeButtonEvent(display, xButton, False, CurrentTime); - } -} - -void -CXWindowsSecondaryScreen::doUpdateKeys(Display* display) -{ - // query the button mapping - UInt32 numButtons = XGetPointerMapping(display, NULL, 0); - unsigned char* tmpButtons = new unsigned char[numButtons]; - XGetPointerMapping(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; - - // update mappings - updateKeysymMap(display); -} - -void -CXWindowsSecondaryScreen::updateKeys(KeyState* keys) -{ - CDisplayLock display(m_screen); - - // ask server which keys are pressed - char xkeys[32]; - XQueryKeymap(display, xkeys); - - // transfer to our state - for (UInt32 i = 0, j = 0; i < 32; j += 8, ++i) { - keys[j + 0] = ((xkeys[i] & 0x01) != 0) ? kDown : 0; - keys[j + 1] = ((xkeys[i] & 0x02) != 0) ? kDown : 0; - keys[j + 2] = ((xkeys[i] & 0x04) != 0) ? kDown : 0; - keys[j + 3] = ((xkeys[i] & 0x08) != 0) ? kDown : 0; - keys[j + 4] = ((xkeys[i] & 0x10) != 0) ? kDown : 0; - keys[j + 5] = ((xkeys[i] & 0x20) != 0) ? kDown : 0; - keys[j + 6] = ((xkeys[i] & 0x40) != 0) ? kDown : 0; - keys[j + 7] = ((xkeys[i] & 0x80) != 0) ? kDown : 0; - } - - // update mappings and current modifiers and mouse buttons - doUpdateKeys(display); -} - -void -CXWindowsSecondaryScreen::updateKeysymMap(Display* display) -{ - // there are up to 4 keysyms per keycode - static const unsigned int maxKeysyms = 4; - - // get the number of keycodes - int minKeycode, maxKeycode; - XDisplayKeycodes(display, &minKeycode, &maxKeycode); - const int numKeycodes = maxKeycode - minKeycode + 1; - - // get the keyboard mapping for all keys - int keysymsPerKeycode; - KeySym* keysyms = XGetKeyboardMapping(display, - minKeycode, numKeycodes, - &keysymsPerKeycode); - - // we only understand up to maxKeysyms keysyms per keycodes - unsigned int numKeysyms = keysymsPerKeycode; - if (numKeysyms > maxKeysyms) { - numKeysyms = maxKeysyms; - } - - // get modifier map from server - XModifierKeymap* modifiers = XGetModifierMapping(display); - - // determine shift and mode switch sensitivity. a keysym is shift - // or mode switch sensitive if its keycode is. a keycode is mode - // mode switch sensitive if it has keysyms for indices 2 or 3. - // it's shift sensitive if the keysym for index 1 (if any) is - // different from the keysym for index 0 and, if the keysym for - // for index 3 (if any) is different from the keysym for index 2. - // that is, if shift changes the generated keysym for the keycode. - std::vector usesShift(numKeycodes); - std::vector usesModeSwitch(numKeycodes); - for (int i = 0; i < numKeycodes; ++i) { - // check mode switch first - if (numKeysyms > 2 && - keysyms[i * keysymsPerKeycode + 2] != NoSymbol || - keysyms[i * keysymsPerKeycode + 3] != NoSymbol) { - usesModeSwitch[i] = true; - } - - // check index 0 with index 1 keysyms - if (keysyms[i * keysymsPerKeycode + 0] != NoSymbol && - keysyms[i * keysymsPerKeycode + 1] != NoSymbol && - keysyms[i * keysymsPerKeycode + 1] != - keysyms[i * keysymsPerKeycode + 0]) { - usesShift[i] = true; - } - - else if (numKeysyms >= 4 && - keysyms[i * keysymsPerKeycode + 2] != NoSymbol && - keysyms[i * keysymsPerKeycode + 3] != NoSymbol && - keysyms[i * keysymsPerKeycode + 3] != - keysyms[i * keysymsPerKeycode + 2]) { - usesShift[i] = true; - } - } - - // initialize - m_keysymMap.clear(); - int keysPerModifier = modifiers->max_keypermod; - - // for each modifier keycode, get the index 0 keycode and add it to - // the keysym map. also collect all keycodes for each modifier. - m_keycodeToModifier.clear(); - for (ModifierIndex i = 0; i < 8; ++i) { - // start with no keycodes for this modifier - m_modifierKeycodes[i].clear(); - - // no mask for this modifier - m_modifierIndexToMask[i] = 0; - - // add each keycode for modifier - for (unsigned int j = 0; j < keysPerModifier; ++j) { - // get keycode and ignore unset keycodes - KeyCode keycode = modifiers->modifiermap[i * keysPerModifier + j]; - if (keycode == 0) { - continue; - } - - // save keycode for modifier and modifier for keycode - m_modifierKeycodes[i].push_back(keycode); - m_keycodeToModifier[keycode] = i; - - // get keysym and get/create key mapping - const int keycodeIndex = keycode - minKeycode; - const KeySym keysym = keysyms[keycodeIndex * - keysymsPerKeycode + 0]; - KeyMapping& mapping = m_keysymMap[keysym]; - - // skip if we already have a keycode for this index - if (mapping.m_keycode[0] != 0) { - continue; - } - - // save modifier mask - m_modifierIndexToMask[i] = mapToModifierMask(i, keysym); - - // fill in keysym info - mapping.m_keycode[0] = keycode; - mapping.m_shiftSensitive[0] = usesShift[keycodeIndex]; - mapping.m_modeSwitchSensitive[0] = usesModeSwitch[keycodeIndex]; - mapping.m_modifierMask = m_modifierIndexToMask[i]; - mapping.m_capsLockSensitive = false; - mapping.m_numLockSensitive = false; - } - } - - // create a convenient NoSymbol entry (if it doesn't exist yet). - // sometimes it's useful to handle NoSymbol like a normal keysym. - // remove any entry for NoSymbol. that keysym doesn't count. - { - KeyMapping& mapping = m_keysymMap[NoSymbol]; - for (unsigned int i = 0; i < numKeysyms; ++i) { - mapping.m_keycode[i] = 0; - mapping.m_shiftSensitive[i] = false; - mapping.m_modeSwitchSensitive[i] = false; - } - mapping.m_modifierMask = 0; - mapping.m_capsLockSensitive = false; - mapping.m_numLockSensitive = false; - } - - // add each keysym to the map, unless we've already inserted a key - // for that keysym index. - for (int i = 0; i < numKeycodes; ++i) { - for (unsigned int j = 0; j < numKeysyms; ++j) { - // lookup keysym - const KeySym keysym = keysyms[i * keysymsPerKeycode + j]; - if (keysym == NoSymbol) { - continue; - } - KeyMapping& mapping = m_keysymMap[keysym]; - - // skip if we already have a keycode for this index - if (mapping.m_keycode[j] != 0) { - continue; - } - - // fill in keysym info - if (mapping.m_keycode[0] == 0) { - mapping.m_modifierMask = 0; - } - mapping.m_keycode[j] = static_cast( - minKeycode + i); - mapping.m_shiftSensitive[j] = usesShift[i]; - mapping.m_modeSwitchSensitive[j] = usesModeSwitch[i]; - mapping.m_numLockSensitive = adjustForNumLock(keysym); - mapping.m_capsLockSensitive = adjustForCapsLock(keysym); - } - } - - // choose the keysym to use for each modifier. if the modifier - // isn't mapped then use NoSymbol. if a modifier has both left - // and right versions then (arbitrarily) prefer the left. also - // collect the available modifier bits. - struct CModifierBitInfo { - public: - KeySym CXWindowsSecondaryScreen::*m_keysym; - KeySym m_left; - KeySym m_right; - }; - static const CModifierBitInfo s_modifierBitTable[] = { - { &CXWindowsSecondaryScreen::m_shiftKeysym, XK_Shift_L, XK_Shift_R }, - { &CXWindowsSecondaryScreen::m_ctrlKeysym, XK_Control_L, XK_Control_R }, - { &CXWindowsSecondaryScreen::m_altKeysym, XK_Alt_L, XK_Alt_R }, - { &CXWindowsSecondaryScreen::m_metaKeysym, XK_Meta_L, XK_Meta_R }, - { &CXWindowsSecondaryScreen::m_superKeysym, XK_Super_L, XK_Super_R }, - { &CXWindowsSecondaryScreen::m_modeSwitchKeysym, XK_Mode_switch, NoSymbol }, - { &CXWindowsSecondaryScreen::m_numLockKeysym, XK_Num_Lock, NoSymbol }, - { &CXWindowsSecondaryScreen::m_capsLockKeysym, XK_Caps_Lock, NoSymbol }, - { &CXWindowsSecondaryScreen::m_scrollLockKeysym, XK_Scroll_Lock, NoSymbol } - }; - m_modifierMask = 0; - m_toggleModifierMask = 0; - for (size_t i = 0; i < sizeof(s_modifierBitTable) / - sizeof(s_modifierBitTable[0]); ++i) { - const CModifierBitInfo& info = s_modifierBitTable[i]; - - // find available keysym - KeySymIndex keyIndex = m_keysymMap.find(info.m_left); - if (keyIndex == m_keysymMap.end() && info.m_right != NoSymbol) { - keyIndex = m_keysymMap.find(info.m_right); - } - if (keyIndex != m_keysymMap.end() && - keyIndex->second.m_modifierMask != 0) { - this->*(info.m_keysym) = keyIndex->first; - } - else { - this->*(info.m_keysym) = NoSymbol; - continue; - } - - // add modifier bit - m_modifierMask |= keyIndex->second.m_modifierMask; - if (isToggleKeysym(this->*(info.m_keysym))) { - m_toggleModifierMask |= keyIndex->second.m_modifierMask; - } - } - - // if there's no mode switch key mapped then remove all keycodes - // that depend on it and no keycode can be mode switch sensitive. - if (m_modeSwitchKeysym == NoSymbol) { - LOG((CLOG_DEBUG2 "no mode switch in keymap")); - for (KeySymMap::iterator i = m_keysymMap.begin(); - i != m_keysymMap.end(); ) { - i->second.m_keycode[2] = 0; - i->second.m_keycode[3] = 0; - i->second.m_modeSwitchSensitive[0] = false; - i->second.m_modeSwitchSensitive[1] = false; - i->second.m_modeSwitchSensitive[2] = false; - i->second.m_modeSwitchSensitive[3] = false; - - // if this keysym no has no keycodes then remove it - // except for the NoSymbol keysym mapping. - if (i->second.m_keycode[0] == 0 && i->second.m_keycode[1] == 0) { - m_keysymMap.erase(i++); - } - else { - ++i; - } - } - } - - // clean up - XFree(keysyms); - XFreeModifiermap(modifiers); -} - -KeyModifierMask -CXWindowsSecondaryScreen::getModifiers() const -{ - CDisplayLock display(m_screen); - - // query the pointer to get the keyboard state - Window root, window; - int xRoot, yRoot, xWindow, yWindow; - unsigned int state; - if (!XQueryPointer(display, m_window, &root, &window, - &xRoot, &yRoot, &xWindow, &yWindow, &state)) { - state = 0; - } - - // update active modifier mask - KeyModifierMask mask = 0; - for (ModifierIndex i = 0; i < 8; ++i) { - const KeyModifierMask bit = m_modifierIndexToMask[i]; - if ((bit & m_toggleModifierMask) == 0) { - for (KeyCodes::const_iterator j = m_modifierKeycodes[i].begin(); - j != m_modifierKeycodes[i].end(); ++j) { -// XXX -- is this right? - if (isKeyDown(*j)) { - mask |= bit; - break; - } - } - } - else if ((bit & state) != 0) { - // toggle is on - mask |= bit; - } - } - - return mask; -} - -CSecondaryScreen::SysKeyID -CXWindowsSecondaryScreen::getToggleSysKey(KeyID keyID) const -{ - // convert KeyID to KeySym - KeySym keysym; - switch (keyID) { - case kKeyNumLock: - keysym = m_numLockKeysym; - break; - - case kKeyCapsLock: - keysym = m_capsLockKeysym; - break; - - case kKeyScrollLock: - keysym = m_scrollLockKeysym; - break; - - default: - return 0; - } - - // lookup the key mapping - KeySymIndex index = m_keysymMap.find(keysym); - if (index == m_keysymMap.end()) { - return 0; - } - return index->second.m_keycode[0]; -} - -bool -CXWindowsSecondaryScreen::isToggleKeysym(KeySym key) -{ - switch (key) { - case XK_Caps_Lock: - case XK_Shift_Lock: - case XK_Num_Lock: - case XK_Scroll_Lock: - return true; - - default: - return false; - } -} - -KeyModifierMask -CXWindowsSecondaryScreen::mapToModifierMask( - ModifierIndex i, KeySym keysym) const -{ - // some modifier indices (0,1,2) are dedicated to particular uses, - // the rest depend on the keysyms bound. - switch (i) { - case 0: - return KeyModifierShift; - - case 1: - return KeyModifierCapsLock; - - case 2: - return KeyModifierControl; - - default: - switch (keysym) { - case XK_Shift_L: - case XK_Shift_R: - return KeyModifierShift; - - case XK_Control_L: - case XK_Control_R: - return KeyModifierControl; - - case XK_Alt_L: - case XK_Alt_R: - return KeyModifierAlt; - - case XK_Meta_L: - case XK_Meta_R: - return KeyModifierMeta; - - case XK_Super_L: - case XK_Super_R: - return KeyModifierSuper; - - case XK_Mode_switch: - return KeyModifierModeSwitch; - - case XK_Caps_Lock: - return KeyModifierCapsLock; - - case XK_Num_Lock: - return KeyModifierNumLock; - - case XK_Scroll_Lock: - return KeyModifierScrollLock; - - default: - return 0; - } - } -} - -// map special KeyID keys to KeySyms -#if defined(HAVE_X11_XF86KEYSYM_H) -static const KeySym g_mapE000[] = -{ - /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x40 */ 0, 0, 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, - /* 0xa4 */ 0, 0, - /* 0xa6 */ XF86XK_Back, XF86XK_Forward, - /* 0xa8 */ XF86XK_Refresh, XF86XK_Stop, - /* 0xaa */ XF86XK_Search, XF86XK_Favorites, - /* 0xac */ XF86XK_HomePage, XF86XK_AudioMute, - /* 0xae */ XF86XK_AudioLowerVolume, XF86XK_AudioRaiseVolume, - /* 0xb0 */ XF86XK_AudioNext, XF86XK_AudioPrev, - /* 0xb2 */ XF86XK_AudioStop, XF86XK_AudioPlay, - /* 0xb4 */ XF86XK_Mail, XF86XK_AudioMedia, - /* 0xb6 */ XF86XK_Launch0, XF86XK_Launch1, - /* 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 -}; -#endif - -KeySym -CXWindowsSecondaryScreen::keyIDToKeySym(KeyID id, KeyModifierMask mask) const -{ - // convert id to keysym - KeySym keysym = NoSymbol; - if ((id & 0xfffff000) == 0xe000) { - // special character - switch (id & 0x0000ff00) { -#if defined(HAVE_X11_XF86KEYSYM_H) - case 0xe000: - return g_mapE000[id & 0xff]; -#endif - - case 0xee00: - // ISO 9995 Function and Modifier Keys - if (id == kKeyLeftTab) { - keysym = XK_ISO_Left_Tab; - } - break; - - case 0xef00: - // MISCELLANY - keysym = static_cast(id - 0xef00 + 0xff00); - break; - } - } - else if ((id >= 0x0020 && id <= 0x007e) || - (id >= 0x00a0 && id <= 0x00ff)) { - // Latin-1 maps directly - return static_cast(id); - } - else { - // lookup keysym in table - return CXWindowsUtil::mapUCS4ToKeySym(id); - } - - // fail if unknown key - if (keysym == NoSymbol) { - return keysym; - } - - // if kKeyTab is requested with shift active then try XK_ISO_Left_Tab - // instead. if that doesn't work, we'll fall back to XK_Tab with - // shift active. this is to handle primary screens that don't map - // XK_ISO_Left_Tab sending events to secondary screens that do. - if (keysym == XK_Tab && (mask & KeyModifierShift) != 0) { - keysym = XK_ISO_Left_Tab; - } - - // some keysyms have emergency backups (particularly the numpad - // keys since most laptops don't have a separate numpad and the - // numpad overlaying the main keyboard may not have movement - // key bindings). figure out the emergency backup. - KeySym backupKeysym; - switch (keysym) { - case XK_KP_Home: - backupKeysym = XK_Home; - break; - - case XK_KP_Left: - backupKeysym = XK_Left; - break; - - case XK_KP_Up: - backupKeysym = XK_Up; - break; - - case XK_KP_Right: - backupKeysym = XK_Right; - break; - - case XK_KP_Down: - backupKeysym = XK_Down; - break; - - case XK_KP_Prior: - backupKeysym = XK_Prior; - break; - - case XK_KP_Next: - backupKeysym = XK_Next; - break; - - case XK_KP_End: - backupKeysym = XK_End; - break; - - case XK_KP_Insert: - backupKeysym = XK_Insert; - break; - - case XK_KP_Delete: - backupKeysym = XK_Delete; - break; - - case XK_ISO_Left_Tab: - backupKeysym = XK_Tab; - break; - - default: - backupKeysym = keysym; - break; - } - - // see if the keysym is assigned to any keycode. if not and the - // backup keysym is then use the backup keysym. - if (backupKeysym != keysym && - m_keysymMap.find(keysym) == m_keysymMap.end() && - m_keysymMap.find(backupKeysym) != m_keysymMap.end()) { - keysym = backupKeysym; - } - - return keysym; -} - -bool -CXWindowsSecondaryScreen::decomposeKeySym(KeySym keysym, - KeySyms& decomposed) const -{ - // unfortunately, X11 doesn't appear to have any way of - // decomposing a keysym into its component keysyms. we'll - // use a lookup table for certain character sets. - const KeySymsMap& table = getDecomposedKeySymTable(); - KeySymsMap::const_iterator i = table.find(keysym); - if (i == table.end()) { - return false; - } - decomposed = i->second; - return true; -} - -bool -CXWindowsSecondaryScreen::adjustForNumLock(KeySym keysym) const -{ - return (IsKeypadKey(keysym) || IsPrivateKeypadKey(keysym)); -} - -bool -CXWindowsSecondaryScreen::adjustForCapsLock(KeySym keysym) const -{ - KeySym lKey, uKey; - XConvertCase(keysym, &lKey, &uKey); - return (lKey != uKey); -} - -const CXWindowsSecondaryScreen::KeySymsMap& -CXWindowsSecondaryScreen::getDecomposedKeySymTable() -{ - static const KeySym s_rawTable[] = { - // non-dead version of dead keys - XK_grave, XK_dead_grave, XK_space, 0, - XK_acute, XK_dead_acute, XK_space, 0, - XK_asciicircum, XK_dead_circumflex, XK_space, 0, - XK_asciitilde, XK_dead_tilde, XK_space, 0, - XK_cedilla, XK_dead_cedilla, XK_space, 0, - XK_ogonek, XK_dead_ogonek, XK_space, 0, - XK_caron, XK_dead_caron, XK_space, 0, - XK_abovedot, XK_dead_abovedot, XK_space, 0, - XK_doubleacute, XK_dead_doubleacute, XK_space, 0, - XK_breve, XK_dead_breve, XK_space, 0, - XK_macron, XK_dead_macron, XK_space, 0, - - // Latin-1 (ISO 8859-1) - XK_Agrave, XK_dead_grave, XK_A, 0, - XK_Aacute, XK_dead_acute, XK_A, 0, - XK_Acircumflex, XK_dead_circumflex, XK_A, 0, - XK_Atilde, XK_dead_tilde, XK_A, 0, - XK_Adiaeresis, XK_dead_diaeresis, XK_A, 0, - XK_Aring, XK_dead_abovering, XK_A, 0, - XK_Ccedilla, XK_dead_cedilla, XK_C, 0, - XK_Egrave, XK_dead_grave, XK_E, 0, - XK_Eacute, XK_dead_acute, XK_E, 0, - XK_Ecircumflex, XK_dead_circumflex, XK_E, 0, - XK_Ediaeresis, XK_dead_diaeresis, XK_E, 0, - XK_Igrave, XK_dead_grave, XK_I, 0, - XK_Iacute, XK_dead_acute, XK_I, 0, - XK_Icircumflex, XK_dead_circumflex, XK_I, 0, - XK_Idiaeresis, XK_dead_diaeresis, XK_I, 0, - XK_Ntilde, XK_dead_tilde, XK_N, 0, - XK_Ograve, XK_dead_grave, XK_O, 0, - XK_Oacute, XK_dead_acute, XK_O, 0, - XK_Ocircumflex, XK_dead_circumflex, XK_O, 0, - XK_Otilde, XK_dead_tilde, XK_O, 0, - XK_Odiaeresis, XK_dead_diaeresis, XK_O, 0, - XK_Ugrave, XK_dead_grave, XK_U, 0, - XK_Uacute, XK_dead_acute, XK_U, 0, - XK_Ucircumflex, XK_dead_circumflex, XK_U, 0, - XK_Udiaeresis, XK_dead_diaeresis, XK_U, 0, - XK_Yacute, XK_dead_acute, XK_Y, 0, - XK_agrave, XK_dead_grave, XK_a, 0, - XK_aacute, XK_dead_acute, XK_a, 0, - XK_acircumflex, XK_dead_circumflex, XK_a, 0, - XK_atilde, XK_dead_tilde, XK_a, 0, - XK_adiaeresis, XK_dead_diaeresis, XK_a, 0, - XK_aring, XK_dead_abovering, XK_a, 0, - XK_ccedilla, XK_dead_cedilla, XK_c, 0, - XK_egrave, XK_dead_grave, XK_e, 0, - XK_eacute, XK_dead_acute, XK_e, 0, - XK_ecircumflex, XK_dead_circumflex, XK_e, 0, - XK_ediaeresis, XK_dead_diaeresis, XK_e, 0, - XK_igrave, XK_dead_grave, XK_i, 0, - XK_iacute, XK_dead_acute, XK_i, 0, - XK_icircumflex, XK_dead_circumflex, XK_i, 0, - XK_idiaeresis, XK_dead_diaeresis, XK_i, 0, - XK_ntilde, XK_dead_tilde, XK_n, 0, - XK_ograve, XK_dead_grave, XK_o, 0, - XK_oacute, XK_dead_acute, XK_o, 0, - XK_ocircumflex, XK_dead_circumflex, XK_o, 0, - XK_otilde, XK_dead_tilde, XK_o, 0, - XK_odiaeresis, XK_dead_diaeresis, XK_o, 0, - XK_ugrave, XK_dead_grave, XK_u, 0, - XK_uacute, XK_dead_acute, XK_u, 0, - XK_ucircumflex, XK_dead_circumflex, XK_u, 0, - XK_udiaeresis, XK_dead_diaeresis, XK_u, 0, - XK_yacute, XK_dead_acute, XK_y, 0, - XK_ydiaeresis, XK_dead_diaeresis, XK_y, 0, - - // Latin-2 (ISO 8859-2) - XK_Aogonek, XK_dead_ogonek, XK_A, 0, - XK_Lcaron, XK_dead_caron, XK_L, 0, - XK_Sacute, XK_dead_acute, XK_S, 0, - XK_Scaron, XK_dead_caron, XK_S, 0, - XK_Scedilla, XK_dead_cedilla, XK_S, 0, - XK_Tcaron, XK_dead_caron, XK_T, 0, - XK_Zacute, XK_dead_acute, XK_Z, 0, - XK_Zcaron, XK_dead_caron, XK_Z, 0, - XK_Zabovedot, XK_dead_abovedot, XK_Z, 0, - XK_aogonek, XK_dead_ogonek, XK_a, 0, - XK_lcaron, XK_dead_caron, XK_l, 0, - XK_sacute, XK_dead_acute, XK_s, 0, - XK_scaron, XK_dead_caron, XK_s, 0, - XK_scedilla, XK_dead_cedilla, XK_s, 0, - XK_tcaron, XK_dead_caron, XK_t, 0, - XK_zacute, XK_dead_acute, XK_z, 0, - XK_zcaron, XK_dead_caron, XK_z, 0, - XK_zabovedot, XK_dead_abovedot, XK_z, 0, - XK_Racute, XK_dead_acute, XK_R, 0, - XK_Abreve, XK_dead_breve, XK_A, 0, - XK_Lacute, XK_dead_acute, XK_L, 0, - XK_Cacute, XK_dead_acute, XK_C, 0, - XK_Ccaron, XK_dead_caron, XK_C, 0, - XK_Eogonek, XK_dead_ogonek, XK_E, 0, - XK_Ecaron, XK_dead_caron, XK_E, 0, - XK_Dcaron, XK_dead_caron, XK_D, 0, - XK_Nacute, XK_dead_acute, XK_N, 0, - XK_Ncaron, XK_dead_caron, XK_N, 0, - XK_Odoubleacute, XK_dead_doubleacute, XK_O, 0, - XK_Rcaron, XK_dead_caron, XK_R, 0, - XK_Uring, XK_dead_abovering, XK_U, 0, - XK_Udoubleacute, XK_dead_doubleacute, XK_U, 0, - XK_Tcedilla, XK_dead_cedilla, XK_T, 0, - XK_racute, XK_dead_acute, XK_r, 0, - XK_abreve, XK_dead_breve, XK_a, 0, - XK_lacute, XK_dead_acute, XK_l, 0, - XK_cacute, XK_dead_acute, XK_c, 0, - XK_ccaron, XK_dead_caron, XK_c, 0, - XK_eogonek, XK_dead_ogonek, XK_e, 0, - XK_ecaron, XK_dead_caron, XK_e, 0, - XK_dcaron, XK_dead_caron, XK_d, 0, - XK_nacute, XK_dead_acute, XK_n, 0, - XK_ncaron, XK_dead_caron, XK_n, 0, - XK_odoubleacute, XK_dead_doubleacute, XK_o, 0, - XK_rcaron, XK_dead_caron, XK_r, 0, - XK_uring, XK_dead_abovering, XK_u, 0, - XK_udoubleacute, XK_dead_doubleacute, XK_u, 0, - XK_tcedilla, XK_dead_cedilla, XK_t, 0, - - // Latin-3 (ISO 8859-3) - XK_Hcircumflex, XK_dead_circumflex, XK_H, 0, - XK_Iabovedot, XK_dead_abovedot, XK_I, 0, - XK_Gbreve, XK_dead_breve, XK_G, 0, - XK_Jcircumflex, XK_dead_circumflex, XK_J, 0, - XK_hcircumflex, XK_dead_circumflex, XK_h, 0, - XK_gbreve, XK_dead_breve, XK_g, 0, - XK_jcircumflex, XK_dead_circumflex, XK_j, 0, - XK_Cabovedot, XK_dead_abovedot, XK_C, 0, - XK_Ccircumflex, XK_dead_circumflex, XK_C, 0, - XK_Gabovedot, XK_dead_abovedot, XK_G, 0, - XK_Gcircumflex, XK_dead_circumflex, XK_G, 0, - XK_Ubreve, XK_dead_breve, XK_U, 0, - XK_Scircumflex, XK_dead_circumflex, XK_S, 0, - XK_cabovedot, XK_dead_abovedot, XK_c, 0, - XK_ccircumflex, XK_dead_circumflex, XK_c, 0, - XK_gabovedot, XK_dead_abovedot, XK_g, 0, - XK_gcircumflex, XK_dead_circumflex, XK_g, 0, - XK_ubreve, XK_dead_breve, XK_u, 0, - XK_scircumflex, XK_dead_circumflex, XK_s, 0, - - // Latin-4 (ISO 8859-4) - XK_scircumflex, XK_dead_circumflex, XK_s, 0, - XK_Rcedilla, XK_dead_cedilla, XK_R, 0, - XK_Itilde, XK_dead_tilde, XK_I, 0, - XK_Lcedilla, XK_dead_cedilla, XK_L, 0, - XK_Emacron, XK_dead_macron, XK_E, 0, - XK_Gcedilla, XK_dead_cedilla, XK_G, 0, - XK_rcedilla, XK_dead_cedilla, XK_r, 0, - XK_itilde, XK_dead_tilde, XK_i, 0, - XK_lcedilla, XK_dead_cedilla, XK_l, 0, - XK_emacron, XK_dead_macron, XK_e, 0, - XK_gcedilla, XK_dead_cedilla, XK_g, 0, - XK_Amacron, XK_dead_macron, XK_A, 0, - XK_Iogonek, XK_dead_ogonek, XK_I, 0, - XK_Eabovedot, XK_dead_abovedot, XK_E, 0, - XK_Imacron, XK_dead_macron, XK_I, 0, - XK_Ncedilla, XK_dead_cedilla, XK_N, 0, - XK_Omacron, XK_dead_macron, XK_O, 0, - XK_Kcedilla, XK_dead_cedilla, XK_K, 0, - XK_Uogonek, XK_dead_ogonek, XK_U, 0, - XK_Utilde, XK_dead_tilde, XK_U, 0, - XK_Umacron, XK_dead_macron, XK_U, 0, - XK_amacron, XK_dead_macron, XK_a, 0, - XK_iogonek, XK_dead_ogonek, XK_i, 0, - XK_eabovedot, XK_dead_abovedot, XK_e, 0, - XK_imacron, XK_dead_macron, XK_i, 0, - XK_ncedilla, XK_dead_cedilla, XK_n, 0, - XK_omacron, XK_dead_macron, XK_o, 0, - XK_kcedilla, XK_dead_cedilla, XK_k, 0, - XK_uogonek, XK_dead_ogonek, XK_u, 0, - XK_utilde, XK_dead_tilde, XK_u, 0, - XK_umacron, XK_dead_macron, XK_u, 0, - - // Latin-8 (ISO 8859-14) -#if defined(XK_Babovedot) - XK_Babovedot, XK_dead_abovedot, XK_B, 0, - XK_babovedot, XK_dead_abovedot, XK_b, 0, - XK_Dabovedot, XK_dead_abovedot, XK_D, 0, - XK_Wgrave, XK_dead_grave, XK_W, 0, - XK_Wacute, XK_dead_acute, XK_W, 0, - XK_dabovedot, XK_dead_abovedot, XK_d, 0, - XK_Ygrave, XK_dead_grave, XK_Y, 0, - XK_Fabovedot, XK_dead_abovedot, XK_F, 0, - XK_fabovedot, XK_dead_abovedot, XK_f, 0, - XK_Mabovedot, XK_dead_abovedot, XK_M, 0, - XK_mabovedot, XK_dead_abovedot, XK_m, 0, - XK_Pabovedot, XK_dead_abovedot, XK_P, 0, - XK_wgrave, XK_dead_grave, XK_w, 0, - XK_pabovedot, XK_dead_abovedot, XK_p, 0, - XK_wacute, XK_dead_acute, XK_w, 0, - XK_Sabovedot, XK_dead_abovedot, XK_S, 0, - XK_ygrave, XK_dead_grave, XK_y, 0, - XK_Wdiaeresis, XK_dead_diaeresis, XK_W, 0, - XK_wdiaeresis, XK_dead_diaeresis, XK_w, 0, - XK_sabovedot, XK_dead_abovedot, XK_s, 0, - XK_Wcircumflex, XK_dead_circumflex, XK_W, 0, - XK_Tabovedot, XK_dead_abovedot, XK_T, 0, - XK_Ycircumflex, XK_dead_circumflex, XK_Y, 0, - XK_wcircumflex, XK_dead_circumflex, XK_w, 0, - XK_tabovedot, XK_dead_abovedot, XK_t, 0, - XK_ycircumflex, XK_dead_circumflex, XK_y, 0, -#endif - - // Latin-9 (ISO 8859-15) -#if defined(XK_Ydiaeresis) - XK_Ydiaeresis, XK_dead_diaeresis, XK_Y, 0, -#endif - - // end of table - 0 - }; - - // fill table if not yet initialized - if (s_decomposedKeySyms.empty()) { - const KeySym* scan = s_rawTable; - while (*scan != 0) { - // add an entry for this keysym - KeySyms& entry = s_decomposedKeySyms[*scan]; - - // add the decomposed keysyms for the keysym - while (*++scan != 0) { - entry.push_back(*scan); - } - - // skip end of entry marker - ++scan; - } - } - - return s_decomposedKeySyms; -} - - -// -// CXWindowsSecondaryScreen::KeyMapping -// - -CXWindowsSecondaryScreen::KeyMapping::KeyMapping() -{ - m_keycode[0] = 0; - m_keycode[1] = 0; - m_keycode[2] = 0; - m_keycode[3] = 0; -} diff --git a/lib/platform/CXWindowsSecondaryScreen.h b/lib/platform/CXWindowsSecondaryScreen.h deleted file mode 100644 index 2f22bcf7..00000000 --- a/lib/platform/CXWindowsSecondaryScreen.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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 CXWINDOWSSECONDARYSCREEN_H -#define CXWINDOWSSECONDARYSCREEN_H - -#include "CSecondaryScreen.h" -#include "IScreenEventHandler.h" -#include "stdmap.h" -#include "stdvector.h" -#if defined(X_DISPLAY_MISSING) -# error X11 is required to build synergy -#else -# include -#endif - -class CXWindowsScreen; -class IScreenReceiver; - -//! X11 secondary screen implementation -class CXWindowsSecondaryScreen : - public CSecondaryScreen, public IScreenEventHandler { -public: - CXWindowsSecondaryScreen(IScreenReceiver*); - virtual ~CXWindowsSecondaryScreen(); - - // CSecondaryScreen overrides - virtual void resetOptions(); - virtual void setOptions(const COptionsList& options); - virtual IScreen* getScreen() const; - - // IScreenEventHandler overrides - virtual void onScreensaver(bool activated); - virtual bool onPreDispatch(const CEvent* event); - virtual bool onEvent(CEvent* event); - virtual void onOneShotTimerExpired(UInt32 id); - -protected: - // CSecondaryScreen overrides - virtual void onPreMainLoop(); - virtual void onPreOpen(); - virtual void onPostOpen(); - virtual void onPreClose(); - virtual void onPreEnter(); - virtual void onPostEnter(); - virtual void onPreLeave(); - virtual void createWindow(); - virtual void destroyWindow(); - virtual void showWindow(SInt32 x, SInt32 y); - virtual void hideWindow(); - virtual void updateKeys(KeyState* sysKeyStates); - virtual KeyModifierMask getModifiers() const; - - virtual bool isAutoRepeating(SysKeyID) const; - virtual KeyModifierMask getModifierKeyMask(SysKeyID) const; - virtual bool isModifierActive(SysKeyID) const; - virtual SysKeyID getToggleSysKey(KeyID keyID) const; - virtual void flush(); - virtual KeyModifierMask - mapKey(Keystrokes&, SysKeyID& sysKeyID, KeyID, - KeyModifierMask, KeyModifierMask, EKeyAction) const; - virtual void fakeKeyEvent(SysKeyID, bool press) const; - virtual void fakeMouseButton(ButtonID, bool press) const; - virtual void fakeMouseMove(SInt32 x, SInt32 y) const; - virtual void fakeMouseWheel(SInt32 delta) const; - -private: - typedef unsigned int ModifierIndex; - class KeyMapping { - public: - KeyMapping(); - - public: - // KeyCode to generate keysym and whether keycode[i] is - // sensitive to shift and mode switch. - KeyCode m_keycode[4]; - bool m_shiftSensitive[4]; - bool m_modeSwitchSensitive[4]; - - // the modifier mask of keysym or 0 if not a modifier - KeyModifierMask m_modifierMask; - - // whether keysym is sensitive to caps and num lock - bool m_numLockSensitive; - bool m_capsLockSensitive; - }; - - typedef std::vector KeyCodes; - typedef std::map KeyCodeToModifierMap; - typedef std::map KeySymMap; - typedef KeySymMap::const_iterator KeySymIndex; - typedef std::vector KeySyms; - typedef std::map KeySymsMap; - - unsigned int mapButton(ButtonID button) const; - - bool mapToKeystrokes(Keystrokes& keys, - SysKeyID& keycode, - KeyModifierMask& finalMask, - KeySymIndex keyIndex, - KeyModifierMask currentMask, - EKeyAction action, - bool isHalfDuplex) const; - bool adjustModifiers(Keystrokes& keys, - Keystrokes& undo, - KeyModifierMask& inOutMask, - KeyModifierMask desiredMask) const; - bool adjustModifier(Keystrokes& keys, - Keystrokes& undo, - KeySym keysym, - bool desireActive) const; - KeyModifierMask mapToModifierMask(ModifierIndex, KeySym) const; - - unsigned int findBestKeyIndex(KeySymIndex keyIndex, - KeyModifierMask currentMask) const; - bool isShiftInverted(KeySymIndex keyIndex, - KeyModifierMask currentMask) const; - - void doUpdateKeys(Display*); - void updateKeysymMap(Display* display); - void updateModifiers(Display* display); - ModifierIndex keySymToModifierIndex(KeySym) const; - static bool isToggleKeysym(KeySym); - - KeySym keyIDToKeySym(KeyID id, KeyModifierMask mask) const; - bool adjustForNumLock(KeySym) const; - bool adjustForCapsLock(KeySym) const; - - bool decomposeKeySym(KeySym keysym, - KeySyms& decomposed) const; - static const KeySymsMap& getDecomposedKeySymTable(); - -private: - CXWindowsScreen* m_screen; - Window m_window; - - // logical to physical button mapping. m_buttons[i] gives the - // physical button for logical button i+1. - std::vector m_buttons; - - // the modifiers that have keys bound to them - KeyModifierMask m_modifierMask; - - // set bits indicate modifiers that toggle (e.g. caps-lock) - KeyModifierMask m_toggleModifierMask; - - // keysym to keycode mapping - KeySymMap m_keysymMap; - - // modifier index to keycodes - KeyCodes m_modifierKeycodes[8]; - - // modifier index to modifier mask - KeyModifierMask m_modifierIndexToMask[8]; - - // keycode to modifier index - KeyCodeToModifierMap m_keycodeToModifier; - - // modifier keysyms - KeySym m_shiftKeysym; - KeySym m_ctrlKeysym; - KeySym m_altKeysym; - KeySym m_metaKeysym; - KeySym m_superKeysym; - KeySym m_modeSwitchKeysym; - KeySym m_numLockKeysym; - KeySym m_capsLockKeysym; - KeySym m_scrollLockKeysym; - - // the keyboard control state the last time this screen was entered - XKeyboardState m_keyControl; - - // 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; - - // a table of keysym decompositions - static KeySymsMap s_decomposedKeySyms; -}; - -#endif diff --git a/lib/platform/CXWindowsUtil.cpp b/lib/platform/CXWindowsUtil.cpp index 05415ddc..066085ab 100644 --- a/lib/platform/CXWindowsUtil.cpp +++ b/lib/platform/CXWindowsUtil.cpp @@ -16,6 +16,14 @@ #include "CThread.h" #include "CLog.h" #include +#define XK_XKB_KEYS +#define XK_LATIN1 +#define XK_LATIN2 +#define XK_LATIN3 +#define XK_LATIN4 +#define XK_LATIN8 +#define XK_LATIN9 +#include /* * This table maps keysym values into the corresponding ISO 10646 @@ -805,6 +813,218 @@ struct codepair { { 0x20ac, 0x20ac } /* EuroSign EURO SIGN */ }; +static const KeySym s_rawDecomposeTable[] = { + // non-dead version of dead keys + XK_grave, XK_dead_grave, XK_space, 0, + XK_acute, XK_dead_acute, XK_space, 0, + XK_asciicircum, XK_dead_circumflex, XK_space, 0, + XK_asciitilde, XK_dead_tilde, XK_space, 0, + XK_cedilla, XK_dead_cedilla, XK_space, 0, + XK_ogonek, XK_dead_ogonek, XK_space, 0, + XK_caron, XK_dead_caron, XK_space, 0, + XK_abovedot, XK_dead_abovedot, XK_space, 0, + XK_doubleacute, XK_dead_doubleacute, XK_space, 0, + XK_breve, XK_dead_breve, XK_space, 0, + XK_macron, XK_dead_macron, XK_space, 0, + + // Latin-1 (ISO 8859-1) + XK_Agrave, XK_dead_grave, XK_A, 0, + XK_Aacute, XK_dead_acute, XK_A, 0, + XK_Acircumflex, XK_dead_circumflex, XK_A, 0, + XK_Atilde, XK_dead_tilde, XK_A, 0, + XK_Adiaeresis, XK_dead_diaeresis, XK_A, 0, + XK_Aring, XK_dead_abovering, XK_A, 0, + XK_Ccedilla, XK_dead_cedilla, XK_C, 0, + XK_Egrave, XK_dead_grave, XK_E, 0, + XK_Eacute, XK_dead_acute, XK_E, 0, + XK_Ecircumflex, XK_dead_circumflex, XK_E, 0, + XK_Ediaeresis, XK_dead_diaeresis, XK_E, 0, + XK_Igrave, XK_dead_grave, XK_I, 0, + XK_Iacute, XK_dead_acute, XK_I, 0, + XK_Icircumflex, XK_dead_circumflex, XK_I, 0, + XK_Idiaeresis, XK_dead_diaeresis, XK_I, 0, + XK_Ntilde, XK_dead_tilde, XK_N, 0, + XK_Ograve, XK_dead_grave, XK_O, 0, + XK_Oacute, XK_dead_acute, XK_O, 0, + XK_Ocircumflex, XK_dead_circumflex, XK_O, 0, + XK_Otilde, XK_dead_tilde, XK_O, 0, + XK_Odiaeresis, XK_dead_diaeresis, XK_O, 0, + XK_Ugrave, XK_dead_grave, XK_U, 0, + XK_Uacute, XK_dead_acute, XK_U, 0, + XK_Ucircumflex, XK_dead_circumflex, XK_U, 0, + XK_Udiaeresis, XK_dead_diaeresis, XK_U, 0, + XK_Yacute, XK_dead_acute, XK_Y, 0, + XK_agrave, XK_dead_grave, XK_a, 0, + XK_aacute, XK_dead_acute, XK_a, 0, + XK_acircumflex, XK_dead_circumflex, XK_a, 0, + XK_atilde, XK_dead_tilde, XK_a, 0, + XK_adiaeresis, XK_dead_diaeresis, XK_a, 0, + XK_aring, XK_dead_abovering, XK_a, 0, + XK_ccedilla, XK_dead_cedilla, XK_c, 0, + XK_egrave, XK_dead_grave, XK_e, 0, + XK_eacute, XK_dead_acute, XK_e, 0, + XK_ecircumflex, XK_dead_circumflex, XK_e, 0, + XK_ediaeresis, XK_dead_diaeresis, XK_e, 0, + XK_igrave, XK_dead_grave, XK_i, 0, + XK_iacute, XK_dead_acute, XK_i, 0, + XK_icircumflex, XK_dead_circumflex, XK_i, 0, + XK_idiaeresis, XK_dead_diaeresis, XK_i, 0, + XK_ntilde, XK_dead_tilde, XK_n, 0, + XK_ograve, XK_dead_grave, XK_o, 0, + XK_oacute, XK_dead_acute, XK_o, 0, + XK_ocircumflex, XK_dead_circumflex, XK_o, 0, + XK_otilde, XK_dead_tilde, XK_o, 0, + XK_odiaeresis, XK_dead_diaeresis, XK_o, 0, + XK_ugrave, XK_dead_grave, XK_u, 0, + XK_uacute, XK_dead_acute, XK_u, 0, + XK_ucircumflex, XK_dead_circumflex, XK_u, 0, + XK_udiaeresis, XK_dead_diaeresis, XK_u, 0, + XK_yacute, XK_dead_acute, XK_y, 0, + XK_ydiaeresis, XK_dead_diaeresis, XK_y, 0, + + // Latin-2 (ISO 8859-2) + XK_Aogonek, XK_dead_ogonek, XK_A, 0, + XK_Lcaron, XK_dead_caron, XK_L, 0, + XK_Sacute, XK_dead_acute, XK_S, 0, + XK_Scaron, XK_dead_caron, XK_S, 0, + XK_Scedilla, XK_dead_cedilla, XK_S, 0, + XK_Tcaron, XK_dead_caron, XK_T, 0, + XK_Zacute, XK_dead_acute, XK_Z, 0, + XK_Zcaron, XK_dead_caron, XK_Z, 0, + XK_Zabovedot, XK_dead_abovedot, XK_Z, 0, + XK_aogonek, XK_dead_ogonek, XK_a, 0, + XK_lcaron, XK_dead_caron, XK_l, 0, + XK_sacute, XK_dead_acute, XK_s, 0, + XK_scaron, XK_dead_caron, XK_s, 0, + XK_scedilla, XK_dead_cedilla, XK_s, 0, + XK_tcaron, XK_dead_caron, XK_t, 0, + XK_zacute, XK_dead_acute, XK_z, 0, + XK_zcaron, XK_dead_caron, XK_z, 0, + XK_zabovedot, XK_dead_abovedot, XK_z, 0, + XK_Racute, XK_dead_acute, XK_R, 0, + XK_Abreve, XK_dead_breve, XK_A, 0, + XK_Lacute, XK_dead_acute, XK_L, 0, + XK_Cacute, XK_dead_acute, XK_C, 0, + XK_Ccaron, XK_dead_caron, XK_C, 0, + XK_Eogonek, XK_dead_ogonek, XK_E, 0, + XK_Ecaron, XK_dead_caron, XK_E, 0, + XK_Dcaron, XK_dead_caron, XK_D, 0, + XK_Nacute, XK_dead_acute, XK_N, 0, + XK_Ncaron, XK_dead_caron, XK_N, 0, + XK_Odoubleacute, XK_dead_doubleacute, XK_O, 0, + XK_Rcaron, XK_dead_caron, XK_R, 0, + XK_Uring, XK_dead_abovering, XK_U, 0, + XK_Udoubleacute, XK_dead_doubleacute, XK_U, 0, + XK_Tcedilla, XK_dead_cedilla, XK_T, 0, + XK_racute, XK_dead_acute, XK_r, 0, + XK_abreve, XK_dead_breve, XK_a, 0, + XK_lacute, XK_dead_acute, XK_l, 0, + XK_cacute, XK_dead_acute, XK_c, 0, + XK_ccaron, XK_dead_caron, XK_c, 0, + XK_eogonek, XK_dead_ogonek, XK_e, 0, + XK_ecaron, XK_dead_caron, XK_e, 0, + XK_dcaron, XK_dead_caron, XK_d, 0, + XK_nacute, XK_dead_acute, XK_n, 0, + XK_ncaron, XK_dead_caron, XK_n, 0, + XK_odoubleacute, XK_dead_doubleacute, XK_o, 0, + XK_rcaron, XK_dead_caron, XK_r, 0, + XK_uring, XK_dead_abovering, XK_u, 0, + XK_udoubleacute, XK_dead_doubleacute, XK_u, 0, + XK_tcedilla, XK_dead_cedilla, XK_t, 0, + + // Latin-3 (ISO 8859-3) + XK_Hcircumflex, XK_dead_circumflex, XK_H, 0, + XK_Iabovedot, XK_dead_abovedot, XK_I, 0, + XK_Gbreve, XK_dead_breve, XK_G, 0, + XK_Jcircumflex, XK_dead_circumflex, XK_J, 0, + XK_hcircumflex, XK_dead_circumflex, XK_h, 0, + XK_gbreve, XK_dead_breve, XK_g, 0, + XK_jcircumflex, XK_dead_circumflex, XK_j, 0, + XK_Cabovedot, XK_dead_abovedot, XK_C, 0, + XK_Ccircumflex, XK_dead_circumflex, XK_C, 0, + XK_Gabovedot, XK_dead_abovedot, XK_G, 0, + XK_Gcircumflex, XK_dead_circumflex, XK_G, 0, + XK_Ubreve, XK_dead_breve, XK_U, 0, + XK_Scircumflex, XK_dead_circumflex, XK_S, 0, + XK_cabovedot, XK_dead_abovedot, XK_c, 0, + XK_ccircumflex, XK_dead_circumflex, XK_c, 0, + XK_gabovedot, XK_dead_abovedot, XK_g, 0, + XK_gcircumflex, XK_dead_circumflex, XK_g, 0, + XK_ubreve, XK_dead_breve, XK_u, 0, + XK_scircumflex, XK_dead_circumflex, XK_s, 0, + + // Latin-4 (ISO 8859-4) + XK_scircumflex, XK_dead_circumflex, XK_s, 0, + XK_Rcedilla, XK_dead_cedilla, XK_R, 0, + XK_Itilde, XK_dead_tilde, XK_I, 0, + XK_Lcedilla, XK_dead_cedilla, XK_L, 0, + XK_Emacron, XK_dead_macron, XK_E, 0, + XK_Gcedilla, XK_dead_cedilla, XK_G, 0, + XK_rcedilla, XK_dead_cedilla, XK_r, 0, + XK_itilde, XK_dead_tilde, XK_i, 0, + XK_lcedilla, XK_dead_cedilla, XK_l, 0, + XK_emacron, XK_dead_macron, XK_e, 0, + XK_gcedilla, XK_dead_cedilla, XK_g, 0, + XK_Amacron, XK_dead_macron, XK_A, 0, + XK_Iogonek, XK_dead_ogonek, XK_I, 0, + XK_Eabovedot, XK_dead_abovedot, XK_E, 0, + XK_Imacron, XK_dead_macron, XK_I, 0, + XK_Ncedilla, XK_dead_cedilla, XK_N, 0, + XK_Omacron, XK_dead_macron, XK_O, 0, + XK_Kcedilla, XK_dead_cedilla, XK_K, 0, + XK_Uogonek, XK_dead_ogonek, XK_U, 0, + XK_Utilde, XK_dead_tilde, XK_U, 0, + XK_Umacron, XK_dead_macron, XK_U, 0, + XK_amacron, XK_dead_macron, XK_a, 0, + XK_iogonek, XK_dead_ogonek, XK_i, 0, + XK_eabovedot, XK_dead_abovedot, XK_e, 0, + XK_imacron, XK_dead_macron, XK_i, 0, + XK_ncedilla, XK_dead_cedilla, XK_n, 0, + XK_omacron, XK_dead_macron, XK_o, 0, + XK_kcedilla, XK_dead_cedilla, XK_k, 0, + XK_uogonek, XK_dead_ogonek, XK_u, 0, + XK_utilde, XK_dead_tilde, XK_u, 0, + XK_umacron, XK_dead_macron, XK_u, 0, + + // Latin-8 (ISO 8859-14) +#if defined(XK_Babovedot) + XK_Babovedot, XK_dead_abovedot, XK_B, 0, + XK_babovedot, XK_dead_abovedot, XK_b, 0, + XK_Dabovedot, XK_dead_abovedot, XK_D, 0, + XK_Wgrave, XK_dead_grave, XK_W, 0, + XK_Wacute, XK_dead_acute, XK_W, 0, + XK_dabovedot, XK_dead_abovedot, XK_d, 0, + XK_Ygrave, XK_dead_grave, XK_Y, 0, + XK_Fabovedot, XK_dead_abovedot, XK_F, 0, + XK_fabovedot, XK_dead_abovedot, XK_f, 0, + XK_Mabovedot, XK_dead_abovedot, XK_M, 0, + XK_mabovedot, XK_dead_abovedot, XK_m, 0, + XK_Pabovedot, XK_dead_abovedot, XK_P, 0, + XK_wgrave, XK_dead_grave, XK_w, 0, + XK_pabovedot, XK_dead_abovedot, XK_p, 0, + XK_wacute, XK_dead_acute, XK_w, 0, + XK_Sabovedot, XK_dead_abovedot, XK_S, 0, + XK_ygrave, XK_dead_grave, XK_y, 0, + XK_Wdiaeresis, XK_dead_diaeresis, XK_W, 0, + XK_wdiaeresis, XK_dead_diaeresis, XK_w, 0, + XK_sabovedot, XK_dead_abovedot, XK_s, 0, + XK_Wcircumflex, XK_dead_circumflex, XK_W, 0, + XK_Tabovedot, XK_dead_abovedot, XK_T, 0, + XK_Ycircumflex, XK_dead_circumflex, XK_Y, 0, + XK_wcircumflex, XK_dead_circumflex, XK_w, 0, + XK_tabovedot, XK_dead_abovedot, XK_t, 0, + XK_ycircumflex, XK_dead_circumflex, XK_y, 0, +#endif + + // Latin-9 (ISO 8859-15) +#if defined(XK_Ydiaeresis) + XK_Ydiaeresis, XK_dead_diaeresis, XK_Y, 0, +#endif + + // end of table + 0 +}; + // // CXWindowsUtil @@ -812,6 +1032,7 @@ struct codepair { CXWindowsUtil::CKeySymMap CXWindowsUtil::s_keySymToUCS4; CXWindowsUtil::CUCS4Map CXWindowsUtil::s_UCS4ToKeySym; +CXWindowsUtil::CKeySymsMap CXWindowsUtil::s_decomposedKeySyms; bool CXWindowsUtil::getWindowProperty(Display* display, Window window, @@ -1008,6 +1229,21 @@ CXWindowsUtil::mapUCS4ToKeySym(UInt32 c) } } +bool +CXWindowsUtil::decomposeKeySym(KeySym keysym, KeySyms& decomposed) +{ + // unfortunately, X11 doesn't appear to have any way of + // decomposing a keysym into its component keysyms. we'll + // use a lookup table for certain character sets. + initKeyMaps(); + CKeySymsMap::const_iterator i = s_decomposedKeySyms.find(keysym); + if (i == s_decomposedKeySyms.end()) { + return false; + } + decomposed = i->second; + return true; +} + Bool CXWindowsUtil::propertyNotifyPredicate(Display*, XEvent* xevent, XPointer arg) { @@ -1031,6 +1267,19 @@ CXWindowsUtil::initKeyMaps() s_UCS4ToKeySym[s_keymap[i].ucs4] = s_keymap[i].keysym; } } + + // fill decomposed key table if not filled yet + if (s_decomposedKeySyms.empty()) { + for (const KeySym* scan = s_rawDecomposeTable; *scan != 0; ++scan) { + // add an entry for this keysym + KeySyms& entry = s_decomposedKeySyms[*scan]; + + // add the decomposed keysyms for the keysym + while (*++scan != 0) { + entry.push_back(*scan); + } + } + } } diff --git a/lib/platform/CXWindowsUtil.h b/lib/platform/CXWindowsUtil.h index 7136b730..179227eb 100644 --- a/lib/platform/CXWindowsUtil.h +++ b/lib/platform/CXWindowsUtil.h @@ -18,6 +18,7 @@ #include "CString.h" #include "BasicTypes.h" #include "stdmap.h" +#include "stdvector.h" #if defined(X_DISPLAY_MISSING) # error X11 is required to build synergy #else @@ -27,6 +28,8 @@ //! X11 utility functions class CXWindowsUtil { public: + typedef std::vector KeySyms; + //! Get property /*! Gets property \c property on \c window. \b Appends the data to @@ -70,6 +73,14 @@ public: */ static KeySym mapUCS4ToKeySym(UInt32); + //! Decompose a KeySym + /*! + Decomposes \c keysym into its component keysyms. All but the last + decomposed KeySym are dead keys. Returns true iff the decomposition + was successful. + */ + static bool decomposeKeySym(KeySym keysym, KeySyms& decomposed); + //! X11 error handler /*! This class sets an X error handler in the c'tor and restores the @@ -133,9 +144,11 @@ private: private: typedef std::map CKeySymMap; typedef std::map CUCS4Map; + typedef std::map CKeySymsMap; static CKeySymMap s_keySymToUCS4; static CUCS4Map s_UCS4ToKeySym; + static CKeySymsMap s_decomposedKeySyms; }; #endif diff --git a/lib/platform/IMSWindowsScreenEventHandler.h b/lib/platform/IMSWindowsScreenEventHandler.h deleted file mode 100644 index 164f41a8..00000000 --- a/lib/platform/IMSWindowsScreenEventHandler.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 IMSWINDOWSSCREENEVENTHANDLER_H -#define IMSWINDOWSSCREENEVENTHANDLER_H - -#include "IScreenEventHandler.h" -#define WIN32_LEAN_AND_MEAN -#include - -//! MS Windows screen event handler interface -class IMSWindowsScreenEventHandler : public IScreenEventHandler { -public: - //! @name manipulators - //@{ - - //! Notify of window creation - /*! - This is called after the window is created. - */ - virtual void postCreateWindow(HWND) = 0; - - //! Notify of window destruction - /*! - This is called before the window is destroyed. - */ - virtual void preDestroyWindow(HWND) = 0; - - //! Notify of newly accessible desktop - /*! - This is called when the user switched from an inaccessible desktop - to an accessible desktop. - */ - virtual void onAccessibleDesktop() = 0; - - //@} - - // IScreenEventHandler overrides - virtual void onScreensaver(bool activated) = 0; - virtual bool onPreDispatch(const CEvent* event) = 0; - virtual bool onEvent(CEvent* event) = 0; -}; - -#endif diff --git a/lib/platform/Makefile.am b/lib/platform/Makefile.am index ea082e15..891271d3 100644 --- a/lib/platform/Makefile.am +++ b/lib/platform/Makefile.am @@ -23,21 +23,20 @@ EXTRA_DIST = \ CMSWindowsClipboardAnyTextConverter.cpp \ CMSWindowsClipboardTextConverter.cpp \ CMSWindowsClipboardUTF16Converter.cpp \ - CMSWindowsPrimaryScreen.cpp \ + CMSWindowsDesktop.cpp \ + CMSWindowsKeyMapper.cpp \ CMSWindowsScreen.cpp \ CMSWindowsScreenSaver.cpp \ - CMSWindowsSecondaryScreen.cpp \ CSynergyHook.cpp \ CMSWindowsClipboard.h \ CMSWindowsClipboardAnyTextConverter.h \ CMSWindowsClipboardTextConverter.h \ CMSWindowsClipboardUTF16Converter.h \ - CMSWindowsPrimaryScreen.h \ + CMSWindowsDesktop.h \ + CMSWindowsKeyMapper.h \ CMSWindowsScreen.h \ CMSWindowsScreenSaver.h \ - CMSWindowsSecondaryScreen.h \ CSynergyHook.h \ - IMSWindowsScreenEventHandler.h \ $(NULL) MAINTAINERCLEANFILES = \ @@ -50,19 +49,17 @@ libplatform_a_SOURCES = \ CXWindowsClipboardTextConverter.cpp \ CXWindowsClipboardUCS2Converter.cpp \ CXWindowsClipboardUTF8Converter.cpp \ - CXWindowsPrimaryScreen.cpp \ + CXWindowsKeyMapper.cpp \ CXWindowsScreen.cpp \ CXWindowsScreenSaver.cpp \ - CXWindowsSecondaryScreen.cpp \ CXWindowsUtil.cpp \ CXWindowsClipboard.h \ CXWindowsClipboardTextConverter.h \ CXWindowsClipboardUCS2Converter.h \ CXWindowsClipboardUTF8Converter.h \ - CXWindowsPrimaryScreen.h \ + CXWindowsKeyMapper.h \ CXWindowsScreen.h \ CXWindowsScreenSaver.h \ - CXWindowsSecondaryScreen.h \ CXWindowsUtil.h \ $(NULL) INCLUDES = \ diff --git a/lib/platform/platform.dsp b/lib/platform/platform.dsp index ae599964..1fad2189 100644 --- a/lib/platform/platform.dsp +++ b/lib/platform/platform.dsp @@ -103,7 +103,11 @@ SOURCE=.\CMSWindowsClipboardUTF16Converter.cpp # End Source File # Begin Source File -SOURCE=.\CMSWindowsPrimaryScreen.cpp +SOURCE=.\CMSWindowsDesktop.cpp +# End Source File +# Begin Source File + +SOURCE=.\CMSWindowsKeyMapper.cpp # End Source File # Begin Source File @@ -113,10 +117,6 @@ SOURCE=.\CMSWindowsScreen.cpp SOURCE=.\CMSWindowsScreenSaver.cpp # End Source File -# Begin Source File - -SOURCE=.\CMSWindowsSecondaryScreen.cpp -# End Source File # End Group # Begin Group "Header Files" @@ -139,7 +139,11 @@ SOURCE=.\CMSWindowsClipboardUTF16Converter.h # End Source File # Begin Source File -SOURCE=.\CMSWindowsPrimaryScreen.h +SOURCE=.\CMSWindowsDesktop.h +# End Source File +# Begin Source File + +SOURCE=.\CMSWindowsKeyMapper.h # End Source File # Begin Source File @@ -149,14 +153,6 @@ SOURCE=.\CMSWindowsScreen.h SOURCE=.\CMSWindowsScreenSaver.h # End Source File -# Begin Source File - -SOURCE=.\CMSWindowsSecondaryScreen.h -# End Source File -# Begin Source File - -SOURCE=.\IMSWindowsScreenEventHandler.h -# End Source File # End Group # End Target # End Project diff --git a/lib/server/CPrimaryClient.cpp b/lib/server/CPrimaryClient.cpp index b425a68d..54a207f1 100644 --- a/lib/server/CPrimaryClient.cpp +++ b/lib/server/CPrimaryClient.cpp @@ -13,11 +13,11 @@ */ #include "CPrimaryClient.h" -#include "IPrimaryScreenFactory.h" +#include "CScreen.h" +#include "IScreenFactory.h" #include "IServer.h" #include "XScreen.h" #include "XSynergy.h" -#include "CPrimaryScreen.h" #include "CClipboard.h" #include "CLog.h" @@ -25,7 +25,7 @@ // CPrimaryClient // -CPrimaryClient::CPrimaryClient(IPrimaryScreenFactory* screenFactory, +CPrimaryClient::CPrimaryClient(IScreenFactory* screenFactory, IServer* server, IPrimaryScreenReceiver* receiver, const CString& name) : @@ -38,7 +38,11 @@ CPrimaryClient::CPrimaryClient(IPrimaryScreenFactory* screenFactory, // create screen LOG((CLOG_DEBUG1 "creating primary screen")); if (screenFactory != NULL) { - m_screen = screenFactory->create(this, receiver); + IPlatformScreen* platformScreen = + screenFactory->create(this, receiver); + if (platformScreen != NULL) { + m_screen = new CScreen(platformScreen, this); + } } if (m_screen == NULL) { throw XScreenOpenFailure(); @@ -86,7 +90,7 @@ CPrimaryClient::isLockedToScreen() const KeyModifierMask CPrimaryClient::getToggleMask() const { - return m_screen->getToggleMask(); + return m_screen->getActiveModifiers(); } void @@ -149,13 +153,28 @@ CPrimaryClient::close() m_screen->close(); } +void +CPrimaryClient::enable() +{ + m_screen->enable(); +} + +void +CPrimaryClient::disable() +{ + m_screen->disable(); +} + void CPrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum, KeyModifierMask, bool screensaver) { // note -- we must not call any server methods except onError(). m_seqNum = seqNum; - m_screen->enter(xAbs, yAbs, screensaver); + if (!screensaver) { + m_screen->warpCursor(xAbs, yAbs); + } + m_screen->enter(); } bool diff --git a/lib/server/CPrimaryClient.h b/lib/server/CPrimaryClient.h index d0202cf1..823c7297 100644 --- a/lib/server/CPrimaryClient.h +++ b/lib/server/CPrimaryClient.h @@ -19,9 +19,9 @@ #include "IScreenReceiver.h" #include "ProtocolTypes.h" +class CScreen; class IClipboard; -class CPrimaryScreen; -class IPrimaryScreenFactory; +class IScreenFactory; class IPrimaryScreenReceiver; class IServer; @@ -38,7 +38,7 @@ public: \c factory. Throws XScreenOpenFailure or whatever the factory can throw if the screen cannot be created. */ - CPrimaryClient(IPrimaryScreenFactory* factory, IServer*, + CPrimaryClient(IScreenFactory* factory, IServer*, IPrimaryScreenReceiver*, const CString& name); ~CPrimaryClient(); @@ -96,6 +96,9 @@ public: virtual bool onGrabClipboard(ClipboardID); virtual void onClipboardChanged(ClipboardID, const CString&); +// XXX -- these go in IClient + virtual void enable(); + virtual void disable(); // IClient overrides virtual void open(); virtual void mainLoop(); @@ -127,7 +130,7 @@ public: private: IServer* m_server; - CPrimaryScreen* m_screen; + CScreen* m_screen; CString m_name; UInt32 m_seqNum; CClientInfo m_info; diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index fa2487d4..0710875d 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -15,7 +15,7 @@ #include "CServer.h" #include "CHTTPServer.h" #include "CPrimaryClient.h" -#include "IPrimaryScreenFactory.h" +#include "IScreenFactory.h" #include "CInputPacketStream.h" #include "COutputPacketStream.h" #include "CProtocolUtil.h" @@ -89,6 +89,7 @@ CServer::open() LOG((CLOG_INFO "opening screen")); openPrimaryScreen(); setStatus(kNotRunning); + m_primaryClient->enable(); } catch (XScreen& e) { // can't open screen @@ -212,6 +213,7 @@ void CServer::close() { if (m_primaryClient != NULL) { + m_primaryClient->disable(); closePrimaryScreen(); } LOG((CLOG_INFO "closed screen")); @@ -280,7 +282,7 @@ CServer::setConfig(const CConfig& config) } void -CServer::setScreenFactory(IPrimaryScreenFactory* adopted) +CServer::setScreenFactory(IScreenFactory* adopted) { CLock lock(&m_mutex); delete m_screenFactory; @@ -1601,7 +1603,7 @@ CServer::runClient(void* vsocket) try { CProtocolUtil::writef(proxy->getOutputStream(), kMsgEBusy); } - catch (XSocket&) { + catch (XIO&) { // ignore } delete proxy; @@ -1614,7 +1616,7 @@ CServer::runClient(void* vsocket) try { CProtocolUtil::writef(proxy->getOutputStream(), kMsgEUnknown); } - catch (XSocket&) { + catch (XIO&) { // ignore } delete proxy; @@ -1646,7 +1648,7 @@ CServer::runClient(void* vsocket) try { CProtocolUtil::writef(proxy->getOutputStream(), kMsgEBad); } - catch (XSocket&) { + catch (XIO&) { // ignore. client probably aborted the connection. } } @@ -1766,7 +1768,7 @@ CServer::handshakeClient(IDataSocket* socket) CProtocolUtil::writef(output, kMsgEIncompatible, kProtocolMajorVersion, kProtocolMinorVersion); } - catch (XSocket&) { + catch (XIO&) { // ignore } } @@ -1776,7 +1778,7 @@ CServer::handshakeClient(IDataSocket* socket) try { CProtocolUtil::writef(output, kMsgEBad); } - catch (XSocket&) { + catch (XIO&) { // ignore. client probably aborted the connection. } } diff --git a/lib/server/CServer.h b/lib/server/CServer.h index 89a52bd6..92476365 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -33,7 +33,7 @@ class CHTTPServer; class CPrimaryClient; class IClient; class IDataSocket; -class IPrimaryScreenFactory; +class IScreenFactory; class IServerProtocol; class ISocketFactory; class IStreamFilterFactory; @@ -102,13 +102,12 @@ public: */ bool setConfig(const CConfig&); - //! Set primary screen factory + //! Set screen factory /*! - Sets the factory for creating primary screens. This must be - set before calling open(). This object takes ownership of the - factory. + Sets the factory for creating screens. This must be set before + calling open(). This object takes ownership of the factory. */ - void setScreenFactory(IPrimaryScreenFactory*); + void setScreenFactory(IScreenFactory*); //! Set socket factory /*! @@ -341,8 +340,8 @@ private: double m_bindTimeout; // factories - IPrimaryScreenFactory* m_screenFactory; - ISocketFactory* m_socketFactory; + IScreenFactory* m_screenFactory; + ISocketFactory* m_socketFactory; IStreamFilterFactory* m_streamFilterFactory; // running threads diff --git a/lib/synergy/CPrimaryScreen.cpp b/lib/synergy/CPrimaryScreen.cpp deleted file mode 100644 index 320733f0..00000000 --- a/lib/synergy/CPrimaryScreen.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* - * 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 "CPrimaryScreen.h" -#include "IScreen.h" -#include "IScreenReceiver.h" -#include "ProtocolTypes.h" -#include "CLock.h" -#include "CThread.h" -#include "CLog.h" - -// -// CPrimaryScreen -// - -CPrimaryScreen::CPrimaryScreen(IScreenReceiver* receiver) : - m_receiver(receiver), - m_active(false) -{ - // do nothing -} - -CPrimaryScreen::~CPrimaryScreen() -{ - // do nothing -} - -void -CPrimaryScreen::mainLoop() -{ - // change our priority - CThread::getCurrentThread().setPriority(-14); - - // run event loop - try { - LOG((CLOG_DEBUG "entering event loop")); - onPreMainLoop(); - getScreen()->mainLoop(); - onPostMainLoop(); - LOG((CLOG_DEBUG "exiting event loop")); - } - catch (...) { - onPostMainLoop(); - LOG((CLOG_DEBUG "exiting event loop")); - throw; - } -} - -void -CPrimaryScreen::exitMainLoop() -{ - getScreen()->exitMainLoop(); -} - -void -CPrimaryScreen::open() -{ - CClientInfo info; - try { - // subclass hook - onPreOpen(); - - // open the screen - getScreen()->open(); - - // create and prepare our window - createWindow(); - - // collect screen info - getScreen()->getShape(info.m_x, info.m_y, info.m_w, info.m_h); - getScreen()->getCursorPos(info.m_mx, info.m_my); - info.m_zoneSize = getJumpZoneSize(); - - // update keyboard state - updateKeys(); - - // get notified of screen saver activation/deactivation - getScreen()->openScreensaver(true); - - // subclass hook - onPostOpen(); - - // reset options - resetOptions(); - } - catch (...) { - close(); - throw; - } - - // enter the screen - { - CLock lock(&m_mutex); - enterNoWarp(); - } - - // send screen info - m_receiver->onInfoChanged(info); -} - -void -CPrimaryScreen::close() -{ - onPreClose(); - getScreen()->closeScreensaver(); - destroyWindow(); - getScreen()->close(); - onPostClose(); -} - -void -CPrimaryScreen::enter(SInt32 x, SInt32 y, bool forScreensaver) -{ - LOG((CLOG_INFO "entering primary at %d,%d%s", x, y, forScreensaver ? " for screen saver" : "")); - CLock lock(&m_mutex); - assert(m_active == true); - - if (!forScreensaver) { - warpCursor(x, y); - } - else { - onEnterScreensaver(); - } - enterNoWarp(); -} - -void -CPrimaryScreen::enterNoWarp() -{ - // note -- must be locked on entry - - // not active anymore - m_active = false; - - // subclass hook - onPreEnter(); - - // restore active window and hide our window - hideWindow(); - - // subclass hook - onPostEnter(); -} - -bool -CPrimaryScreen::leave() -{ - LOG((CLOG_INFO "leaving primary")); - CLock lock(&m_mutex); - assert(m_active == false); - - // subclass hook - onPreLeave(); - - // show our window - if (!showWindow()) { - onPostLeave(false); - return false; - } - - // get keyboard state as we leave - updateKeys(); - - // warp mouse to center - warpCursorToCenter(); - - // subclass hook - onPostLeave(true); - - // local client now active - m_active = true; - - // make sure our idea of clipboard ownership is correct - getScreen()->checkClipboards(); - - return true; -} - -void -CPrimaryScreen::setClipboard(ClipboardID id, - const IClipboard* clipboard) -{ - getScreen()->setClipboard(id, clipboard); -} - -void -CPrimaryScreen::grabClipboard(ClipboardID id) -{ - getScreen()->setClipboard(id, NULL); -} - -bool -CPrimaryScreen::isActive() const -{ - CLock lock(&m_mutex); - return m_active; -} - -void -CPrimaryScreen::getClipboard(ClipboardID id, - IClipboard* clipboard) const -{ - getScreen()->getClipboard(id, clipboard); -} - -void -CPrimaryScreen::onPreMainLoop() -{ - // do nothing -} - -void -CPrimaryScreen::onPostMainLoop() -{ - // do nothing -} - -void -CPrimaryScreen::onPreOpen() -{ - // do nothing -} - -void -CPrimaryScreen::onPostOpen() -{ - // do nothing -} - -void -CPrimaryScreen::onPreClose() -{ - // do nothing -} - -void -CPrimaryScreen::onPostClose() -{ - // do nothing -} - -void -CPrimaryScreen::onPreEnter() -{ - // do nothing -} - -void -CPrimaryScreen::onPostEnter() -{ - // do nothing -} - -void -CPrimaryScreen::onEnterScreensaver() -{ - // do nothing -} - -void -CPrimaryScreen::onPreLeave() -{ - // do nothing -} - -void -CPrimaryScreen::onPostLeave(bool) -{ - // do nothing -} diff --git a/lib/synergy/CPrimaryScreen.h b/lib/synergy/CPrimaryScreen.h deleted file mode 100644 index c370d19b..00000000 --- a/lib/synergy/CPrimaryScreen.h +++ /dev/null @@ -1,349 +0,0 @@ -/* - * 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 CPRIMARYSCREEN_H -#define CPRIMARYSCREEN_H - -#include "ClipboardTypes.h" -#include "KeyTypes.h" -#include "OptionTypes.h" -#include "CMutex.h" - -class IClipboard; -class IScreen; -class IScreenReceiver; - -//! Generic server-side screen -/*! -This is a platform independent base class for primary screen -implementations. A primary screen is a server-side screen. -Each platform will derive a class from CPrimaryScreen to handle -platform dependent operations. -*/ -class CPrimaryScreen { -public: - CPrimaryScreen(IScreenReceiver*); - virtual ~CPrimaryScreen(); - - //! @name manipulators - //@{ - - //! Open screen - /*! - Opens the screen. This includes initializing the screen, opening - the screen saver, synchronizing keyboard state, and causing events - to be reported to an IPrimaryScreenReceiver (set through another - interface). Calls close() before returning (rethrowing) if it - fails for any reason. - */ - void open(); - - //! Run event loop - /*! - Run the screen's event loop. This returns when it detects - the application should terminate or when exitMainLoop() is called. - mainLoop() may only be called between open() and close(). - */ - void mainLoop(); - - //! Exit event loop - /*! - Force mainLoop() to return. This call can return before - mainLoop() does (i.e. asynchronously). - */ - void exitMainLoop(); - - //! Close screen - /*! - Closes the screen. This close the screen saver and the screen. - */ - void close(); - - //! Enter screen - /*! - Called when the user navigates to the primary screen. Warps - the cursor to the absolute coordinates \c x,y and unhides - it. If \c forScreensaver is true then we're entering because - the screen saver started and the cursor is not warped. - */ - void enter(SInt32 x, SInt32 y, bool forScreensaver); - - //! Leave screen - /*! - Called when the user navigates off the primary screen. Returns - true iff successful. - */ - 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. 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; - - //! 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); - - //! 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; - - //! Install a one-shot timer - /*! - Installs a one-shot timer for \c timeout seconds and returns the - id of the timer. - */ - virtual UInt32 addOneShotTimer(double timeout) = 0; - - //@} - //! @name accessors - //@{ - - //! Test if active - /*! - Returns true iff the screen is active (i.e. the user has left - the screen). Note this is the reverse of a secdonary screen. - */ - bool isActive() const; - - //! Get clipboard - /*! - Saves the contents of the system clipboard indicated by \c id. - */ - void getClipboard(ClipboardID, IClipboard*) 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. - */ - virtual SInt32 getJumpZoneSize() const = 0; - - //! Get toggle key state - /*! - Return the primary screen's current toggle modifier key state. - The returned mask should have the corresponding bit set for - each toggle key that is active. For example, if caps lock is - on then the returned mask should have \c KeyModifierCapsLock set. - */ - virtual KeyModifierMask getToggleMask() const = 0; - - //! Get screen lock state - /*! - Return true if any key or button is being pressed or if there's - any other reason that the user should not be allowed to switch - screens. Active toggle keys (including the scroll lock key) - should not be counted as reasons to lock to the screen. - If this method returns true it should log a message on why at - the CLOG_DEBUG level. - */ - virtual bool isLockedToScreen() const = 0; - - //! Get screen - /*! - Return the platform dependent screen. - */ - virtual IScreen* getScreen() const = 0; - - //@} - -protected: - //! Pre-mainLoop() hook - /*! - Called on entry to mainLoop(). Override to perform platform specific - operations. Default does nothing. May throw. - */ - virtual void onPreMainLoop(); - - //! Post-mainLoop() hook - /*! - Called on exit from mainLoop(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostMainLoop(); - - //! Pre-open() hook - /*! - Called on entry to open(). Override to perform platform specific - operations. Default does nothing. May throw. - */ - virtual void onPreOpen(); - - //! Post-open() hook - /*! - Called on exit from open() iff the open was successful. Default - does nothing. May throw. - */ - virtual void onPostOpen(); - - //! Pre-close() hook - /*! - Called on entry to close(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPreClose(); - - //! Post-close() hook - /*! - Called on exit from close(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostClose(); - - //! Pre-enter() hook - /*! - Called from enter() after the cursor has been warped or, if - \c forScreensaver is true, onEnterScreensaver() was called. Override - to perform platform specific operations. Default does nothing. May - \b not throw. - */ - virtual void onPreEnter(); - - //! Post-enter() hook - /*! - Called on exit from enter(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostEnter(); - - //! Pre-enter() for screen saver hook - /*! - Called on entry to enter() if the \c forScreensaver passed to it was - true. Override to perform platform specific operations. Default - does nothing. May \b not throw. - */ - virtual void onEnterScreensaver(); - - //! Pre-leave() hook - /*! - Called on entry to leave() after desktop synchronization. Override - to perform platform specific operations. Default does nothing. May - \b not throw. - */ - virtual void onPreLeave(); - - //! Post-leave() hook - /*! - Called on exit from leave(). \c success is the value returned by - showWindow(). Override to perform platform specific operations. - Default does nothing. May \b not throw. - */ - virtual void onPostLeave(bool success); - - //! Create window - /*! - Called to create the window. This window is generally used to - receive events, hide the cursor, and to capture keyboard and mouse - input. - */ - virtual void createWindow() = 0; - - //! Destroy window - /*! - Called to destroy the window created by createWindow(). - */ - virtual void destroyWindow() = 0; - - //! Show window - /*! - Called when the user navigates off the primary screen. Hide the - cursor and grab exclusive access to the input devices. Every call - to showWindow() has a matching call to hideWindow() which preceeds - it. Return true iff successful (in particular, iff the input - devices were grabbed). - - After a successful showWindow(), user input events and - screensaver activation/deactivation should be reported to an - IPrimaryScreenReceiver (set through another interface) until - hideWindow() is called. Report mouse motion to its - onMouseMoveSecondary(). User input should not be delivered to - any application except this one. - */ - virtual bool showWindow() = 0; - - //! Hide window - /*! - Called when the user navigates back to the primary screen. Show - the cursor and ungrab the input devices. - - After hideWindow(), user input events should be delivered normally - to other applications. Mouse motion over (at least) the jump zones - must be reported to an IPrimaryScreenReceiver's onMouseMovePrimary(). - */ - virtual void hideWindow() = 0; - - //! Warp cursor for relative motion - /*! - Prepare the cursor to report relative motion. When the user has - navigated to another screen, synergy requires the cursor motion - deltas, not the absolute coordinates. Typically this is done by - warping the cursor to the center of the primary screen and then - every time it moves compute the motion and warp back to the - center (but without reporting that warp as motion). This is - only called after a successful showWindow(). - */ - virtual void warpCursorToCenter() = 0; - - //! Synchronize key state - /*! - Check the current keyboard state. Normally a screen will save - the keyboard state in this method and use this shadow state - when handling user input and in methods like isLockedToScreen(). - */ - virtual void updateKeys() = 0; - -private: - void enterNoWarp(); - -private: - CMutex m_mutex; - - // object to notify of changes - IScreenReceiver* m_receiver; - - // m_active is true if this screen has been left - bool m_active; -}; - -#endif diff --git a/lib/synergy/CScreen.cpp b/lib/synergy/CScreen.cpp new file mode 100644 index 00000000..ba3eb39b --- /dev/null +++ b/lib/synergy/CScreen.cpp @@ -0,0 +1,1001 @@ +/* + * 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 "IScreenReceiver.h" +#include "ISecondaryScreen.h" +#include "ProtocolTypes.h" +#include "CLock.h" +#include "CThread.h" +#include "CLog.h" + +// +// CScreen +// + +CScreen::CScreen(IPlatformScreen* platformScreen, IScreenReceiver* receiver) : + m_screen(platformScreen), + m_receiver(receiver), + m_isPrimary(platformScreen->isPrimary()), + m_enabled(false), + m_entered(m_isPrimary), + m_toggleKeys(0), + m_screenSaverSync(true) +{ + // do nothing +} + +CScreen::~CScreen() +{ + delete m_screen; +} + +void +CScreen::open() +{ + CLock lock(&m_mutex); + + // open screen + m_screen->open(this); + + // reset options + resetOptions(); + + LOG((CLOG_DEBUG "opened display")); +} + +void +CScreen::close() +{ + CLock lock(&m_mutex); + assert(!m_enabled); + assert(m_entered == m_isPrimary); + + // close screen + m_screen->close(); + + LOG((CLOG_DEBUG "closed display")); +} + +void +CScreen::enable() +{ + CLock lock(&m_mutex); + assert(!m_enabled); + + m_screen->enable(); + if (m_isPrimary) { + enablePrimary(); + } + else { + enableSecondary(); + } + + // note activation + m_enabled = true; +} + +void +CScreen::disable() +{ + CLock lock(&m_mutex); + assert(m_enabled); + + m_screen->disable(); + if (m_isPrimary) { + disablePrimary(); + } + else { + disableSecondary(); + } + + // note deactivation + m_enabled = false; +} + +void +CScreen::mainLoop() +{ + // change our priority + CThread::getCurrentThread().setPriority(-14); + + // run event loop + try { + LOG((CLOG_DEBUG "entering event loop")); + m_screen->mainLoop(); + LOG((CLOG_DEBUG "exiting event loop")); + } + catch (...) { + LOG((CLOG_DEBUG "exiting event loop")); + throw; + } +} + +void +CScreen::exitMainLoop() +{ + m_screen->exitMainLoop(); +} + +void +CScreen::enter() +{ + CLock lock(&m_mutex); + assert(m_entered == false); + LOG((CLOG_INFO "entering screen")); + + // now on screen + m_entered = true; + + if (m_isPrimary) { + enterPrimary(); + } + else { + enterSecondary(); + } + m_screen->enter(); +} + +bool +CScreen::leave() +{ + CLock lock(&m_mutex); + 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) +{ + CLock lock(&m_mutex); + + 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) +{ + CLock lock(&m_mutex); + assert(!m_isPrimary); + + // 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; + } + } + + // get the sequence of keys to simulate key press and the final + // modifier state. + Keystrokes keys; + KeyButton key = m_screen->mapKey(keys, *this, id, mask, false); + if (key == 0) { + LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id)); + return; + } + if (keys.empty()) { + // do nothing if there are no associated keys + return; + } + + // generate key events + doKeystrokes(keys, 1); + + // note that key is down + updateKeyState(button, key, true); +} + +void +CScreen::keyRepeat(KeyID id, + KeyModifierMask mask, SInt32 count, KeyButton button) +{ + CLock lock(&m_mutex); + assert(!m_isPrimary); + + // if we haven't seen this button go down then ignore it + ServerKeyMap::iterator index = m_serverKeyMap.find(button); + if (index == m_serverKeyMap.end()) { + return; + } + + // get the sequence of keys to simulate key repeat and the final + // modifier state. + Keystrokes keys; + KeyButton key = m_screen->mapKey(keys, *this, id, mask, true); + if (key == 0) { + LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id)); + return; + } + if (keys.empty()) { + // do nothing if there are no associated keys + return; + } + + // if the keycode 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 + // keycodes for the two keysyms might be different. + key &= 0xffu; + if (key != index->second) { + // replace key up with previous key id but leave key down + // alone so it uses the new keycode and store that keycode + // in the server key map. + for (Keystrokes::iterator index2 = keys.begin(); + index2 != keys.end(); ++index2) { + if ((index2->m_key & 0xffu) == key) { + index2->m_key = index->second; + break; + } + } + + // note that old key is now up + m_keys[index->second] &= ~kDown; + m_fakeKeys[index->second] &= ~kDown; + + // map server key to new key + index->second = key; + + // note that new key is now down + m_keys[index->second] |= kDown; + m_fakeKeys[index->second] |= kDown; + } + + // generate key events + doKeystrokes(keys, count); +} + +void +CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button) +{ + CLock lock(&m_mutex); + assert(!m_isPrimary); + + // if we haven't seen this button go down then ignore it + ServerKeyMap::iterator index = m_serverKeyMap.find(button); + if (index == m_serverKeyMap.end()) { + return; + } + KeyButton key = index->second; + + // get the sequence of keys to simulate key release + Keystrokes keys; + Keystroke keystroke; + keystroke.m_key = key; + keystroke.m_press = false; + keystroke.m_repeat = false; + keys.push_back(keystroke); + + // generate key events + doKeystrokes(keys, 1); + + // note that key is now up + updateKeyState(button, key, false); +} + +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::mouseWheel(SInt32 delta) +{ + assert(!m_isPrimary); + m_screen->fakeMouseWheel(delta); +} + +void +CScreen::resetOptions() +{ + CLock lock(&m_mutex); + + // reset options + m_numLockHalfDuplex = false; + m_capsLockHalfDuplex = false; + + // 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) +{ + CLock lock(&m_mutex); + + // 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) { + m_capsLockHalfDuplex = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexNumLock) { + m_numLockHalfDuplex = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off")); + } + } + + // 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); +} + +UInt32 +CScreen::addOneShotTimer(double timeout) +{ + return m_screen->addOneShotTimer(timeout); +} + +bool +CScreen::isOnScreen() const +{ + CLock lock(&m_mutex); + return m_entered; +} + +void +CScreen::getClipboard(ClipboardID id, + IClipboard* clipboard) const +{ + m_screen->getClipboard(id, clipboard); +} + +SInt32 +CScreen::getJumpZoneSize() const +{ + if (!m_isPrimary) { + return 0; + } + else { + return m_screen->getJumpZoneSize(); + } +} + +bool +CScreen::isLockedToScreen() const +{ + // check for pressed mouse buttons + if (m_screen->isAnyMouseButtonDown()) { + LOG((CLOG_DEBUG "locked by mouse button")); + return true; + } + + // we don't keep primary key state up to date so get the + // current state. + const_cast(this)->updateKeys(); + + // check for scroll lock toggled on + if (isModifierActive(KeyModifierScrollLock)) { + LOG((CLOG_DEBUG "locked by scroll lock")); + return true; + } + + // check for any pressed key + KeyButton key = isAnyKeyDown(); + if (key != 0) { + LOG((CLOG_DEBUG "locked by %s", m_screen->getKeyName(key))); + return true; + } + + // not locked + return false; +} + +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::updateKeys() +{ + CLock lock(&m_mutex); + + // clear key state + memset(m_keys, 0, sizeof(m_keys)); + memset(m_fakeKeys, 0, sizeof(m_fakeKeys)); + m_maskToKeys.clear(); + m_keyToMask.clear(); + + // let subclass set m_keys + m_screen->updateKeys(); + + // figure out active modifier mask + m_mask = getModifierMask(); + LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask)); +} + +void +CScreen::releaseKeys() +{ + CLock lock(&m_mutex); + + // release keys that we've synthesized a press for and only those + // keys. we don't want to synthesize a release on a key the user + // is still physically pressing. + for (KeyButton i = 1; i < 256; ++i) { + if ((m_fakeKeys[i] & kDown) != 0) { + fakeKeyEvent(i, false, false); + m_keys[i] &= ~kDown; + m_fakeKeys[i] &= ~kDown; + } + } +} + +void +CScreen::setKeyDown(KeyButton key) +{ + CLock lock(&m_mutex); + + m_keys[key & 0xffu] |= kDown; +} + +void +CScreen::setToggled(KeyModifierMask mask) +{ + CLock lock(&m_mutex); + + if (!isToggle(mask)) { + return; + } + MaskToKeys::const_iterator i = m_maskToKeys.find(mask); + if (i == m_maskToKeys.end()) { + return; + } + for (KeyButtons::const_iterator j = i->second.begin(); + j != i->second.end(); ++j) { + m_keys[(*j) & 0xffu] |= kToggled; + } +} + +void +CScreen::addModifier(KeyModifierMask mask, KeyButtons& keys) +{ + CLock lock(&m_mutex); + + // the modifier must have associated keys + if (keys.empty()) { + return; + } + + // the mask must not be zero + assert(mask != 0); + + // the mask must have exactly one high bit + assert((mask & (mask - 1)) == 0); + + // index mask by keycodes + for (KeyButtons::iterator j = keys.begin(); j != keys.end(); ++j) { + // key must be valid + assert(((*j) & 0xffu) != 0); + m_keyToMask[static_cast((*j) & 0xffu)] = mask; + } + + // index keys by mask + m_maskToKeys[mask].swap(keys); +} + +void +CScreen::setToggleState(KeyModifierMask mask) +{ + // toggle modifiers that don't match the desired state + KeyModifierMask different = (m_mask ^ mask); + if ((different & KeyModifierCapsLock) != 0) { + toggleKey(KeyModifierCapsLock); + } + if ((different & KeyModifierNumLock) != 0) { + toggleKey(KeyModifierNumLock); + } + if ((different & KeyModifierScrollLock) != 0) { + toggleKey(KeyModifierScrollLock); + } +} + +KeyButton +CScreen::isAnyKeyDown() const +{ + CLock lock(&m_mutex); + + for (UInt32 i = 1; i < 256; ++i) { + if ((m_keys[i] & kDown) != 0) { + return static_cast(i); + } + } + return 0; +} + +bool +CScreen::isKeyDown(KeyButton key) const +{ + CLock lock(&m_mutex); + + key &= 0xffu; + return (key != 0 && ((m_keys[key] & kDown) != 0)); +} + +bool +CScreen::isToggle(KeyModifierMask mask) const +{ + static const KeyModifierMask s_toggleMask = + KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock; + return ((mask & s_toggleMask) != 0); +} + +bool +CScreen::isHalfDuplex(KeyModifierMask mask) const +{ + CLock lock(&m_mutex); + + return ((mask == KeyModifierCapsLock && m_capsLockHalfDuplex) || + (mask == KeyModifierNumLock && m_numLockHalfDuplex)); +} + +bool +CScreen::isModifierActive(KeyModifierMask mask) const +{ + CLock lock(&m_mutex); + + MaskToKeys::const_iterator i = m_maskToKeys.find(mask); + if (i == m_maskToKeys.end()) { + return false; + } + + KeyButtons::const_iterator j = i->second.begin(); + if (isToggle(mask)) { + // modifier is a toggle + if (isKeyToggled(*j)) { + return true; + } + } + else { + // modifier is not a toggle + for (; j != i->second.end(); ++j) { + if (isKeyDown(*j)) { + return true; + } + } + } + return false; +} + +KeyModifierMask +CScreen::getActiveModifiers() const +{ + CLock lock(&m_mutex); + if (m_isPrimary) { + // we don't keep primary key state up to date so get the + // current state. + const_cast(this)->updateKeys(); + } + return m_mask; +} + +bool +CScreen::mapModifier(Keystrokes& keys, Keystrokes& undo, + KeyModifierMask mask, bool desireActive) const +{ + CLock lock(&m_mutex); + + // look up modifier + MaskToKeys::const_iterator i = m_maskToKeys.find(mask); + if (i == m_maskToKeys.end()) { + return false; + } + + // ignore if already in desired state + if (isModifierActive(mask) == desireActive) { + return true; + } + + // initialize keystroke + Keystroke keystroke; + keystroke.m_repeat = false; + + // handle toggles + if (isToggle(mask)) { + keystroke.m_key = i->second.front(); + keystroke.m_press = true; + keys.push_back(keystroke); + keystroke.m_press = false; + keys.push_back(keystroke); + keystroke.m_press = false; + undo.push_back(keystroke); + keystroke.m_press = true; + undo.push_back(keystroke); + } + + else if (desireActive) { + // press + keystroke.m_key = i->second.front(); + keystroke.m_press = true; + keys.push_back(keystroke); + keystroke.m_press = false; + undo.push_back(keystroke); + } + + else { + // releasing a modifier is quite different from pressing one. + // when we release a modifier we have to release every keycode that + // is assigned to the modifier since the modifier is active if any + // one of them is down. when we press a modifier we just have to + // press one of those keycodes. + for (KeyButtons::const_iterator j = i->second.begin(); + j != i->second.end(); ++j) { + if (isKeyDown(*j)) { + keystroke.m_key = *j; + keystroke.m_press = false; + keys.push_back(keystroke); + keystroke.m_press = true; + undo.push_back(keystroke); + } + } + } + + return true; +} + +KeyModifierMask +CScreen::getMaskForKey(KeyButton key) const +{ + CLock lock(&m_mutex); + KeyToMask::const_iterator i = m_keyToMask.find(key); + if (i == m_keyToMask.end()) { + return 0; + } + else { + return i->second; + } +} + +void +CScreen::enablePrimary() +{ + // get notified of screen saver activation/deactivation + m_screen->openScreensaver(true); + + // collect and send screen info + CClientInfo info; + m_screen->getShape(info.m_x, info.m_y, info.m_w, info.m_h); + m_screen->getCursorPos(info.m_mx, info.m_my); + info.m_zoneSize = getJumpZoneSize(); + m_receiver->onInfoChanged(info); +} + +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() +{ + // update our keyboard state to reflect the local state + updateKeys(); + + // remember toggle key state. we'll restore this when we leave. + m_toggleKeys = m_mask; +} + +void +CScreen::leavePrimary() +{ + // do nothing +} + +void +CScreen::leaveSecondary() +{ + // release any keys we think are still down + releaseKeys(); + + // restore toggle key state + setToggleState(m_toggleKeys); +} + +KeyModifierMask +CScreen::getModifierMask() const +{ + KeyModifierMask mask = 0; + if (isModifierActive(KeyModifierShift)) { + mask |= KeyModifierShift; + } + if (isModifierActive(KeyModifierControl)) { + mask |= KeyModifierControl; + } + if (isModifierActive(KeyModifierAlt)) { + mask |= KeyModifierAlt; + } + if (isModifierActive(KeyModifierMeta)) { + mask |= KeyModifierMeta; + } + if (isModifierActive(KeyModifierSuper)) { + mask |= KeyModifierSuper; + } + if (isModifierActive(KeyModifierModeSwitch)) { + mask |= KeyModifierModeSwitch; + } + if (isModifierActive(KeyModifierNumLock)) { + mask |= KeyModifierNumLock; + } + if (isModifierActive(KeyModifierCapsLock)) { + mask |= KeyModifierCapsLock; + } + if (isModifierActive(KeyModifierScrollLock)) { + mask |= KeyModifierScrollLock; + } + return mask; +} + +void +CScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) +{ + // do nothing if no keys or no repeats + if (count < 1 || keys.empty()) { + return; + } + + // generate key events + LOG((CLOG_DEBUG2 "keystrokes:")); + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { + if (k->m_repeat) { + // repeat from here up to but not including the next key + // with m_repeat == false count times. + Keystrokes::const_iterator start = k; + for (; count > 0; --count) { + // send repeating events + for (k = start; k != keys.end() && k->m_repeat; ++k) { + fakeKeyEvent(k->m_key, k->m_press, true); + } + } + + // 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 + fakeKeyEvent(k->m_key, k->m_press, false); + + // next key + ++k; + } + } +} + +void +CScreen::fakeKeyEvent(KeyButton key, bool press, bool repeat) const +{ + // half-duplex keys are special. we ignore releases and convert + // a press when the toggle is active to a release. + KeyModifierMask mask = getMaskForKey(key); + if (isHalfDuplex(mask)) { + if (repeat || !press) { + return; + } + if (isModifierActive(mask)) { + press = false; + } + } + + // send key event + LOG((CLOG_DEBUG2 " %d %s%s", key, press ? "down" : "up", repeat ? " repeat" : "")); + m_screen->fakeKeyEvent(key, press); +} + +void +CScreen::updateKeyState(KeyButton button, KeyButton key, bool press) +{ + // ignore bogus keys + key &= 0xffu; + if (button == 0 || key == 0) { + return; + } + + // update shadow state. shadow state doesn't change on auto-repeat. + if (press) { + // key is now down + m_serverKeyMap[button] = key; + m_keys[key] |= kDown; + m_fakeKeys[key] |= kDown; + } + else { + // key is now up + m_serverKeyMap.erase(button); + m_keys[key] &= ~kDown; + m_fakeKeys[key] &= ~kDown; + } + KeyModifierMask mask = getMaskForKey(key); + if (mask != 0) { + // key is a modifier + if (isToggle(mask)) { + // key is a toggle modifier + if (press) { + m_keys[key] ^= kToggled; + m_mask ^= mask; + + // if key is half duplex then don't report it as down + if (isHalfDuplex(mask)) { + m_keys[key] &= ~kDown; + m_fakeKeys[key] &= ~kDown; + } + } + } + else { + // key is a normal modifier + if (press) { + m_mask |= mask; + } + else if (!isModifierActive(mask)) { + // no key for modifier is down anymore + m_mask &= ~mask; + } + } + LOG((CLOG_DEBUG2 "new mask: 0x%04x", m_mask)); + } +} + +void +CScreen::toggleKey(KeyModifierMask mask) +{ + // get the system key ID for this toggle key ID + MaskToKeys::const_iterator i = m_maskToKeys.find(mask); + if (i == m_maskToKeys.end()) { + return; + } + KeyButton key = i->second.front(); + + // toggle the key + fakeKeyEvent(key, true, false); + fakeKeyEvent(key, false, false); + + // toggle shadow state + m_mask ^= mask; + key &= 0xffu; + m_keys[key] ^= kToggled; +} + +bool +CScreen::isKeyToggled(KeyButton key) const +{ + key &= 0xffu; + return (key != 0 && ((m_keys[key] & kToggled) != 0)); +} diff --git a/lib/synergy/CScreen.h b/lib/synergy/CScreen.h new file mode 100644 index 00000000..7a0f3d29 --- /dev/null +++ b/lib/synergy/CScreen.h @@ -0,0 +1,357 @@ +/* + * 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 CSECONDARYSCREEN_H +#define CSECONDARYSCREEN_H + +#include "IKeyState.h" +#include "ClipboardTypes.h" +#include "MouseTypes.h" +#include "OptionTypes.h" +#include "CMutex.h" +#include "stdmap.h" + +class IClipboard; +class IPlatformScreen; +class IScreenReceiver; + +//! Platform independent screen +/*! +This is a platform independent screen. It can work as either a +primary or secondary screen. +*/ +class CScreen : public IKeyState { +public: + CScreen(IPlatformScreen* platformScreen, IScreenReceiver*); + virtual ~CScreen(); + + //! @name manipulators + //@{ + + //! Open screen + /*! + Opens the screen. + */ + void open(); + + //! Close screen + /*! + Closes the screen. + */ + void close(); + + //! 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(); + + //! Run event loop + /*! + Run the screen's event loop. This returns when it detects + the application should terminate or when exitMainLoop() is called. + mainLoop() may only be called between open() and close(). + */ + void mainLoop(); + + //! Exit event loop + /*! + Force mainLoop() to return. This call can return before + mainLoop() does (i.e. asynchronously). + */ + void exitMainLoop(); + + //! Enter screen + /*! + Called when the user navigates to this screen. + */ + void enter(); + + //! 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 wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c delta. + \c delta is positive for motion away from the user and negative for + motion towards the user. Each wheel click should generate a delta + of +/-120. + */ + void mouseWheel(SInt32 delta); + + //! 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); + + //! Install a one-shot timer + /*! + Installs a one-shot timer for \c timeout seconds and returns the + id of the timer. + */ + UInt32 addOneShotTimer(double timeout); + + //@} + //! @name accessors + //@{ + + //! Test if cursor on screen + /*! + Returns true iff the cursor is on the screen. + */ + bool isOnScreen() const; + + //! Get clipboard + /*! + Saves the contents of the system clipboard indicated by \c id. + */ + void getClipboard(ClipboardID id, IClipboard*) const; + + //! Get jump zone size + /*! + Returns 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 screen lock state + /*! + Returns true if there's any reason that the user should not be + allowed to leave the screen. Active toggle keys (excluding the + scroll lock key) are not be counted as reasons to lock to the + screen. If this method returns true it logs a message on why at + the CLOG_DEBUG level. + */ + bool isLockedToScreen() const; + + //! Get screen shape + /*! + Returns 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. + */ + void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + + //! Get cursor position + /*! + Returns the current position of the cursor in \c x,y. + */ + void getCursorPos(SInt32& x, SInt32& y) const; + + //@} + + // IKeyState overrides + virtual void updateKeys(); + virtual void releaseKeys(); + virtual void setKeyDown(KeyButton key); + virtual void setToggled(KeyModifierMask); + virtual void addModifier(KeyModifierMask, KeyButtons&); + virtual void setToggleState(KeyModifierMask); + virtual KeyButton isAnyKeyDown() const; + virtual bool isKeyDown(KeyButton) const; + virtual bool isToggle(KeyModifierMask) const; + virtual bool isHalfDuplex(KeyModifierMask) const; + virtual bool isModifierActive(KeyModifierMask) const; + virtual KeyModifierMask + getActiveModifiers() const; + virtual bool mapModifier(Keystrokes& keys, Keystrokes& undo, + KeyModifierMask mask, bool desireActive) const; + virtual KeyModifierMask + getMaskForKey(KeyButton) const; + +protected: + void enablePrimary(); + void enableSecondary(); + void disablePrimary(); + void disableSecondary(); + + void enterPrimary(); + void enterSecondary(); + void leavePrimary(); + void leaveSecondary(); + +private: + // Get the modifier mask for the current key state + KeyModifierMask getModifierMask() const; + + // Send fake keystrokes + void doKeystrokes(const Keystrokes&, SInt32 count); + + // Send a fake key event + void fakeKeyEvent(KeyButton, bool press, bool repeat) const; + + // Update the shadow state for a key + void updateKeyState(KeyButton button, + KeyButton key, bool press); + + // Toggle a modifier + void toggleKey(KeyModifierMask); + + // Test if a modifier is toggled + bool isKeyToggled(KeyButton) const; + +private: + typedef std::map ServerKeyMap; + typedef std::map MaskToKeys; + typedef std::map KeyToMask; + + CMutex m_mutex; + + // our platform dependent screen + IPlatformScreen* m_screen; + + // our screen receiver + IScreenReceiver* m_receiver; + + // 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) + bool m_numLockHalfDuplex; + bool m_capsLockHalfDuplex; + + // keyboard state + + // map server key buttons to local system keys + ServerKeyMap m_serverKeyMap; + + // system key states as set by us or the user + KeyState m_keys[256]; + + // system key states as set by us + KeyState m_fakeKeys[256]; + + // modifier info + MaskToKeys m_maskToKeys; + KeyToMask m_keyToMask; + + // current active modifiers + KeyModifierMask m_mask; + + // the toggle key state when this screen was last entered + KeyModifierMask m_toggleKeys; +}; + +#endif diff --git a/lib/synergy/CSecondaryScreen.cpp b/lib/synergy/CSecondaryScreen.cpp deleted file mode 100644 index 6dfaed19..00000000 --- a/lib/synergy/CSecondaryScreen.cpp +++ /dev/null @@ -1,781 +0,0 @@ -/* - * 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 "CSecondaryScreen.h" -#include "IScreen.h" -#include "CLock.h" -#include "CThread.h" -#include "CLog.h" - -// -// CSecondaryScreen -// - -CSecondaryScreen::CSecondaryScreen() : - m_remoteReady(false), - m_active(false), - m_toggleKeys(0), - m_screenSaverSync(true) -{ - // do nothing -} - -CSecondaryScreen::~CSecondaryScreen() -{ - // do nothing -} - -void -CSecondaryScreen::mainLoop() -{ - // change our priority - CThread::getCurrentThread().setPriority(-14); - - // run event loop - try { - LOG((CLOG_DEBUG "entering event loop")); - onPreMainLoop(); - getScreen()->mainLoop(); - onPostMainLoop(); - LOG((CLOG_DEBUG "exiting event loop")); - } - catch (...) { - onPostMainLoop(); - LOG((CLOG_DEBUG "exiting event loop")); - throw; - } -} - -void -CSecondaryScreen::exitMainLoop() -{ - getScreen()->exitMainLoop(); -} - -void -CSecondaryScreen::open() -{ - try { - // subclass hook - onPreOpen(); - - // open the screen - getScreen()->open(); - - // create and prepare our window. pretend we're active so - // we don't try to show our window until later. - { - CLock lock(&m_mutex); - assert(m_active == false); - m_active = true; - } - createWindow(); - { - CLock lock(&m_mutex); - m_active = false; - } - - // subclass hook - onPostOpen(); - - // reset options - resetOptions(); - } - catch (...) { - close(); - throw; - } -} - -void -CSecondaryScreen::close() -{ - onPreClose(); - destroyWindow(); - getScreen()->close(); - onPostClose(); -} - -void -CSecondaryScreen::remoteControl() -{ - // assume primary has all clipboards - for (ClipboardID id = 0; id < kClipboardEnd; ++id) { - grabClipboard(id); - } - - // update keyboard state - { - CLock lock(&m_mutex); - updateKeys(); - } - - // now remote ready. fake being active for call to leave(). - bool screenSaverSync; - { - CLock lock(&m_mutex); - m_remoteReady = true; - m_active = true; - - // copy screen saver synchronization state - screenSaverSync = m_screenSaverSync; - } - - // disable the screen saver if synchronization is enabled - if (screenSaverSync) { - getScreen()->openScreensaver(false); - } - - // hide the cursor - leave(); -} - -void -CSecondaryScreen::localControl() -{ - getScreen()->closeScreensaver(); - - // not remote ready anymore - CLock lock(&m_mutex); - m_remoteReady = false; -} - -void -CSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) -{ - CLock lock(&m_mutex); - assert(m_active == false); - - LOG((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); - - sync(); - - // now active - m_active = true; - - // subclass hook - onPreEnter(); - - // update our keyboard state to reflect the local state - updateKeys(); - - // toggle modifiers that don't match the desired state and - // remember previous toggle key state. - m_toggleKeys = m_mask; - setToggleState(mask); - - // warp to requested location - fakeMouseMove(x, y); - - // show mouse - hideWindow(); - - // subclass hook - onPostEnter(); -} - -void -CSecondaryScreen::leave() -{ - LOG((CLOG_INFO "leaving screen")); - CLock lock(&m_mutex); - assert(m_active == true); - - sync(); - - // subclass hook - onPreLeave(); - - // restore toggle key state - setToggleState(m_toggleKeys); - - // hide mouse - SInt32 x, y; - getScreen()->getCursorCenter(x, y); - showWindow(x, y); - - // subclass hook - onPostLeave(); - - // not active anymore - m_active = false; - - // make sure our idea of clipboard ownership is correct - getScreen()->checkClipboards(); -} - -void -CSecondaryScreen::setClipboard(ClipboardID id, - const IClipboard* clipboard) -{ - getScreen()->setClipboard(id, clipboard); -} - -void -CSecondaryScreen::grabClipboard(ClipboardID id) -{ - getScreen()->setClipboard(id, NULL); -} - -void -CSecondaryScreen::screensaver(bool activate) -{ - // get screen saver synchronization flag - bool screenSaverSync; - { - CLock lock(&m_mutex); - screenSaverSync = m_screenSaverSync; - } - - // activate/deactivation screen saver iff synchronization enabled - if (screenSaverSync) { - getScreen()->screensaver(activate); - } -} - -CSecondaryScreen::SysKeyID -CSecondaryScreen::getUnhanded(SysKeyID) const -{ - // no key represents both left and right sides of any key - return 0; -} - -CSecondaryScreen::SysKeyID -CSecondaryScreen::getOtherHanded(SysKeyID) const -{ - // no key represents both left and right sides of any key - return 0; -} - -bool -CSecondaryScreen::synthesizeCtrlAltDel(EKeyAction) -{ - // pass keys through unchanged - return false; -} - -void -CSecondaryScreen::sync() const -{ - // do nothing -} - -void -CSecondaryScreen::flush() -{ - // do nothing -} - -void -CSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) -{ - // do nothing if no keys or no repeats - if (count < 1 || keys.empty()) { - return; - } - - // generate key events - LOG((CLOG_DEBUG2 "keystrokes:")); - for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { - if (k->m_repeat) { - // repeat from here up to but not including the next key - // with m_repeat == false count times. - Keystrokes::const_iterator start = k; - for (; count > 0; --count) { - // send repeating events - for (k = start; k != keys.end() && k->m_repeat; ++k) { - LOG((CLOG_DEBUG2 " %d %s repeat", k->m_sysKeyID, k->m_press ? "down" : "up")); - fakeKeyEvent(k->m_sysKeyID, k->m_press); - } - } - - // 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 - LOG((CLOG_DEBUG2 " %d %s", k->m_sysKeyID, k->m_press ? "down" : "up")); - fakeKeyEvent(k->m_sysKeyID, k->m_press); - - // next key - ++k; - } - } - - flush(); -} - -void -CSecondaryScreen::keyDown(KeyID key, - KeyModifierMask mask, KeyButton button) -{ - CLock lock(&m_mutex); - sync(); - - // check for ctrl+alt+del emulation - if (key == kKeyDelete && - (mask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - LOG((CLOG_DEBUG "emulating ctrl+alt+del press")); - if (synthesizeCtrlAltDel(kPress)) { - return; - } - } - - // get the sequence of keys to simulate key press and the final - // modifier state. - Keystrokes keys; - SysKeyID sysKeyID; - m_mask = mapKey(keys, sysKeyID, key, m_mask, mask, kPress); - if (keys.empty()) { - // do nothing if there are no associated keys (i.e. lookup failed) - return; - } - sysKeyID &= 0xffu; - - // generate key events - doKeystrokes(keys, 1); - - // do not record button down if button or system key is 0 (invalid) - if (button != 0 && sysKeyID != 0) { - // note that key is now down - SysKeyID unhandedSysKeyID = getUnhanded(sysKeyID); - m_serverKeyMap[button] = sysKeyID; - m_keys[sysKeyID] |= kDown; - m_fakeKeys[sysKeyID] |= kDown; - if (unhandedSysKeyID != 0) { - m_keys[unhandedSysKeyID] |= kDown; - m_fakeKeys[unhandedSysKeyID] |= kDown; - } - } -} - - -void -CSecondaryScreen::keyRepeat(KeyID key, - KeyModifierMask mask, SInt32 count, KeyButton button) -{ - CLock lock(&m_mutex); - sync(); - - // if we haven't seen this button go down then ignore it - ServerKeyMap::iterator index = m_serverKeyMap.find(button); - if (index == m_serverKeyMap.end()) { - return; - } - - // get the sequence of keys to simulate key repeat and the final - // modifier state. - Keystrokes keys; - SysKeyID sysKeyID; - m_mask = mapKey(keys, sysKeyID, key, m_mask, mask, kRepeat); - if (keys.empty()) { - return; - } - sysKeyID &= 0xffu; - - // if this key shouldn't auto-repeat then ignore - if (!isAutoRepeating(sysKeyID)) { - return; - } - - // if the keycode 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 - // keycodes for the two keysyms might be different. - if (sysKeyID != index->second) { - // replace key up with previous key id but leave key down - // alone so it uses the new keycode and store that keycode - // in the server key map. - for (Keystrokes::iterator index2 = keys.begin(); - index2 != keys.end(); ++index2) { - if ((index2->m_sysKeyID & 0xffu) == sysKeyID) { - index2->m_sysKeyID = index->second; - break; - } - } - - // note that old key is now up - m_keys[index->second] &= ~kDown; - m_fakeKeys[index->second] &= ~kDown; - - // map server key to new key - index->second = sysKeyID; - - // note that new key is now down - m_keys[index->second] |= kDown; - m_fakeKeys[index->second] |= kDown; - } - - // generate key events - doKeystrokes(keys, count); -} - -void -CSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) -{ - CLock lock(&m_mutex); - sync(); - - // if we haven't seen this button go down then ignore it - ServerKeyMap::iterator index = m_serverKeyMap.find(button); - if (index == m_serverKeyMap.end()) { - return; - } - SysKeyID sysKeyID = index->second; - - // check for ctrl+alt+del emulation - if (key == kKeyDelete && - (mask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - LOG((CLOG_DEBUG "emulating ctrl+alt+del release")); - if (synthesizeCtrlAltDel(kRelease)) { - return; - } - } - - // get the sequence of keys to simulate key release - Keystrokes keys; - Keystroke keystroke; - keystroke.m_sysKeyID = sysKeyID; - keystroke.m_press = false; - keystroke.m_repeat = false; - keys.push_back(keystroke); - - // generate key events - doKeystrokes(keys, 1); - - // note that key is now up - SysKeyID unhandedSysKeyID = getUnhanded(sysKeyID); - m_serverKeyMap.erase(index); - m_keys[sysKeyID] &= ~kDown; - m_fakeKeys[sysKeyID] &= ~kDown; - if (unhandedSysKeyID != 0) { - SysKeyID otherHandedSysKeyID = getOtherHanded(sysKeyID); - if ((m_keys[otherHandedSysKeyID] & kDown) == 0) { - m_keys[unhandedSysKeyID] &= ~kDown; - m_fakeKeys[unhandedSysKeyID] &= ~kDown; - } - } - - // get the new modifier state - mask = getModifierKeyMask(sysKeyID); - if (mask != 0) { - // key is a modifier key - if ((mask & (KeyModifierCapsLock | - KeyModifierNumLock | - KeyModifierScrollLock)) != 0) { - // modifier is a toggle - m_mask ^= mask; - } - else if (!isModifierActive(sysKeyID)) { - // all keys for this modifier are released - m_mask &= ~mask; - } - } -} - -void -CSecondaryScreen::mouseDown(ButtonID button) -{ - CLock lock(&m_mutex); - sync(); - fakeMouseButton(button, true); - flush(); -} - -void -CSecondaryScreen::mouseUp(ButtonID button) -{ - CLock lock(&m_mutex); - sync(); - fakeMouseButton(button, false); - flush(); -} - -void -CSecondaryScreen::mouseMove(SInt32 x, SInt32 y) -{ - CLock lock(&m_mutex); - sync(); - fakeMouseMove(x, y); - flush(); -} - -void -CSecondaryScreen::mouseWheel(SInt32 delta) -{ - CLock lock(&m_mutex); - sync(); - fakeMouseWheel(delta); - flush(); -} - -void -CSecondaryScreen::setToggleState(KeyModifierMask mask) -{ - // toggle modifiers that don't match the desired state - KeyModifierMask different = (m_mask ^ mask); - if ((different & KeyModifierCapsLock) != 0) { - toggleKey(kKeyCapsLock, KeyModifierCapsLock); - } - if ((different & KeyModifierNumLock) != 0) { - toggleKey(kKeyNumLock, KeyModifierNumLock); - } - if ((different & KeyModifierScrollLock) != 0) { - toggleKey(kKeyScrollLock, KeyModifierScrollLock); - } -} - -void -CSecondaryScreen::resetOptions() -{ - // set screen saver synchronization flag and see if we need to - // update the screen saver synchronization. reset other options. - bool screenSaverSyncOn; - { - CLock lock(&m_mutex); - screenSaverSyncOn = (!m_screenSaverSync && m_remoteReady); - m_screenSaverSync = true; - m_numLockHalfDuplex = false; - m_capsLockHalfDuplex = false; - } - - // update screen saver synchronization - if (screenSaverSyncOn) { - getScreen()->openScreensaver(false); - } -} - -void -CSecondaryScreen::setOptions(const COptionsList& options) -{ - // update options - bool updateScreenSaverSync = false; - bool oldScreenSaverSync; - { - CLock lock(&m_mutex); - oldScreenSaverSync = m_screenSaverSync; - for (UInt32 i = 0, n = options.size(); i < n; i += 2) { - if (options[i] == kOptionScreenSaverSync) { - updateScreenSaverSync = true; - m_screenSaverSync = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off")); - } - else if (options[i] == kOptionHalfDuplexCapsLock) { - m_capsLockHalfDuplex = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off")); - } - else if (options[i] == kOptionHalfDuplexNumLock) { - m_numLockHalfDuplex = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off")); - } - } - if (!m_remoteReady || oldScreenSaverSync == m_screenSaverSync) { - updateScreenSaverSync = false; - } - } - - // update screen saver synchronization - if (updateScreenSaverSync) { - if (oldScreenSaverSync) { - getScreen()->closeScreensaver(); - } - else { - getScreen()->openScreensaver(false); - } - } -} - -bool -CSecondaryScreen::isActive() const -{ - CLock lock(&m_mutex); - return m_active; -} - -void -CSecondaryScreen::getClipboard(ClipboardID id, - IClipboard* clipboard) const -{ - getScreen()->getClipboard(id, clipboard); -} - -SInt32 -CSecondaryScreen::getJumpZoneSize() const -{ - return 0; -} - -void -CSecondaryScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const -{ - sync(); - getScreen()->getShape(x, y, w, h); -} - -void -CSecondaryScreen::getCursorPos(SInt32& x, SInt32& y) const -{ - sync(); - getScreen()->getCursorPos(x, y); -} - -void -CSecondaryScreen::onPreMainLoop() -{ - // do nothing -} - -void -CSecondaryScreen::onPostMainLoop() -{ - // do nothing -} - -void -CSecondaryScreen::onPreOpen() -{ - // do nothing -} - -void -CSecondaryScreen::onPostOpen() -{ - // do nothing -} - -void -CSecondaryScreen::onPreClose() -{ - // do nothing -} - -void -CSecondaryScreen::onPostClose() -{ - // do nothing -} - -void -CSecondaryScreen::onPreEnter() -{ - // do nothing -} - -void -CSecondaryScreen::onPostEnter() -{ - // do nothing -} - -void -CSecondaryScreen::onPreLeave() -{ - // do nothing -} - -void -CSecondaryScreen::onPostLeave() -{ - // do nothing -} - -void -CSecondaryScreen::updateKeys() -{ - sync(); - - // clear key state - memset(m_keys, 0, sizeof(m_keys)); - memset(m_fakeKeys, 0, sizeof(m_fakeKeys)); - - // let subclass set m_keys - updateKeys(m_keys); - - // get m_mask from subclass - m_mask = getModifiers(); - LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask)); -} - -void -CSecondaryScreen::releaseKeys() -{ - CLock lock(&m_mutex); - sync(); - - // release keys that we've synthesized a press for and only those - // keys. we don't want to synthesize a release on a key the user - // is still physically pressing. - for (UInt32 i = 1; i < 256; ++i) { - if ((m_fakeKeys[i] & kDown) != 0) { - fakeKeyEvent(i, false); - m_keys[i] &= ~kDown; - m_fakeKeys[i] &= ~kDown; - } - } - - flush(); -} - -void -CSecondaryScreen::toggleKey(KeyID keyID, KeyModifierMask mask) -{ - // get the system key ID for this toggle key ID - SysKeyID sysKeyID = getToggleSysKey(keyID); - if (sysKeyID == 0) { - return; - } - - // toggle the key - if (isKeyHalfDuplex(keyID)) { - // "half-duplex" toggle - fakeKeyEvent(sysKeyID, (m_mask & mask) == 0); - } - else { - // normal toggle - fakeKeyEvent(sysKeyID, true); - fakeKeyEvent(sysKeyID, false); - } - flush(); - - // toggle shadow state - m_mask ^= mask; - sysKeyID &= 0xffu; - m_keys[sysKeyID] ^= kToggled; - m_fakeKeys[sysKeyID] ^= kToggled; -} - -bool -CSecondaryScreen::isKeyDown(SysKeyID sysKeyID) const -{ - sysKeyID &= 0xffu; - return (sysKeyID != 0 && ((m_keys[sysKeyID] & kDown) != 0)); -} - -bool -CSecondaryScreen::isKeyToggled(SysKeyID sysKeyID) const -{ - sysKeyID &= 0xffu; - return (sysKeyID != 0 && ((m_keys[sysKeyID] & kToggled) != 0)); -} - -bool -CSecondaryScreen::isKeyHalfDuplex(KeyID keyID) const -{ - return ((keyID == kKeyCapsLock && m_capsLockHalfDuplex) || - (keyID == kKeyNumLock && m_numLockHalfDuplex)); -} diff --git a/lib/synergy/CSecondaryScreen.h b/lib/synergy/CSecondaryScreen.h deleted file mode 100644 index c69692b9..00000000 --- a/lib/synergy/CSecondaryScreen.h +++ /dev/null @@ -1,446 +0,0 @@ -/* - * 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 CSECONDARYSCREEN_H -#define CSECONDARYSCREEN_H - -#include "ClipboardTypes.h" -#include "KeyTypes.h" -#include "MouseTypes.h" -#include "OptionTypes.h" -#include "CMutex.h" -#include "stdmap.h" -#include "stdvector.h" - -class IClipboard; -class IScreen; - -//! Generic client-side screen -/*! -This is a platform independent base class for secondary screen -implementations. A secondary screen is a client-side screen. -Each platform will derive a class from CSecondaryScreen to handle -platform dependent operations. -*/ -class CSecondaryScreen { -public: - CSecondaryScreen(); - virtual ~CSecondaryScreen(); - - //! @name manipulators - //@{ - - //! Open screen - /*! - Opens the screen. It also causes events to the reported to an - IScreenReceiver (which is set through some other interface). - Calls close() before returning (rethrowing) if it fails for any - reason. - */ - void open(); - - //! Run event loop - /*! - Run the screen's event loop. This returns when it detects - the application should terminate or when exitMainLoop() is called. - mainLoop() may only be called between open() and close(). - */ - void mainLoop(); - - //! Exit event loop - /*! - Force mainLoop() to return. This call can return before - mainLoop() does (i.e. asynchronously). - */ - void exitMainLoop(); - - //! Prepare for remote control - /*! - Prepares the screen for remote control by the server. In - particular, it disables the screen saver. - */ - void remoteControl(); - - //! Release from remote control - /*! - Cleans up the screen from remote control by the server. In - particular, it enables the screen saver. It also synthesizes - key up events for any keys that are logically down; without - this the client will leave its keyboard in the wrong logical - state. - */ - void localControl(); - - //! Close screen - /*! - Closes the screen. - */ - void close(); - - //! Enter screen - /*! - Called when the user navigates to this secondary screen. Warps - the cursor to the absolute coordinates \c x,y and unhides - it. Also prepares to synthesize input events. - */ - void enter(SInt32 x, SInt32 y, KeyModifierMask mask); - - //! Leave screen - /*! - Called when the user navigates off the secondary screen. Cleans - up input event synthesis and hides the cursor. - */ - void leave(); - - //! 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 wheel motion - /*! - Synthesize mouse events to generate mouse wheel motion of \c delta. - \c delta is positive for motion away from the user and negative for - motion towards the user. Each wheel click should generate a delta - of +/-120. - */ - void mouseWheel(SInt32 delta); - - //! Notify of options changes - /*! - Reset all options to their default values. Overrides should call - the superclass's method. - */ - virtual void resetOptions(); - - //! 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. Overrides - should call the superclass's method. - */ - virtual void setOptions(const COptionsList& options); - - //@} - //! @name accessors - //@{ - - //! Test if active - /*! - Returns true iff the screen is active (i.e. the user has entered - the screen). Note this is the reverse of a primary screen. - */ - bool isActive() const; - - //! Get clipboard - /*! - Saves the contents of the system clipboard indicated by \c id. - */ - void getClipboard(ClipboardID id, IClipboard*) 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 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; - - //! Get cursor position - /*! - Return the current position of the cursor in \c x,y. - */ - virtual void getCursorPos(SInt32& x, SInt32& y) const; - - //! Get screen - /*! - Return the platform dependent screen. - */ - virtual IScreen* getScreen() const = 0; - - //@} - -protected: - typedef UInt8 KeyState; - typedef UInt32 SysKeyID; - enum EKeyState { kDown = 0x01, kToggled = 0x80 }; - enum EKeyAction { kPress, kRelease, kRepeat }; - class Keystroke { - public: - SysKeyID m_sysKeyID; - bool m_press; - bool m_repeat; - }; - typedef std::vector Keystrokes; - typedef std::map ServerKeyMap; - - void updateKeys(); - void releaseKeys(); - void doKeystrokes(const Keystrokes&, SInt32 count); - bool isKeyDown(SysKeyID) const; - bool isKeyToggled(SysKeyID) const; - bool isKeyHalfDuplex(KeyID) const; - - //! Pre-mainLoop() hook - /*! - Called on entry to mainLoop(). Override to perform platform specific - operations. Default does nothing. May throw. - */ - virtual void onPreMainLoop(); - - //! Post-mainLoop() hook - /*! - Called on exit from mainLoop(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostMainLoop(); - - //! Pre-open() hook - /*! - Called on entry to open(). Override to perform platform specific - operations. Default does nothing. May throw. - */ - virtual void onPreOpen(); - - //! Post-open() hook - /*! - Called on exit from open() iff the open was successful. Default - does nothing. May throw. - */ - virtual void onPostOpen(); - - //! Pre-close() hook - /*! - Called on entry to close(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPreClose(); - - //! Post-close() hook - /*! - Called on exit from close(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostClose(); - - //! Pre-enter() hook - /*! - Called on entry to enter() after desktop synchronization. Override - to perform platform specific operations. Default does nothing. May - \b not throw. - */ - virtual void onPreEnter(); - - //! Post-enter() hook - /*! - Called on exit from enter(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostEnter(); - - //! Pre-leave() hook - /*! - Called on entry to leave() after desktop synchronization. Override - to perform platform specific operations. Default does nothing. May - \b not throw. - */ - virtual void onPreLeave(); - - //! Post-leave() hook - /*! - Called on exit from leave(). Override to perform platform specific - operations. Default does nothing. May \b not throw. - */ - virtual void onPostLeave(); - - //! Create window - /*! - Called to create the window. This window is generally used to - receive events and hide the cursor. - */ - virtual void createWindow() = 0; - - //! Destroy window - /*! - Called to destroy the window created by createWindow(). - */ - virtual void destroyWindow() = 0; - - //! Show window - /*! - Called when the user navigates off this secondary screen. It needn't - actually show the window created by createWindow() but it must move - the cursor to x,y, hide it, and clean up event synthesis. - */ - virtual void showWindow(SInt32 x, SInt32 y) = 0; - - //! Hide window - /*! - Called when the user navigates to this secondary screen. It should - hide the window (if shown), show the cursor, and prepare to synthesize - input events. - */ - virtual void hideWindow() = 0; - - //! Synchronize key state - /*! - Save the current keyboard state. Normally a screen will save - the keyboard state in this method and use this shadow state, - available through isKeyDown() and getKeyState(), when - synthesizing events. - */ - virtual void updateKeys(KeyState* sysKeyStates) = 0; - - //! Get modifier key state - /*! - Return the current keyboard modifier state. - */ - virtual KeyModifierMask getModifiers() const = 0; - - //! Synchronize toggle key state - /*! - Toggles modifiers that don't match the given state so that they do. - */ - void setToggleState(KeyModifierMask); - - virtual SysKeyID getUnhanded(SysKeyID) const; - virtual SysKeyID getOtherHanded(SysKeyID) const; - virtual bool isAutoRepeating(SysKeyID) const = 0; - virtual KeyModifierMask getModifierKeyMask(SysKeyID) const = 0; - virtual bool isModifierActive(SysKeyID) const = 0; - virtual SysKeyID getToggleSysKey(KeyID keyID) const = 0; - virtual bool synthesizeCtrlAltDel(EKeyAction); - virtual void sync() const; - virtual void flush(); - - virtual KeyModifierMask - mapKey(Keystrokes&, SysKeyID& sysKeyID, KeyID, - KeyModifierMask currentMask, - KeyModifierMask desiredMask, EKeyAction) const = 0; - virtual void fakeKeyEvent(SysKeyID, bool press) const = 0; - virtual void fakeMouseButton(ButtonID, bool press) const = 0; - - //! Warp cursor - /*! - Warp the cursor to the absolute coordinates \c x,y. - */ - virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; - - virtual void fakeMouseWheel(SInt32 delta) const = 0; - -private: - void toggleKey(KeyID, KeyModifierMask); - -private: - CMutex m_mutex; - - // true if ready for remote control - bool m_remoteReady; - - // m_active is true if this screen has been entered - bool m_active; - - // true if screen saver should be synchronized to server - bool m_screenSaverSync; - - // map server key buttons to local system keys - ServerKeyMap m_serverKeyMap; - - // system key states as set by us or the user - KeyState m_keys[256]; - - // system key states as set by us - KeyState m_fakeKeys[256]; - - // current active modifiers -// XXX -- subclasses still have and use this - KeyModifierMask m_mask; - - // the toggle key state when this screen was last entered - KeyModifierMask m_toggleKeys; - - // note toggle keys that toggles on up/down (false) or on - // transition (true) - bool m_numLockHalfDuplex; - bool m_capsLockHalfDuplex; -}; - -#endif diff --git a/lib/synergy/IKeyState.h b/lib/synergy/IKeyState.h new file mode 100644 index 00000000..14a415c2 --- /dev/null +++ b/lib/synergy/IKeyState.h @@ -0,0 +1,156 @@ +/* + * 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 "CString.h" +#include "stdvector.h" + +class IKeyState : public IInterface { +public: + class Keystroke { + public: + KeyButton m_key; + bool m_press; + bool m_repeat; + }; + + typedef std::vector Keystrokes; + typedef std::vector KeyButtons; + + //! @name manipulators + //@{ + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state and current keyboard mapping. + */ + virtual void updateKeys() = 0; + + //! Release fake pressed keys + /*! + Send fake key events to release keys that aren't physically pressed + but are logically pressed. + */ + virtual void releaseKeys() = 0; + + //! Mark key as being down + /*! + Sets the state of \c key to down. + */ + virtual void setKeyDown(KeyButton key) = 0; + + //! Mark modifier as being toggled on + /*! + Sets the state of the keys for the given (single) modifier to be + toggled on. + */ + virtual void setToggled(KeyModifierMask) = 0; + + //! Add keys for modifier + /*! + Sets the keys that are mapped to the given (single) modifier. For + example, if buttons 5 and 23 were mapped to KeyModifierShift (perhaps + as left and right shift keys) then the mask would be KeyModifierShift + and \c keys would contain 5 and 23. A modifier with no keys is + ignored. All keys must be valid (not zero). \c keys may be modified + by the call. + */ + virtual void addModifier(KeyModifierMask, KeyButtons& keys) = 0; + + //! Set toggle key state + /*! + Update the local toggle key state to match the given state. + */ + virtual void setToggleState(KeyModifierMask) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if any key is down + /*! + If any key is down then returns one of those keys. Otherwise returns 0. + */ + virtual KeyButton isAnyKeyDown() const = 0; + + //! Test if key is pressed + /*! + Returns true iff the given key is down. Half-duplex toggles + should always return false. + */ + virtual bool isKeyDown(KeyButton) const = 0; + + //! Test if modifier is a toggle + /*! + Returns true iff the given (single) modifier is a toggle. + */ + virtual bool isToggle(KeyModifierMask) const = 0; + + //! Test if modifier is half-duplex + /*! + Returns true iff the given (single) modifier is a half-duplex + toggle key. + */ + virtual bool isHalfDuplex(KeyModifierMask) const = 0; + + //! Test if modifier is active + /*! + Returns true iff the given (single) modifier is currently active. + */ + virtual bool isModifierActive(KeyModifierMask) const = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active. + */ + virtual KeyModifierMask + getActiveModifiers() const = 0; + + //! Get key events to change modifier state + /*! + Retrieves the key events necessary to activate (\c desireActive is true) + or deactivate (\c desireActive is false) the modifier given by \c mask + by pushing them onto the back of \c keys. \c mask must specify exactly + one modifier. \c undo receives the key events necessary to restore the + modifier's previous state. They're pushed onto \c undo in the reverse + order they should be executed. Returns true if the modifier can be + adjusted, false otherwise. + */ + virtual bool mapModifier(Keystrokes& keys, Keystrokes& undo, + KeyModifierMask mask, bool desireActive) const = 0; + + //! Get modifier mask for key + /*! + Returns the modifier mask for \c key. If \c key is not a modifier + key then returns 0. + */ + virtual KeyModifierMask + getMaskForKey(KeyButton) const = 0; + + //@} + +protected: + typedef UInt8 KeyState; + enum EKeyState { + kDown = 0x01, //!< Key is down + kToggled = 0x80 //!< Key is toggled on + }; +}; + +#endif diff --git a/lib/synergy/IScreen.h b/lib/synergy/IPlatformScreen.h similarity index 58% rename from lib/synergy/IScreen.h rename to lib/synergy/IPlatformScreen.h index 16769821..149c5526 100644 --- a/lib/synergy/IScreen.h +++ b/lib/synergy/IPlatformScreen.h @@ -12,21 +12,24 @@ * GNU General Public License for more details. */ -#ifndef ISCREEN_H -#define ISCREEN_H +#ifndef IPLATFORMSCREEN_H +#define IPLATFORMSCREEN_H -#include "IInterface.h" +#include "IPrimaryScreen.h" +#include "ISecondaryScreen.h" #include "ClipboardTypes.h" +#include "OptionTypes.h" class IClipboard; +class IKeyState; //! Screen interface /*! This interface defines the methods common to all platform dependent -screen implementations that are use by both primary and secondary +screen implementations that are used by both primary and secondary screens. */ -class IScreen : public IInterface { +class IPlatformScreen : public IPrimaryScreen, public ISecondaryScreen { public: //! @name manipulators //@{ @@ -37,7 +40,29 @@ public: if the screen cannot be opened but retrying later may succeed. Otherwise throw some other XScreenOpenFailure exception. */ - virtual void open() = 0; + virtual void open(IKeyState*) = 0; + + //! Close screen + /*! + Called to close the screen. close() should quietly ignore calls + that don't have a matching successful call to open(). + */ + virtual void close() = 0; + + //! 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; //! Run event loop /*! @@ -54,12 +79,20 @@ public: */ virtual void exitMainLoop() = 0; - //! Close screen + //! Enter screen /*! - Called to close the screen. close() should quietly ignore calls - that don't have a matching successful call to open(). + Called when the user navigates to this screen. */ - virtual void close() = 0; + 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 /*! @@ -84,6 +117,7 @@ public: it's closed. If \c notify is false then the screen saver is disabled on open and restored on close. */ +// XXX -- pass an interface pointer, not a notify flag virtual void openScreensaver(bool notify) = 0; //! Close screen saver @@ -101,20 +135,35 @@ public: */ virtual void screensaver(bool activate) = 0; - //! Attach to desktop + //! Notify of options changes /*! - Called to ensure that this thread is attached to the visible desktop. - This is mainly intended for microsoft windows which has an artificial - distinction between desktops where a thread cannot interact with the - visible desktop unless the thread is attached to that desktop. Since - it doesn't report when the visible desktop changes we must poll. + Reset all options to their default values. */ - virtual void syncDesktop() = 0; + 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; + + //! Get keyboard state + /*! + Put the current keyboard state into the IKeyState passed to \c open(). + */ + virtual void updateKeys() = 0; //@} //! @name accessors //@{ + //! Test if is primary screen + /*! + Return true iff this screen is a primary screen. + */ + virtual bool isPrimary() const = 0; + //! Get clipboard /*! Save the contents of the clipboard indicated by \c id and return @@ -136,15 +185,26 @@ public: */ virtual void getCursorPos(SInt32& x, SInt32& y) 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; - //@} + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides) = 0; + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual UInt32 addOneShotTimer(double timeout) = 0; + virtual SInt32 getJumpZoneSize() const = 0; + virtual bool isAnyMouseButtonDown() const = 0; + virtual const char* getKeyName(KeyButton) const = 0; + + // ISecondaryScreen overrides + virtual void fakeKeyEvent(KeyButton id, bool press) const = 0; + virtual bool fakeCtrlAltDel() const = 0; + virtual void fakeMouseButton(ButtonID id, bool press) const = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; + virtual void fakeMouseWheel(SInt32 delta) const = 0; + virtual KeyButton mapKey(IKeyState::Keystrokes&, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const = 0; }; #endif diff --git a/lib/synergy/IPrimaryScreen.h b/lib/synergy/IPrimaryScreen.h new file mode 100644 index 00000000..560436f3 --- /dev/null +++ b/lib/synergy/IPrimaryScreen.h @@ -0,0 +1,84 @@ +/* + * 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 "IKeyState.h" + +//! Primary screen interface +/*! +This interface defines the methods common to all platform dependent +primary screen implementations. +*/ +class IPrimaryScreen : public IInterface { +public: +// XXX -- may need an interface for sending events + //! @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; + + //! Install a one-shot timer + /*! + Installs a one-shot timer for \c timeout seconds and returns the + id of the timer. + */ +// XXX -- need to specify the receiver of the event. or we should +// pass a job. need a method to remove the timer? + virtual UInt32 addOneShotTimer(double timeout) = 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. + */ + virtual bool isAnyMouseButtonDown() const = 0; + + //! Get name of key + /*! + Return a string describing the given key. + */ + virtual const char* getKeyName(KeyButton) const = 0; + + //@} +}; + +#endif diff --git a/lib/synergy/IScreenEventHandler.h b/lib/synergy/IScreenEventHandler.h deleted file mode 100644 index 699d295c..00000000 --- a/lib/synergy/IScreenEventHandler.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 ISCREENEVENTHANDLER_H -#define ISCREENEVENTHANDLER_H - -#include "IInterface.h" - -// the platform screen should define this -class CEvent; - -class IScreen; - -//! Screen event handler interface -/*! -This is the interface through which IScreen sends notification of events. -Each platform will derive two types from IScreenEventHandler, one -for handling events on the primary screen and one for the -secondary screen. The header file with the IScreen subclass for -each platform should define the CEvent type, which depends on the -type of native events for that platform. -*/ -class IScreenEventHandler : public IInterface { -public: - //! @name manipulators - //@{ - - //! Notify of screen saver change - /*! - Called when the screensaver is activated or deactivated. - */ - virtual void onScreensaver(bool activated) = 0; - - //! Event filtering - /*! - Called for each event before event translation and dispatch. Return - true to skip translation and dispatch. Subclasses should call the - superclass's version first and return true if it returns true. - */ - virtual bool onPreDispatch(const CEvent* event) = 0; - - //! Event handling - /*! - Called to handle an event. Iff the event was handled return true and - store the result, if any, in event->m_result, which defaults to zero. - */ - virtual bool onEvent(CEvent* event) = 0; - - //! Notify of one-shot timer expiration - /*! - Called when a one-shot timer expires. - */ - virtual void onOneShotTimerExpired(UInt32 id) = 0; - - //@} -}; - -#endif diff --git a/lib/synergy/IPrimaryScreenFactory.h b/lib/synergy/IScreenFactory.h similarity index 64% rename from lib/synergy/IPrimaryScreenFactory.h rename to lib/synergy/IScreenFactory.h index 630449a5..8a5ab410 100644 --- a/lib/synergy/IPrimaryScreenFactory.h +++ b/lib/synergy/IScreenFactory.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2002 Chris Schoeneman + * 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 @@ -12,27 +12,29 @@ * GNU General Public License for more details. */ -#ifndef IPRIMARYSCREENFACTORY_H -#define IPRIMARYSCREENFACTORY_H +#ifndef ISCREENFACTORY_H +#define ISCREENFACTORY_H #include "IInterface.h" -class CPrimaryScreen; class IPrimaryScreenReceiver; +class IPlatformScreen; class IScreenReceiver; //! Primary screen factory interface /*! -This interface provides factory methods to create primary screens. +This interface provides factory methods to create primary and +secondary screens. */ -class IPrimaryScreenFactory : public IInterface { +class IScreenFactory : public IInterface { public: //! Create screen /*! - Create and return a primary screen. The caller must delete the - returned object. + Create and return a screen. The caller must delete the returned + object. The screen is a primary screen iff the IPrimaryScreenReceiver + is not NULL. */ - virtual CPrimaryScreen* + virtual IPlatformScreen* create(IScreenReceiver*, IPrimaryScreenReceiver*) = 0; }; diff --git a/lib/synergy/ISecondaryScreen.h b/lib/synergy/ISecondaryScreen.h new file mode 100644 index 00000000..0a556d59 --- /dev/null +++ b/lib/synergy/ISecondaryScreen.h @@ -0,0 +1,82 @@ +/* + * 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 "IKeyState.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 key press/release + /*! + Synthesize a press or release of key \c id. + */ + virtual void fakeKeyEvent(KeyButton id, bool press) const = 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() const = 0; + + //! 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 wheel + /*! + Synthesize a mouse wheel event of amount \c delta. + */ + virtual void fakeMouseWheel(SInt32 delta) const = 0; + + //! Map key press/repeat to keystrokes + /*! + Convert a press/repeat of key \c id with the modifiers as given + in \c desiredMask into the keystrokes necessary to synthesize + that key event. This may expand into multiple keys due to modifiers + that don't match the current modifier state from \c keyState, or to + needing to compose a character using dead key, or to other reasons. + Return the platform specific code of the key being pressed. If \c id + cannot be mapped or if \c isAutoRepeat is true and the key does not + auto-repeat then return 0. + */ + virtual KeyButton mapKey(IKeyState::Keystrokes&, + const IKeyState& keyState, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const = 0; + + //@} +}; + +#endif diff --git a/lib/synergy/ISecondaryScreenFactory.h b/lib/synergy/ISecondaryScreenFactory.h deleted file mode 100644 index 03667ec8..00000000 --- a/lib/synergy/ISecondaryScreenFactory.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 ISECONDARYSCREENFACTORY_H -#define ISECONDARYSCREENFACTORY_H - -#include "IInterface.h" - -class CSecondaryScreen; -class IScreenReceiver; - -//! Secondary screen factory interface -/*! -This interface provides factory methods to create secondary screens. -*/ -class ISecondaryScreenFactory : public IInterface { -public: - //! Create screen - /*! - Create and return a secondary screen. The caller must delete the - returned object. - */ - virtual CSecondaryScreen* - create(IScreenReceiver*) = 0; -}; - -#endif diff --git a/lib/synergy/Makefile.am b/lib/synergy/Makefile.am index 4f1f1353..9923fa6e 100644 --- a/lib/synergy/Makefile.am +++ b/lib/synergy/Makefile.am @@ -28,27 +28,26 @@ libsynergy_a_SOURCES = \ CClipboard.cpp \ CInputPacketStream.cpp \ COutputPacketStream.cpp \ - CPrimaryScreen.cpp \ CProtocolUtil.cpp \ - CSecondaryScreen.cpp \ + CScreen.cpp \ XScreen.cpp \ XSynergy.cpp \ CClipboard.h \ CInputPacketStream.h \ COutputPacketStream.h \ - CPrimaryScreen.h \ CProtocolUtil.h \ - CSecondaryScreen.h \ + CScreen.h \ ClipboardTypes.h \ IClient.h \ IClipboard.h \ - IPrimaryScreenFactory.h \ + IKeyState.h \ + IPlatformScreen.h \ + IPrimaryScreen.h \ IPrimaryScreenReceiver.h \ - IScreen.h \ - IScreenEventHandler.h \ + IScreenFactory.h \ IScreenReceiver.h \ IScreenSaver.h \ - ISecondaryScreenFactory.h \ + ISecondaryScreen.h \ IServer.h \ KeyTypes.h \ MouseTypes.h \ diff --git a/lib/synergy/libsynergy.dsp b/lib/synergy/libsynergy.dsp index 583271c6..928405ea 100644 --- a/lib/synergy/libsynergy.dsp +++ b/lib/synergy/libsynergy.dsp @@ -99,15 +99,11 @@ SOURCE=.\COutputPacketStream.cpp # End Source File # Begin Source File -SOURCE=.\CPrimaryScreen.cpp -# End Source File -# Begin Source File - SOURCE=.\CProtocolUtil.cpp # End Source File # Begin Source File -SOURCE=.\CSecondaryScreen.cpp +SOURCE=.\CScreen.cpp # End Source File # Begin Source File @@ -139,15 +135,11 @@ SOURCE=.\COutputPacketStream.h # End Source File # Begin Source File -SOURCE=.\CPrimaryScreen.h -# End Source File -# Begin Source File - SOURCE=.\CProtocolUtil.h # End Source File # Begin Source File -SOURCE=.\CSecondaryScreen.h +SOURCE=.\CScreen.h # End Source File # Begin Source File @@ -159,7 +151,15 @@ SOURCE=.\IClipboard.h # End Source File # Begin Source File -SOURCE=.\IPrimaryScreenFactory.h +SOURCE=.\IKeyState.h +# End Source File +# Begin Source File + +SOURCE=.\IPlatformScreen.h +# End Source File +# Begin Source File + +SOURCE=.\IPrimaryScreen.h # End Source File # Begin Source File @@ -167,11 +167,7 @@ SOURCE=.\IPrimaryScreenReceiver.h # End Source File # Begin Source File -SOURCE=.\IScreen.h -# End Source File -# Begin Source File - -SOURCE=.\IScreenEventHandler.h +SOURCE=.\IScreenFactory.h # End Source File # Begin Source File @@ -183,7 +179,7 @@ SOURCE=.\IScreenSaver.h # End Source File # Begin Source File -SOURCE=.\ISecondaryScreenFactory.h +SOURCE=.\ISecondaryScreen.h # End Source File # Begin Source File