Files
barrier/lib/synergy/CSecondaryScreen.cpp
crs 221628fd84 Checkpoint refactoring. CSecondaryScreen now does the work common
across platform secondary screens.  X11 screen was compiled and
tested but not the win23 screen.  Will next change inheritance
hierarchy.
2003-07-26 13:41:41 +00:00

782 lines
16 KiB
C++

/*
* 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));
}