fix(macos): build key map with UCKeyTranslate to honor keyboard type
This commit is contained in:
parent
abcd4278fe
commit
a074de1b7c
6 changed files with 135 additions and 309 deletions
|
|
@ -84,6 +84,8 @@ elseif(APPLE)
|
|||
OSXCocoaApp.m
|
||||
OSXEventQueueBuffer.cpp
|
||||
OSXEventQueueBuffer.h
|
||||
OSXKeyLayoutResource.cpp
|
||||
OSXKeyLayoutResource.h
|
||||
OSXKeyState.cpp
|
||||
OSXKeyState.h
|
||||
OSXMediaKeySupport.h
|
||||
|
|
@ -99,8 +101,6 @@ elseif(APPLE)
|
|||
OSXScreenSaverControl.h
|
||||
OSXScreenSaverUtil.h
|
||||
OSXScreenSaverUtil.m
|
||||
OSXUchrKeyResource.cpp
|
||||
OSXUchrKeyResource.h
|
||||
)
|
||||
elseif(UNIX)
|
||||
set(PLATFORM_SOURCES
|
||||
|
|
|
|||
93
src/lib/platform/OSXKeyLayoutResource.cpp
Normal file
93
src/lib/platform/OSXKeyLayoutResource.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2026 Deskflow Developers
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
#include "platform/OSXKeyLayoutResource.h"
|
||||
|
||||
// there are 128 virtual key codes and 32 modifier combinations (the
|
||||
// right-handed modifier bits are ignored, matching the uchr parser).
|
||||
static const uint32_t s_numButtons = 128;
|
||||
static const uint32_t s_numModifierCombinations = 32;
|
||||
|
||||
OSXKeyLayoutResource::OSXKeyLayoutResource(const void *resource, uint32_t keyboardType)
|
||||
: m_layout(static_cast<const UCKeyboardLayout *>(resource)),
|
||||
m_keyboardType(keyboardType)
|
||||
{
|
||||
}
|
||||
|
||||
bool OSXKeyLayoutResource::isValid() const
|
||||
{
|
||||
return m_layout != nullptr;
|
||||
}
|
||||
|
||||
uint32_t OSXKeyLayoutResource::getNumModifierCombinations() const
|
||||
{
|
||||
return s_numModifierCombinations;
|
||||
}
|
||||
|
||||
uint32_t OSXKeyLayoutResource::getNumTables() const
|
||||
{
|
||||
// one table per modifier combination
|
||||
return s_numModifierCombinations;
|
||||
}
|
||||
|
||||
uint32_t OSXKeyLayoutResource::getNumButtons() const
|
||||
{
|
||||
return s_numButtons;
|
||||
}
|
||||
|
||||
uint32_t OSXKeyLayoutResource::getTableForModifier(uint32_t mask) const
|
||||
{
|
||||
// the modifier combination is the table
|
||||
return (mask < s_numModifierCombinations) ? mask : 0;
|
||||
}
|
||||
|
||||
KeyID OSXKeyLayoutResource::getKey(uint32_t table, uint32_t button) const
|
||||
{
|
||||
if (m_layout == nullptr || button >= s_numButtons) {
|
||||
return kKeyNone;
|
||||
}
|
||||
|
||||
// UCKeyTranslate's modifier key state uses the same low five bits as our
|
||||
// table index (cmd, shift, caps, option, control).
|
||||
const UInt32 modifierState = table;
|
||||
|
||||
UInt32 deadKeyState = 0;
|
||||
UniChar chars[4] = {0};
|
||||
UniCharCount count = 0;
|
||||
OSStatus status = UCKeyTranslate(
|
||||
m_layout, static_cast<UInt16>(button), kUCKeyActionDown, modifierState, m_keyboardType, 0, &deadKeyState,
|
||||
sizeof(chars) / sizeof(chars[0]), &count, chars
|
||||
);
|
||||
if (status != noErr) {
|
||||
return kKeyNone;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
// no output. a non-zero dead-key state means this is a dead key: resolve
|
||||
// it to its spacing character by following it with a space, then convert
|
||||
// to the dead KeyID.
|
||||
if (deadKeyState == 0) {
|
||||
return kKeyNone;
|
||||
}
|
||||
UniChar spaceChars[4] = {0};
|
||||
UniCharCount spaceCount = 0;
|
||||
status = UCKeyTranslate(
|
||||
m_layout, kVK_Space, kUCKeyActionDown, 0, m_keyboardType, 0, &deadKeyState,
|
||||
sizeof(spaceChars) / sizeof(spaceChars[0]), &spaceCount, spaceChars
|
||||
);
|
||||
if (status != noErr || spaceCount != 1) {
|
||||
return kKeyNone;
|
||||
}
|
||||
return deskflow::KeyMap::getDeadKey(unicharToKeyID(spaceChars[0]));
|
||||
}
|
||||
|
||||
// no support for multi-character output (matches the uchr parser).
|
||||
if (count != 1) {
|
||||
return kKeyNone;
|
||||
}
|
||||
|
||||
return unicharToKeyID(chars[0]);
|
||||
}
|
||||
34
src/lib/platform/OSXKeyLayoutResource.h
Normal file
34
src/lib/platform/OSXKeyLayoutResource.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2026 Deskflow Developers
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/IOSXKeyResource.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
//! Key resource for a keyboard layout, backed by UCKeyTranslate
|
||||
/*!
|
||||
Builds the key map for a keyboard layout using UCKeyTranslate. Each modifier
|
||||
combination is treated as its own table.
|
||||
*/
|
||||
class OSXKeyLayoutResource : public IOSXKeyResource
|
||||
{
|
||||
public:
|
||||
OSXKeyLayoutResource(const void *resource, uint32_t keyboardType);
|
||||
|
||||
// IOSXKeyResource overrides
|
||||
bool isValid() const override;
|
||||
uint32_t getNumModifierCombinations() const override;
|
||||
uint32_t getNumTables() const override;
|
||||
uint32_t getNumButtons() const override;
|
||||
uint32_t getTableForModifier(uint32_t mask) const override;
|
||||
KeyID getKey(uint32_t table, uint32_t button) const override;
|
||||
|
||||
private:
|
||||
const UCKeyboardLayout *m_layout;
|
||||
uint32_t m_keyboardType;
|
||||
};
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
#include "platform/OSXKeyState.h"
|
||||
#include "arch/Arch.h"
|
||||
#include "base/Log.h"
|
||||
#include "platform/OSXKeyLayoutResource.h"
|
||||
#include "platform/OSXMediaKeySupport.h"
|
||||
#include "platform/OSXUchrKeyResource.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <IOKit/hidsystem/IOHIDLib.h>
|
||||
|
|
@ -527,8 +527,7 @@ void OSXKeyState::getKeyMap(deskflow::KeyMap &keyMap)
|
|||
const void *resource;
|
||||
bool layoutValid = false;
|
||||
|
||||
// add regular keys
|
||||
// try uchr resource first
|
||||
// add regular keys from the layout's key data
|
||||
TISInputSourceRef keyboardLayout = (TISInputSourceRef)CFArrayGetValueAtIndex(m_groups.get(), g);
|
||||
CFDataRef resourceRef = nullptr;
|
||||
{
|
||||
|
|
@ -541,10 +540,10 @@ void OSXKeyState::getKeyMap(deskflow::KeyMap &keyMap)
|
|||
resource = CFDataGetBytePtr(resourceRef);
|
||||
|
||||
if (layoutValid) {
|
||||
OSXUchrKeyResource uchr(resource, keyboardType);
|
||||
if (uchr.isValid()) {
|
||||
LOG_VERBOSE("using uchr resource for group %d", g);
|
||||
getKeyMap(keyMap, g, uchr);
|
||||
OSXKeyLayoutResource keyResource(resource, keyboardType);
|
||||
if (keyResource.isValid()) {
|
||||
LOG_VERBOSE("using key layout for group %d", g);
|
||||
getKeyMap(keyMap, g, keyResource);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2016 Synergy App Ltd
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
#include "platform/OSXUchrKeyResource.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
//
|
||||
// OSXUchrKeyResource
|
||||
//
|
||||
|
||||
OSXUchrKeyResource::OSXUchrKeyResource(const void *resource, uint32_t keyboardType)
|
||||
: m_m(nullptr),
|
||||
m_cti(nullptr),
|
||||
m_sdi(nullptr),
|
||||
m_sri(nullptr),
|
||||
m_st(nullptr)
|
||||
{
|
||||
m_resource = static_cast<const UCKeyboardLayout *>(resource);
|
||||
if (m_resource == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find the keyboard info for the current keyboard type
|
||||
const UCKeyboardTypeHeader *th = nullptr;
|
||||
const UCKeyboardLayout *r = m_resource;
|
||||
for (ItemCount i = 0; i < r->keyboardTypeCount; ++i) {
|
||||
if (keyboardType >= r->keyboardTypeList[i].keyboardTypeFirst &&
|
||||
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 == nullptr) {
|
||||
// cannot find a suitable keyboard type
|
||||
return;
|
||||
}
|
||||
|
||||
// get tables for keyboard type
|
||||
const uint8_t *const base = reinterpret_cast<const uint8_t *>(m_resource);
|
||||
m_m = reinterpret_cast<const UCKeyModifiersToTableNum *>(base + th->keyModifiersToTableNumOffset);
|
||||
m_cti = reinterpret_cast<const UCKeyToCharTableIndex *>(base + th->keyToCharTableIndexOffset);
|
||||
m_sdi = reinterpret_cast<const UCKeySequenceDataIndex *>(base + th->keySequenceDataIndexOffset);
|
||||
if (th->keyStateRecordsIndexOffset != 0) {
|
||||
m_sri = reinterpret_cast<const UCKeyStateRecordsIndex *>(base + th->keyStateRecordsIndexOffset);
|
||||
}
|
||||
if (th->keyStateTerminatorsOffset != 0) {
|
||||
m_st = reinterpret_cast<const UCKeyStateTerminators *>(base + th->keyStateTerminatorsOffset);
|
||||
}
|
||||
|
||||
// find the space key, but only if it can combine with dead keys.
|
||||
// a dead key followed by a space yields the non-dead version of
|
||||
// the dead key.
|
||||
m_spaceOutput = 0xffffu;
|
||||
uint32_t table = getTableForModifier(0);
|
||||
for (uint32_t button = 0, n = getNumButtons(); button < n; ++button) {
|
||||
KeyID id = getKey(table, button);
|
||||
if (id == 0x20) {
|
||||
UCKeyOutput c = reinterpret_cast<const UCKeyOutput *>(base + m_cti->keyToCharTableOffsets[table])[button];
|
||||
if ((c & kUCKeyOutputTestForIndexMask) == kUCKeyOutputStateIndexMask) {
|
||||
m_spaceOutput = (c & kUCKeyOutputGetIndexMask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OSXUchrKeyResource::isValid() const
|
||||
{
|
||||
return (m_m != nullptr);
|
||||
}
|
||||
|
||||
uint32_t OSXUchrKeyResource::getNumModifierCombinations() const
|
||||
{
|
||||
// only 32 (not 256) because the righthanded modifier bits are ignored
|
||||
return 32;
|
||||
}
|
||||
|
||||
uint32_t OSXUchrKeyResource::getNumTables() const
|
||||
{
|
||||
return m_cti->keyToCharTableCount;
|
||||
}
|
||||
|
||||
uint32_t OSXUchrKeyResource::getNumButtons() const
|
||||
{
|
||||
return m_cti->keyToCharTableSize;
|
||||
}
|
||||
|
||||
uint32_t OSXUchrKeyResource::getTableForModifier(uint32_t mask) const
|
||||
{
|
||||
if (mask >= m_m->modifiersCount) {
|
||||
return m_m->defaultTableNum;
|
||||
} else {
|
||||
return m_m->tableNum[mask];
|
||||
}
|
||||
}
|
||||
|
||||
KeyID OSXUchrKeyResource::getKey(uint32_t table, uint32_t button) const
|
||||
{
|
||||
assert(table < getNumTables());
|
||||
assert(button < getNumButtons());
|
||||
|
||||
const uint8_t *const base = reinterpret_cast<const uint8_t *>(m_resource);
|
||||
const UCKeyOutput *cPtr = reinterpret_cast<const UCKeyOutput *>(base + m_cti->keyToCharTableOffsets[table]);
|
||||
|
||||
const UCKeyOutput c = cPtr[button];
|
||||
|
||||
KeySequence keys;
|
||||
switch (c & kUCKeyOutputTestForIndexMask) {
|
||||
case kUCKeyOutputStateIndexMask:
|
||||
if (!getDeadKey(keys, c & kUCKeyOutputGetIndexMask)) {
|
||||
return kKeyNone;
|
||||
}
|
||||
break;
|
||||
|
||||
case kUCKeyOutputSequenceIndexMask:
|
||||
default:
|
||||
if (!addSequence(keys, c)) {
|
||||
return kKeyNone;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// XXX -- no support for multiple characters
|
||||
if (keys.size() != 1) {
|
||||
return kKeyNone;
|
||||
}
|
||||
|
||||
return keys.front();
|
||||
}
|
||||
|
||||
bool OSXUchrKeyResource::getDeadKey(KeySequence &keys, uint16_t index) const
|
||||
{
|
||||
if (m_sri == nullptr || index >= m_sri->keyStateRecordCount) {
|
||||
// XXX -- should we be using some other fallback?
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t state = 0;
|
||||
if (!getKeyRecord(keys, index, state)) {
|
||||
return false;
|
||||
}
|
||||
if (state == 0) {
|
||||
// not a dead key
|
||||
return true;
|
||||
}
|
||||
|
||||
// no dead keys if we couldn't find the space key
|
||||
if (m_spaceOutput == 0xffffu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the dead key should not have put anything in the key list
|
||||
if (!keys.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the character generated by pressing the space key after the
|
||||
// dead key. if we're still in a compose state afterwards then we're
|
||||
// confused so we bail.
|
||||
if (!getKeyRecord(keys, m_spaceOutput, state) || state != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// convert keys to their dead counterparts
|
||||
for (KeySequence::iterator i = keys.begin(); i != keys.end(); ++i) {
|
||||
*i = deskflow::KeyMap::getDeadKey(*i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OSXUchrKeyResource::getKeyRecord(KeySequence &keys, uint16_t index, uint16_t &state) const
|
||||
{
|
||||
const uint8_t *const base = reinterpret_cast<const uint8_t *>(m_resource);
|
||||
const UCKeyStateRecord *sr = reinterpret_cast<const UCKeyStateRecord *>(base + m_sri->keyStateRecordOffsets[index]);
|
||||
const UCKeyStateEntryTerminal *kset = reinterpret_cast<const UCKeyStateEntryTerminal *>(sr->stateEntryData);
|
||||
|
||||
uint16_t nextState = 0;
|
||||
bool found = false;
|
||||
if (state == 0) {
|
||||
found = true;
|
||||
nextState = sr->stateZeroNextState;
|
||||
if (!addSequence(keys, sr->stateZeroCharData)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we have a next entry
|
||||
switch (sr->stateEntryFormat) {
|
||||
case kUCKeyStateEntryTerminalFormat:
|
||||
for (uint16_t j = 0; j < sr->stateEntryCount; ++j) {
|
||||
if (kset[j].curState == state) {
|
||||
if (!addSequence(keys, kset[j].charData)) {
|
||||
return false;
|
||||
}
|
||||
nextState = 0;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kUCKeyStateEntryRangeFormat:
|
||||
// XXX -- not supported yet
|
||||
break;
|
||||
|
||||
default:
|
||||
// XXX -- unknown format
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// use a terminator
|
||||
if (m_st != nullptr && state < m_st->keyStateTerminatorCount) {
|
||||
if (!addSequence(keys, m_st->keyStateTerminators[state - 1])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
nextState = sr->stateZeroNextState;
|
||||
if (!addSequence(keys, sr->stateZeroCharData)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// next
|
||||
state = nextState;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OSXUchrKeyResource::addSequence(KeySequence &keys, UCKeyCharSeq c) const
|
||||
{
|
||||
if ((c & kUCKeyOutputTestForIndexMask) == kUCKeyOutputSequenceIndexMask) {
|
||||
uint16_t index = (c & kUCKeyOutputGetIndexMask);
|
||||
if (index < m_sdi->charSequenceCount &&
|
||||
m_sdi->charSequenceOffsets[index] != m_sdi->charSequenceOffsets[index + 1]) {
|
||||
// XXX -- sequences not supported yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (c != 0xfffe && c != 0xffff) {
|
||||
KeyID id = unicharToKeyID(c);
|
||||
if (id != kKeyNone) {
|
||||
keys.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2016 Synergy App Ltd
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "platform/IOSXKeyResource.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
using KeyLayout = TISInputSourceRef;
|
||||
|
||||
class OSXUchrKeyResource : public IOSXKeyResource
|
||||
{
|
||||
public:
|
||||
OSXUchrKeyResource(const void *, uint32_t keyboardType);
|
||||
|
||||
// KeyResource overrides
|
||||
bool isValid() const override;
|
||||
uint32_t getNumModifierCombinations() const override;
|
||||
uint32_t getNumTables() const override;
|
||||
uint32_t getNumButtons() const override;
|
||||
uint32_t getTableForModifier(uint32_t mask) const override;
|
||||
KeyID getKey(uint32_t table, uint32_t button) const override;
|
||||
|
||||
private:
|
||||
using KeySequence = std::vector<KeyID>;
|
||||
|
||||
bool getDeadKey(KeySequence &keys, uint16_t index) const;
|
||||
bool getKeyRecord(KeySequence &keys, uint16_t index, uint16_t &state) const;
|
||||
bool addSequence(KeySequence &keys, UCKeyCharSeq c) const;
|
||||
|
||||
private:
|
||||
const UCKeyboardLayout *m_resource;
|
||||
const UCKeyModifiersToTableNum *m_m;
|
||||
const UCKeyToCharTableIndex *m_cti;
|
||||
const UCKeySequenceDataIndex *m_sdi;
|
||||
const UCKeyStateRecordsIndex *m_sri;
|
||||
const UCKeyStateTerminators *m_st;
|
||||
uint16_t m_spaceOutput;
|
||||
};
|
||||
Loading…
Reference in a new issue