/* Name: preservedkeymap Copyright: Copyright (C) SIL International. Documentation: Description: Create Date: 1 Dec 2012 Modified Date: 10 Feb 2015 Authors: mcdurdin Related Files: Dependencies: Bugs: Todo: Notes: History: 01 Dec 2012 - mcdurdin - I3623 - V9.0 - Add preserved key mapping from .kmx file 17 Jan 2013 - mcdurdin - I3762 - V9.0 - underlying layout cannot be controlled cleanly via TSF so support translation 11 Aug 2013 - mcdurdin - I3775 - V9.0 - Fail I3762 - AltGr combinations are not matched on French base layout 15 Apr 2014 - mcdurdin - I4128 - V9.0 - Shift states still not working with unprocessed keys in V9 16 Apr 2014 - mcdurdin - I4169 - V9.0 - Mnemonic layouts should be recompiled to positional based on user-selected base keyboard 03 Aug 2014 - mcdurdin - I4351 - V9.0 - RALT keys are not preserved correctly in TIP 27 Jan 2015 - mcdurdin - I4575 - V9.0 - Support output of TAB and ENTER for unmatched key events 27 Jan 2015 - mcdurdin - I4575 - V9.0 - Support output of TAB and ENTER for unmatched key events 10 Feb 2015 - mcdurdin - I4592 - V9.0 - If a computer does not have US keyboard installed, then AltGr rules can go wrong */ // I3623 // I4169 // I4575 // I4575 #include "pch.h" WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 struct PreservedKey { GUID guid; TF_PRESERVEDKEY key; }; class PreservedKeyMap { public: BOOL MapKeyboard(KEYBOARD *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys); /** * Updates a map of preserved keys (with GUID) that are used in keyboard rules. * Passing in NULL for pPreservedKeys will cause cPreservedKeys to be set to the number * of preserved keys needed for the supplied pKeyboard; this should be used in creating * pPreservedKeys list to sufficient size. When pPreservedKeys list is passed the * cPreservedKeys will be the actual count of the number of unique pPreservedKeys * * @param pKeyboard the keyboard for which the rules will be extracted from * @param pPreservedKeys preallocated array for preserved keys to be filled in, or nullptr to retrieve required size * @param cPreservedKeys number of preserved keys in pPreservedKeys - or the size pPreservedKeys needs to be * @return BOOL return TRUE on success */ BOOL MapKeyboardCore(km_kbp_keyboard *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys); private: BOOL m_BaseKeyboardUsesAltGr; // I4592 UINT ShiftToTSFShift(UINT ShiftFlags); BOOL MapUSCharToVK(UINT *puKey, UINT *puShiftFlags); BOOL MapKeyRule(KEY *pKey, TF_PRESERVEDKEY *pPreservedKey); BOOL MapKeyRuleCore(km_kbp_keyboard_key *pKeyRule, TF_PRESERVEDKEY *pPreservedKey); BOOL IsMatchingKey(PreservedKey *pKey, PreservedKey *pKeys, size_t cKeys); }; const struct { UINT key; BOOL shift; } USCharMap[] = { { VK_SPACE, FALSE }, // 20 ' ' { '1', TRUE }, // 21 '!' { VK_QUOTE, TRUE }, // 22 '"' { '3', TRUE }, // 23 '#' { '4', TRUE }, // 24 '$' { '5', TRUE }, // 25 '%' { '7', TRUE }, // 26 '&' { VK_QUOTE, FALSE }, // 27 ''' { '9', TRUE }, // 28 '(' { '0', TRUE }, // 29 ')' { '8', TRUE }, // 2A '*' { VK_EQUAL, TRUE }, // 2B '+' { VK_COMMA, FALSE }, // 2C ',' { VK_HYPHEN, FALSE }, // 2D '-' { VK_PERIOD, FALSE }, // 2E '.' { VK_SLASH, FALSE }, // 2F '/' { '0', FALSE }, // 30 '0' { '1', FALSE }, // 31 '1' { '2', FALSE }, // 32 '2' { '3', FALSE }, // 33 '3' { '4', FALSE }, // 34 '4' { '5', FALSE }, // 35 '5' { '6', FALSE }, // 36 '6' { '7', FALSE }, // 37 '7' { '8', FALSE }, // 38 '8' { '9', FALSE }, // 39 '9' { VK_COLON, TRUE }, // 3A ':' { VK_COLON, FALSE }, // 3B ';' { VK_COMMA, TRUE }, // 3C '<' { VK_EQUAL, FALSE }, // 3D '=' { VK_PERIOD, TRUE }, // 3E '>' { VK_SLASH, TRUE }, // 3F '?' { '2', TRUE }, // 40 '@' { 'A', TRUE }, // 41 'A' { 'B', TRUE }, // 42 'B' { 'C', TRUE }, // 43 'C' { 'D', TRUE }, // 44 'D' { 'E', TRUE }, // 45 'E' { 'F', TRUE }, // 46 'F' { 'G', TRUE }, // 47 'G' { 'H', TRUE }, // 48 'H' { 'I', TRUE }, // 49 'I' { 'J', TRUE }, // 4A 'J' { 'K', TRUE }, // 4B 'K' { 'L', TRUE }, // 4C 'L' { 'M', TRUE }, // 4D 'M' { 'N', TRUE }, // 4E 'N' { 'O', TRUE }, // 4F 'O' { 'P', TRUE }, // 50 'P' { 'Q', TRUE }, // 51 'Q' { 'R', TRUE }, // 52 'R' { 'S', TRUE }, // 53 'S' { 'T', TRUE }, // 54 'T' { 'U', TRUE }, // 55 'U' { 'V', TRUE }, // 56 'V' { 'W', TRUE }, // 57 'W' { 'X', TRUE }, // 58 'X' { 'Y', TRUE }, // 59 'Y' { 'Z', TRUE }, // 5A 'Z' { VK_LBRKT, FALSE }, // 5B '[' { VK_BKSLASH, FALSE }, // 5C '\' { VK_RBRKT, FALSE }, // 5D ']' { '6', TRUE }, // 5E '^' { VK_HYPHEN, TRUE }, // 5F '_' { VK_ACCENT, FALSE }, // 60 '`' { 'A', FALSE }, // 61 'a' { 'B', FALSE }, // 62 'b' { 'C', FALSE }, // 63 'c' { 'D', FALSE }, // 64 'd' { 'E', FALSE }, // 65 'e' { 'F', FALSE }, // 66 'f' { 'G', FALSE }, // 67 'g' { 'H', FALSE }, // 68 'h' { 'I', FALSE }, // 69 'i' { 'J', FALSE }, // 6A 'j' { 'K', FALSE }, // 6B 'k' { 'L', FALSE }, // 6C 'l' { 'M', FALSE }, // 6D 'm' { 'N', FALSE }, // 6E 'n' { 'O', FALSE }, // 6F 'o' { 'P', FALSE }, // 70 'p' { 'Q', FALSE }, // 71 'q' { 'R', FALSE }, // 72 'r' { 'S', FALSE }, // 73 's' { 'T', FALSE }, // 74 't' { 'U', FALSE }, // 75 'u' { 'V', FALSE }, // 76 'v' { 'W', FALSE }, // 77 'w' { 'X', FALSE }, // 78 'x' { 'Y', FALSE }, // 79 'y' { 'Z', FALSE }, // 7A 'z' { VK_LBRKT, TRUE }, // 7B '{' { VK_BKSLASH, TRUE }, // 7C '|' { VK_RBRKT, TRUE }, // 7D '}' { VK_ACCENT, TRUE } // 7E '~' }; BOOL PreservedKeyMap::MapUSCharToVK(UINT *puKey, UINT *puShiftFlags) { if(*puKey >= 0x20 && *puKey < 0x7F) { *puShiftFlags = ISVIRTUALKEY | (USCharMap[*puKey - 0x20].shift ? K_SHIFTFLAG : 0); *puKey = USCharMap[*puKey - 0x20].key; return TRUE; } return FALSE; } UINT PreservedKeyMap::ShiftToTSFShift(UINT ShiftFlags) { UINT res = 0; if(ShiftFlags & K_SHIFTFLAG) res |= TF_MOD_SHIFT; if(ShiftFlags & K_CTRLFLAG) res |= TF_MOD_CONTROL; if(ShiftFlags & K_ALTFLAG) res |= TF_MOD_ALT; if(ShiftFlags & LCTRLFLAG) res |= TF_MOD_LCONTROL; if(ShiftFlags & RCTRLFLAG) res |= TF_MOD_RCONTROL; if(ShiftFlags & LALTFLAG) res |= TF_MOD_LALT; if(ShiftFlags & RALTFLAG) { if(m_BaseKeyboardUsesAltGr) { res |= TF_MOD_RALT|TF_MOD_LCONTROL; // We need to mimic the Windows RAlt+LCtrl == AltGr behaviour // I4592 } else { res |= TF_MOD_RALT; // I3775 = no longer applicable ifwe use US English base keyboard // I4351 } } return res; } BOOL PreservedKeyMap::MapKeyRule(KEY *pKey, TF_PRESERVEDKEY *pPreservedKey) { UINT ShiftFlags; UINT Key; Key = pKey->Key; ShiftFlags = pKey->ShiftFlags; if(Key == VK_BACK || Key == VK_RETURN || Key == VK_TAB) // I4575 { // // We never map backspace, return or tab because these are the only supported virtual key outputs, // and result in recursion. Sadly, this is an imperfect solution forced upon us by preserved key // limitations. // // Other virtual key output will be blocked with this version. return FALSE; } if (Key > 255) { // // Touch-defined keys have a value > 255, but these should never be preserved // return FALSE; } if(ShiftFlags == 0) { if(!MapUSCharToVK(&Key, &ShiftFlags)) return FALSE; } pPreservedKey->uVKey = (UINT) USVKToScanCodeToLayoutVK( (WORD) Key); // I3762 pPreservedKey->uModifiers = ShiftToTSFShift(ShiftFlags); return TRUE; } BOOL PreservedKeyMap::MapKeyRuleCore(km_kbp_keyboard_key *pKeyRule, TF_PRESERVEDKEY *pPreservedKey) { UINT ShiftFlags; UINT Key; Key = pKeyRule->key; ShiftFlags = pKeyRule->modifier_flag; if (Key == VK_BACK || Key == VK_RETURN || Key == VK_TAB) // I4575 { // // We never map backspace, return or tab because these are the only supported virtual key outputs, // and result in recursion. Sadly, this is an imperfect solution forced upon us by preserved key // limitations. // // Other virtual key output will be blocked with this version. return FALSE; } if (Key > 255) { // // Touch-defined keys have a value > 255, but these should never be preserved // return FALSE; } if (ShiftFlags == 0) { if (!MapUSCharToVK(&Key, &ShiftFlags)) return FALSE; } pPreservedKey->uVKey = (UINT)USVKToScanCodeToLayoutVK((WORD)Key); // I3762 pPreservedKey->uModifiers = ShiftToTSFShift(ShiftFlags); return TRUE; } BOOL PreservedKeyMap::IsMatchingKey(PreservedKey *pKey, PreservedKey *pKeys, size_t cKeys) { for(size_t i = 0; i < cKeys; i++) { if(pKeys[i].key.uModifiers == pKey->key.uModifiers && pKeys[i].key.uVKey == pKey->key.uVKey) { return TRUE; } } return FALSE; } BOOL PreservedKeyMap::MapKeyboard(KEYBOARD *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys) { size_t cKeys = 0, n; DWORD i, j; GROUP *pGroup; m_BaseKeyboardUsesAltGr = KeyboardGivesCtrlRAltForRAlt(); // I4592 // This is not the same as m_BaseKeyboardUsesAltGr -- we are turning // the simulation back on after (possibly) turning it off, for a // consistent experience. TODO: determine if m_BaseKeyboardUseAltGr // is still needed given we always use kbdus as base for Keyman 10+ BOOL bSimulateAltGr = Globals::get_SimulateAltGr(); // We only want to translate RALT and RALT+SHIFT for Ctrl+Alt rules. // So we exclude all our other favourite modifier keys. const UINT RALT_MATCHING_MASK = TF_MOD_CONTROL | TF_MOD_ALT | TF_MOD_LCONTROL | TF_MOD_RCONTROL | TF_MOD_LALT | TF_MOD_RALT; for(i = 0; i < pKeyboard->cxGroupArray; i++) { if(pKeyboard->dpGroupArray[i].fUsingKeys) { cKeys += pKeyboard->dpGroupArray[i].cxKeyArray; } } if(cKeys == 0) { return FALSE; } if (bSimulateAltGr) { // We might need twice as many preserved keys to map both LCtrl+LAlt+x and RAlt+x cKeys *= 2; } if(pPreservedKeys == NULL) { *cPreservedKeys = cKeys; return TRUE; } if(*cPreservedKeys < cKeys) { return FALSE; } PreservedKey *pKeys = *pPreservedKeys; for(n = i = 0; i < pKeyboard->cxGroupArray; i++) { pGroup = &pKeyboard->dpGroupArray[i]; if(pGroup->fUsingKeys) { for(j = 0; j < pGroup->cxKeyArray; j++) { // If we have a key rule for the key, we should preserve it if(MapKeyRule(&pGroup->dpKeyArray[j], &pKeys[n].key)) { // Don't attempt to add the same preserved key twice. Bad things happen if(!IsMatchingKey(&pKeys[n], pKeys, n)) { CoCreateGuid(&pKeys[n].guid); n++; if (bSimulateAltGr && (pKeys[n-1].key.uModifiers & RALT_MATCHING_MASK) == TF_MOD_RALT) { // Do this for RALT and RALT+SHIFT only, so we've tested against that mask // Copy the key and fix modifiers pKeys[n].key = pKeys[n - 1].key; pKeys[n].key.uModifiers = (pKeys[n].key.uModifiers & ~TF_MOD_RALT) | TF_MOD_LCONTROL | TF_MOD_LALT; CoCreateGuid(&pKeys[n].guid); n++; } } } } } } *cPreservedKeys = n; // return actual count of allocated keys, usually smaller than allocated count return TRUE; } BOOL PreservedKeyMap::MapKeyboardCore(km_kbp_keyboard *pKeyboard, PreservedKey **pPreservedKeys, size_t *cPreservedKeys) { size_t cKeys = 0, cRules = 0, n = 0; DWORD i; m_BaseKeyboardUsesAltGr = KeyboardGivesCtrlRAltForRAlt(); // I4592 // This is not the same as m_BaseKeyboardUsesAltGr -- we are turning // the simulation back on after (possibly) turning it off, for a // consistent experience. TODO: determine if m_BaseKeyboardUseAltGr // is still needed given we always use kbdus as base for Keyman 10+ BOOL bSimulateAltGr = Globals::get_SimulateAltGr(); // We only want to translate RALT and RALT+SHIFT for Ctrl+Alt rules. // So we exclude all our other favourite modifier keys. const UINT RALT_MATCHING_MASK = TF_MOD_CONTROL | TF_MOD_ALT | TF_MOD_LCONTROL | TF_MOD_RCONTROL | TF_MOD_LALT | TF_MOD_RALT; // This is where we will call down to the api to get list of keys used in the keyboard rules km_kbp_keyboard_key *kb_key_list; km_kbp_status err_status = km_kbp_keyboard_get_key_list(pKeyboard, &kb_key_list); if ((err_status != KM_KBP_STATUS_OK) || (kb_key_list ==nullptr)) { return FALSE; } km_kbp_keyboard_key *key_rule_it = kb_key_list; for (; key_rule_it->key; ++key_rule_it) { ++cRules; } cKeys = cRules; if (cKeys == 0) { km_kbp_keyboard_key_list_dispose(kb_key_list); return FALSE; } if (bSimulateAltGr) { // We might need twice as many preserved keys to map both LCtrl+LAlt+x and RAlt+x cKeys *= 2; } if (pPreservedKeys == NULL) { *cPreservedKeys = cKeys; km_kbp_keyboard_key_list_dispose(kb_key_list); return TRUE; } if (*cPreservedKeys < cKeys) { km_kbp_keyboard_key_list_dispose(kb_key_list); return FALSE; } PreservedKey *pKeys = *pPreservedKeys; for (i = 0; i < cRules; i++) { // If we have a key rule for the key, we should preserve it if (MapKeyRuleCore(&kb_key_list[i], &pKeys[n].key)) { // Don't attempt to add the same preserved key twice. Bad things happen if (!IsMatchingKey(&pKeys[n], pKeys, n)) { CoCreateGuid(&pKeys[n].guid); n++; if (bSimulateAltGr && (pKeys[n - 1].key.uModifiers & RALT_MATCHING_MASK) == TF_MOD_RALT) { // Do this for RALT and RALT+SHIFT only, so we've tested against that mask // Copy the key and fix modifiers pKeys[n].key = pKeys[n - 1].key; pKeys[n].key.uModifiers = (pKeys[n].key.uModifiers & ~TF_MOD_RALT) | TF_MOD_LCONTROL | TF_MOD_LALT; CoCreateGuid(&pKeys[n].guid); n++; } } } } km_kbp_keyboard_key_list_dispose(kb_key_list); *cPreservedKeys = n; // return actual count of allocated keys, usually smaller than allocated count return TRUE; } extern "C" __declspec(dllexport) BOOL WINAPI GetKeyboardPreservedKeys(PreservedKey **pPreservedKeys, size_t *cPreservedKeys) { PreservedKeyMap pkm; PKEYMAN64THREADDATA _td = ThreadGlobals(); if (!_td) { return FALSE; } if (!_td->lpActiveKeyboard) { return FALSE; } // It could be an active core keyboard if (Globals::get_CoreIntegration()) { if (!_td->lpActiveKeyboard->lpCoreKeyboard) { return FALSE; } // use api to get key rules return pkm.MapKeyboardCore(_td->lpActiveKeyboard->lpCoreKeyboard, pPreservedKeys, cPreservedKeys); } else { if (!_td->lpActiveKeyboard->Keyboard) { return FALSE; } return pkm.MapKeyboard(_td->lpActiveKeyboard->Keyboard, pPreservedKeys, cPreservedKeys); } }