/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2002 Chris Schoeneman * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "CXWindowsSecondaryScreen.h" #include "CXWindowsClipboard.h" #include "CXWindowsScreen.h" #include "CXWindowsScreenSaver.h" #include "CXWindowsUtil.h" #include "IScreenReceiver.h" #include "XScreen.h" #include "CThread.h" #include "CLog.h" #if defined(X_DISPLAY_MISSING) # error X11 is required to build synergy #else # include # include # define XK_MISCELLANY # define XK_XKB_KEYS # include # if defined(HAVE_X11_EXTENSIONS_XTEST_H) # include # else # error The XTest extension is required to build synergy # 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 // CXWindowsSecondaryScreen::CXWindowsSecondaryScreen(IScreenReceiver* receiver) : CSecondaryScreen(), m_window(None) { m_screen = new CXWindowsScreen(receiver, this); } CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen() { assert(m_window == None); delete m_screen; } void CXWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask) { Keystrokes keys; KeyCode keycode; // get the sequence of keys to simulate key press and the final // modifier state. m_mask = mapKey(keys, keycode, key, mask, kPress); if (keys.empty()) { return; } // generate key events doKeystrokes(keys, 1); // note that key is now down m_keys[keycode] = true; } void CXWindowsSecondaryScreen::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count) { Keystrokes keys; KeyCode keycode; // get the sequence of keys to simulate key repeat and the final // modifier state. m_mask = mapKey(keys, keycode, key, mask, kRepeat); if (keys.empty()) { return; } // generate key events doKeystrokes(keys, count); } void CXWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask) { Keystrokes keys; KeyCode keycode; // get the sequence of keys to simulate key release and the final // modifier state. m_mask = mapKey(keys, keycode, key, mask, kRelease); if (keys.empty()) { return; } // generate key events doKeystrokes(keys, 1); // note that key is now up m_keys[keycode] = false; } void CXWindowsSecondaryScreen::mouseDown(ButtonID button) { const unsigned int xButton = mapButton(button); if (xButton != 0) { CDisplayLock display(m_screen); XTestFakeButtonEvent(display, xButton, True, CurrentTime); XSync(display, False); } } void CXWindowsSecondaryScreen::mouseUp(ButtonID button) { const unsigned int xButton = mapButton(button); if (xButton != 0) { CDisplayLock display(m_screen); XTestFakeButtonEvent(display, xButton, False, CurrentTime); XSync(display, False); } } 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((delta >= 0) ? 4 : 5); 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); } XSync(display, False); } IScreen* CXWindowsSecondaryScreen::getScreen() const { return m_screen; } void CXWindowsSecondaryScreen::onScreensaver(bool) { // ignore } bool CXWindowsSecondaryScreen::onPreDispatch(const CEvent*) { return false; } bool CXWindowsSecondaryScreen::onEvent(CEvent* event) { assert(event != NULL); XEvent& xevent = event->m_event; // handle event switch (xevent.type) { case MappingNotify: // keyboard mapping changed updateKeys(); return true; case LeaveNotify: // mouse moved out of hider window somehow. hide the window. hideWindow(); return true; } } SInt32 CXWindowsSecondaryScreen::getJumpZoneSize() const { return 0; } void CXWindowsSecondaryScreen::onPreMainLoop() { assert(m_window != None); } void CXWindowsSecondaryScreen::onPreOpen() { assert(m_window == None); } void CXWindowsSecondaryScreen::onPostOpen() { // check for peculiarities // FIXME -- may have to get these from some database m_numLockHalfDuplex = false; m_capsLockHalfDuplex = false; // m_numLockHalfDuplex = true; // m_capsLockHalfDuplex = true; } void CXWindowsSecondaryScreen::onPreEnter() { assert(m_window != None); } void CXWindowsSecondaryScreen::onPreLeave() { assert(m_window != None); } void CXWindowsSecondaryScreen::createWindow() { { CDisplayLock display(m_screen); // verify the availability of the XTest extension int majorOpcode, firstEvent, firstError; if (!XQueryExtension(display, XTestExtensionName, &majorOpcode, &firstEvent, &firstError)) { LOG((CLOG_ERR "XTEST extension not available")); throw XScreenOpenFailure(); } // cursor hider window attributes. this window is used to hide the // cursor when it's not on the screen. the window is hidden as soon // as the cursor enters the screen or the display's real cursor is // moved. XSetWindowAttributes attr; attr.event_mask = LeaveWindowMask; attr.do_not_propagate_mask = 0; attr.override_redirect = True; attr.cursor = m_screen->getBlankCursor(); // create the cursor hider window m_window = XCreateWindow(display, m_screen->getRoot(), 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, CWDontPropagate | CWEventMask | CWOverrideRedirect | CWCursor, &attr); if (m_window == None) { throw XScreenOpenFailure(); } LOG((CLOG_DEBUG "window is 0x%08x", m_window)); // become impervious to server grabs XTestGrabControl(display, True); } // tell generic screen about the window m_screen->setWindow(m_window); } void CXWindowsSecondaryScreen::destroyWindow() { { CDisplayLock display(m_screen); if (display != NULL) { // release keys that are still pressed releaseKeys(display); // no longer impervious to server grabs XTestGrabControl(display, False); // update XSync(display, False); } } // destroy window if (m_window != None) { m_screen->setWindow(None); CDisplayLock display(m_screen); if (display != NULL) { XDestroyWindow(display, m_window); } m_window = None; } } void CXWindowsSecondaryScreen::showWindow() { // move hider window under the mouse (rather than moving the mouse // somewhere else on the screen) SInt32 x, y; getCursorPos(x, y); CDisplayLock display(m_screen); XMoveWindow(display, m_window, x, y); // raise and show the hider window. take activation. // FIXME -- take focus? XMapRaised(display, m_window); } void CXWindowsSecondaryScreen::hideWindow() { assert(m_window != None); CDisplayLock display(m_screen); XUnmapWindow(display, m_window); } void CXWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) { CDisplayLock display(m_screen); Display* pDisplay = display; XTestFakeMotionEvent(display, DefaultScreen(pDisplay), x, y, CurrentTime); XSync(display, False); } void CXWindowsSecondaryScreen::setToggleState(KeyModifierMask mask) { CDisplayLock display(m_screen); // toggle modifiers that don't match the desired state unsigned int xMask = maskToX(mask); if ((xMask & m_capsLockMask) != (m_mask & m_capsLockMask)) { toggleKey(display, XK_Caps_Lock, m_capsLockMask); } if ((xMask & m_numLockMask) != (m_mask & m_numLockMask)) { toggleKey(display, XK_Num_Lock, m_numLockMask); } if ((xMask & m_scrollLockMask) != (m_mask & m_scrollLockMask)) { toggleKey(display, XK_Scroll_Lock, 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 { if (id < 1 || id > m_buttons.size()) { // out of range return 0; } else if (m_buttons[id - 1] == 0) { // button not mapped return 0; } else { return static_cast(id); } } KeyModifierMask CXWindowsSecondaryScreen::mapKey(Keystrokes& keys, KeyCode& keycode, KeyID id, KeyModifierMask mask, EKeyAction action) const { // note -- must have display locked on entry // the system translates key events into characters depending // on the modifier key state at the time of the event. to // generate the right keysym we need to set the modifier key // states appropriately. // // 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. // note if the key is "half-duplex" const bool isHalfDuplex = ((id == kKeyCapsLock && m_capsLockHalfDuplex) || (id == kKeyNumLock && m_numLockHalfDuplex)); // ignore releases and repeats for half-duplex keys if (isHalfDuplex && action != kPress) { return m_mask; } // convert the id to a keysym and adjust the mask if necessary unsigned int outMask = m_mask; KeyCodeIndex keyIndex = findKey(id, outMask); if (keyIndex == noKey()) { // cannot convert id to keysym LOG((CLOG_DEBUG2 "no keysym for key")); return m_mask; } // get the keysym we're trying to generate and possible keycodes KeySym keysym = keyIndex->first; const KeyCodeMask& entry = keyIndex->second; // we can choose any of the available keycode/modifier states to // generate our keysym. the most desireable is the one most // closely matching the input mask. determine the order we // should try modifier states, from best match to worst. this // doesn't concern itself with whether or not a given modifier // state has an associated keycode. we'll just skip those later // if necessary. // default is none, shift, mode switch, shift + mode switch unsigned int desired = maskToX(mask); unsigned int index[4]; index[0] = 0; index[1] = 1; index[2] = 2; index[3] = 3; // if mode switch is active then 2 and 3 are better than 0 and 1 if (getBits(desired, m_modeSwitchMask) != 0) { index[0] ^= 2; index[1] ^= 2; index[2] ^= 2; index[3] ^= 2; } // if shift is active then 1 and 3 are better than 0 and 2. however, // if the key is affected by NumLock and NumLock is active then 1 and // 3 are better if shift is *not* down (because NumLock acts like // shift for those keysyms and shift cancels NumLock). similarly for // keys affected by CapsLock. bool desireShift = (getBits(desired, ShiftMask) != 0); bool invertShift = false; LOG((CLOG_DEBUG2 "desire shift: %s", desireShift ? "yes" : "no")); if (adjustForNumLock(keysym)) { LOG((CLOG_DEBUG2 "num lock sensitive")); if (m_numLockMask != 0) { LOG((CLOG_DEBUG2 "we have num lock")); if (getBits(desired, m_numLockMask) != 0) { LOG((CLOG_DEBUG2 "num lock desired, invert shift")); invertShift = true; } } } else if (adjustForCapsLock(keysym)) { LOG((CLOG_DEBUG2 "caps lock sensitive")); if (m_capsLockMask != 0) { LOG((CLOG_DEBUG2 "we have caps lock")); if (getBits(desired, m_capsLockMask) != 0) { LOG((CLOG_DEBUG2 "caps lock desired, invert shift")); invertShift = true; } } } if (desireShift != invertShift) { index[0] ^= 1; index[1] ^= 1; index[2] ^= 1; index[3] ^= 1; } // find the first modifier state with a keycode we can generate. // note that if m_modeSwitchMask is 0 then we can't generate // m_keycode[2] and m_keycode[3]. unsigned int bestIndex; for (bestIndex = 0; bestIndex < 4; ++bestIndex) { if (entry.m_keycode[index[bestIndex]] != 0) { if (index[bestIndex] < 2 || m_modeSwitchMask != 0) { bestIndex = index[bestIndex]; break; } } } if (bestIndex == 4) { // no keycode/modifiers to generate the keysym return m_mask; } // get the keycode keycode = entry.m_keycode[bestIndex]; LOG((CLOG_DEBUG2 "bestIndex = %d, keycode = %d", bestIndex, keycode)); // note if the key is a modifier ModifierMap::const_iterator modIndex = m_keycodeToModifier.find(keycode); unsigned int modifierBit = 0; if (modIndex != m_keycodeToModifier.end()) { modifierBit = (1 << modIndex->second); } // if the key is a modifier and that modifier is already in the // desired state then ignore the request since there's nothing // to do. never ignore a toggle modifier on press or release, // though. if (modifierBit != 0) { if (action == kRepeat) { LOG((CLOG_DEBUG2 "ignore repeating modifier")); return m_mask; } 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; } } } // bestIndex tells us if shift and mode switch should be on or off, // except if caps lock or num lock was down then we invert the sense // of bestIndex's lowest bit. // we must match both. unsigned int required = ShiftMask | m_modeSwitchMask; if (((bestIndex & 1) == 0) != invertShift) { desired = clearBits(desired, ShiftMask); } else { desired = setBits(desired, ShiftMask); } if ((bestIndex & 2) == 0) { desired = clearBits(desired, m_modeSwitchMask); } else { desired = setBits(desired, m_modeSwitchMask); } // if the key is a modifier then remove it from the desired mask. // we'll be matching the modifiers in the desired mask then adding // a key press or release for the keysym. if we don't clear the // modifier bit from the desired mask we'll end up dealing with // that key twice, once while matching modifiers and once while // handling the keysym. // // note that instead of clearing the bit, we make it identical to // the same bit in m_mask, meaning it's already in the right state. desired = assignBits(desired, modifierBit, m_mask); required = clearBits(required, modifierBit); LOG((CLOG_DEBUG2 "desired = 0x%04x, current = 0x%04x", desired, m_mask)); // add the key events required to get to the modifier state // necessary to generate an event yielding id. also save the // key events required to restore the state. if the key is // a modifier key then skip this because modifiers should not // modify modifiers. Keystrokes undo; Keystroke keystroke; if (desired != m_mask) { for (unsigned int i = 0; i < 8; ++i) { unsigned int bit = (1 << i); if (getBits(desired, bit) != getBits(m_mask, bit)) { LOG((CLOG_DEBUG2 "fix modifier %d", i)); // get the keycode we're using for this modifier. if // there isn't one then bail if the modifier is required // or ignore it if not required. KeyCode modifierKey = m_modifierToKeycode[i]; if (modifierKey == 0) { LOG((CLOG_DEBUG2 "no key mapped to modifier 0x%04x", bit)); if (getBits(required, bit) != 0) { keys.clear(); return m_mask; } else { continue; } } keystroke.m_keycode = modifierKey; keystroke.m_repeat = false; if (getBits(desired, bit)) { // modifier is not active but should be. if the // modifier is a toggle then toggle it on with a // press/release, otherwise activate it with a // press. use the first keycode for the modifier. LOG((CLOG_DEBUG2 "modifier 0x%04x is not active", bit)); if (getBits(m_toggleModifierMask, bit) != 0) { LOG((CLOG_DEBUG2 "modifier 0x%04x is a toggle", bit)); if ((bit == m_capsLockMask && m_capsLockHalfDuplex) || (bit == m_numLockMask && m_numLockHalfDuplex)) { keystroke.m_press = True; keys.push_back(keystroke); keystroke.m_press = False; undo.push_back(keystroke); } else { keystroke.m_press = True; keys.push_back(keystroke); keystroke.m_press = False; keys.push_back(keystroke); undo.push_back(keystroke); keystroke.m_press = True; undo.push_back(keystroke); } } else { keystroke.m_press = True; keys.push_back(keystroke); keystroke.m_press = False; undo.push_back(keystroke); } } else { // modifier is active but should not be. if the // modifier is a toggle then toggle it off with a // press/release, otherwise deactivate it with a // release. we must check each keycode for the // modifier if not a toggle. LOG((CLOG_DEBUG2 "modifier 0x%04x is active", bit)); if (getBits(m_toggleModifierMask, bit) != 0) { LOG((CLOG_DEBUG2 "modifier 0x%04x is a toggle", bit)); if ((bit == m_capsLockMask && m_capsLockHalfDuplex) || (bit == m_numLockMask && m_numLockHalfDuplex)) { keystroke.m_press = False; keys.push_back(keystroke); keystroke.m_press = True; undo.push_back(keystroke); } else { keystroke.m_press = True; keys.push_back(keystroke); keystroke.m_press = False; keys.push_back(keystroke); undo.push_back(keystroke); keystroke.m_press = True; undo.push_back(keystroke); } } else { for (unsigned int j = 0; j < m_keysPerModifier; ++j) { const KeyCode key = m_modifierToKeycodes[i * m_keysPerModifier + j]; if (key != 0 && m_keys[key]) { keystroke.m_keycode = key; keystroke.m_press = False; keys.push_back(keystroke); keystroke.m_press = True; undo.push_back(keystroke); } } } } } } } // note if the press of a half-duplex key should be treated as a release if (isHalfDuplex && getBits(m_mask, modifierBit) != 0) { action = kRelease; } // add the key event keystroke.m_keycode = keycode; switch (action) { case kPress: keystroke.m_press = True; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRelease: keystroke.m_press = False; keystroke.m_repeat = false; keys.push_back(keystroke); break; case kRepeat: keystroke.m_press = False; keystroke.m_repeat = true; keys.push_back(keystroke); keystroke.m_press = True; keys.push_back(keystroke); break; } // add key events to restore the modifier state. apply events in // the reverse order that they're stored in undo. while (!undo.empty()) { keys.push_back(undo.back()); undo.pop_back(); } // if the key is a modifier key then compute the modifier map after // this key is pressed or released. mask = m_mask; if (modifierBit != 0) { // can't be repeating if we've gotten here assert(action != kRepeat); // toggle keys modify the state on release. other keys set the // bit on press and clear the bit on release. if half-duplex // then toggle each time we get here. if (getBits(m_toggleModifierMask, modifierBit) != 0) { if (isHalfDuplex || action == kRelease) { mask = flipBits(mask, modifierBit); } } else if (action == kPress) { mask = setBits(mask, modifierBit); } else if (action == kRelease) { // can't reset bit until all keys that set it are released. // scan those keys to see if any (except keycode) are pressed. bool down = false; for (unsigned int j = 0; !down && j < m_keysPerModifier; ++j) { KeyCode modKeycode = m_modifierToKeycodes[modIndex->second * m_keysPerModifier + j]; if (modKeycode != 0 && modKeycode != keycode) { down = m_keys[modKeycode]; } } if (!down) { mask = clearBits(mask, modifierBit); } } } LOG((CLOG_DEBUG2 "final mask: 0x%04x", mask)); return mask; } void CXWindowsSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) { // 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 XTestFakeKeyEvent(display, k->m_keycode, k->m_press, CurrentTime); // next key ++k; } } // update XSync(display, False); } unsigned int CXWindowsSecondaryScreen::maskToX(KeyModifierMask inMask) const { unsigned int outMask = 0; if (inMask & KeyModifierShift) { outMask |= ShiftMask; } if (inMask & KeyModifierControl) { outMask |= ControlMask; } 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::releaseKeys(Display* display) { assert(display != NULL); // key up for each key that's down for (UInt32 i = 0; i < 256; ++i) { if (m_keys[i]) { XTestFakeKeyEvent(display, i, False, CurrentTime); m_keys[i] = false; } } } void CXWindowsSecondaryScreen::updateKeys() { CDisplayLock display(m_screen); // query the button mapping UInt32 numButtons = XGetPointerMapping(display, NULL, 0); unsigned char* tmpButtons = new unsigned char[numButtons]; XGetPointerMapping(display, tmpButtons, numButtons); // find the largest logical button id unsigned char maxButton = 0; for (UInt32 i = 0; i < numButtons; ++i) { if (tmpButtons[i] > maxButton) { maxButton = tmpButtons[i]; } } // allocate button array m_buttons.resize(maxButton); // fill in button array values. m_buttons[i] is the physical // button number for logical button i+1. for (UInt32 i = 0; i < numButtons; ++i) { m_buttons[i] = 0; } for (UInt32 i = 0; i < numButtons; ++i) { m_buttons[tmpButtons[i] - 1] = i + 1; } // clean up delete[] tmpButtons; // ask server which keys are pressed char keys[32]; XQueryKeymap(display, keys); // 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); } // update mappings and current modifiers updateModifierMap(display); updateKeycodeMap(display); updateModifiers(display); } void CXWindowsSecondaryScreen::updateModifiers(Display* display) { // query the pointer to get the keyboard state Window root, window; int xRoot, yRoot, xWindow, yWindow; unsigned int state; if (!XQueryPointer(display, m_window, &root, &window, &xRoot, &yRoot, &xWindow, &yWindow, &state)) { state = 0; } // update active modifier mask m_mask = 0; for (unsigned int i = 0; i < 8; ++i) { const unsigned int bit = (1 << i); if ((bit & m_toggleModifierMask) == 0) { for (unsigned int j = 0; j < m_keysPerModifier; ++j) { if (m_keys[m_modifierToKeycodes[i * m_keysPerModifier + j]]) m_mask |= bit; } } else if ((bit & state) != 0) { // toggle is on m_mask |= bit; } } } void CXWindowsSecondaryScreen::updateKeycodeMap(Display* display) { // there are up to 4 keysyms per keycode static const unsigned int maxKeysyms = 4; // table for counting 1 bits static const int s_numBits[maxKeysyms] = { 0, 1, 1, 2 }; // get the number of keycodes int minKeycode, maxKeycode; XDisplayKeycodes(display, &minKeycode, &maxKeycode); const int numKeycodes = maxKeycode - minKeycode + 1; // get the keyboard mapping for all keys int keysymsPerKeycode; KeySym* keysyms = XGetKeyboardMapping(display, minKeycode, numKeycodes, &keysymsPerKeycode); // we only understand up to maxKeysyms keysyms per keycodes unsigned int numKeysyms = keysymsPerKeycode; if (numKeysyms > maxKeysyms) { numKeysyms = maxKeysyms; } // initialize m_keycodeMap.clear(); // insert keys for (int i = 0; i < numKeycodes; ++i) { // compute mask over all mapped keysyms. if a keycode has, say, // no shifted keysym then we can ignore the shift state when // synthesizing an event to generate it. unsigned int globalMask = 0; for (unsigned int j = 0; j < numKeysyms; ++j) { const KeySym keysym = keysyms[i * keysymsPerKeycode + j]; if (keysym != NoSymbol) { globalMask |= j; } } // map each keysym to it's keycode/modifier mask for (unsigned int j = 0; j < numKeysyms; ++j) { // get keysym KeySym keysym = keysyms[i * keysymsPerKeycode + j]; // get modifier mask required for this keysym. note that // a keysym of NoSymbol means that a keysym using fewer // modifiers would be generated using these modifiers. // for example, given // keycode 86 = KP_Add // then we'll generate KP_Add regardless of the modifiers. // we add an entry for that keysym for these modifiers. unsigned int index = j; if (keysym == NoSymbol && (index == 1 || index == 3)) { // shift doesn't matter index = index - 1; keysym = keysyms[i * keysymsPerKeycode + index]; } if (keysym == NoSymbol && index == 2) { // mode switch doesn't matter index = 0; keysym = keysyms[i * keysymsPerKeycode + index]; } if (keysym == NoSymbol && index == 0) { // no symbols at all for this keycode continue; } // look it up, creating a new entry if necessary KeyCodeMask& entry = m_keycodeMap[keysym]; // save keycode for keysym and modifiers entry.m_keycode[j] = static_cast(minKeycode + i); } } // clean up XFree(keysyms); } unsigned int CXWindowsSecondaryScreen::indexToModifierMask(int index) const { assert(index >= 0 && index <= 3); switch (index) { case 0: return 0; case 1: return ShiftMask | LockMask; case 2: return m_modeSwitchMask; case 3: return ShiftMask | LockMask | m_modeSwitchMask; } } void CXWindowsSecondaryScreen::updateModifierMap(Display* display) { // get modifier map from server XModifierKeymap* keymap = XGetModifierMapping(display); // initialize m_modifierMask = 0; m_toggleModifierMask = 0; m_altMask = 0; m_metaMask = 0; m_superMask = 0; m_modeSwitchMask = 0; m_numLockMask = 0; m_capsLockMask = 0; m_scrollLockMask = 0; m_keysPerModifier = keymap->max_keypermod; m_modifierToKeycode.clear(); m_modifierToKeycode.resize(8); m_modifierToKeycodes.clear(); m_modifierToKeycodes.resize(8 * m_keysPerModifier); // set keycodes and masks for (unsigned int i = 0; i < 8; ++i) { const unsigned int bit = (1 << i); for (unsigned int j = 0; j < m_keysPerModifier; ++j) { KeyCode keycode = keymap->modifiermap[i * m_keysPerModifier + j]; // save in modifier to keycode m_modifierToKeycodes[i * m_keysPerModifier + j] = keycode; // no further interest in unmapped modifier if (keycode == 0) { continue; } // save keycode for modifier if we don't have one yet if (m_modifierToKeycode[i] == 0) { m_modifierToKeycode[i] = keycode; } // save in keycode to modifier m_keycodeToModifier.insert(std::make_pair(keycode, i)); // save bit in all-modifiers mask m_modifierMask |= bit; // modifier is a toggle if the keysym is a toggle modifier const KeySym keysym = XKeycodeToKeysym(display, keycode, 0); if (isToggleKeysym(keysym)) { m_toggleModifierMask |= bit; } // note mask for particular modifiers switch (keysym) { case XK_Alt_L: case XK_Alt_R: m_altMask |= bit; break; case XK_Meta_L: case XK_Meta_R: m_metaMask |= bit; break; case XK_Super_L: case XK_Super_R: m_superMask |= bit; break; case XK_Mode_switch: m_modeSwitchMask |= bit; break; case XK_Num_Lock: m_numLockMask |= bit; break; case XK_Caps_Lock: m_capsLockMask |= bit; break; case XK_Scroll_Lock: m_scrollLockMask |= bit; } } } XFreeModifiermap(keymap); } void CXWindowsSecondaryScreen::toggleKey(Display* display, KeySym keysym, unsigned int mask) { // lookup the keycode KeyCodeMap::const_iterator index = m_keycodeMap.find(keysym); if (index == m_keycodeMap.end()) { return; } // FIXME -- which keycode? KeyCode keycode = index->second.m_keycode[0]; // toggle the key if ((keysym == XK_Caps_Lock && m_capsLockHalfDuplex) || (keysym == XK_Num_Lock && 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; } bool CXWindowsSecondaryScreen::isToggleKeysym(KeySym key) { switch (key) { case XK_Caps_Lock: case XK_Shift_Lock: case XK_Num_Lock: case XK_Scroll_Lock: return true; default: return false; } } CXWindowsSecondaryScreen::KeyCodeIndex CXWindowsSecondaryScreen::findKey(KeyID id, KeyModifierMask& mask) const { // convert id to keysym KeySym keysym = NoSymbol; switch (id & 0xffffff00) { case 0x0000: // Latin-1 keysym = static_cast(id); break; case 0xee00: // ISO 9995 Function and Modifier Keys if (id == kKeyLeftTab) { keysym = XK_ISO_Left_Tab; } break; case 0xef00: // MISCELLANY keysym = static_cast(id - 0xef00 + 0xff00); break; } // fail if unknown key if (keysym == NoSymbol) { return m_keycodeMap.end(); } // if kKeyTab is requested with shift active then try XK_ISO_Left_Tab // instead. if that doesn't work, we'll fall back to XK_Tab with // shift active. this is to handle primary screens that don't map // XK_ISO_Left_Tab sending events to secondary screens that do. if (keysym == XK_Tab && (mask & ShiftMask) != 0) { keysym = XK_ISO_Left_Tab; mask &= ~ShiftMask; } // find the keycodes that generate the keysym KeyCodeIndex index = m_keycodeMap.find(keysym); if (index == noKey()) { // try backup keysym for certain keys (particularly the numpad // keys since most laptops don't have a separate numpad and the // numpad overlaying the main keyboard may not have movement // key bindings). switch (keysym) { case XK_KP_Home: keysym = XK_Home; break; case XK_KP_Left: keysym = XK_Left; break; case XK_KP_Up: keysym = XK_Up; break; case XK_KP_Right: keysym = XK_Right; break; case XK_KP_Down: keysym = XK_Down; break; case XK_KP_Prior: keysym = XK_Prior; break; case XK_KP_Next: keysym = XK_Next; break; case XK_KP_End: keysym = XK_End; break; case XK_KP_Insert: keysym = XK_Insert; break; case XK_KP_Delete: keysym = XK_Delete; break; case XK_ISO_Left_Tab: keysym = XK_Tab; mask |= ShiftMask; break; default: return index; } index = m_keycodeMap.find(keysym); } return index; } CXWindowsSecondaryScreen::KeyCodeIndex CXWindowsSecondaryScreen::noKey() const { return m_keycodeMap.end(); } bool CXWindowsSecondaryScreen::adjustForNumLock(KeySym keysym) const { if (IsKeypadKey(keysym) || IsPrivateKeypadKey(keysym)) { // it's NumLock sensitive LOG((CLOG_DEBUG2 "keypad key: NumLock %s", ((m_mask & m_numLockMask) != 0) ? "active" : "inactive")); return true; } return false; } bool CXWindowsSecondaryScreen::adjustForCapsLock(KeySym keysym) const { KeySym lKey, uKey; XConvertCase(keysym, &lKey, &uKey); if (lKey != uKey) { // it's CapsLock sensitive LOG((CLOG_DEBUG2 "case convertible: CapsLock %s", ((m_mask & m_capsLockMask) != 0) ? "active" : "inactive")); return true; } return false; } // // CXWindowsSecondaryScreen::KeyCodeMask // CXWindowsSecondaryScreen::KeyCodeMask::KeyCodeMask() { m_keycode[0] = 0; m_keycode[1] = 0; m_keycode[2] = 0; m_keycode[3] = 0; }