/* * Keyman is copyright (C) SIL International. MIT License. * * Mnemonic layout support for linux * * Defines the entry point for the console application. * * Note: this program deliberately leaks memory as it has a very short life cycle and managing the memory allocations * for the subcomponents of the compiled keyboard is an unnecessary optimisation. Just so you know. */ #include "mcompile.h" const int nr_DK_pairs = 1000; static const int size_DK_array = (nr_DK_pairs + 1) *3; /** @brief convert mnemonic keyboard layout to positional keyboard layout and translate keyboard */ KMX_BOOL KMX_DoConvert(LPKMX_KEYBOARD kbd, KMX_BOOL bDeadkeyConversion, gint argc, gchar* argv[]); /** @brief Collect the key data, translate it to kmx and append to the existing keyboard */ bool KMX_ImportRules(LPKMX_KEYBOARD kp, vec_dword_3D& all_vector, GdkKeymap** keymap, std::vector* KMX_FDeadkeys, KMX_BOOL bDeadkeyConversion); // I4353 // I4327 /** @brief return a vector of [usvk, ch_out] pairs: all existing combinations of a deadkey + character for the underlying keyboard */ int KMX_GetDeadkeys(vec_dword_2D& dk_Table, KMX_WORD deadkey, std::vector &dk_vec, GdkKeymap* keymap); std::vector KMX_FDeadkeys; // I4353 #define _countof(a) (sizeof(a) / sizeof(*(a))) /** * @brief main function for mcompile for Linux * @param argc number of commandline arguments * @param argv pointer to commandline arguments: executable, inputfile, outputfile * @return 0 on success */ int main(int argc, char* argv[]) { int bDeadkeyConversion = 0; if (argc > 1) bDeadkeyConversion = (strcmp(argv[1], "-d") == 0); // I4552 int n = (bDeadkeyConversion ? 2 : 1); if (argc < 3 || argc > 4 || (argc - n) != 2) { // I4273// I4273 printf( "Usage: \tmcompile [-d] infile.kmx outfile.kmx\n" " \tmcompile converts a Keyman mnemonic layout to\n" " \ta positional one based on the currently used \n" " \tLinux keyboard layout\n" " \t(-d convert deadkeys to plain keys) \n \n"); // I4552 return 1; } // -u option is not available for Linux and macOS KMX_CHAR* infile = argv[n]; KMX_CHAR* outfile = argv[n + 1]; printf("mcompile%s \"%s\" \"%s\"\n", bDeadkeyConversion ? " -d" : "", infile, outfile); // I4174 // 1. Load the keyman keyboard file // 2. For each key on the system layout, determine its output character and perform a // 1-1 replacement on the keyman keyboard of that character with the base VK + shift // state. This fixup will transform the char to a vk, which will avoid any issues // with the key. // // // For each deadkey, we need to determine its possible outputs. Then we generate a VK // rule for that deadkey, e.g. [K_LBRKT] > dk(c101) // // Next, update each rule that references the output from that deadkey to add an extra // context deadkey at the end of the context match, e.g. 'a' dk(c101) + [K_SPACE] > 'b'. // This will require a memory layout change for the .kmx file, plus fixups on the // context+output index offsets // // --> virtual character keys // // [CTRL ' '] : we look at the character, and replace it in the same way, but merely // switch the shift state from the VIRTUALCHARKEY to VIRTUALKEY, without changing any // other properties of the key. // // 3. Write the new keyman keyboard file LPKMX_KEYBOARD kmxfile; if (!KMX_LoadKeyboard(infile, &kmxfile)) { KMX_LogError(L"Failed to load keyboard (%d)\n", errno); return 3; } if (KMX_DoConvert(kmxfile, bDeadkeyConversion, argc, (gchar**)argv)) { // I4552F if(!KMX_SaveKeyboard(kmxfile, outfile)) { KMX_LogError(L"Failed to save keyboard (%d)\n", errno); return 3; } } delete kmxfile; return 0; } // Map of all shift states that we will work with const KMX_DWORD VKShiftState[] = {0, K_SHIFTFLAG, LCTRLFLAG | RALTFLAG, K_SHIFTFLAG | LCTRLFLAG | RALTFLAG, 0xFFFF}; // // TranslateKey // // For each key rule on the keyboard, remap its key to the // correct shift state and key. Adjust the LCTRL+RALT -> RALT if necessary // /** * @brief translate each key of a group: remap the content of a key (key->Key) of the US keyboard to a character (ch) * @param key pointer to a key * @param vk a keyvalue of the US keyboard * @param shift shiftstate * @param ch character of the underlying keyboard to be remapped */ void KMX_TranslateKey(LPKMX_KEY key, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { // The weird LCTRL+RALT is Windows' way of mapping the AltGr key. // We store that as just RALT, and use the option "Simulate RAlt with Ctrl+Alt" // to provide an alternate.. if ((shift & (LCTRLFLAG | RALTFLAG)) == (LCTRLFLAG | RALTFLAG)) shift &= ~LCTRLFLAG; if (key->ShiftFlags == 0 && key->Key == ch) { // Key is a mnemonic key with no shift state defined. // Remap the key according to the character on the key cap. // KMX_LogError(L"Converted mnemonic rule on line %d, + '%c' TO + [%x K_%d]", key->Line, key->Key, shift, vk); key->ShiftFlags = ISVIRTUALKEY | shift; key->Key = vk; } else if (key->ShiftFlags & VIRTUALCHARKEY && key->Key == ch) { // Key is a virtual character key with a hard-coded shift state. // Do not remap the shift state, just move the key. // This will not result in 100% wonderful mappings as there could // be overlap, depending on how keys are arranged on the target layout. // But that is up to the designer. // KMX_LogError(L"Converted mnemonic virtual char key rule on line %d, + [%x '%c'] TO + [%x K_%d]", key->Line, key->ShiftFlags, key->Key, key->ShiftFlags & ~VIRTUALCHARKEY, vk); key->ShiftFlags &= ~VIRTUALCHARKEY; key->Key = vk; } } /** * @brief translate a group of a keyboard * @param group pointer to a keyboard group * @param vk a keyvalue of the US keyboard * @param shift shiftstate * @param ch character of the underlying keyboard to be remapped */ void KMX_TranslateGroup(LPKMX_GROUP group, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { for (unsigned int i = 0; i < group->cxKeyArray; i++) { KMX_TranslateKey(&group->dpKeyArray[i], vk, shift, ch); } } /** * @brief translate a keyboard * @param kbd pointer to the US keyboard * @param vk a keyvalue of the US keyboard * @param shift shiftstate * @param ch character of the underlying keyboard to be remapped */ void KMX_TranslateKeyboard(LPKMX_KEYBOARD kbd, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { if (kbd->dpGroupArray[i].fUsingKeys) { KMX_TranslateGroup(&kbd->dpGroupArray[i], vk, shift, ch); } } } /** * @brief check key for unconverted key rules * @param key pointer to a key */ void KMX_ReportUnconvertedKeyRule(LPKMX_KEY key) { if (key->ShiftFlags == 0) { // KMX_LogError(L"Did not find a match for mnemonic rule on line %d, + '%c' > ...", key->Line, key->Key); } else if (key->ShiftFlags & VIRTUALCHARKEY) { KMX_LogError(L"Did not find a match for mnemonic virtual character key rule on line %d, + [%x '%c'] > ...", key->Line, key->ShiftFlags, key->Key); } } /** * @brief check a group for unconverted rules * @param group pointer to a keyboard group */ void KMX_ReportUnconvertedGroupRules(LPKMX_GROUP group) { for (unsigned int i = 0; i < group->cxKeyArray; i++) { KMX_ReportUnconvertedKeyRule(&group->dpKeyArray[i]); } } /** * @brief check a keyboard for unconverted rules * @param kbd pointer to the US keyboard */ void KMX_ReportUnconvertedKeyboardRules(LPKMX_KEYBOARD kbd) { for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { if (kbd->dpGroupArray[i].fUsingKeys) { KMX_ReportUnconvertedGroupRules(&kbd->dpGroupArray[i]); } } } /** * @brief remap the content of a key (key->dpContext) of the US keyboard to a deadkey sequence * @param key pointer to a key * @param deadkey a deadkey to be remapped * @param vk a keyvalue of the US keyboard * @param shift shiftstate * @param ch character of the underlying keyboard */ void KMX_TranslateDeadkeyKey(LPKMX_KEY key, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift, KMX_WORD ch) { if ((key->ShiftFlags == 0 || key->ShiftFlags & VIRTUALCHARKEY) && key->Key == ch) { // The weird LCTRL+RALT is Windows' way of mapping the AltGr key. // We store that as just RALT, and use the option "Simulate RAlt with Ctrl+Alt" // to provide an alternate.. if ((shift & (LCTRLFLAG | RALTFLAG)) == (LCTRLFLAG | RALTFLAG)) // I4327 shift &= ~LCTRLFLAG; if (key->ShiftFlags == 0) { // KMX_LogError(L"Converted mnemonic rule on line %d, + '%c' TO dk(%d) + [%x K_%d]", key->Line, key->Key, deadkey, shift, vk); key->ShiftFlags = ISVIRTUALKEY | shift; } else { // KMX_LogError(L"Converted mnemonic virtual char key rule on line %d, + [%x '%c'] TO dk(%d) + [%x K_%d]", key->Line, key->ShiftFlags, key->Key, deadkey, key->ShiftFlags & ~VIRTUALCHARKEY, vk); key->ShiftFlags &= ~VIRTUALCHARKEY; } int len = u16len(key->dpContext); PKMX_WCHAR context = new KMX_WCHAR[len + 4]; memcpy(context, key->dpContext, len * sizeof(KMX_WCHAR)); context[len] = UC_SENTINEL; context[len + 1] = CODE_DEADKEY; context[len + 2] = deadkey; context[len + 3] = 0; key->dpContext = context; key->Key = vk; } } /** * @brief translate a group * @param group pointer to a keyboard group * @param deadkey deadkey to be remapped * @param vk a keyvalue of the US keyboard * @param shift shiftstate * @param ch character of the underlying keyboard */ void KMX_TranslateDeadkeyGroup(LPKMX_GROUP group, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift, KMX_WORD ch) { for (unsigned int i = 0; i < group->cxKeyArray; i++) { KMX_TranslateDeadkeyKey(&group->dpKeyArray[i], deadkey, vk, shift, ch); } } /** * @brief translate a keyboard * @param kbd pointer to the US keyboard * @param deadkey a deadkey to be remapped * @param vk a keyvalue of the US keyboard * @param shift shiftstate * @param ch character of the underlying keyboard */ void KMX_TranslateDeadkeyKeyboard(LPKMX_KEYBOARD kbd, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift, KMX_WORD ch) { for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { if (kbd->dpGroupArray[i].fUsingKeys) { KMX_TranslateDeadkeyGroup(&kbd->dpGroupArray[i], deadkey, vk, shift, ch); } } } /** * @brief add a deadkey rule * @param kbd pointer to the US keyboard * @param deadkey a deadkey to be added * @param vk a keyvalue of the US keyboard * @param shift shiftstate */ void KMX_AddDeadkeyRule(LPKMX_KEYBOARD kbd, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift) { // The weird LCTRL+RALT is Windows' way of mapping the AltGr key. // We store that as just RALT, and use the option "Simulate RAlt with Ctrl+Alt" // to provide an alternate.. if ((shift & (LCTRLFLAG | RALTFLAG)) == (LCTRLFLAG | RALTFLAG)) // I4549 shift &= ~LCTRLFLAG; // If the first group is not a matching-keys group, then we need to add into // each subgroup, otherwise just the match group for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { if (kbd->dpGroupArray[i].fUsingKeys) { LPKMX_KEY keys = new KMX_KEY[kbd->dpGroupArray[i].cxKeyArray + 1]; memcpy(keys + 1, kbd->dpGroupArray[i].dpKeyArray, kbd->dpGroupArray[i].cxKeyArray * sizeof(KMX_KEY)); keys[0].dpContext = new KMX_WCHAR[1]; keys[0].dpContext[0] = 0; keys[0].dpOutput = new KMX_WCHAR[4]; keys[0].dpOutput[0] = UC_SENTINEL; keys[0].dpOutput[1] = CODE_DEADKEY; keys[0].dpOutput[2] = deadkey; // TODO: translate to unique index keys[0].dpOutput[3] = 0; keys[0].Key = vk; keys[0].Line = 0; keys[0].ShiftFlags = shift | ISVIRTUALKEY; kbd->dpGroupArray[i].dpKeyArray = keys; kbd->dpGroupArray[i].cxKeyArray++; KMX_LogError(L"Add deadkey rule: + [%d K_%d] > dk(%d)", shift, vk, deadkey); if (i == kbd->StartGroup[1]) break; // If this is the initial group, that's all we need to do. } } } /** * @brief find the maximal deadkey id * @param str the deadkey * @return the maximum deadkey id */ KMX_WCHAR KMX_ScanXStringForMaxDeadkeyID(PKMX_WCHAR str) { KMX_WCHAR dkid = 0; while (str && *str) { if (*str == UC_SENTINEL && *(str + 1) == CODE_DEADKEY) { dkid = std::max(dkid, *(str + 2)); } str = KMX_incxstr(str); } return dkid; } struct KMX_dkidmap { KMX_WCHAR src_deadkey, dst_deadkey; }; /** * @brief find the deadkey id for a given deadkey * @param kbd pointer to the keyboard * @param deadkey for which an id is to be found * @return 0 if failed; * otherwise a deadkey-id */ KMX_WCHAR KMX_GetUniqueDeadkeyID(LPKMX_KEYBOARD kbd, KMX_WCHAR deadkey) { LPKMX_GROUP gp; LPKMX_KEY kp; LPKMX_STORE sp; KMX_DWORD i, j; KMX_WCHAR dkid = 0; static KMX_WCHAR s_next_dkid = 0; static KMX_dkidmap* s_dkids = NULL; static int s_ndkids = 0; if (!kbd) { if (s_dkids) { delete s_dkids; } s_dkids = NULL; s_ndkids = 0; s_next_dkid = 0; return 0; } for (int xi = 0; xi < s_ndkids; xi++) { if (s_dkids[xi].src_deadkey == deadkey) { return s_dkids[xi].dst_deadkey; } } if (s_next_dkid != 0) { s_dkids = (KMX_dkidmap*)realloc(s_dkids, sizeof(KMX_dkidmap) * (s_ndkids + 1)); s_dkids[s_ndkids].src_deadkey = deadkey; return s_dkids[s_ndkids++].dst_deadkey = ++s_next_dkid; } for (i = 0, gp = kbd->dpGroupArray; i < kbd->cxGroupArray; i++, gp++) { for (j = 0, kp = gp->dpKeyArray; j < gp->cxKeyArray; j++, kp++) { dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(kp->dpContext)); dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(kp->dpOutput)); } dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(gp->dpMatch)); dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(gp->dpNoMatch)); } for (i = 0, sp = kbd->dpStoreArray; i < kbd->cxStoreArray; i++, sp++) { dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(sp->dpString)); } s_dkids = (KMX_dkidmap*)realloc(s_dkids, sizeof(KMX_dkidmap) * (s_ndkids + 1)); s_dkids[s_ndkids].src_deadkey = deadkey; return s_dkids[s_ndkids++].dst_deadkey = s_next_dkid = ++dkid; } /** * @brief Lookup the deadkey table for the deadkey in the physical keyboard. Then for each character, go through and map it through * @param kbd pointer to the keyboard * @param vk_US virtual key of the us keyboard * @param shift shiftstate * @param deadkey character produced by a deadkey * @param all_vector vector that holds the data of the US keyboard and the currently used (underlying) keyboard * @param keymap pointer to the currently used (underlying) keyboard Layout * @param dk_Table a vector of all possible deadkey combinations for all Linux keyboards */ void KMX_ConvertDeadkey(LPKMX_KEYBOARD kbd, KMX_WORD vk_US, KMX_DWORD shift, KMX_WCHAR deadkey, vec_dword_3D& all_vector, GdkKeymap* keymap, vec_dword_2D dk_Table) { std::vector vec_deadkeys; // Lookup the deadkey table for the deadkey in the physical keyboard // Then for each character, go through and map it through KMX_WCHAR dkid = KMX_GetUniqueDeadkeyID(kbd, deadkey); // Add the deadkey to the mapping table for use in the import rules phase KMX_DeadkeyMapping KMX_deadkeyMapping = {deadkey, dkid, shift, vk_US}; // I4353 KMX_FDeadkeys.push_back(KMX_deadkeyMapping); // dkid, vk, shift); // I4353 KMX_AddDeadkeyRule(kbd, dkid, vk_US, shift); KMX_GetDeadkeys(dk_Table, deadkey, vec_deadkeys, keymap); // returns vector of [usvk, ch_out] pairs int n=0; while (n < (int)vec_deadkeys.size() - 2) { // Look up the ch KMX_DWORD KeyValUnderlying = (KMX_DWORD) KMX_get_KeyValUnderlying_From_KeyValUS(all_vector, vec_deadkeys[n]); KMX_TranslateDeadkeyKeyboard(kbd, dkid, KeyValUnderlying, vec_deadkeys[n + 1], vec_deadkeys[n + 2]); n += 3; } } /** * @brief convert a mnemonic keyboard to a positional keyboard * (i.e. setting *sp->dpString = '0' / TSS_MNEMONIC=0) * @param kbd pointer to keyboard * @return TRUE if conversion was successful; * FALSE otherwise */ KMX_BOOL KMX_SetKeyboardToPositional(LPKMX_KEYBOARD kbd) { LPKMX_STORE sp; KMX_DWORD i; for (i = 0, sp = kbd->dpStoreArray; i < kbd->cxStoreArray; i++, sp++) { if (sp->dwSystemID == TSS_MNEMONIC) { if (!sp->dpString) { KMX_LogError(L"Invalid &mnemoniclayout system store"); return FALSE; } if (u16cmp((const KMX_WCHAR*)sp->dpString, u"1") != 0) { KMX_LogError(L"Keyboard is not a mnemonic layout keyboard"); return FALSE; } *sp->dpString = '0'; return TRUE; } } KMX_LogError(L"Keyboard is not a mnemonic layout keyboard"); return FALSE; } /** * @brief convert mnemonic keyboard layout to positional keyboard layout and translate keyboard * @param kbd pointer to US keyboard * @param bDeadkeyConversion option for converting a deadkey to a character: 1 = dk conversion; 0 = no dk conversion * @param argc number of command line arguments * @param argv pointer to command line arguments * @return TRUE if conversion was successful; * FALSE if not */ KMX_BOOL KMX_DoConvert(LPKMX_KEYBOARD kbd, KMX_BOOL bDeadkeyConversion, gint argc, gchar* argv[]) { KMX_WCHAR DeadKey = 0; if (!KMX_SetKeyboardToPositional(kbd)) return FALSE; // Go through each of the shift states - base, shift, ctrl+alt, ctrl+alt+shift, [caps vs ncaps?] // Currently, we go in this order so the 102nd key works. But this is not ideal for keyboards without 102nd key: // I4651 // it catches only the first key that matches a given rule, but multiple keys may match that rule. This is particularly // evident for the 102nd key on UK, for example, where \ can be generated with VK_OEM_102 or AltGr+VK_QUOTE. // For now, we get the least shifted version, which is hopefully adequate. GdkKeymap* keymap; if (InitializeGDK(&keymap, argc, argv)) { printf("ERROR: can't Initialize GDK\n"); return FALSE; } // create vector that contains Keycode, base, shift for US-KEyboard and underlying keyboard vec_dword_3D all_vector; if (createOneVectorFromBothKeyboards(all_vector, keymap)) { printf("ERROR: can't create one vector from both keyboards\n"); return FALSE; } vec_dword_2D dk_Table; create_DKTable(dk_Table); for (int j = 0; VKShiftState[j] != 0xFFFF; j++) { // I4651 // Loop through each possible key on the keyboard for (int i = 0; KMX_VKMap[i]; i++) { // I4651 // windows uses VK, Linux uses SC/Keycode KMX_DWORD scUnderlying = (KMX_DWORD)KMX_get_KeyCodeUnderlying_From_VKUS(KMX_VKMap[i]); KMX_WCHAR ch = KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(keymap, scUnderlying, VKShiftState[j], &DeadKey); // printf("--- VK_%d -> SC_ [%c] dk=%d ( ss %i) \n", KMX_VKMap[i], ch == 0 ? 32 : ch, DeadKey, VKShiftState[j]); if (bDeadkeyConversion) { // I4552 if (ch == 0xFFFF) { ch = DeadKey; } } switch (ch) { case 0x0000: break; case 0xFFFF: KMX_ConvertDeadkey(kbd, KMX_VKMap[i], VKShiftState[j], DeadKey, all_vector, keymap, dk_Table); break; default: KMX_TranslateKeyboard(kbd, KMX_VKMap[i], VKShiftState[j], ch); } } } KMX_ReportUnconvertedKeyboardRules(kbd); if (!KMX_ImportRules(kbd, all_vector, &keymap, &KMX_FDeadkeys, bDeadkeyConversion)) { // I4353 // I4552 return FALSE; } return TRUE; } /** * @brief return a vector of [usvk, ch_out] pairs: all existing combinations of a deadkey + character for the underlying keyboard * @param dk_Table shiftstate of the deadkey * @param deadkey deadkey character * @param[out] dk_vec vector of [usvk, shiftstate, ch_out] triplets * @param keymap pointer to the currently used (underlying) keyboard Layout * @return size of vector of [usvk, shiftstate, ch_out] triplets */ int KMX_GetDeadkeys(vec_dword_2D& dk_Table, KMX_WORD deadkey, std::vector &dk_vec, GdkKeymap* keymap) { KMX_DWORD shift; vec_dword_2D dk_SingleTable; query_dk_combinations_for_specific_dk(dk_Table, deadkey, dk_SingleTable); for (int i = 0; i < (int)dk_SingleTable.size(); i++) { KMX_WORD vk = KMX_change_keyname_to_capital(dk_SingleTable[i][1], shift, keymap); if (vk != 0) { dk_vec.push_back(vk); dk_vec.push_back(shift); dk_vec.push_back(dk_SingleTable[i][2]); } else { KMX_LogError(L"Warning: complex deadkey not supported."); } } return dk_vec.size(); } /** * @brief print (error) messages * @param fmt text to print */ void KMX_LogError(const wchar_t* fmt, ...) { wchar_t fmtbuf[256]; const wchar_t* end = L"\0"; const wchar_t* nl = L"\n"; va_list vars; int j = 0; va_start(vars, fmt); vswprintf(fmtbuf, _countof(fmtbuf), fmt, vars); fmtbuf[255] = 0; do { putwchar(fmtbuf[j]); j++; } while (fmtbuf[j] != *end); putwchar(*nl); }