/* * 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 "CSecondaryScreen.h" #include "IScreen.h" #include "CLock.h" #include "CThread.h" #include "CLog.h" // // CSecondaryScreen // CSecondaryScreen::CSecondaryScreen() : m_remoteReady(false), m_active(false), m_toggleKeys(0), m_screenSaverSync(true) { // do nothing } CSecondaryScreen::~CSecondaryScreen() { // do nothing } void CSecondaryScreen::mainLoop() { // change our priority CThread::getCurrentThread().setPriority(-14); // run event loop try { LOG((CLOG_DEBUG "entering event loop")); onPreMainLoop(); getScreen()->mainLoop(); onPostMainLoop(); LOG((CLOG_DEBUG "exiting event loop")); } catch (...) { onPostMainLoop(); LOG((CLOG_DEBUG "exiting event loop")); throw; } } void CSecondaryScreen::exitMainLoop() { getScreen()->exitMainLoop(); } void CSecondaryScreen::open() { try { // subclass hook onPreOpen(); // open the screen getScreen()->open(); // create and prepare our window. pretend we're active so // we don't try to show our window until later. { CLock lock(&m_mutex); assert(m_active == false); m_active = true; } createWindow(); { CLock lock(&m_mutex); m_active = false; } // subclass hook onPostOpen(); // reset options resetOptions(); } catch (...) { close(); throw; } } void CSecondaryScreen::close() { onPreClose(); destroyWindow(); getScreen()->close(); onPostClose(); } void CSecondaryScreen::remoteControl() { // assume primary has all clipboards for (ClipboardID id = 0; id < kClipboardEnd; ++id) { grabClipboard(id); } // update keyboard state { CLock lock(&m_mutex); updateKeys(); } // now remote ready. fake being active for call to leave(). bool screenSaverSync; { CLock lock(&m_mutex); m_remoteReady = true; m_active = true; // copy screen saver synchronization state screenSaverSync = m_screenSaverSync; } // disable the screen saver if synchronization is enabled if (screenSaverSync) { getScreen()->openScreensaver(false); } // hide the cursor leave(); } void CSecondaryScreen::localControl() { getScreen()->closeScreensaver(); // not remote ready anymore CLock lock(&m_mutex); m_remoteReady = false; } void CSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) { CLock lock(&m_mutex); assert(m_active == false); LOG((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); sync(); // now active m_active = true; // subclass hook onPreEnter(); // update our keyboard state to reflect the local state updateKeys(); // toggle modifiers that don't match the desired state and // remember previous toggle key state. m_toggleKeys = m_mask; setToggleState(mask); // warp to requested location fakeMouseMove(x, y); // show mouse hideWindow(); // subclass hook onPostEnter(); } void CSecondaryScreen::leave() { LOG((CLOG_INFO "leaving screen")); CLock lock(&m_mutex); assert(m_active == true); sync(); // subclass hook onPreLeave(); // restore toggle key state setToggleState(m_toggleKeys); // hide mouse SInt32 x, y; getScreen()->getCursorCenter(x, y); showWindow(x, y); // subclass hook onPostLeave(); // not active anymore m_active = false; // make sure our idea of clipboard ownership is correct getScreen()->checkClipboards(); } void CSecondaryScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) { getScreen()->setClipboard(id, clipboard); } void CSecondaryScreen::grabClipboard(ClipboardID id) { getScreen()->setClipboard(id, NULL); } void CSecondaryScreen::screensaver(bool activate) { // get screen saver synchronization flag bool screenSaverSync; { CLock lock(&m_mutex); screenSaverSync = m_screenSaverSync; } // activate/deactivation screen saver iff synchronization enabled if (screenSaverSync) { getScreen()->screensaver(activate); } } CSecondaryScreen::SysKeyID CSecondaryScreen::getUnhanded(SysKeyID) const { // no key represents both left and right sides of any key return 0; } CSecondaryScreen::SysKeyID CSecondaryScreen::getOtherHanded(SysKeyID) const { // no key represents both left and right sides of any key return 0; } bool CSecondaryScreen::synthesizeCtrlAltDel(EKeyAction) { // pass keys through unchanged return false; } void CSecondaryScreen::sync() const { // do nothing } void CSecondaryScreen::flush() { // do nothing } void CSecondaryScreen::doKeystrokes(const Keystrokes& keys, SInt32 count) { // do nothing if no keys or no repeats if (count < 1 || keys.empty()) { return; } // generate key events LOG((CLOG_DEBUG2 "keystrokes:")); 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) { LOG((CLOG_DEBUG2 " %d %s repeat", k->m_sysKeyID, k->m_press ? "down" : "up")); fakeKeyEvent(k->m_sysKeyID, k->m_press); } } // 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 LOG((CLOG_DEBUG2 " %d %s", k->m_sysKeyID, k->m_press ? "down" : "up")); fakeKeyEvent(k->m_sysKeyID, k->m_press); // next key ++k; } } flush(); } void CSecondaryScreen::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) { CLock lock(&m_mutex); sync(); // check for ctrl+alt+del emulation if (key == kKeyDelete && (mask & (KeyModifierControl | KeyModifierAlt)) == (KeyModifierControl | KeyModifierAlt)) { LOG((CLOG_DEBUG "emulating ctrl+alt+del press")); if (synthesizeCtrlAltDel(kPress)) { return; } } // get the sequence of keys to simulate key press and the final // modifier state. Keystrokes keys; SysKeyID sysKeyID; m_mask = mapKey(keys, sysKeyID, key, m_mask, mask, kPress); if (keys.empty()) { // do nothing if there are no associated keys (i.e. lookup failed) return; } sysKeyID &= 0xffu; // generate key events doKeystrokes(keys, 1); // do not record button down if button or system key is 0 (invalid) if (button != 0 && sysKeyID != 0) { // note that key is now down SysKeyID unhandedSysKeyID = getUnhanded(sysKeyID); m_serverKeyMap[button] = sysKeyID; m_keys[sysKeyID] |= kDown; m_fakeKeys[sysKeyID] |= kDown; if (unhandedSysKeyID != 0) { m_keys[unhandedSysKeyID] |= kDown; m_fakeKeys[unhandedSysKeyID] |= kDown; } } } void CSecondaryScreen::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button) { CLock lock(&m_mutex); sync(); // 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. Keystrokes keys; SysKeyID sysKeyID; m_mask = mapKey(keys, sysKeyID, key, m_mask, mask, kRepeat); if (keys.empty()) { return; } sysKeyID &= 0xffu; // if this key shouldn't auto-repeat then ignore if (!isAutoRepeating(sysKeyID)) { return; } // 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 (sysKeyID != index->second) { // replace key up with previous key id 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_sysKeyID & 0xffu) == sysKeyID) { index2->m_sysKeyID = index->second; break; } } // note that old key is now up m_keys[index->second] &= ~kDown; m_fakeKeys[index->second] &= ~kDown; // map server key to new key index->second = sysKeyID; // note that new key is now down m_keys[index->second] |= kDown; m_fakeKeys[index->second] |= kDown; } // generate key events doKeystrokes(keys, count); } void CSecondaryScreen::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) { CLock lock(&m_mutex); sync(); // 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; } SysKeyID sysKeyID = index->second; // check for ctrl+alt+del emulation if (key == kKeyDelete && (mask & (KeyModifierControl | KeyModifierAlt)) == (KeyModifierControl | KeyModifierAlt)) { LOG((CLOG_DEBUG "emulating ctrl+alt+del release")); if (synthesizeCtrlAltDel(kRelease)) { return; } } // get the sequence of keys to simulate key release Keystrokes keys; Keystroke keystroke; keystroke.m_sysKeyID = sysKeyID; keystroke.m_press = false; keystroke.m_repeat = false; keys.push_back(keystroke); // generate key events doKeystrokes(keys, 1); // note that key is now up SysKeyID unhandedSysKeyID = getUnhanded(sysKeyID); m_serverKeyMap.erase(index); m_keys[sysKeyID] &= ~kDown; m_fakeKeys[sysKeyID] &= ~kDown; if (unhandedSysKeyID != 0) { SysKeyID otherHandedSysKeyID = getOtherHanded(sysKeyID); if ((m_keys[otherHandedSysKeyID] & kDown) == 0) { m_keys[unhandedSysKeyID] &= ~kDown; m_fakeKeys[unhandedSysKeyID] &= ~kDown; } } // get the new modifier state mask = getModifierKeyMask(sysKeyID); if (mask != 0) { // key is a modifier key if ((mask & (KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock)) != 0) { // modifier is a toggle m_mask ^= mask; } else if (!isModifierActive(sysKeyID)) { // all keys for this modifier are released m_mask &= ~mask; } } } void CSecondaryScreen::mouseDown(ButtonID button) { CLock lock(&m_mutex); sync(); fakeMouseButton(button, true); flush(); } void CSecondaryScreen::mouseUp(ButtonID button) { CLock lock(&m_mutex); sync(); fakeMouseButton(button, false); flush(); } void CSecondaryScreen::mouseMove(SInt32 x, SInt32 y) { CLock lock(&m_mutex); sync(); fakeMouseMove(x, y); flush(); } void CSecondaryScreen::mouseWheel(SInt32 delta) { CLock lock(&m_mutex); sync(); fakeMouseWheel(delta); flush(); } void CSecondaryScreen::setToggleState(KeyModifierMask mask) { // toggle modifiers that don't match the desired state KeyModifierMask different = (m_mask ^ mask); if ((different & KeyModifierCapsLock) != 0) { toggleKey(kKeyCapsLock, KeyModifierCapsLock); } if ((different & KeyModifierNumLock) != 0) { toggleKey(kKeyNumLock, KeyModifierNumLock); } if ((different & KeyModifierScrollLock) != 0) { toggleKey(kKeyScrollLock, KeyModifierScrollLock); } } void CSecondaryScreen::resetOptions() { // set screen saver synchronization flag and see if we need to // update the screen saver synchronization. reset other options. bool screenSaverSyncOn; { CLock lock(&m_mutex); screenSaverSyncOn = (!m_screenSaverSync && m_remoteReady); m_screenSaverSync = true; m_numLockHalfDuplex = false; m_capsLockHalfDuplex = false; } // update screen saver synchronization if (screenSaverSyncOn) { getScreen()->openScreensaver(false); } } void CSecondaryScreen::setOptions(const COptionsList& options) { // update options bool updateScreenSaverSync = false; bool oldScreenSaverSync; { CLock lock(&m_mutex); oldScreenSaverSync = m_screenSaverSync; for (UInt32 i = 0, n = options.size(); i < n; i += 2) { if (options[i] == kOptionScreenSaverSync) { updateScreenSaverSync = true; m_screenSaverSync = (options[i + 1] != 0); LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off")); } else if (options[i] == kOptionHalfDuplexCapsLock) { m_capsLockHalfDuplex = (options[i + 1] != 0); LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", m_capsLockHalfDuplex ? "on" : "off")); } else if (options[i] == kOptionHalfDuplexNumLock) { m_numLockHalfDuplex = (options[i + 1] != 0); LOG((CLOG_DEBUG1 "half-duplex num-lock %s", m_numLockHalfDuplex ? "on" : "off")); } } if (!m_remoteReady || oldScreenSaverSync == m_screenSaverSync) { updateScreenSaverSync = false; } } // update screen saver synchronization if (updateScreenSaverSync) { if (oldScreenSaverSync) { getScreen()->closeScreensaver(); } else { getScreen()->openScreensaver(false); } } } bool CSecondaryScreen::isActive() const { CLock lock(&m_mutex); return m_active; } void CSecondaryScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const { getScreen()->getClipboard(id, clipboard); } SInt32 CSecondaryScreen::getJumpZoneSize() const { return 0; } void CSecondaryScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { sync(); getScreen()->getShape(x, y, w, h); } void CSecondaryScreen::getCursorPos(SInt32& x, SInt32& y) const { sync(); getScreen()->getCursorPos(x, y); } void CSecondaryScreen::onPreMainLoop() { // do nothing } void CSecondaryScreen::onPostMainLoop() { // do nothing } void CSecondaryScreen::onPreOpen() { // do nothing } void CSecondaryScreen::onPostOpen() { // do nothing } void CSecondaryScreen::onPreClose() { // do nothing } void CSecondaryScreen::onPostClose() { // do nothing } void CSecondaryScreen::onPreEnter() { // do nothing } void CSecondaryScreen::onPostEnter() { // do nothing } void CSecondaryScreen::onPreLeave() { // do nothing } void CSecondaryScreen::onPostLeave() { // do nothing } void CSecondaryScreen::updateKeys() { sync(); // clear key state memset(m_keys, 0, sizeof(m_keys)); memset(m_fakeKeys, 0, sizeof(m_fakeKeys)); // let subclass set m_keys updateKeys(m_keys); // get m_mask from subclass m_mask = getModifiers(); LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask)); } void CSecondaryScreen::releaseKeys() { CLock lock(&m_mutex); sync(); // release keys that we've synthesized a press for and only those // keys. we don't want to synthesize a release on a key the user // is still physically pressing. for (UInt32 i = 1; i < 256; ++i) { if ((m_fakeKeys[i] & kDown) != 0) { fakeKeyEvent(i, false); m_keys[i] &= ~kDown; m_fakeKeys[i] &= ~kDown; } } flush(); } void CSecondaryScreen::toggleKey(KeyID keyID, KeyModifierMask mask) { // get the system key ID for this toggle key ID SysKeyID sysKeyID = getToggleSysKey(keyID); if (sysKeyID == 0) { return; } // toggle the key if (isKeyHalfDuplex(keyID)) { // "half-duplex" toggle fakeKeyEvent(sysKeyID, (m_mask & mask) == 0); } else { // normal toggle fakeKeyEvent(sysKeyID, true); fakeKeyEvent(sysKeyID, false); } flush(); // toggle shadow state m_mask ^= mask; sysKeyID &= 0xffu; m_keys[sysKeyID] ^= kToggled; m_fakeKeys[sysKeyID] ^= kToggled; } bool CSecondaryScreen::isKeyDown(SysKeyID sysKeyID) const { sysKeyID &= 0xffu; return (sysKeyID != 0 && ((m_keys[sysKeyID] & kDown) != 0)); } bool CSecondaryScreen::isKeyToggled(SysKeyID sysKeyID) const { sysKeyID &= 0xffu; return (sysKeyID != 0 && ((m_keys[sysKeyID] & kToggled) != 0)); } bool CSecondaryScreen::isKeyHalfDuplex(KeyID keyID) const { return ((keyID == kKeyCapsLock && m_capsLockHalfDuplex) || (keyID == kKeyNumLock && m_numLockHalfDuplex)); }