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