mirror of
https://github.com/debauchee/barrier.git
synced 2024-12-28 13:43:37 +03:00
8d99fd2511
This new design is simpler. For keyboard support, clients need only implement 4 virtual methods on a class derived from CKeyState and one trivial method in the class derived from CPlatformScreen, which is now the superclass of platform screens instead of IPlatformScreen. Keyboard methods have been removed from IPlatformScreen, IPrimaryScreen and ISecondaryScreen. Also, all keyboard state tracking is now in exactly one place (the CKeyState subclass) rather than in CScreen, the platform screen, and the key mapper. Still need to convert Win32.
533 lines
12 KiB
C++
533 lines
12 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2004 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 "CKeyState.h"
|
|
#include "IEventQueue.h"
|
|
#include "CLog.h"
|
|
#include <string.h>
|
|
|
|
//
|
|
// CKeyState
|
|
//
|
|
|
|
CKeyState::CKeyState() :
|
|
m_halfDuplex(0),
|
|
m_mask(0)
|
|
{
|
|
memset(&m_keys, 0, sizeof(m_keys));
|
|
memset(&m_serverKeyMap, 0, sizeof(m_serverKeyMap));
|
|
memset(&m_keyToMask, 0, sizeof(m_keyToMask));
|
|
}
|
|
|
|
CKeyState::~CKeyState()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void
|
|
CKeyState::setKeyDown(KeyButton button, bool down)
|
|
{
|
|
button &= kButtonMask;
|
|
if (button != 0) {
|
|
if (down) {
|
|
m_keys[button] |= kDown;
|
|
}
|
|
else {
|
|
m_keys[button] &= ~kDown;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CKeyState::setToggled(KeyModifierMask modifier)
|
|
{
|
|
if (isToggle(modifier)) {
|
|
const KeyButtons& buttons = m_maskToKeys[getIndexForModifier(modifier)];
|
|
for (KeyButtons::const_iterator j = buttons.begin();
|
|
j != buttons.end(); ++j) {
|
|
m_keys[(*j) & kButtonMask] |= kToggled;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CKeyState::sendKeyEvent(
|
|
void* target, bool press, bool isAutoRepeat,
|
|
KeyID key, KeyModifierMask mask,
|
|
SInt32 count, KeyButton button)
|
|
{
|
|
if (isHalfDuplex(m_keyToMask[button])) {
|
|
if (isAutoRepeat) {
|
|
// ignore auto-repeat on half-duplex keys
|
|
}
|
|
else {
|
|
EVENTQUEUE->addEvent(CEvent(getKeyDownEvent(), target,
|
|
CKeyInfo::alloc(key, mask, button, 1)));
|
|
EVENTQUEUE->addEvent(CEvent(getKeyUpEvent(), target,
|
|
CKeyInfo::alloc(key, mask, button, 1)));
|
|
}
|
|
}
|
|
else {
|
|
if (isAutoRepeat) {
|
|
EVENTQUEUE->addEvent(CEvent(getKeyRepeatEvent(), target,
|
|
CKeyInfo::alloc(key, mask, button, count)));
|
|
}
|
|
else if (press) {
|
|
EVENTQUEUE->addEvent(CEvent(getKeyDownEvent(), target,
|
|
CKeyInfo::alloc(key, mask, button, 1)));
|
|
}
|
|
else {
|
|
EVENTQUEUE->addEvent(CEvent(getKeyUpEvent(), target,
|
|
CKeyInfo::alloc(key, mask, button, 1)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CKeyState::updateKeys()
|
|
{
|
|
static const KeyModifierMask s_masks[] = {
|
|
KeyModifierShift,
|
|
KeyModifierControl,
|
|
KeyModifierAlt,
|
|
KeyModifierMeta,
|
|
KeyModifierSuper,
|
|
KeyModifierModeSwitch,
|
|
KeyModifierCapsLock,
|
|
KeyModifierNumLock,
|
|
KeyModifierScrollLock
|
|
};
|
|
|
|
// reset our state
|
|
memset(&m_keys, 0, sizeof(m_keys));
|
|
memset(&m_serverKeyMap, 0, sizeof(m_serverKeyMap));
|
|
memset(&m_keyToMask, 0, sizeof(m_keyToMask));
|
|
for (UInt32 i = 0; i < sizeof(m_maskToKeys)/sizeof(m_maskToKeys[0]); ++i) {
|
|
m_maskToKeys[i].clear();
|
|
}
|
|
|
|
// let subclass set the state
|
|
doUpdateKeys();
|
|
|
|
// figure out the active modifiers
|
|
m_mask = 0;
|
|
for (UInt32 i = 0; i < sizeof(s_masks) / sizeof(s_masks[0]); ++i) {
|
|
if (isModifierActive(s_masks[i])) {
|
|
m_mask |= s_masks[i];
|
|
}
|
|
}
|
|
LOG((CLOG_DEBUG2 "modifiers on update: 0x%04x", m_mask));
|
|
}
|
|
|
|
void
|
|
CKeyState::setHalfDuplexMask(KeyModifierMask mask)
|
|
{
|
|
m_halfDuplex = mask & (KeyModifierCapsLock |
|
|
KeyModifierNumLock |
|
|
KeyModifierScrollLock);
|
|
}
|
|
|
|
void
|
|
CKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton button)
|
|
{
|
|
// get the sequence of keys to simulate key press and the final
|
|
// modifier state.
|
|
Keystrokes keys;
|
|
KeyButton localID = (mapKey(keys, id, mask, false) & kButtonMask);
|
|
if (keys.empty()) {
|
|
// do nothing if there are no associated keys
|
|
LOG((CLOG_DEBUG2 "cannot map key 0x%08x", id));
|
|
return;
|
|
}
|
|
|
|
// generate key events
|
|
fakeKeyEvents(keys, 1);
|
|
|
|
// note that key is down
|
|
updateKeyState(button & kButtonMask, localID, true);
|
|
}
|
|
|
|
void
|
|
CKeyState::fakeKeyRepeat(
|
|
KeyID id, KeyModifierMask mask,
|
|
SInt32 count, KeyButton button)
|
|
{
|
|
button &= kButtonMask;
|
|
|
|
// if we haven't seen this button go down then ignore it
|
|
KeyButton oldLocalID = m_serverKeyMap[button];
|
|
if (oldLocalID == 0) {
|
|
return;
|
|
}
|
|
|
|
// get the sequence of keys to simulate key repeat and the final
|
|
// modifier state.
|
|
Keystrokes keys;
|
|
KeyButton localID = (mapKey(keys, id, mask, true) & kButtonMask);
|
|
if (localID == 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.
|
|
if (localID != oldLocalID) {
|
|
// replace key up with previous key id but leave key down
|
|
// alone so it uses the new keycode.
|
|
for (Keystrokes::iterator index = keys.begin();
|
|
index != keys.end(); ++index) {
|
|
if (index->m_key == localID) {
|
|
index->m_key = oldLocalID;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// note that old key is now up
|
|
m_keys[oldLocalID] &= ~kDown;
|
|
|
|
// map server key to new key
|
|
m_serverKeyMap[button] = localID;
|
|
|
|
// note that new key is now down
|
|
m_keys[localID] |= kDown;
|
|
}
|
|
|
|
// generate key events
|
|
fakeKeyEvents(keys, count);
|
|
}
|
|
|
|
void
|
|
CKeyState::fakeKeyUp(KeyButton button)
|
|
{
|
|
// if we haven't seen this button go down then ignore it
|
|
KeyButton localID = m_serverKeyMap[button & kButtonMask];
|
|
if (localID == 0) {
|
|
return;
|
|
}
|
|
|
|
// get the sequence of keys to simulate key release
|
|
Keystrokes keys;
|
|
Keystroke keystroke;
|
|
keystroke.m_key = localID;
|
|
keystroke.m_press = false;
|
|
keystroke.m_repeat = false;
|
|
keys.push_back(keystroke);
|
|
|
|
// generate key events
|
|
fakeKeyEvents(keys, 1);
|
|
|
|
// note that key is now up
|
|
updateKeyState(button, localID, false);
|
|
}
|
|
|
|
void
|
|
CKeyState::fakeToggle(KeyModifierMask modifier)
|
|
{
|
|
const KeyButtons& buttons = m_maskToKeys[getIndexForModifier(modifier)];
|
|
if (buttons.empty() || !isToggle(modifier)) {
|
|
return;
|
|
}
|
|
KeyButton button = buttons[0];
|
|
|
|
// get the sequence of keys to simulate key toggle
|
|
Keystrokes keys;
|
|
Keystroke keystroke;
|
|
keystroke.m_key = button;
|
|
keystroke.m_press = true;
|
|
keystroke.m_repeat = false;
|
|
keys.push_back(keystroke);
|
|
keystroke.m_press = false;
|
|
keys.push_back(keystroke);
|
|
|
|
// generate key events
|
|
fakeKeyEvents(keys, 1);
|
|
|
|
// note the toggle
|
|
m_keys[button] ^= kToggled;
|
|
m_mask ^= modifier;
|
|
}
|
|
|
|
bool
|
|
CKeyState::isKeyDown(KeyButton button) const
|
|
{
|
|
return ((m_keys[button & kButtonMask] & kDown) != 0);
|
|
}
|
|
|
|
KeyModifierMask
|
|
CKeyState::getActiveModifiers() const
|
|
{
|
|
return m_mask;
|
|
}
|
|
|
|
void
|
|
CKeyState::addModifier(KeyModifierMask modifier, const KeyButtons& buttons)
|
|
{
|
|
// the mask must not be zero
|
|
assert(modifier != 0);
|
|
|
|
// the mask must have exactly one high bit
|
|
assert((modifier & (modifier - 1)) == 0);
|
|
|
|
for (KeyButtons::const_iterator j = buttons.begin();
|
|
j != buttons.end(); ++j) {
|
|
KeyButton button = static_cast<KeyButton>(((*j) & kButtonMask));
|
|
if (button != 0) {
|
|
m_keyToMask[button] = modifier;
|
|
}
|
|
}
|
|
|
|
// index keys by mask
|
|
m_maskToKeys[getIndexForModifier(modifier)] = buttons;
|
|
}
|
|
|
|
bool
|
|
CKeyState::mapModifier(Keystrokes& keys, Keystrokes& undo,
|
|
KeyModifierMask mask, bool desireActive) const
|
|
{
|
|
// look up modifier
|
|
const KeyButtons& buttons = m_maskToKeys[getIndexForModifier(mask)];
|
|
if (buttons.empty()) {
|
|
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 = buttons[0];
|
|
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 = buttons[0];
|
|
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 = buttons.begin();
|
|
j != buttons.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;
|
|
}
|
|
|
|
bool
|
|
CKeyState::isToggle(KeyModifierMask mask) const
|
|
{
|
|
return (mask == KeyModifierCapsLock ||
|
|
mask == KeyModifierNumLock ||
|
|
mask == KeyModifierScrollLock);
|
|
}
|
|
|
|
bool
|
|
CKeyState::isHalfDuplex(KeyModifierMask mask) const
|
|
{
|
|
return ((mask & m_halfDuplex) != 0);
|
|
}
|
|
|
|
bool
|
|
CKeyState::isModifierActive(KeyModifierMask mask) const
|
|
{
|
|
const KeyButtons& buttons = m_maskToKeys[getIndexForModifier(mask)];
|
|
KeyButtons::const_iterator j = buttons.begin();
|
|
if (isToggle(mask)) {
|
|
// modifier is a toggle
|
|
if ((m_keys[*j] & kToggled) != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
// modifier is not a toggle
|
|
for (; j != buttons.end(); ++j) {
|
|
if ((m_keys[*j] & kDown) != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UInt32
|
|
CKeyState::getIndexForModifier(KeyModifierMask mask) const
|
|
{
|
|
switch (mask) {
|
|
case KeyModifierShift:
|
|
return 0;
|
|
|
|
case KeyModifierControl:
|
|
return 1;
|
|
|
|
case KeyModifierAlt:
|
|
return 2;
|
|
|
|
case KeyModifierMeta:
|
|
return 3;
|
|
|
|
case KeyModifierSuper:
|
|
return 4;
|
|
|
|
case KeyModifierModeSwitch:
|
|
return 5;
|
|
|
|
case KeyModifierCapsLock:
|
|
return 6;
|
|
|
|
case KeyModifierNumLock:
|
|
return 7;
|
|
|
|
case KeyModifierScrollLock:
|
|
return 8;
|
|
|
|
default:
|
|
assert(0 && "invalid modifier mask");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
CKeyState::fakeKeyEvents(const Keystrokes& keys, UInt32 count)
|
|
{
|
|
// do nothing if no keys or no repeats
|
|
if (count == 0 || 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;
|
|
while (count-- > 0) {
|
|
// 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
|
|
CKeyState::fakeKeyEvent(KeyButton button, bool press, bool isAutoRepeat)
|
|
{
|
|
// half-duplex keys are special. we ignore releases and convert
|
|
// a press when the toggle is active to a release.
|
|
KeyModifierMask mask = m_keyToMask[button];
|
|
if (isHalfDuplex(mask)) {
|
|
if (isAutoRepeat || !press) {
|
|
return;
|
|
}
|
|
if (isModifierActive(mask)) {
|
|
press = false;
|
|
}
|
|
}
|
|
|
|
// send key event
|
|
LOG((CLOG_DEBUG2 " %d %s%s", button, press ? "down" : "up", isAutoRepeat ? " repeat" : ""));
|
|
doFakeKeyEvent(button, press, isAutoRepeat);
|
|
}
|
|
|
|
void
|
|
CKeyState::updateKeyState(KeyButton serverID, KeyButton localID, bool press)
|
|
{
|
|
// ignore bogus keys
|
|
if (serverID == 0 || localID == 0) {
|
|
return;
|
|
}
|
|
|
|
// update key state. state doesn't change when auto-repeating.
|
|
if (press) {
|
|
m_serverKeyMap[serverID] = localID;
|
|
m_keys[localID] |= kDown;
|
|
}
|
|
else {
|
|
m_serverKeyMap[serverID] = 0;
|
|
m_keys[localID] &= ~kDown;
|
|
}
|
|
|
|
// update modifier state
|
|
KeyModifierMask mask = m_keyToMask[localID];
|
|
if (mask != 0) {
|
|
if (isToggle(mask)) {
|
|
m_keys[localID] ^= kToggled;
|
|
m_mask ^= mask;
|
|
|
|
// never report half-duplex keys as down
|
|
if (isHalfDuplex(mask)) {
|
|
m_keys[localID] &= ~kDown;
|
|
}
|
|
}
|
|
else {
|
|
if (press) {
|
|
m_mask |= mask;
|
|
}
|
|
else if (!isModifierActive(mask)) {
|
|
m_mask &= ~mask;
|
|
}
|
|
}
|
|
LOG((CLOG_DEBUG2 "new mask: 0x%04x", m_mask));
|
|
}
|
|
}
|