From 419eadfaf9c4d901a130ebb4bc4bd016af24913e Mon Sep 17 00:00:00 2001 From: crs Date: Fri, 26 Apr 2002 17:38:01 +0000 Subject: [PATCH] changed processing of key events in X. secondary screen now activates/deactivates modifiers as necessary to get a keycode interpreted as the expected keysym. still some work and testing to do on this. --- client/CXWindowsSecondaryScreen.cpp | 469 +++++++++++++++++++++++++++- client/CXWindowsSecondaryScreen.h | 53 +++- server/CXWindowsPrimaryScreen.cpp | 40 ++- server/CXWindowsPrimaryScreen.h | 2 +- synergy/KeyTypes.h | 2 +- 5 files changed, 536 insertions(+), 30 deletions(-) diff --git a/client/CXWindowsSecondaryScreen.cpp b/client/CXWindowsSecondaryScreen.cpp index 553564e1..5e60a4d7 100644 --- a/client/CXWindowsSecondaryScreen.cpp +++ b/client/CXWindowsSecondaryScreen.cpp @@ -4,6 +4,9 @@ #include "CLog.h" #include #include +#include +#define XK_MISCELLANY +#include #include // @@ -35,6 +38,17 @@ void CXWindowsSecondaryScreen::run() // handle event switch (xevent.type) { + case MappingNotify: { + // keyboard mapping changed + CDisplayLock display(this); + XRefreshKeyboardMapping(&xevent.xmapping); + updateKeys(display); + updateKeycodeMap(display); + updateModifierMap(display); + updateModifiers(display); + break; + } + case LeaveNotify: { // mouse moved out of hider window somehow. hide the window. assert(m_window != None); @@ -118,6 +132,12 @@ void CXWindowsSecondaryScreen::open(CClient* client) if (!XQueryExtension(display, XTestExtensionName, &majorOpcode, &firstEvent, &firstError)) throw int(6); // FIXME -- make exception for this + + // update key state + updateKeys(display); + updateKeycodeMap(display); + updateModifierMap(display); + updateModifiers(display); } void CXWindowsSecondaryScreen::close() @@ -143,6 +163,10 @@ void CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y) // show cursor XUnmapWindow(display, m_window); + + // update our keyboard state to reflect the local state + updateKeys(display); + updateModifiers(display); } void CXWindowsSecondaryScreen::leave() @@ -154,8 +178,25 @@ void CXWindowsSecondaryScreen::leave() void CXWindowsSecondaryScreen::keyDown( KeyID key, KeyModifierMask mask) { + Keystrokes keys; + KeyCode keycode; + CDisplayLock display(this); - XTestFakeKeyEvent(display, mapKey(key, mask), True, CurrentTime); + + // get the sequence of keys to simulate key press and the final + // modifier state. + m_mask = mapKey(keys, keycode, key, mask, True); + if (keys.empty()) + return; + + // generate key events + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ++k) + XTestFakeKeyEvent(display, k->first, k->second, CurrentTime); + + // note that key is now down + m_keys[keycode] = true; + + // update XSync(display, False); } @@ -169,8 +210,25 @@ void CXWindowsSecondaryScreen::keyRepeat( void CXWindowsSecondaryScreen::keyUp( KeyID key, KeyModifierMask mask) { + Keystrokes keys; + KeyCode keycode; + CDisplayLock display(this); - XTestFakeKeyEvent(display, mapKey(key, mask), False, CurrentTime); + + // get the sequence of keys to simulate key release and the final + // modifier state. + m_mask = mapKey(keys, keycode, key, mask, False); + if (keys.empty()) + return; + + // generate key events + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ++k) + XTestFakeKeyEvent(display, k->first, k->second, CurrentTime); + + // note that key is now up + m_keys[keycode] = false; + + // update XSync(display, False); } @@ -294,17 +352,408 @@ void CXWindowsSecondaryScreen::leaveNoLock(Display* display) XWarpPointer(display, None, m_window, 0, 0, 0, 0, 0, 0); } -KeyCode CXWindowsSecondaryScreen::mapKey( - KeyID id, KeyModifierMask /*mask*/) const -{ - CDisplayLock display(this); - // FIXME -- use mask - return XKeysymToKeycode(display, static_cast(id)); -} - unsigned int CXWindowsSecondaryScreen::mapButton( ButtonID id) const { // FIXME -- should use button mapping? return static_cast(id); } + +KeyModifierMask CXWindowsSecondaryScreen::mapKey( + Keystrokes& keys, + KeyCode& keycode, + KeyID id, KeyModifierMask mask, + Bool press) 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. + + // lookup the a keycode for this key id. also return the + // key modifier mask required. + unsigned int outMask; + if (!findKeyCode(keycode, outMask, id, maskToX(mask))) { + // we cannot generate the desired keysym because no key + // maps to that keysym. just return the current mask. + return m_mask; + } + + // if we cannot match the modifier mask then don't return any + // keys and just return the current mask. + if ((outMask & m_modifierMask) != outMask) { + return m_mask; + } + + // note if the key is a modifier + ModifierMap::const_iterator index = m_keycodeToModifier.find(keycode); + const bool isModifier = (index != m_keycodeToModifier.end()); + + // 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; + if (outMask != m_mask && !isModifier) { + for (unsigned int i = 0; i < 8; ++i) { + unsigned int bit = (1 << i); + if ((outMask & bit) != (m_mask & bit)) { + // get list of keycodes for the modifier. there must + // be at least one. + const KeyCode* modifierKeys = + &m_modifierToKeycode[i * m_keysPerModifier]; + assert(modifierKeys[0] != 0); + + if ((outMask & bit) != 0) { + // modifier is not active but should be. if the + // modifier is a toggle then toggle it on with a + // press/release, otherwise activate it with a + // press. use the first keycode for the modifier. + const KeyCode modifierKey = modifierKeys[0]; + keys.push_back(std::make_pair(modifierKey, True)); + if ((bit & m_toggleModifierMask) != 0) { + keys.push_back(std::make_pair(modifierKey, False)); + undo.push_back(std::make_pair(modifierKey, False)); + undo.push_back(std::make_pair(modifierKey, True)); + } + else { + undo.push_back(std::make_pair(modifierKey, False)); + } + } + + else { + // modifier is active but should not be. if the + // modifier is a toggle then toggle it off with a + // press/release, otherwise deactivate it with a + // release. we must check each keycode for the + // modifier if not a toggle. + if (bit & m_toggleModifierMask) { + const KeyCode modifierKey = modifierKeys[0]; + keys.push_back(std::make_pair(modifierKey, True)); + keys.push_back(std::make_pair(modifierKey, False)); + undo.push_back(std::make_pair(modifierKey, False)); + undo.push_back(std::make_pair(modifierKey, True)); + } + else { + for (unsigned int j = 0; j < m_keysPerModifier; ++j) { + const KeyCode key = modifierKeys[j]; + if (m_keys[key]) { + keys.push_back(std::make_pair(key, False)); + undo.push_back(std::make_pair(key, True)); + } + } + } + } + } + } + } + + // add the key event + keys.push_back(std::make_pair(keycode, press)); + + // 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. + mask = m_mask; + if (isModifier) { + // get modifier + const unsigned int modifierBit = (1 << index->second); + + // toggle keys modify the state on press if toggling on and on + // release if toggling off. other keys set the bit on press + // and clear the bit on release. + if ((modifierBit & m_toggleModifierMask) != 0) { + if (((mask & modifierBit) == 0) == press) + mask ^= modifierBit; + } + else if (press) { + mask |= modifierBit; + } + else { + // 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; + const KeyCode* modifierKeys = &m_modifierToKeycode[ + index->second * m_keysPerModifier]; + for (unsigned int j = 0; !down && j < m_keysPerModifier; ++j) { + if (m_keys[modifierKeys[j]]) + down = true; + } + if (!down) + mask &= ~modifierBit; + } + } + + return mask; +} + +bool CXWindowsSecondaryScreen::findKeyCode( + KeyCode& keycode, + unsigned int& maskOut, + KeyID id, + unsigned int maskIn) const +{ + // find a keycode to generate id. XKeysymToKeycode() almost does + // what we need but won't tell us which index to use with the + // keycode. return false if there's no keycode to generate id. + KeyCodeMap::const_iterator index = m_keycodeMap.find(id); + if (index == m_keycodeMap.end()) + return false; + + // save the keycode + keycode = index->second.keycode; + + // compute output mask. that's the set of modifiers that need to + // be enabled when the keycode event is encountered in order to + // generate the id keysym and match maskIn. it's possible that + // maskIn wants, say, a shift key to be down but that would make + // it impossible to generate the keysym. in that case we must + // override maskIn. + // + // this is complicated by caps/shift-lock and num-lock. for + // example, if id is a keypad keysym and maskIn indicates that + // shift is not active but keyMask indicates that shift is + // required then we can either activate shift and then send + // the keycode or we can activate num-lock and then send the + // keycode. the latter is better because applications may + // treat, say, shift+Home differently than Home. + maskOut = (maskIn & ~index->second.keyMaskMask); + if (IsKeypadKey(id) || IsPrivateKeypadKey(id)) { + // compare shift state of maskIn and keyMask + const bool agree = ((maskIn & ShiftMask) == + (index->second.keyMask & ShiftMask)); + + // get num-lock state + const bool numLockActive = ((m_mask & m_numLockMask) != 0); + + // if num-lock is active and the shift states agree or if + // num-lock is not active and the shift states do not agree + // then we should toggle num-lock. + if (numLockActive == agree) { + maskOut |= (maskIn & ShiftMask) | + (index->second.keyMask & ~ShiftMask); + if (numLockActive) + maskOut &= ~m_numLockMask; + else + maskOut |= m_numLockMask; + } + else { + maskOut |= index->second.keyMask; + } + } + else { + // compare shift state of maskIn and keyMask + const bool agree = ((maskIn & ShiftMask) == + (index->second.keyMask & ShiftMask)); + + // get caps-lock state + const bool capsLockActive = ((m_mask & m_capsLockMask) != 0); + + // if caps-lock is active and the shift states agree or if + // caps-lock is not active and the shift states do not agree + // then we should toggle caps-lock. + if (capsLockActive == agree) { + maskOut |= (maskIn & ShiftMask) | + (index->second.keyMask & ~ShiftMask); + if (capsLockActive) + maskOut &= ~m_capsLockMask; + else + maskOut |= m_capsLockMask; + } + else { + maskOut |= index->second.keyMask; + } + } + + return true; +} + +unsigned int CXWindowsSecondaryScreen::maskToX( + KeyModifierMask inMask) const +{ + // FIXME -- should be configurable. not using Mod3Mask. + unsigned int outMask = 0; + if (inMask & KeyModifierShift) + outMask |= ShiftMask; + if (inMask & KeyModifierControl) + outMask |= ControlMask; + if (inMask & KeyModifierAlt) + outMask |= Mod1Mask; + if (inMask & KeyModifierMeta) + outMask |= Mod4Mask; + if (inMask & KeyModifierCapsLock) + outMask |= LockMask; + if (inMask & KeyModifierNumLock) + outMask |= Mod2Mask; + if (inMask & KeyModifierScrollLock) + outMask |= Mod5Mask; + return outMask; +} + +void CXWindowsSecondaryScreen::updateKeys(Display* display) +{ + // ask server which keys are pressed + char keys[32]; + XQueryKeymap(display, keys); + + // transfer to our state + for (unsigned int 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); + } +} + +void CXWindowsSecondaryScreen::updateModifiers( + Display*) +{ + // 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_modifierToKeycode[i * m_keysPerModifier + j]]) + m_mask |= bit; + } + } + else { + // FIXME -- not sure how to check current lock states + } + } +} + +void CXWindowsSecondaryScreen::updateKeycodeMap( + Display* display) +{ + // 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); + + // restrict keysyms per keycode to 2 because, frankly, i have no + // idea how/what modifiers are used to access keysyms beyond the + // first 2. + int numKeysyms = 2; // keysymsPerKeycode + + // initialize + KeyCodeMask entry; + m_keycodeMap.clear(); + + // insert keys + for (int i = 0; i < numKeycodes; ++i) { + // how many keysyms for this keycode? + int n; + for (n = 0; n < numKeysyms; ++n) { + if (keysyms[i * keysymsPerKeycode + n] == NoSymbol) { + break; + } + } + + // move to next keycode if there are no keysyms + if (n == 0) { + continue; + } + + // set the mask of modifiers that this keycode uses + entry.keyMaskMask = (n == 1) ? 0 : (ShiftMask | LockMask); + + // add entries for this keycode + entry.keycode = static_cast(minKeycode + i); + for (int j = 0; j < numKeysyms; ++j) { + entry.keyMask = (j == 0) ? 0 : ShiftMask; + m_keycodeMap.insert(std::make_pair(keysyms[i * + keysymsPerKeycode + j], entry)); + } + } + + // clean up + XFree(keysyms); +} + +void CXWindowsSecondaryScreen::updateModifierMap( + Display* display) +{ + // get modifier map from server + XModifierKeymap* keymap = XGetModifierMapping(display); + + // initialize + m_modifierMask = 0; + m_toggleModifierMask = 0; + m_numLockMask = 0; + m_capsLockMask = 0; + m_keysPerModifier = keymap->max_keypermod; + m_modifierToKeycode.clear(); + m_modifierToKeycode.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_modifierToKeycode[i * m_keysPerModifier + j] = keycode; + + // save in keycode to modifier + m_keycodeToModifier.insert(std::make_pair(keycode, i)); + + // modifier is enabled if keycode isn't 0 + if (keycode != 0) + 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 num/caps-lock + if (keysym == XK_Num_Lock) + m_numLockMask |= bit; + if (keysym == XK_Caps_Lock) + m_capsLockMask |= bit; + } + } + } + + XFreeModifiermap(keymap); +} + +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; + } +} diff --git a/client/CXWindowsSecondaryScreen.h b/client/CXWindowsSecondaryScreen.h index 11586f26..5597dfe7 100644 --- a/client/CXWindowsSecondaryScreen.h +++ b/client/CXWindowsSecondaryScreen.h @@ -3,6 +3,7 @@ #include "CXWindowsScreen.h" #include "ISecondaryScreen.h" +#include class CXWindowsSecondaryScreen : public CXWindowsScreen, public ISecondaryScreen { public: @@ -35,13 +36,63 @@ class CXWindowsSecondaryScreen : public CXWindowsScreen, public ISecondaryScreen virtual void onCloseDisplay(); private: + struct KeyCodeMask { + public: + KeyCode keycode; + unsigned int keyMask; + unsigned int keyMaskMask; + }; + typedef std::pair Keystroke; + typedef std::vector Keystrokes; + typedef std::vector KeyCodes; + typedef std::map KeyCodeMap; + typedef std::map ModifierMap; + void leaveNoLock(Display*); - KeyCode mapKey(KeyID, KeyModifierMask) const; unsigned int mapButton(ButtonID button) const; + unsigned int mapKey(Keystrokes&, KeyCode&, KeyID, + KeyModifierMask, Bool press) const; + bool findKeyCode(KeyCode&, unsigned int&, + KeyID id, unsigned int) const; + unsigned int maskToX(KeyModifierMask) const; + + void updateKeys(Display* display); + void updateKeycodeMap(Display* display); + void updateModifiers(Display* display); + void updateModifierMap(Display* display); + static bool isToggleKeysym(KeySym); + private: CClient* m_client; Window m_window; + + // set entries indicate keys that are pressed. indexed by keycode. + bool m_keys[256]; + + // current active modifiers (X key masks) + unsigned int m_mask; + + // maps key IDs to X keycodes and the X modifier key mask needed + // to generate the right keysym + KeyCodeMap m_keycodeMap; + + // the modifiers that have keys bound to them + unsigned int m_modifierMask; + + // set bits indicate modifiers that toggle (e.g. caps-lock) + unsigned int m_toggleModifierMask; + + // masks that indicate which modifier bits are num-lock and caps-lock + unsigned int m_numLockMask; + unsigned int m_capsLockMask; + + // map X modifier key indices to the key codes bound to them + unsigned int m_keysPerModifier; + KeyCodes m_modifierToKeycode; + + // maps keycodes to modifier indices + ModifierMap m_keycodeToModifier; }; #endif diff --git a/server/CXWindowsPrimaryScreen.cpp b/server/CXWindowsPrimaryScreen.cpp index 2401e9f4..cd8d736a 100644 --- a/server/CXWindowsPrimaryScreen.cpp +++ b/server/CXWindowsPrimaryScreen.cpp @@ -4,6 +4,7 @@ #include "CLog.h" #include #include +#include // // CXWindowsPrimaryScreen @@ -40,10 +41,17 @@ void CXWindowsPrimaryScreen::run() break; } + case MappingNotify: { + // keyboard mapping changed + CDisplayLock display(this); + XRefreshKeyboardMapping(&xevent.xmapping); + break; + } + case KeyPress: { log((CLOG_DEBUG "event: KeyPress code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); const KeyModifierMask mask = mapModifier(xevent.xkey.state); - const KeyID key = mapKey(xevent.xkey.keycode, mask); + const KeyID key = mapKey(&xevent.xkey); if (key != kKeyNone) { m_server->onKeyDown(key, mask); } @@ -55,7 +63,7 @@ void CXWindowsPrimaryScreen::run() case KeyRelease: { log((CLOG_DEBUG "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); const KeyModifierMask mask = mapModifier(xevent.xkey.state); - const KeyID key = mapKey(xevent.xkey.keycode, mask); + const KeyID key = mapKey(&xevent.xkey); if (key != kKeyNone) { m_server->onKeyUp(key, mask); } @@ -406,33 +414,31 @@ KeyModifierMask CXWindowsPrimaryScreen::mapModifier( { // FIXME -- should be configurable KeyModifierMask mask = 0; - if (state & 1) + if (state & ShiftMask) mask |= KeyModifierShift; - if (state & 2) + if (state & LockMask) mask |= KeyModifierCapsLock; - if (state & 4) + if (state & ControlMask) mask |= KeyModifierControl; - if (state & 8) + if (state & Mod1Mask) mask |= KeyModifierAlt; - if (state & 16) + if (state & Mod2Mask) mask |= KeyModifierNumLock; - if (state & 32) + if (state & Mod4Mask) mask |= KeyModifierMeta; - if (state & 128) + if (state & Mod5Mask) mask |= KeyModifierScrollLock; return mask; } -KeyID CXWindowsPrimaryScreen::mapKey( - KeyCode keycode, KeyModifierMask mask) const +KeyID CXWindowsPrimaryScreen::mapKey(XKeyEvent* event) const { - int index; - if (mask & KeyModifierShift) - index = 1; - else - index = 0; + KeySym keysym; + char dummy[1]; + CDisplayLock display(this); - return static_cast(XKeycodeToKeysym(display, keycode, index)); + XLookupString(event, dummy, 0, &keysym, NULL); + return static_cast(keysym); } ButtonID CXWindowsPrimaryScreen::mapButton( diff --git a/server/CXWindowsPrimaryScreen.h b/server/CXWindowsPrimaryScreen.h index 74f7456d..a0a1b9c8 100644 --- a/server/CXWindowsPrimaryScreen.h +++ b/server/CXWindowsPrimaryScreen.h @@ -36,7 +36,7 @@ class CXWindowsPrimaryScreen : public CXWindowsScreen, public IPrimaryScreen { SInt32 xAbsolute, SInt32 yAbsolute); KeyModifierMask mapModifier(unsigned int state) const; - KeyID mapKey(KeyCode, KeyModifierMask) const; + KeyID mapKey(XKeyEvent*) const; ButtonID mapButton(unsigned int button) const; private: diff --git a/synergy/KeyTypes.h b/synergy/KeyTypes.h index ec070e3f..f9ca8241 100644 --- a/synergy/KeyTypes.h +++ b/synergy/KeyTypes.h @@ -6,7 +6,7 @@ // type to hold a key identifier typedef UInt32 KeyID; -// type to hold bitmask of key modifiers (i.e. shift keys) +// type to hold bitmask of key modifiers (e.g. shift keys) typedef UInt32 KeyModifierMask; // key codes