/* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2016 Symless Ltd. * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ #include "platform/OSXUchrKeyResource.h" #include // // 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(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(m_resource); m_m = reinterpret_cast(base + th->keyModifiersToTableNumOffset); m_cti = reinterpret_cast(base + th->keyToCharTableIndexOffset); m_sdi = reinterpret_cast(base + th->keySequenceDataIndexOffset); if (th->keyStateRecordsIndexOffset != 0) { m_sri = reinterpret_cast(base + th->keyStateRecordsIndexOffset); } if (th->keyStateTerminatorsOffset != 0) { m_st = reinterpret_cast(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(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(m_resource); const UCKeyOutput *cPtr = reinterpret_cast(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(m_resource); const UCKeyStateRecord *sr = reinterpret_cast(base + m_sri->keyStateRecordOffsets[index]); const UCKeyStateEntryTerminal *kset = reinterpret_cast(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; }