diff --git a/lib/platform/CXWindowsPrimaryScreen.cpp b/lib/platform/CXWindowsPrimaryScreen.cpp index e3538cef..360bea85 100644 --- a/lib/platform/CXWindowsPrimaryScreen.cpp +++ b/lib/platform/CXWindowsPrimaryScreen.cpp @@ -42,7 +42,8 @@ CXWindowsPrimaryScreen::CXWindowsPrimaryScreen( m_receiver(primaryReceiver), m_window(None), m_im(NULL), - m_ic(NULL) + m_ic(NULL), + m_lastKeycode(0) { m_screen = new CXWindowsScreen(receiver, this); } @@ -223,8 +224,28 @@ CXWindowsPrimaryScreen::onEvent(CEvent* event) XEvent& xevent = event->m_event; // let input methods try to handle event first - if (m_ic != NULL && XFilterEvent(&xevent, None)) { - return true; + if (m_ic != NULL) { + // XFilterEvent() may eat the event and generate a new KeyPress + // event with a keycode of 0 because there isn't an actual key + // associated with the keysym. but the KeyRelease may pass + // through XFilterEvent() and keep its keycode. this means + // there's a mismatch between KeyPress and KeyRelease keycodes. + // since we use the keycode on the client to detect when a key + // is released this won't do. so we remember the keycode on + // the most recent KeyPress (and clear it on a matching + // KeyRelease) so we have a keycode for a synthesized KeyPress. + if (xevent.type == KeyPress && xevent.xkey.keycode != 0) { + m_lastKeycode = xevent.xkey.keycode; + } + else if (xevent.type == KeyRelease && + xevent.xkey.keycode == m_lastKeycode) { + m_lastKeycode = 0; + } + + // now filter the event + if (XFilterEvent(&xevent, None)) { + return true; + } } // handle event @@ -257,16 +278,23 @@ CXWindowsPrimaryScreen::onEvent(CEvent* event) key = kKeyDelete; } + // get which button. see call to XFilterEvent() above + // for more info. + KeyCode keycode = xevent.xkey.keycode; + if (keycode == 0) { + keycode = m_lastKeycode; + } + // handle key m_receiver->onKeyDown(key, mask, - static_cast(xevent.xkey.keycode)); + static_cast(keycode)); if (key == kKeyCapsLock && m_capsLockHalfDuplex) { m_receiver->onKeyUp(key, mask | KeyModifierCapsLock, - static_cast(xevent.xkey.keycode)); + static_cast(keycode)); } else if (key == kKeyNumLock && m_numLockHalfDuplex) { m_receiver->onKeyUp(key, mask | KeyModifierNumLock, - static_cast(xevent.xkey.keycode)); + static_cast(keycode)); } } } @@ -509,6 +537,9 @@ CXWindowsPrimaryScreen::onPostOpen() XWindowAttributes attr; XGetWindowAttributes(display, m_window, &attr); XSelectInput(display, m_window, attr.your_event_mask | mask); + + // no previous keycode + m_lastKeycode = 0; } void @@ -523,6 +554,7 @@ CXWindowsPrimaryScreen::onPreClose() XCloseIM(m_im); m_im = NULL; } + m_lastKeycode = 0; } void diff --git a/lib/platform/CXWindowsPrimaryScreen.h b/lib/platform/CXWindowsPrimaryScreen.h index 828f73e6..2343ca07 100644 --- a/lib/platform/CXWindowsPrimaryScreen.h +++ b/lib/platform/CXWindowsPrimaryScreen.h @@ -117,8 +117,10 @@ private: // position of center pixel of screen SInt32 m_xCenter, m_yCenter; - XIM m_im; - XIC m_ic; + // input method stuff + XIM m_im; + XIC m_ic; + KeyCode m_lastKeycode; }; #endif diff --git a/lib/platform/CXWindowsSecondaryScreen.cpp b/lib/platform/CXWindowsSecondaryScreen.cpp index 734a58fc..4609068e 100644 --- a/lib/platform/CXWindowsSecondaryScreen.cpp +++ b/lib/platform/CXWindowsSecondaryScreen.cpp @@ -126,9 +126,6 @@ void CXWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) { - Keystrokes keys; - KeyCode keycode; - // check for ctrl+alt+del emulation if (key == kKeyDelete && (mask & (KeyModifierControl | KeyModifierAlt)) == @@ -139,29 +136,30 @@ CXWindowsSecondaryScreen::keyDown(KeyID key, // 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); - // note that key is now down - m_keys[keycode] = true; - m_fakeKeys[keycode] = true; - - // note which server key generated this key - m_serverKeyMap[button] = keycode; + // 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; + } } void CXWindowsSecondaryScreen::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button) { - Keystrokes keys; - KeyCode keycode; - // if we haven't seen this button go down then ignore it ServerKeyMap::iterator index = m_serverKeyMap.find(button); if (index == m_serverKeyMap.end()) { @@ -170,6 +168,8 @@ CXWindowsSecondaryScreen::keyRepeat(KeyID key, // 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; @@ -180,15 +180,20 @@ CXWindowsSecondaryScreen::keyRepeat(KeyID key, return; } - // if we've seen this button (and we should have) then make sure - // we release the same key we pressed when we saw it. - if (index != m_serverKeyMap.end() && keycode != index->second) { + // 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. + // 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 == index->second) { + if (index2->m_keycode == keycode) { index2->m_keycode = index->second; break; } @@ -214,15 +219,12 @@ void CXWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) { - Keystrokes keys; - KeyCode keycode; - // 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 = index->second; + KeyCode keycode = index->second; // check for ctrl+alt+del emulation if (key == kKeyDelete && @@ -232,6 +234,9 @@ CXWindowsSecondaryScreen::keyUp(KeyID key, // 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); @@ -241,13 +246,9 @@ CXWindowsSecondaryScreen::keyUp(KeyID key, doKeystrokes(keys, 1); // note that key is now up + m_serverKeyMap.erase(index); m_keys[keycode] = false; m_fakeKeys[keycode] = false; - - // remove server key from map - if (index != m_serverKeyMap.end()) { - m_serverKeyMap.erase(index); - } } void @@ -798,6 +799,7 @@ CXWindowsSecondaryScreen::mapKeyRelease(Keystrokes& keys, KeyCode keycode) const 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 {