diff --git a/lib/client/CClient.cpp b/lib/client/CClient.cpp index 7ae42424..82f73cd7 100644 --- a/lib/client/CClient.cpp +++ b/lib/client/CClient.cpp @@ -365,21 +365,22 @@ CClient::setClipboardDirty(ClipboardID, bool) } void -CClient::keyDown(KeyID id, KeyModifierMask mask) +CClient::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) { - m_screen->keyDown(id, mask); + m_screen->keyDown(id, mask, button); } void -CClient::keyRepeat(KeyID id, KeyModifierMask mask, SInt32 count) +CClient::keyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) { - m_screen->keyRepeat(id, mask, count); + m_screen->keyRepeat(id, mask, count, button); } void -CClient::keyUp(KeyID id, KeyModifierMask mask) +CClient::keyUp(KeyID id, KeyModifierMask mask, KeyButton button) { - m_screen->keyUp(id, mask); + m_screen->keyUp(id, mask, button); } void diff --git a/lib/client/CClient.h b/lib/client/CClient.h index 1cdcf3fa..61efddaa 100644 --- a/lib/client/CClient.h +++ b/lib/client/CClient.h @@ -152,9 +152,10 @@ public: virtual void setClipboard(ClipboardID, const CString&); virtual void grabClipboard(ClipboardID); virtual void setClipboardDirty(ClipboardID, bool dirty); - virtual void keyDown(KeyID, KeyModifierMask); - virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); - virtual void keyUp(KeyID, KeyModifierMask); + 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 xAbs, SInt32 yAbs); diff --git a/lib/client/CServerProxy.cpp b/lib/client/CServerProxy.cpp index 6f6690cc..3421bd60 100644 --- a/lib/client/CServerProxy.cpp +++ b/lib/client/CServerProxy.cpp @@ -503,9 +503,10 @@ CServerProxy::keyDown() flushCompressedMouse(); // parse - UInt16 id, mask; - CProtocolUtil::readf(getInputStream(), kMsgDKeyDown + 4, &id, &mask); - LOG((CLOG_DEBUG1 "recv key down id=%d, mask=0x%04x", id, mask)); + UInt16 id, mask, button; + CProtocolUtil::readf(getInputStream(), kMsgDKeyDown + 4, + &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key down id=%d, mask=0x%04x, button=0x%04x", id, mask, button)); // translate KeyID id2 = translateKey(static_cast(id)); @@ -516,7 +517,7 @@ CServerProxy::keyDown() LOG((CLOG_DEBUG1 "key down translated to id=%d, mask=0x%04x", id2, mask2)); // forward - getClient()->keyDown(id2, mask2); + getClient()->keyDown(id2, mask2, button); } void @@ -526,10 +527,10 @@ CServerProxy::keyRepeat() flushCompressedMouse(); // parse - UInt16 id, mask, count; - CProtocolUtil::readf(getInputStream(), - kMsgDKeyRepeat + 4, &id, &mask, &count); - LOG((CLOG_DEBUG1 "recv key repeat id=%d, mask=0x%04x, count=%d", id, mask, count)); + UInt16 id, mask, count, button; + CProtocolUtil::readf(getInputStream(), kMsgDKeyRepeat + 4, + &id, &mask, &count, &button); + LOG((CLOG_DEBUG1 "recv key repeat id=%d, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button)); // translate KeyID id2 = translateKey(static_cast(id)); @@ -540,7 +541,7 @@ CServerProxy::keyRepeat() LOG((CLOG_DEBUG1 "key down translated to id=%d, mask=0x%04x", id2, mask2)); // forward - getClient()->keyRepeat(id2, mask2, count); + getClient()->keyRepeat(id2, mask2, count, button); } void @@ -550,9 +551,9 @@ CServerProxy::keyUp() flushCompressedMouse(); // parse - UInt16 id, mask; - CProtocolUtil::readf(getInputStream(), kMsgDKeyUp + 4, &id, &mask); - LOG((CLOG_DEBUG1 "recv key up id=%d, mask=0x%04x", id, mask)); + UInt16 id, mask, button; + CProtocolUtil::readf(getInputStream(), kMsgDKeyUp + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key up id=%d, mask=0x%04x, button=0x%04x", id, mask, button)); // translate KeyID id2 = translateKey(static_cast(id)); @@ -563,7 +564,7 @@ CServerProxy::keyUp() LOG((CLOG_DEBUG1 "key down translated to id=%d, mask=0x%04x", id2, mask2)); // forward - getClient()->keyUp(id2, mask2); + getClient()->keyUp(id2, mask2, button); } void diff --git a/lib/platform/CMSWindowsPrimaryScreen.cpp b/lib/platform/CMSWindowsPrimaryScreen.cpp index db73e861..bcb2d04c 100644 --- a/lib/platform/CMSWindowsPrimaryScreen.cpp +++ b/lib/platform/CMSWindowsPrimaryScreen.cpp @@ -521,9 +521,11 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) // process as if it were a key up KeyModifierMask mask; + KeyButton button = static_cast( + (lParam & 0x00ff0000u) >> 16); const KeyID key = mapKey(wParam, lParam, &mask); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x", key, mask)); - m_receiver->onKeyUp(key, mask); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_receiver->onKeyUp(key, mask, button); updateKey(wParam, false); } if ((m_keys[VK_RWIN] & 0x80) != 0 && @@ -536,9 +538,11 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) // process as if it were a key up KeyModifierMask mask; + KeyButton button = static_cast( + (lParam & 0x00ff0000u) >> 16); const KeyID key = mapKey(wParam, lParam, &mask); - LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x", key, mask)); - m_receiver->onKeyUp(key, mask); + LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_receiver->onKeyUp(key, mask, button); updateKey(wParam, false); } } @@ -554,18 +558,20 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) if (!ignore()) { KeyModifierMask mask; const KeyID key = mapKey(msg->wParam, msg->lParam, &mask); - if (key != kKeyNone) { + KeyButton button = static_cast( + (msg->lParam & 0x00ff0000u) >> 16); + if (key != kKeyNone && key != kKeyMultiKey) { if ((msg->lParam & 0x80000000) == 0) { // key press const bool wasDown = ((msg->lParam & 0x40000000) != 0); const SInt32 repeat = (SInt32)(msg->lParam & 0xffff); if (repeat >= 2 || wasDown) { - LOG((CLOG_DEBUG1 "event: key repeat key=%d mask=0x%04x count=%d", key, mask, repeat)); - m_receiver->onKeyRepeat(key, mask, repeat); + LOG((CLOG_DEBUG1 "event: key repeat key=%d mask=0x%04x count=%d button=0x%04x", key, mask, repeat, button)); + m_receiver->onKeyRepeat(key, mask, repeat, button); } else { - LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x", key, mask)); - m_receiver->onKeyDown(key, mask); + LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_receiver->onKeyDown(key, mask, button); } // update key state @@ -580,14 +586,14 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) // keys like alt+tab, ctrl+esc, etc. if (m_is95Family && !isModifier(msg->wParam) && (m_keys[msg->wParam] & 0x80) == 0) { - LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x", key, mask)); - m_receiver->onKeyDown(key, mask); + LOG((CLOG_DEBUG1 "event: fake key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_receiver->onKeyDown(key, mask, button); updateKey(msg->wParam, true); } // do key up - LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x", key, mask)); - m_receiver->onKeyUp(key, mask); + LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); + m_receiver->onKeyUp(key, mask, button); // update key state updateKey(msg->wParam, false); @@ -1408,11 +1414,6 @@ CMSWindowsPrimaryScreen::mapKey( return id; } - // check for dead keys - if (MapVirtualKey(vkCode, 2) >= 0x8000) { - return kKeyMultiKey; - } - // save the control state then clear it. ToAscii() maps ctrl+letter // to the corresponding control code and ctrl+backspace to delete. // we don't want that translation so we clear the control modifier @@ -1421,14 +1422,21 @@ CMSWindowsPrimaryScreen::mapKey( BYTE lControl = m_keys[VK_LCONTROL]; BYTE rControl = m_keys[VK_RCONTROL]; BYTE control = m_keys[VK_CONTROL]; + BYTE lMenu = m_keys[VK_LMENU]; + BYTE menu = m_keys[VK_MENU]; if ((mask & KeyModifierModeSwitch) == 0) { m_keys[VK_LCONTROL] = 0; m_keys[VK_RCONTROL] = 0; m_keys[VK_CONTROL] = 0; } + else { + m_keys[VK_LCONTROL] = 0x80; + m_keys[VK_CONTROL] = 0x80; + m_keys[VK_LMENU] = 0x80; + m_keys[VK_MENU] = 0x80; + } // convert to ascii - // FIXME -- support unicode WORD ascii; int result = ToAscii(vkCode, scanCode, m_keys, &ascii, 0); @@ -1436,23 +1444,45 @@ CMSWindowsPrimaryScreen::mapKey( m_keys[VK_LCONTROL] = lControl; m_keys[VK_RCONTROL] = rControl; m_keys[VK_CONTROL] = control; + m_keys[VK_LMENU] = lMenu; + m_keys[VK_MENU] = menu; - // if result is less than zero then it was a dead key. that key - // is remembered by the keyboard which we don't want. remove it - // by calling ToAscii() again with arbitrary arguments. + // if result is less than zero then it was a dead key. leave it + // there. if (result < 0) { - ToAscii(vkCode, scanCode, m_keys, &ascii, 0); return kKeyMultiKey; } // if result is 1 then the key was succesfully converted else if (result == 1) { + if (ascii >= 0x80) { + // character is not really ASCII. instead it's some + // character in the current ANSI code page. try to + // convert that to a Unicode character. if we fail + // then use the single byte character as is. + char src = static_cast(ascii & 0xff); + wchar_t unicode; + if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, + &src, 1, &unicode, 1) > 0) { + return static_cast(unicode); + } + } return static_cast(ascii & 0x00ff); } // if result is 2 then a previous dead key could not be composed. - // put the old dead key back. else if (result == 2) { + // if the two characters are the same and this is a key release + // then this event is the dead key being released. we put the + // dead key back in that case, otherwise we discard both key + // events because we can't compose the character. alternatively + // we could generate key events for both keys. + if (((ascii & 0xff00) >> 8) != (ascii & 0x00ff) || + (info & 0x80000000) == 0) { + // cannot compose key + return kKeyNone; + } + // get the scan code of the dead key and the shift state // required to generate it. vkCode = VkKeyScan(static_cast(ascii & 0x00ff)); diff --git a/lib/platform/CMSWindowsSecondaryScreen.cpp b/lib/platform/CMSWindowsSecondaryScreen.cpp index 16e7fe9b..25db82f3 100644 --- a/lib/platform/CMSWindowsSecondaryScreen.cpp +++ b/lib/platform/CMSWindowsSecondaryScreen.cpp @@ -49,7 +49,8 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() } void -CMSWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask) +CMSWindowsSecondaryScreen::keyDown(KeyID key, + KeyModifierMask mask, KeyButton button) { Keystrokes keys; UINT virtualKey; @@ -89,11 +90,14 @@ CMSWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask) m_fakeKeys[VK_MENU] |= 0x80; break; } + + // note which server key generated this key + m_serverKeyMap[button] = virtualKey; } void CMSWindowsSecondaryScreen::keyRepeat(KeyID key, - KeyModifierMask mask, SInt32 count) + KeyModifierMask mask, SInt32 count, KeyButton button) { Keystrokes keys; UINT virtualKey; @@ -101,6 +105,12 @@ CMSWindowsSecondaryScreen::keyRepeat(KeyID key, 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. m_mask = mapKey(keys, virtualKey, key, mask, kRepeat); @@ -108,12 +118,40 @@ CMSWindowsSecondaryScreen::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. + ServerKeyMap::iterator index = m_serverKeyMap.find(button); + if (index != m_serverKeyMap.end() && 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 == index->second) { + 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 key, KeyModifierMask mask) +CMSWindowsSecondaryScreen::keyUp(KeyID key, + KeyModifierMask mask, KeyButton button) { Keystrokes keys; UINT virtualKey; @@ -121,11 +159,41 @@ CMSWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask) 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 release and the final // modifier state. m_mask = mapKey(keys, virtualKey, key, mask, kRelease); + + // if there are no keys to generate then we should at least generate + // a key release for the key we pressed. if (keys.empty()) { - return; + Keystroke keystroke; + virtualKey = index->second; + keystroke.m_virtualKey = virtualKey; + keystroke.m_press = false; + keystroke.m_repeat = false; + keys.push_back(keystroke); + } + + // 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() && virtualKey != index->second) { + // replace key up with previous virtual key + for (Keystrokes::iterator index2 = keys.begin(); + index2 != keys.end(); ++index2) { + if (index2->m_virtualKey == virtualKey) { + index2->m_virtualKey = index->second; + break; + } + } + + // use old virtual key + virtualKey = index->second; } // generate key events @@ -177,6 +245,11 @@ CMSWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask) } break; } + + // remove server key from map + if (index != m_serverKeyMap.end()) { + m_serverKeyMap.erase(index); + } } void @@ -716,8 +789,20 @@ CMSWindowsSecondaryScreen::mapKey(Keystrokes& keys, UINT& virtualKey, // if not in map then ask system to convert character if (virtualKey == 0) { // translate. return no keys if unknown key. - // FIXME -- handle unicode - TCHAR ascii = static_cast(id & 0x000000ff); + char ascii; + wchar_t unicode = static_cast(id & 0x0000ffff); + BOOL error; + if (WideCharToMultiByte(CP_THREAD_ACP, +#if defined(WC_NO_BEST_FIT_CHARS) + WC_NO_BEST_FIT_CHARS | +#endif + WC_COMPOSITECHECK | + WC_DEFAULTCHAR, + &unicode, 1, + &ascii, 1, NULL, &error) == 0 || error) { + LOG((CLOG_DEBUG2 "character %d not in code page", id)); + return m_mask; + } SHORT vk = VkKeyScan(ascii); if (vk == 0xffff) { LOG((CLOG_DEBUG2 "no virtual key for character %d", id)); diff --git a/lib/platform/CMSWindowsSecondaryScreen.h b/lib/platform/CMSWindowsSecondaryScreen.h index afa26a8c..e60327a4 100644 --- a/lib/platform/CMSWindowsSecondaryScreen.h +++ b/lib/platform/CMSWindowsSecondaryScreen.h @@ -38,9 +38,10 @@ public: virtual ~CMSWindowsSecondaryScreen(); // CSecondaryScreen overrides - virtual void keyDown(KeyID, KeyModifierMask); - virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); - virtual void keyUp(KeyID, KeyModifierMask); + 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); @@ -83,6 +84,7 @@ private: bool m_repeat; }; typedef std::vector Keystrokes; + typedef std::map ServerKeyMap; // open/close desktop (for windows 95/98/me) bool openDesktop(); @@ -123,6 +125,9 @@ private: // current active modifiers KeyModifierMask m_mask; + + // map server key buttons to local virtual keys + ServerKeyMap m_serverKeyMap; }; #endif diff --git a/lib/platform/CXWindowsPrimaryScreen.cpp b/lib/platform/CXWindowsPrimaryScreen.cpp index 54cf8898..b4469cb1 100644 --- a/lib/platform/CXWindowsPrimaryScreen.cpp +++ b/lib/platform/CXWindowsPrimaryScreen.cpp @@ -241,12 +241,15 @@ CXWindowsPrimaryScreen::onEvent(CEvent* event) const KeyModifierMask mask = mapModifier(xevent.xkey.state); const KeyID key = mapKey(&xevent.xkey); if (key != kKeyNone) { - m_receiver->onKeyDown(key, mask); + m_receiver->onKeyDown(key, mask, + static_cast(xevent.xkey.keycode)); if (key == kKeyCapsLock && m_capsLockHalfDuplex) { - m_receiver->onKeyUp(key, mask | KeyModifierCapsLock); + m_receiver->onKeyUp(key, mask | KeyModifierCapsLock, + static_cast(xevent.xkey.keycode)); } else if (key == kKeyNumLock && m_numLockHalfDuplex) { - m_receiver->onKeyUp(key, mask | KeyModifierNumLock); + m_receiver->onKeyUp(key, mask | KeyModifierNumLock, + static_cast(xevent.xkey.keycode)); } } } @@ -280,12 +283,15 @@ CXWindowsPrimaryScreen::onEvent(CEvent* event) // no press event follows so it's a plain release LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); if (key == kKeyCapsLock && m_capsLockHalfDuplex) { - m_receiver->onKeyDown(key, mask); + m_receiver->onKeyDown(key, mask, + static_cast(xevent.xkey.keycode)); } else if (key == kKeyNumLock && m_numLockHalfDuplex) { - m_receiver->onKeyDown(key, mask); + m_receiver->onKeyDown(key, mask, + static_cast(xevent.xkey.keycode)); } - m_receiver->onKeyUp(key, mask); + m_receiver->onKeyUp(key, mask, + static_cast(xevent.xkey.keycode)); } else { // found a press event following so it's a repeat. @@ -293,7 +299,8 @@ CXWindowsPrimaryScreen::onEvent(CEvent* event) // repeats but we'll just send a repeat of 1. // note that we discard the press event. LOG((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", xevent.xkey.keycode, xevent.xkey.state)); - m_receiver->onKeyRepeat(key, mask, 1); + m_receiver->onKeyRepeat(key, mask, 1, + static_cast(xevent.xkey.keycode)); } } } diff --git a/lib/platform/CXWindowsSecondaryScreen.cpp b/lib/platform/CXWindowsSecondaryScreen.cpp index 7a67783c..0fbbc223 100644 --- a/lib/platform/CXWindowsSecondaryScreen.cpp +++ b/lib/platform/CXWindowsSecondaryScreen.cpp @@ -95,7 +95,8 @@ CXWindowsSecondaryScreen::~CXWindowsSecondaryScreen() } void -CXWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask) +CXWindowsSecondaryScreen::keyDown(KeyID key, + KeyModifierMask mask, KeyButton button) { Keystrokes keys; KeyCode keycode; @@ -113,15 +114,24 @@ CXWindowsSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask) // 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; } void CXWindowsSecondaryScreen::keyRepeat(KeyID key, - KeyModifierMask mask, SInt32 count) + 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()) { + return; + } + // get the sequence of keys to simulate key repeat and the final // modifier state. m_mask = mapKey(keys, keycode, key, mask, kRepeat); @@ -129,21 +139,78 @@ 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) { + // 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_keycode == index->second) { + 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) +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; + } + // get the sequence of keys to simulate key release and the final // modifier state. m_mask = mapKey(keys, keycode, key, mask, kRelease); + + // if there are no keys to generate then we should at least generate + // a key release for the key we pressed. if (keys.empty()) { - return; + Keystroke keystroke; + keycode = index->second; + keystroke.m_keycode = keycode; + keystroke.m_press = False; + keystroke.m_repeat = false; + keys.push_back(keystroke); + } + + // 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) { + // replace key up with previous keycode + for (Keystrokes::iterator index2 = keys.begin(); + index2 != keys.end(); ++index2) { + if (index2->m_keycode == keycode) { + index2->m_keycode = index->second; + break; + } + } + + // use old keycode + keycode = index->second; } // generate key events @@ -152,6 +219,11 @@ CXWindowsSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask) // note that key is now up m_keys[keycode] = false; m_fakeKeys[keycode] = false; + + // remove server key from map + if (index != m_serverKeyMap.end()) { + m_serverKeyMap.erase(index); + } } void diff --git a/lib/platform/CXWindowsSecondaryScreen.h b/lib/platform/CXWindowsSecondaryScreen.h index 18530130..d8c6a88d 100644 --- a/lib/platform/CXWindowsSecondaryScreen.h +++ b/lib/platform/CXWindowsSecondaryScreen.h @@ -37,9 +37,10 @@ public: virtual ~CXWindowsSecondaryScreen(); // CSecondaryScreen overrides - virtual void keyDown(KeyID, KeyModifierMask); - virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); - virtual void keyUp(KeyID, KeyModifierMask); + 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); @@ -91,6 +92,7 @@ private: typedef std::map KeyCodeMap; typedef KeyCodeMap::const_iterator KeyCodeIndex; typedef std::map ModifierMap; + typedef std::map ServerKeyMap; unsigned int mapButton(ButtonID button) const; @@ -165,6 +167,9 @@ private: // maps keycodes to modifier indices ModifierMap m_keycodeToModifier; + + // map server key buttons to local keycodes + ServerKeyMap m_serverKeyMap; }; #endif diff --git a/lib/server/CClientProxy.h b/lib/server/CClientProxy.h index 43585001..2ab41c35 100644 --- a/lib/server/CClientProxy.h +++ b/lib/server/CClientProxy.h @@ -68,9 +68,10 @@ public: virtual void setClipboard(ClipboardID, const CString&) = 0; virtual void grabClipboard(ClipboardID) = 0; virtual void setClipboardDirty(ClipboardID, bool) = 0; - virtual void keyDown(KeyID, KeyModifierMask) = 0; - virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count) = 0; - virtual void keyUp(KeyID, KeyModifierMask) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; virtual void mouseDown(ButtonID) = 0; virtual void mouseUp(ButtonID) = 0; virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; diff --git a/lib/server/CClientProxy1_0.cpp b/lib/server/CClientProxy1_0.cpp index be0effca..7a9e734c 100644 --- a/lib/server/CClientProxy1_0.cpp +++ b/lib/server/CClientProxy1_0.cpp @@ -200,24 +200,25 @@ CClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) } void -CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask) +CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) { LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown, key, mask); + CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown1_0, key, mask); } void -CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count) +CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton) { LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat, key, mask, count); + CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat1_0, key, mask, count); } void -CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask) +CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton) { LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); - CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp, key, mask); + CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp1_0, key, mask); } void diff --git a/lib/server/CClientProxy1_0.h b/lib/server/CClientProxy1_0.h index 6725e5be..0a86e1a9 100644 --- a/lib/server/CClientProxy1_0.h +++ b/lib/server/CClientProxy1_0.h @@ -37,9 +37,10 @@ public: virtual void setClipboard(ClipboardID, const CString&); virtual void grabClipboard(ClipboardID); virtual void setClipboardDirty(ClipboardID, bool); - virtual void keyDown(KeyID, KeyModifierMask); - virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); - virtual void keyUp(KeyID, KeyModifierMask); + 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 xAbs, SInt32 yAbs); diff --git a/lib/server/CClientProxy1_1.cpp b/lib/server/CClientProxy1_1.cpp new file mode 100644 index 00000000..a09b27bd --- /dev/null +++ b/lib/server/CClientProxy1_1.cpp @@ -0,0 +1,56 @@ +/* + * 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 "CClientProxy1_1.h" +#include "CProtocolUtil.h" +#include "CLog.h" +#include + +// +// CClientProxy1_1 +// + +CClientProxy1_1::CClientProxy1_1(IServer* server, const CString& name, + IInputStream* input, IOutputStream* output) : + CClientProxy1_0(server, name, input, output) +{ + // do nothing +} + +CClientProxy1_1::~CClientProxy1_1() +{ + // do nothing +} + +void +CClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown, key, mask, button); +} + +void +CClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button)); + CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat, key, mask, count, button); +} + +void +CClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp, key, mask, button); +} diff --git a/lib/server/CClientProxy1_1.h b/lib/server/CClientProxy1_1.h new file mode 100644 index 00000000..dbae783e --- /dev/null +++ b/lib/server/CClientProxy1_1.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef CCLIENTPROXY1_1_H +#define CCLIENTPROXY1_1_H + +#include "CClientProxy1_0.h" + +//! Proxy for client implementing protocol version 1.1 +class CClientProxy1_1 : public CClientProxy1_0 { +public: + CClientProxy1_1(IServer* server, const CString& name, + IInputStream* adoptedInput, + IOutputStream* adoptedOutput); + ~CClientProxy1_1(); + + // IClient overrides + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); +}; + +#endif diff --git a/lib/server/CPrimaryClient.cpp b/lib/server/CPrimaryClient.cpp index 5cd66876..b425a68d 100644 --- a/lib/server/CPrimaryClient.cpp +++ b/lib/server/CPrimaryClient.cpp @@ -201,19 +201,19 @@ CPrimaryClient::setClipboardDirty(ClipboardID id, bool dirty) } void -CPrimaryClient::keyDown(KeyID, KeyModifierMask) +CPrimaryClient::keyDown(KeyID, KeyModifierMask, KeyButton) { // ignore } void -CPrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32) +CPrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton) { // ignore } void -CPrimaryClient::keyUp(KeyID, KeyModifierMask) +CPrimaryClient::keyUp(KeyID, KeyModifierMask, KeyButton) { // ignore } diff --git a/lib/server/CPrimaryClient.h b/lib/server/CPrimaryClient.h index d6c3f7c6..d0202cf1 100644 --- a/lib/server/CPrimaryClient.h +++ b/lib/server/CPrimaryClient.h @@ -107,9 +107,10 @@ public: virtual void setClipboard(ClipboardID, const CString&); virtual void grabClipboard(ClipboardID); virtual void setClipboardDirty(ClipboardID, bool); - virtual void keyDown(KeyID, KeyModifierMask); - virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); - virtual void keyUp(KeyID, KeyModifierMask); + 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 xAbs, SInt32 yAbs); diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index 2756f025..fd687114 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -20,6 +20,7 @@ #include "COutputPacketStream.h" #include "CProtocolUtil.h" #include "CClientProxy1_0.h" +#include "CClientProxy1_1.h" #include "OptionTypes.h" #include "ProtocolTypes.h" #include "XScreen.h" @@ -615,9 +616,9 @@ CServer::onOneShotTimerExpired(UInt32 id) } void -CServer::onKeyDown(KeyID id, KeyModifierMask mask) +CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) { - LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x", id, mask)); + LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); CLock lock(&m_mutex); assert(m_active != NULL); @@ -627,13 +628,13 @@ CServer::onKeyDown(KeyID id, KeyModifierMask mask) } // relay - m_active->keyDown(id, mask); + m_active->keyDown(id, mask, button); } void -CServer::onKeyUp(KeyID id, KeyModifierMask mask) +CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button) { - LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x", id, mask)); + LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); CLock lock(&m_mutex); assert(m_active != NULL); @@ -643,13 +644,14 @@ CServer::onKeyUp(KeyID id, KeyModifierMask mask) } // relay - m_active->keyUp(id, mask); + m_active->keyUp(id, mask, button); } void -CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, SInt32 count) +CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) { - LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d", id, mask, count)); + LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); CLock lock(&m_mutex); assert(m_active != NULL); @@ -660,7 +662,7 @@ CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, SInt32 count) } // relay - m_active->keyRepeat(id, mask, count); + m_active->keyRepeat(id, mask, count, button); } void @@ -1698,18 +1700,7 @@ CServer::handshakeClient(IDataSocket* socket) } // disallow invalid version numbers - if (major < 0 || minor < 0) { - throw XIncompatibleClient(major, minor); - } - - // disallow connection from test versions to release versions - if (major == 0 && kProtocolMajorVersion != 0) { - throw XIncompatibleClient(major, minor); - } - - // hangup (with error) if version isn't supported - if (major > kProtocolMajorVersion || - (major == kProtocolMajorVersion && minor > kProtocolMinorVersion)) { + if (major <= 0 || minor < 0) { throw XIncompatibleClient(major, minor); } @@ -1720,7 +1711,22 @@ CServer::handshakeClient(IDataSocket* socket) // create client proxy for highest version supported by the client LOG((CLOG_DEBUG1 "creating proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); - proxy = new CClientProxy1_0(this, name, input, output); + if (major == 1) { + switch (minor) { + case 0: + proxy = new CClientProxy1_0(this, name, input, output); + break; + + case 1: + proxy = new CClientProxy1_1(this, name, input, output); + break; + } + } + + // hangup (with error) if version isn't supported + if (proxy == NULL) { + throw XIncompatibleClient(major, minor); + } // negotiate // FIXME diff --git a/lib/server/CServer.h b/lib/server/CServer.h index 8e644853..1e69f108 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -187,9 +187,10 @@ public: // IPrimaryScreenReceiver overrides virtual void onScreensaver(bool activated); virtual void onOneShotTimerExpired(UInt32 id); - virtual void onKeyDown(KeyID, KeyModifierMask); - virtual void onKeyUp(KeyID, KeyModifierMask); - virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32 count); + virtual void onKeyDown(KeyID, KeyModifierMask, KeyButton); + virtual void onKeyUp(KeyID, KeyModifierMask, KeyButton); + virtual void onKeyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); virtual void onMouseDown(ButtonID); virtual void onMouseUp(ButtonID); virtual bool onMouseMovePrimary(SInt32 x, SInt32 y); diff --git a/lib/server/Makefile.am b/lib/server/Makefile.am index b1900797..ba6b8415 100644 --- a/lib/server/Makefile.am +++ b/lib/server/Makefile.am @@ -27,6 +27,7 @@ noinst_LIBRARIES = libserver.a libserver_a_SOURCES = \ CClientProxy.cpp \ CClientProxy1_0.cpp \ + CClientProxy1_1.cpp \ CConfig.cpp \ CHTTPServer.cpp \ CPrimaryClient.cpp \ diff --git a/lib/synergy/CSecondaryScreen.h b/lib/synergy/CSecondaryScreen.h index 3ffeba2b..239caf34 100644 --- a/lib/synergy/CSecondaryScreen.h +++ b/lib/synergy/CSecondaryScreen.h @@ -124,23 +124,28 @@ public: //! Notify of key press /*! Synthesize key events to generate a press of key \c id. If possible - match the given modifier mask. + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). */ - virtual void keyDown(KeyID id, KeyModifierMask) = 0; + virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; //! 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) = 0; + virtual void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton) = 0; //! 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) = 0; + virtual void keyUp(KeyID id, KeyModifierMask, KeyButton) = 0; //! Notify of mouse press /*! diff --git a/lib/synergy/IClient.h b/lib/synergy/IClient.h index 18804bce..01ab4411 100644 --- a/lib/synergy/IClient.h +++ b/lib/synergy/IClient.h @@ -103,23 +103,28 @@ public: //! Notify of key press /*! Synthesize key events to generate a press of key \c id. If possible - match the given modifier mask. + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). */ - virtual void keyDown(KeyID id, KeyModifierMask) = 0; + virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; //! 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) = 0; + virtual void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton) = 0; //! 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) = 0; + virtual void keyUp(KeyID id, KeyModifierMask, KeyButton) = 0; //! Notify of mouse press /*! diff --git a/lib/synergy/IPrimaryScreenReceiver.h b/lib/synergy/IPrimaryScreenReceiver.h index ec36f263..44b1beee 100644 --- a/lib/synergy/IPrimaryScreenReceiver.h +++ b/lib/synergy/IPrimaryScreenReceiver.h @@ -43,11 +43,12 @@ public: // call to notify of events. onMouseMovePrimary() returns // true iff the mouse enters a jump zone and jumps. //! Notify of key press - virtual void onKeyDown(KeyID, KeyModifierMask) = 0; + virtual void onKeyDown(KeyID, KeyModifierMask, KeyButton) = 0; //! Notify of key release - virtual void onKeyUp(KeyID, KeyModifierMask) = 0; + virtual void onKeyUp(KeyID, KeyModifierMask, KeyButton) = 0; //! Notify of key repeat - virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32 count) = 0; + virtual void onKeyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; //! Notify of mouse button press virtual void onMouseDown(ButtonID) = 0; //! Notify of mouse button release diff --git a/lib/synergy/KeyTypes.h b/lib/synergy/KeyTypes.h index 21028824..9cef9c71 100644 --- a/lib/synergy/KeyTypes.h +++ b/lib/synergy/KeyTypes.h @@ -19,12 +19,19 @@ //! Key ID /*! -Type to hold a key identifier. The encoding is UTF-32, using +Type to hold a key symbol identifier. The encoding is UTF-32, using U+E000 through U+EFFF for the various control keys (e.g. arrow keys, function keys, modifier keys, etc). */ typedef UInt32 KeyID; +//! Key Code +/*! +Type to hold a physical key identifier. That is, it identifies a +physical key on the keyboard. +*/ +typedef UInt16 KeyButton; + //! Modifier key mask /*! Type to hold a bitmask of key modifiers (e.g. shift keys). diff --git a/lib/synergy/ProtocolTypes.h b/lib/synergy/ProtocolTypes.h index 8f9bd569..539ddaf6 100644 --- a/lib/synergy/ProtocolTypes.h +++ b/lib/synergy/ProtocolTypes.h @@ -18,8 +18,10 @@ #include "BasicTypes.h" // protocol version number +// 1.0: initial protocol +// 1.1: adds KeyCode to key press, release, and repeat static const SInt16 kProtocolMajorVersion = 1; -static const SInt16 kProtocolMinorVersion = 0; +static const SInt16 kProtocolMinorVersion = 1; // default contact port number static const UInt16 kDefaultPort = 24800; @@ -137,16 +139,34 @@ static const char kMsgCInfoAck[] = "CIAK"; // // key pressed: primary -> secondary -// $1 = KeyID, $2 = KeyModifierMask -static const char kMsgDKeyDown[] = "DKDN%2i%2i"; +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +// the KeyButton identifies the physical key on the primary used to +// generate this key. the secondary should note the KeyButton along +// with the physical key it uses to generate the key press. on +// release, the secondary can then use the primary's KeyButton to +// find its corresponding physical key and release it. this is +// necessary because the KeyID on release may not be the KeyID of +// the press. this can happen with combining (dead) keys or if +// the keyboard layouts are not identical and the user releases +// a modifier key before releasing the modified key. +static const char kMsgDKeyDown[] = "DKDN%2i%2i%2i"; + +// key pressed 1.0: same as above but without KeyButton +static const char kMsgDKeyDown1_0[] = "DKDN%2i%2i"; // key auto-repeat: primary -> secondary -// $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats -static const char kMsgDKeyRepeat[] = "DKRP%2i%2i%2i"; +// $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats, $4 = KeyButton +static const char kMsgDKeyRepeat[] = "DKRP%2i%2i%2i%2i"; + +// key auto-repeat 1.0: same as above but without KeyButton +static const char kMsgDKeyRepeat1_0[] = "DKRP%2i%2i%2i"; // key released: primary -> secondary -// $1 = KeyID, $2 = KeyModifierMask -static const char kMsgDKeyUp[] = "DKUP%2i%2i"; +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +static const char kMsgDKeyUp[] = "DKUP%2i%2i%2i"; + +// key released 1.0: same as above but without KeyButton +static const char kMsgDKeyUp1_0[] = "DKUP%2i%2i"; // mouse button pressed: primary -> secondary // $1 = ButtonID