diff --git a/client/CClient.cpp b/client/CClient.cpp index 2e278cb7..d1ef18a6 100644 --- a/client/CClient.cpp +++ b/client/CClient.cpp @@ -341,13 +341,14 @@ void CClient::closeSecondaryScreen() void CClient::onEnter() { SInt16 x, y; + UInt16 mask; { CLock lock(&m_mutex); - CProtocolUtil::readf(m_input, kMsgCEnter + 4, &x, &y, &m_seqNum); + CProtocolUtil::readf(m_input, kMsgCEnter + 4, &x, &y, &m_seqNum, &mask); m_active = true; } - log((CLOG_DEBUG1 "recv enter, %d,%d %d", x, y, m_seqNum)); - m_screen->enter(x, y); + log((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, m_seqNum, mask)); + m_screen->enter(x, y, static_cast(mask)); } void CClient::onLeave() diff --git a/client/CMSWindowsSecondaryScreen.cpp b/client/CMSWindowsSecondaryScreen.cpp index e1dc6ab4..6d2fd886 100644 --- a/client/CMSWindowsSecondaryScreen.cpp +++ b/client/CMSWindowsSecondaryScreen.cpp @@ -109,11 +109,12 @@ void CMSWindowsSecondaryScreen::close() m_client = NULL; } -void CMSWindowsSecondaryScreen::enter(SInt32 x, SInt32 y) +void CMSWindowsSecondaryScreen::enter( + SInt32 x, SInt32 y, KeyModifierMask mask) { assert(m_window != NULL); - log((CLOG_INFO "entering screen at %d,%d", x, y)); + log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); // warp to requested location SInt32 w, h; @@ -130,6 +131,17 @@ void CMSWindowsSecondaryScreen::enter(SInt32 x, SInt32 y) // update our keyboard state to reflect the local state updateKeys(); updateModifiers(); + + // toggle modifiers that don't match the desired state + if ((mask & KeyModifierCapsLock) != (m_mask & KeyModifierCapsLock)) { + toggleKey(VK_CAPITAL, KeyModifierCapsLock); + } + if ((mask & KeyModifierNumLock) != (m_mask & KeyModifierNumLock)) { + toggleKey(VK_NUMLOCK, KeyModifierNumLock); + } + if ((mask & KeyModifierScrollLock) != (m_mask & KeyModifierScrollLock)) { + toggleKey(VK_SCROLL, KeyModifierScrollLock); + } } void CMSWindowsSecondaryScreen::leave() @@ -1184,3 +1196,16 @@ void CMSWindowsSecondaryScreen::updateModifiers() if ((m_keys[VK_SCROLL] & 0x01) != 0) m_mask |= KeyModifierScrollLock; } + +void CMSWindowsSecondaryScreen::toggleKey( + UINT virtualKey, KeyModifierMask mask) +{ + // send key events to simulate a press and release + const UINT code = MapVirtualKey(virtualKey, 0); + keybd_event(virtualKey, code, 0, 0); + keybd_event(virtualKey, code, KEYEVENTF_KEYUP, 0); + + // toggle shadow state + m_mask ^= mask; + m_keys[virtualKey] ^= 0x01; +} diff --git a/client/CMSWindowsSecondaryScreen.h b/client/CMSWindowsSecondaryScreen.h index dbd9b5a8..56235c65 100644 --- a/client/CMSWindowsSecondaryScreen.h +++ b/client/CMSWindowsSecondaryScreen.h @@ -16,7 +16,8 @@ public: virtual void stop(); virtual void open(CClient*); virtual void close(); - virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute); + virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute, + KeyModifierMask mask); virtual void leave(); virtual void keyDown(KeyID, KeyModifierMask); virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); @@ -48,6 +49,7 @@ private: void updateKeys(); void updateModifiers(); + void toggleKey(UINT virtualKey, KeyModifierMask mask); private: CClient* m_client; diff --git a/client/CXWindowsSecondaryScreen.cpp b/client/CXWindowsSecondaryScreen.cpp index d1ea46ab..8ec64d2d 100644 --- a/client/CXWindowsSecondaryScreen.cpp +++ b/client/CXWindowsSecondaryScreen.cpp @@ -177,7 +177,8 @@ void CXWindowsSecondaryScreen::close() m_client = NULL; } -void CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y) +void CXWindowsSecondaryScreen::enter( + SInt32 x, SInt32 y, KeyModifierMask mask) { assert(m_window != None); @@ -193,6 +194,19 @@ void CXWindowsSecondaryScreen::enter(SInt32 x, SInt32 y) // update our keyboard state to reflect the local state updateKeys(display); updateModifiers(display); + + // 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); + } + XSync(display, False); } void CXWindowsSecondaryScreen::leave() @@ -461,9 +475,14 @@ KeyModifierMask CXWindowsSecondaryScreen::mapKey( 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)); + if (bit != m_capsLockMask || !m_capsLockHalfDuplex) { + 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 { undo.push_back(std::make_pair(modifierKey, False)); @@ -476,12 +495,18 @@ KeyModifierMask CXWindowsSecondaryScreen::mapKey( // press/release, otherwise deactivate it with a // release. we must check each keycode for the // modifier if not a toggle. - if (bit & m_toggleModifierMask) { + if ((bit & m_toggleModifierMask) != 0) { 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)); + if (bit != m_capsLockMask || !m_capsLockHalfDuplex) { + 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 { + keys.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) { @@ -601,11 +626,11 @@ unsigned int CXWindowsSecondaryScreen::maskToX( if (inMask & KeyModifierMeta) outMask |= Mod4Mask; if (inMask & KeyModifierCapsLock) - outMask |= LockMask; + outMask |= m_capsLockMask; if (inMask & KeyModifierNumLock) - outMask |= Mod2Mask; + outMask |= m_numLockMask; if (inMask & KeyModifierScrollLock) - outMask |= Mod5Mask; + outMask |= m_scrollLockMask; return outMask; } @@ -712,6 +737,7 @@ void CXWindowsSecondaryScreen::updateModifierMap( m_toggleModifierMask = 0; m_numLockMask = 0; m_capsLockMask = 0; + m_scrollLockMask = 0; m_keysPerModifier = keymap->max_keypermod; m_modifierToKeycode.clear(); m_modifierToKeycode.resize(8 * m_keysPerModifier); @@ -738,10 +764,15 @@ void CXWindowsSecondaryScreen::updateModifierMap( m_toggleModifierMask |= bit; // note num/caps-lock - if (keysym == XK_Num_Lock) + if (keysym == XK_Num_Lock) { m_numLockMask |= bit; - if (keysym == XK_Caps_Lock) + } + else if (keysym == XK_Caps_Lock) { m_capsLockMask |= bit; + } + else if (keysym == XK_Scroll_Lock) { + m_scrollLockMask |= bit; + } } } } @@ -749,6 +780,31 @@ void CXWindowsSecondaryScreen::updateModifierMap( 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; + KeyCode keycode = index->second.keycode; + + // toggle the key + if (keysym == XK_Caps_Lock && m_capsLockHalfDuplex) { + // "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) { diff --git a/client/CXWindowsSecondaryScreen.h b/client/CXWindowsSecondaryScreen.h index 66fe2c55..15388442 100644 --- a/client/CXWindowsSecondaryScreen.h +++ b/client/CXWindowsSecondaryScreen.h @@ -15,7 +15,8 @@ public: virtual void stop(); virtual void open(CClient*); virtual void close(); - virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute); + virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute, + KeyModifierMask mask); virtual void leave(); virtual void keyDown(KeyID, KeyModifierMask); virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count); @@ -62,6 +63,7 @@ private: void updateKeycodeMap(Display* display); void updateModifiers(Display* display); void updateModifierMap(Display* display); + void toggleKey(Display*, KeySym, unsigned int mask); static bool isToggleKeysym(KeySym); private: @@ -88,9 +90,10 @@ private: // 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 + // masks that indicate which modifier bits are for toggle keys unsigned int m_numLockMask; unsigned int m_capsLockMask; + unsigned int m_scrollLockMask; // map X modifier key indices to the key codes bound to them unsigned int m_keysPerModifier; diff --git a/server/CMSWindowsPrimaryScreen.cpp b/server/CMSWindowsPrimaryScreen.cpp index 9c4f3169..51196271 100644 --- a/server/CMSWindowsPrimaryScreen.cpp +++ b/server/CMSWindowsPrimaryScreen.cpp @@ -252,6 +252,18 @@ void CMSWindowsPrimaryScreen::getClipboard( CClipboard::copy(dst, &src); } +KeyModifierMask CXWindowsPrimaryScreen::getToggleMask() const +{ + KeyModifierMask mask; + if ((m_keys[VK_CAPITAL] & 0x01) != 0) + mask |= KeyModifierCapsLock; + if ((m_keys[VK_NUMLOCK] & 0x01) != 0) + mask |= KeyModifierNumLock; + if ((m_keys[VK_SCROLL] & 0x01) != 0) + mask |= KeyModifierScrollLock; + return mask; +} + #include "resource.h" // FIXME void CMSWindowsPrimaryScreen::onOpenDisplay() diff --git a/server/CMSWindowsPrimaryScreen.h b/server/CMSWindowsPrimaryScreen.h index 9e19a4b5..f24d51a2 100644 --- a/server/CMSWindowsPrimaryScreen.h +++ b/server/CMSWindowsPrimaryScreen.h @@ -26,6 +26,7 @@ public: virtual void getSize(SInt32* width, SInt32* height) const; virtual SInt32 getJumpZoneSize() const; virtual void getClipboard(ClipboardID, IClipboard*) const; + virtual KeyModifierMask getToggleMask() const; protected: // CMSWindowsScreen overrides diff --git a/server/CServer.cpp b/server/CServer.cpp index 4e82f043..aaea5f20 100644 --- a/server/CServer.cpp +++ b/server/CServer.cpp @@ -537,7 +537,8 @@ void CServer::switchScreen(CScreenInfo* dst, m_primary->enter(x, y); } else { - m_active->m_protocol->sendEnter(x, y, m_seqNum); + m_active->m_protocol->sendEnter(x, y, m_seqNum, + m_primary->getToggleMask()); } // send the clipboard data to new active screen diff --git a/server/CServerProtocol.h b/server/CServerProtocol.h index d2cb2d80..c64fa6cf 100644 --- a/server/CServerProtocol.h +++ b/server/CServerProtocol.h @@ -31,7 +31,8 @@ public: virtual void run() = 0; virtual void queryInfo() = 0; virtual void sendClose() = 0; - virtual void sendEnter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum) = 0; + virtual void sendEnter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask) = 0; virtual void sendLeave() = 0; virtual void sendClipboard(ClipboardID, const CString&) = 0; virtual void sendGrabClipboard(ClipboardID) = 0; diff --git a/server/CServerProtocol1_0.cpp b/server/CServerProtocol1_0.cpp index 33159fc2..7e9a9802 100644 --- a/server/CServerProtocol1_0.cpp +++ b/server/CServerProtocol1_0.cpp @@ -91,10 +91,12 @@ void CServerProtocol1_0::sendClose() } void CServerProtocol1_0::sendEnter( - SInt32 xAbs, SInt32 yAbs, UInt32 seqNum) + SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask) { - log((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d", getClient().c_str(), xAbs, yAbs, seqNum)); - CProtocolUtil::writef(getOutputStream(), kMsgCEnter, xAbs, yAbs, seqNum); + log((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getClient().c_str(), xAbs, yAbs, seqNum, mask)); + CProtocolUtil::writef(getOutputStream(), kMsgCEnter, + xAbs, yAbs, seqNum, mask); } void CServerProtocol1_0::sendLeave() diff --git a/server/CServerProtocol1_0.h b/server/CServerProtocol1_0.h index f992bec8..e7fbcc58 100644 --- a/server/CServerProtocol1_0.h +++ b/server/CServerProtocol1_0.h @@ -16,7 +16,8 @@ public: virtual void run(); virtual void queryInfo(); virtual void sendClose(); - virtual void sendEnter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum); + virtual void sendEnter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask); virtual void sendLeave(); virtual void sendClipboard(ClipboardID, const CString&); virtual void sendGrabClipboard(ClipboardID); diff --git a/server/CXWindowsPrimaryScreen.cpp b/server/CXWindowsPrimaryScreen.cpp index bafcbd02..bff3596b 100644 --- a/server/CXWindowsPrimaryScreen.cpp +++ b/server/CXWindowsPrimaryScreen.cpp @@ -47,6 +47,7 @@ void CXWindowsPrimaryScreen::run() // keyboard mapping changed CDisplayLock display(this); XRefreshKeyboardMapping(&xevent.xmapping); + updateModifierMap(display); break; } @@ -219,6 +220,12 @@ void CXWindowsPrimaryScreen::open(CServer* server) // FIXME -- may have to get these from some database m_capsLockHalfDuplex = false; // m_capsLockHalfDuplex = true; + + // update key state + { + CDisplayLock display(this); + updateModifierMap(display); + } } void CXWindowsPrimaryScreen::close() @@ -365,6 +372,31 @@ void CXWindowsPrimaryScreen::getClipboard( getDisplayClipboard(id, clipboard, m_window, getCurrentTime(m_window)); } +KeyModifierMask CXWindowsPrimaryScreen::getToggleMask() const +{ + CDisplayLock display(this); + + // query the pointer to get the keyboard state + // FIXME -- is there a better way to do this? + Window root, window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (!XQueryPointer(display, m_window, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) + return 0; + + // convert to KeyModifierMask + KeyModifierMask mask; + if (state & m_numLockMask) + mask |= KeyModifierNumLock; + if (state & m_capsLockMask) + mask |= KeyModifierCapsLock; + if (state & m_scrollLockMask) + mask |= KeyModifierScrollLock; + + return mask; +} + void CXWindowsPrimaryScreen::onOpenDisplay() { assert(m_window == None); @@ -483,3 +515,38 @@ ButtonID CXWindowsPrimaryScreen::mapButton( else return kButtonNone; } + +void CXWindowsPrimaryScreen::updateModifierMap( + Display* display) +{ + // get modifier map from server + XModifierKeymap* keymap = XGetModifierMapping(display); + + // initialize + m_numLockMask = 0; + m_capsLockMask = 0; + m_scrollLockMask = 0; + + // set keycodes and masks + for (unsigned int i = 0; i < 8; ++i) { + const unsigned int bit = (1 << i); + for (int j = 0; j < keymap->max_keypermod; ++j) { + KeyCode keycode = keymap->modifiermap[i * + keymap->max_keypermod + j]; + + // note toggle modifier bits + const KeySym keysym = XKeycodeToKeysym(display, keycode, 0); + if (keysym == XK_Num_Lock) { + m_numLockMask |= bit; + } + else if (keysym == XK_Caps_Lock) { + m_capsLockMask |= bit; + } + else if (keysym == XK_Scroll_Lock) { + m_scrollLockMask |= bit; + } + } + } + + XFreeModifiermap(keymap); +} diff --git a/server/CXWindowsPrimaryScreen.h b/server/CXWindowsPrimaryScreen.h index ba1b4584..5df90544 100644 --- a/server/CXWindowsPrimaryScreen.h +++ b/server/CXWindowsPrimaryScreen.h @@ -24,6 +24,7 @@ public: virtual void getSize(SInt32* width, SInt32* height) const; virtual SInt32 getJumpZoneSize() const; virtual void getClipboard(ClipboardID, IClipboard*) const; + virtual KeyModifierMask getToggleMask() const; protected: // CXWindowsScreen overrides @@ -40,12 +41,21 @@ private: KeyID mapKey(XKeyEvent*) const; ButtonID mapButton(unsigned int button) const; + void updateModifierMap(Display* display); + private: CServer* m_server; bool m_active; Window m_window; + // note if caps lock key toggles on up/down (false) or on + // transition (true) bool m_capsLockHalfDuplex; + + // masks that indicate which modifier bits are for toggle keys + unsigned int m_numLockMask; + unsigned int m_capsLockMask; + unsigned int m_scrollLockMask; }; #endif diff --git a/synergy/IPrimaryScreen.h b/synergy/IPrimaryScreen.h index 198c917a..64c44bec 100644 --- a/synergy/IPrimaryScreen.h +++ b/synergy/IPrimaryScreen.h @@ -79,6 +79,11 @@ public: // and should avoid setting the clipboard object if the screen's // clipboard hasn't changed. virtual void getClipboard(ClipboardID, IClipboard*) const = 0; + + // get the primary screen's current toggle modifier key state. + // the returned mask should have the corresponding bit set for + // each toggle key that is active. + virtual KeyModifierMask getToggleMask() const = 0; }; #endif diff --git a/synergy/ISecondaryScreen.h b/synergy/ISecondaryScreen.h index 11752c2b..c7f1ba3a 100644 --- a/synergy/ISecondaryScreen.h +++ b/synergy/ISecondaryScreen.h @@ -34,7 +34,8 @@ public: // called when the user navigates to the secondary screen. warp // the cursor to the given coordinates and unhide it. prepare to // simulate input events. - virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute) = 0; + virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute, + KeyModifierMask mask) = 0; // called when the user navigates off the secondary screen. clean // up input event simulation and hide the cursor. diff --git a/synergy/IServerProtocol.h b/synergy/IServerProtocol.h index 1bf4a9df..c8d40f2a 100644 --- a/synergy/IServerProtocol.h +++ b/synergy/IServerProtocol.h @@ -24,7 +24,8 @@ public: // send various messages to client virtual void sendClose() = 0; - virtual void sendEnter(SInt32 xAbs, SInt32 yAbs, UInt32 seqNum) = 0; + virtual void sendEnter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask) = 0; virtual void sendLeave() = 0; virtual void sendClipboard(ClipboardID, const CString&) = 0; virtual void sendGrabClipboard(ClipboardID) = 0; diff --git a/synergy/ProtocolTypes.h b/synergy/ProtocolTypes.h index f9b5edf7..13588df9 100644 --- a/synergy/ProtocolTypes.h +++ b/synergy/ProtocolTypes.h @@ -24,8 +24,11 @@ static const char kMsgCClose[] = "CBYE"; // entering screen at screen position $1 = x, $2 = y. x,y are // absolute screen coordinates. $3 = sequence number, which is // used to order messages between screens. the secondary screen -// must return this number with some messages. -static const char kMsgCEnter[] = "CINN%2i%2i%4i"; +// must return this number with some messages. $4 = modifier key +// mask. this will have bits set for each toggle modifier key +// that is activated on entry to the screen. the secondary screen +// should adjust its toggle modifiers to reflect that state. +static const char kMsgCEnter[] = "CINN%2i%2i%4i%2i"; // leave screen: primary -> secondary // leaving screen. the secondary screen should send clipboard