/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2002 Chris Schoeneman * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "CMSWindowsSecondaryScreen.h" #include "CMSWindowsScreen.h" #include "XScreen.h" #include "CLock.h" #include "CThread.h" #include "CFunctionJob.h" #include "CLog.h" #include "CArchMiscWindows.h" #include // these are only defined when WINVER >= 0x0500 #if !defined(SPI_GETMOUSESPEED) #define SPI_GETMOUSESPEED 112 #endif #if !defined(SPI_SETMOUSESPEED) #define SPI_SETMOUSESPEED 113 #endif // X button stuff #if !defined(WM_XBUTTONDOWN) #define WM_XBUTTONDOWN 0x020B #define WM_XBUTTONUP 0x020C #define WM_XBUTTONDBLCLK 0x020D #define WM_NCXBUTTONDOWN 0x00AB #define WM_NCXBUTTONUP 0x00AC #define WM_NCXBUTTONDBLCLK 0x00AD #define MOUSEEVENTF_XDOWN 0x0100 #define MOUSEEVENTF_XUP 0x0200 #define XBUTTON1 0x0001 #define XBUTTON2 0x0002 #endif // multimedia keys #if !defined(VK_BROWSER_BACK) #define VK_BROWSER_BACK 0xA6 #define VK_BROWSER_FORWARD 0xA7 #define VK_BROWSER_REFRESH 0xA8 #define VK_BROWSER_STOP 0xA9 #define VK_BROWSER_SEARCH 0xAA #define VK_BROWSER_FAVORITES 0xAB #define VK_BROWSER_HOME 0xAC #define VK_VOLUME_MUTE 0xAD #define VK_VOLUME_DOWN 0xAE #define VK_VOLUME_UP 0xAF #define VK_MEDIA_NEXT_TRACK 0xB0 #define VK_MEDIA_PREV_TRACK 0xB1 #define VK_MEDIA_STOP 0xB2 #define VK_MEDIA_PLAY_PAUSE 0xB3 #define VK_LAUNCH_MAIL 0xB4 #define VK_LAUNCH_MEDIA_SELECT 0xB5 #define VK_LAUNCH_APP1 0xB6 #define VK_LAUNCH_APP2 0xB7 #endif // // CMSWindowsSecondaryScreen // // a list of modifier key info const CMSWindowsSecondaryScreen::CModifierInfo CMSWindowsSecondaryScreen::s_modifier[] = { { KeyModifierShift, VK_LSHIFT, VK_RSHIFT, false }, { KeyModifierControl, VK_LCONTROL, VK_RCONTROL | 0x100,false }, { KeyModifierAlt, VK_LMENU, VK_RMENU | 0x100, false }, // note -- no keys for KeyModifierMeta { KeyModifierSuper, VK_LWIN | 0x100, VK_RWIN | 0x100, false }, { KeyModifierCapsLock, VK_CAPITAL, 0, true }, { KeyModifierNumLock, VK_NUMLOCK | 0x100, 0, true }, { KeyModifierScrollLock,VK_SCROLL, 0, true } }; CMSWindowsSecondaryScreen::CMSWindowsSecondaryScreen( IScreenReceiver* receiver) : m_is95Family(CArchMiscWindows::isWindows95Family()), m_window(NULL) { m_screen = new CMSWindowsScreen(receiver, this); } CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() { assert(m_window == NULL); delete m_screen; } CSecondaryScreen::SysKeyID CMSWindowsSecondaryScreen::getUnhanded(SysKeyID sysKeyID) const { switch (sysKeyID) { case VK_LSHIFT: case VK_RSHIFT: return VK_SHIFT; case VK_LCONTROL: case VK_RCONTROL: return VK_CONTROL; case VK_LMENU: case VK_RMENU: return VK_MENU; default: return 0; } } CSecondaryScreen::SysKeyID CMSWindowsSecondaryScreen::getOtherHanded(SysKeyID sysKeyID) const { switch (sysKeyID) { case VK_LSHIFT: return VK_RSHIFT; case VK_RSHIFT: return VK_LSHIFT; case VK_LCONTROL: return VK_RCONTROL; case VK_RCONTROL: return VK_LCONTROL; case VK_LMENU: return VK_RMENU; case VK_RMENU: return VK_LMENU; default: return 0; } } bool CMSWindowsSecondaryScreen::isAutoRepeating(SysKeyID) const { return true; } void CMSWindowsSecondaryScreen::sync() const { m_screen->syncDesktop(); } IScreen* CMSWindowsSecondaryScreen::getScreen() const { return m_screen; } void CMSWindowsSecondaryScreen::onScreensaver(bool) { // ignore } bool CMSWindowsSecondaryScreen::onPreDispatch(const CEvent*) { return false; } bool CMSWindowsSecondaryScreen::onEvent(CEvent* event) { assert(event != NULL); const MSG& msg = event->m_msg; switch (msg.message) { case WM_ACTIVATEAPP: if (msg.wParam == FALSE) { // some other app activated. hide the hider window. ShowWindow(m_window, SW_HIDE); } break; } return false; } void CMSWindowsSecondaryScreen::onOneShotTimerExpired(UInt32) { // ignore } void CMSWindowsSecondaryScreen::postCreateWindow(HWND window) { m_window = window; // update key state updateKeys(); // hide cursor if this screen isn't active if (!isActive()) { SInt32 x, y; getScreen()->getCursorCenter(x, y); showWindow(x, y); } } void CMSWindowsSecondaryScreen::preDestroyWindow(HWND) { // do nothing } void CMSWindowsSecondaryScreen::onAccessibleDesktop() { // get the current keyboard state updateKeys(); } void CMSWindowsSecondaryScreen::onPreMainLoop() { assert(m_window != NULL); } void CMSWindowsSecondaryScreen::onPreOpen() { assert(m_window == NULL); } void CMSWindowsSecondaryScreen::onPreEnter() { assert(m_window != NULL); } void CMSWindowsSecondaryScreen::onPreLeave() { assert(m_window != NULL); } void CMSWindowsSecondaryScreen::createWindow() { // open the desktop and the window m_window = m_screen->openDesktop(); if (m_window == NULL) { throw XScreenOpenFailure(); } } void CMSWindowsSecondaryScreen::destroyWindow() { // release keys that are logically pressed releaseKeys(); // close the desktop and the window m_screen->closeDesktop(); m_window = NULL; } void CMSWindowsSecondaryScreen::showWindow(SInt32 x, SInt32 y) { // move hider window under the given position MoveWindow(m_window, x, y, 1, 1, FALSE); // raise and show the hider window ShowWindow(m_window, SW_SHOWNA); // now warp the mouse fakeMouseMove(x, y); } void CMSWindowsSecondaryScreen::hideWindow() { ShowWindow(m_window, SW_HIDE); } CSecondaryScreen::KeyState CMSWindowsSecondaryScreen::getKeyState(UINT virtualKey) const { BYTE sysState = static_cast(GetKeyState(virtualKey)); KeyState state = 0; if (sysState & 0x01u) { state |= kToggled; } if (sysState & 0x80u) { state |= kDown; } return state; } void CMSWindowsSecondaryScreen::updateKeys(KeyState* keys) { // we only care about the modifier key states keys[VK_LSHIFT] = getKeyState(VK_LSHIFT); keys[VK_RSHIFT] = getKeyState(VK_RSHIFT); keys[VK_SHIFT] = getKeyState(VK_SHIFT); keys[VK_LCONTROL] = getKeyState(VK_LCONTROL); keys[VK_RCONTROL] = getKeyState(VK_RCONTROL); keys[VK_CONTROL] = getKeyState(VK_CONTROL); keys[VK_LMENU] = getKeyState(VK_LMENU); keys[VK_RMENU] = getKeyState(VK_RMENU); keys[VK_MENU] = getKeyState(VK_MENU); keys[VK_LWIN] = getKeyState(VK_LWIN); keys[VK_RWIN] = getKeyState(VK_RWIN); keys[VK_APPS] = getKeyState(VK_APPS); keys[VK_CAPITAL] = getKeyState(VK_CAPITAL); keys[VK_NUMLOCK] = getKeyState(VK_NUMLOCK); keys[VK_SCROLL] = getKeyState(VK_SCROLL); } KeyModifierMask CMSWindowsSecondaryScreen::getModifiers() const { // update active modifier mask KeyModifierMask mask = 0; if (isKeyDown(VK_LSHIFT) || isKeyDown(VK_RSHIFT)) { mask |= KeyModifierShift; } if (isKeyDown(VK_LCONTROL) || isKeyDown(VK_RCONTROL)) { mask |= KeyModifierControl; } if (isKeyDown(VK_LMENU) || isKeyDown(VK_RMENU)) { mask |= KeyModifierAlt; } // note -- no keys for KeyModifierMeta if (isKeyDown(VK_LWIN) || isKeyDown(VK_RWIN)) { mask |= KeyModifierSuper; } if (isKeyToggled(VK_CAPITAL)) { mask |= KeyModifierCapsLock; } if (isKeyToggled(VK_NUMLOCK)) { mask |= KeyModifierNumLock; } if (isKeyToggled(VK_SCROLL)) { mask |= KeyModifierScrollLock; } // note -- do not save KeyModifierModeSwitch in mask return mask; } // map special KeyID keys to virtual key codes. if the key is an // extended key then the entry is the virtual key code | 0x100. // unmapped keys have a 0 entry. static const UINT g_mapE000[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */ 0, 0, 0, 0, /* 0xa4 */ 0, 0, VK_BROWSER_BACK|0x100, VK_BROWSER_FORWARD|0x100, /* 0xa8 */ VK_BROWSER_REFRESH|0x100, VK_BROWSER_STOP|0x100, /* 0xaa */ VK_BROWSER_SEARCH|0x100, VK_BROWSER_FAVORITES|0x100, /* 0xac */ VK_BROWSER_HOME|0x100, VK_VOLUME_MUTE|0x100, /* 0xae */ VK_VOLUME_DOWN|0x100, VK_VOLUME_UP|0x100, /* 0xb0 */ VK_MEDIA_NEXT_TRACK|0x100, VK_MEDIA_PREV_TRACK|0x100, /* 0xb2 */ VK_MEDIA_STOP|0x100, VK_MEDIA_PLAY_PAUSE|0x100, /* 0xb4 */ VK_LAUNCH_MAIL|0x100, VK_LAUNCH_MEDIA_SELECT|0x100, /* 0xb6 */ VK_LAUNCH_APP1|0x100, VK_LAUNCH_APP2|0x100, /* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0 }; static const UINT g_mapEE00[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x18 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */ VK_TAB, 0, 0, 0, 0, 0, 0, 0, /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0 }; static const UINT g_mapEF00[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x08 */ VK_BACK, VK_TAB, 0, VK_CLEAR, 0, VK_RETURN, 0, 0, /* 0x10 */ 0, 0, 0, VK_PAUSE, VK_SCROLL, 0/*sys-req*/, 0, 0, /* 0x18 */ 0, 0, 0, VK_ESCAPE, 0, 0, 0, 0, /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x28 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */ VK_HOME|0x100, VK_LEFT|0x100, VK_UP|0x100, VK_RIGHT|0x100, /* 0x54 */ VK_DOWN|0x100, VK_PRIOR|0x100, VK_NEXT|0x100, VK_END|0x100, /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 */ VK_SELECT|0x100, VK_SNAPSHOT|0x100, VK_EXECUTE|0x100, VK_INSERT|0x100, /* 0x64 */ 0, 0, 0, VK_APPS|0x100, /* 0x68 */ 0, 0, VK_HELP|0x100, VK_CANCEL|0x100, 0, 0, 0, 0, /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, VK_NUMLOCK|0x100, /* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0, /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN|0x100, 0, 0, /* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP, /* 0x98 */ VK_RIGHT, VK_DOWN, VK_PRIOR, VK_NEXT, /* 0x9c */ VK_END, 0, VK_INSERT, VK_DELETE, /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa8 */ 0, 0, VK_MULTIPLY, VK_ADD, /* 0xac */ VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE|0x100, /* 0xb0 */ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, /* 0xb4 */ VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, /* 0xb8 */ VK_NUMPAD8, VK_NUMPAD9, 0, 0, 0, 0, VK_F1, VK_F2, /* 0xc0 */ VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, /* 0xc8 */ VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, /* 0xd0 */ VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, 0, 0, /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, /* 0xe4 */ VK_RCONTROL|0x100, VK_CAPITAL, 0, 0, /* 0xe8 */ 0, VK_LMENU, VK_RMENU|0x100, VK_LWIN|0x100, /* 0xec */ VK_RWIN|0x100, 0, 0, 0, /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE|0x100 }; DWORD CMSWindowsSecondaryScreen::mapButton(ButtonID button, bool press, DWORD* inData) const { DWORD dummy; DWORD* data = (inData != NULL) ? inData : &dummy; // the system will swap the meaning of left/right for us if // the user has configured a left-handed mouse but we don't // want it to swap since we want the handedness of the // server's mouse. so pre-swap for a left-handed mouse. if (GetSystemMetrics(SM_SWAPBUTTON)) { switch (button) { case kButtonLeft: button = kButtonRight; break; case kButtonRight: button = kButtonLeft; break; } } // map button id to button flag and button data *data = 0; switch (button) { case kButtonLeft: return press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; case kButtonMiddle: return press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; case kButtonRight: return press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; case kButtonExtra0 + 0: *data = XBUTTON1; return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; case kButtonExtra0 + 1: *data = XBUTTON2; return press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; default: return 0; } } KeyModifierMask CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, SysKeyID& virtualKey, KeyID id, KeyModifierMask currentMask, KeyModifierMask desiredMask, EKeyAction action) const { virtualKey = 0; // check for special keys if ((id & 0xfffff000) == 0xe000) { if ((id & 0xff00) == 0xe000) { virtualKey = g_mapE000[id & 0xff]; } else if ((id & 0xff00) == 0xee00) { virtualKey = g_mapEE00[id & 0xff]; } else if ((id & 0xff00) == 0xef00) { virtualKey = g_mapEF00[id & 0xff]; } if (virtualKey == 0) { LOG((CLOG_DEBUG2 "unknown special key")); return currentMask; } } // special handling of VK_SNAPSHOT if ((virtualKey & 0xff) == VK_SNAPSHOT) { // ignore key repeats on print screen if (action != kRepeat) { // get event flags DWORD flags = 0; if (isExtendedKey(virtualKey)) { flags |= KEYEVENTF_EXTENDEDKEY; } if (action != kPress) { flags |= KEYEVENTF_KEYUP; } // active window or fullscreen? BYTE scan = 0; if ((desiredMask & KeyModifierAlt) == 0) { scan = 1; } // send event keybd_event(static_cast(virtualKey & 0xff), scan, flags, 0); } return currentMask; } // handle other special keys if (virtualKey != 0) { // compute the final desired modifier mask. special keys use // the desired modifiers as given except we keep the caps lock, // num lock, and scroll lock as is. KeyModifierMask outMask = (currentMask & (KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock)); outMask |= (desiredMask & (KeyModifierShift | KeyModifierControl | KeyModifierAlt | KeyModifierMeta | KeyModifierSuper)); // strip out extended key flag UINT virtualKey2 = (virtualKey & ~0x100); // check numeric keypad. note that virtual keys do not distinguish // between the keypad and non-keypad movement keys. however, the // virtual keys do distinguish between keypad numbers and operators // (e.g. add, multiply) and their main keyboard counterparts. // therefore, we can ignore the num-lock state for movement virtual // keys but not for numeric keys. if (virtualKey2 >= VK_NUMPAD0 && virtualKey2 <= VK_DIVIDE) { // set required shift state based on current numlock state if ((outMask & KeyModifierNumLock) == 0) { if ((currentMask & KeyModifierNumLock) == 0) { LOG((CLOG_DEBUG2 "turn on num lock for keypad key")); outMask |= KeyModifierNumLock; } else { LOG((CLOG_DEBUG2 "turn on shift for keypad key")); outMask |= KeyModifierShift; } } } // check for left tab. that requires the shift key. if (id == kKeyLeftTab) { // XXX -- this isn't used; outMask meant instead? desiredMask |= KeyModifierShift; } // now generate the keystrokes and return the resulting modifier mask LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d mask 0x%04x", id, virtualKey2, outMask)); return mapToKeystrokes(keys, virtualKey, currentMask, outMask, action); } // determine the thread that'll receive this event // FIXME -- we can't be sure we'll get the right thread here HWND targetWindow = GetForegroundWindow(); DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL); // figure out the code page for the target thread. i'm just // guessing here. get the target thread's keyboard layout, // extract the language id from that, and choose the code page // based on that language. HKL hkl = GetKeyboardLayout(targetThread); LANGID langID = static_cast(LOWORD(hkl)); UINT codePage = getCodePageFromLangID(langID); LOG((CLOG_DEBUG2 "using code page %d and language id 0x%04x for thread 0x%08x", codePage, langID, targetThread)); // regular characters are complicated by dead keys. it may not be // possible to generate a desired character directly. we may need // to generate a dead key first then some other character. the // app receiving the events will compose these two characters into // a single precomposed character. // // as best as i can tell this is the simplest way to convert a // character into its uncomposed version. along the way we'll // discover if the key cannot be handled at all. we convert // from wide char to multibyte, then from multibyte to wide char // forcing conversion to composite characters, then from wide // char back to multibyte without making precomposed characters. // // after the first conversion to multibyte we see if we can map // the key. if so then we don't bother trying to decompose dead // keys. BOOL error; char multiByte[2 * MB_LEN_MAX]; wchar_t unicode[2]; unicode[0] = static_cast(id & 0x0000ffff); int nChars = WideCharToMultiByte(codePage, WC_COMPOSITECHECK | WC_DEFAULTCHAR, unicode, 1, multiByte, sizeof(multiByte), NULL, &error); if (nChars == 0 || error) { LOG((CLOG_DEBUG2 "KeyID 0x%08x not in code page", id)); return currentMask; } virtualKey = mapCharacter(keys, multiByte[0], hkl, currentMask, desiredMask, action); if (virtualKey != static_cast(-1)) { LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); if ((MapVirtualKey(virtualKey, 2) & 0x80000000u) != 0) { // it looks like this character is a dead key but // MapVirtualKey() will claim it's a dead key even if it's // not (though i don't think it ever claims it's not when // it is). we need a backup test to ensure that this is // really a dead key. we could use ToAscii() for this but // that keeps state and it's a hassle to restore that state. // OemKeyScan() appears to do the trick. if the character // cannot be generated with a single keystroke then it // returns 0xffffffff. if (OemKeyScan(multiByte[0]) != 0xffffffffu) { // character mapped to a dead key but we want the // character for real so send a space key afterwards. LOG((CLOG_DEBUG2 "character mapped to dead key")); Keystroke keystroke; keystroke.m_sysKeyID = VK_SPACE; keystroke.m_press = true; keystroke.m_repeat = false; keys.push_back(keystroke); keystroke.m_press = false; keys.push_back(keystroke); // ignore the release of this key since we already // handled it in mapCharacter(). virtualKey = 0; } } return currentMask; } nChars = MultiByteToWideChar(codePage, MB_COMPOSITE | MB_ERR_INVALID_CHARS, multiByte, nChars, unicode, 2); if (nChars == 0) { LOG((CLOG_DEBUG2 "KeyID 0x%08x mb->wc mapping failed", id)); return currentMask; } nChars = WideCharToMultiByte(codePage, 0, unicode, nChars, multiByte, sizeof(multiByte), NULL, &error); if (nChars == 0 || error) { LOG((CLOG_DEBUG2 "KeyID 0x%08x wc->mb mapping failed", id)); return currentMask; } // we expect one or two characters in multiByte. if there are two // then the *second* is a dead key. process the dead key if there. // FIXME -- we assume each character is one byte here if (nChars > 2) { LOG((CLOG_DEBUG2 "multibyte characters not supported for character 0x%04x", id)); return currentMask; } if (nChars == 2) { LOG((CLOG_DEBUG2 "KeyID 0x%08x needs dead key %u", id, (unsigned char)multiByte[1])); mapCharacter(keys, multiByte[1], hkl, currentMask, desiredMask, action); } // process character LOG((CLOG_DEBUG2 "KeyID 0x%08x maps to character %u", id, (unsigned char)multiByte[0])); virtualKey = mapCharacter(keys, multiByte[0], hkl, currentMask, desiredMask, action); // non-special key cannot modify the modifier mask return currentMask; } KeyModifierMask CMSWindowsSecondaryScreen::getModifierKeyMask(SysKeyID virtualKey) const { switch (virtualKey) { case VK_CAPITAL: return KeyModifierCapsLock; case VK_NUMLOCK: return KeyModifierNumLock; case VK_SCROLL: return KeyModifierScrollLock; default: return 0; } } bool CMSWindowsSecondaryScreen::isModifierActive(SysKeyID virtualKey) const { // check if any virtual key for this modifier is down. return false // for toggle modifiers. const CModifierInfo* modifier = getModifierInfo(virtualKey); if (modifier != NULL && !modifier->m_isToggle) { if (isKeyDown(modifier->m_virtualKey & 0xff) || isKeyDown(modifier->m_virtualKey2 & 0xff)) { return true; } } return false; } UINT CMSWindowsSecondaryScreen::mapCharacter(Keystrokes& keys, char c, HKL hkl, KeyModifierMask currentMask, KeyModifierMask desiredMask, EKeyAction action) const { // translate the character into its virtual key and its required // modifier state. SHORT virtualKeyAndModifierState = VkKeyScanEx(c, hkl); // get virtual key UINT virtualKey = LOBYTE(virtualKeyAndModifierState); if (LOBYTE(virtualKeyAndModifierState) == 0xff) { LOG((CLOG_DEBUG2 "cannot map character %d", static_cast(c))); return static_cast(-1); } // get the required modifier state BYTE modifierState = HIBYTE(virtualKeyAndModifierState); // compute the final desired modifier mask. this is the // desired modifier mask except that the system might require // that certain modifiers be up or down in order to generate // the character. to start with, we know that we want to keep // the caps lock, num lock, scroll lock modifiers as is. also, // the system never requires the meta or super modifiers so we // can set those however we like. KeyModifierMask outMask = (currentMask & (KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock)); outMask |= (desiredMask & (KeyModifierMeta | KeyModifierSuper)); // win32 does not permit ctrl and alt used together to // modify a character because ctrl and alt together mean // AltGr. if the desired mask has both ctrl and alt then // strip them both out. if ((desiredMask & (KeyModifierControl | KeyModifierAlt)) == (KeyModifierControl | KeyModifierAlt)) { desiredMask &= ~(KeyModifierControl | KeyModifierAlt); } // strip out the desired shift state. we're forced to use // a particular shift state to generate the desired character. outMask &= ~KeyModifierShift; // use the required modifiers. if AltGr is required then // modifierState will indicate control and alt. if ((modifierState & 1) != 0) { outMask |= KeyModifierShift; } if ((modifierState & 2) != 0) { outMask |= KeyModifierControl; } if ((modifierState & 4) != 0) { outMask |= KeyModifierAlt; } // use desired modifiers if ((desiredMask & KeyModifierControl) != 0) { outMask |= KeyModifierControl; } if ((desiredMask & KeyModifierAlt) != 0) { outMask |= KeyModifierAlt; } // handle combination of caps-lock and shift. if caps-lock is // off locally then use shift as necessary. if caps-lock is on // locally then it reverses the meaning of shift for keys that // are subject to case conversion. if ((outMask & KeyModifierCapsLock) != 0) { // there doesn't seem to be a simple way to test if a // character respects the caps lock key. for normal // characters it's easy enough but CharLower() and // CharUpper() don't map dead keys even though they // do respect caps lock for some unfathomable reason. // first check the easy way. if that doesn't work // then see if it's a dead key. unsigned char uc = static_cast(c); if (CharLower((LPTSTR)uc) != CharUpper((LPTSTR)uc) || (MapVirtualKey(virtualKey, 2) & 0x80000000lu) != 0) { LOG((CLOG_DEBUG2 "flip shift")); outMask ^= KeyModifierShift; } } // now generate the keystrokes. ignore the resulting modifier // mask since it can't have changed (because we don't call this // method for modifier keys). LOG((CLOG_DEBUG2 "character %d to virtual key %d mask 0x%04x", (unsigned char)c, virtualKey, outMask)); mapToKeystrokes(keys, virtualKey, currentMask, outMask, action); return virtualKey; } KeyModifierMask CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, UINT virtualKey, KeyModifierMask currentMask, KeyModifierMask desiredMask, EKeyAction action) const { const CModifierInfo* modifier = getModifierInfo(virtualKey); // add the key events required to get to the desired modifier state. // also save the key events required to restore the current state. // if the key is a modifier key then skip this because modifiers // should not modify modifiers. Keystrokes undo; Keystroke keystroke; if (desiredMask != currentMask && modifier == NULL) { const unsigned int s_numModifiers = sizeof(s_modifier) / sizeof(s_modifier[0]); for (unsigned int i = 0; i < s_numModifiers; ++i) { KeyModifierMask bit = s_modifier[i].m_mask; if ((desiredMask & bit) != (currentMask & bit)) { if ((desiredMask & bit) != 0) { // modifier is not active but should be. if the // modifier is a toggle then toggle it on with a // press/release, otherwise activate it with a // press. keystroke.m_sysKeyID = s_modifier[i].m_virtualKey; keystroke.m_press = true; keystroke.m_repeat = false; keys.push_back(keystroke); if (s_modifier[i].m_isToggle) { keystroke.m_press = false; keys.push_back(keystroke); undo.push_back(keystroke); keystroke.m_press = true; undo.push_back(keystroke); } else { keystroke.m_press = false; undo.push_back(keystroke); } } else { // modifier is active but should not be. if the // modifier is a toggle then toggle it off with a // press/release, otherwise deactivate it with a // release. we must check each keycode for the // modifier if not a toggle. if (s_modifier[i].m_isToggle) { keystroke.m_sysKeyID = s_modifier[i].m_virtualKey; keystroke.m_press = true; keystroke.m_repeat = false; keys.push_back(keystroke); keystroke.m_press = false; keys.push_back(keystroke); undo.push_back(keystroke); keystroke.m_press = true; undo.push_back(keystroke); } else { UINT key = s_modifier[i].m_virtualKey; if (isKeyDown(key & 0xff)) { keystroke.m_sysKeyID = key; keystroke.m_press = false; keystroke.m_repeat = false; keys.push_back(keystroke); keystroke.m_press = true; undo.push_back(keystroke); } key = s_modifier[i].m_virtualKey2; if (isKeyDown(key & 0xff)) { keystroke.m_sysKeyID = key; keystroke.m_press = false; keystroke.m_repeat = false; keys.push_back(keystroke); keystroke.m_press = true; undo.push_back(keystroke); } } } } } } // add the key event keystroke.m_sysKeyID = virtualKey; switch (action) { case kPress: keystroke.m_press = true; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRelease: keystroke.m_press = false; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRepeat: keystroke.m_press = true; keystroke.m_repeat = true; keys.push_back(keystroke); break; } // if this is a dead key press then send a release immediately. // the dead key may not be processed correctly if its release // event comes after we release the modifiers. if (action == kPress && (MapVirtualKey(virtualKey, 2) & 0x80000000lu) != 0) { keystroke.m_press = false; keys.push_back(keystroke); } // add key events to restore the modifier state. apply events in // the reverse order that they're stored in undo. while (!undo.empty()) { keys.push_back(undo.back()); undo.pop_back(); } // if the key is a modifier key then compute the modifier mask after // this key is pressed. toggle keys modify the state on release. // other keys set the modifier bit on press. KeyModifierMask mask = currentMask; if (action == kPress) { if (modifier != NULL && !modifier->m_isToggle) { mask |= modifier->m_mask; } } LOG((CLOG_DEBUG2 "previous modifiers 0x%04x, final modifiers 0x%04x", currentMask, mask)); return mask; } const CMSWindowsSecondaryScreen::CModifierInfo* CMSWindowsSecondaryScreen::getModifierInfo(UINT virtualKey) const { // note if the key is a modifier. strip out extended key flag from // virtual key before lookup. switch (virtualKey & 0xffu) { case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: return s_modifier + 0; case VK_CONTROL: case VK_LCONTROL: case VK_RCONTROL: return s_modifier + 1; case VK_MENU: case VK_LMENU: case VK_RMENU: return s_modifier + 2; case VK_LWIN: case VK_RWIN: return s_modifier + 3; case VK_CAPITAL: return s_modifier + 4; case VK_NUMLOCK: return s_modifier + 5; case VK_SCROLL: return s_modifier + 6; default: return NULL; } } CSecondaryScreen::SysKeyID CMSWindowsSecondaryScreen::getToggleSysKey(KeyID keyID) const { switch (keyID) { case kKeyNumLock: return VK_NUMLOCK | 0x100; case kKeyCapsLock: return VK_CAPITAL; case kKeyScrollLock: return VK_SCROLL; default: return 0; } } UINT CMSWindowsSecondaryScreen::virtualKeyToScanCode(UINT& virtualKey) const { // try mapping given virtual key UINT code = MapVirtualKey(virtualKey & 0xff, 0); if (code != 0) { return code; } // no dice. if the virtual key distinguishes between left/right // then try the one that doesn't distinguish sides. windows (or // keyboard drivers) are inconsistent in their treatment of these // virtual keys. the following behaviors have been observed: // // win2k (gateway desktop): // MapVirtualKey(vk, 0): // VK_SHIFT == VK_LSHIFT != VK_RSHIFT // VK_CONTROL == VK_LCONTROL == VK_RCONTROL // VK_MENU == VK_LMENU == VK_RMENU // MapVirtualKey(sc, 3): // VK_LSHIFT and VK_RSHIFT mapped independently // VK_LCONTROL is mapped but not VK_RCONTROL // VK_LMENU is mapped but not VK_RMENU // // win me (sony vaio laptop): // MapVirtualKey(vk, 0): // VK_SHIFT mapped; VK_LSHIFT, VK_RSHIFT not mapped // VK_CONTROL mapped; VK_LCONTROL, VK_RCONTROL not mapped // VK_MENU mapped; VK_LMENU, VK_RMENU not mapped // MapVirtualKey(sc, 3): // all scan codes unmapped (function apparently unimplemented) switch (virtualKey & 0xff) { case VK_LSHIFT: case VK_RSHIFT: virtualKey = VK_SHIFT; return MapVirtualKey(VK_SHIFT, 0); case VK_LCONTROL: case VK_RCONTROL: virtualKey = VK_CONTROL; return MapVirtualKey(VK_CONTROL, 0); case VK_LMENU: case VK_RMENU: virtualKey = VK_MENU; return MapVirtualKey(VK_MENU, 0); default: return 0; } } bool CMSWindowsSecondaryScreen::isExtendedKey(UINT virtualKey) const { // see if we've already encoded the extended flag if ((virtualKey & 0x100) != 0) { return true; } // check known virtual keys switch (virtualKey & 0xffu) { case VK_NUMLOCK: case VK_RCONTROL: case VK_RMENU: case VK_LWIN: case VK_RWIN: case VK_APPS: return true; default: return false; } } void CMSWindowsSecondaryScreen::fakeKeyEvent(UINT virtualKey, bool press) const { DWORD flags = 0; if (isExtendedKey(virtualKey)) { flags |= KEYEVENTF_EXTENDEDKEY; } if (!press) { flags |= KEYEVENTF_KEYUP; } const UINT code = virtualKeyToScanCode(virtualKey); keybd_event(static_cast(virtualKey & 0xff), static_cast(code), flags, 0); } void CMSWindowsSecondaryScreen::fakeMouseButton(ButtonID button, bool press) const { // map button id to button flag DWORD data; DWORD flags = mapButton(button, press, &data); // send event if (flags != 0) { mouse_event(flags, 0, 0, data, 0); } } void CMSWindowsSecondaryScreen::fakeMouseMove(SInt32 x, SInt32 y) const { // motion is simple (i.e. it's on the primary monitor) if there // is only one monitor. bool simple = !m_screen->isMultimon(); if (!simple) { // also simple if motion is within the primary monitor simple = (x >= 0 && x < GetSystemMetrics(SM_CXSCREEN) && y >= 0 && y < GetSystemMetrics(SM_CYSCREEN)); } // move the mouse directly to target position if motion is simple if (simple) { // when using absolute positioning with mouse_event(), // the normalized device coordinates range over only // the primary screen. SInt32 w = GetSystemMetrics(SM_CXSCREEN); SInt32 h = GetSystemMetrics(SM_CYSCREEN); mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, (DWORD)((65536.0 * x) / w), (DWORD)((65536.0 * y) / h), 0, 0); } // windows 98 (and Me?) is broken. you cannot set the absolute // position of the mouse except on the primary monitor but you // can do relative moves onto any monitor. this is, in microsoft's // words, "by design." apparently the designers of windows 2000 // we're a little less lazy and did it right. // // microsoft recommends in Q193003 to absolute position the cursor // somewhere on the primary monitor then relative move to the // desired location. this doesn't work for us because when the // user drags a scrollbar, a window, etc. it causes the dragged // item to jump back a forth between the position on the primary // monitor and the desired position. while it always ends up in // the right place, the effect is disconcerting. // // instead we'll get the cursor's current position and do just a // relative move from there to the desired position. relative // moves are subject to cursor acceleration which we don't want. // so we disable acceleration, do the relative move, then restore // acceleration. there's a slight chance we'll end up in the // wrong place if the user moves the cursor using this system's // mouse while simultaneously moving the mouse on the server // system. that defeats the purpose of synergy so we'll assume // that won't happen. even if it does, the next mouse move will // correct the position. else { // save mouse speed & acceleration int oldSpeed[4]; bool accelChanged = SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) && SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); // use 1:1 motion if (accelChanged) { int newSpeed[4] = { 0, 0, 0, 1 }; accelChanged = SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); } // get current mouse position POINT pos; GetCursorPos(&pos); // move relative to mouse position mouse_event(MOUSEEVENTF_MOVE, x - pos.x, y - pos.y, 0, 0); // restore mouse speed & acceleration if (accelChanged) { SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); } } } void CMSWindowsSecondaryScreen::fakeMouseWheel(SInt32 delta) { mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0); } UINT CMSWindowsSecondaryScreen::getCodePageFromLangID(LANGID langid) const { // construct a locale id from the language id LCID lcid = MAKELCID(langid, SORT_DEFAULT); // get the ANSI code page for this locale char data[6]; if (GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE, data, 6) == 0) { // can't get code page LOG((CLOG_DEBUG1 "can't find code page for langid 0x%04x", langid)); return CP_ACP; } // convert stringified code page into a number UINT codePage = static_cast(atoi(data)); if (codePage == 0) { // parse failed LOG((CLOG_DEBUG1 "can't parse code page %s for langid 0x%04x", data, langid)); return CP_ACP; } return codePage; } bool CMSWindowsSecondaryScreen::synthesizeCtrlAltDel(EKeyAction action) { // ignore except for key press if (action != kPress) { return true; } if (!m_is95Family) { // to fake ctrl+alt+del on the NT family we broadcast a suitable // hotkey to all windows on the winlogon desktop. however, we // the current thread must be on that desktop to do the broadcast // and we can't switch just any thread because some own windows // or hooks. so start a new thread to do the real work. CThread cad(new CFunctionJob( &CMSWindowsSecondaryScreen::ctrlAltDelThread)); cad.wait(); } else { Keystrokes keys; UINT virtualKey; KeyID key = kKeyDelete; KeyModifierMask mask = KeyModifierControl | KeyModifierAlt; // get the sequence of keys to simulate ctrl+alt+del mapKey(keys, virtualKey, key, mask, kPress); if (!keys.empty()) { // generate key events doKeystrokes(keys, 1); } } return true; } void CMSWindowsSecondaryScreen::ctrlAltDelThread(void*) { // get the Winlogon desktop at whatever privilege we can HDESK desk = OpenDesktop("Winlogon", 0, FALSE, MAXIMUM_ALLOWED); if (desk != NULL) { if (SetThreadDesktop(desk)) { PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELPARAM(MOD_CONTROL | MOD_ALT, VK_DELETE)); } else { LOG((CLOG_DEBUG "can't switch to Winlogon desk: %d", GetLastError())); } CloseDesktop(desk); } else { LOG((CLOG_DEBUG "can't open Winlogon desk: %d", GetLastError())); } }