From e2a31e8b66c5a98cdf78249342a70247b965c52e Mon Sep 17 00:00:00 2001 From: crs Date: Fri, 26 Mar 2004 20:59:26 +0000 Subject: [PATCH] Converted win32 to new keyboard state tracking design. Also changed locking to screen so that keys no longer count (only mouse buttons and scroll lock toggled on). This is to deal with the unreliability of key event reporting which can leave us locked to a screen with no key physically pressed. The result of this is that clients get key repeats and releases without the corresponding key press. CKeyState handles this by discarding repeat/release events on keys it hasn't seen go down. Also made a few other minor fixes to win32 keyboard handling. --- lib/platform/CMSWindowsDesks.cpp | 865 ++++++++++++++ lib/platform/CMSWindowsDesks.h | 252 ++++ lib/platform/CMSWindowsDesktop.cpp | 75 -- lib/platform/CMSWindowsDesktop.h | 58 - lib/platform/CMSWindowsKeyMapper.h | 196 ---- ...wsKeyMapper.cpp => CMSWindowsKeyState.cpp} | 867 +++++++------- lib/platform/CMSWindowsKeyState.h | 130 +++ lib/platform/CMSWindowsScreen.cpp | 1009 ++--------------- lib/platform/CMSWindowsScreen.h | 125 +- lib/platform/CSynergyHook.cpp | 8 +- lib/platform/CXWindowsKeyState.cpp | 9 +- lib/platform/CXWindowsKeyState.h | 1 + lib/platform/CXWindowsScreen.cpp | 12 +- lib/platform/CXWindowsScreen.h | 1 - lib/platform/platform.dsp | 8 +- lib/server/CServer.cpp | 16 +- lib/server/CServer.h | 6 + lib/synergy/CKeyState.cpp | 26 +- lib/synergy/CKeyState.h | 11 +- lib/synergy/CPlatformScreen.cpp | 6 + lib/synergy/CPlatformScreen.h | 2 +- lib/synergy/CScreen.cpp | 16 +- lib/synergy/IKeyState.h | 7 + lib/synergy/IPlatformScreen.h | 2 +- lib/synergy/ISecondaryScreen.h | 7 - lib/synergy/libsynergy.dsp | 20 + 26 files changed, 1974 insertions(+), 1761 deletions(-) create mode 100644 lib/platform/CMSWindowsDesks.cpp create mode 100644 lib/platform/CMSWindowsDesks.h delete mode 100644 lib/platform/CMSWindowsDesktop.cpp delete mode 100644 lib/platform/CMSWindowsDesktop.h delete mode 100644 lib/platform/CMSWindowsKeyMapper.h rename lib/platform/{CMSWindowsKeyMapper.cpp => CMSWindowsKeyState.cpp} (72%) create mode 100644 lib/platform/CMSWindowsKeyState.h diff --git a/lib/platform/CMSWindowsDesks.cpp b/lib/platform/CMSWindowsDesks.cpp new file mode 100644 index 00000000..0e2b7734 --- /dev/null +++ b/lib/platform/CMSWindowsDesks.cpp @@ -0,0 +1,865 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "CMSWindowsDesks.h" +#include "CMSWindowsDesktop.h" +#include "CMSWindowsScreen.h" +#include "IScreenSaver.h" +#include "XScreen.h" +#include "CLock.h" +#include "CThread.h" +#include "CLog.h" +#include "IEventQueue.h" +#include "IJob.h" +#include "TMethodEventJob.h" +#include "TMethodJob.h" +#include "CArchMiscWindows.h" +#include + +// these are only defined when WINVER >= 0x0500 +#if !defined(SPI_GETMOUSESPEED) +#define SPI_GETMOUSESPEED 112 +#endif +#if !defined(SPI_SETMOUSESPEED) +#define SPI_SETMOUSESPEED 113 +#endif + +// 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 +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// ; +#define SYNERGY_MSG_SWITCH SYNERGY_HOOK_LAST_MSG + 1 +// ; +#define SYNERGY_MSG_ENTER SYNERGY_HOOK_LAST_MSG + 2 +// ; +#define SYNERGY_MSG_LEAVE SYNERGY_HOOK_LAST_MSG + 3 +// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code +#define SYNERGY_MSG_FAKE_KEY SYNERGY_HOOK_LAST_MSG + 4 + // flags, XBUTTON id +#define SYNERGY_MSG_FAKE_BUTTON SYNERGY_HOOK_LAST_MSG + 5 +// x; y +#define SYNERGY_MSG_FAKE_MOVE SYNERGY_HOOK_LAST_MSG + 6 +// delta; +#define SYNERGY_MSG_FAKE_WHEEL SYNERGY_HOOK_LAST_MSG + 7 +// POINT*; +#define SYNERGY_MSG_CURSOR_POS SYNERGY_HOOK_LAST_MSG + 8 +// IKeyState*; +#define SYNERGY_MSG_SYNC_KEYS SYNERGY_HOOK_LAST_MSG + 9 +// install; +#define SYNERGY_MSG_SCREENSAVER SYNERGY_HOOK_LAST_MSG + 10 + +// +// CMSWindowsDesks +// + +CMSWindowsDesks::CMSWindowsDesks( + bool isPrimary, HINSTANCE hookLibrary, + const IScreenSaver* screensaver, IJob* updateKeys) : + m_isPrimary(isPrimary), + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_timer(NULL), + m_screensaver(screensaver), + m_screensaverNotify(false), + m_activeDesk(NULL), + m_activeDeskName(), + m_mutex(), + m_deskReady(&m_mutex, false), + m_updateKeys(updateKeys) +{ + queryHookLibrary(hookLibrary); + m_cursor = createBlankCursor(); + m_deskClass = createDeskWindowClass(m_isPrimary); + m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); +} + +CMSWindowsDesks::~CMSWindowsDesks() +{ + disable(); + destroyClass(m_deskClass); + destroyCursor(m_cursor); + delete m_updateKeys; +} + +void +CMSWindowsDesks::enable() +{ + // set the active desk and (re)install the hooks + checkDesk(); + + // install the desk timer. this timer periodically checks + // which desk is active and reinstalls the hooks as necessary. + // we wouldn't need this if windows notified us of a desktop + // change but as far as i can tell it doesn't. + m_timer = EVENTQUEUE->newTimer(0.2, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, + new TMethodEventJob( + this, &CMSWindowsDesks::handleCheckDesk)); + + updateKeys(); +} + +void +CMSWindowsDesks::disable() +{ + // remove timer + if (m_timer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); + EVENTQUEUE->deleteTimer(m_timer); + m_timer = NULL; + } + + // destroy desks + removeDesks(); + + m_isOnScreen = m_isPrimary; +} + +void +CMSWindowsDesks::enter() +{ + sendMessage(SYNERGY_MSG_ENTER, 0, 0); +} + +void +CMSWindowsDesks::leave(HKL keyLayout) +{ + sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)keyLayout, 0); +} + +void +CMSWindowsDesks::updateKeys() +{ + sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); +} + +void +CMSWindowsDesks::setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon) +{ + m_x = x; + m_y = y; + m_w = width; + m_h = height; + m_xCenter = xCenter; + m_yCenter = yCenter; + m_multimon = isMultimon; +} + +void +CMSWindowsDesks::installScreensaverHooks(bool install) +{ + if (m_isPrimary && m_screensaverNotify != install) { + m_screensaverNotify = install; + sendMessage(SYNERGY_MSG_SCREENSAVER, install, 0); + } +} + +void +CMSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const +{ + POINT pos; + sendMessage(SYNERGY_MSG_CURSOR_POS, reinterpret_cast(&pos), 0); + x = pos.x; + y = pos.y; +} + +void +CMSWindowsDesks::fakeKeyEvent( + KeyButton button, UINT virtualKey, + bool press, bool /*isAutoRepeat*/) const +{ + DWORD flags = 0; + if (((button & 0x100u) != 0)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (!press) { + flags |= KEYEVENTF_KEYUP; + } + sendMessage(SYNERGY_MSG_FAKE_KEY, flags, + MAKEWORD(static_cast(button & 0xffu), + static_cast(virtualKey & 0xffu))); +} + +void +CMSWindowsDesks::fakeMouseButton(ButtonID button, bool press) const +{ + // the system will swap the meaning of left/right for us if + // the user has configured a left-handed mouse but we don't + // want it to swap since we want the handedness of the + // server's mouse. so pre-swap for a left-handed mouse. + if (GetSystemMetrics(SM_SWAPBUTTON)) { + switch (button) { + case kButtonLeft: + button = kButtonRight; + break; + + case kButtonRight: + button = kButtonLeft; + break; + } + } + + // map button id to button flag and button data + DWORD data = 0; + DWORD flags; + switch (button) { + case kButtonLeft: + flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + break; + + case kButtonMiddle: + flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + break; + + case kButtonRight: + flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + break; + + case kButtonExtra0 + 0: + data = XBUTTON1; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + case kButtonExtra0 + 1: + data = XBUTTON2; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + default: + return; + } + + // do it + sendMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data); +} + +void +CMSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const +{ + sendMessage(SYNERGY_MSG_FAKE_MOVE, + static_cast(x), + static_cast(y)); +} + +void +CMSWindowsDesks::fakeMouseWheel(SInt32 delta) const +{ + sendMessage(SYNERGY_MSG_FAKE_WHEEL, delta, 0); +} + +void +CMSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const +{ + if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { + PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); + waitForDesk(); + } +} + +void +CMSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary) +{ + // look up functions + if (m_isPrimary) { + m_install = (InstallFunc)GetProcAddress(hookLibrary, "install"); + m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall"); + m_installScreensaver = + (InstallScreenSaverFunc)GetProcAddress( + hookLibrary, "installScreenSaver"); + m_uninstallScreensaver = + (UninstallScreenSaverFunc)GetProcAddress( + hookLibrary, "uninstallScreenSaver"); + if (m_install == NULL || + m_uninstall == NULL || + m_installScreensaver == NULL || + m_uninstallScreensaver == NULL) { + LOG((CLOG_ERR "Invalid hook library")); + throw XScreenOpenFailure(); + } + } + else { + m_install = NULL; + m_uninstall = NULL; + m_installScreensaver = NULL; + m_uninstallScreensaver = NULL; + } +} + +HCURSOR +CMSWindowsDesks::createBlankCursor() const +{ + // create a transparent cursor + int cw = GetSystemMetrics(SM_CXCURSOR); + int ch = GetSystemMetrics(SM_CYCURSOR); + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; + UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; + memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(CMSWindowsScreen::getInstance(), + 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +CMSWindowsDesks::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +CMSWindowsDesks::createDeskWindowClass(bool isPrimary) const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = isPrimary ? + &CMSWindowsDesks::primaryDeskProc : + &CMSWindowsDesks::secondaryDeskProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = CMSWindowsScreen::getInstance(); + classInfo.hIcon = NULL; + classInfo.hCursor = m_cursor; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "SynergyDesk"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +CMSWindowsDesks::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass((LPCTSTR)windowClass, CMSWindowsScreen::getInstance()); + } +} + +HWND +CMSWindowsDesks::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + (LPCTSTR)windowClass, + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + CMSWindowsScreen::getInstance(), + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +CMSWindowsDesks::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +LRESULT CALLBACK +CMSWindowsDesks::primaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +CMSWindowsDesks::secondaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // would like to detect any local user input and hide the hider + // window but for now we just detect mouse motion. + bool hide = false; + switch (msg) { + case WM_MOUSEMOVE: + if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { + hide = true; + } + break; + } + + if (hide && IsWindowVisible(hwnd)) { + ReleaseCapture(); + SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +void +CMSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const +{ + // motion is simple (i.e. it's on the primary monitor) if there + // is only one monitor. + 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 + if (simple) { + // when using absolute positioning with mouse_event(), + // the normalized device coordinates range over only + // the primary screen. + SInt32 w = GetSystemMetrics(SM_CXSCREEN); + SInt32 h = GetSystemMetrics(SM_CYSCREEN); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.0f * x) / (w - 1) + 0.5f), + (DWORD)((65535.0f * y) / (h - 1) + 0.5f), + 0, 0); + } + + // windows 98 (and Me?) 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 and forth between the position on the primary + // monitor and the desired position. while it always ends up in + // the right place, the effect is disconcerting. + // + // instead we'll get the cursor's current position and do just a + // relative move from there to the desired position. 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); + } + + // move relative to mouse position + POINT pos; + GetCursorPos(&pos); + 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 +CMSWindowsDesks::deskEnter(CDesk* desk) +{ + if (!m_isPrimary) { + ReleaseCapture(); + } + ShowCursor(TRUE); + SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); +} + +void +CMSWindowsDesks::deskLeave(CDesk* desk, HKL keyLayout) +{ + ShowCursor(FALSE); + if (m_isPrimary) { + // update key state + m_updateKeys->run(); + + // map a window to hide the cursor and to use whatever keyboard + // layout we choose rather than the keyboard layout of the last + // active window. + int x, y, w, h; + if (desk->m_lowLevel) { + // with a low level hook the cursor will never budge so + // just a 1x1 window is sufficient. + x = m_xCenter; + y = m_yCenter; + w = 1; + h = 1; + } + else { + // with regular hooks the cursor will jitter as it's moved + // by the user then back to the center by us. to be sure + // we never lose it, cover all the monitors with the window. + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); + } + else { + // move hider window under the cursor center, raise, and show it + SetWindowPos(desk->m_window, HWND_TOPMOST, + m_xCenter, m_yCenter, 1, 1, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // watch for mouse motion. if we see any then we hide the + // hider window so the user can use the physically attached + // mouse if desired. we'd rather not capture the mouse but + // we aren't notified when the mouse leaves our window. + SetCapture(desk->m_window); + + // warp the mouse to the cursor center + deskMouseMove(m_xCenter, m_yCenter); + } +} + +void +CMSWindowsDesks::deskThread(void* vdesk) +{ + MSG msg; + + // use given desktop for this thread + CDesk* desk = reinterpret_cast(vdesk); + desk->m_threadID = GetCurrentThreadId(); + desk->m_window = NULL; + if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { + // create a message queue + PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); + + // create a window. we use this window to hide the cursor. + try { + desk->m_window = createWindow(m_deskClass, "SynergyDesk"); + LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); + } + catch (...) { + // ignore + LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); + } + + // a window on the primary screen should never activate + if (m_isPrimary && desk->m_window != NULL) { + EnableWindow(desk->m_window, FALSE); + } + } + + // tell main thread that we're ready + { + CLock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + while (GetMessage(&msg, NULL, 0, 0)) { + switch (msg.message) { + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + + case SYNERGY_MSG_SWITCH: + if (m_isPrimary) { + m_uninstall(); + if (m_screensaverNotify) { + m_uninstallScreensaver(); + m_installScreensaver(); + } + switch (m_install()) { + case kHOOK_FAILED: + // we won't work on this desk + desk->m_lowLevel = false; + break; + + case kHOOK_OKAY: + desk->m_lowLevel = false; + break; + + case kHOOK_OKAY_LL: + desk->m_lowLevel = true; + break; + } + } + break; + + case SYNERGY_MSG_ENTER: + m_isOnScreen = true; + deskEnter(desk); + break; + + case SYNERGY_MSG_LEAVE: + m_isOnScreen = false; + m_keyLayout = (HKL)msg.wParam; + deskLeave(desk, m_keyLayout); + break; + + case SYNERGY_MSG_FAKE_KEY: + keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), msg.wParam, 0); + break; + + case SYNERGY_MSG_FAKE_BUTTON: + if (msg.wParam != 0) { + mouse_event(msg.wParam, 0, 0, msg.lParam, 0); + } + break; + + case SYNERGY_MSG_FAKE_MOVE: + deskMouseMove(static_cast(msg.wParam), + static_cast(msg.lParam)); + break; + + case SYNERGY_MSG_FAKE_WHEEL: + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, msg.wParam, 0); + break; + + case SYNERGY_MSG_CURSOR_POS: { + POINT* pos = reinterpret_cast(msg.wParam); + if (!GetCursorPos(pos)) { + pos->x = m_xCenter; + pos->y = m_yCenter; + } + break; + } + + case SYNERGY_MSG_SYNC_KEYS: + m_updateKeys->run(); + break; + + case SYNERGY_MSG_SCREENSAVER: + if (msg.wParam != 0) { + m_installScreensaver(); + } + else { + m_uninstallScreensaver(); + } + break; + } + + // notify that message was processed + CLock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + // clean up + deskEnter(desk); + if (desk->m_window != NULL) { + DestroyWindow(desk->m_window); + } + if (desk->m_desk != NULL) { + closeDesktop(desk->m_desk); + } +} + +CMSWindowsDesks::CDesk* +CMSWindowsDesks::addDesk(const CString& name, HDESK hdesk) +{ + CDesk* desk = new CDesk; + desk->m_name = name; + desk->m_desk = hdesk; + desk->m_targetID = GetCurrentThreadId(); + desk->m_thread = new CThread(new TMethodJob( + this, &CMSWindowsDesks::deskThread, desk)); + waitForDesk(); + m_desks.insert(std::make_pair(name, desk)); + return desk; +} + +void +CMSWindowsDesks::removeDesks() +{ + for (CDesks::iterator index = m_desks.begin(); + index != m_desks.end(); ++index) { + CDesk* desk = index->second; + PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); + desk->m_thread->wait(); + delete desk->m_thread; + delete desk; + } + m_desks.clear(); + m_activeDesk = NULL; + m_activeDeskName = ""; +} + +void +CMSWindowsDesks::checkDesk() +{ + // get current desktop. if we already know about it then return. + CDesk* desk; + HDESK hdesk = openInputDesktop(); + CString name = getDesktopName(hdesk); + CDesks::const_iterator index = m_desks.find(name); + if (index == m_desks.end()) { + desk = addDesk(name, hdesk); + // hold on to hdesk until thread exits so the desk can't + // be removed by the system + } + else { + closeDesktop(hdesk); + desk = index->second; + } + + // if active desktop changed then tell the old and new desk threads + // about the change. don't switch desktops when the screensaver is + // active becaue we'd most likely switch to the screensaver desktop + // which would have the side effect of forcing the screensaver to + // stop. + if (name != m_activeDeskName && !m_screensaver->isActive()) { + // show cursor on previous desk + bool wasOnScreen = m_isOnScreen; + if (!wasOnScreen) { + sendMessage(SYNERGY_MSG_ENTER, 0, 0); + } + + // check for desk accessibility change. we don't get events + // from an inaccessible desktop so when we switch from an + // inaccessible desktop to an accessible one we have to + // update the keyboard state. + LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); + bool isAccessible = isDeskAccessible(desk); + if (isDeskAccessible(m_activeDesk) != isAccessible) { + if (isAccessible) { + LOG((CLOG_DEBUG "desktop is now accessible")); + sendMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); + } + else { + LOG((CLOG_DEBUG "desktop is now inaccessible")); + } + } + + // switch desk + m_activeDesk = desk; + m_activeDeskName = name; + sendMessage(SYNERGY_MSG_SWITCH, 0, 0); + + // hide cursor on new desk + if (!wasOnScreen) { + sendMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); + } + } +} + +bool +CMSWindowsDesks::isDeskAccessible(const CDesk* desk) const +{ + return (desk != NULL && desk->m_desk != NULL); +} + +void +CMSWindowsDesks::waitForDesk() const +{ + CMSWindowsDesks* self = const_cast(this); + + CLock lock(&m_mutex); + while (!(bool)m_deskReady) { + m_deskReady.wait(); + } + self->m_deskReady = false; +} + +void +CMSWindowsDesks::handleCheckDesk(const CEvent&, void*) +{ + checkDesk(); +} + +HDESK +CMSWindowsDesks::openInputDesktop() +{ + if (m_is95Family) { + // there's only one desktop on windows 95 et al. + return GetThreadDesktop(GetCurrentThreadId()); + } + else { + return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, + DESKTOP_CREATEWINDOW | + DESKTOP_HOOKCONTROL | + GENERIC_WRITE); + } +} + +void +CMSWindowsDesks::closeDesktop(HDESK desk) +{ + // on 95/98/me we don't need to close the desktop returned by + // openInputDesktop(). + if (desk != NULL && !m_is95Family) { + CloseDesktop(desk); + } +} + +CString +CMSWindowsDesks::getDesktopName(HDESK desk) +{ + if (desk == NULL) { + return CString(); + } + else if (m_is95Family) { + return "desktop"; + } + else { + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + CString result(name); + return result; + } +} diff --git a/lib/platform/CMSWindowsDesks.h b/lib/platform/CMSWindowsDesks.h new file mode 100644 index 00000000..774fad5d --- /dev/null +++ b/lib/platform/CMSWindowsDesks.h @@ -0,0 +1,252 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSDESKS_H +#define CMSWINDOWSDESKS_H + +#include "CSynergyHook.h" +#include "KeyTypes.h" +#include "MouseTypes.h" +#include "CCondVar.h" +#include "CMutex.h" +#include "CString.h" +#include "stdmap.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CEvent; +class CEventQueueTimer; +class CThread; +class IJob; +class IScreenSaver; + +//! Microsoft Windows desk handling +/*! +Desks in Microsoft Windows are only remotely like desktops on X11 +systems. A desk is another virtual surface for windows but desks +impose serious restrictions: a thread can interact with only one +desk at a time, you can't switch desks if the thread has any hooks +installed or owns any windows, windows cannot exist on multiple +desks at once, etc. Basically, they're useless except for running +the login window or the screensaver, which is what they're used +for. Synergy must deal with them mainly because of the login +window and screensaver but users can create their own desks and +synergy should work on those too. + +This class encapsulates all the desk nastiness. Clients of this +object don't have to know anything about desks. +*/ +class CMSWindowsDesks { +public: + //! Constructor + /*! + \p isPrimary is true iff the desk is for a primary screen. + \p screensaver points to a screensaver object and it's used + only to check if the screensaver is active. The \p updateKeys + job is adopted and is called when the key state should be + updated in a thread attached to the current desk. + \p hookLibrary must be a handle to the hook library. + */ + CMSWindowsDesks(bool isPrimary, HINSTANCE hookLibrary, + const IScreenSaver* screensaver, IJob* updateKeys); + ~CMSWindowsDesks(); + + //! @name manipulators + //@{ + + //! Enable desk tracking + /*! + Enables desk tracking. While enabled, this object checks to see + if the desk has changed and ensures that the hooks are installed + on the new desk. \c setShape should be called at least once + before calling \c enable. + */ + void enable(); + + //! Disable desk tracking + /*! + Disables desk tracking. \sa enable. + */ + void disable(); + + //! Notify of entering a desk + /*! + Prepares a desk for when the cursor enters it. + */ + void enter(); + + //! Notify of leaving a desk + /*! + Prepares a desk for when the cursor leaves it. + */ + void leave(HKL keyLayout); + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state and current keyboard mapping. + */ + void updateKeys(); + + //! Tell desk about new size + /*! + This tells the desks that the display size has changed. + */ + void setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon); + + //! Install/uninstall screensaver hooks + /*! + If \p install is true then the screensaver hooks are installed and, + if desk tracking is enabled, updated whenever the desk changes. If + \p install is false then the screensaver hooks are uninstalled. + */ + void installScreensaverHooks(bool install); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + void getCursorPos(SInt32& x, SInt32& y) const; + + //! Fake key press/release + /*! + Synthesize a press or release of key \c button. + */ + void fakeKeyEvent(KeyButton button, UINT virtualKey, + bool press, bool isAutoRepeat) const; + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + void fakeMouseButton(ButtonID id, bool press) const; + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + void fakeMouseMove(SInt32 x, SInt32 y) const; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c delta. + */ + void fakeMouseWheel(SInt32 delta) const; + + //@} + +private: + class CDesk { + public: + CString m_name; + CThread* m_thread; + DWORD m_threadID; + DWORD m_targetID; + HDESK m_desk; + HWND m_window; + bool m_lowLevel; + }; + typedef std::map CDesks; + + // initialization and shutdown operations + void queryHookLibrary(HINSTANCE hookLibrary); + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // message handlers + void deskMouseMove(SInt32 x, SInt32 y) const; + void deskEnter(CDesk* desk); + void deskLeave(CDesk* desk, HKL keyLayout); + void deskThread(void* vdesk); + + // desk switch checking and handling + CDesk* addDesk(const CString& name, HDESK hdesk); + void removeDesks(); + void checkDesk(); + bool isDeskAccessible(const CDesk* desk) const; + void handleCheckDesk(const CEvent& event, void*); + + // communication with desk threads + void waitForDesk() const; + void sendMessage(UINT, WPARAM, LPARAM) const; + + // desk API wrappers + HDESK openInputDesktop(); + void closeDesktop(HDESK); + CString getDesktopName(HDESK); + + // our desk window procs + static LRESULT CALLBACK primaryDeskProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); + +private: + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if windows 95/98/me + bool m_is95Family; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_deskClass; + HCURSOR m_cursor; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // the timer used to check for desktop switching + CEventQueueTimer* m_timer; + + // screen saver stuff + const IScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // the current desk and it's name + CDesk* m_activeDesk; + CString m_activeDeskName; + + // one desk per desktop and a cond var to communicate with it + CMutex m_mutex; + CCondVar m_deskReady; + CDesks m_desks; + + // hook library stuff + InstallFunc m_install; + UninstallFunc m_uninstall; + InstallScreenSaverFunc m_installScreensaver; + UninstallScreenSaverFunc m_uninstallScreensaver; + + // keyboard stuff + IJob* m_updateKeys; + HKL m_keyLayout; +}; + +#endif diff --git a/lib/platform/CMSWindowsDesktop.cpp b/lib/platform/CMSWindowsDesktop.cpp deleted file mode 100644 index f62b6b1b..00000000 --- a/lib/platform/CMSWindowsDesktop.cpp +++ /dev/null @@ -1,75 +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 "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 deleted file mode 100644 index 908f5c87..00000000 --- a/lib/platform/CMSWindowsDesktop.h +++ /dev/null @@ -1,58 +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 CMSWINDOWSDESKTOP_H -#define CMSWINDOWSDESKTOP_H - -#include "CString.h" -#include - -//! Encapsulate Microsoft Windows desktop -class CMSWindowsDesktop { -public: - //! Open the desktop - /*! - Opens the desktop named \p name. The caller must close the desktop. - */ - static HDESK openDesktop(const CString& name); - - //! 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.h b/lib/platform/CMSWindowsKeyMapper.h deleted file mode 100644 index 883c5c60..00000000 --- a/lib/platform/CMSWindowsKeyMapper.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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" -#include "CString.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(LPARAM eventLParam); - - //! Set the active keyboard layout - /*! - Uses \p keyLayout when finding scan codes via \c keyToScanCode() - and mapping keys in \c mapKeyFromEvent(). - */ - void setKeyLayout(HKL keyLayout); - - //@} - //! @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 charAndVirtKey, LPARAM info, - KeyModifierMask* maskOut, bool* altgr) const; - - //! Check if virtual key is a modifier - /*! - Returns true iff \p virtKey refers to a modifier key. - */ - bool isModifier(UINT virtKey) const; - - //! Test shadow key state - /*! - Returns true iff the shadow state indicates the key is pressed. - */ - bool isPressed(UINT virtKey) const; - - //! Map button to a virtual key - /*! - Returns the virtual key for \c button. - */ - UINT buttonToVirtualKey(KeyButton button) const; - - //! Map virtual key to a button - /*! - Returns the button for virtual key \c virtKey. - */ - KeyButton virtualKeyToButton(UINT virtKey) const; - - //! Check for extended key - /*! - Returns true iff \c key is an extended key - */ - bool isExtendedKey(KeyButton button) const; - - //! Get current modifier key state - /*! - Returns the current modifier key state. - */ - KeyModifierMask getActiveModifiers() 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 button, - 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; - - //! Test shadow key toggle state - /*! - Returns true iff the shadow state indicates the key is toggled on. - */ - bool isToggled(UINT virtKey) const; - - //! Get shadow modifier key state - /*! - Returns the shadow modifier key state. - */ - KeyModifierMask getShadowModifiers(bool needAltGr) const; - - // pass character to ToAsciiEx(), returning what it returns - int toAscii(TCHAR c, HKL hkl, bool menu, WORD* chars) const; - - // return true iff \c c is a dead character - bool isDeadChar(TCHAR c, HKL hkl, bool menu) const; - -private: - class CModifierKeys { - public: - enum { s_maxKeys = 2 }; - KeyModifierMask m_mask; - KeyButton m_keys[s_maxKeys]; - }; - - // map of key state for each scan code. this would be 8 bits - // except windows reuses some scan codes for "extended" keys - // we actually need 9 bits. an example is the left and right - // alt keys; they share the same scan code but the right key - // is "extended". - BYTE m_keys[512]; - UINT m_scanCodeToVirtKey[512]; - KeyButton m_virtKeyToScanCode[256]; - mutable TCHAR m_deadKey; - HKL m_keyLayout; - CString m_keyName; - - static const CModifierKeys s_modifiers[]; - static const char* s_vkToName[]; - static const KeyID s_virtualKey[][2]; - static const UINT s_mapE000[]; - static const UINT s_mapEE00[]; - static const UINT s_mapEF00[]; -}; - -#endif diff --git a/lib/platform/CMSWindowsKeyMapper.cpp b/lib/platform/CMSWindowsKeyState.cpp similarity index 72% rename from lib/platform/CMSWindowsKeyMapper.cpp rename to lib/platform/CMSWindowsKeyState.cpp index b07da1fd..1d9554d7 100644 --- a/lib/platform/CMSWindowsKeyMapper.cpp +++ b/lib/platform/CMSWindowsKeyState.cpp @@ -12,9 +12,19 @@ * GNU General Public License for more details. */ -#include "CMSWindowsKeyMapper.h" +#include "CMSWindowsKeyState.h" +#include "CMSWindowsDesks.h" +#include "CThread.h" +#include "CFunctionJob.h" #include "CLog.h" #include "CStringUtil.h" +#include "CArchMiscWindows.h" + +// extended mouse buttons +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif // multimedia keys #if !defined(VK_BROWSER_BACK) @@ -39,10 +49,10 @@ #endif // -// CMSWindowsKeyMapper +// CMSWindowsKeyState // -const char* CMSWindowsKeyMapper::s_vkToName[] = +const char* CMSWindowsKeyState::s_vkToName[] = { "vk 0x00", "Left Button", @@ -303,7 +313,7 @@ const char* CMSWindowsKeyMapper::s_vkToName[] = }; // map virtual keys to synergy key enumeration -const KeyID CMSWindowsKeyMapper::s_virtualKey[][2] = +const KeyID CMSWindowsKeyState::s_virtualKey[][2] = { /* 0x00 */ kKeyNone, kKeyNone, // reserved /* 0x01 */ kKeyNone, kKeyNone, // VK_LBUTTON @@ -564,7 +574,7 @@ const KeyID CMSWindowsKeyMapper::s_virtualKey[][2] = }; // map special KeyID keys to virtual key codes -const UINT CMSWindowsKeyMapper::s_mapE000[] = +const UINT CMSWindowsKeyState::s_mapE000[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -606,7 +616,7 @@ const UINT CMSWindowsKeyMapper::s_mapE000[] = /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0 }; -const UINT CMSWindowsKeyMapper::s_mapEE00[] = +const UINT CMSWindowsKeyState::s_mapEE00[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -644,7 +654,7 @@ const UINT CMSWindowsKeyMapper::s_mapEE00[] = /* in g_mapEF00, 0xac is VK_DECIMAL not VK_SEPARATOR because win32 * doesn't seem to use VK_SEPARATOR but instead maps VK_DECIMAL to * the same meaning. */ -const UINT CMSWindowsKeyMapper::s_mapEF00[] = +const UINT CMSWindowsKeyState::s_mapEF00[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x08 */ VK_BACK, VK_TAB, 0, VK_CLEAR, 0, VK_RETURN, 0, 0, @@ -687,60 +697,405 @@ const UINT CMSWindowsKeyMapper::s_mapEF00[] = /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE }; -CMSWindowsKeyMapper::CMSWindowsKeyMapper() : m_deadKey(0) +CMSWindowsKeyState::CMSWindowsKeyState(CMSWindowsDesks* desks) : + m_is95Family(CArchMiscWindows::isWindows95Family()), + m_desks(desks), + m_keyLayout(GetKeyboardLayout(0)) { - m_keyLayout = GetKeyboardLayout(0); } -CMSWindowsKeyMapper::~CMSWindowsKeyMapper() +CMSWindowsKeyState::~CMSWindowsKeyState() { // do nothing } void -CMSWindowsKeyMapper::update(IKeyState* keyState) +CMSWindowsKeyState::setKeyLayout(HKL keyLayout) { - // clear shadow state - memset(m_keys, 0, sizeof(m_keys)); + m_keyLayout = keyLayout; +} +void +CMSWindowsKeyState::fixKey(void* target, UINT virtualKey) +{ + // check if virtualKey is up but we think it's down. if so then + // synthesize a key release for it. + // + // we use GetAsyncKeyState() to check the state of the keys even + // though we might not be in sync with that yet. + KeyButton button = m_virtKeyToScanCode[virtualKey]; + if (isKeyDown(button) && (GetAsyncKeyState(virtualKey) & 0x8000) == 0) { + // compute appropriate parameters for fake event + LPARAM lParam = 0xc0000000 | ((LPARAM)button << 16); + + // process as if it were a key up + KeyModifierMask mask; + KeyID key = mapKeyFromEvent(virtualKey, lParam, &mask); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button); + CKeyState::setKeyDown(button, false); + } +} + +KeyID +CMSWindowsKeyState::mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) 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 + + // extract character and virtual key + char c = (char)((charAndVirtKey & 0xff00u) >> 8); + UINT vkCode = (charAndVirtKey & 0xffu); + + // handle some keys via table lookup + int extended = ((info >> 24) & 1); + KeyID id = s_virtualKey[vkCode][extended]; + + // check if not in table; map character to key id + if (id == kKeyNone && c != 0) { + if ((c & 0x80u) == 0) { + // ASCII + id = static_cast(c) & 0xffu; + } + else { + // character is not really ASCII. instead it's some + // character in the current ANSI code page. try to + // convert that to a Unicode character. if we fail + // then use the single byte character as is. + char src = c; + wchar_t unicode; + if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, + &src, 1, &unicode, 1) > 0) { + id = static_cast(unicode); + } + else { + id = static_cast(c) & 0xffu; + } + } + } + + // set mask + KeyModifierMask activeMask = getActiveModifiers(); + bool needAltGr = false; + if (id != kKeyNone && 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 = VkKeyScanEx(c, m_keyLayout); + 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 ((activeMask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + needAltGr = true; + } + } + else { + BYTE modifierState = HIBYTE(virtualKeyAndModifierState); + if ((modifierState & 6) == 6) { + // key requires ctrl and alt == AltGr + needAltGr = true; + } + } + } + + // map modifier key + if (maskOut != NULL) { + if (needAltGr) { + activeMask |= KeyModifierModeSwitch; + activeMask &= ~(KeyModifierControl | KeyModifierAlt); + } + else { + activeMask &= ~KeyModifierModeSwitch; + } + *maskOut = activeMask; + } + + return id; +} + +KeyButton +CMSWindowsKeyState::virtualKeyToButton(UINT virtualKey) const +{ + return m_virtKeyToScanCode[virtualKey & 0xffu]; +} + +void +CMSWindowsKeyState::setKeyDown(KeyButton button, bool down) +{ + CKeyState::setKeyDown(button, down); + + // 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 (down && isKeyDown(m_virtKeyToScanCode[VK_DELETE])) { + KeyModifierMask mask = getActiveModifiers(); + if ((mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + CKeyState::setKeyDown(m_virtKeyToScanCode[VK_LCONTROL], false); + CKeyState::setKeyDown(m_virtKeyToScanCode[VK_RCONTROL], false); + CKeyState::setKeyDown(m_virtKeyToScanCode[VK_LMENU], false); + CKeyState::setKeyDown(m_virtKeyToScanCode[VK_RMENU], false); + CKeyState::setKeyDown(m_virtKeyToScanCode[VK_DELETE], false); + } + } +} + +void +CMSWindowsKeyState::sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (press || isAutoRepeat) { + // 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 = isKeyDown(m_virtKeyToScanCode[VK_LCONTROL]); + bool ctrlR = isKeyDown(m_virtKeyToScanCode[VK_RCONTROL]); + bool altL = isKeyDown(m_virtKeyToScanCode[VK_LMENU]); + bool altR = isKeyDown(m_virtKeyToScanCode[VK_RMENU]); + if ((mask & KeyModifierModeSwitch) != 0) { + KeyModifierMask mask2 = (mask & + ~(KeyModifierControl | + KeyModifierAlt | + KeyModifierModeSwitch)); + if (ctrlL) { + CKeyState::sendKeyEvent(target, false, false, + kKeyControl_L, mask2, 1, + m_virtKeyToScanCode[VK_LCONTROL]); + } + if (ctrlR) { + CKeyState::sendKeyEvent(target, false, false, + kKeyControl_R, mask2, 1, + m_virtKeyToScanCode[VK_RCONTROL]); + } + if (altL) { + CKeyState::sendKeyEvent(target, false, false, + kKeyAlt_L, mask2, 1, + m_virtKeyToScanCode[VK_LMENU]); + } + if (altR) { + CKeyState::sendKeyEvent(target, false, false, + kKeyAlt_R, mask2, 1, + m_virtKeyToScanCode[VK_RMENU]); + } + } + + // send key + if (press) { + CKeyState::sendKeyEvent(target, true, false, + key, mask, 1, button); + if (count > 0) { + --count; + } + } + if (count >= 1) { + CKeyState::sendKeyEvent(target, true, true, + key, mask, count, button); + } + + // restore ctrl and alt state + if ((mask & KeyModifierModeSwitch) != 0) { + KeyModifierMask mask2 = (mask & + ~(KeyModifierControl | + KeyModifierAlt | + KeyModifierModeSwitch)); + if (ctrlL) { + CKeyState::sendKeyEvent(target, true, false, + kKeyControl_L, mask2, 1, + m_virtKeyToScanCode[VK_LCONTROL]); + mask2 |= KeyModifierControl; + } + if (ctrlR) { + CKeyState::sendKeyEvent(target, true, false, + kKeyControl_R, mask2, 1, + m_virtKeyToScanCode[VK_RCONTROL]); + mask2 |= KeyModifierControl; + } + if (altL) { + CKeyState::sendKeyEvent(target, true, false, + kKeyAlt_L, mask2, 1, + m_virtKeyToScanCode[VK_LMENU]); + mask2 |= KeyModifierAlt; + } + if (altR) { + CKeyState::sendKeyEvent(target, true, false, + kKeyAlt_R, mask2, 1, + m_virtKeyToScanCode[VK_RMENU]); + 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 && isKeyDown(button)) { + switch (m_scanCodeToVirtKey[button]) { + 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: + break; + + default: + CKeyState::sendKeyEvent(target, + true, false, key, mask, 1, button); + break; + } + } + + // do key up + CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button); + } +} + +bool +CMSWindowsKeyState::fakeCtrlAltDel() +{ + if (!m_is95Family) { + // to fake ctrl+alt+del on the NT family we broadcast a suitable + // hotkey to all windows on the winlogon desktop. however, the + // current thread must be on that desktop to do the broadcast + // and we can't switch just any thread because some own windows + // or hooks. so start a new thread to do the real work. + CThread cad(new CFunctionJob(&CMSWindowsKeyState::ctrlAltDelThread)); + cad.wait(); + } + else { + // simulate ctrl+alt+del + fakeKeyDown(kKeyDelete, KeyModifierControl | KeyModifierAlt, + m_virtKeyToScanCode[VK_DELETE]); + } + return true; +} + +void +CMSWindowsKeyState::ctrlAltDelThread(void*) +{ + // get the Winlogon desktop at whatever privilege we can + HDESK desk = OpenDesktop("Winlogon", 0, FALSE, MAXIMUM_ALLOWED); + if (desk != NULL) { + if (SetThreadDesktop(desk)) { + PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, + MAKELPARAM(MOD_CONTROL | MOD_ALT, VK_DELETE)); + } + else { + LOG((CLOG_DEBUG "can't switch to Winlogon desk: %d", GetLastError())); + } + CloseDesktop(desk); + } + else { + LOG((CLOG_DEBUG "can't open Winlogon desk: %d", GetLastError())); + } +} + +const char* +CMSWindowsKeyState::getKeyName(KeyButton button) const +{ + char keyName[100]; + char keyName2[100]; + CMSWindowsKeyState* self = const_cast(this); + if (GetKeyNameText(button << 16, keyName, sizeof(keyName)) != 0) { + // get the extended name of the key if button is not extended + // or vice versa. if the names are different then report both. + button ^= 0x100u; + if (GetKeyNameText(button << 16, keyName2, sizeof(keyName2)) != 0 && + strcmp(keyName, keyName2) != 0) { + self->m_keyName = CStringUtil::print("%s or %s", keyName, keyName2); + } + else { + self->m_keyName = keyName; + } + } + else if (m_scanCodeToVirtKey[button] != 0) { + self->m_keyName = s_vkToName[m_scanCodeToVirtKey[button]]; + } + else { + self->m_keyName = CStringUtil::print("scan code 0x%03x", button); + } + return m_keyName.c_str(); +} + +void +CMSWindowsKeyState::doUpdateKeys() +{ // clear scan code to/from virtual key mapping memset(m_scanCodeToVirtKey, 0, sizeof(m_scanCodeToVirtKey)); memset(m_virtKeyToScanCode, 0, sizeof(m_virtKeyToScanCode)); - // add modifiers. note that VK_RMENU shows up under the Alt key - // and ModeSwitch. when simulating AltGr we need to use the right - // alt key so we use KeyModifierModeSwitch to get it. - if (keyState != NULL) { - IKeyState::KeyButtons keys; - keys.push_back((KeyButton)MapVirtualKey(VK_LSHIFT, 0)); - keys.push_back((KeyButton)MapVirtualKey(VK_RSHIFT, 0)); - keyState->addModifier(KeyModifierShift, keys); - keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_LCONTROL, 0)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RCONTROL, 0) | 0x100)); - keyState->addModifier(KeyModifierControl, keys); - keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_LMENU, 0)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RMENU, 0) | 0x100)); - keyState->addModifier(KeyModifierAlt, keys); - keys.clear(); - keys.push_back((KeyButton)(MapVirtualKey(VK_LWIN, 0) | 0x100)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RWIN, 0) | 0x100)); - keyState->addModifier(KeyModifierSuper, keys); - keys.clear(); - keys.push_back((KeyButton)(MapVirtualKey(VK_RMENU, 0) | 0x100)); - keyState->addModifier(KeyModifierModeSwitch, keys); - keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_CAPITAL, 0)); - keyState->addModifier(KeyModifierCapsLock, keys); - keys.clear(); - keys.push_back((KeyButton)(MapVirtualKey(VK_NUMLOCK, 0) | 0x100)); - keyState->addModifier(KeyModifierNumLock, keys); - keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_SCROLL, 0)); - keyState->addModifier(KeyModifierScrollLock, keys); - keys.clear(); - } + // add modifiers. note that ModeSwitch is mapped to VK_RMENU and + // that it's mapped *before* the Alt modifier. we must map it so + // KeyModifierModeSwitch mask can be converted to keystrokes. it + // must be mapped before the Alt modifier so that the Alt modifier + // takes precedence when mapping keystrokes to modifier masks. + // + // we have to explicitly set the extended key flag for some + // modifiers because the win32 API is inadequate. + KeyButtons keys; + keys.push_back((KeyButton)(MapVirtualKey(VK_RMENU, 0) | 0x100)); + addModifier(KeyModifierModeSwitch, keys); + keys.clear(); + keys.push_back((KeyButton)MapVirtualKey(VK_LSHIFT, 0)); + keys.push_back((KeyButton)(MapVirtualKey(VK_RSHIFT, 0) | 0x100)); + addModifier(KeyModifierShift, keys); + keys.clear(); + keys.push_back((KeyButton)MapVirtualKey(VK_LCONTROL, 0)); + keys.push_back((KeyButton)(MapVirtualKey(VK_RCONTROL, 0) | 0x100)); + addModifier(KeyModifierControl, keys); + keys.clear(); + keys.push_back((KeyButton)MapVirtualKey(VK_LMENU, 0)); + keys.push_back((KeyButton)(MapVirtualKey(VK_RMENU, 0) | 0x100)); + addModifier(KeyModifierAlt, keys); + keys.clear(); + keys.push_back((KeyButton)(MapVirtualKey(VK_LWIN, 0) | 0x100)); + keys.push_back((KeyButton)(MapVirtualKey(VK_RWIN, 0) | 0x100)); + addModifier(KeyModifierSuper, keys); + keys.clear(); + keys.push_back((KeyButton)MapVirtualKey(VK_CAPITAL, 0)); + addModifier(KeyModifierCapsLock, keys); + keys.clear(); + keys.push_back((KeyButton)(MapVirtualKey(VK_NUMLOCK, 0) | 0x100)); + addModifier(KeyModifierNumLock, keys); + keys.clear(); + keys.push_back((KeyButton)MapVirtualKey(VK_SCROLL, 0)); + addModifier(KeyModifierScrollLock, keys); /* FIXME -- potential problem here on win me // win me (sony vaio laptop): @@ -751,18 +1106,30 @@ CMSWindowsKeyMapper::update(IKeyState* keyState) // MapVirtualKey(sc, 3): // all scan codes unmapped (function apparently unimplemented) */ - BYTE keys[256]; - GetKeyboardState(keys); + BYTE keyState[256]; + GetKeyboardState(keyState); for (UINT i = 1; i < 256; ++i) { - // skip certain virtual keys (the ones for the mouse buttons) - if (i < VK_BACK && i != VK_CANCEL) { + // skip mouse button virtual keys + switch (i) { + case VK_LBUTTON: + case VK_RBUTTON: + case VK_MBUTTON: + case VK_XBUTTON1: + case VK_XBUTTON2: continue; + + default: + break; } // map to a scancode and back to a virtual key UINT scancode = MapVirtualKey(i, 0); UINT virtKey = MapVirtualKey(scancode, 3); - if (scancode == 0 || virtKey == 0) { + if (virtKey == 0) { + // assume MapVirtualKey(xxx, 3) is unimplemented + virtKey = i; + } + else if (scancode == 0) { // the VK_PAUSE virtual key doesn't map properly if (i == VK_PAUSE) { // i hope this works on all keyboards @@ -775,6 +1142,25 @@ CMSWindowsKeyMapper::update(IKeyState* keyState) } // we need some adjustments due to inadequacies in the API. + // the API provides no means to query the keyboard by scan + // code that i can see. so we're doing it by virtual key. + // but a single virtual key can map to multiple physical + // keys. for example, VK_HOME maps to NumPad 7 and to the + // (extended key) Home key. this means we can never tell + // which of the two keys is pressed. + // + // this is a problem if a key is down when this method is + // called. if the extended key is down we'll record the + // non-extended key as being down. when the extended key + // goes up, we'll record that correctly and leave the + // non-extended key as being down. to deal with that we + // always re-check the keyboard state if we think we're + // locked to a screen because a key is down. the re-check + // should clear it up. + // + // the win32 functions that take scan codes are: + + // // if the mapped virtual key doesn't match the starting // point then there's a really good chance that that virtual // key is mapped to an extended key. however, this is not @@ -788,36 +1174,28 @@ CMSWindowsKeyMapper::update(IKeyState* keyState) } // okay, now we have the scan code for the virtual key. - // save the key state. m_scanCodeToVirtKey[scancode] = i; m_virtKeyToScanCode[i] = (KeyButton)scancode; - m_keys[scancode] = (BYTE)(keys[i] & 0x80); - if (keyState != NULL) { - keyState->setKeyDown((KeyButton)scancode, (keys[i] & 0x80) != 0); + + // save the key state + if ((keyState[i] & 0x80) != 0) { + setKeyDown((KeyButton)scancode, true); } + // toggle state applies to all keys but we only want it for // the modifier keys with corresponding lights. - if ((keys[i] & 0x01) != 0) { + if ((keyState[i] & 0x01) != 0) { switch (i) { case VK_CAPITAL: - m_keys[scancode] |= 0x01; - if (keyState != NULL) { - keyState->setToggled(KeyModifierCapsLock); - } + setToggled(KeyModifierCapsLock); break; case VK_NUMLOCK: - m_keys[scancode] |= 0x01; - if (keyState != NULL) { - keyState->setToggled(KeyModifierNumLock); - } + setToggled(KeyModifierNumLock); break; case VK_SCROLL: - m_keys[scancode] |= 0x01; - if (keyState != NULL) { - keyState->setToggled(KeyModifierScrollLock); - } + setToggled(KeyModifierScrollLock); break; } } @@ -825,58 +1203,15 @@ CMSWindowsKeyMapper::update(IKeyState* keyState) } void -CMSWindowsKeyMapper::updateKey(LPARAM eventLParam) +CMSWindowsKeyState::doFakeKeyEvent(KeyButton button, + bool press, bool isAutoRepeat) { - bool pressed = ((eventLParam & 0x80000000u) == 0); - UINT scanCode = ((eventLParam & 0x01ff0000u) >> 16); - UINT virtKey = m_scanCodeToVirtKey[scanCode]; - if (virtKey == 0) { - // unmapped key - return; - } - - if (pressed) { - m_keys[scanCode] |= 0x80; - - // 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 (isPressed(VK_CONTROL) && - isPressed(VK_MENU) && - isPressed(VK_DELETE)) { - m_keys[m_virtKeyToScanCode[VK_LCONTROL]] &= ~0x80; - m_keys[m_virtKeyToScanCode[VK_RCONTROL]] &= ~0x80; - m_keys[m_virtKeyToScanCode[VK_LMENU]] &= ~0x80; - m_keys[m_virtKeyToScanCode[VK_RMENU]] &= ~0x80; - m_keys[m_virtKeyToScanCode[VK_DELETE]] &= ~0x80; - } - } - else { - m_keys[scanCode] &= ~0x80; - - // handle toggle keys - switch (virtKey) { - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - m_keys[scanCode] ^= 0x01; - break; - - default: - break; - } - } -} - -void -CMSWindowsKeyMapper::setKeyLayout(HKL keyLayout) -{ - m_keyLayout = keyLayout; + UINT vk = m_scanCodeToVirtKey[button]; + m_desks->fakeKeyEvent(button, vk, press, isAutoRepeat); } KeyButton -CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, - const IKeyState& keyState, KeyID id, +CMSWindowsKeyState::mapKey(Keystrokes& keys, KeyID id, KeyModifierMask mask, bool isAutoRepeat) const { UINT virtualKey = 0; @@ -929,7 +1264,7 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, // keys but not for numeric keys. if (virtualKey >= VK_NUMPAD0 && virtualKey <= VK_DIVIDE) { requiredMask |= KeyModifierNumLock; - if (!keyState.isModifierActive(KeyModifierNumLock)) { + if ((getActiveModifiers() & KeyModifierNumLock) != 0) { LOG((CLOG_DEBUG2 "turn on num lock for keypad key")); outMask |= KeyModifierNumLock; } @@ -944,7 +1279,7 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, // now generate the keystrokes and return the resulting modifier mask KeyButton scanCode = m_virtKeyToScanCode[virtualKey]; LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d scan code 0x%04x mask 0x%04x", id, virtualKey, scanCode, outMask)); - return mapToKeystrokes(keys, keyState, scanCode, + return mapToKeystrokes(keys, scanCode, outMask, requiredMask, isAutoRepeat); } @@ -991,15 +1326,14 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, LOG((CLOG_DEBUG2 "KeyID 0x%08x not in code page", id)); return 0; } - KeyButton button = mapCharacter(keys, keyState, - multiByte[0], hkl, isAutoRepeat); + KeyButton button = mapCharacter(keys, multiByte[0], hkl, isAutoRepeat); if (button != 0) { LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); if (isDeadChar(multiByte[0], hkl, false)) { // 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 keystroke; keystroke.m_key = m_virtKeyToScanCode[VK_SPACE]; keystroke.m_press = true; keystroke.m_repeat = false; @@ -1040,262 +1374,16 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, } 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); + mapCharacter(keys, multiByte[1], hkl, isAutoRepeat); } // process character LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); - return mapCharacter(keys, keyState, multiByte[0], hkl, isAutoRepeat); -} - -KeyID -CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM charAndVirtKey, - 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 - - char c = (char)((charAndVirtKey & 0xff00u) >> 8); - UINT vkCode = (charAndVirtKey & 0xffu); - - // 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 - KeyID id = s_virtualKey[vkCode][extended]; - if (id == kKeyNone) { - // not in table; map character to key id - if (c != 0) { - if ((c & 0x80u) != 0) { - // character is not really ASCII. instead it's some - // character in the current ANSI code page. try to - // convert that to a Unicode character. if we fail - // then use the single byte character as is. - char src = c; - wchar_t unicode; - if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, - &src, 1, &unicode, 1) > 0) { - id = static_cast(unicode); - } - else { - id = static_cast(c) & 0xffu; - } - } - else { - id = static_cast(c) & 0xffu; - } - } - } - - // 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 = VkKeyScanEx(c, m_keyLayout); - 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 (isPressed(VK_CONTROL) && isPressed(VK_MENU)) { - 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 - if (maskOut != NULL) { - *maskOut = getShadowModifiers(needAltGr); - } - - return id; -} - -bool -CMSWindowsKeyMapper::isModifier(UINT virtKey) const -{ - switch (virtKey) { - 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; - } -} - -bool -CMSWindowsKeyMapper::isPressed(UINT virtKey) const -{ - switch (virtKey) { - case VK_SHIFT: - return ((m_keys[m_virtKeyToScanCode[VK_LSHIFT]] & 0x80) != 0 || - (m_keys[m_virtKeyToScanCode[VK_RSHIFT]] & 0x80) != 0); - - case VK_CONTROL: - return ((m_keys[m_virtKeyToScanCode[VK_LCONTROL]] & 0x80) != 0 || - (m_keys[m_virtKeyToScanCode[VK_RCONTROL]] & 0x80) != 0); - - case VK_MENU: - return ((m_keys[m_virtKeyToScanCode[VK_LMENU]] & 0x80) != 0 || - (m_keys[m_virtKeyToScanCode[VK_RMENU]] & 0x80) != 0); - - default: - return ((m_keys[m_virtKeyToScanCode[virtKey & 0xffu]] & 0x80) != 0); - } -} - -bool -CMSWindowsKeyMapper::isToggled(UINT virtKey) const -{ - return ((m_keys[m_virtKeyToScanCode[virtKey & 0xffu]] & 0x01) != 0); + return mapCharacter(keys, multiByte[0], hkl, isAutoRepeat); } UINT -CMSWindowsKeyMapper::buttonToVirtualKey(KeyButton button) const -{ - return m_scanCodeToVirtKey[button & 0x1ffu]; -} - -KeyButton -CMSWindowsKeyMapper::virtualKeyToButton(UINT virtKey) const -{ - return m_virtKeyToScanCode[virtKey & 0xffu]; -} - -bool -CMSWindowsKeyMapper::isExtendedKey(KeyButton button) const -{ - return ((button & 0x100u) != 0); -} - -KeyModifierMask -CMSWindowsKeyMapper::getActiveModifiers() const -{ - KeyModifierMask mask = 0; - if (GetKeyState(VK_SHIFT) < 0 || - GetKeyState(VK_LSHIFT) < 0 || - GetKeyState(VK_RSHIFT) < 0) { - mask |= KeyModifierShift; - } - if (GetKeyState(VK_CONTROL) < 0 || - GetKeyState(VK_LCONTROL) < 0 || - GetKeyState(VK_RCONTROL) < 0) { - mask |= KeyModifierControl; - } - if (GetKeyState(VK_MENU) < 0 || - GetKeyState(VK_LMENU) < 0 || - GetKeyState(VK_RMENU) < 0) { - mask |= KeyModifierAlt; - } - if (GetKeyState(VK_LWIN) < 0 || - GetKeyState(VK_RWIN) < 0) { - mask |= KeyModifierSuper; - } - if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) { - mask |= KeyModifierCapsLock; - } - if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) { - mask |= KeyModifierNumLock; - } - if ((GetKeyState(VK_SCROLL) & 0x01) != 0) { - mask |= KeyModifierScrollLock; - } - return mask; -} - -KeyModifierMask -CMSWindowsKeyMapper::getShadowModifiers(bool needAltGr) const -{ - KeyModifierMask mask = 0; - if (isPressed(VK_SHIFT)) { - mask |= KeyModifierShift; - } - if (needAltGr) { - mask |= KeyModifierModeSwitch; - } - else { - if (isPressed(VK_CONTROL)) { - mask |= KeyModifierControl; - } - if (isPressed(VK_MENU)) { - mask |= KeyModifierAlt; - } - } - if (isPressed(VK_LWIN) || isPressed(VK_RWIN)) { - mask |= KeyModifierSuper; - } - if (isToggled(VK_CAPITAL)) { - mask |= KeyModifierCapsLock; - } - if (isToggled(VK_NUMLOCK)) { - mask |= KeyModifierNumLock; - } - if (isToggled(VK_SCROLL)) { - mask |= KeyModifierScrollLock; - } - return mask; -} - -const char* -CMSWindowsKeyMapper::getKeyName(KeyButton key) const -{ - char keyName[100]; - CMSWindowsKeyMapper* self = const_cast(this); - if (GetKeyNameText((key & 0x01ffu) << 16, keyName, sizeof(keyName)) != 0) { - self->m_keyName = keyName; - } - else if (m_scanCodeToVirtKey[key] != 0) { - self->m_keyName = s_vkToName[m_scanCodeToVirtKey[key]]; - } - else { - self->m_keyName = CStringUtil::print("scan code 0x%03x", key & 0x01ffu); - } - return m_keyName.c_str(); -} - -UINT -CMSWindowsKeyMapper::getCodePageFromLangID(LANGID langid) const +CMSWindowsKeyState::getCodePageFromLangID(LANGID langid) const { // construct a locale id from the language id LCID lcid = MAKELCID(langid, SORT_DEFAULT); @@ -1320,10 +1408,11 @@ CMSWindowsKeyMapper::getCodePageFromLangID(LANGID langid) const } KeyButton -CMSWindowsKeyMapper::mapCharacter(IKeyState::Keystrokes& keys, - const IKeyState& keyState, char c, HKL hkl, - bool isAutoRepeat) const +CMSWindowsKeyState::mapCharacter(Keystrokes& keys, + char c, HKL hkl, bool isAutoRepeat) const { + KeyModifierMask activeMask = getActiveModifiers(); + // translate the character into its virtual key and its required // modifier state. SHORT virtualKeyAndModifierState = VkKeyScanEx(c, hkl); @@ -1345,13 +1434,11 @@ CMSWindowsKeyMapper::mapCharacter(IKeyState::Keystrokes& keys, // otherwise users couldn't do, say, ctrl+z. // // the space character (ascii 32) is special in that it's unaffected - // by shift and should match the shift state from keyState. + // by shift and should match our stored shift state. KeyModifierMask desiredMask = 0; KeyModifierMask requiredMask = KeyModifierShift; if (c == 32) { - if (keyState.isModifierActive(KeyModifierShift)) { - desiredMask |= KeyModifierShift; - } + desiredMask |= (activeMask & KeyModifierShift); } else if ((modifierState & 0x01u) == 1) { desiredMask |= KeyModifierShift; @@ -1370,7 +1457,7 @@ CMSWindowsKeyMapper::mapCharacter(IKeyState::Keystrokes& keys, // 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)) { + if ((activeMask & 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 @@ -1391,26 +1478,25 @@ CMSWindowsKeyMapper::mapCharacter(IKeyState::Keystrokes& keys, // method for modifier keys). KeyButton scanCode = m_virtKeyToScanCode[virtualKey]; LOG((CLOG_DEBUG2 "character %d to virtual key %d scan code 0x%04x mask 0x%08x", (unsigned char)c, virtualKey, scanCode, desiredMask)); - return mapToKeystrokes(keys, keyState, scanCode, + return mapToKeystrokes(keys, scanCode, desiredMask, requiredMask, isAutoRepeat); } KeyButton -CMSWindowsKeyMapper::mapToKeystrokes(IKeyState::Keystrokes& keys, - const IKeyState& keyState, KeyButton button, +CMSWindowsKeyState::mapToKeystrokes(Keystrokes& keys, KeyButton button, 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)) { + Keystrokes undo; + if (!adjustModifiers(keys, undo, desiredMask, requiredMask)) { LOG((CLOG_DEBUG2 "failed to adjust modifiers")); keys.clear(); return 0; } // add the key event - IKeyState::Keystroke keystroke; + Keystroke keystroke; keystroke.m_key = button; keystroke.m_press = true; keystroke.m_repeat = isAutoRepeat; @@ -1426,9 +1512,8 @@ CMSWindowsKeyMapper::mapToKeystrokes(IKeyState::Keystrokes& keys, } bool -CMSWindowsKeyMapper::adjustModifiers(IKeyState::Keystrokes& keys, - IKeyState::Keystrokes& undo, - const IKeyState& keyState, +CMSWindowsKeyState::adjustModifiers(Keystrokes& keys, + Keystrokes& undo, KeyModifierMask desiredMask, KeyModifierMask requiredMask) const { @@ -1437,7 +1522,7 @@ CMSWindowsKeyMapper::adjustModifiers(IKeyState::Keystrokes& keys, for (KeyModifierMask mask = 1u; requiredMask != 0; mask <<= 1) { if ((mask & requiredMask) != 0) { bool active = ((desiredMask & mask) != 0); - if (!keyState.mapModifier(keys, undo, mask, active)) { + if (!mapModifier(keys, undo, mask, active)) { return false; } requiredMask ^= mask; @@ -1447,7 +1532,7 @@ CMSWindowsKeyMapper::adjustModifiers(IKeyState::Keystrokes& keys, } int -CMSWindowsKeyMapper::toAscii(TCHAR c, HKL hkl, bool menu, WORD* chars) const +CMSWindowsKeyState::toAscii(TCHAR c, HKL hkl, bool menu, WORD* chars) const { // ignore bogus character if (c == 0) { @@ -1494,7 +1579,7 @@ CMSWindowsKeyMapper::toAscii(TCHAR c, HKL hkl, bool menu, WORD* chars) const } bool -CMSWindowsKeyMapper::isDeadChar(TCHAR c, HKL hkl, bool menu) const +CMSWindowsKeyState::isDeadChar(TCHAR c, HKL hkl, bool menu) const { // first clear out ToAsciiEx()'s internal buffer by sending it // a space. diff --git a/lib/platform/CMSWindowsKeyState.h b/lib/platform/CMSWindowsKeyState.h new file mode 100644 index 00000000..cc888103 --- /dev/null +++ b/lib/platform/CMSWindowsKeyState.h @@ -0,0 +1,130 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2003 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef CMSWINDOWSKEYSTATE_H +#define CMSWINDOWSKEYSTATE_H + +#include "CKeyState.h" +#include "CString.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CMSWindowsDesks; + +//! Microsoft Windows key mapper +/*! +This class maps KeyIDs to keystrokes. +*/ +class CMSWindowsKeyState : public CKeyState { +public: + CMSWindowsKeyState(CMSWindowsDesks* desks); + virtual ~CMSWindowsKeyState(); + + //! @name accessors + //@{ + + //! Set the active keyboard layout + /*! + Uses \p keyLayout when querying the keyboard. + */ + void setKeyLayout(HKL keyLayout); + + //! Check the named virtual key for release + /*! + If \p virtualKey isn't really pressed but we think it is then + update our state and post a key release event to \p eventTarget. + */ + void fixKey(void* eventTarget, UINT virtualKey); + + //! Map key event to a key + /*! + Converts a key event into a KeyID and the shadow modifier state + to a modifier mask. + */ + KeyID mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const; + + //! Map a virtual key to a button + /*! + Returns the button for the \p virtualKey. + */ + KeyButton virtualKeyToButton(UINT virtualKey) const; + + //@} + + // IKeyState overrides + virtual void setKeyDown(KeyButton button, bool down); + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual bool fakeCtrlAltDel(); + virtual const char* getKeyName(KeyButton) const; + +protected: + // IKeyState overrides + virtual void doUpdateKeys(); + virtual void doFakeKeyEvent(KeyButton button, + bool press, bool isAutoRepeat); + virtual KeyButton mapKey(Keystrokes& keys, KeyID id, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + +private: + // send ctrl+alt+del hotkey event on NT family + static void ctrlAltDelThread(void*); + + // 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(Keystrokes& keys, + 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(Keystrokes& keys, KeyButton button, + KeyModifierMask desiredMask, + KeyModifierMask requiredMask, + bool isAutoRepeat) const; + + // get keystrokes to get modifiers in a desired state + bool adjustModifiers(Keystrokes& keys, + Keystrokes& undo, + KeyModifierMask desiredMask, + KeyModifierMask requiredMask) const; + + // pass character to ToAsciiEx(), returning what it returns + int toAscii(TCHAR c, HKL hkl, bool menu, WORD* chars) const; + + // return true iff \c c is a dead character + bool isDeadChar(TCHAR c, HKL hkl, bool menu) const; + +private: + bool m_is95Family; + CMSWindowsDesks* m_desks; + HKL m_keyLayout; + CString m_keyName; + UINT m_scanCodeToVirtKey[512]; + KeyButton m_virtKeyToScanCode[256]; + + static const char* s_vkToName[]; + static const KeyID s_virtualKey[][2]; + static const UINT s_mapE000[]; + static const UINT s_mapEE00[]; + static const UINT s_mapEF00[]; +}; + +#endif diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp index 34ba1865..e6213f6b 100644 --- a/lib/platform/CMSWindowsScreen.cpp +++ b/lib/platform/CMSWindowsScreen.cpp @@ -14,8 +14,9 @@ #include "CMSWindowsScreen.h" #include "CMSWindowsClipboard.h" -#include "CMSWindowsDesktop.h" +#include "CMSWindowsDesks.h" #include "CMSWindowsEventQueueBuffer.h" +#include "CMSWindowsKeyState.h" #include "CMSWindowsScreenSaver.h" #include "CClipboard.h" #include "XScreen.h" @@ -30,28 +31,7 @@ #include "TMethodJob.h" #include "CArch.h" #include "CArchMiscWindows.h" -#include -#include -#include - -// ; -#define SYNERGY_MSG_SWITCH SYNERGY_HOOK_LAST_MSG + 1 -// ; -#define SYNERGY_MSG_ENTER SYNERGY_HOOK_LAST_MSG + 2 -// ; -#define SYNERGY_MSG_LEAVE SYNERGY_HOOK_LAST_MSG + 3 -// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code -#define SYNERGY_MSG_FAKE_KEY SYNERGY_HOOK_LAST_MSG + 4 - // flags, XBUTTON id -#define SYNERGY_MSG_FAKE_BUTTON SYNERGY_HOOK_LAST_MSG + 5 -// x; y -#define SYNERGY_MSG_FAKE_MOVE SYNERGY_HOOK_LAST_MSG + 6 -// delta; -#define SYNERGY_MSG_FAKE_WHEEL SYNERGY_HOOK_LAST_MSG + 7 -// POINT*; -#define SYNERGY_MSG_CURSOR_POS SYNERGY_HOOK_LAST_MSG + 8 -// IKeyState*; -#define SYNERGY_MSG_SYNC_KEYS SYNERGY_HOOK_LAST_MSG + 9 +#include // // add backwards compatible multihead support (and suppress bogus warning) @@ -62,14 +42,6 @@ #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 @@ -83,6 +55,10 @@ #define XBUTTON1 0x0001 #define XBUTTON2 0x0002 #endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif // // CMSWindowsScreen @@ -97,7 +73,6 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary, m_is95Family(CArchMiscWindows::isWindows95Family()), 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), @@ -108,26 +83,18 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary, m_mark(0), m_markReceived(0), m_keyLayout(NULL), - m_timer(NULL), m_screensaver(NULL), m_screensaverNotify(false), m_nextClipboardWindow(NULL), m_ownClipboard(false), - m_activeDesk(NULL), - m_activeDeskName(), + m_desks(NULL), m_hookLibrary(NULL), m_init(NULL), m_cleanup(NULL), - m_install(NULL), - m_uninstall(NULL), m_setSides(NULL), m_setZone(NULL), m_setMode(NULL), - m_installScreensaver(NULL), - m_uninstallScreensaver(NULL), m_keyState(NULL), - m_mutex(), - m_deskReady(&m_mutex, false), m_suspend(suspend), m_resume(resume) { @@ -139,27 +106,28 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary, if (m_isPrimary) { m_hookLibrary = openHookLibrary("synrgyhk"); } - m_cursor = createBlankCursor(); - m_class = createWindowClass(); - m_deskClass = createDeskWindowClass(m_isPrimary); - updateScreenShape(); - m_window = createWindow(m_class, "Synergy"); m_screensaver = new CMSWindowsScreenSaver(); + m_desks = new CMSWindowsDesks(m_isPrimary, + m_hookLibrary, m_screensaver, + new TMethodJob(this, + &CMSWindowsScreen::updateKeysCB)); + m_keyState = new CMSWindowsKeyState(m_desks); + updateScreenShape(); + m_class = createWindowClass(); + m_window = createWindow(m_class, "Synergy"); LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); LOG((CLOG_DEBUG "window is 0x%08x", m_window)); } catch (...) { + delete m_keyState; + delete m_desks; delete m_screensaver; destroyWindow(m_window); - destroyClass(m_deskClass); destroyClass(m_class); - destroyCursor(m_cursor); closeHookLibrary(m_hookLibrary); - m_screensaver = NULL; - m_class = 0; - m_cursor = NULL; - m_hookLibrary = NULL; - s_screen = NULL; + delete m_suspend; + delete m_resume; + s_screen = NULL; throw; } @@ -179,11 +147,11 @@ CMSWindowsScreen::~CMSWindowsScreen() disable(); EVENTQUEUE->adoptBuffer(NULL); EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); + delete m_keyState; + delete m_desks; delete m_screensaver; destroyWindow(m_window); - destroyClass(m_deskClass); destroyClass(m_class); - destroyCursor(m_cursor); closeHookLibrary(m_hookLibrary); delete m_suspend; delete m_resume; @@ -205,13 +173,6 @@ CMSWindowsScreen::getInstance() return s_instance; } -void -CMSWindowsScreen::setKeyState(IKeyState* keyState) -{ - m_keyState = keyState; - updateKeys(); -} - void CMSWindowsScreen::enable() { @@ -220,10 +181,10 @@ CMSWindowsScreen::enable() // install our clipboard snooper m_nextClipboardWindow = SetClipboardViewer(m_window); - if (m_isPrimary) { - // update shadow key state - m_keyMapper.update(NULL); + // track the active desk and (re)install the hooks + m_desks->enable(); + if (m_isPrimary) { // set jump zones m_setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); @@ -236,29 +197,13 @@ CMSWindowsScreen::enable() // the server would not be able to wake us up. CArchMiscWindows::addBusyState(CArchMiscWindows::kSYSTEM); } - - // set the active desk and (re)install the hooks - checkDesk(); - - // install the desk timer. this timer periodically checks - // which desk is active and reinstalls the hooks as necessary. - // we wouldn't need this if windows notified us of a desktop - // change but as far as i can tell it doesn't. - m_timer = EVENTQUEUE->newTimer(0.2, NULL); - EVENTQUEUE->adoptHandler(CEvent::kTimer, m_timer, - new TMethodEventJob( - this, &CMSWindowsScreen::handleCheckDesk)); } void CMSWindowsScreen::disable() { - // remove timer - if (m_timer != NULL) { - EVENTQUEUE->removeHandler(CEvent::kTimer, m_timer); - EVENTQUEUE->deleteTimer(m_timer); - m_timer = NULL; - } + // stop tracking the active desk + m_desks->disable(); if (m_isPrimary) { // disable hooks @@ -273,9 +218,6 @@ CMSWindowsScreen::disable() CArchMiscWindows::kDISPLAY); } - // destroy desks - removeDesks(); - // stop snooping the clipboard ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; @@ -286,7 +228,7 @@ CMSWindowsScreen::disable() void CMSWindowsScreen::enter() { - sendDeskMessage(SYNERGY_MSG_ENTER, 0, 0); + m_desks->enter(); if (m_isPrimary) { // enable special key sequences on win95 family enableSpecialKeys(true); @@ -312,18 +254,12 @@ CMSWindowsScreen::leave() m_keyLayout = GetKeyboardLayout(thread); // tell the key mapper about the keyboard layout - m_keyMapper.setKeyLayout(m_keyLayout); - sendDeskMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); + m_keyState->setKeyLayout(m_keyLayout); // tell desk that we're leaving and tell it the keyboard layout - sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); + m_desks->leave(m_keyLayout); if (m_isPrimary) { -/* XXX - // update keys - m_keyMapper.update(NULL); -*/ - // warp to center warpCursor(m_xCenter, m_yCenter); @@ -391,7 +327,7 @@ CMSWindowsScreen::openScreensaver(bool notify) m_screensaverNotify = notify; if (m_screensaverNotify) { - m_installScreensaver(); + m_desks->installScreensaverHooks(true); } else { m_screensaver->disable(); @@ -403,7 +339,7 @@ CMSWindowsScreen::closeScreensaver() { if (m_screensaver != NULL) { if (m_screensaverNotify) { - m_uninstallScreensaver(); + m_desks->installScreensaverHooks(false); } else { m_screensaver->enable(); @@ -437,14 +373,6 @@ CMSWindowsScreen::setOptions(const COptionsList&) // no options } -void -CMSWindowsScreen::updateKeys() -{ - sendDeskMessage(SYNERGY_MSG_SYNC_KEYS, 0, 0); - memset(m_buttons, 0, sizeof(m_buttons)); - // FIXME -- get the button state -} - void CMSWindowsScreen::setSequenceNumber(UInt32 seqNum) { @@ -485,11 +413,7 @@ CMSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const void CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const { - POINT pos; - sendDeskMessage(SYNERGY_MSG_CURSOR_POS, - reinterpret_cast(&pos), 0); - x = pos.x; - y = pos.y; + m_desks->getCursorPos(x, y); } void @@ -529,7 +453,7 @@ bool CMSWindowsScreen::isAnyMouseButtonDown() const { static const char* buttonToName[] = { - "button 0", + "", "Left Button", "Middle Button", "Right Button", @@ -537,8 +461,8 @@ CMSWindowsScreen::isAnyMouseButtonDown() const "X Button 2" }; - for (UInt32 i = 0; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { - if ((m_buttons[i] & 0x80) != 0) { + for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + if (m_buttons[i]) { LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i])); return true; } @@ -547,12 +471,6 @@ CMSWindowsScreen::isAnyMouseButtonDown() const return false; } -KeyModifierMask -CMSWindowsScreen::getActiveModifiers() const -{ - return m_keyMapper.getActiveModifiers(); -} - void CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const { @@ -560,87 +478,28 @@ CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const y = m_yCenter; } -const char* -CMSWindowsScreen::getKeyName(KeyButton virtualKey) const -{ - return m_keyMapper.getKeyName(virtualKey); -} - -void -CMSWindowsScreen::fakeKeyEvent(KeyButton id, bool press) const -{ - DWORD flags = 0; - if (m_keyMapper.isExtendedKey(id)) { - flags |= KEYEVENTF_EXTENDEDKEY; - } - if (!press) { - flags |= KEYEVENTF_KEYUP; - } - UINT vk = m_keyMapper.buttonToVirtualKey(id); - sendDeskMessage(SYNERGY_MSG_FAKE_KEY, flags, - MAKEWORD(static_cast(id & 0xffu), - static_cast(vk & 0xffu))); -} - -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, 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 { - DWORD data; - DWORD flags = mapButtonToEvent(id, press, &data); - sendDeskMessage(SYNERGY_MSG_FAKE_BUTTON, flags, data); + m_desks->fakeMouseButton(id, press); } void CMSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) const { - sendDeskMessage(SYNERGY_MSG_FAKE_MOVE, - static_cast(x), - static_cast(y)); + m_desks->fakeMouseMove(x, y); } void CMSWindowsScreen::fakeMouseWheel(SInt32 delta) const { - sendDeskMessage(SYNERGY_MSG_FAKE_WHEEL, delta, 0); + m_desks->fakeMouseWheel(delta); } -KeyButton -CMSWindowsScreen::mapKey(IKeyState::Keystrokes& keys, - const IKeyState& keyState, KeyID id, - KeyModifierMask desiredMask, - bool isAutoRepeat) const +void +CMSWindowsScreen::updateKeys() { - return m_keyMapper.mapKey(keys, keyState, id, desiredMask, isAutoRepeat); + m_desks->updateKeys(); } HINSTANCE @@ -657,25 +516,13 @@ CMSWindowsScreen::openHookLibrary(const char* name) m_setSides = (SetSidesFunc)GetProcAddress(hookLibrary, "setSides"); m_setZone = (SetZoneFunc)GetProcAddress(hookLibrary, "setZone"); m_setMode = (SetModeFunc)GetProcAddress(hookLibrary, "setMode"); - m_install = (InstallFunc)GetProcAddress(hookLibrary, "install"); - m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall"); m_init = (InitFunc)GetProcAddress(hookLibrary, "init"); m_cleanup = (CleanupFunc)GetProcAddress(hookLibrary, "cleanup"); - m_installScreensaver = - (InstallScreenSaverFunc)GetProcAddress( - hookLibrary, "installScreenSaver"); - m_uninstallScreensaver = - (UninstallScreenSaverFunc)GetProcAddress( - 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) { + m_cleanup == NULL) { LOG((CLOG_ERR "Invalid hook library; use a newer %s.dll", name)); throw XScreenOpenFailure(); } @@ -741,27 +588,6 @@ CMSWindowsScreen::createWindowClass() const return RegisterClassEx(&classInfo); } -ATOM -CMSWindowsScreen::createDeskWindowClass(bool isPrimary) const -{ - WNDCLASSEX classInfo; - classInfo.cbSize = sizeof(classInfo); - classInfo.style = CS_DBLCLKS | CS_NOCLOSE; - classInfo.lpfnWndProc = isPrimary ? - &CMSWindowsScreen::primaryDeskProc : - &CMSWindowsScreen::secondaryDeskProc; - 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 = "SynergyDesk"; - classInfo.hIconSm = NULL; - return RegisterClassEx(&classInfo); -} - void CMSWindowsScreen::destroyClass(ATOM windowClass) const { @@ -829,6 +655,23 @@ CMSWindowsScreen::handleSystemEvent(const CEvent& event, void*) DispatchMessage(msg); } +void +CMSWindowsScreen::updateButtons() +{ + m_buttons[kButtonNone] = false; + m_buttons[kButtonLeft] = (GetKeyState(VK_LBUTTON) < 0); + m_buttons[kButtonMiddle] = (GetKeyState(VK_MBUTTON) < 0); + m_buttons[kButtonRight] = (GetKeyState(VK_RBUTTON) < 0); + m_buttons[kButtonExtra0 + 0] = (GetKeyState(VK_XBUTTON1) < 0); + m_buttons[kButtonExtra0 + 1] = (GetKeyState(VK_XBUTTON2) < 0); +} + +IKeyState* +CMSWindowsScreen::getKeyState() const +{ + return m_keyState; +} + bool CMSWindowsScreen::onPreDispatch(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) @@ -850,27 +693,24 @@ 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 + // fake a key release for the windows keys if we think they're + // down but they're really up. we have to do this because if the + // user presses and releases a windows key without pressing any + // other key while it's down then the system will eat the key // release. if we don't detect that and synthesize the release - // then the cclient won't take the usual windows key release action + // then 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. + // since the key could go up at any time we'll check the state on + // every event. only check on the windows 95 family since the NT + // family reports the key release as usual. obviously we skip + // this if the event is for a windows key itself. if (m_is95Family && message != SYNERGY_MSG_KEY) { if (wParam != VK_LWIN) { - fixKey(VK_LWIN); + m_keyState->fixKey(getEventTarget(), VK_LWIN); } if (wParam != VK_RWIN) { - fixKey(VK_RWIN); + m_keyState->fixKey(getEventTarget(), VK_RWIN); } } @@ -998,182 +838,48 @@ CMSWindowsScreen::onMark(UInt32 mark) bool CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) { - WPARAM charAndVirtKey = wParam; - wParam &= 0xffu; + LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, lParam)); // update key state. ignore key repeats. + KeyButton button = (KeyButton)((lParam & 0x01ff0000) >> 16); if ((lParam & 0xc0000000u) == 0x00000000) { - KeyButton scancode = (KeyButton)((lParam & 0x01ff0000) >> 16); - m_keyState->setKeyDown(scancode, true); + m_keyState->setKeyDown(button, true); } - else if ((lParam & 0xc0000000u) == 0xc0000000) { - KeyButton scancode = (KeyButton)((lParam & 0x01ff0000) >> 16); - m_keyState->setKeyDown(scancode, false); + else if ((lParam & 0x80000000u) == 0x80000000) { + m_keyState->setKeyDown(button, false); } // 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))) { + UINT virtKey = (wParam & 0xffu); + if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && + (m_keyState->getActiveModifiers() & + (KeyModifierControl | KeyModifierAlt)) != 0) { LOG((CLOG_DEBUG "emulate ctrl+alt+del")); - wParam = VK_DELETE; - lParam &= 0xfffe0000; - lParam |= m_keyMapper.virtualKeyToButton(wParam) << 16; - lParam |= 0x00000001; - charAndVirtKey = wParam; + // switch wParam and lParam to be as if VK_DELETE was + // pressed or released + wParam = VK_DELETE; + lParam &= 0xfe000000; + lParam |= m_keyState->virtualKeyToButton(wParam) << 16; + lParam |= 0x00000001; } - // process key normally - bool altgr; + // process key KeyModifierMask mask; - const KeyID key = m_keyMapper.mapKeyFromEvent( - charAndVirtKey, lParam, &mask, &altgr); - KeyButton button = static_cast( - (lParam & 0x01ff0000u) >> 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; - KeyModifierMask mask2 = (mask & - ~(KeyModifierControl | - KeyModifierAlt | - KeyModifierModeSwitch)); - if (ctrlL) { - key = kKeyControl_L; - button = m_keyMapper.virtualKeyToButton(VK_LCONTROL); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyUpEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - } - if (ctrlR) { - key = kKeyControl_R; - button = m_keyMapper.virtualKeyToButton(VK_RCONTROL); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyUpEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - } - if (altL) { - key = kKeyAlt_L; - button = m_keyMapper.virtualKeyToButton(VK_LMENU); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyUpEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - } - if (altR) { - key = kKeyAlt_R; - button = m_keyMapper.virtualKeyToButton(VK_RMENU); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyUpEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - } - } - - // 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)); - sendEvent(getKeyDownEvent(), - CKeyInfo::alloc(key, mask, button, 1)); - 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)); - sendEvent(getKeyRepeatEvent(), - CKeyInfo::alloc(key, mask, button, repeat)); - } - - // restore ctrl and alt state - if (altgr) { - KeyID key; - KeyButton button; - KeyModifierMask mask2 = (mask & - ~(KeyModifierControl | - KeyModifierAlt | - KeyModifierModeSwitch)); - if (ctrlL) { - key = kKeyControl_L; - button = m_keyMapper.virtualKeyToButton(VK_LCONTROL); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyDownEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - mask2 |= KeyModifierControl; - } - if (ctrlR) { - key = kKeyControl_R; - button = m_keyMapper.virtualKeyToButton(VK_RCONTROL); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyDownEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - mask2 |= KeyModifierControl; - } - if (altL) { - key = kKeyAlt_L; - button = m_keyMapper.virtualKeyToButton(VK_LMENU); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyDownEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - mask2 |= KeyModifierAlt; - } - if (altR) { - key = kKeyAlt_R; - button = m_keyMapper.virtualKeyToButton(VK_RMENU); - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask2, button)); - sendEvent(getKeyDownEvent(), - CKeyInfo::alloc(key, mask2, button, 1)); - 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 && - !m_keyMapper.isModifier(wParam) && - m_keyMapper.isPressed(wParam)) { - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); - sendEvent(getKeyDownEvent(), - CKeyInfo::alloc(key, mask, button, 1)); - m_keyMapper.updateKey(lParam & 0x3fffffffu); - } - - // do key up - LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); - sendEvent(getKeyUpEvent(), - CKeyInfo::alloc(key, mask, button, 1)); - } + KeyID key = m_keyState->mapKeyFromEvent(wParam, lParam, &mask); + button = static_cast((lParam & 0x01ff0000u) >> 16); + if (key != kKeyNone) { + m_keyState->sendKeyEvent(getEventTarget(), + ((lParam & 0x80000000) == 0), + ((lParam & 0x40000000) == 1), + key, mask, (SInt32)(lParam & 0xffff), button); } else { - LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", wParam, lParam)); + LOG((CLOG_DEBUG2 "event: cannot map key")); } } - // keep our shadow key state up to date - m_keyMapper.updateKey(lParam); - return true; } @@ -1184,6 +890,16 @@ CMSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam) bool pressed = false; const ButtonID button = mapButtonFromEvent(wParam, lParam); + // keep our shadow key state up to date + if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { + if (pressed) { + m_buttons[button] = true; + } + else { + m_buttons[button] = false; + } + } + // ignore message if posted prior to last mark change if (!ignore()) { switch (wParam) { @@ -1227,16 +943,6 @@ CMSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam) } } - // 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; } @@ -1452,6 +1158,9 @@ CMSWindowsScreen::updateScreenShape() m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) || m_h != GetSystemMetrics(SM_CYSCREEN)); LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + + // tell the desks + m_desks->setShape(m_x, m_y, m_w, m_h, m_xCenter, m_yCenter, m_multimon); } void @@ -1468,73 +1177,6 @@ CMSWindowsScreen::enableSpecialKeys(bool enable) const } } -void -CMSWindowsScreen::fixKey(UINT virtualKey) -{ - if (m_keyMapper.isPressed(virtualKey) && - (GetAsyncKeyState(virtualKey) & 0x8000) == 0) { - // compute appropriate parameters for fake event - KeyButton button = m_keyMapper.virtualKeyToButton(virtualKey); - LPARAM lParam = 0xc0000000 | ((LPARAM)button << 16); - - // process as if it were a key up - KeyModifierMask mask; - KeyID key = m_keyMapper.mapKeyFromEvent(virtualKey, - lParam, &mask, NULL); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); - sendEvent(getKeyUpEvent(), CKeyInfo::alloc(key, mask, button, 1)); - m_keyMapper.updateKey(lParam); - } -} - -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 { @@ -1584,23 +1226,10 @@ CMSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const } void -CMSWindowsScreen::ctrlAltDelThread(void*) +CMSWindowsScreen::updateKeysCB(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())); - } + m_keyState->updateKeys(); + updateButtons(); } LRESULT CALLBACK @@ -1615,417 +1244,3 @@ CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) return result; } - -LRESULT CALLBACK -CMSWindowsScreen::primaryDeskProc( - HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - return DefWindowProc(hwnd, msg, wParam, lParam); -} - -LRESULT CALLBACK -CMSWindowsScreen::secondaryDeskProc( - HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - // would like to detect any local user input and hide the hider - // window but for now we just detect mouse motion. - bool hide = false; - switch (msg) { - case WM_MOUSEMOVE: - if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { - hide = true; - } - break; - } - - if (hide && IsWindowVisible(hwnd)) { - ReleaseCapture(); - SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | - SWP_NOACTIVATE | SWP_HIDEWINDOW); - } - - return DefWindowProc(hwnd, msg, wParam, lParam); -} - -void -CMSWindowsScreen::deskMouseMove(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 - if (simple) { - // when using absolute positioning with mouse_event(), - // the normalized device coordinates range over only - // the primary screen. - SInt32 w = GetSystemMetrics(SM_CXSCREEN); - SInt32 h = GetSystemMetrics(SM_CYSCREEN); - mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65535.0f * x) / (w - 1) + 0.5f), - (DWORD)((65535.0f * y) / (h - 1) + 0.5f), - 0, 0); - } - - // windows 98 (and Me?) 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 and forth between the position on the primary - // monitor and the desired position. while it always ends up in - // the right place, the effect is disconcerting. - // - // instead we'll get the cursor's current position and do just a - // relative move from there to the desired position. 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); - } - - // move relative to mouse position - POINT pos; - GetCursorPos(&pos); - 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::deskEnter(CDesk* desk) -{ - if (!m_isPrimary) { - ReleaseCapture(); - } - ShowCursor(TRUE); - SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | - SWP_NOACTIVATE | SWP_HIDEWINDOW); -} - -void -CMSWindowsScreen::deskLeave(CDesk* desk, HKL keyLayout) -{ - ShowCursor(FALSE); - if (m_isPrimary) { - // map a window to hide the cursor and to use whatever keyboard - // layout we choose rather than the keyboard layout of the last - // active window. - int x, y, w, h; - if (desk->m_lowLevel) { - // with a low level hook the cursor will never budge so - // just a 1x1 window is sufficient. - x = m_xCenter; - y = m_yCenter; - w = 1; - h = 1; - } - else { - // with regular hooks the cursor will jitter as it's moved - // by the user then back to the center by us. to be sure - // we never lose it, cover all the monitors with the window. - x = m_x; - y = m_y; - w = m_w; - h = m_h; - } - SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, - SWP_NOACTIVATE | SWP_SHOWWINDOW); - - // switch to requested keyboard layout - ActivateKeyboardLayout(keyLayout, 0); - } - else { - // move hider window under the cursor center, raise, and show it - SetWindowPos(desk->m_window, HWND_TOPMOST, - m_xCenter, m_yCenter, 1, 1, - SWP_NOACTIVATE | SWP_SHOWWINDOW); - - // watch for mouse motion. if we see any then we hide the - // hider window so the user can use the physically attached - // mouse if desired. we'd rather not capture the mouse but - // we aren't notified when the mouse leaves our window. - SetCapture(desk->m_window); - - // warp the mouse to the cursor center - deskMouseMove(m_xCenter, m_yCenter); - } -} - -void -CMSWindowsScreen::deskThread(void* vdesk) -{ - MSG msg; - - // use given desktop for this thread - CDesk* desk = reinterpret_cast(vdesk); - desk->m_threadID = GetCurrentThreadId(); - desk->m_window = NULL; - if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { - // create a message queue - PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); - - // create a window. we use this window to hide the cursor. - try { - desk->m_window = createWindow(m_deskClass, "SynergyDesk"); - LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); - } - catch (...) { - // ignore - LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); - } - - // a window on the primary screen should never activate - if (m_isPrimary && desk->m_window != NULL) { - EnableWindow(desk->m_window, FALSE); - } - } - - // tell main thread that we're ready - { - CLock lock(&m_mutex); - m_deskReady = true; - m_deskReady.broadcast(); - } - - while (GetMessage(&msg, NULL, 0, 0)) { - switch (msg.message) { - default: - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - - case SYNERGY_MSG_SWITCH: - if (m_isPrimary) { - m_uninstall(); - if (m_screensaverNotify) { - m_uninstallScreensaver(); - m_installScreensaver(); - } - switch (m_install()) { - case kHOOK_FAILED: - // we won't work on this desk - desk->m_lowLevel = false; - break; - - case kHOOK_OKAY: - desk->m_lowLevel = false; - break; - - case kHOOK_OKAY_LL: - desk->m_lowLevel = true; - break; - } - } - break; - - case SYNERGY_MSG_ENTER: - deskEnter(desk); - break; - - case SYNERGY_MSG_LEAVE: - deskLeave(desk, (HKL)msg.wParam); - break; - - case SYNERGY_MSG_FAKE_KEY: - keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), msg.wParam, 0); - break; - - case SYNERGY_MSG_FAKE_BUTTON: - if (msg.wParam != 0) { - mouse_event(msg.wParam, 0, 0, msg.lParam, 0); - } - break; - - case SYNERGY_MSG_FAKE_MOVE: - deskMouseMove(static_cast(msg.wParam), - static_cast(msg.lParam)); - break; - - case SYNERGY_MSG_FAKE_WHEEL: - mouse_event(MOUSEEVENTF_WHEEL, 0, 0, msg.wParam, 0); - break; - - case SYNERGY_MSG_CURSOR_POS: { - POINT* pos = reinterpret_cast(msg.wParam); - if (!GetCursorPos(pos)) { - pos->x = m_xCenter; - pos->y = m_yCenter; - } - break; - } - - case SYNERGY_MSG_SYNC_KEYS: - m_keyMapper.update(m_keyState); - break; - } - - // notify that message was processed - CLock lock(&m_mutex); - m_deskReady = true; - m_deskReady.broadcast(); - } - - // clean up - deskEnter(desk); - if (desk->m_window != NULL) { - DestroyWindow(desk->m_window); - } - if (desk->m_desk != NULL) { - CMSWindowsDesktop::closeDesktop(desk->m_desk); - } -} - -CMSWindowsScreen::CDesk* -CMSWindowsScreen::addDesk(const CString& name, HDESK hdesk) -{ - CDesk* desk = new CDesk; - desk->m_name = name; - desk->m_desk = hdesk; - desk->m_targetID = GetCurrentThreadId(); - desk->m_thread = new CThread(new TMethodJob( - this, &CMSWindowsScreen::deskThread, desk)); - waitForDesk(); - m_desks.insert(std::make_pair(name, desk)); - return desk; -} - -void -CMSWindowsScreen::removeDesks() -{ - for (CDesks::iterator index = m_desks.begin(); - index != m_desks.end(); ++index) { - CDesk* desk = index->second; - PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); - desk->m_thread->wait(); - delete desk->m_thread; - delete desk; - } - m_desks.clear(); - m_activeDesk = NULL; - m_activeDeskName = ""; -} - -void -CMSWindowsScreen::checkDesk() -{ - // get current desktop. if we already know about it then return. - CDesk* desk; - HDESK hdesk = CMSWindowsDesktop::openInputDesktop(); - CString name = CMSWindowsDesktop::getDesktopName(hdesk); - CDesks::const_iterator index = m_desks.find(name); - if (index == m_desks.end()) { - desk = addDesk(name, hdesk); - // hold on to hdesk until thread exits so the desk can't - // be removed by the system - } - else { - CMSWindowsDesktop::closeDesktop(hdesk); - desk = index->second; - } - - // if active desktop changed then tell the old and new desk threads - // about the change. don't switch desktops when the screensaver is - // active becaue we'd most likely switch to the screensaver desktop - // which would have the side effect of forcing the screensaver to - // stop. - if (name != m_activeDeskName && !m_screensaver->isActive()) { - // show cursor on previous desk - if (!m_isOnScreen) { - sendDeskMessage(SYNERGY_MSG_ENTER, 0, 0); - } - - // check for desk accessibility change. we don't get events - // from an inaccessible desktop so when we switch from an - // inaccessible desktop to an accessible one we have to - // update the keyboard state. - LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); - bool isAccessible = isDeskAccessible(desk); - if (isDeskAccessible(m_activeDesk) != isAccessible) { - if (isAccessible) { - LOG((CLOG_DEBUG "desktop is now accessible")); - updateKeys(); - } - else { - LOG((CLOG_DEBUG "desktop is now inaccessible")); - } - } - - // switch desk - m_activeDesk = desk; - m_activeDeskName = name; - sendDeskMessage(SYNERGY_MSG_SWITCH, 0, 0); - - // hide cursor on new desk - if (!m_isOnScreen) { - sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); - } - } -} - -bool -CMSWindowsScreen::isDeskAccessible(const CDesk* desk) const -{ - return (desk != NULL && desk->m_desk != NULL); -} - -void -CMSWindowsScreen::sendDeskMessage(UINT msg, WPARAM wParam, LPARAM lParam) const -{ - if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { - PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); - waitForDesk(); - } -} - -void -CMSWindowsScreen::waitForDesk() const -{ - CMSWindowsScreen* self = const_cast(this); - - CLock lock(&m_mutex); - while (!(bool)m_deskReady) { - m_deskReady.wait(); - } - self->m_deskReady = false; -} - -void -CMSWindowsScreen::handleCheckDesk(const CEvent&, void*) -{ - checkDesk(); -} diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h index f9e0b9b7..8a43c833 100644 --- a/lib/platform/CMSWindowsScreen.h +++ b/lib/platform/CMSWindowsScreen.h @@ -15,8 +15,7 @@ #ifndef CMSWINDOWSSCREEN_H #define CMSWINDOWSSCREEN_H -#include "IPlatformScreen.h" -#include "CMSWindowsKeyMapper.h" +#include "CPlatformScreen.h" #include "CSynergyHook.h" #include "CCondVar.h" #include "CMutex.h" @@ -24,13 +23,14 @@ #define WIN32_LEAN_AND_MEAN #include -class CEventQueueTimer; +class CMSWindowsDesks; +class CMSWindowsKeyState; class CMSWindowsScreenSaver; class CThread; class IJob; //! Implementation of IPlatformScreen for Microsoft Windows -class CMSWindowsScreen : public IPlatformScreen { +class CMSWindowsScreen : public CPlatformScreen { public: CMSWindowsScreen(bool isPrimary, IJob* suspend, IJob* resume); virtual ~CMSWindowsScreen(); @@ -57,23 +57,6 @@ public: //@} - // IPlatformScreen overrides - virtual void setKeyState(IKeyState*); - virtual void enable(); - virtual void disable(); - virtual void enter(); - virtual bool leave(); - virtual bool setClipboard(ClipboardID, const IClipboard*); - virtual void checkClipboards(); - virtual void openScreensaver(bool notify); - virtual void closeScreensaver(); - virtual void screensaver(bool activate); - virtual void resetOptions(); - virtual void setOptions(const COptionsList& options); - virtual void updateKeys(); - virtual void setSequenceNumber(UInt32); - virtual bool isPrimary() const; - // IScreen overrides virtual void* getEventTarget() const; virtual bool getClipboard(ClipboardID id, IClipboard*) const; @@ -86,34 +69,38 @@ public: virtual void warpCursor(SInt32 x, SInt32 y); virtual SInt32 getJumpZoneSize() const; virtual bool isAnyMouseButtonDown() const; - virtual KeyModifierMask getActiveModifiers() const; virtual void getCursorCenter(SInt32& x, SInt32& y) 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; + + // IKeyState overrides + virtual void updateKeys(); + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const COptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const CEvent&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; private: - class CDesk { - public: - CString m_name; - CThread* m_thread; - DWORD m_threadID; - DWORD m_targetID; - HDESK m_desk; - HWND m_window; - bool m_lowLevel; - }; - typedef std::map CDesks; - // initialization and shutdown operations HINSTANCE openHookLibrary(const char* name); void closeHookLibrary(HINSTANCE hookLibrary) const; @@ -129,9 +116,6 @@ private: void sendEvent(CEvent::Type type, void* = NULL); void sendClipboardEvent(CEvent::Type type, ClipboardID id); - // system event handler (does DispatchMessage) - void handleSystemEvent(const CEvent& event, void*); - // handle message before it gets dispatched. returns true iff // the message should not be dispatched. bool onPreDispatch(HWND, UINT, WPARAM, LPARAM); @@ -169,42 +153,15 @@ private: // enable/disable special key combinations so we can catch/pass them void enableSpecialKeys(bool) const; - // send fake key up if shadow state says virtualKey is down but - // system says it isn't. - void fixKey(UINT virtualKey); - - // 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*); + // job to update the key state + void updateKeysCB(void*); // our window proc static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); - // our desk window procs - static LRESULT CALLBACK primaryDeskProc(HWND, UINT, WPARAM, LPARAM); - static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); - - void deskMouseMove(SInt32 x, SInt32 y) const; - void deskEnter(CDesk* desk); - void deskLeave(CDesk* desk, HKL keyLayout); - void deskThread(void* vdesk); - CDesk* addDesk(const CString& name, HDESK hdesk); - void removeDesks(); - void checkDesk(); - bool isDeskAccessible(const CDesk* desk) const; - void sendDeskMessage(UINT, WPARAM, LPARAM) const; - void waitForDesk() const; - void handleCheckDesk(const CEvent& event, void*); - private: static HINSTANCE s_instance; @@ -219,8 +176,6 @@ private: // our resources ATOM m_class; - ATOM m_deskClass; - HCURSOR m_cursor; // screen shape stuff SInt32 m_x, m_y; @@ -245,9 +200,6 @@ private: // the keyboard layout to use when off primary screen HKL m_keyLayout; - // the timer used to check for desktop switching - CEventQueueTimer* m_timer; - // screen saver stuff CMSWindowsScreenSaver* m_screensaver; bool m_screensaverNotify; @@ -258,33 +210,22 @@ private: HWND m_nextClipboardWindow; bool m_ownClipboard; - // the current desk and it's name - CDesk* m_activeDesk; - CString m_activeDeskName; - // one desk per desktop and a cond var to communicate with it - CMutex m_mutex; - CCondVar m_deskReady; - CDesks m_desks; + CMSWindowsDesks* m_desks; // 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; // keyboard stuff - IKeyState* m_keyState; - CMSWindowsKeyMapper m_keyMapper; + CMSWindowsKeyState* m_keyState; // map of button state - BYTE m_buttons[1 + kButtonExtra0 + 1]; + bool m_buttons[1 + kButtonExtra0 + 1]; // suspend/resume callbacks IJob* m_suspend; diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp index f7003c4a..955dc2ae 100644 --- a/lib/platform/CSynergyHook.cpp +++ b/lib/platform/CSynergyHook.cpp @@ -219,8 +219,8 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // and ctrl+backspace to delete. we don't want those translations // so clear the control modifier state. however, if we want to // simulate AltGr (which is ctrl+alt) then we must not clear it. - BYTE control = keys[VK_CONTROL]; - BYTE menu = keys[VK_MENU]; + UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL]; + UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU]; if ((control & 0x80) == 0 || (menu & 0x80) == 0) { keys[VK_LCONTROL] = 0; keys[VK_RCONTROL] = 0; @@ -228,8 +228,10 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) } else { keys[VK_LCONTROL] = 0x80; + keys[VK_RCONTROL] = 0x80; keys[VK_CONTROL] = 0x80; keys[VK_LMENU] = 0x80; + keys[VK_RMENU] = 0x80; keys[VK_MENU] = 0x80; } @@ -237,7 +239,7 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // we don't know and there doesn't appear to be any way to find // out. so we'll just assume a menu is active if the menu key // is down. - // XXX -- figure out some way to check if a menu is active + // FIXME -- figure out some way to check if a menu is active UINT flags = 0; if ((menu & 0x80) != 0) flags |= 1; diff --git a/lib/platform/CXWindowsKeyState.cpp b/lib/platform/CXWindowsKeyState.cpp index 0397ce3c..27b23d9a 100644 --- a/lib/platform/CXWindowsKeyState.cpp +++ b/lib/platform/CXWindowsKeyState.cpp @@ -174,6 +174,13 @@ CXWindowsKeyState::mapModifiersFromX(unsigned int state) const return mask; } +bool +CXWindowsKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + const char* CXWindowsKeyState::getKeyName(KeyButton keycode) const { @@ -864,7 +871,7 @@ CXWindowsKeyState::findBestKeyIndex(KeySymIndex keyIndex, // 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 + // FIXME -- do this right for (unsigned int i = 0; i < 4; ++i) { if (keyIndex->second.m_keycode[i] != 0) { return i; diff --git a/lib/platform/CXWindowsKeyState.h b/lib/platform/CXWindowsKeyState.h index 98458228..36abeeb2 100644 --- a/lib/platform/CXWindowsKeyState.h +++ b/lib/platform/CXWindowsKeyState.h @@ -50,6 +50,7 @@ public: //@} // IKeyState overrides + virtual bool fakeCtrlAltDel(); virtual const char* getKeyName(KeyButton) const; protected: diff --git a/lib/platform/CXWindowsScreen.cpp b/lib/platform/CXWindowsScreen.cpp index 2531ba8e..fe368d13 100644 --- a/lib/platform/CXWindowsScreen.cpp +++ b/lib/platform/CXWindowsScreen.cpp @@ -241,9 +241,8 @@ CXWindowsScreen::enable() // warp the mouse to the cursor center fakeMouseMove(m_xCenter, m_yCenter); } - else { - m_keyState->updateKeys(); - } + + updateKeys(); } void @@ -545,13 +544,6 @@ CXWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const y = m_yCenter; } -bool -CXWindowsScreen::fakeCtrlAltDel() const -{ - // pass keys through unchanged - return false; -} - void CXWindowsScreen::fakeMouseButton(ButtonID button, bool press) const { diff --git a/lib/platform/CXWindowsScreen.h b/lib/platform/CXWindowsScreen.h index 41b42a75..c7be5bb4 100644 --- a/lib/platform/CXWindowsScreen.h +++ b/lib/platform/CXWindowsScreen.h @@ -53,7 +53,6 @@ public: virtual void getCursorCenter(SInt32& x, SInt32& y) const; // ISecondaryScreen overrides - 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; diff --git a/lib/platform/platform.dsp b/lib/platform/platform.dsp index 123e513b..a5036971 100644 --- a/lib/platform/platform.dsp +++ b/lib/platform/platform.dsp @@ -103,7 +103,7 @@ SOURCE=.\CMSWindowsClipboardUTF16Converter.cpp # End Source File # Begin Source File -SOURCE=.\CMSWindowsDesktop.cpp +SOURCE=.\CMSWindowsDesks.cpp # End Source File # Begin Source File @@ -111,7 +111,7 @@ SOURCE=.\CMSWindowsEventQueueBuffer.cpp # End Source File # Begin Source File -SOURCE=.\CMSWindowsKeyMapper.cpp +SOURCE=.\CMSWindowsKeyState.cpp # End Source File # Begin Source File @@ -147,7 +147,7 @@ SOURCE=.\CMSWindowsClipboardUTF16Converter.h # End Source File # Begin Source File -SOURCE=.\CMSWindowsDesktop.h +SOURCE=.\CMSWindowsDesks.h # End Source File # Begin Source File @@ -155,7 +155,7 @@ SOURCE=.\CMSWindowsEventQueueBuffer.h # End Source File # Begin Source File -SOURCE=.\CMSWindowsKeyMapper.h +SOURCE=.\CMSWindowsKeyState.h # End Source File # Begin Source File diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index b870b968..7aa040c1 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -294,7 +294,7 @@ UInt32 CServer::getActivePrimarySides() const { UInt32 sides = 0; - if (!isLockedToScreen()) { + if (!isLockedToScreenServer()) { if (getNeighbor(m_primaryClient, kLeft) != NULL) { sides |= kLeftMask; } @@ -312,7 +312,7 @@ CServer::getActivePrimarySides() const } bool -CServer::isLockedToScreen() const +CServer::isLockedToScreenServer() const { // locked if scroll-lock is toggled on if ((m_primaryClient->getToggleMask() & KeyModifierScrollLock) != 0) { @@ -320,6 +320,18 @@ CServer::isLockedToScreen() const return true; } + // not locked + return false; +} + +bool +CServer::isLockedToScreen() const +{ + // locked if we say we're locked + if (isLockedToScreenServer()) { + return true; + } + // locked if primary says we're locked if (m_primaryClient->isLockedToScreen()) { return true; diff --git a/lib/server/CServer.h b/lib/server/CServer.h index 874449e1..286eab0e 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -120,6 +120,12 @@ private: UInt32 getActivePrimarySides() const; // returns true iff mouse should be locked to the current screen + // according to this object only, ignoring what the primary client + // says. + bool isLockedToScreenServer() const; + + // returns true iff mouse should be locked to the current screen + // according to this object or the primary client. bool isLockedToScreen() const; // returns the jump zone of the client diff --git a/lib/synergy/CKeyState.cpp b/lib/synergy/CKeyState.cpp index 9cd80de5..5dffa9d0 100644 --- a/lib/synergy/CKeyState.cpp +++ b/lib/synergy/CKeyState.cpp @@ -39,14 +39,7 @@ void CKeyState::setKeyDown(KeyButton button, bool down) { button &= kButtonMask; - if (button != 0) { - if (down) { - m_keys[button] |= kDown; - } - else { - m_keys[button] &= ~kDown; - } - } + updateKeyState(button, button, down); } void @@ -144,7 +137,8 @@ CKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) // get the sequence of keys to simulate key press and the final // modifier state. Keystrokes keys; - KeyButton localID = (mapKey(keys, id, mask, false) & kButtonMask); + KeyButton localID = + (KeyButton)(mapKey(keys, id, mask, false) & kButtonMask); if (keys.empty()) { // do nothing if there are no associated keys LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id)); @@ -155,7 +149,7 @@ CKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) fakeKeyEvents(keys, 1); // note that key is down - updateKeyState(button & kButtonMask, localID, true); + updateKeyState((KeyButton)(button & kButtonMask), localID, true); } void @@ -174,7 +168,7 @@ CKeyState::fakeKeyRepeat( // get the sequence of keys to simulate key repeat and the final // modifier state. Keystrokes keys; - KeyButton localID = (mapKey(keys, id, mask, true) & kButtonMask); + KeyButton localID = (KeyButton)(mapKey(keys, id, mask, true) & kButtonMask); if (localID == 0) { LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id)); return; @@ -511,12 +505,16 @@ CKeyState::updateKeyState(KeyButton serverID, KeyButton localID, bool press) KeyModifierMask mask = m_keyToMask[localID]; if (mask != 0) { if (isToggle(mask)) { - m_keys[localID] ^= kToggled; - m_mask ^= mask; - // never report half-duplex keys as down if (isHalfDuplex(mask)) { m_keys[localID] &= ~kDown; + press = true; + } + + // toggle on the press + if (press) { + m_keys[localID] ^= kToggled; + m_mask ^= mask; } } else { diff --git a/lib/synergy/CKeyState.h b/lib/synergy/CKeyState.h index a53cba3f..0ed89f2d 100644 --- a/lib/synergy/CKeyState.h +++ b/lib/synergy/CKeyState.h @@ -33,9 +33,10 @@ public: //! Mark key as being down /*! - Sets the state of \p button to down or up. + Sets the state of \p button to down or up. If this is overridden + it must forward to the superclass. */ - void setKeyDown(KeyButton button, bool down); + virtual void setKeyDown(KeyButton button, bool down); //! Mark modifier as being toggled on /*! @@ -47,9 +48,10 @@ public: //! Post a key event /*! Posts a key event. This may adjust the event or post additional - events in some circumstances. + events in some circumstances. If this is overridden it must forward + to the superclass. */ - void sendKeyEvent(void* target, + virtual void sendKeyEvent(void* target, bool press, bool isAutoRepeat, KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button); @@ -69,6 +71,7 @@ public: SInt32 count, KeyButton button); virtual void fakeKeyUp(KeyButton button); virtual void fakeToggle(KeyModifierMask modifier); + virtual bool fakeCtrlAltDel() = 0; virtual bool isKeyDown(KeyButton) const; virtual KeyModifierMask getActiveModifiers() const; diff --git a/lib/synergy/CPlatformScreen.cpp b/lib/synergy/CPlatformScreen.cpp index 95e0fe77..ea0760a5 100644 --- a/lib/synergy/CPlatformScreen.cpp +++ b/lib/synergy/CPlatformScreen.cpp @@ -63,6 +63,12 @@ CPlatformScreen::fakeToggle(KeyModifierMask modifier) getKeyState()->fakeToggle(modifier); } +bool +CPlatformScreen::fakeCtrlAltDel() +{ + return getKeyState()->fakeCtrlAltDel(); +} + bool CPlatformScreen::isKeyDown(KeyButton button) const { diff --git a/lib/synergy/CPlatformScreen.h b/lib/synergy/CPlatformScreen.h index 68071fbb..b5fcde01 100644 --- a/lib/synergy/CPlatformScreen.h +++ b/lib/synergy/CPlatformScreen.h @@ -43,7 +43,6 @@ public: virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; // ISecondaryScreen overrides - 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; @@ -57,6 +56,7 @@ public: SInt32 count, KeyButton button); virtual void fakeKeyUp(KeyButton button); virtual void fakeToggle(KeyModifierMask modifier); + virtual bool fakeCtrlAltDel(); virtual bool isKeyDown(KeyButton) const; virtual KeyModifierMask getActiveModifiers() const; diff --git a/lib/synergy/CScreen.cpp b/lib/synergy/CScreen.cpp index 9c4594da..787e72ba 100644 --- a/lib/synergy/CScreen.cpp +++ b/lib/synergy/CScreen.cpp @@ -98,13 +98,13 @@ CScreen::enter(KeyModifierMask toggleMask) // now on screen m_entered = true; + m_screen->enter(); if (m_isPrimary) { enterPrimary(); } else { enterSecondary(toggleMask); } - m_screen->enter(); } bool @@ -315,6 +315,16 @@ CScreen::isLockedToScreen() const return true; } +// note -- we don't lock to the screen if a key is down. key +// reporting is simply not reliable enough to trust. the effect +// of switching screens with a key down is that the client will +// receive key repeats and key releases for keys that it hasn't +// see go down. that's okay because CKeyState will ignore those +// events. the user might be surprised that any modifier keys +// held while crossing to another screen don't apply on the +// target screen. if that ends up being a problem we can try +// to synthesize a key press for those modifiers on entry. +/* // check for any pressed key KeyButton key = isAnyKeyDown(); if (key != 0) { @@ -332,6 +342,7 @@ CScreen::isLockedToScreen() const LOG((CLOG_DEBUG "spuriously locked by %s", m_screen->getKeyName(key))); } } +*/ // not locked return false; @@ -431,9 +442,6 @@ CScreen::enterPrimary() void CScreen::enterSecondary(KeyModifierMask toggleMask) { - // update our keyboard state to reflect the local state - m_screen->updateKeys(); - // remember toggle key state. we'll restore this when we leave. m_toggleKeys = getActiveModifiers(); diff --git a/lib/synergy/IKeyState.h b/lib/synergy/IKeyState.h index 7d0b8f63..04e7be8e 100644 --- a/lib/synergy/IKeyState.h +++ b/lib/synergy/IKeyState.h @@ -87,6 +87,13 @@ public: */ virtual void fakeToggle(KeyModifierMask modifier) = 0; + //! Fake ctrl+alt+del + /*! + Synthesize a press of ctrl+alt+del. Return true if processing is + complete and false if normal key processing should continue. + */ + virtual bool fakeCtrlAltDel() = 0; + //@} //! @name accessors //@{ diff --git a/lib/synergy/IPlatformScreen.h b/lib/synergy/IPlatformScreen.h index b97f3453..13bd0a31 100644 --- a/lib/synergy/IPlatformScreen.h +++ b/lib/synergy/IPlatformScreen.h @@ -151,7 +151,6 @@ public: virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; // ISecondaryScreen overrides - 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; @@ -165,6 +164,7 @@ public: SInt32 count, KeyButton button) = 0; virtual void fakeKeyUp(KeyButton button) = 0; virtual void fakeToggle(KeyModifierMask modifier) = 0; + virtual bool fakeCtrlAltDel() = 0; virtual bool isKeyDown(KeyButton) const = 0; virtual KeyModifierMask getActiveModifiers() const = 0; diff --git a/lib/synergy/ISecondaryScreen.h b/lib/synergy/ISecondaryScreen.h index 65ac7708..1311e7af 100644 --- a/lib/synergy/ISecondaryScreen.h +++ b/lib/synergy/ISecondaryScreen.h @@ -28,13 +28,6 @@ public: //! @name accessors //@{ - //! 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. diff --git a/lib/synergy/libsynergy.dsp b/lib/synergy/libsynergy.dsp index a0f80876..f0fd4cc9 100644 --- a/lib/synergy/libsynergy.dsp +++ b/lib/synergy/libsynergy.dsp @@ -91,10 +91,18 @@ SOURCE=.\CClipboard.cpp # End Source File # Begin Source File +SOURCE=.\CKeyState.cpp +# End Source File +# Begin Source File + SOURCE=.\CPacketStreamFilter.cpp # End Source File # Begin Source File +SOURCE=.\CPlatformScreen.cpp +# End Source File +# Begin Source File + SOURCE=.\CProtocolUtil.cpp # End Source File # Begin Source File @@ -107,6 +115,10 @@ SOURCE=.\IClipboard.cpp # End Source File # Begin Source File +SOURCE=.\IKeyState.cpp +# End Source File +# Begin Source File + SOURCE=.\IPrimaryScreen.cpp # End Source File # Begin Source File @@ -131,6 +143,10 @@ SOURCE=.\CClipboard.h # End Source File # Begin Source File +SOURCE=.\CKeyState.h +# End Source File +# Begin Source File + SOURCE=.\ClipboardTypes.h # End Source File # Begin Source File @@ -139,6 +155,10 @@ SOURCE=.\CPacketStreamFilter.h # End Source File # Begin Source File +SOURCE=.\CPlatformScreen.h +# End Source File +# Begin Source File + SOURCE=.\CProtocolUtil.h # End Source File # Begin Source File