barrier/lib/synergy/CScreen.cpp
2004-02-28 12:19:49 +00:00

935 lines
19 KiB
C++

/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2003 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 "CScreen.h"
#include "IPlatformScreen.h"
#include "ProtocolTypes.h"
#include "CLog.h"
#include "IEventQueue.h"
//
// CScreen
//
CScreen::CScreen(IPlatformScreen* platformScreen) :
m_screen(platformScreen),
m_isPrimary(platformScreen->isPrimary()),
m_enabled(false),
m_entered(m_isPrimary),
m_screenSaverSync(true),
m_toggleKeys(0)
{
assert(m_screen != NULL);
// open screen
m_screen->setKeyState(this);
// reset options
resetOptions();
LOG((CLOG_DEBUG "opened display"));
}
CScreen::~CScreen()
{
if (m_enabled) {
disable();
}
assert(!m_enabled);
assert(m_entered == m_isPrimary);
delete m_screen;
LOG((CLOG_DEBUG "closed display"));
}
void
CScreen::enable()
{
assert(!m_enabled);
m_screen->enable();
if (m_isPrimary) {
enablePrimary();
}
else {
enableSecondary();
}
// note activation
m_enabled = true;
}
void
CScreen::disable()
{
assert(m_enabled);
if (!m_isPrimary && m_entered) {
leave();
}
else if (m_isPrimary && !m_entered) {
enter();
}
m_screen->disable();
if (m_isPrimary) {
disablePrimary();
}
else {
disableSecondary();
}
// note deactivation
m_enabled = false;
}
void
CScreen::enter()
{
assert(m_entered == false);
LOG((CLOG_INFO "entering screen"));
// now on screen
m_entered = true;
if (m_isPrimary) {
enterPrimary();
}
else {
enterSecondary();
}
m_screen->enter();
}
bool
CScreen::leave()
{
assert(m_entered == true);
LOG((CLOG_INFO "leaving screen"));
if (!m_screen->leave()) {
return false;
}
if (m_isPrimary) {
leavePrimary();
}
else {
leaveSecondary();
}
// make sure our idea of clipboard ownership is correct
m_screen->checkClipboards();
// now not on screen
m_entered = false;
return true;
}
void
CScreen::reconfigure(UInt32 activeSides)
{
assert(m_isPrimary);
m_screen->reconfigure(activeSides);
}
void
CScreen::warpCursor(SInt32 x, SInt32 y)
{
assert(m_isPrimary);
m_screen->warpCursor(x, y);
}
void
CScreen::setClipboard(ClipboardID id, const IClipboard* clipboard)
{
m_screen->setClipboard(id, clipboard);
}
void
CScreen::grabClipboard(ClipboardID id)
{
m_screen->setClipboard(id, NULL);
}
void
CScreen::screensaver(bool activate)
{
if (!m_isPrimary) {
// activate/deactivation screen saver iff synchronization enabled
if (m_screenSaverSync) {
m_screen->screensaver(activate);
}
}
}
void
CScreen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button)
{
assert(!m_isPrimary);
// check for ctrl+alt+del emulation
if (id == kKeyDelete &&
(mask & (KeyModifierControl | KeyModifierAlt)) ==
(KeyModifierControl | KeyModifierAlt)) {
LOG((CLOG_DEBUG "emulating ctrl+alt+del press"));
if (m_screen->fakeCtrlAltDel()) {
return;
}
}
// get the sequence of keys to simulate key press and the final
// modifier state.
Keystrokes keys;
KeyButton key = m_screen->mapKey(keys, *this, id, mask, false);
if (keys.empty()) {
// do nothing if there are no associated keys
LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id));
return;
}
// generate key events
doKeystrokes(keys, 1);
// note that key is down
updateKeyState(button, key, true);
}
void
CScreen::keyRepeat(KeyID id,
KeyModifierMask mask, SInt32 count, KeyButton button)
{
assert(!m_isPrimary);
// 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;
KeyButton key = m_screen->mapKey(keys, *this, id, mask, true);
if (key == 0) {
LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id));
return;
}
if (keys.empty()) {
// do nothing if there are no associated keys
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.
key &= 0xffu;
if (key != 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_key & 0xffu) == key) {
index2->m_key = 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 = key;
// note that new key is now down
m_keys[index->second] |= kDown;
m_fakeKeys[index->second] |= kDown;
}
// generate key events
doKeystrokes(keys, count);
}
void
CScreen::keyUp(KeyID, KeyModifierMask, KeyButton button)
{
assert(!m_isPrimary);
// 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;
}
KeyButton key = index->second;
// get the sequence of keys to simulate key release
Keystrokes keys;
Keystroke keystroke;
keystroke.m_key = key;
keystroke.m_press = false;
keystroke.m_repeat = false;
keys.push_back(keystroke);
// generate key events
doKeystrokes(keys, 1);
// note that key is now up
updateKeyState(button, key, false);
}
void
CScreen::mouseDown(ButtonID button)
{
assert(!m_isPrimary);
m_screen->fakeMouseButton(button, true);
}
void
CScreen::mouseUp(ButtonID button)
{
assert(!m_isPrimary);
m_screen->fakeMouseButton(button, false);
}
void
CScreen::mouseMove(SInt32 x, SInt32 y)
{
assert(!m_isPrimary);
m_screen->fakeMouseMove(x, y);
}
void
CScreen::mouseWheel(SInt32 delta)
{
assert(!m_isPrimary);
m_screen->fakeMouseWheel(delta);
}
void
CScreen::resetOptions()
{
// reset options
m_numLockHalfDuplex = false;
m_capsLockHalfDuplex = false;
// if screen saver synchronization was off then turn it on since
// that's the default option state.
if (!m_screenSaverSync) {
m_screenSaverSync = true;
if (!m_isPrimary) {
m_screen->openScreensaver(false);
}
}
// let screen handle its own options
m_screen->resetOptions();
}
void
CScreen::setOptions(const COptionsList& options)
{
// update options
bool oldScreenSaverSync = m_screenSaverSync;
for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
if (options[i] == kOptionScreenSaverSync) {
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"));
}
}
// update screen saver synchronization
if (!m_isPrimary && oldScreenSaverSync != m_screenSaverSync) {
if (m_screenSaverSync) {
m_screen->openScreensaver(false);
}
else {
m_screen->closeScreensaver();
}
}
// let screen handle its own options
m_screen->setOptions(options);
}
void
CScreen::setSequenceNumber(UInt32 seqNum)
{
m_screen->setSequenceNumber(seqNum);
}
bool
CScreen::isOnScreen() const
{
return m_entered;
}
bool
CScreen::isLockedToScreen() const
{
// check for pressed mouse buttons
if (m_screen->isAnyMouseButtonDown()) {
LOG((CLOG_DEBUG "locked by mouse button"));
return true;
}
// we don't keep primary key state up to date so get the
// current state.
const_cast<CScreen*>(this)->updateKeys();
// check for scroll lock toggled on
if (isModifierActive(KeyModifierScrollLock)) {
LOG((CLOG_DEBUG "locked by scroll lock"));
return true;
}
// check for any pressed key
KeyButton key = isAnyKeyDown();
if (key != 0) {
LOG((CLOG_DEBUG "locked by %s", m_screen->getKeyName(key)));
return true;
}
// not locked
return false;
}
SInt32
CScreen::getJumpZoneSize() const
{
if (!m_isPrimary) {
return 0;
}
else {
return m_screen->getJumpZoneSize();
}
}
void
CScreen::getCursorCenter(SInt32& x, SInt32& y) const
{
m_screen->getCursorCenter(x, y);
}
void*
CScreen::getEventTarget() const
{
return m_screen;
}
bool
CScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const
{
return m_screen->getClipboard(id, clipboard);
}
void
CScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
m_screen->getShape(x, y, w, h);
}
void
CScreen::getCursorPos(SInt32& x, SInt32& y) const
{
m_screen->getCursorPos(x, y);
}
void
CScreen::updateKeys()
{
// clear key state
memset(m_keys, 0, sizeof(m_keys));
memset(m_fakeKeys, 0, sizeof(m_fakeKeys));
m_maskToKeys.clear();
m_keyToMask.clear();
// let subclass set m_keys
m_screen->updateKeys();
// figure out active modifier mask
m_mask = getModifierMask();
LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask));
}
void
CScreen::releaseKeys()
{
// 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 (KeyButton i = 1; i < 256; ++i) {
if ((m_fakeKeys[i] & kDown) != 0) {
fakeKeyEvent(i, false, false);
m_keys[i] &= ~kDown;
m_fakeKeys[i] &= ~kDown;
}
}
}
void
CScreen::setKeyDown(KeyButton key)
{
m_keys[key & 0xffu] |= kDown;
}
void
CScreen::setToggled(KeyModifierMask mask)
{
if (!isToggle(mask)) {
return;
}
MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
if (i == m_maskToKeys.end()) {
return;
}
for (KeyButtons::const_iterator j = i->second.begin();
j != i->second.end(); ++j) {
m_keys[(*j) & 0xffu] |= kToggled;
}
}
void
CScreen::addModifier(KeyModifierMask mask, KeyButtons& keys)
{
// the modifier must have associated keys
if (keys.empty()) {
return;
}
// the mask must not be zero
assert(mask != 0);
// the mask must have exactly one high bit
assert((mask & (mask - 1)) == 0);
// index mask by keycodes
for (KeyButtons::iterator j = keys.begin(); j != keys.end(); ++j) {
// key must be valid
assert(((*j) & 0xffu) != 0);
m_keyToMask[static_cast<KeyButton>((*j) & 0xffu)] = mask;
}
// index keys by mask
m_maskToKeys[mask].swap(keys);
}
void
CScreen::setToggleState(KeyModifierMask mask)
{
// toggle modifiers that don't match the desired state
KeyModifierMask different = (m_mask ^ mask);
if ((different & KeyModifierCapsLock) != 0) {
toggleKey(KeyModifierCapsLock);
}
if ((different & KeyModifierNumLock) != 0) {
toggleKey(KeyModifierNumLock);
}
if ((different & KeyModifierScrollLock) != 0) {
toggleKey(KeyModifierScrollLock);
}
}
KeyButton
CScreen::isAnyKeyDown() const
{
for (UInt32 i = 1; i < 256; ++i) {
if ((m_keys[i] & kDown) != 0) {
return static_cast<KeyButton>(i);
}
}
return 0;
}
bool
CScreen::isKeyDown(KeyButton key) const
{
key &= 0xffu;
return (key != 0 && ((m_keys[key] & kDown) != 0));
}
bool
CScreen::isToggle(KeyModifierMask mask) const
{
static const KeyModifierMask s_toggleMask =
KeyModifierCapsLock | KeyModifierNumLock | KeyModifierScrollLock;
return ((mask & s_toggleMask) != 0);
}
bool
CScreen::isHalfDuplex(KeyModifierMask mask) const
{
return ((mask == KeyModifierCapsLock && m_capsLockHalfDuplex) ||
(mask == KeyModifierNumLock && m_numLockHalfDuplex));
}
bool
CScreen::isModifierActive(KeyModifierMask mask) const
{
MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
if (i == m_maskToKeys.end()) {
return false;
}
KeyButtons::const_iterator j = i->second.begin();
if (isToggle(mask)) {
// modifier is a toggle
if (isKeyToggled(*j)) {
return true;
}
}
else {
// modifier is not a toggle
for (; j != i->second.end(); ++j) {
if (isKeyDown(*j)) {
return true;
}
}
}
return false;
}
KeyModifierMask
CScreen::getActiveModifiers() const
{
if (m_isPrimary) {
// we don't keep primary key state up to date so get the
// current state.
const_cast<CScreen*>(this)->updateKeys();
}
return m_mask;
}
bool
CScreen::mapModifier(Keystrokes& keys, Keystrokes& undo,
KeyModifierMask mask, bool desireActive) const
{
// look up modifier
MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
if (i == m_maskToKeys.end()) {
return false;
}
// ignore if already in desired state
if (isModifierActive(mask) == desireActive) {
return true;
}
// initialize keystroke
Keystroke keystroke;
keystroke.m_repeat = false;
// handle toggles
if (isToggle(mask)) {
keystroke.m_key = i->second.front();
keystroke.m_press = true;
keys.push_back(keystroke);
keystroke.m_press = false;
keys.push_back(keystroke);
keystroke.m_press = false;
undo.push_back(keystroke);
keystroke.m_press = true;
undo.push_back(keystroke);
}
else if (desireActive) {
// press
keystroke.m_key = i->second.front();
keystroke.m_press = true;
keys.push_back(keystroke);
keystroke.m_press = false;
undo.push_back(keystroke);
}
else {
// releasing a modifier is quite different from pressing one.
// when we release a modifier we have to release every keycode that
// is assigned to the modifier since the modifier is active if any
// one of them is down. when we press a modifier we just have to
// press one of those keycodes.
for (KeyButtons::const_iterator j = i->second.begin();
j != i->second.end(); ++j) {
if (isKeyDown(*j)) {
keystroke.m_key = *j;
keystroke.m_press = false;
keys.push_back(keystroke);
keystroke.m_press = true;
undo.push_back(keystroke);
}
}
}
return true;
}
KeyModifierMask
CScreen::getMaskForKey(KeyButton key) const
{
KeyToMask::const_iterator i = m_keyToMask.find(key);
if (i == m_keyToMask.end()) {
return 0;
}
else {
return i->second;
}
}
void
CScreen::enablePrimary()
{
// get notified of screen saver activation/deactivation
m_screen->openScreensaver(true);
// claim screen changed size
EVENTQUEUE->addEvent(CEvent(getShapeChangedEvent(), getEventTarget()));
}
void
CScreen::enableSecondary()
{
// assume primary has all clipboards
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
grabClipboard(id);
}
// disable the screen saver if synchronization is enabled
if (m_screenSaverSync) {
m_screen->openScreensaver(false);
}
}
void
CScreen::disablePrimary()
{
// done with screen saver
m_screen->closeScreensaver();
}
void
CScreen::disableSecondary()
{
// done with screen saver
m_screen->closeScreensaver();
}
void
CScreen::enterPrimary()
{
// do nothing
}
void
CScreen::enterSecondary()
{
// update our keyboard state to reflect the local state
updateKeys();
// remember toggle key state. we'll restore this when we leave.
m_toggleKeys = m_mask;
}
void
CScreen::leavePrimary()
{
// do nothing
}
void
CScreen::leaveSecondary()
{
// release any keys we think are still down
releaseKeys();
// restore toggle key state
setToggleState(m_toggleKeys);
}
KeyModifierMask
CScreen::getModifierMask() const
{
KeyModifierMask mask = 0;
if (isModifierActive(KeyModifierShift)) {
mask |= KeyModifierShift;
}
if (isModifierActive(KeyModifierControl)) {
mask |= KeyModifierControl;
}
if (isModifierActive(KeyModifierAlt)) {
mask |= KeyModifierAlt;
}
if (isModifierActive(KeyModifierMeta)) {
mask |= KeyModifierMeta;
}
if (isModifierActive(KeyModifierSuper)) {
mask |= KeyModifierSuper;
}
if (isModifierActive(KeyModifierModeSwitch)) {
mask |= KeyModifierModeSwitch;
}
if (isModifierActive(KeyModifierNumLock)) {
mask |= KeyModifierNumLock;
}
if (isModifierActive(KeyModifierCapsLock)) {
mask |= KeyModifierCapsLock;
}
if (isModifierActive(KeyModifierScrollLock)) {
mask |= KeyModifierScrollLock;
}
return mask;
}
void
CScreen::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) {
fakeKeyEvent(k->m_key, k->m_press, true);
}
}
// 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
fakeKeyEvent(k->m_key, k->m_press, false);
// next key
++k;
}
}
}
void
CScreen::fakeKeyEvent(KeyButton key, bool press, bool repeat) const
{
// half-duplex keys are special. we ignore releases and convert
// a press when the toggle is active to a release.
KeyModifierMask mask = getMaskForKey(key);
if (isHalfDuplex(mask)) {
if (repeat || !press) {
return;
}
if (isModifierActive(mask)) {
press = false;
}
}
// send key event
LOG((CLOG_DEBUG2 " %d %s%s", key, press ? "down" : "up", repeat ? " repeat" : ""));
m_screen->fakeKeyEvent(key, press);
}
void
CScreen::updateKeyState(KeyButton button, KeyButton key, bool press)
{
// ignore bogus keys
key &= 0xffu;
if (button == 0 || key == 0) {
return;
}
// update shadow state. shadow state doesn't change on auto-repeat.
if (press) {
// key is now down
m_serverKeyMap[button] = key;
m_keys[key] |= kDown;
m_fakeKeys[key] |= kDown;
}
else {
// key is now up
m_serverKeyMap.erase(button);
m_keys[key] &= ~kDown;
m_fakeKeys[key] &= ~kDown;
}
KeyModifierMask mask = getMaskForKey(key);
if (mask != 0) {
// key is a modifier
if (isToggle(mask)) {
// key is a toggle modifier
if (press) {
m_keys[key] ^= kToggled;
m_mask ^= mask;
// if key is half duplex then don't report it as down
if (isHalfDuplex(mask)) {
m_keys[key] &= ~kDown;
m_fakeKeys[key] &= ~kDown;
}
}
}
else {
// key is a normal modifier
if (press) {
m_mask |= mask;
}
else if (!isModifierActive(mask)) {
// no key for modifier is down anymore
m_mask &= ~mask;
}
}
LOG((CLOG_DEBUG2 "new mask: 0x%04x", m_mask));
}
}
void
CScreen::toggleKey(KeyModifierMask mask)
{
// get the system key ID for this toggle key ID
MaskToKeys::const_iterator i = m_maskToKeys.find(mask);
if (i == m_maskToKeys.end()) {
return;
}
KeyButton key = i->second.front();
// toggle the key
fakeKeyEvent(key, true, false);
fakeKeyEvent(key, false, false);
// toggle shadow state
m_mask ^= mask;
key &= 0xffu;
m_keys[key] ^= kToggled;
}
bool
CScreen::isKeyToggled(KeyButton key) const
{
key &= 0xffu;
return (key != 0 && ((m_keys[key] & kToggled) != 0));
}