barrier/lib/synergy/CKeyMap.cpp

1331 lines
35 KiB
C++

/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2005 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 "CKeyMap.h"
#include "KeyTypes.h"
#include "CLog.h"
#include <assert.h>
#include <cctype>
#include <cstdlib>
CKeyMap::CNameToKeyMap* CKeyMap::s_nameToKeyMap = NULL;
CKeyMap::CNameToModifierMap* CKeyMap::s_nameToModifierMap = NULL;
CKeyMap::CKeyToNameMap* CKeyMap::s_keyToNameMap = NULL;
CKeyMap::CModifierToNameMap* CKeyMap::s_modifierToNameMap = NULL;
CKeyMap::CKeyMap() :
m_numGroups(0),
m_composeAcrossGroups(false)
{
m_modifierKeyItem.m_id = kKeyNone;
m_modifierKeyItem.m_group = 0;
m_modifierKeyItem.m_button = 0;
m_modifierKeyItem.m_required = 0;
m_modifierKeyItem.m_sensitive = 0;
m_modifierKeyItem.m_generates = 0;
m_modifierKeyItem.m_dead = false;
m_modifierKeyItem.m_lock = false;
m_modifierKeyItem.m_client = 0;
}
CKeyMap::~CKeyMap()
{
// do nothing
}
void
CKeyMap::swap(CKeyMap& x)
{
m_keyIDMap.swap(x.m_keyIDMap);
m_modifierKeys.swap(x.m_modifierKeys);
m_halfDuplex.swap(x.m_halfDuplex);
m_halfDuplexMods.swap(x.m_halfDuplexMods);
SInt32 tmp1 = m_numGroups;
m_numGroups = x.m_numGroups;
x.m_numGroups = tmp1;
bool tmp2 = m_composeAcrossGroups;
m_composeAcrossGroups = x.m_composeAcrossGroups;
x.m_composeAcrossGroups = tmp2;
}
void
CKeyMap::addKeyEntry(const KeyItem& item)
{
// ignore kKeyNone
if (item.m_id == kKeyNone) {
return;
}
// resize number of groups for key
SInt32 numGroups = item.m_group + 1;
if (getNumGroups() > numGroups) {
numGroups = getNumGroups();
}
KeyGroupTable& groupTable = m_keyIDMap[item.m_id];
if (groupTable.size() < static_cast<size_t>(numGroups)) {
groupTable.resize(numGroups);
}
// make a list from the item
KeyItemList items;
items.push_back(item);
// set group and dead key flag on the item
KeyItem& newItem = items.back();
newItem.m_dead = isDeadKey(item.m_id);
// mask the required bits with the sensitive bits
newItem.m_required &= newItem.m_sensitive;
// see if we already have this item; just return if so
KeyEntryList& entries = groupTable[item.m_group];
for (size_t i = 0, n = entries.size(); i < n; ++i) {
if (entries[i].size() == 1 && newItem == entries[i][0]) {
return;
}
}
// add item list
entries.push_back(items);
LOG((CLOG_DEBUG1 "add key: %04x %d %03x %04x (%04x %04x %04x)%s", newItem.m_id, newItem.m_group, newItem.m_button, newItem.m_client, newItem.m_required, newItem.m_sensitive, newItem.m_generates, newItem.m_dead ? " dead" : ""));
}
void
CKeyMap::addKeyAliasEntry(KeyID targetID, SInt32 group,
KeyModifierMask targetRequired,
KeyModifierMask targetSensitive,
KeyID sourceID,
KeyModifierMask sourceRequired,
KeyModifierMask sourceSensitive)
{
// if we can already generate the target as desired then we're done.
if (findCompatibleKey(targetID, group, targetRequired,
targetSensitive) != NULL) {
return;
}
// find a compatible source, preferably in the same group
for (SInt32 gd = 0, n = getNumGroups(); gd < n; ++gd) {
SInt32 eg = getEffectiveGroup(group, gd);
const KeyItemList* sourceEntry =
findCompatibleKey(sourceID, eg,
sourceRequired, sourceSensitive);
if (sourceEntry != NULL && sourceEntry->size() == 1) {
CKeyMap::KeyItem targetItem = sourceEntry->back();
targetItem.m_id = targetID;
targetItem.m_group = eg;
addKeyEntry(targetItem);
break;
}
}
}
bool
CKeyMap::addKeyCombinationEntry(KeyID id, SInt32 group,
const KeyID* keys, UInt32 numKeys)
{
// disallow kKeyNone
if (id == kKeyNone) {
return false;
}
SInt32 numGroups = group + 1;
if (getNumGroups() > numGroups) {
numGroups = getNumGroups();
}
KeyGroupTable& groupTable = m_keyIDMap[id];
if (groupTable.size() < static_cast<size_t>(numGroups)) {
groupTable.resize(numGroups);
}
if (!groupTable[group].empty()) {
// key is already in the table
return false;
}
// convert to buttons
KeyItemList items;
for (UInt32 i = 0; i < numKeys; ++i) {
KeyIDMap::const_iterator gtIndex = m_keyIDMap.find(keys[i]);
if (gtIndex == m_keyIDMap.end()) {
return false;
}
const KeyGroupTable& groupTable = gtIndex->second;
// if we allow group switching during composition then search all
// groups for keys, otherwise search just the given group.
SInt32 n = 1;
if (m_composeAcrossGroups) {
n = (SInt32)groupTable.size();
}
bool found = false;
for (SInt32 gd = 0; gd < n && !found; ++gd) {
SInt32 eg = (group + gd) % getNumGroups();
const KeyEntryList& entries = groupTable[eg];
for (size_t j = 0; j < entries.size(); ++j) {
if (entries[j].size() == 1) {
found = true;
items.push_back(entries[j][0]);
break;
}
}
}
if (!found) {
// required key is not in keyboard group
return false;
}
}
// add key
groupTable[group].push_back(items);
return true;
}
void
CKeyMap::allowGroupSwitchDuringCompose()
{
m_composeAcrossGroups = true;
}
void
CKeyMap::addHalfDuplexButton(KeyButton button)
{
m_halfDuplex.insert(button);
}
void
CKeyMap::clearHalfDuplexModifiers()
{
m_halfDuplexMods.clear();
}
void
CKeyMap::addHalfDuplexModifier(KeyID key)
{
m_halfDuplexMods.insert(key);
}
void
CKeyMap::finish()
{
m_numGroups = findNumGroups();
// make sure every key has the same number of groups
for (KeyIDMap::iterator i = m_keyIDMap.begin();
i != m_keyIDMap.end(); ++i) {
i->second.resize(m_numGroups);
}
// compute keys that generate each modifier
setModifierKeys();
}
void
CKeyMap::foreachKey(ForeachKeyCallback cb, void* userData)
{
for (KeyIDMap::iterator i = m_keyIDMap.begin();
i != m_keyIDMap.end(); ++i) {
KeyGroupTable& groupTable = i->second;
for (size_t group = 0; group < groupTable.size(); ++group) {
KeyEntryList& entryList = groupTable[group];
for (size_t j = 0; j < entryList.size(); ++j) {
KeyItemList& itemList = entryList[j];
for (size_t k = 0; k < itemList.size(); ++k) {
(*cb)(i->first, static_cast<SInt32>(group),
itemList[k], userData);
}
}
}
}
}
const CKeyMap::KeyItem*
CKeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
KeyModifierMask desiredMask,
bool isAutoRepeat) const
{
LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x", id, id, desiredMask, currentState));
// handle group change
if (id == kKeyNextGroup) {
keys.push_back(Keystroke(1, false, false));
return NULL;
}
else if (id == kKeyPrevGroup) {
keys.push_back(Keystroke(-1, false, false));
return NULL;
}
const KeyItem* item;
switch (id) {
case kKeyShift_L:
case kKeyShift_R:
case kKeyControl_L:
case kKeyControl_R:
case kKeyAlt_L:
case kKeyAlt_R:
case kKeyMeta_L:
case kKeyMeta_R:
case kKeySuper_L:
case kKeySuper_R:
case kKeyAltGr:
case kKeyCapsLock:
case kKeyNumLock:
case kKeyScrollLock:
item = mapModifierKey(keys, id, group, activeModifiers,
currentState, desiredMask, isAutoRepeat);
break;
case kKeySetModifiers:
if (!keysForModifierState(0, group, activeModifiers, currentState,
desiredMask, desiredMask, 0, keys)) {
LOG((CLOG_DEBUG1 "unable to set modifiers %04x", desiredMask));
return NULL;
}
return &m_modifierKeyItem;
case kKeyClearModifiers:
if (!keysForModifierState(0, group, activeModifiers, currentState,
currentState & ~desiredMask,
desiredMask, 0, keys)) {
LOG((CLOG_DEBUG1 "unable to clear modifiers %04x", desiredMask));
return NULL;
}
return &m_modifierKeyItem;
default:
if (isCommand(desiredMask)) {
item = mapCommandKey(keys, id, group, activeModifiers,
currentState, desiredMask, isAutoRepeat);
}
else {
item = mapCharacterKey(keys, id, group, activeModifiers,
currentState, desiredMask, isAutoRepeat);
}
break;
}
if (item != NULL) {
LOG((CLOG_DEBUG1 "mapped to %03x, new state %04x", item->m_button, currentState));
}
return item;
}
SInt32
CKeyMap::getNumGroups() const
{
return m_numGroups;
}
SInt32
CKeyMap::getEffectiveGroup(SInt32 group, SInt32 offset) const
{
return (group + offset + getNumGroups()) % getNumGroups();
}
const CKeyMap::KeyItemList*
CKeyMap::findCompatibleKey(KeyID id, SInt32 group,
KeyModifierMask required, KeyModifierMask sensitive) const
{
assert(group >= 0 && group < getNumGroups());
KeyIDMap::const_iterator i = m_keyIDMap.find(id);
if (i == m_keyIDMap.end()) {
return NULL;
}
const KeyEntryList& entries = i->second[group];
for (size_t j = 0; j < entries.size(); ++j) {
if ((entries[j].back().m_sensitive & sensitive) == 0 ||
(entries[j].back().m_required & sensitive) ==
(required & sensitive)) {
return &entries[j];
}
}
return NULL;
}
bool
CKeyMap::isHalfDuplex(KeyID key, KeyButton button) const
{
return (m_halfDuplex.count(button) + m_halfDuplexMods.count(key) > 0);
}
bool
CKeyMap::isCommand(KeyModifierMask mask) const
{
return ((mask & getCommandModifiers()) != 0);
}
KeyModifierMask
CKeyMap::getCommandModifiers() const
{
// we currently treat ctrl, alt, meta and super as command modifiers.
// some platforms may have a more limited set (OS X only needs Alt)
// but this works anyway.
return KeyModifierControl |
KeyModifierAlt |
KeyModifierMeta |
KeyModifierSuper;
}
void
CKeyMap::collectButtons(const ModifierToKeys& mods, ButtonToKeyMap& keys)
{
keys.clear();
for (ModifierToKeys::const_iterator i = mods.begin();
i != mods.end(); ++i) {
keys.insert(std::make_pair(i->second.m_button, &i->second));
}
}
void
CKeyMap::initModifierKey(KeyItem& item)
{
item.m_generates = 0;
item.m_lock = false;
switch (item.m_id) {
case kKeyShift_L:
case kKeyShift_R:
item.m_generates = KeyModifierShift;
break;
case kKeyControl_L:
case kKeyControl_R:
item.m_generates = KeyModifierControl;
break;
case kKeyAlt_L:
case kKeyAlt_R:
item.m_generates = KeyModifierAlt;
break;
case kKeyMeta_L:
case kKeyMeta_R:
item.m_generates = KeyModifierMeta;
break;
case kKeySuper_L:
case kKeySuper_R:
item.m_generates = KeyModifierSuper;
break;
case kKeyAltGr:
item.m_generates = KeyModifierAltGr;
break;
case kKeyCapsLock:
item.m_generates = KeyModifierCapsLock;
item.m_lock = true;
break;
case kKeyNumLock:
item.m_generates = KeyModifierNumLock;
item.m_lock = true;
break;
case kKeyScrollLock:
item.m_generates = KeyModifierScrollLock;
item.m_lock = true;
break;
default:
// not a modifier
break;
}
}
SInt32
CKeyMap::findNumGroups() const
{
size_t max = 0;
for (KeyIDMap::const_iterator i = m_keyIDMap.begin();
i != m_keyIDMap.end(); ++i) {
if (i->second.size() > max) {
max = i->second.size();
}
}
return static_cast<SInt32>(max);
}
void
CKeyMap::setModifierKeys()
{
m_modifierKeys.clear();
m_modifierKeys.resize(kKeyModifierNumBits * getNumGroups());
for (KeyIDMap::const_iterator i = m_keyIDMap.begin();
i != m_keyIDMap.end(); ++i) {
const KeyGroupTable& groupTable = i->second;
for (size_t g = 0; g < groupTable.size(); ++g) {
const KeyEntryList& entries = groupTable[g];
for (size_t j = 0; j < entries.size(); ++j) {
// skip multi-key sequences
if (entries[j].size() != 1) {
continue;
}
// skip keys that don't generate a modifier
const KeyItem& item = entries[j].back();
if (item.m_generates == 0) {
continue;
}
// add key to each indicated modifier in this group
for (SInt32 b = 0; b < kKeyModifierNumBits; ++b) {
// skip if item doesn't generate bit b
if (((1u << b) & item.m_generates) != 0) {
SInt32 mIndex = (SInt32)g * kKeyModifierNumBits + b;
m_modifierKeys[mIndex].push_back(&item);
}
}
}
}
}
}
const CKeyMap::KeyItem*
CKeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
KeyModifierMask desiredMask,
bool isAutoRepeat) const
{
static const KeyModifierMask s_overrideModifiers = 0xffffu;
// find KeySym in table
KeyIDMap::const_iterator i = m_keyIDMap.find(id);
if (i == m_keyIDMap.end()) {
// unknown key
LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id));
return NULL;
}
const KeyGroupTable& keyGroupTable = i->second;
// find the first key that generates this KeyID
const KeyItem* keyItem = NULL;
SInt32 numGroups = getNumGroups();
for (SInt32 groupOffset = 0; groupOffset < numGroups; ++groupOffset) {
SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
const KeyEntryList& entryList = keyGroupTable[effectiveGroup];
for (size_t i = 0; i < entryList.size(); ++i) {
if (entryList[i].size() != 1) {
// ignore multikey entries
continue;
}
// only match based on shift; we're after the right button
// not the right character. we'll use desiredMask as-is,
// overriding the key's required modifiers, when synthesizing
// this button.
const KeyItem& item = entryList[i].back();
if ((item.m_required & KeyModifierShift & desiredMask) ==
(item.m_sensitive & KeyModifierShift & desiredMask)) {
LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup));
keyItem = &item;
break;
}
}
if (keyItem != NULL) {
break;
}
}
if (keyItem == NULL) {
// no mapping for this keysym
LOG((CLOG_DEBUG1 "no mapping for key %04x", id));
return NULL;
}
// make working copy of modifiers
ModifierToKeys newModifiers = activeModifiers;
KeyModifierMask newState = currentState;
SInt32 newGroup = group;
// don't try to change CapsLock
desiredMask = (desiredMask & ~KeyModifierCapsLock) |
(currentState & KeyModifierCapsLock);
// add the key
if (!keysForKeyItem(*keyItem, newGroup, newModifiers,
newState, desiredMask,
s_overrideModifiers, isAutoRepeat, keys)) {
LOG((CLOG_DEBUG1 "can't map key"));
keys.clear();
return NULL;
}
// add keystrokes to restore modifier keys
if (!keysToRestoreModifiers(*keyItem, group, newModifiers, newState,
activeModifiers, keys)) {
LOG((CLOG_DEBUG1 "failed to restore modifiers"));
keys.clear();
return NULL;
}
// add keystrokes to restore group
if (newGroup != group) {
keys.push_back(Keystroke(group, true, true));
}
// save new modifiers
activeModifiers = newModifiers;
currentState = newState;
return keyItem;
}
const CKeyMap::KeyItem*
CKeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
KeyModifierMask desiredMask,
bool isAutoRepeat) const
{
// find KeySym in table
KeyIDMap::const_iterator i = m_keyIDMap.find(id);
if (i == m_keyIDMap.end()) {
// unknown key
LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id));
return NULL;
}
const KeyGroupTable& keyGroupTable = i->second;
// find best key in any group, starting with the active group
SInt32 keyIndex = -1;
SInt32 numGroups = getNumGroups();
SInt32 groupOffset;
LOG((CLOG_DEBUG1 "find best: %04x %04x", currentState, desiredMask));
for (groupOffset = 0; groupOffset < numGroups; ++groupOffset) {
SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
keyIndex = findBestKey(keyGroupTable[effectiveGroup],
currentState, desiredMask);
if (keyIndex != -1) {
LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup));
break;
}
}
if (keyIndex == -1) {
// no mapping for this keysym
LOG((CLOG_DEBUG1 "no mapping for key %04x", id));
return NULL;
}
// get keys to press for key
SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
const KeyItemList& itemList = keyGroupTable[effectiveGroup][keyIndex];
if (itemList.empty()) {
return NULL;
}
const KeyItem& keyItem = itemList.back();
// make working copy of modifiers
ModifierToKeys newModifiers = activeModifiers;
KeyModifierMask newState = currentState;
SInt32 newGroup = group;
// add each key
for (size_t j = 0; j < itemList.size(); ++j) {
if (!keysForKeyItem(itemList[j], newGroup, newModifiers,
newState, desiredMask,
0, isAutoRepeat, keys)) {
LOG((CLOG_DEBUG1 "can't map key"));
keys.clear();
return NULL;
}
}
// add keystrokes to restore modifier keys
if (!keysToRestoreModifiers(keyItem, group, newModifiers, newState,
activeModifiers, keys)) {
LOG((CLOG_DEBUG1 "failed to restore modifiers"));
keys.clear();
return NULL;
}
// add keystrokes to restore group
if (newGroup != group) {
keys.push_back(Keystroke(group, true, true));
}
// save new modifiers
activeModifiers = newModifiers;
currentState = newState;
return &keyItem;
}
const CKeyMap::KeyItem*
CKeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
KeyModifierMask desiredMask,
bool isAutoRepeat) const
{
return mapCharacterKey(keys, id, group, activeModifiers,
currentState, desiredMask, isAutoRepeat);
}
SInt32
CKeyMap::findBestKey(const KeyEntryList& entryList,
KeyModifierMask /*currentState*/,
KeyModifierMask desiredState) const
{
// check for an item that can accommodate the desiredState exactly
for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) {
const KeyItem& item = entryList[i].back();
if ((item.m_required & desiredState) ==
(item.m_sensitive & desiredState)) {
LOG((CLOG_DEBUG1 "best key index %d of %d (exact)", i, entryList.size()));
return i;
}
}
// choose the item that requires the fewest modifier changes
SInt32 bestCount = 32;
SInt32 bestIndex = -1;
for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) {
const KeyItem& item = entryList[i].back();
KeyModifierMask change =
((item.m_required ^ desiredState) & item.m_sensitive);
SInt32 n = getNumModifiers(change);
if (n < bestCount) {
bestCount = n;
bestIndex = i;
}
}
if (bestIndex != -1) {
LOG((CLOG_DEBUG1 "best key index %d of %d (%d modifiers)", bestIndex, entryList.size(), bestCount));
}
return bestIndex;
}
const CKeyMap::KeyItem*
CKeyMap::keyForModifier(KeyButton button, SInt32 group,
SInt32 modifierBit) const
{
assert(modifierBit >= 0 && modifierBit < kKeyModifierNumBits);
assert(group >= 0 && group < getNumGroups());
// find a key that generates the given modifier in the given group
// but doesn't use the given button, presumably because we're trying
// to generate a KeyID that's only bound the the given button.
// this is important when a shift button is modified by shift; we
// must use the other shift button to do the shifting.
const ModifierKeyItemList& items =
m_modifierKeys[group * kKeyModifierNumBits + modifierBit];
for (ModifierKeyItemList::const_iterator i = items.begin();
i != items.end(); ++i) {
if ((*i)->m_button != button) {
return (*i);
}
}
return NULL;
}
bool
CKeyMap::keysForKeyItem(const KeyItem& keyItem, SInt32& group,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState, KeyModifierMask desiredState,
KeyModifierMask overrideModifiers,
bool isAutoRepeat,
Keystrokes& keystrokes) const
{
static const KeyModifierMask s_notRequiredMask =
KeyModifierAltGr | KeyModifierNumLock | KeyModifierScrollLock;
// add keystrokes to adjust the group
if (group != keyItem.m_group) {
group = keyItem.m_group;
keystrokes.push_back(Keystroke(group, true, false));
}
EKeystroke type;
if (keyItem.m_dead) {
// adjust modifiers for dead key
if (!keysForModifierState(keyItem.m_button, group,
activeModifiers, currentState,
keyItem.m_required, keyItem.m_sensitive,
0, keystrokes)) {
LOG((CLOG_DEBUG1 "unable to match modifier state for dead key %d", keyItem.m_button));
return false;
}
// press and release the dead key
type = kKeystrokeClick;
}
else {
// if this a command key then we don't have to match some of the
// key's required modifiers.
KeyModifierMask sensitive = keyItem.m_sensitive & ~overrideModifiers;
// XXX -- must handle pressing a modifier. in particular, if we want
// to synthesize a KeyID on level 1 of a KeyButton that has Shift_L
// mapped to level 0 then we must release that button if it's down
// (in order to satisfy a shift modifier) then press a different
// button (any other button) mapped to the shift modifier and then
// the Shift_L button.
// match key's required state
LOG((CLOG_DEBUG1 "state: %04x,%04x,%04x", currentState, keyItem.m_required, sensitive));
if (!keysForModifierState(keyItem.m_button, group,
activeModifiers, currentState,
keyItem.m_required, sensitive,
0, keystrokes)) {
LOG((CLOG_DEBUG1 "unable to match modifier state (%04x,%04x) for key %d", keyItem.m_required, keyItem.m_sensitive, keyItem.m_button));
return false;
}
// match desiredState as closely as possible. we must not
// change any modifiers in keyItem.m_sensitive. and if the key
// is a modifier, we don't want to change that modifier.
LOG((CLOG_DEBUG1 "desired state: %04x %04x,%04x,%04x", desiredState, currentState, keyItem.m_required, keyItem.m_sensitive));
if (!keysForModifierState(keyItem.m_button, group,
activeModifiers, currentState,
desiredState,
~(sensitive | keyItem.m_generates),
s_notRequiredMask, keystrokes)) {
LOG((CLOG_DEBUG1 "unable to match desired modifier state (%04x,%04x) for key %d", desiredState, ~keyItem.m_sensitive & 0xffffu, keyItem.m_button));
return false;
}
// repeat or press of key
type = isAutoRepeat ? kKeystrokeRepeat : kKeystrokePress;
}
addKeystrokes(type, keyItem, activeModifiers, currentState, keystrokes);
return true;
}
bool
CKeyMap::keysToRestoreModifiers(const KeyItem& keyItem, SInt32,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
const ModifierToKeys& desiredModifiers,
Keystrokes& keystrokes) const
{
// XXX -- we're not considering modified modifiers here
ModifierToKeys oldModifiers = activeModifiers;
// get the pressed modifier buttons before and after
ButtonToKeyMap oldKeys, newKeys;
collectButtons(oldModifiers, oldKeys);
collectButtons(desiredModifiers, newKeys);
// release unwanted keys
for (ModifierToKeys::const_iterator i = oldModifiers.begin();
i != oldModifiers.end(); ++i) {
KeyButton button = i->second.m_button;
if (button != keyItem.m_button && newKeys.count(button) == 0) {
EKeystroke type = kKeystrokeRelease;
if (i->second.m_lock) {
type = kKeystrokeUnmodify;
}
addKeystrokes(type, i->second,
activeModifiers, currentState, keystrokes);
}
}
// press wanted keys
for (ModifierToKeys::const_iterator i = desiredModifiers.begin();
i != desiredModifiers.end(); ++i) {
KeyButton button = i->second.m_button;
if (button != keyItem.m_button && oldKeys.count(button) == 0) {
EKeystroke type = kKeystrokePress;
if (i->second.m_lock) {
type = kKeystrokeModify;
}
addKeystrokes(type, i->second,
activeModifiers, currentState, keystrokes);
}
}
return true;
}
bool
CKeyMap::keysForModifierState(KeyButton button, SInt32 group,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
KeyModifierMask requiredState, KeyModifierMask sensitiveMask,
KeyModifierMask notRequiredMask,
Keystrokes& keystrokes) const
{
// compute which modifiers need changing
KeyModifierMask flipMask = ((currentState ^ requiredState) & sensitiveMask);
// if a modifier is not required then don't even try to match it. if
// we don't mask out notRequiredMask then we'll try to match those
// modifiers but succeed if we can't. however, this is known not
// to work if the key itself is a modifier (the numlock toggle can
// interfere) so we don't try to match at all.
flipMask &= ~notRequiredMask;
LOG((CLOG_DEBUG1 "flip: %04x (%04x vs %04x in %04x - %04x)", flipMask, currentState, requiredState, sensitiveMask & 0xffffu, notRequiredMask & 0xffffu));
if (flipMask == 0) {
return true;
}
// fix modifiers. this is complicated by the fact that a modifier may
// be sensitive to other modifiers! (who thought that up?)
//
// we'll assume that modifiers with higher bits are affected by modifiers
// with lower bits. there's not much basis for that assumption except
// that we're pretty sure shift isn't changed by other modifiers.
for (SInt32 bit = kKeyModifierNumBits; bit-- > 0; ) {
KeyModifierMask mask = (1u << bit);
if ((flipMask & mask) == 0) {
// modifier is already correct
continue;
}
// do we want the modifier active or inactive?
bool active = ((requiredState & mask) != 0);
// get the KeyItem for the modifier in the group
const KeyItem* keyItem = keyForModifier(button, group, bit);
if (keyItem == NULL) {
if ((mask & notRequiredMask) == 0) {
LOG((CLOG_DEBUG1 "no key for modifier %04x", mask));
return false;
}
else {
continue;
}
}
// if this modifier is sensitive to modifiers then adjust those
// modifiers. also check if our assumption was correct. note
// that we only need to adjust the modifiers on key down.
KeyModifierMask sensitive = keyItem->m_sensitive;
if ((sensitive & mask) != 0) {
// modifier is sensitive to itself. that makes no sense
// so ignore it.
LOG((CLOG_DEBUG1 "modifier %04x modified by itself", mask));
sensitive &= ~mask;
}
if (sensitive != 0) {
if (sensitive > mask) {
// our assumption is incorrect
LOG((CLOG_DEBUG1 "modifier %04x modified by %04x", mask, sensitive));
return false;
}
if (active && !keysForModifierState(button, group,
activeModifiers, currentState,
keyItem->m_required, sensitive,
notRequiredMask, keystrokes)) {
return false;
}
else if (!active) {
// release the modifier
// XXX -- this doesn't work! if Alt and Meta are mapped
// to one key and we want to release Meta we can't do
// that without also releasing Alt.
// need to think about support for modified modifiers.
}
}
// current state should match required state
if ((currentState & sensitive) != (keyItem->m_required & sensitive)) {
LOG((CLOG_DEBUG1 "unable to match modifier state for modifier %04x (%04x vs %04x in %04x)", mask, currentState, keyItem->m_required, sensitive));
return false;
}
// add keystrokes
EKeystroke type = active ? kKeystrokeModify : kKeystrokeUnmodify;
addKeystrokes(type, *keyItem, activeModifiers, currentState,
keystrokes);
}
return true;
}
void
CKeyMap::addKeystrokes(EKeystroke type, const KeyItem& keyItem,
ModifierToKeys& activeModifiers,
KeyModifierMask& currentState,
Keystrokes& keystrokes) const
{
KeyButton button = keyItem.m_button;
UInt32 data = keyItem.m_client;
switch (type) {
case kKeystrokePress:
keystrokes.push_back(Keystroke(button, true, false, data));
if (keyItem.m_generates != 0) {
if (!keyItem.m_lock || (currentState & keyItem.m_generates) == 0) {
// add modifier key and activate modifier
activeModifiers.insert(std::make_pair(
keyItem.m_generates, keyItem));
currentState |= keyItem.m_generates;
}
else {
// deactivate locking modifier
activeModifiers.erase(keyItem.m_generates);
currentState &= ~keyItem.m_generates;
}
}
break;
case kKeystrokeRelease:
keystrokes.push_back(Keystroke(button, false, false, data));
if (keyItem.m_generates != 0 && !keyItem.m_lock) {
// remove key from active modifiers
std::pair<ModifierToKeys::iterator,
ModifierToKeys::iterator> range =
activeModifiers.equal_range(keyItem.m_generates);
for (ModifierToKeys::iterator i = range.first;
i != range.second; ++i) {
if (i->second.m_button == button) {
activeModifiers.erase(i);
break;
}
}
// if no more keys for this modifier then deactivate modifier
if (activeModifiers.count(keyItem.m_generates) == 0) {
currentState &= ~keyItem.m_generates;
}
}
break;
case kKeystrokeRepeat:
keystrokes.push_back(Keystroke(button, false, true, data));
keystrokes.push_back(Keystroke(button, true, true, data));
// no modifier changes on key repeat
break;
case kKeystrokeClick:
keystrokes.push_back(Keystroke(button, true, false, data));
keystrokes.push_back(Keystroke(button, false, false, data));
// no modifier changes on key click
break;
case kKeystrokeModify:
case kKeystrokeUnmodify:
if (keyItem.m_lock) {
// we assume there's just one button for this modifier
if (m_halfDuplex.count(button) > 0) {
if (type == kKeystrokeModify) {
// turn half-duplex toggle on (press)
keystrokes.push_back(Keystroke(button, true, false, data));
}
else {
// turn half-duplex toggle off (release)
keystrokes.push_back(Keystroke(button, false, false, data));
}
}
else {
// toggle (click)
keystrokes.push_back(Keystroke(button, true, false, data));
keystrokes.push_back(Keystroke(button, false, false, data));
}
}
else if (type == kKeystrokeModify) {
// press modifier
keystrokes.push_back(Keystroke(button, true, false, data));
}
else {
// release all the keys that generate the modifier that are
// currently down
std::pair<ModifierToKeys::const_iterator,
ModifierToKeys::const_iterator> range =
activeModifiers.equal_range(keyItem.m_generates);
for (ModifierToKeys::const_iterator i = range.first;
i != range.second; ++i) {
keystrokes.push_back(Keystroke(i->second.m_button,
false, false, i->second.m_client));
}
}
if (type == kKeystrokeModify) {
activeModifiers.insert(std::make_pair(
keyItem.m_generates, keyItem));
currentState |= keyItem.m_generates;
}
else {
activeModifiers.erase(keyItem.m_generates);
currentState &= ~keyItem.m_generates;
}
break;
}
}
SInt32
CKeyMap::getNumModifiers(KeyModifierMask state)
{
SInt32 n = 0;
for (; state != 0; state >>= 1) {
if ((state & 1) != 0) {
++n;
}
}
return n;
}
bool
CKeyMap::isDeadKey(KeyID key)
{
return (key == kKeyCompose || (key >= 0x0300 && key <= 0x036f));
}
KeyID
CKeyMap::getDeadKey(KeyID key)
{
if (isDeadKey(key)) {
// already dead
return key;
}
switch (key) {
case '`':
return kKeyDeadGrave;
case 0xb4u:
return kKeyDeadAcute;
case '^':
case 0x2c6:
return kKeyDeadCircumflex;
case '~':
case 0x2dcu:
return kKeyDeadTilde;
case 0xafu:
return kKeyDeadMacron;
case 0x2d8u:
return kKeyDeadBreve;
case 0x2d9u:
return kKeyDeadAbovedot;
case 0xa8u:
return kKeyDeadDiaeresis;
case 0xb0u:
case 0x2dau:
return kKeyDeadAbovering;
case '\"':
case 0x2ddu:
return kKeyDeadDoubleacute;
case 0x2c7u:
return kKeyDeadCaron;
case 0xb8u:
return kKeyDeadCedilla;
case 0x2dbu:
return kKeyDeadOgonek;
default:
// unknown
return kKeyNone;
}
}
CString
CKeyMap::formatKey(KeyID key, KeyModifierMask mask)
{
// initialize tables
initKeyNameMaps();
CString x;
for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) {
KeyModifierMask mod = (1u << i);
if ((mask & mod) != 0 && s_modifierToNameMap->count(mod) > 0) {
x += s_modifierToNameMap->find(mod)->second;
x += "+";
}
}
if (key != kKeyNone) {
if (s_keyToNameMap->count(key) > 0) {
x += s_keyToNameMap->find(key)->second;
}
// XXX -- we're assuming ASCII here
else if (key >= 33 && key < 127) {
x += (char)key;
}
else {
x += CStringUtil::print("\\u%04x", key);
}
}
else if (!x.empty()) {
// remove trailing '+'
x.erase(x.size() - 1);
}
return x;
}
bool
CKeyMap::parseKey(const CString& x, KeyID& key)
{
// initialize tables
initKeyNameMaps();
// parse the key
key = kKeyNone;
if (s_nameToKeyMap->count(x) > 0) {
key = s_nameToKeyMap->find(x)->second;
}
// XXX -- we're assuming ASCII encoding here
else if (x.size() == 1) {
if (!isgraph(x[0])) {
// unknown key
return false;
}
key = (KeyID)x[0];
}
else if (x.size() == 6 && x[0] == '\\' && x[1] == 'u') {
// escaped unicode (\uXXXX where XXXX is a hex number)
char* end;
key = (KeyID)strtol(x.c_str() + 2, &end, 16);
if (*end != '\0') {
return false;
}
}
else if (!x.empty()) {
// unknown key
return false;
}
return true;
}
bool
CKeyMap::parseModifiers(CString& x, KeyModifierMask& mask)
{
// initialize tables
initKeyNameMaps();
mask = 0;
CString::size_type tb = x.find_first_not_of(" \t", 0);
while (tb != CString::npos) {
// get next component
CString::size_type te = x.find_first_of(" \t+)", tb);
if (te == CString::npos) {
te = x.size();
}
CString c = x.substr(tb, te - tb);
if (c.empty()) {
// missing component
return false;
}
if (s_nameToModifierMap->count(c) > 0) {
KeyModifierMask mod = s_nameToModifierMap->find(c)->second;
if ((mask & mod) != 0) {
// modifier appears twice
return false;
}
mask |= mod;
}
else {
// unknown string
x.erase(0, tb);
CString::size_type tb = x.find_first_not_of(" \t");
CString::size_type te = x.find_last_not_of(" \t");
if (tb == CString::npos) {
x = "";
}
else {
x = x.substr(tb, te - tb + 1);
}
return true;
}
// check for '+' or end of string
tb = x.find_first_not_of(" \t", te);
if (tb != CString::npos) {
if (x[tb] != '+') {
// expected '+'
return false;
}
tb = x.find_first_not_of(" \t", tb + 1);
}
}
// parsed the whole thing
x = "";
return true;
}
void
CKeyMap::initKeyNameMaps()
{
// initialize tables
if (s_nameToKeyMap == NULL) {
s_nameToKeyMap = new CNameToKeyMap;
s_keyToNameMap = new CKeyToNameMap;
for (const KeyNameMapEntry* i = kKeyNameMap; i->m_name != NULL; ++i) {
(*s_nameToKeyMap)[i->m_name] = i->m_id;
(*s_keyToNameMap)[i->m_id] = i->m_name;
}
}
if (s_nameToModifierMap == NULL) {
s_nameToModifierMap = new CNameToModifierMap;
s_modifierToNameMap = new CModifierToNameMap;
for (const KeyModifierNameMapEntry* i = kModifierNameMap;
i->m_name != NULL; ++i) {
(*s_nameToModifierMap)[i->m_name] = i->m_mask;
(*s_modifierToNameMap)[i->m_mask] = i->m_name;
}
}
}
//
// CKeyMap::KeyItem
//
bool
CKeyMap::KeyItem::operator==(const KeyItem& x) const
{
return (m_id == x.m_id &&
m_group == x.m_group &&
m_button == x.m_button &&
m_required == x.m_required &&
m_sensitive == x.m_sensitive &&
m_generates == x.m_generates &&
m_dead == x.m_dead &&
m_lock == x.m_lock &&
m_client == x.m_client);
}
//
// CKeyMap::Keystroke
//
CKeyMap::Keystroke::Keystroke(KeyButton button,
bool press, bool repeat, UInt32 data) :
m_type(kButton)
{
m_data.m_button.m_button = button;
m_data.m_button.m_press = press;
m_data.m_button.m_repeat = repeat;
m_data.m_button.m_client = data;
}
CKeyMap::Keystroke::Keystroke(SInt32 group, bool absolute, bool restore) :
m_type(kGroup)
{
m_data.m_group.m_group = group;
m_data.m_group.m_absolute = absolute;
m_data.m_group.m_restore = restore;
}