diff --git a/lib/platform/CMSWindowsSecondaryScreen.cpp b/lib/platform/CMSWindowsSecondaryScreen.cpp index cc617337..c04cfcb0 100644 --- a/lib/platform/CMSWindowsSecondaryScreen.cpp +++ b/lib/platform/CMSWindowsSecondaryScreen.cpp @@ -86,8 +86,7 @@ const CMSWindowsSecondaryScreen::CModifierInfo CMSWindowsSecondaryScreen::CMSWindowsSecondaryScreen( IScreenReceiver* receiver) : m_is95Family(CArchMiscWindows::isWindows95Family()), - m_window(NULL), - m_mask(0) + m_window(NULL) { m_screen = new CMSWindowsScreen(receiver, this); } @@ -99,246 +98,64 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() delete m_screen; } -void -CMSWindowsSecondaryScreen::keyDown(KeyID key, - KeyModifierMask mask, KeyButton button) +CSecondaryScreen::SysKeyID +CMSWindowsSecondaryScreen::getUnhanded(SysKeyID sysKeyID) const { - CLock lock(&m_mutex); - m_screen->syncDesktop(); - - // check for ctrl+alt+del emulation - if (key == kKeyDelete && - (mask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - synthesizeCtrlAltDel(); - return; - } - - // get the sequence of keys to simulate key press and the final - // modifier state. - Keystrokes keys; - UINT virtualKey; - m_mask = mapKey(keys, virtualKey, key, mask, kPress); - if (keys.empty()) { - // do nothing if there are no associated keys (i.e. lookup failed) - return; - } - - // generate key events - doKeystrokes(keys, 1); - - // do not record button down if button or virtual key is 0 (invalid) - if (button != 0 && virtualKey != 0) { - // note that key is now down - m_serverKeyMap[button] = virtualKey; - m_keys[virtualKey] |= 0x80; - m_fakeKeys[virtualKey] |= 0x80; - switch (virtualKey) { - case VK_LSHIFT: - case VK_RSHIFT: - m_keys[VK_SHIFT] |= 0x80; - m_fakeKeys[VK_SHIFT] |= 0x80; - break; - - case VK_LCONTROL: - case VK_RCONTROL: - m_keys[VK_CONTROL] |= 0x80; - m_fakeKeys[VK_CONTROL] |= 0x80; - break; - - case VK_LMENU: - case VK_RMENU: - m_keys[VK_MENU] |= 0x80; - m_fakeKeys[VK_MENU] |= 0x80; - break; - } - } -} - -void -CMSWindowsSecondaryScreen::keyRepeat(KeyID key, - KeyModifierMask mask, SInt32 count, KeyButton button) -{ - CLock lock(&m_mutex); - m_screen->syncDesktop(); - - // if we haven't seen this button go down then ignore it - ServerKeyMap::iterator index = m_serverKeyMap.find(button); - if (index == m_serverKeyMap.end()) { - return; - } - - // get the sequence of keys to simulate key repeat and the final - // modifier state. - Keystrokes keys; - UINT virtualKey; - m_mask = mapKey(keys, virtualKey, key, mask, kRepeat); - if (keys.empty()) { - return; - } - - // if the keycode for the auto-repeat is not the same as for the - // initial press then mark the initial key as released and the new - // key as pressed. this can happen when we auto-repeat after a - // dead key. for example, a dead accent followed by 'a' will - // generate an 'a with accent' followed by a repeating 'a'. the - // keycodes for the two keysyms might be different. - if (virtualKey != index->second) { - // replace key up with previous keycode but leave key down - // alone so it uses the new keycode and store that keycode - // in the server key map. - for (Keystrokes::iterator index2 = keys.begin(); - index2 != keys.end(); ++index2) { - if (index2->m_virtualKey == virtualKey) { - index2->m_virtualKey = index->second; - break; - } - } - - // note that old key is now up - m_keys[index->second] = false; - m_fakeKeys[index->second] = false; - - // map server key to new key - index->second = virtualKey; - - // note that new key is now down - m_keys[index->second] = true; - m_fakeKeys[index->second] = true; - } - - // generate key events - doKeystrokes(keys, count); -} - -void -CMSWindowsSecondaryScreen::keyUp(KeyID, KeyModifierMask, KeyButton button) -{ - CLock lock(&m_mutex); - m_screen->syncDesktop(); - - // if we haven't seen this button go down then ignore it - ServerKeyMap::iterator index = m_serverKeyMap.find(button); - if (index == m_serverKeyMap.end()) { - return; - } - UINT virtualKey = index->second; - - // get the sequence of keys to simulate key release and the final - // modifier state. - Keystrokes keys; - m_mask = mapKeyRelease(keys, virtualKey); - - // generate key events - doKeystrokes(keys, 1); - - // note that key is now up - m_serverKeyMap.erase(index); - m_keys[virtualKey] &= ~0x80; - m_fakeKeys[virtualKey] &= ~0x80; - switch (virtualKey) { + switch (sysKeyID) { case VK_LSHIFT: - if ((m_keys[VK_RSHIFT] & 0x80) == 0) { - m_keys[VK_SHIFT] &= ~0x80; - m_fakeKeys[VK_SHIFT] &= ~0x80; - } - break; - case VK_RSHIFT: - if ((m_keys[VK_LSHIFT] & 0x80) == 0) { - m_keys[VK_SHIFT] &= ~0x80; - m_fakeKeys[VK_SHIFT] &= ~0x80; - } - break; + return VK_SHIFT; case VK_LCONTROL: - if ((m_keys[VK_RCONTROL] & 0x80) == 0) { - m_keys[VK_CONTROL] &= ~0x80; - m_fakeKeys[VK_CONTROL] &= ~0x80; - } - break; - case VK_RCONTROL: - if ((m_keys[VK_LCONTROL] & 0x80) == 0) { - m_keys[VK_CONTROL] &= ~0x80; - m_fakeKeys[VK_CONTROL] &= ~0x80; - } - break; + return VK_CONTROL; case VK_LMENU: - if ((m_keys[VK_RMENU] & 0x80) == 0) { - m_keys[VK_MENU] &= ~0x80; - m_fakeKeys[VK_MENU] &= ~0x80; - } - break; + 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: - if ((m_keys[VK_LMENU] & 0x80) == 0) { - m_keys[VK_MENU] &= ~0x80; - m_fakeKeys[VK_MENU] &= ~0x80; - } - break; + return VK_LMENU; + + default: + return 0; } } -void -CMSWindowsSecondaryScreen::mouseDown(ButtonID button) +bool +CMSWindowsSecondaryScreen::isAutoRepeating(SysKeyID) const +{ + return true; +} + +void +CMSWindowsSecondaryScreen::sync() const { - CLock lock(&m_mutex); m_screen->syncDesktop(); - - // map button id to button flag - DWORD data; - DWORD flags = mapButton(button, true, &data); - - // send event - if (flags != 0) { - mouse_event(flags, 0, 0, data, 0); - } -} - -void -CMSWindowsSecondaryScreen::mouseUp(ButtonID button) -{ - CLock lock(&m_mutex); - m_screen->syncDesktop(); - - // map button id to button flag - DWORD data; - DWORD flags = mapButton(button, false, &data); - - // send event - if (flags != 0) { - mouse_event(flags, 0, 0, data, 0); - } -} - -void -CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) -{ - CLock lock(&m_mutex); - m_screen->syncDesktop(); - warpCursor(x, y); -} - -void -CMSWindowsSecondaryScreen::mouseWheel(SInt32 delta) -{ - CLock lock(&m_mutex); - m_screen->syncDesktop(); - mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0); -} - -void -CMSWindowsSecondaryScreen::resetOptions() -{ - CSecondaryScreen::resetOptions(); -} - -void -CMSWindowsSecondaryScreen::setOptions(const COptionsList& options) -{ - CSecondaryScreen::setOptions(options); } IScreen* @@ -383,12 +200,6 @@ CMSWindowsSecondaryScreen::onOneShotTimerExpired(UInt32) // ignore } -SInt32 -CMSWindowsSecondaryScreen::getJumpZoneSize() const -{ - return 0; -} - void CMSWindowsSecondaryScreen::postCreateWindow(HWND window) { @@ -473,7 +284,7 @@ CMSWindowsSecondaryScreen::showWindow(SInt32 x, SInt32 y) ShowWindow(m_window, SW_SHOWNA); // now warp the mouse - warpCursor(x, y); + fakeMouseMove(x, y); } void @@ -482,164 +293,70 @@ CMSWindowsSecondaryScreen::hideWindow() ShowWindow(m_window, SW_HIDE); } -void -CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) +CSecondaryScreen::KeyState +CMSWindowsSecondaryScreen::getKeyState(UINT virtualKey) 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)); + BYTE sysState = static_cast(GetKeyState(virtualKey)); + KeyState state = 0; + if (sysState & 0x01u) { + state |= kToggled; } - - // 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); - } + if (sysState & 0x80u) { + state |= kDown; } + return state; } void -CMSWindowsSecondaryScreen::updateKeys() +CMSWindowsSecondaryScreen::updateKeys(KeyState* keys) { - // clear key state - memset(m_keys, 0, sizeof(m_keys)); - memset(m_fakeKeys, 0, sizeof(m_keys)); - // we only care about the modifier key states - m_keys[VK_LSHIFT] = static_cast(GetKeyState(VK_LSHIFT)); - m_keys[VK_RSHIFT] = static_cast(GetKeyState(VK_RSHIFT)); - m_keys[VK_SHIFT] = static_cast(GetKeyState(VK_SHIFT)); - m_keys[VK_LCONTROL] = static_cast(GetKeyState(VK_LCONTROL)); - m_keys[VK_RCONTROL] = static_cast(GetKeyState(VK_RCONTROL)); - m_keys[VK_CONTROL] = static_cast(GetKeyState(VK_CONTROL)); - m_keys[VK_LMENU] = static_cast(GetKeyState(VK_LMENU)); - m_keys[VK_RMENU] = static_cast(GetKeyState(VK_RMENU)); - m_keys[VK_MENU] = static_cast(GetKeyState(VK_MENU)); - m_keys[VK_LWIN] = static_cast(GetKeyState(VK_LWIN)); - m_keys[VK_RWIN] = static_cast(GetKeyState(VK_RWIN)); - m_keys[VK_APPS] = static_cast(GetKeyState(VK_APPS)); - m_keys[VK_CAPITAL] = static_cast(GetKeyState(VK_CAPITAL)); - m_keys[VK_NUMLOCK] = static_cast(GetKeyState(VK_NUMLOCK)); - m_keys[VK_SCROLL] = static_cast(GetKeyState(VK_SCROLL)); - - // copy over lock states to m_fakeKeys - m_fakeKeys[VK_CAPITAL] = static_cast(m_keys[VK_CAPITAL] & 0x01); - m_fakeKeys[VK_NUMLOCK] = static_cast(m_keys[VK_NUMLOCK] & 0x01); - m_fakeKeys[VK_SCROLL] = static_cast(m_keys[VK_SCROLL] & 0x01); - - // update active modifier mask - m_mask = 0; - if ((m_keys[VK_LSHIFT] & 0x80) != 0 || (m_keys[VK_RSHIFT] & 0x80) != 0) { - m_mask |= KeyModifierShift; - } - if ((m_keys[VK_LCONTROL] & 0x80) != 0 || - (m_keys[VK_RCONTROL] & 0x80) != 0) { - m_mask |= KeyModifierControl; - } - if ((m_keys[VK_LMENU] & 0x80) != 0 || (m_keys[VK_RMENU] & 0x80) != 0) { - m_mask |= KeyModifierAlt; - } - // note -- no keys for KeyModifierMeta - if ((m_keys[VK_LWIN] & 0x80) != 0 || (m_keys[VK_RWIN] & 0x80) != 0) { - m_mask |= KeyModifierSuper; - } - if ((m_keys[VK_CAPITAL] & 0x01) != 0) { - m_mask |= KeyModifierCapsLock; - } - if ((m_keys[VK_NUMLOCK] & 0x01) != 0) { - m_mask |= KeyModifierNumLock; - } - if ((m_keys[VK_SCROLL] & 0x01) != 0) { - m_mask |= KeyModifierScrollLock; - } - // note -- do not save KeyModifierModeSwitch in m_mask - LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask)); -} - -void -CMSWindowsSecondaryScreen::setToggleState(KeyModifierMask mask) -{ - // toggle modifiers that don't match the desired state - if ((mask & KeyModifierCapsLock) != (m_mask & KeyModifierCapsLock)) { - toggleKey(VK_CAPITAL, KeyModifierCapsLock); - } - if ((mask & KeyModifierNumLock) != (m_mask & KeyModifierNumLock)) { - toggleKey(VK_NUMLOCK | 0x100, KeyModifierNumLock); - } - if ((mask & KeyModifierScrollLock) != (m_mask & KeyModifierScrollLock)) { - toggleKey(VK_SCROLL, KeyModifierScrollLock); - } + 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::getToggleState() const +CMSWindowsSecondaryScreen::getModifiers() const { - return (m_mask & (KeyModifierCapsLock | - KeyModifierNumLock | - KeyModifierScrollLock)); + // 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 @@ -814,8 +531,10 @@ CMSWindowsSecondaryScreen::mapButton(ButtonID button, } KeyModifierMask -CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, - KeyID id, KeyModifierMask mask, EKeyAction action) const +CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, + SysKeyID& virtualKey, KeyID id, + KeyModifierMask currentMask, + KeyModifierMask desiredMask, EKeyAction action) const { virtualKey = 0; @@ -832,7 +551,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, } if (virtualKey == 0) { LOG((CLOG_DEBUG2 "unknown special key")); - return m_mask; + return currentMask; } } @@ -851,14 +570,14 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, // active window or fullscreen? BYTE scan = 0; - if ((mask & KeyModifierAlt) == 0) { + if ((desiredMask & KeyModifierAlt) == 0) { scan = 1; } // send event keybd_event(static_cast(virtualKey & 0xff), scan, flags, 0); } - return m_mask; + return currentMask; } // handle other special keys @@ -866,11 +585,11 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, // 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 = (m_mask & + KeyModifierMask outMask = (currentMask & (KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock)); - outMask |= (mask & + outMask |= (desiredMask & (KeyModifierShift | KeyModifierControl | KeyModifierAlt | @@ -889,7 +608,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, if (virtualKey2 >= VK_NUMPAD0 && virtualKey2 <= VK_DIVIDE) { // set required shift state based on current numlock state if ((outMask & KeyModifierNumLock) == 0) { - if ((m_mask & KeyModifierNumLock) == 0) { + if ((currentMask & KeyModifierNumLock) == 0) { LOG((CLOG_DEBUG2 "turn on num lock for keypad key")); outMask |= KeyModifierNumLock; } @@ -902,12 +621,13 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, // check for left tab. that requires the shift key. if (id == kKeyLeftTab) { - mask |= KeyModifierShift; + // 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, m_mask, outMask, action); + return mapToKeystrokes(keys, virtualKey, currentMask, outMask, action); } // determine the thread that'll receive this event @@ -951,9 +671,10 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, NULL, &error); if (nChars == 0 || error) { LOG((CLOG_DEBUG2 "KeyID 0x%08x not in code page", id)); - return m_mask; + return currentMask; } - virtualKey = mapCharacter(keys, multiByte[0], hkl, m_mask, mask, action); + 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) { @@ -971,11 +692,11 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, // character for real so send a space key afterwards. LOG((CLOG_DEBUG2 "character mapped to dead key")); Keystroke keystroke; - keystroke.m_virtualKey = VK_SPACE; - keystroke.m_press = true; - keystroke.m_repeat = false; + keystroke.m_sysKeyID = VK_SPACE; + keystroke.m_press = true; + keystroke.m_repeat = false; keys.push_back(keystroke); - keystroke.m_press = false; + keystroke.m_press = false; keys.push_back(keystroke); // ignore the release of this key since we already @@ -983,7 +704,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, virtualKey = 0; } } - return m_mask; + return currentMask; } nChars = MultiByteToWideChar(codePage, MB_COMPOSITE | MB_ERR_INVALID_CHARS, @@ -991,7 +712,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, unicode, 2); if (nChars == 0) { LOG((CLOG_DEBUG2 "KeyID 0x%08x mb->wc mapping failed", id)); - return m_mask; + return currentMask; } nChars = WideCharToMultiByte(codePage, 0, @@ -1000,7 +721,7 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, NULL, &error); if (nChars == 0 || error) { LOG((CLOG_DEBUG2 "KeyID 0x%08x wc->mb mapping failed", id)); - return m_mask; + return currentMask; } // we expect one or two characters in multiByte. if there are two @@ -1008,60 +729,54 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, // 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 m_mask; + 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, m_mask, mask, action); + 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, m_mask, mask, action); + virtualKey = mapCharacter(keys, multiByte[0], + hkl, currentMask, desiredMask, action); // non-special key cannot modify the modifier mask - return m_mask; + return currentMask; } KeyModifierMask -CMSWindowsSecondaryScreen::mapKeyRelease(Keystrokes& keys, - UINT virtualKey) const +CMSWindowsSecondaryScreen::getModifierKeyMask(SysKeyID virtualKey) const { - // add key release - Keystroke keystroke; - keystroke.m_virtualKey = virtualKey; - keystroke.m_press = false; - keystroke.m_repeat = false; - keys.push_back(keystroke); + switch (virtualKey) { + case VK_CAPITAL: + return KeyModifierCapsLock; - // if this is a modifier keycode then update the current modifier mask + 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) { - if (modifier->m_isToggle) { - // toggle keys modify the state on release - return (m_mask ^ modifier->m_mask); - } - else { - // can't reset bit until all keys that set it are released. - // scan those keys to see if any (except virtualKey) are - // pressed. - bool down = false; - if (virtualKey != (modifier->m_virtualKey & 0xff) && - (m_keys[modifier->m_virtualKey & 0xff] & 0x80) != 0) { - down = true; - } - if (modifier->m_virtualKey2 != 0 && - virtualKey != (modifier->m_virtualKey2 & 0xff) && - (m_keys[modifier->m_virtualKey2 & 0xff] & 0x80) != 0) { - down = true; - } - if (!down) { - return (m_mask & ~modifier->m_mask); - } + if (modifier != NULL && !modifier->m_isToggle) { + if (isKeyDown(modifier->m_virtualKey & 0xff) || + isKeyDown(modifier->m_virtualKey2 & 0xff)) { + return true; } } - - return m_mask; + return false; } UINT @@ -1186,9 +901,9 @@ CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, // modifier is a toggle then toggle it on with a // press/release, otherwise activate it with a // press. - keystroke.m_virtualKey = s_modifier[i].m_virtualKey; - keystroke.m_press = true; - keystroke.m_repeat = false; + 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; @@ -1210,33 +925,33 @@ CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, // release. we must check each keycode for the // modifier if not a toggle. if (s_modifier[i].m_isToggle) { - keystroke.m_virtualKey = s_modifier[i].m_virtualKey; - keystroke.m_press = true; - keystroke.m_repeat = false; + keystroke.m_sysKeyID = s_modifier[i].m_virtualKey; + keystroke.m_press = true; + keystroke.m_repeat = false; keys.push_back(keystroke); - keystroke.m_press = false; + keystroke.m_press = false; keys.push_back(keystroke); undo.push_back(keystroke); - keystroke.m_press = true; + keystroke.m_press = true; undo.push_back(keystroke); } else { UINT key = s_modifier[i].m_virtualKey; - if ((m_keys[key & 0xff] & 0x80) != 0) { - keystroke.m_virtualKey = key; - keystroke.m_press = false; - keystroke.m_repeat = false; + if (isKeyDown(key & 0xff)) { + keystroke.m_sysKeyID = key; + keystroke.m_press = false; + keystroke.m_repeat = false; keys.push_back(keystroke); - keystroke.m_press = true; + keystroke.m_press = true; undo.push_back(keystroke); } key = s_modifier[i].m_virtualKey2; - if (key != 0 && (m_keys[key & 0xff] & 0x80) != 0) { - keystroke.m_virtualKey = key; - keystroke.m_press = false; - keystroke.m_repeat = false; + if (isKeyDown(key & 0xff)) { + keystroke.m_sysKeyID = key; + keystroke.m_press = false; + keystroke.m_repeat = false; keys.push_back(keystroke); - keystroke.m_press = true; + keystroke.m_press = true; undo.push_back(keystroke); } } @@ -1246,7 +961,7 @@ CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, } // add the key event - keystroke.m_virtualKey = virtualKey; + keystroke.m_sysKeyID = virtualKey; switch (action) { case kPress: keystroke.m_press = true; @@ -1297,46 +1012,13 @@ CMSWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, return mask; } -void -CMSWindowsSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) -{ - // do nothing if no keys or no repeats - if (count < 1 || keys.empty()) { - return; - } - - // generate key events - for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { - if (k->m_repeat) { - // repeat from here up to but not including the next key - // with m_repeat == false count times. - Keystrokes::const_iterator start = k; - for (; count > 0; --count) { - // send repeating events - for (k = start; k != keys.end() && k->m_repeat; ++k) { - sendKeyEvent(k->m_virtualKey, k->m_press); - } - } - - // note -- k is now on the first non-repeat key after the - // repeat keys, exactly where we'd like to continue from. - } - else { - // send event - sendKeyEvent(k->m_virtualKey, k->m_press); - - // next key - ++k; - } - } -} 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 & ~0x100) { + switch (virtualKey & 0xffu) { case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: @@ -1370,72 +1052,22 @@ CMSWindowsSecondaryScreen::getModifierInfo(UINT virtualKey) const } } -void -CMSWindowsSecondaryScreen::releaseKeys() +CSecondaryScreen::SysKeyID +CMSWindowsSecondaryScreen::getToggleSysKey(KeyID keyID) const { - // release keys that we've synthesized a press for and only those - // keys. we don't want to synthesize a release on a key the user - // is still physically pressing. + switch (keyID) { + case kKeyNumLock: + return VK_NUMLOCK | 0x100; - CLock lock(&m_mutex); + case kKeyCapsLock: + return VK_CAPITAL; - m_screen->syncDesktop(); + case kKeyScrollLock: + return VK_SCROLL; - // release left/right modifier keys first. if the platform doesn't - // support them then they won't be set and the non-side-distinuishing - // key will retain its state. if the platform does support them then - // the non-side-distinguishing will be reset. - if ((m_fakeKeys[VK_LSHIFT] & 0x80) != 0) { - sendKeyEvent(VK_LSHIFT, false); - m_fakeKeys[VK_SHIFT] = 0; - m_fakeKeys[VK_LSHIFT] = 0; + default: + return 0; } - if ((m_fakeKeys[VK_RSHIFT] & 0x80) != 0) { - sendKeyEvent(VK_RSHIFT, false); - m_fakeKeys[VK_SHIFT] = 0; - m_fakeKeys[VK_RSHIFT] = 0; - } - if ((m_fakeKeys[VK_LCONTROL] & 0x80) != 0) { - sendKeyEvent(VK_LCONTROL, false); - m_fakeKeys[VK_CONTROL] = 0; - m_fakeKeys[VK_LCONTROL] = 0; - } - if ((m_fakeKeys[VK_RCONTROL] & 0x80) != 0) { - sendKeyEvent(VK_RCONTROL, false); - m_fakeKeys[VK_CONTROL] = 0; - m_fakeKeys[VK_RCONTROL] = 0; - } - if ((m_fakeKeys[VK_LMENU] & 0x80) != 0) { - sendKeyEvent(VK_LMENU, false); - m_fakeKeys[VK_MENU] = 0; - m_fakeKeys[VK_LMENU] = 0; - } - if ((m_fakeKeys[VK_RMENU] & 0x80) != 0) { - sendKeyEvent(VK_RMENU, false); - m_fakeKeys[VK_MENU] = 0; - m_fakeKeys[VK_RMENU] = 0; - } - - // now check all the other keys - for (UInt32 i = 0; i < sizeof(m_fakeKeys) / sizeof(m_fakeKeys[0]); ++i) { - if ((m_fakeKeys[i] & 0x80) != 0) { - sendKeyEvent(i, false); - m_fakeKeys[i] = 0; - } - } -} - -void -CMSWindowsSecondaryScreen::toggleKey(UINT virtualKey, KeyModifierMask mask) -{ - // send key events to simulate a press and release - sendKeyEvent(virtualKey, true); - sendKeyEvent(virtualKey, false); - - // toggle shadow state - m_mask ^= mask; - m_keys[virtualKey & 0xff] ^= 0x01; - m_fakeKeys[virtualKey & 0xff] ^= 0x01; } UINT @@ -1499,7 +1131,7 @@ CMSWindowsSecondaryScreen::isExtendedKey(UINT virtualKey) const } // check known virtual keys - switch (virtualKey & 0xff) { + switch (virtualKey & 0xffu) { case VK_NUMLOCK: case VK_RCONTROL: case VK_RMENU: @@ -1514,7 +1146,7 @@ CMSWindowsSecondaryScreen::isExtendedKey(UINT virtualKey) const } void -CMSWindowsSecondaryScreen::sendKeyEvent(UINT virtualKey, bool press) +CMSWindowsSecondaryScreen::fakeKeyEvent(UINT virtualKey, bool press) const { DWORD flags = 0; if (isExtendedKey(virtualKey)) { @@ -1526,7 +1158,104 @@ CMSWindowsSecondaryScreen::sendKeyEvent(UINT virtualKey, bool press) const UINT code = virtualKeyToScanCode(virtualKey); keybd_event(static_cast(virtualKey & 0xff), static_cast(code), flags, 0); - LOG((CLOG_DEBUG1 "send key %d, 0x%04x, %s%s", virtualKey & 0xff, code, ((flags & KEYEVENTF_KEYUP) ? "release" : "press"), ((flags & KEYEVENTF_EXTENDEDKEY) ? " extended" : ""))); +} + +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 @@ -1554,10 +1283,14 @@ CMSWindowsSecondaryScreen::getCodePageFromLangID(LANGID langid) const return codePage; } -void -CMSWindowsSecondaryScreen::synthesizeCtrlAltDel() +bool +CMSWindowsSecondaryScreen::synthesizeCtrlAltDel(EKeyAction action) { - LOG((CLOG_DEBUG "emulating ctrl+alt+del")); + // 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 @@ -1581,6 +1314,7 @@ CMSWindowsSecondaryScreen::synthesizeCtrlAltDel() doKeystrokes(keys, 1); } } + return true; } void diff --git a/lib/platform/CMSWindowsSecondaryScreen.h b/lib/platform/CMSWindowsSecondaryScreen.h index ed1bdf98..c402a9ba 100644 --- a/lib/platform/CMSWindowsSecondaryScreen.h +++ b/lib/platform/CMSWindowsSecondaryScreen.h @@ -25,8 +25,6 @@ #include "IMSWindowsScreenEventHandler.h" #include "CMutex.h" #include "CString.h" -#include "stdmap.h" -#include "stdvector.h" class CMSWindowsScreen; class IScreenReceiver; @@ -39,16 +37,6 @@ public: virtual ~CMSWindowsSecondaryScreen(); // CSecondaryScreen overrides - virtual void keyDown(KeyID, KeyModifierMask, KeyButton); - virtual void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton); - virtual void keyUp(KeyID, KeyModifierMask, KeyButton); - virtual void mouseDown(ButtonID); - virtual void mouseUp(ButtonID); - virtual void mouseMove(SInt32 xAbsolute, SInt32 yAbsolute); - virtual void mouseWheel(SInt32 delta); - virtual void resetOptions(); - virtual void setOptions(const COptionsList& options); virtual IScreen* getScreen() const; // IMSWindowsScreenEventHandler overrides @@ -56,7 +44,6 @@ public: virtual bool onPreDispatch(const CEvent* event); virtual bool onEvent(CEvent* event); virtual void onOneShotTimerExpired(UInt32 id); - virtual SInt32 getJumpZoneSize() const; virtual void postCreateWindow(HWND); virtual void preDestroyWindow(HWND); virtual void onAccessibleDesktop(); @@ -71,20 +58,26 @@ protected: virtual void destroyWindow(); virtual void showWindow(SInt32 x, SInt32 y); virtual void hideWindow(); - virtual void warpCursor(SInt32 x, SInt32 y); - virtual void updateKeys(); - virtual void releaseKeys(); - virtual void setToggleState(KeyModifierMask); - virtual KeyModifierMask getToggleState() const; + virtual void updateKeys(KeyState* sysKeyStates); + virtual KeyModifierMask getModifiers() const; + + virtual SysKeyID getUnhanded(SysKeyID) const; + virtual SysKeyID getOtherHanded(SysKeyID) const; + virtual bool isAutoRepeating(SysKeyID) const; + virtual KeyModifierMask getModifierKeyMask(SysKeyID) const; + virtual bool isModifierActive(SysKeyID) const; + virtual SysKeyID getToggleSysKey(KeyID keyID) const; + virtual bool synthesizeCtrlAltDel(EKeyAction); + virtual void sync() const; + virtual KeyModifierMask + mapKey(Keystrokes&, SysKeyID& sysKeyID, KeyID, + KeyModifierMask, KeyModifierMask, EKeyAction) const; + virtual void fakeKeyEvent(SysKeyID, bool press) const; + virtual void fakeMouseButton(ButtonID, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseWheel(SInt32 delta) const; private: - enum EKeyAction { kPress, kRelease, kRepeat }; - class Keystroke { - public: - UINT m_virtualKey; - bool m_press; - bool m_repeat; - }; class CModifierInfo { public: KeyModifierMask m_mask; @@ -92,8 +85,6 @@ private: UINT m_virtualKey2; bool m_isToggle; }; - typedef std::vector Keystrokes; - typedef std::map ServerKeyMap; // open/close desktop (for windows 95/98/me) bool openDesktop(); @@ -108,9 +99,6 @@ private: // key and button queries and operations DWORD mapButton(ButtonID button, bool press, DWORD* data) const; - KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID, - KeyModifierMask, EKeyAction) const; - KeyModifierMask mapKeyRelease(Keystrokes& keys, UINT virtualKey) const; UINT mapCharacter(Keystrokes& keys, char c, HKL hkl, KeyModifierMask currentMask, @@ -121,19 +109,14 @@ private: KeyModifierMask currentMask, KeyModifierMask desiredMask, EKeyAction action) const; - void doKeystrokes(const Keystrokes&, SInt32 count); const CModifierInfo* getModifierInfo(UINT virtualKey) const; - void toggleKey(UINT virtualKey, KeyModifierMask mask); + KeyState getKeyState(UINT virtualKey) const; UINT virtualKeyToScanCode(UINT& virtualKey) const; bool isExtendedKey(UINT virtualKey) const; - void sendKeyEvent(UINT virtualKey, bool press); UINT getCodePageFromLangID(LANGID) const; - // generate a fake ctrl+alt+del - void synthesizeCtrlAltDel(); - // thread that generates fake ctrl+alt+del static void ctrlAltDelThread(void*); @@ -147,18 +130,6 @@ private: // our window HWND m_window; - // virtual key states as set by us or the user - BYTE m_keys[256]; - - // virtual key states as set by us - BYTE m_fakeKeys[256]; - - // current active modifiers - KeyModifierMask m_mask; - - // map server key buttons to local virtual keys - ServerKeyMap m_serverKeyMap; - // modifier table static const CModifierInfo s_modifier[]; }; diff --git a/lib/platform/CXWindowsSecondaryScreen.cpp b/lib/platform/CXWindowsSecondaryScreen.cpp index b69369e9..11096b3a 100644 --- a/lib/platform/CXWindowsSecondaryScreen.cpp +++ b/lib/platform/CXWindowsSecondaryScreen.cpp @@ -57,46 +57,6 @@ # endif #endif -// -// utility functions -// - -inline -static -unsigned int getBits(unsigned int src, unsigned int mask) -{ - return src & mask; -} - -inline -static -unsigned int setBits(unsigned int src, unsigned int mask) -{ - return src | mask; -} - -inline -static -unsigned int clearBits(unsigned int src, unsigned int mask) -{ - return src & ~mask; -} - -inline -static -unsigned int flipBits(unsigned int src, unsigned int mask) -{ - return src ^ mask; -} - -inline -static -unsigned int assignBits(unsigned int src, - unsigned int mask, unsigned int value) -{ - return setBits(clearBits(src, mask), clearBits(value, ~mask)); -} - // // CXWindowsSecondaryScreen @@ -122,220 +82,39 @@ CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen() delete m_screen; } -void -CXWindowsSecondaryScreen::keyDown(KeyID key, - KeyModifierMask mask, KeyButton button) +bool +CXWindowsSecondaryScreen::isAutoRepeating(SysKeyID sysKeyID) const { - // check for ctrl+alt+del emulation - if (key == kKeyDelete && - (mask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - LOG((CLOG_DEBUG "ctrl+alt+del emulation")); - // just pass the key through - } - - // get the sequence of keys to simulate key press and the final - // modifier state. - Keystrokes keys; - KeyCode keycode; - m_mask = mapKey(keys, keycode, key, mask, kPress); - if (keys.empty()) { - // do nothing if there are no associated keys (i.e. lookup failed) - return; - } - - // generate key events - doKeystrokes(keys, 1); - - // do not record button down if button is 0 (invalid) - if (button != 0) { - // note that key is now down - m_serverKeyMap[button] = keycode; - m_keys[keycode] = true; - m_fakeKeys[keycode] = true; - } + char bit = static_cast(1 << (sysKeyID & 7)); + return ((m_keyControl.auto_repeats[sysKeyID >> 3] & bit) != 0); } void -CXWindowsSecondaryScreen::keyRepeat(KeyID key, - KeyModifierMask mask, SInt32 count, KeyButton button) +CXWindowsSecondaryScreen::flush() { - // if we haven't seen this button go down then ignore it - ServerKeyMap::iterator index = m_serverKeyMap.find(button); - if (index == m_serverKeyMap.end()) { - return; - } - - // get the sequence of keys to simulate key repeat and the final - // modifier state. - Keystrokes keys; - KeyCode keycode; - m_mask = mapKey(keys, keycode, key, mask, kRepeat); - if (keys.empty()) { - return; - } - - // if this keycode shouldn't auto-repeat then ignore - if ((m_keyControl.auto_repeats[keycode >> 3] & (1 << (keycode & 7))) == 0) { - return; - } - - // if the keycode for the auto-repeat is not the same as for the - // initial press then mark the initial key as released and the new - // key as pressed. this can happen when we auto-repeat after a - // dead key. for example, a dead accent followed by 'a' will - // generate an 'a with accent' followed by a repeating 'a'. the - // keycodes for the two keysyms might be different. - if (keycode != index->second) { - // replace key up with previous keycode but leave key down - // alone so it uses the new keycode and store that keycode - // in the server key map. the key up is the first keystroke - // with the keycode returned by mapKey(). - for (Keystrokes::iterator index2 = keys.begin(); - index2 != keys.end(); ++index2) { - if (index2->m_keycode == keycode) { - index2->m_keycode = index->second; - break; - } - } - - // note that old key is now up - m_keys[index->second] = false; - m_fakeKeys[index->second] = false; - - // map server key to new key - index->second = keycode; - - // note that new key is now down - m_keys[index->second] = true; - m_fakeKeys[index->second] = true; - } - - // generate key events - doKeystrokes(keys, count); -} - -void -CXWindowsSecondaryScreen::keyUp(KeyID key, - KeyModifierMask mask, KeyButton button) -{ - // if we haven't seen this button go down then ignore it - ServerKeyMap::iterator index = m_serverKeyMap.find(button); - if (index == m_serverKeyMap.end()) { - return; - } - KeyCode keycode = index->second; - - // check for ctrl+alt+del emulation - if (key == kKeyDelete && - (mask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - LOG((CLOG_DEBUG "ctrl+alt+del emulation")); - // just pass the key through - } - - // get the sequence of keys to simulate key release and the final - // modifier state. - Keystrokes keys; - if (!((key == kKeyCapsLock && m_capsLockHalfDuplex) || - (key == kKeyNumLock && m_numLockHalfDuplex))) { - m_mask = mapKeyRelease(keys, keycode); - } - - // generate key events - doKeystrokes(keys, 1); - - // note that key is now up - m_serverKeyMap.erase(index); - m_keys[keycode] = false; - m_fakeKeys[keycode] = false; -} - -void -CXWindowsSecondaryScreen::flush(Display* display) const -{ - XFlush(display); -} - -void -CXWindowsSecondaryScreen::mouseDown(ButtonID button) -{ - const unsigned int xButton = mapButton(button); - if (xButton != 0) { - CDisplayLock display(m_screen); - XTestFakeButtonEvent(display, xButton, True, CurrentTime); - flush(display); - } -} - -void -CXWindowsSecondaryScreen::mouseUp(ButtonID button) -{ - const unsigned int xButton = mapButton(button); - if (xButton != 0) { - CDisplayLock display(m_screen); - XTestFakeButtonEvent(display, xButton, False, CurrentTime); - flush(display); - } -} - -void -CXWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) -{ - warpCursor(x, y); -} - -void -CXWindowsSecondaryScreen::mouseWheel(SInt32 delta) -{ - // choose button depending on rotation direction - const unsigned int xButton = mapButton(static_cast( - (delta >= 0) ? -1 : -2)); - if (xButton == 0) { - return; - } - - // now use absolute value of delta - if (delta < 0) { - delta = -delta; - } - - // send as many clicks as necessary CDisplayLock display(m_screen); - for (; delta >= 120; delta -= 120) { - XTestFakeButtonEvent(display, xButton, True, CurrentTime); - XTestFakeButtonEvent(display, xButton, False, CurrentTime); + if (display != NULL) { + XFlush(display); } - flush(display); } void CXWindowsSecondaryScreen::resetOptions() { - m_numLockHalfDuplex = false; - m_capsLockHalfDuplex = false; - m_xtestIsXineramaUnaware = true; CSecondaryScreen::resetOptions(); + m_xtestIsXineramaUnaware = true; } void CXWindowsSecondaryScreen::setOptions(const COptionsList& options) { + CSecondaryScreen::setOptions(options); for (UInt32 i = 0, n = options.size(); i < n; i += 2) { - if (options[i] == kOptionHalfDuplexCapsLock) { - m_capsLockHalfDuplex = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off")); - } - else if (options[i] == kOptionHalfDuplexNumLock) { - m_numLockHalfDuplex = (options[i + 1] != 0); - LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off")); - } - else if (options[i] == kOptionXTestXineramaUnaware) { + if (options[i] == kOptionXTestXineramaUnaware) { m_xtestIsXineramaUnaware = (options[i + 1] != 0); LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false")); } } - CSecondaryScreen::setOptions(options); } IScreen* @@ -384,12 +163,6 @@ CXWindowsSecondaryScreen::onOneShotTimerExpired(UInt32) // ignore } -SInt32 -CXWindowsSecondaryScreen::getJumpZoneSize() const -{ - return 0; -} - void CXWindowsSecondaryScreen::onPreMainLoop() { @@ -523,16 +296,13 @@ void CXWindowsSecondaryScreen::destroyWindow() { { + // release keys that are still pressed + releaseKeys(); + CDisplayLock display(m_screen); if (display != NULL) { - // release keys that are still pressed - doReleaseKeys(display); - // no longer impervious to server grabs XTestGrabControl(display, False); - - // update - flush(display); } } @@ -564,7 +334,7 @@ CXWindowsSecondaryScreen::showWindow(SInt32 x, SInt32 y) // now warp the mouse. we warp after showing the window so we're // guaranteed to get the mouse leave event and to prevent the // keyboard focus from changing under point-to-focus policies. - warpCursor(x, y); + fakeMouseMove(x, y); } void @@ -576,56 +346,6 @@ CXWindowsSecondaryScreen::hideWindow() XUnmapWindow(display, m_window); } -void -CXWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) -{ - CDisplayLock display(m_screen); - Display* pDisplay = display; - - if (m_xinerama && m_xtestIsXineramaUnaware) { - XWarpPointer(display, None, m_screen->getRoot(), 0, 0, 0, 0, x, y); - } - else { - XTestFakeMotionEvent(display, DefaultScreen(pDisplay), - x, y, CurrentTime); - } - flush(display); -} - -void -CXWindowsSecondaryScreen::setToggleState(KeyModifierMask mask) -{ - CDisplayLock display(m_screen); - - // toggle modifiers that don't match the desired state - ModifierMask xMask = maskToX(mask); - if ((xMask & m_capsLockMask) != (m_mask & m_capsLockMask)) { - toggleKey(display, m_capsLockKeysym, m_capsLockMask); - } - if ((xMask & m_numLockMask) != (m_mask & m_numLockMask)) { - toggleKey(display, m_numLockKeysym, m_numLockMask); - } - if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) { - toggleKey(display, m_scrollLockKeysym, m_scrollLockMask); - } -} - -KeyModifierMask -CXWindowsSecondaryScreen::getToggleState() const -{ - KeyModifierMask mask = 0; - if ((m_mask & m_capsLockMask) != 0) { - mask |= KeyModifierCapsLock; - } - if ((m_mask & m_numLockMask) != 0) { - mask |= KeyModifierNumLock; - } - if ((m_mask & m_scrollLockMask) != 0) { - mask |= KeyModifierScrollLock; - } - return mask; -} - unsigned int CXWindowsSecondaryScreen::mapButton(ButtonID id) const { @@ -655,9 +375,11 @@ CXWindowsSecondaryScreen::mapButton(ButtonID id) const return static_cast(m_buttons[id - 1]); } -CXWindowsSecondaryScreen::ModifierMask -CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, - KeyID id, KeyModifierMask mask, EKeyAction action) const +KeyModifierMask +CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, + SysKeyID& keycode, KeyID id, + KeyModifierMask currentMask, + KeyModifierMask desiredMask, EKeyAction action) const { // note -- must have display locked on entry @@ -666,27 +388,23 @@ CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, // generate the right keysym we need to set the modifier key // states appropriately. // - // the mask passed by the caller is the desired mask. however, - // there may not be a keycode mapping to generate the desired - // keysym with that mask. we override the bits in the mask - // that cannot be accomodated. + // desiredMask is the mask desired by the caller. however, there + // may not be a keycode mapping to generate the desired keysym + // with that mask. we override the bits in the mask that cannot + // be accomodated. // ignore releases and repeats for half-duplex keys - const bool isHalfDuplex = ((id == kKeyCapsLock && m_capsLockHalfDuplex) || - (id == kKeyNumLock && m_numLockHalfDuplex)); + const bool isHalfDuplex = isKeyHalfDuplex(id); if (isHalfDuplex && action != kPress) { - return m_mask; + return currentMask; } - // requested notes the modifiers requested by the server. - ModifierMask requested = maskToX(mask); - // convert KeyID to a KeySym - KeySym keysym = keyIDToKeySym(id, requested); + KeySym keysym = keyIDToKeySym(id, desiredMask); if (keysym == NoSymbol) { // unknown key LOG((CLOG_DEBUG2 "no keysym for id 0x%08x", id)); - return m_mask; + return currentMask; } // get the mapping for this keysym @@ -712,28 +430,29 @@ CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, // and that modifier is already in the desired state then // ignore the request since there's nothing to do. never // ignore a toggle modifier on press or release, though. - const KeyMapping& keyMapping = keyIndex->second; - const ModifierMask modifierBit = keyMapping.m_modifierMask; + const KeyMapping& keyMapping = keyIndex->second; + const KeyModifierMask modifierBit = keyMapping.m_modifierMask; if (modifierBit != 0) { if (action == kRepeat) { LOG((CLOG_DEBUG2 "ignore repeating modifier")); - return m_mask; + return currentMask; } - if (getBits(m_toggleModifierMask, modifierBit) == 0) { - if ((action == kPress && (m_mask & modifierBit) != 0) || - (action == kRelease && (m_mask & modifierBit) == 0)) { - LOG((CLOG_DEBUG2 "modifier in proper state: 0x%04x", m_mask)); - return m_mask; + if ((m_toggleModifierMask & modifierBit) == 0) { + if ((action == kPress && (currentMask & modifierBit) != 0) || + (action == kRelease && (currentMask & modifierBit) == 0)) { + LOG((CLOG_DEBUG2 "modifier in proper state: 0x%04x", currentMask)); + return currentMask; } } } // create the keystrokes for this keysym - ModifierMask mask; - if (!mapToKeystrokes(keys, keycode, mask, keyIndex, m_mask, action)) { + KeyModifierMask mask; + if (!mapToKeystrokes(keys, keycode, mask, + keyIndex, currentMask, action, isHalfDuplex)) { // failed to generate keystrokes keys.clear(); - return m_mask; + return currentMask; } else { // success @@ -754,7 +473,7 @@ CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, // and the keycode from the last keysym (which should be the // only non-dead key). the dead keys are not sensitive to // anything but shift and mode switch. - ModifierMask mask; + KeyModifierMask mask; for (KeySyms::const_iterator i = decomposition.begin(); i != decomposition.end();) { // increment the iterator @@ -768,15 +487,15 @@ CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, // missing a required keysym LOG((CLOG_DEBUG2 "no keycode for decomposed keysym 0x%08x", keysym)); keys.clear(); - return m_mask; + return currentMask; } // the keysym is mapped to some keycode if (!mapToKeystrokes(keys, keycode, mask, - keyIndex, m_mask, action)) { + keyIndex, currentMask, action, isHalfDuplex)) { // failed to generate keystrokes keys.clear(); - return m_mask; + return currentMask; } // on to the next keysym @@ -787,50 +506,41 @@ CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, } LOG((CLOG_DEBUG2 "no keycode for keysym")); - return m_mask; + return currentMask; } -CXWindowsSecondaryScreen::ModifierMask -CXWindowsSecondaryScreen::mapKeyRelease(Keystrokes& keys, KeyCode keycode) const +KeyModifierMask +CXWindowsSecondaryScreen::getModifierKeyMask(SysKeyID keycode) const { - // add key release - Keystroke keystroke; - keystroke.m_keycode = keycode; - keystroke.m_press = False; - keystroke.m_repeat = false; - keys.push_back(keystroke); - - // if this is a modifier keycode then update the current modifier mask KeyCodeToModifierMap::const_iterator i = m_keycodeToModifier.find(keycode); - if (i != m_keycodeToModifier.end()) { - ModifierMask bit = (1 << i->second); - if (getBits(m_toggleModifierMask, bit) != 0) { - // toggle keys modify the state on release - return flipBits(m_mask, bit); - } - else { - // can't reset bit until all keys that set it are released. - // scan those keys to see if any (except keycode) are pressed. - KeyCodes::const_iterator j; - const KeyCodes& keycodes = m_modifierKeycodes[i->second]; - for (j = keycodes.begin(); j != keycodes.end(); ++j) { - KeyCode modKeycode = *j; - if (modKeycode != keycode && m_keys[modKeycode]) { - break; - } - } - if (j == keycodes.end()) { - return clearBits(m_mask, bit); + if (i == m_keycodeToModifier.end()) { + return 0; + } + return m_modifierIndexToMask[i->second]; +} + +bool +CXWindowsSecondaryScreen::isModifierActive(SysKeyID keycode) const +{ + // check if any keycode for this modifier is down. return false + // for toggle modifiers. + KeyCodeToModifierMap::const_iterator i = m_keycodeToModifier.find(keycode); + if (i != m_keycodeToModifier.end() && + (m_modifierIndexToMask[i->second] & m_toggleModifierMask) != 0) { + const KeyCodes& keycodes = m_modifierKeycodes[i->second]; + for (KeyCodes::const_iterator j = keycodes.begin(); + j != keycodes.end(); ++j) { + if (isKeyDown(*j)) { + return true; } } } - - return m_mask; + return false; } unsigned int CXWindowsSecondaryScreen::findBestKeyIndex(KeySymIndex keyIndex, - ModifierMask /*currentMask*/) const + KeyModifierMask /*currentMask*/) const { // there are up to 4 keycodes per keysym to choose from. the // best choice is the one that requires the fewest adjustments @@ -856,7 +566,7 @@ CXWindowsSecondaryScreen::findBestKeyIndex(KeySymIndex keyIndex, bool CXWindowsSecondaryScreen::isShiftInverted(KeySymIndex keyIndex, - ModifierMask currentMask) const + KeyModifierMask currentMask) const { // each keycode has up to 4 keysym associated with it, one each for: // no modifiers, shift, mode switch, and shift and mode switch. if @@ -867,7 +577,7 @@ CXWindowsSecondaryScreen::isShiftInverted(KeySymIndex keyIndex, // method returns true iff the sense of shift should be inverted // for this key given a modifier state. if (keyIndex->second.m_numLockSensitive) { - if (getBits(currentMask, m_numLockMask) != 0) { + if ((currentMask & KeyModifierNumLock) != 0) { return true; } } @@ -875,7 +585,7 @@ CXWindowsSecondaryScreen::isShiftInverted(KeySymIndex keyIndex, // if a keysym is num lock sensitive it is never caps lock // sensitive, thus the else here. else if (keyIndex->second.m_capsLockSensitive) { - if (getBits(currentMask, m_capsLockMask) != 0) { + if ((currentMask & KeyModifierCapsLock) != 0) { return true; } } @@ -883,28 +593,14 @@ CXWindowsSecondaryScreen::isShiftInverted(KeySymIndex keyIndex, return false; } -CXWindowsSecondaryScreen::ModifierMask -CXWindowsSecondaryScreen::getModifierMask(KeySym keysym) const -{ - // find the keysym mapping. if it exists and there's a keycode - // for index 0 (the index we use for modifiers) then return the - // modifierMask, which might be 0. otherwise return 0. - KeySymIndex keyIndex = m_keysymMap.find(keysym); - if (keyIndex != m_keysymMap.end() && keyIndex->second.m_keycode[0] != 0) { - return keyIndex->second.m_modifierMask; - } - else { - return 0; - } -} - bool CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, - KeyCode& keycode, - ModifierMask& finalMask, + SysKeyID& keycode, + KeyModifierMask& finalMask, KeySymIndex keyIndex, - ModifierMask currentMask, - EKeyAction action) const + KeyModifierMask currentMask, + EKeyAction action, + bool isHalfDuplex) const { // keyIndex must be valid assert(keyIndex != m_keysymMap.end()); @@ -948,23 +644,23 @@ CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, // is not sensitive to shift then don't adjust it, otherwise // something like shift+home would become just home. similiarly // for mode switch. - ModifierMask desiredMask = currentMask; - if (keyIndex->second.m_modifierMask != m_shiftMask) { + KeyModifierMask desiredMask = currentMask; + if (keyIndex->second.m_modifierMask != KeyModifierShift) { if (keyIndex->second.m_shiftSensitive[bestIndex]) { if ((bestIndex & 1) != 0) { - desiredMask = setBits(desiredMask, m_shiftMask); + desiredMask |= KeyModifierShift; } else { - desiredMask = clearBits(desiredMask, m_shiftMask); + desiredMask &= ~KeyModifierShift; } } - if (keyIndex->second.m_modifierMask != m_modeSwitchMask) { + if (keyIndex->second.m_modifierMask != KeyModifierModeSwitch) { if (keyIndex->second.m_modeSwitchSensitive[bestIndex]) { if ((bestIndex & 2) != 0) { - desiredMask = setBits(desiredMask, m_modeSwitchMask); + desiredMask |= KeyModifierModeSwitch; } else { - desiredMask = clearBits(desiredMask, m_modeSwitchMask); + desiredMask &= ~KeyModifierModeSwitch; } } } @@ -972,41 +668,38 @@ CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, // adjust the modifiers to match the desired modifiers Keystrokes undo; - ModifierMask tmpMask = currentMask; + KeyModifierMask tmpMask = currentMask; if (!adjustModifiers(keys, undo, tmpMask, desiredMask)) { LOG((CLOG_DEBUG2 "failed to adjust modifiers")); return false; } // note if the press of a half-duplex key should be treated as a release - const bool isHalfDuplex = - ((keysym == m_capsLockKeysym && m_capsLockHalfDuplex) || - (keysym == m_numLockKeysym && m_numLockHalfDuplex)); - if (isHalfDuplex && getBits(currentMask, mapping.m_modifierMask) != 0) { + if (isHalfDuplex && (currentMask & mapping.m_modifierMask) != 0) { action = kRelease; } // add the key event Keystroke keystroke; - keystroke.m_keycode = keycode; + keystroke.m_sysKeyID = keycode; switch (action) { case kPress: - keystroke.m_press = True; + keystroke.m_press = true; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRelease: - keystroke.m_press = False; + keystroke.m_press = false; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRepeat: - keystroke.m_press = False; + keystroke.m_press = false; keystroke.m_repeat = true; keys.push_back(keystroke); - keystroke.m_press = True; + keystroke.m_press = true; keys.push_back(keystroke); break; } @@ -1027,13 +720,13 @@ CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, // toggle keys modify the state on release. other keys set the // bit on press and clear the bit on release. if half-duplex // then toggle each time we get here. - if (getBits(m_toggleModifierMask, mapping.m_modifierMask) != 0) { + if ((m_toggleModifierMask & mapping.m_modifierMask) != 0) { if (isHalfDuplex) { - finalMask = flipBits(finalMask, mapping.m_modifierMask); + finalMask ^= mapping.m_modifierMask; } } else if (action == kPress) { - finalMask = setBits(finalMask, mapping.m_modifierMask); + finalMask |= mapping.m_modifierMask; } } @@ -1043,14 +736,14 @@ CXWindowsSecondaryScreen::mapToKeystrokes(Keystrokes& keys, bool CXWindowsSecondaryScreen::adjustModifiers(Keystrokes& keys, Keystrokes& undo, - ModifierMask& inOutMask, - ModifierMask desiredMask) const + KeyModifierMask& inOutMask, + KeyModifierMask desiredMask) const { // get mode switch set correctly. do this before shift because // mode switch may be sensitive to the shift modifier and will // set/reset it as necessary. - const bool wantModeSwitch = ((desiredMask & m_modeSwitchMask) != 0); - const bool haveModeSwitch = ((inOutMask & m_modeSwitchMask) != 0); + const bool wantModeSwitch = ((desiredMask & KeyModifierModeSwitch) != 0); + const bool haveModeSwitch = ((inOutMask & KeyModifierModeSwitch) != 0); if (wantModeSwitch != haveModeSwitch) { LOG((CLOG_DEBUG2 "fix mode switch")); @@ -1059,14 +752,14 @@ CXWindowsSecondaryScreen::adjustModifiers(Keystrokes& keys, assert(modeSwitchIndex != m_keysymMap.end()); if (modeSwitchIndex->second.m_shiftSensitive[0]) { const bool wantShift = false; - const bool haveShift = ((inOutMask & m_shiftMask) != 0); + const bool haveShift = ((inOutMask & KeyModifierShift) != 0); if (wantShift != haveShift) { // add shift keystrokes LOG((CLOG_DEBUG2 "fix shift for mode switch")); if (!adjustModifier(keys, undo, m_shiftKeysym, wantShift)) { return false; } - inOutMask ^= m_shiftMask; + inOutMask ^= KeyModifierShift; } } @@ -1074,19 +767,19 @@ CXWindowsSecondaryScreen::adjustModifiers(Keystrokes& keys, if (!adjustModifier(keys, undo, m_modeSwitchKeysym, wantModeSwitch)) { return false; } - inOutMask ^= m_modeSwitchMask; + inOutMask ^= KeyModifierModeSwitch; } // get shift set correctly - const bool wantShift = ((desiredMask & m_shiftMask) != 0); - const bool haveShift = ((inOutMask & m_shiftMask) != 0); + const bool wantShift = ((desiredMask & KeyModifierShift) != 0); + const bool haveShift = ((inOutMask & KeyModifierShift) != 0); if (wantShift != haveShift) { // add shift keystrokes LOG((CLOG_DEBUG2 "fix shift")); if (!adjustModifier(keys, undo, m_shiftKeysym, wantShift)) { return false; } - inOutMask ^= m_shiftMask; + inOutMask ^= KeyModifierShift; } return true; @@ -1121,7 +814,7 @@ CXWindowsSecondaryScreen::adjustModifier(Keystrokes& keys, // initialize keystroke Keystroke keystroke; - keystroke.m_repeat = false; + keystroke.m_repeat = false; // releasing a modifier is quite different from pressing one. // when we release a modifier we have to release every keycode that @@ -1130,10 +823,10 @@ CXWindowsSecondaryScreen::adjustModifier(Keystrokes& keys, // press one of those keycodes. if (desireActive) { // press - keystroke.m_keycode = keyIndex->second.m_keycode[0]; - keystroke.m_press = True; + keystroke.m_sysKeyID = keyIndex->second.m_keycode[0]; + keystroke.m_press = true; keys.push_back(keystroke); - keystroke.m_press = False; + keystroke.m_press = false; undo.push_back(keystroke); } else { @@ -1144,11 +837,11 @@ CXWindowsSecondaryScreen::adjustModifier(Keystrokes& keys, const KeyCodes& keycodes = m_modifierKeycodes[index->second]; for (KeyCodes::const_iterator j = keycodes.begin(); j != keycodes.end(); ++j) { - if (m_keys[*j]) { - keystroke.m_keycode = *j; - keystroke.m_press = False; + if (isKeyDown(*j)) { + keystroke.m_sysKeyID = *j; + keystroke.m_press = false; keys.push_back(keystroke); - keystroke.m_press = True; + keystroke.m_press = true; undo.push_back(keystroke); } } @@ -1159,97 +852,64 @@ CXWindowsSecondaryScreen::adjustModifier(Keystrokes& keys, } void -CXWindowsSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) +CXWindowsSecondaryScreen::fakeKeyEvent(SysKeyID keycode, bool press) const { - // do nothing if no keys or no repeats - if (count < 1 || keys.empty()) { - return; - } - - // lock display CDisplayLock display(m_screen); - - // generate key events - for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { - if (k->m_repeat) { - // repeat from here up to but not including the next key - // with m_repeat == false count times. - Keystrokes::const_iterator start = k; - for (; count > 0; --count) { - // send repeating events - for (k = start; k != keys.end() && k->m_repeat; ++k) { - XTestFakeKeyEvent(display, - k->m_keycode, k->m_press, CurrentTime); - } - } - - // note -- k is now on the first non-repeat key after the - // repeat keys, exactly where we'd like to continue from. - } - else { - // send event - LOG((CLOG_DEBUG2 "keystrokes:")); - LOG((CLOG_DEBUG2 " %d %s", k->m_keycode, k->m_press ? "down" : "up")); - XTestFakeKeyEvent(display, k->m_keycode, k->m_press, CurrentTime); - - // next key - ++k; - } + if (display != NULL) { + XTestFakeKeyEvent(display, keycode, press ? True : False, CurrentTime); } - - // update - flush(display); -} - -CXWindowsSecondaryScreen::ModifierMask -CXWindowsSecondaryScreen::maskToX(KeyModifierMask inMask) const -{ - ModifierMask outMask = 0; - if (inMask & KeyModifierShift) { - outMask |= m_shiftMask; - } - if (inMask & KeyModifierControl) { - outMask |= m_ctrlMask; - } - if (inMask & KeyModifierAlt) { - outMask |= m_altMask; - } - if (inMask & KeyModifierMeta) { - outMask |= m_metaMask; - } - if (inMask & KeyModifierSuper) { - outMask |= m_superMask; - } - if (inMask & KeyModifierModeSwitch) { - outMask |= m_modeSwitchMask; - } - if (inMask & KeyModifierCapsLock) { - outMask |= m_capsLockMask; - } - if (inMask & KeyModifierNumLock) { - outMask |= m_numLockMask; - } - if (inMask & KeyModifierScrollLock) { - outMask |= m_scrollLockMask; - } - return outMask; } void -CXWindowsSecondaryScreen::doReleaseKeys(Display* display) +CXWindowsSecondaryScreen::fakeMouseButton(ButtonID button, bool press) const { - assert(display != NULL); - - // key release for each key that we faked a press for - for (UInt32 i = 0; i < 256; ++i) { - if (m_fakeKeys[i]) { - XTestFakeKeyEvent(display, i, False, CurrentTime); - m_fakeKeys[i] = false; - m_keys[i] = false; + const unsigned int xButton = mapButton(button); + if (xButton != 0) { + CDisplayLock display(m_screen); + if (display != NULL) { + XTestFakeButtonEvent(display, xButton, + press ? True : False, CurrentTime); } } } +void +CXWindowsSecondaryScreen::fakeMouseMove(SInt32 x, SInt32 y) const +{ + CDisplayLock display(m_screen); + if (m_xinerama && m_xtestIsXineramaUnaware) { + XWarpPointer(display, None, m_screen->getRoot(), 0, 0, 0, 0, x, y); + } + else { + Display* pDisplay = display; + XTestFakeMotionEvent(display, DefaultScreen(pDisplay), + x, y, CurrentTime); + } +} + +void +CXWindowsSecondaryScreen::fakeMouseWheel(SInt32 delta) const +{ + // choose button depending on rotation direction + const unsigned int xButton = mapButton(static_cast( + (delta >= 0) ? -1 : -2)); + if (xButton == 0) { + return; + } + + // now use absolute value of delta + if (delta < 0) { + delta = -delta; + } + + // send as many clicks as necessary + CDisplayLock display(m_screen); + for (; delta >= 120; delta -= 120) { + XTestFakeButtonEvent(display, xButton, True, CurrentTime); + XTestFakeButtonEvent(display, xButton, False, CurrentTime); + } +} + void CXWindowsSecondaryScreen::doUpdateKeys(Display* display) { @@ -1281,48 +941,35 @@ CXWindowsSecondaryScreen::doUpdateKeys(Display* display) // clean up delete[] tmpButtons; - // update mappings and current modifiers + // update mappings updateKeysymMap(display); - updateModifiers(display); } void -CXWindowsSecondaryScreen::updateKeys() +CXWindowsSecondaryScreen::updateKeys(KeyState* keys) { CDisplayLock display(m_screen); // ask server which keys are pressed - char keys[32]; - XQueryKeymap(display, keys); + char xkeys[32]; + XQueryKeymap(display, xkeys); // transfer to our state for (UInt32 i = 0, j = 0; i < 32; j += 8, ++i) { - m_keys[j + 0] = ((keys[i] & 0x01) != 0); - m_keys[j + 1] = ((keys[i] & 0x02) != 0); - m_keys[j + 2] = ((keys[i] & 0x04) != 0); - m_keys[j + 3] = ((keys[i] & 0x08) != 0); - m_keys[j + 4] = ((keys[i] & 0x10) != 0); - m_keys[j + 5] = ((keys[i] & 0x20) != 0); - m_keys[j + 6] = ((keys[i] & 0x40) != 0); - m_keys[j + 7] = ((keys[i] & 0x80) != 0); + keys[j + 0] = ((xkeys[i] & 0x01) != 0) ? kDown : 0; + keys[j + 1] = ((xkeys[i] & 0x02) != 0) ? kDown : 0; + keys[j + 2] = ((xkeys[i] & 0x04) != 0) ? kDown : 0; + keys[j + 3] = ((xkeys[i] & 0x08) != 0) ? kDown : 0; + keys[j + 4] = ((xkeys[i] & 0x10) != 0) ? kDown : 0; + keys[j + 5] = ((xkeys[i] & 0x20) != 0) ? kDown : 0; + keys[j + 6] = ((xkeys[i] & 0x40) != 0) ? kDown : 0; + keys[j + 7] = ((xkeys[i] & 0x80) != 0) ? kDown : 0; } - // we've fake pressed no keys - m_fakeKeys.reset(); - // update mappings and current modifiers and mouse buttons doUpdateKeys(display); } -void -CXWindowsSecondaryScreen::releaseKeys() -{ - CDisplayLock display(m_screen); - if (display != NULL) { - doReleaseKeys(display); - } -} - void CXWindowsSecondaryScreen::updateKeysymMap(Display* display) { @@ -1394,6 +1041,9 @@ CXWindowsSecondaryScreen::updateKeysymMap(Display* display) // start with no keycodes for this modifier m_modifierKeycodes[i].clear(); + // no mask for this modifier + m_modifierIndexToMask[i] = 0; + // add each keycode for modifier for (unsigned int j = 0; j < keysPerModifier; ++j) { // get keycode and ignore unset keycodes @@ -1417,11 +1067,14 @@ CXWindowsSecondaryScreen::updateKeysymMap(Display* display) continue; } + // save modifier mask + m_modifierIndexToMask[i] = mapToModifierMask(i, keysym); + // fill in keysym info mapping.m_keycode[0] = keycode; mapping.m_shiftSensitive[0] = usesShift[keycodeIndex]; mapping.m_modeSwitchSensitive[0] = usesModeSwitch[keycodeIndex]; - mapping.m_modifierMask = (1 << i); + mapping.m_modifierMask = m_modifierIndexToMask[i]; mapping.m_capsLockSensitive = false; mapping.m_numLockSensitive = false; } @@ -1543,25 +1196,16 @@ CXWindowsSecondaryScreen::updateKeysymMap(Display* display) } } - // cache the bits for the modifier - m_shiftMask = getModifierMask(m_shiftKeysym); - m_ctrlMask = getModifierMask(m_ctrlKeysym); - m_altMask = getModifierMask(m_altKeysym); - m_metaMask = getModifierMask(m_metaKeysym); - m_superMask = getModifierMask(m_superKeysym); - m_capsLockMask = getModifierMask(m_capsLockKeysym); - m_numLockMask = getModifierMask(m_numLockKeysym); - m_modeSwitchMask = getModifierMask(m_modeSwitchKeysym); - m_scrollLockMask = getModifierMask(m_scrollLockKeysym); - // clean up XFree(keysyms); XFreeModifiermap(modifiers); } -void -CXWindowsSecondaryScreen::updateModifiers(Display* display) +KeyModifierMask +CXWindowsSecondaryScreen::getModifiers() const { + CDisplayLock display(m_screen); + // query the pointer to get the keyboard state Window root, window; int xRoot, yRoot, xWindow, yWindow; @@ -1572,50 +1216,56 @@ CXWindowsSecondaryScreen::updateModifiers(Display* display) } // update active modifier mask - m_mask = 0; + KeyModifierMask mask = 0; for (ModifierIndex i = 0; i < 8; ++i) { - const ModifierMask bit = (1 << i); + const KeyModifierMask bit = m_modifierIndexToMask[i]; if ((bit & m_toggleModifierMask) == 0) { for (KeyCodes::const_iterator j = m_modifierKeycodes[i].begin(); j != m_modifierKeycodes[i].end(); ++j) { - if (m_keys[*j]) { - m_mask |= bit; +// XXX -- is this right? + if (isKeyDown(*j)) { + mask |= bit; break; } } } else if ((bit & state) != 0) { // toggle is on - m_mask |= bit; + mask |= bit; } } + + return mask; } -void -CXWindowsSecondaryScreen::toggleKey(Display* display, - KeySym keysym, ModifierMask mask) +CSecondaryScreen::SysKeyID +CXWindowsSecondaryScreen::getToggleSysKey(KeyID keyID) const { + // convert KeyID to KeySym + KeySym keysym; + switch (keyID) { + case kKeyNumLock: + keysym = m_numLockKeysym; + break; + + case kKeyCapsLock: + keysym = m_capsLockKeysym; + break; + + case kKeyScrollLock: + keysym = m_scrollLockKeysym; + break; + + default: + return 0; + } + // lookup the key mapping KeySymIndex index = m_keysymMap.find(keysym); if (index == m_keysymMap.end()) { - return; + return 0; } - KeyCode keycode = index->second.m_keycode[0]; - - // toggle the key - if ((keysym == m_capsLockKeysym && m_capsLockHalfDuplex) || - (keysym == m_numLockKeysym && m_numLockHalfDuplex)) { - // "half-duplex" toggle - XTestFakeKeyEvent(display, keycode, (m_mask & mask) == 0, CurrentTime); - } - else { - // normal toggle - XTestFakeKeyEvent(display, keycode, True, CurrentTime); - XTestFakeKeyEvent(display, keycode, False, CurrentTime); - } - - // toggle shadow state - m_mask ^= mask; + return index->second.m_keycode[0]; } bool @@ -1633,6 +1283,62 @@ CXWindowsSecondaryScreen::isToggleKeysym(KeySym key) } } +KeyModifierMask +CXWindowsSecondaryScreen::mapToModifierMask( + ModifierIndex i, KeySym keysym) const +{ + // some modifier indices (0,1,2) are dedicated to particular uses, + // the rest depend on the keysyms bound. + switch (i) { + case 0: + return KeyModifierShift; + + case 1: + return KeyModifierCapsLock; + + case 2: + return KeyModifierControl; + + default: + switch (keysym) { + case XK_Shift_L: + case XK_Shift_R: + return KeyModifierShift; + + case XK_Control_L: + case XK_Control_R: + return KeyModifierControl; + + case XK_Alt_L: + case XK_Alt_R: + return KeyModifierAlt; + + case XK_Meta_L: + case XK_Meta_R: + return KeyModifierMeta; + + case XK_Super_L: + case XK_Super_R: + return KeyModifierSuper; + + case XK_Mode_switch: + return KeyModifierModeSwitch; + + case XK_Caps_Lock: + return KeyModifierCapsLock; + + case XK_Num_Lock: + return KeyModifierNumLock; + + case XK_Scroll_Lock: + return KeyModifierScrollLock; + + default: + return 0; + } + } +} + // map special KeyID keys to KeySyms #if defined(HAVE_X11_XF86KEYSYM_H) static const KeySym g_mapE000[] = @@ -1681,7 +1387,7 @@ static const KeySym g_mapE000[] = #endif KeySym -CXWindowsSecondaryScreen::keyIDToKeySym(KeyID id, ModifierMask mask) const +CXWindowsSecondaryScreen::keyIDToKeySym(KeyID id, KeyModifierMask mask) const { // convert id to keysym KeySym keysym = NoSymbol; @@ -1725,7 +1431,7 @@ CXWindowsSecondaryScreen::keyIDToKeySym(KeyID id, ModifierMask mask) const // instead. if that doesn't work, we'll fall back to XK_Tab with // shift active. this is to handle primary screens that don't map // XK_ISO_Left_Tab sending events to secondary screens that do. - if (keysym == XK_Tab && (mask & ShiftMask) != 0) { + if (keysym == XK_Tab && (mask & KeyModifierShift) != 0) { keysym = XK_ISO_Left_Tab; } @@ -2002,6 +1708,7 @@ CXWindowsSecondaryScreen::getDecomposedKeySymTable() XK_umacron, XK_dead_macron, XK_u, 0, // Latin-8 (ISO 8859-14) +#if defined(XK_Babovedot) XK_Babovedot, XK_dead_abovedot, XK_B, 0, XK_babovedot, XK_dead_abovedot, XK_b, 0, XK_Dabovedot, XK_dead_abovedot, XK_D, 0, @@ -2028,9 +1735,12 @@ CXWindowsSecondaryScreen::getDecomposedKeySymTable() XK_wcircumflex, XK_dead_circumflex, XK_w, 0, XK_tabovedot, XK_dead_abovedot, XK_t, 0, XK_ycircumflex, XK_dead_circumflex, XK_y, 0, +#endif // Latin-9 (ISO 8859-15) +#if defined(XK_Ydiaeresis) XK_Ydiaeresis, XK_dead_diaeresis, XK_Y, 0, +#endif // end of table 0 diff --git a/lib/platform/CXWindowsSecondaryScreen.h b/lib/platform/CXWindowsSecondaryScreen.h index c2de53ee..2f22bcf7 100644 --- a/lib/platform/CXWindowsSecondaryScreen.h +++ b/lib/platform/CXWindowsSecondaryScreen.h @@ -17,7 +17,6 @@ #include "CSecondaryScreen.h" #include "IScreenEventHandler.h" -#include "stdbitset.h" #include "stdmap.h" #include "stdvector.h" #if defined(X_DISPLAY_MISSING) @@ -37,14 +36,6 @@ public: virtual ~CXWindowsSecondaryScreen(); // CSecondaryScreen overrides - virtual void keyDown(KeyID, KeyModifierMask, KeyButton); - virtual void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton); - virtual void keyUp(KeyID, KeyModifierMask, KeyButton); - virtual void mouseDown(ButtonID); - virtual void mouseUp(ButtonID); - virtual void mouseMove(SInt32 x, SInt32 y); - virtual void mouseWheel(SInt32 delta); virtual void resetOptions(); virtual void setOptions(const COptionsList& options); virtual IScreen* getScreen() const; @@ -54,7 +45,6 @@ public: virtual bool onPreDispatch(const CEvent* event); virtual bool onEvent(CEvent* event); virtual void onOneShotTimerExpired(UInt32 id); - virtual SInt32 getJumpZoneSize() const; protected: // CSecondaryScreen overrides @@ -69,22 +59,24 @@ protected: virtual void destroyWindow(); virtual void showWindow(SInt32 x, SInt32 y); virtual void hideWindow(); - virtual void warpCursor(SInt32 x, SInt32 y); - virtual void updateKeys(); - virtual void releaseKeys(); - virtual void setToggleState(KeyModifierMask); - virtual KeyModifierMask getToggleState() const; + virtual void updateKeys(KeyState* sysKeyStates); + virtual KeyModifierMask getModifiers() const; + + virtual bool isAutoRepeating(SysKeyID) const; + virtual KeyModifierMask getModifierKeyMask(SysKeyID) const; + virtual bool isModifierActive(SysKeyID) const; + virtual SysKeyID getToggleSysKey(KeyID keyID) const; + virtual void flush(); + virtual KeyModifierMask + mapKey(Keystrokes&, SysKeyID& sysKeyID, KeyID, + KeyModifierMask, KeyModifierMask, EKeyAction) const; + virtual void fakeKeyEvent(SysKeyID, bool press) const; + virtual void fakeMouseButton(ButtonID, bool press) const; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const; + virtual void fakeMouseWheel(SInt32 delta) const; private: - enum EKeyAction { kPress, kRelease, kRepeat }; typedef unsigned int ModifierIndex; - typedef unsigned int ModifierMask; - class Keystroke { - public: - KeyCode m_keycode; - Bool m_press; - bool m_repeat; - }; class KeyMapping { public: KeyMapping(); @@ -97,7 +89,7 @@ private: bool m_modeSwitchSensitive[4]; // the modifier mask of keysym or 0 if not a modifier - ModifierMask m_modifierMask; + KeyModifierMask m_modifierMask; // whether keysym is sensitive to caps and num lock bool m_numLockSensitive; @@ -108,50 +100,40 @@ private: typedef std::map KeyCodeToModifierMap; typedef std::map KeySymMap; typedef KeySymMap::const_iterator KeySymIndex; - typedef std::vector Keystrokes; typedef std::vector KeySyms; typedef std::map KeySymsMap; - typedef std::map ServerKeyMap; - - void flush(Display*) const; unsigned int mapButton(ButtonID button) const; - ModifierMask mapKey(Keystrokes&, KeyCode&, KeyID, - KeyModifierMask, EKeyAction) const; - ModifierMask mapKeyRelease(Keystrokes&, KeyCode) const; bool mapToKeystrokes(Keystrokes& keys, - KeyCode& keycode, - ModifierMask& finalMask, + SysKeyID& keycode, + KeyModifierMask& finalMask, KeySymIndex keyIndex, - ModifierMask currentMask, - EKeyAction action) const; + KeyModifierMask currentMask, + EKeyAction action, + bool isHalfDuplex) const; bool adjustModifiers(Keystrokes& keys, Keystrokes& undo, - ModifierMask& inOutMask, - ModifierMask desiredMask) const; + KeyModifierMask& inOutMask, + KeyModifierMask desiredMask) const; bool adjustModifier(Keystrokes& keys, Keystrokes& undo, KeySym keysym, bool desireActive) const; - void doKeystrokes(const Keystrokes&, SInt32 count); - ModifierMask maskToX(KeyModifierMask) const; + KeyModifierMask mapToModifierMask(ModifierIndex, KeySym) const; unsigned int findBestKeyIndex(KeySymIndex keyIndex, - ModifierMask currentMask) const; + KeyModifierMask currentMask) const; bool isShiftInverted(KeySymIndex keyIndex, - ModifierMask currentMask) const; - ModifierMask getModifierMask(KeySym) const; + KeyModifierMask currentMask) const; void doUpdateKeys(Display*); - void doReleaseKeys(Display*); void updateKeysymMap(Display* display); void updateModifiers(Display* display); ModifierIndex keySymToModifierIndex(KeySym) const; - void toggleKey(Display*, KeySym, ModifierMask mask); static bool isToggleKeysym(KeySym); - KeySym keyIDToKeySym(KeyID id, ModifierMask mask) const; + KeySym keyIDToKeySym(KeyID id, KeyModifierMask mask) const; bool adjustForNumLock(KeySym) const; bool adjustForCapsLock(KeySym) const; @@ -163,31 +145,15 @@ private: CXWindowsScreen* m_screen; Window m_window; - // note toggle keys that toggles on up/down (false) or on - // transition (true) - bool m_numLockHalfDuplex; - bool m_capsLockHalfDuplex; - - // set entries indicate keys that are pressed (by us or by the user). - // indexed by keycode. - std::bitset<256> m_keys; - - // set entries indicate keys that are synthetically pressed by us. - // this is normally the same as m_keys. - std::bitset<256> m_fakeKeys; - // logical to physical button mapping. m_buttons[i] gives the // physical button for logical button i+1. std::vector m_buttons; - // current active modifiers (X key masks) - ModifierMask m_mask; - // the modifiers that have keys bound to them - ModifierMask m_modifierMask; + KeyModifierMask m_modifierMask; // set bits indicate modifiers that toggle (e.g. caps-lock) - ModifierMask m_toggleModifierMask; + KeyModifierMask m_toggleModifierMask; // keysym to keycode mapping KeySymMap m_keysymMap; @@ -195,6 +161,9 @@ private: // modifier index to keycodes KeyCodes m_modifierKeycodes[8]; + // modifier index to modifier mask + KeyModifierMask m_modifierIndexToMask[8]; + // keycode to modifier index KeyCodeToModifierMap m_keycodeToModifier; @@ -209,20 +178,6 @@ private: KeySym m_capsLockKeysym; KeySym m_scrollLockKeysym; - // modifier masks - ModifierMask m_shiftMask; - ModifierMask m_ctrlMask; - ModifierMask m_altMask; - ModifierMask m_metaMask; - ModifierMask m_superMask; - ModifierMask m_modeSwitchMask; - ModifierMask m_numLockMask; - ModifierMask m_capsLockMask; - ModifierMask m_scrollLockMask; - - // map server key buttons to local keycodes - ServerKeyMap m_serverKeyMap; - // the keyboard control state the last time this screen was entered XKeyboardState m_keyControl; diff --git a/lib/platform/IMSWindowsScreenEventHandler.h b/lib/platform/IMSWindowsScreenEventHandler.h index ee24ea8e..164f41a8 100644 --- a/lib/platform/IMSWindowsScreenEventHandler.h +++ b/lib/platform/IMSWindowsScreenEventHandler.h @@ -50,7 +50,6 @@ public: virtual void onScreensaver(bool activated) = 0; virtual bool onPreDispatch(const CEvent* event) = 0; virtual bool onEvent(CEvent* event) = 0; - virtual SInt32 getJumpZoneSize() const = 0; }; #endif diff --git a/lib/synergy/CSecondaryScreen.cpp b/lib/synergy/CSecondaryScreen.cpp index 3bf40d46..6dfaed19 100644 --- a/lib/synergy/CSecondaryScreen.cpp +++ b/lib/synergy/CSecondaryScreen.cpp @@ -116,7 +116,10 @@ CSecondaryScreen::remoteControl() } // update keyboard state - updateKeys(); + { + CLock lock(&m_mutex); + updateKeys(); + } // now remote ready. fake being active for call to leave(). bool screenSaverSync; @@ -156,7 +159,7 @@ CSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) LOG((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); - getScreen()->syncDesktop(); + sync(); // now active m_active = true; @@ -167,14 +170,13 @@ CSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) // update our keyboard state to reflect the local state updateKeys(); - // remember toggle key state - m_toggleKeys = getToggleState(); - - // toggle modifiers that don't match the desired state + // toggle modifiers that don't match the desired state and + // remember previous toggle key state. + m_toggleKeys = m_mask; setToggleState(mask); // warp to requested location - warpCursor(x, y); + fakeMouseMove(x, y); // show mouse hideWindow(); @@ -190,7 +192,7 @@ CSecondaryScreen::leave() CLock lock(&m_mutex); assert(m_active == true); - getScreen()->syncDesktop(); + sync(); // subclass hook onPreLeave(); @@ -198,7 +200,7 @@ CSecondaryScreen::leave() // restore toggle key state setToggleState(m_toggleKeys); - // warp and hide mouse + // hide mouse SInt32 x, y; getScreen()->getCursorCenter(x, y); showWindow(x, y); @@ -242,16 +244,314 @@ CSecondaryScreen::screensaver(bool activate) } } +CSecondaryScreen::SysKeyID +CSecondaryScreen::getUnhanded(SysKeyID) const +{ + // no key represents both left and right sides of any key + return 0; +} + +CSecondaryScreen::SysKeyID +CSecondaryScreen::getOtherHanded(SysKeyID) const +{ + // no key represents both left and right sides of any key + return 0; +} + +bool +CSecondaryScreen::synthesizeCtrlAltDel(EKeyAction) +{ + // pass keys through unchanged + return false; +} + +void +CSecondaryScreen::sync() const +{ + // do nothing +} + +void +CSecondaryScreen::flush() +{ + // do nothing +} + +void +CSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) +{ + // do nothing if no keys or no repeats + if (count < 1 || keys.empty()) { + return; + } + + // generate key events + LOG((CLOG_DEBUG2 "keystrokes:")); + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { + if (k->m_repeat) { + // repeat from here up to but not including the next key + // with m_repeat == false count times. + Keystrokes::const_iterator start = k; + for (; count > 0; --count) { + // send repeating events + for (k = start; k != keys.end() && k->m_repeat; ++k) { + LOG((CLOG_DEBUG2 " %d %s repeat", k->m_sysKeyID, k->m_press ? "down" : "up")); + fakeKeyEvent(k->m_sysKeyID, k->m_press); + } + } + + // note -- k is now on the first non-repeat key after the + // repeat keys, exactly where we'd like to continue from. + } + else { + // send event + LOG((CLOG_DEBUG2 " %d %s", k->m_sysKeyID, k->m_press ? "down" : "up")); + fakeKeyEvent(k->m_sysKeyID, k->m_press); + + // next key + ++k; + } + } + + flush(); +} + +void +CSecondaryScreen::keyDown(KeyID key, + KeyModifierMask mask, KeyButton button) +{ + CLock lock(&m_mutex); + sync(); + + // check for ctrl+alt+del emulation + if (key == kKeyDelete && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + LOG((CLOG_DEBUG "emulating ctrl+alt+del press")); + if (synthesizeCtrlAltDel(kPress)) { + return; + } + } + + // get the sequence of keys to simulate key press and the final + // modifier state. + Keystrokes keys; + SysKeyID sysKeyID; + m_mask = mapKey(keys, sysKeyID, key, m_mask, mask, kPress); + if (keys.empty()) { + // do nothing if there are no associated keys (i.e. lookup failed) + return; + } + sysKeyID &= 0xffu; + + // generate key events + doKeystrokes(keys, 1); + + // do not record button down if button or system key is 0 (invalid) + if (button != 0 && sysKeyID != 0) { + // note that key is now down + SysKeyID unhandedSysKeyID = getUnhanded(sysKeyID); + m_serverKeyMap[button] = sysKeyID; + m_keys[sysKeyID] |= kDown; + m_fakeKeys[sysKeyID] |= kDown; + if (unhandedSysKeyID != 0) { + m_keys[unhandedSysKeyID] |= kDown; + m_fakeKeys[unhandedSysKeyID] |= kDown; + } + } +} + + +void +CSecondaryScreen::keyRepeat(KeyID key, + KeyModifierMask mask, SInt32 count, KeyButton button) +{ + CLock lock(&m_mutex); + sync(); + + // if we haven't seen this button go down then ignore it + ServerKeyMap::iterator index = m_serverKeyMap.find(button); + if (index == m_serverKeyMap.end()) { + return; + } + + // get the sequence of keys to simulate key repeat and the final + // modifier state. + Keystrokes keys; + SysKeyID sysKeyID; + m_mask = mapKey(keys, sysKeyID, key, m_mask, mask, kRepeat); + if (keys.empty()) { + return; + } + sysKeyID &= 0xffu; + + // if this key shouldn't auto-repeat then ignore + if (!isAutoRepeating(sysKeyID)) { + return; + } + + // if the keycode for the auto-repeat is not the same as for the + // initial press then mark the initial key as released and the new + // key as pressed. this can happen when we auto-repeat after a + // dead key. for example, a dead accent followed by 'a' will + // generate an 'a with accent' followed by a repeating 'a'. the + // keycodes for the two keysyms might be different. + if (sysKeyID != index->second) { + // replace key up with previous key id but leave key down + // alone so it uses the new keycode and store that keycode + // in the server key map. + for (Keystrokes::iterator index2 = keys.begin(); + index2 != keys.end(); ++index2) { + if ((index2->m_sysKeyID & 0xffu) == sysKeyID) { + index2->m_sysKeyID = index->second; + break; + } + } + + // note that old key is now up + m_keys[index->second] &= ~kDown; + m_fakeKeys[index->second] &= ~kDown; + + // map server key to new key + index->second = sysKeyID; + + // note that new key is now down + m_keys[index->second] |= kDown; + m_fakeKeys[index->second] |= kDown; + } + + // generate key events + doKeystrokes(keys, count); +} + +void +CSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + CLock lock(&m_mutex); + sync(); + + // if we haven't seen this button go down then ignore it + ServerKeyMap::iterator index = m_serverKeyMap.find(button); + if (index == m_serverKeyMap.end()) { + return; + } + SysKeyID sysKeyID = index->second; + + // check for ctrl+alt+del emulation + if (key == kKeyDelete && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + LOG((CLOG_DEBUG "emulating ctrl+alt+del release")); + if (synthesizeCtrlAltDel(kRelease)) { + return; + } + } + + // get the sequence of keys to simulate key release + Keystrokes keys; + Keystroke keystroke; + keystroke.m_sysKeyID = sysKeyID; + keystroke.m_press = false; + keystroke.m_repeat = false; + keys.push_back(keystroke); + + // generate key events + doKeystrokes(keys, 1); + + // note that key is now up + SysKeyID unhandedSysKeyID = getUnhanded(sysKeyID); + m_serverKeyMap.erase(index); + m_keys[sysKeyID] &= ~kDown; + m_fakeKeys[sysKeyID] &= ~kDown; + if (unhandedSysKeyID != 0) { + SysKeyID otherHandedSysKeyID = getOtherHanded(sysKeyID); + if ((m_keys[otherHandedSysKeyID] & kDown) == 0) { + m_keys[unhandedSysKeyID] &= ~kDown; + m_fakeKeys[unhandedSysKeyID] &= ~kDown; + } + } + + // get the new modifier state + mask = getModifierKeyMask(sysKeyID); + if (mask != 0) { + // key is a modifier key + if ((mask & (KeyModifierCapsLock | + KeyModifierNumLock | + KeyModifierScrollLock)) != 0) { + // modifier is a toggle + m_mask ^= mask; + } + else if (!isModifierActive(sysKeyID)) { + // all keys for this modifier are released + m_mask &= ~mask; + } + } +} + +void +CSecondaryScreen::mouseDown(ButtonID button) +{ + CLock lock(&m_mutex); + sync(); + fakeMouseButton(button, true); + flush(); +} + +void +CSecondaryScreen::mouseUp(ButtonID button) +{ + CLock lock(&m_mutex); + sync(); + fakeMouseButton(button, false); + flush(); +} + +void +CSecondaryScreen::mouseMove(SInt32 x, SInt32 y) +{ + CLock lock(&m_mutex); + sync(); + fakeMouseMove(x, y); + flush(); +} + +void +CSecondaryScreen::mouseWheel(SInt32 delta) +{ + CLock lock(&m_mutex); + sync(); + fakeMouseWheel(delta); + flush(); +} + +void +CSecondaryScreen::setToggleState(KeyModifierMask mask) +{ + // toggle modifiers that don't match the desired state + KeyModifierMask different = (m_mask ^ mask); + if ((different & KeyModifierCapsLock) != 0) { + toggleKey(kKeyCapsLock, KeyModifierCapsLock); + } + if ((different & KeyModifierNumLock) != 0) { + toggleKey(kKeyNumLock, KeyModifierNumLock); + } + if ((different & KeyModifierScrollLock) != 0) { + toggleKey(kKeyScrollLock, KeyModifierScrollLock); + } +} + void CSecondaryScreen::resetOptions() { // set screen saver synchronization flag and see if we need to - // update the screen saver synchronization. + // update the screen saver synchronization. reset other options. bool screenSaverSyncOn; { CLock lock(&m_mutex); - screenSaverSyncOn = (!m_screenSaverSync && m_remoteReady); - m_screenSaverSync = true; + screenSaverSyncOn = (!m_screenSaverSync && m_remoteReady); + m_screenSaverSync = true; + m_numLockHalfDuplex = false; + m_capsLockHalfDuplex = false; } // update screen saver synchronization @@ -275,6 +575,14 @@ CSecondaryScreen::setOptions(const COptionsList& options) m_screenSaverSync = (options[i + 1] != 0); LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off")); } + else if (options[i] == kOptionHalfDuplexCapsLock) { + m_capsLockHalfDuplex = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexNumLock) { + m_numLockHalfDuplex = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off")); + } } if (!m_remoteReady || oldScreenSaverSync == m_screenSaverSync) { updateScreenSaverSync = false; @@ -306,17 +614,23 @@ CSecondaryScreen::getClipboard(ClipboardID id, getScreen()->getClipboard(id, clipboard); } +SInt32 +CSecondaryScreen::getJumpZoneSize() const +{ + return 0; +} + void CSecondaryScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { - getScreen()->syncDesktop(); + sync(); getScreen()->getShape(x, y, w, h); } void CSecondaryScreen::getCursorPos(SInt32& x, SInt32& y) const { - getScreen()->syncDesktop(); + sync(); getScreen()->getCursorPos(x, y); } @@ -379,3 +693,89 @@ CSecondaryScreen::onPostLeave() { // do nothing } + +void +CSecondaryScreen::updateKeys() +{ + sync(); + + // clear key state + memset(m_keys, 0, sizeof(m_keys)); + memset(m_fakeKeys, 0, sizeof(m_fakeKeys)); + + // let subclass set m_keys + updateKeys(m_keys); + + // get m_mask from subclass + m_mask = getModifiers(); + LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask)); +} + +void +CSecondaryScreen::releaseKeys() +{ + CLock lock(&m_mutex); + sync(); + + // release keys that we've synthesized a press for and only those + // keys. we don't want to synthesize a release on a key the user + // is still physically pressing. + for (UInt32 i = 1; i < 256; ++i) { + if ((m_fakeKeys[i] & kDown) != 0) { + fakeKeyEvent(i, false); + m_keys[i] &= ~kDown; + m_fakeKeys[i] &= ~kDown; + } + } + + flush(); +} + +void +CSecondaryScreen::toggleKey(KeyID keyID, KeyModifierMask mask) +{ + // get the system key ID for this toggle key ID + SysKeyID sysKeyID = getToggleSysKey(keyID); + if (sysKeyID == 0) { + return; + } + + // toggle the key + if (isKeyHalfDuplex(keyID)) { + // "half-duplex" toggle + fakeKeyEvent(sysKeyID, (m_mask & mask) == 0); + } + else { + // normal toggle + fakeKeyEvent(sysKeyID, true); + fakeKeyEvent(sysKeyID, false); + } + flush(); + + // toggle shadow state + m_mask ^= mask; + sysKeyID &= 0xffu; + m_keys[sysKeyID] ^= kToggled; + m_fakeKeys[sysKeyID] ^= kToggled; +} + +bool +CSecondaryScreen::isKeyDown(SysKeyID sysKeyID) const +{ + sysKeyID &= 0xffu; + return (sysKeyID != 0 && ((m_keys[sysKeyID] & kDown) != 0)); +} + +bool +CSecondaryScreen::isKeyToggled(SysKeyID sysKeyID) const +{ + sysKeyID &= 0xffu; + return (sysKeyID != 0 && ((m_keys[sysKeyID] & kToggled) != 0)); +} + +bool +CSecondaryScreen::isKeyHalfDuplex(KeyID keyID) const +{ + return ((keyID == kKeyCapsLock && m_capsLockHalfDuplex) || + (keyID == kKeyNumLock && m_numLockHalfDuplex)); +} diff --git a/lib/synergy/CSecondaryScreen.h b/lib/synergy/CSecondaryScreen.h index f1790a83..c69692b9 100644 --- a/lib/synergy/CSecondaryScreen.h +++ b/lib/synergy/CSecondaryScreen.h @@ -20,6 +20,8 @@ #include "MouseTypes.h" #include "OptionTypes.h" #include "CMutex.h" +#include "stdmap.h" +#include "stdvector.h" class IClipboard; class IScreen; @@ -130,41 +132,41 @@ public: synthesize an up or repeat for the same client key synthesized by keyDown(). */ - virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; + void keyDown(KeyID id, KeyModifierMask, KeyButton); //! Notify of key repeat /*! Synthesize key events to generate a press and release of key \c id \c count times. If possible match the given modifier mask. */ - virtual void keyRepeat(KeyID id, KeyModifierMask, - SInt32 count, KeyButton) = 0; + void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton); //! Notify of key release /*! Synthesize key events to generate a release of key \c id. If possible match the given modifier mask. */ - virtual void keyUp(KeyID id, KeyModifierMask, KeyButton) = 0; + void keyUp(KeyID id, KeyModifierMask, KeyButton); //! Notify of mouse press /*! Synthesize mouse events to generate a press of mouse button \c id. */ - virtual void mouseDown(ButtonID id) = 0; + void mouseDown(ButtonID id); //! Notify of mouse release /*! Synthesize mouse events to generate a release of mouse button \c id. */ - virtual void mouseUp(ButtonID id) = 0; + void mouseUp(ButtonID id); //! Notify of mouse motion /*! Synthesize mouse events to generate mouse motion to the absolute screen position \c xAbs,yAbs. */ - virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + void mouseMove(SInt32 xAbs, SInt32 yAbs); //! Notify of mouse wheel motion /*! @@ -173,7 +175,7 @@ public: motion towards the user. Each wheel click should generate a delta of +/-120. */ - virtual void mouseWheel(SInt32 delta) = 0; + void mouseWheel(SInt32 delta); //! Notify of options changes /*! @@ -212,7 +214,7 @@ public: Return the jump zone size, the size of the regions on the edges of the screen that cause the cursor to jump to another screen. */ - virtual SInt32 getJumpZoneSize() const = 0; + SInt32 getJumpZoneSize() const; //! Get screen shape /*! @@ -237,6 +239,26 @@ public: //@} protected: + typedef UInt8 KeyState; + typedef UInt32 SysKeyID; + enum EKeyState { kDown = 0x01, kToggled = 0x80 }; + enum EKeyAction { kPress, kRelease, kRepeat }; + class Keystroke { + public: + SysKeyID m_sysKeyID; + bool m_press; + bool m_repeat; + }; + typedef std::vector Keystrokes; + typedef std::map ServerKeyMap; + + void updateKeys(); + void releaseKeys(); + void doKeystrokes(const Keystrokes&, SInt32 count); + bool isKeyDown(SysKeyID) const; + bool isKeyToggled(SysKeyID) const; + bool isKeyHalfDuplex(KeyID) const; + //! Pre-mainLoop() hook /*! Called on entry to mainLoop(). Override to perform platform specific @@ -338,38 +360,54 @@ protected: */ virtual void hideWindow() = 0; + //! Synchronize key state + /*! + Save the current keyboard state. Normally a screen will save + the keyboard state in this method and use this shadow state, + available through isKeyDown() and getKeyState(), when + synthesizing events. + */ + virtual void updateKeys(KeyState* sysKeyStates) = 0; + + //! Get modifier key state + /*! + Return the current keyboard modifier state. + */ + virtual KeyModifierMask getModifiers() const = 0; + + //! Synchronize toggle key state + /*! + Toggles modifiers that don't match the given state so that they do. + */ + void setToggleState(KeyModifierMask); + + virtual SysKeyID getUnhanded(SysKeyID) const; + virtual SysKeyID getOtherHanded(SysKeyID) const; + virtual bool isAutoRepeating(SysKeyID) const = 0; + virtual KeyModifierMask getModifierKeyMask(SysKeyID) const = 0; + virtual bool isModifierActive(SysKeyID) const = 0; + virtual SysKeyID getToggleSysKey(KeyID keyID) const = 0; + virtual bool synthesizeCtrlAltDel(EKeyAction); + virtual void sync() const; + virtual void flush(); + + virtual KeyModifierMask + mapKey(Keystrokes&, SysKeyID& sysKeyID, KeyID, + KeyModifierMask currentMask, + KeyModifierMask desiredMask, EKeyAction) const = 0; + virtual void fakeKeyEvent(SysKeyID, bool press) const = 0; + virtual void fakeMouseButton(ButtonID, bool press) const = 0; + //! Warp cursor /*! Warp the cursor to the absolute coordinates \c x,y. */ - virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) const = 0; - //! Synchronize key state - /*! - Check the current keyboard state. Normally a screen will save - the keyboard state in this method and use this shadow state - when synthesizing events. - */ - virtual void updateKeys() = 0; + virtual void fakeMouseWheel(SInt32 delta) const = 0; - //! Release keys - /*! - Synthesizes key release event for any key that our key state - says is down. - */ - virtual void releaseKeys() = 0; - - //! Synchronize toggle key state - /*! - Toggle modifiers that don't match the given state so that they do. - */ - virtual void setToggleState(KeyModifierMask) = 0; - - //! Get the toggle key state - /*! - Returns the current state of the toggle keys. - */ - virtual KeyModifierMask getToggleState() const = 0; +private: + void toggleKey(KeyID, KeyModifierMask); private: CMutex m_mutex; @@ -380,11 +418,29 @@ private: // m_active is true if this screen has been entered bool m_active; + // true if screen saver should be synchronized to server + bool m_screenSaverSync; + + // map server key buttons to local system keys + ServerKeyMap m_serverKeyMap; + + // system key states as set by us or the user + KeyState m_keys[256]; + + // system key states as set by us + KeyState m_fakeKeys[256]; + + // current active modifiers +// XXX -- subclasses still have and use this + KeyModifierMask m_mask; + // the toggle key state when this screen was last entered KeyModifierMask m_toggleKeys; - // true if screen saver should be synchronized to server - bool m_screenSaverSync; + // note toggle keys that toggles on up/down (false) or on + // transition (true) + bool m_numLockHalfDuplex; + bool m_capsLockHalfDuplex; }; #endif diff --git a/lib/synergy/IScreenEventHandler.h b/lib/synergy/IScreenEventHandler.h index a68858ea..699d295c 100644 --- a/lib/synergy/IScreenEventHandler.h +++ b/lib/synergy/IScreenEventHandler.h @@ -63,16 +63,6 @@ public: */ virtual void onOneShotTimerExpired(UInt32 id) = 0; - //@} - //! @name accessors - //@{ - - //! Get jump zone size - /*! - Called to get the jump zone size. - */ - virtual SInt32 getJumpZoneSize() const = 0; - //@} };