Added support for unicode keyboard layouts on OS X.

This commit is contained in:
crs 2004-12-29 17:07:08 +00:00
parent fedd2224e8
commit ee787415c2
2 changed files with 368 additions and 81 deletions

View File

@ -14,6 +14,7 @@
#include "COSXKeyState.h"
#include "CLog.h"
#include "CArch.h"
#include <stdio.h>
struct CKCHRDeadKeyRecord {
@ -112,6 +113,7 @@ static const CKeyEntry s_controlKeys[] = {
// modifier keys. i don't know how to make the mac properly
// interpret the right hand versions of modifier keys so they're
// currently mapped to the left hand version.
// { kKeyNumLock, 71 },
{ kKeyShift_L, 56 },
{ kKeyShift_R, 56 /*60*/ },
{ kKeyControl_L, 59 },
@ -122,15 +124,20 @@ static const CKeyEntry s_controlKeys[] = {
{ kKeySuper_R, 58 /*61*/ },
{ kKeyMeta_L, 58 },
{ kKeyMeta_R, 58 /*61*/ },
{ kKeyCapsLock, 57 },
{ kKeyNumLock, 71 }
{ kKeyCapsLock, 57 }
};
// special key that synthesizes a delay. see doFakeKeyEvent() and
// mapKey().
static const KeyButton kDelayKey = 510;
//
// COSXKeyState
//
COSXKeyState::COSXKeyState()
COSXKeyState::COSXKeyState() :
m_uchrFound(false)
{
setHalfDuplexMask(0);
SInt16 currentKeyScript = GetScriptManagerVariable(smKeyScript);
@ -197,8 +204,12 @@ COSXKeyState::doUpdateKeys()
{
// save key mapping
m_keyMap.clear();
if (!filluchrKeysMap(m_keyMap, m_capsLockSet)) {
fillKCHRKeysMap(m_keyMap, m_capsLockSet);
m_uchrFound = false;
if (m_uchrResource != NULL) {
m_uchrFound = filluchrKeysMap(m_keyMap);
}
if (!m_uchrFound && m_KCHRResource != NULL) {
fillKCHRKeysMap(m_keyMap);
}
fillSpecialKeys(m_keyMap, m_virtualKeyMap);
@ -235,15 +246,26 @@ void
COSXKeyState::doFakeKeyEvent(KeyButton button, bool press, bool)
{
LOG((CLOG_DEBUG2 "doFakeKeyEvent button:%d, press:%d", button, press));
// if it's the special delay key then just pause briefly
if (button == kDelayKey) {
ARCH->sleep(0.01);
return;
}
// let system figure out character for us
CGPostKeyboardEvent(0, mapKeyButtonToVirtualKey(button), press);
}
KeyButton
COSXKeyState::mapKey(Keystrokes& keys, KeyID id,
KeyModifierMask /*desiredMask*/,
KeyModifierMask desiredMask,
bool isAutoRepeat) const
{
// see if the keyboard layout has changed since we last checked it.
// reload the keyboard info if so.
const_cast<COSXKeyState*>(this)->checkKeyboardLayout();
// look up virtual key
CKeyIDMap::const_iterator keyIndex = m_keyMap.find(id);
if (keyIndex == m_keyMap.end()) {
@ -254,12 +276,6 @@ COSXKeyState::mapKey(Keystrokes& keys, KeyID id,
return 0;
}
// if the virtual key is caps-lock sensitive then suppress shift
KeyModifierMask mask = ~0;
if (m_capsLockSet.count(id) != 0) {
mask &= ~KeyModifierShift;
}
// FIXME -- for both calls to addKeystrokes below we'd prefer to use
// a required mask that generates the same character but matches
// the desiredMask as closely as possible.
@ -284,13 +300,36 @@ COSXKeyState::mapKey(Keystrokes& keys, KeyID id,
keystroke.m_press = false;
keystroke.m_repeat = false;
keys.push_back(keystroke);
// pause briefly before sending the next key because some
// apps (e.g. TextEdit) seem to ignore dead keys that occur
// very shortly before the next key. why they'd do that i
// don't know.
keystroke.m_key = kDelayKey;
keystroke.m_press = false;
keystroke.m_repeat = false;
keys.push_back(keystroke);
}
// add final key
// add final key.
// if the desired mask includes Alt or Control then match the
// desired mask. this ensures that combinations like
// Command+Shift+S use the Command and Shift modifiers and
// those like Command+S do not use the shift modifier.
if ((desiredMask & (KeyModifierControl | KeyModifierAlt)) != 0) {
return addKeystrokes(keys, sequence.back().m_button,
desiredMask,
KeyModifierShift | KeyModifierSuper |
KeyModifierAlt | KeyModifierControl |
KeyModifierCapsLock,
isAutoRepeat);
}
else {
return addKeystrokes(keys, sequence.back().m_button,
sequence.back().m_requiredState,
sequence.back().m_requiredMask & mask,
sequence.back().m_requiredMask,
isAutoRepeat);
}
}
KeyButton
@ -387,8 +426,50 @@ COSXKeyState::mapKeyFromEvent(CKeyIDs& ids,
}
// check for character keys
if (m_uchrResource != NULL) {
// FIXME -- implement this
if (m_uchrFound) {
// get the event modifiers and remove the command and control
// keys.
UInt32 modifiers;
GetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
NULL, sizeof(modifiers), NULL, &modifiers);
modifiers &= ~(cmdKey | controlKey | rightControlKey);
// build keycode
UInt16 keycode =
static_cast<UInt16>((modifiers & 0xff00u) | (vkCode & 0x00ffu));
// choose action
UInt16 action;
switch (eventKind) {
case kEventRawKeyDown:
action = kUCKeyActionDown;
break;
case kEventRawKeyRepeat:
action = kUCKeyActionAutoKey;
break;
default:
return 0;
}
// translate key
UniCharCount count;
UniChar chars[2];
OSStatus status = UCKeyTranslate(m_uchrResource, keycode, action,
modifiers, m_keyboardType, 0, &m_deadKeyState,
sizeof(chars) / sizeof(chars[0]), &count, chars);
// get the characters
if (status == 0) {
if (count != 0 || m_deadKeyState == 0) {
m_deadKeyState = 0;
for (UniCharCount i = 0; i < count; ++i) {
ids.push_back(unicharToKeyID(chars[i]));
}
return mapVirtualKeyToKeyButton(vkCode);
}
}
}
else if (m_KCHRResource != NULL) {
// get the event modifiers and remove the command and control
@ -478,10 +559,13 @@ COSXKeyState::checkKeyboardLayout()
{
SInt16 currentKeyScript = GetScriptManagerVariable(smKeyScript);
SInt16 keyboardLayoutID = GetScriptVariable(currentKeyScript, smScriptKeys);
if (keyboardLayoutID != m_keyboardLayoutID) {
UInt32 keyboardType = LMGetKbdType();
if (keyboardLayoutID != m_keyboardLayoutID ||
keyboardType != m_keyboardType) {
// layout changed
m_keyboardType = keyboardType;
setKeyboardLayout(keyboardLayoutID);
doUpdateKeys();
updateKeys();
}
}
@ -494,11 +578,10 @@ COSXKeyState::setKeyboardLayout(SInt16 keyboardLayoutID)
m_uchrHandle = GetResource('uchr', m_keyboardLayoutID);
m_KCHRResource = NULL;
m_uchrResource = NULL;
/* FIXME -- don't use uchr resource yet
if (m_uchrHandle != NULL) {
m_uchrResource = reinterpret_cast<UCKeyboardLayout*>(*m_uchrHandle);
}
else */if (m_KCHRHandle != NULL) {
if (m_KCHRHandle != NULL) {
m_KCHRResource = reinterpret_cast<CKCHRResource*>(*m_KCHRHandle);
}
}
@ -526,29 +609,19 @@ COSXKeyState::fillSpecialKeys(CKeyIDMap& keyMap,
}
bool
COSXKeyState::fillKCHRKeysMap(CKeyIDMap& keyMap, CKeySet& capsLockSet) const
COSXKeyState::fillKCHRKeysMap(CKeyIDMap& keyMap) const
{
assert(m_KCHRResource != NULL);
CKCHRResource* r = m_KCHRResource;
// note caps-lock sensitive keys
SInt32 uIndex = r->m_tableSelectionIndex[0];
SInt32 clIndex = r->m_tableSelectionIndex[alphaLock >> 8];
for (SInt32 j = 0; j < 128; ++j) {
UInt8 c = r->m_characterTables[clIndex][j];
if (r->m_characterTables[uIndex][j] != c) {
KeyID keyID = charToKeyID(c);
capsLockSet.insert(keyID);
}
}
// build non-composed keys to virtual keys mapping
std::map<UInt8, CKeyEventInfo> vkMap;
for (SInt32 i = 0; i < r->m_numTables; ++i) {
// determine the modifier keys for table i
KeyModifierMask mask =
maskForTable(static_cast<UInt8>(i), r->m_tableSelectionIndex);
maskForTable(static_cast<UInt8>(i), r->m_tableSelectionIndex,
256, static_cast<UInt8>(i));
// build the KeyID to virtual key map
for (SInt32 j = 0; j < 128; ++j) {
@ -561,7 +634,9 @@ COSXKeyState::fillKCHRKeysMap(CKeyIDMap& keyMap, CKeySet& capsLockSet) const
// generate the character. this mostly works as-is, though.
CKeyEventInfo info;
info.m_button = mapVirtualKeyToKeyButton(j);
info.m_requiredMask = mask;
info.m_requiredMask =
KeyModifierShift | KeyModifierSuper |
KeyModifierCapsLock;
info.m_requiredState = mask;
// save character to virtual key mapping
@ -594,7 +669,8 @@ COSXKeyState::fillKCHRKeysMap(CKeyIDMap& keyMap, CKeySet& capsLockSet) const
for (SInt32 i = 0; i < dkp->m_numRecords; ++i) {
// determine the modifier keys for table i
KeyModifierMask mask =
maskForTable(dkr->m_tableIndex, r->m_tableSelectionIndex);
maskForTable(dkr->m_tableIndex, r->m_tableSelectionIndex,
256, dkr->m_tableIndex);
// map each completion
for (SInt32 j = 0; j < dkr->m_numCompletions; ++j) {
@ -627,9 +703,11 @@ COSXKeyState::fillKCHRKeysMap(CKeyIDMap& keyMap, CKeySet& capsLockSet) const
CKeyEventInfo info;
info.m_button = mapVirtualKeyToKeyButton(
dkr->m_virtualKey);
info.m_requiredMask = mask;
info.m_requiredMask =
KeyModifierShift | KeyModifierSuper |
KeyModifierAlt | KeyModifierControl |
KeyModifierCapsLock;
info.m_requiredState = mask;
sequence.push_back(info);
sequence.push_back(vkMap[dkr->m_completion[j][0]]);
}
@ -645,10 +723,198 @@ COSXKeyState::fillKCHRKeysMap(CKeyIDMap& keyMap, CKeySet& capsLockSet) const
}
bool
COSXKeyState::filluchrKeysMap(CKeyIDMap&, CKeySet&) const
COSXKeyState::filluchrKeysMap(CKeyIDMap& keyMap) const
{
// FIXME -- implement this
assert(m_uchrResource != NULL);
UCKeyboardLayout* r = m_uchrResource;
UInt8* base = reinterpret_cast<UInt8*>(r);
UCKeyLayoutFeatureInfo* fi = NULL;
if (r->keyLayoutFeatureInfoOffset != 0) {
fi = reinterpret_cast<UCKeyLayoutFeatureInfo*>(
base + r->keyLayoutFeatureInfoOffset);
}
// find the keyboard info for the current keyboard type
UCKeyboardTypeHeader* th = NULL;
for (ItemCount i = 0; i < r->keyboardTypeCount; ++i) {
if (m_keyboardType >= r->keyboardTypeList[i].keyboardTypeFirst &&
m_keyboardType <= r->keyboardTypeList[i].keyboardTypeLast) {
th = r->keyboardTypeList + i;
break;
}
if (r->keyboardTypeList[i].keyboardTypeFirst == 0) {
// found the default. use it unless we find a match.
th = r->keyboardTypeList + i;
}
}
if (th == NULL) {
// cannot find a suitable keyboard type
return false;
}
// get tables for keyboard type
UCKeyModifiersToTableNum* m =
reinterpret_cast<UCKeyModifiersToTableNum*>(
base + th->keyModifiersToTableNumOffset);
UCKeyToCharTableIndex* cti =
reinterpret_cast<UCKeyToCharTableIndex*>(
base + th->keyToCharTableIndexOffset);
UCKeySequenceDataIndex* sdi =
reinterpret_cast<UCKeySequenceDataIndex*>(
base + th->keySequenceDataIndexOffset);
// build non-composed keys to virtual keys mapping
CDeadKeyMap dkMap;
for (UInt32 i = 0; i < cti->keyToCharTableCount; ++i) {
// determine the modifier keys for table i
KeyModifierMask mask =
maskForTable(static_cast<UInt8>(i), m->tableNum,
m->modifiersCount, m->defaultTableNum);
// get the character table
UCKeyOutput* ct =
reinterpret_cast<UCKeyOutput*>(
base + cti->keyToCharTableOffsets[i]);
// build the KeyID to virtual key map
for (SInt32 j = 0; j < cti->keyToCharTableSize; ++j) {
// get character
UInt16 c = ct[j];
// ignore key sequence mappings
if ((c & 0xc000) == 0x8000) {
UInt16 index = (c & 0x3fff);
if (index < sdi->charSequenceCount &&
sdi->charSequenceOffsets[index] !=
sdi->charSequenceOffsets[index + 1]) {
continue;
}
// not a sequence mapping. use character as-is.
}
// just record dead key mappings
else if ((c & 0xc000) == 0x4000) {
UInt16 index = (c & 0x3fff);
if (dkMap.count(index) == 0) {
dkMap[index] = std::make_pair(j, mask);
}
continue;
}
// skip non-glyph character
if (c < 32 || c == 127 || c == 0xfffe || c == 0xffff) {
continue;
}
// map character to KeyID
KeyID keyID = unicharToKeyID(c);
// if we've seen this character already then do nothing
if (keyMap.count(keyID) != 0) {
continue;
}
// save entry for character
// FIXME -- should set only those bits in m_requiredMask that
// correspond to modifiers that are truly necessary to
// generate the character.
CKeyEventInfo info;
info.m_button = mapVirtualKeyToKeyButton(j);
info.m_requiredMask =
KeyModifierShift | KeyModifierSuper |
KeyModifierCapsLock;
info.m_requiredState = mask;
keyMap[keyID].push_back(info);
}
}
// build composed keys to virtual keys mapping
UCKeyStateRecordsIndex* sri = NULL;
if (th->keyStateRecordsIndexOffset != NULL) {
sri = reinterpret_cast<UCKeyStateRecordsIndex*>(
base + th->keyStateRecordsIndexOffset);
}
UCKeyStateTerminators* st = NULL;
if (th->keyStateTerminatorsOffset != NULL) {
st = reinterpret_cast<UCKeyStateTerminators*>(
base + th->keyStateTerminatorsOffset);
}
CKeySequence sequence;
mapDeadKeySequence(keyMap, sequence, 0, base, sri, st, dkMap);
return true;
}
void
COSXKeyState::mapDeadKeySequence(CKeyIDMap& keyMap,
CKeySequence& sequence,
UInt16 state, const UInt8* base,
const UCKeyStateRecordsIndex* sri,
const UCKeyStateTerminators* st,
CDeadKeyMap& dkMap)
{
for (CDeadKeyMap::const_iterator i = dkMap.begin(); i != dkMap.end(); ++i) {
UInt16 index = i->first;
const UCKeyStateRecord* sr =
reinterpret_cast<const UCKeyStateRecord*>(
base + sri->keyStateRecordOffsets[index]);
const UCKeyStateEntryTerminal* kset =
reinterpret_cast<const UCKeyStateEntryTerminal*>(
sr->stateEntryData);
UInt16 c = 0;
UInt16 nextState = 0;
if (state == 0) {
c = sr->stateZeroCharData;
nextState = sr->stateZeroNextState;
}
else if (sr->stateEntryFormat == kUCKeyStateEntryTerminalFormat) {
for (UInt16 j = 0; j < sr->stateEntryCount; ++j) {
if (kset[j].curState == state) {
c = kset[j].charData;
break;
}
}
// XXX -- default state terminator not supported yet
}
else if (sr->stateEntryFormat == kUCKeyStateEntryRangeFormat) {
// XXX -- not supported yet
}
// push character onto sequence. m_requiredMask should only
// have those modifiers that are truly necessary to generate
// the character.
CKeyEventInfo info;
info.m_button = mapVirtualKeyToKeyButton(
i->second.first);
info.m_requiredMask =
KeyModifierShift | KeyModifierSuper |
KeyModifierCapsLock;
info.m_requiredState = i->second.second;
sequence.push_back(info);
if (nextState != 0) {
// next in dead key sequence
mapDeadKeySequence(keyMap, sequence,
nextState, base, sri, st, dkMap);
}
else if (c >= 32 && c != 127 && c != 0xfffe && c != 0xffff) {
// terminate sequence
KeyID keyID = unicharToKeyID(c);
// if we've seen this character already then do nothing,
// otherwise save it in the key map.
if (keyMap.count(keyID) == 0) {
keyMap[keyID] = sequence;
}
}
// pop character from sequence
sequence.pop_back();
}
}
KeyButton
@ -718,7 +984,8 @@ COSXKeyState::unicharToKeyID(UniChar c)
}
KeyModifierMask
COSXKeyState::maskForTable(UInt8 i, UInt8* tableSelectors)
COSXKeyState::maskForTable(UInt8 i, UInt8* tableSelectors,
UInt32 numEntries, UInt8 defaultIndex)
{
// this is a table of 0 to 255 sorted by the number of 1 bits then
// numerical order.
@ -741,10 +1008,13 @@ COSXKeyState::maskForTable(UInt8 i, UInt8* tableSelectors)
238, 243, 245, 246, 249, 250, 252, 127, 191, 223, 239, 247, 251, 253, 254, 255
};
while (true) {
// find first entry in tableSelectors that maps to i. this is the
// one that uses the fewest modifier keys.
UInt8 maxIndex = static_cast<UInt8>(numEntries - 1);
for (UInt32 j = 0; j < 256; ++j) {
if (tableSelectors[s_indexTable[j]] == i) {
if (s_indexTable[j] <= maxIndex &&
tableSelectors[s_indexTable[j]] == i) {
// convert our mask to a traditional mac modifier mask
// (which just means shifting it left 8 bits).
UInt16 macMask = (static_cast<UInt16>(s_indexTable[j]) << 8);
@ -770,6 +1040,13 @@ COSXKeyState::maskForTable(UInt8 i, UInt8* tableSelectors)
}
}
// should never get here since we've tried every 8 bit number
// no match. try defaultIndex.
if (i == defaultIndex) {
break;
}
i = defaultIndex;
}
// should never get here.
return 0;
}

View File

@ -79,7 +79,7 @@ private:
typedef std::vector<CKeyEventInfo> CKeySequence;
typedef std::map<KeyID, CKeySequence> CKeyIDMap;
typedef std::map<UInt32, KeyID> CVirtualKeyMap;
typedef std::set<KeyID> CKeySet;
typedef std::map<UInt16, std::pair<SInt32, KeyModifierMask> > CDeadKeyMap;
KeyButton addKeystrokes(Keystrokes& keys,
KeyButton keyButton,
@ -109,15 +109,14 @@ private:
// map maps each KeyID to the sequence of keys (with modifiers)
// that would have to be synthesized to generate the KeyID character.
// Returns false iff no KCHR resource was found.
bool fillKCHRKeysMap(CKeyIDMap& keyMap,
CKeySet& capsLockSet) const;
bool fillKCHRKeysMap(CKeyIDMap& keyMap) const;
// Convert the uchr resource to a KeyID to key sequence map. the
// map maps each KeyID to the sequence of keys (with modifiers)
// that would have to be synthesized to generate the KeyID character.
// Returns false iff no uchr resource was found.
bool filluchrKeysMap(CKeyIDMap& keyMap,
CKeySet& capsLockSet) const;
// Returns false iff no uchr resource was found or it couldn't be
// mapped.
bool filluchrKeysMap(CKeyIDMap& keyMap) const;
// Maps an OS X virtual key id to a KeyButton. This simply remaps
// the ids so we don't use KeyButton 0.
@ -134,9 +133,19 @@ private:
static KeyID unicharToKeyID(UniChar);
// Choose the modifier mask with the fewest modifiers for character
// mapping table i.
// mapping table i. The tableSelectors table has numEntries. If
// no mapping is found for i, try mapping defaultIndex.
static KeyModifierMask
maskForTable(UInt8 i, UInt8* tableSelectors);
maskForTable(UInt8 i, UInt8* tableSelectors,
UInt32 numEntries, UInt8 defaultIndex);
// Save characters built from dead key sequences.
static void mapDeadKeySequence(CKeyIDMap& keyMap,
CKeySequence& sequence,
UInt16 state, const UInt8* base,
const UCKeyStateRecordsIndex* sri,
const UCKeyStateTerminators* st,
CDeadKeyMap& dkMap);
private:
// OS X uses a physical key if 0 for the 'A' key. synergy reserves
@ -157,6 +166,7 @@ private:
};
SInt16 m_keyboardLayoutID;
UInt32 m_keyboardType;
mutable UInt32 m_deadKeyState;
Handle m_KCHRHandle;
Handle m_uchrHandle;
@ -164,7 +174,7 @@ private:
UCKeyboardLayout* m_uchrResource;
CKeyIDMap m_keyMap;
CVirtualKeyMap m_virtualKeyMap;
CKeySet m_capsLockSet;
bool m_uchrFound;
};
#endif