diff --git a/VERSION.md b/VERSION.md index 689f7b264ea..079e4ba078c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.126 \ No newline at end of file +18.0.126 diff --git a/common/include/km_types.h b/common/include/km_types.h index 68d741afb01..05bc0aa432b 100644 --- a/common/include/km_types.h +++ b/common/include/km_types.h @@ -1,8 +1,5 @@ #pragma once #include - -#include - /* #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/linux/mcompile/keymap/build.sh b/linux/mcompile/keymap/build.sh index 7c66e4b0b49..d7c6aac34f8 100755 --- a/linux/mcompile/keymap/build.sh +++ b/linux/mcompile/keymap/build.sh @@ -4,6 +4,7 @@ # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +. "${THIS_SCRIPT%/*}/../../../resources/build/meson-utils.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE #. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" @@ -26,38 +27,7 @@ builder_describe_outputs \ TARGET_PATH="$THIS_SCRIPT_PATH/build" -do_clean() { - rm -rf "$THIS_SCRIPT_PATH/resources" - rm -rf "$TARGET_PATH" -} - -do_configure() { - # Import our standard compiler defines; this is copied from - # /resources/build/meson/standard.meson.build by build.sh, because meson doesn't - # allow us to reference a file outside its root - mkdir -p "$THIS_SCRIPT_PATH/resources" - cp "$KEYMAN_ROOT/resources/build/meson/standard.meson.build" "$THIS_SCRIPT_PATH/resources/meson.build" - - pushd "$THIS_SCRIPT_PATH" > /dev/null - # Additional arguments are used by Linux build, e.g. -Dprefix=${INSTALLDIR} - meson setup build --buildtype $BUILDER_CONFIGURATION "${builder_extra_params[@]}" - popd > /dev/null - -} - -do_build() { - pushd "$TARGET_PATH" > /dev/null - ninja - popd > /dev/null -} - -do_test() { - pushd "$TARGET_PATH" > /dev/null - meson test "${builder_extra_params[@]}" - popd > /dev/null -} - -builder_run_action clean do_clean -builder_run_action configure do_configure -builder_run_action build do_build -builder_run_action test do_test +builder_run_action clean do_meson_clean +builder_run_action configure do_meson_configure +builder_run_action build do_meson_build +builder_run_action test do_meson_test diff --git a/mac/build.sh b/mac/build.sh index fbca48bd05f..c9dedc04ae6 100755 --- a/mac/build.sh +++ b/mac/build.sh @@ -20,6 +20,7 @@ builder_describe "Builds Keyman for macOS." \ "install Installs result of Keyman4MacIM locally." \ ":engine KeymanEngine4Mac" \ ":app Keyman4MacIM" \ + ":mcompile mnemonic layout recompiler- mac" \ ":testapp Keyman4Mac (test harness)" \ "--quick,-q Bypasses notarization for $(builder_term install)" diff --git a/mac/mcompile/.gitignore b/mac/mcompile/.gitignore new file mode 100644 index 00000000000..f59fab25960 --- /dev/null +++ b/mac/mcompile/.gitignore @@ -0,0 +1,2 @@ +resources/ +build/ \ No newline at end of file diff --git a/mac/mcompile/build.sh b/mac/mcompile/build.sh new file mode 100755 index 00000000000..c689cd7a39b --- /dev/null +++ b/mac/mcompile/build.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh" +. "${THIS_SCRIPT%/*}/../../resources/build/meson-utils.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +################################ Main script ################################ + +builder_describe \ + "Mnemonic layout recompiler for macOS" \ + "@/common/include" \ + "clean" \ + "configure" \ + "build" \ + "test" + +builder_parse "$@" + +builder_describe_outputs \ + configure build/build.ninja \ + build build/mcompile + +TARGET_PATH="$THIS_SCRIPT_PATH/build" + +builder_run_action clean do_meson_clean +builder_run_action configure do_meson_configure +builder_run_action build do_meson_build +builder_run_action test do_meson_test diff --git a/mac/mcompile/keymap.cpp b/mac/mcompile/keymap.cpp new file mode 100644 index 00000000000..bb939e0cd0c --- /dev/null +++ b/mac/mcompile/keymap.cpp @@ -0,0 +1,894 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for mac + * + * Throughout mcompile we use the following naming conventions: + * KEYCODE: (name on Linux, Mac):The physical position of a key on a keyboard e.g. Keycode for 'Z' on US: 6 on Mac | 52 on Linux/x11 | 44 on Windows + * SCANCODE (name on Windows): The physical position of a key on a keyboard e.g. Keycode for 'Z' on US: 44 on Windows + * VIRTUAL KEY: The value of a character on a key e.g. 'A' = 65; 'a' = 97 - not neccessarily the same as ACSII- exists on a Windows keyboard only + * KEYVAL(UE): The value of a character on a key e.g. 'A' = 65; 'a' = 97 - not neccessarily the same as ACSII + */ + +#include "keymap.h" +#include "kmx_file.h" + +const KMX_DWORD max_shiftstate = 10; +const KMX_DWORD INVALID_NAME = 0; +const KMX_DWORD keycode_max = 50; +const int keycode_spacebar = 49; + +const int MAC_BASE = 0; +const int MAC_SHIFT = 2; +const int MAC_OPT = 8; +const int MAC_SHIFT_OPT = 10; + +// KeyValues for the US English keyboard: A, S, D, F, H, G, Z, X, C, V, §, B, Q, W, E, R, Y, T, 1, 2, 3, 4, 6, 5, =, 9, 7, -, 8, 0, ], O, U, [, I, P,CR, L, J, ', K, ;, \, ,, /, N, M, . +const std::vector us_Base = {97,115,100,102,104,103,122,120,99,118,167,98,113,119,101,114,121,116,49,50,51,52,54,53,61,57,55,45,56,48, 93,111,117, 91,105,112,13,108,106,39,107,59, 92,44,47,110,109,46}; +const std::vector us_Shift = {65, 83, 68, 70, 72, 71, 90, 88,67, 86,177,66, 81, 87, 69, 82, 89, 84,33,64,35,36,94,37,43,40,38,95,42,41,125, 79, 85,123, 73, 80,13, 76, 74,34, 75,58,124,60,63, 78, 77,62}; + +// Map of all US English virtual key codes that we can translate +const KMX_DWORD KMX_VKMap[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + + VK_SPACE, /* 32 */ + + VK_ACCENT, /* 192 VK_OEM_3 K_BKQUOTE */ + VK_HYPHEN, /* - 189 VK_OEM_MINUS */ + VK_EQUAL, /* = 187 VK_OEM_PLUS */ + + VK_LBRKT, /* [ 219 VK_OEM_4 */ + VK_RBRKT, /* ] 221 VK_OEM_6 */ + VK_BKSLASH, /* \ 220 VK_OEM_5 */ + + VK_COLON, /* ; 186 VK_OEM_1 */ + VK_QUOTE, /* ' 222 VK_OEM_7 */ + + VK_COMMA, /* , 188 VK_OEM_COMMA */ + VK_PERIOD, /* . 190 VK_OEM_PERIOD */ + VK_SLASH, /* / 191 VK_OEM_2 */ + + VK_xDF, /* ß (?) 223*/ + VK_OEM_102, /* < > | 226 */ + 0}; + +/** + * @brief array of USVirtualKey-ScanCode-pairs + * we use the same type of array as throughout Keyman even though we have lots of unused fields + */ +const KMX_DWORD mac_USVirtualKeyToScanCode[256] = { + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0x31, // L"K_SPACE", // &H20 + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0x1D, // L"K_0", // &H30 + 0x12, // L"K_1", // &H31 + 0x13, // L"K_2", // &H32 + 0x14, // L"K_3", // &H33 + 0x15, // L"K_4", // &H34 + 0x17, // L"K_5", // &H35 + 0x16, // L"K_6", // &H36 + 0x1A, // L"K_7", // &H37 + 0x1C, // L"K_8", // &H38 + 0x19, // L"K_9", // &H39 + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0x00, // L"K_A", // &H41 + 0x0B, // L"K_B", // &H42 + 0x08, // L"K_C", // &H43 + 0x02, // L"K_D", // &H44 + 0x0E, // L"K_E", // &H45 + 0x03, // L"K_F", // &H46 + 0x05, // L"K_G", // &H47 + 0x04, // L"K_H", // &H48 + 0x22, // L"K_I", // &H49 + 0x26, // L"K_J", // &H4A + 0x28, // L"K_K", // &H4B + 0x25, // L"K_L", // &H4C + 0x2E, // L"K_M", // &H4D + 0x2D, // L"K_N", // &H4E + 0x1F, // L"K_O", // &H4F + 0x23, // L"K_P", // &H50 + 0x0C, // L"K_Q", // &H51 + 0x0F, // L"K_R", // &H52 + 0x01, // L"K_S", // &H53 + 0x11, // L"K_T", // &H54 + 0x20, // L"K_U", // &H55 + 0x09, // L"K_V", // &H56 + 0x0D, // L"K_W", // &H57 + 0x07, // L"K_X", // &H58 + 0x10, // L"K_Y", // &H59 + 0x06, // L"K_Z", // &H5A + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0x29, // L"K_COLON", // &HBA (186) + 0x18, // L"K_EQUAL", // &HBB (187) + 0x2B, // L"K_COMMA", // &HBC (188) + 0x1B, // L"K_HYPHEN", // &HBD (189) + 0x2F, // L"K_PERIOD", // &HBE (190) + 0x2C, // L"K_SLASH", // &HBF (191) + 0x0A, // L"K_BKQUOTE", // &HC0 (192) + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0x21, // L"K_LBRKT", // &HDB (219) + 0x2A, // L"K_BKSLASH", // &HDC (220) + 0x1E, // L"K_RBRKT", // &HDD (221) + 0x27, // L"K_QUOTE", // &HDE (222) + 0x1B, // L"K_oDF", // &HDF (223) + 0xFFFF, // not used + 0xFFFF, // not used + 0x32, // L"K_oE2", // &HE2 (226) + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used + 0xFFFF, // not used +}; + +/** + * @brief array of ScanCode-USVirtualKey-pairs + * we use the same type of array as throughout Keyman even though we have lots of unused fields + */ +const KMX_DWORD mac_ScanCodeToUSVirtualKey[128] = { + 0x41, // L"K_A", // &H41 + 0x53, // L"K_S", // &H53 + 0x44, // L"K_D", // &H44 + 0x46, // L"K_F", // &H46 + 0x48, // L"K_H", // &H48 + 0x47, // L"K_G", // &H47 + 0x5A, // L"K_Z", // &H5A + 0x58, // L"K_X", // &H58 + 0x43, // L"K_C", // &H43 + 0x56, // L"K_V", // &H56 + 0xC0, // L"K_BKQUOTE", // &HC0 (192) + 0x42, // L"K_B", // &H42 + 0x51, // L"K_Q", // &H51 + 0x57, // L"K_W", // &H57 + 0x45, // L"K_E", // &H45 + 0x52, // L"K_R", // &H52 + 0x59, // L"K_Y", // &H59 + 0x54, // L"K_T", // &H54 + 0x31, // L"K_1", // &H31 + 0x32, // L"K_2", // &H32 + 0x33, // L"K_3", // &H33 + 0x34, // L"K_4", // &H34 + 0x36, // L"K_6", // &H36 + 0x35, // L"K_5", // &H35 + 0xBB, // L"K_EQUAL", // &HBB (187) + 0x39, // L"K_9", // &H39 + 0x37, // L"K_7", // &H37 + 0xBD, // L"K_H YPHEN", // &HBD (189) + 0x38, // L"K_8", // &H38 + 0x30, // L"K_0", // &H30 + 0xDD, // L"K_RBRKT", // &HDD (221) + 0x4F, // L"K_O", // &H4F + 0x55, // L"K_U", // &H55 + 0xDB, // L"K_LBRKT", // &HDB (219) + 0x49, // L"K_I", // &H49 + 0x50, // L"K_P", // &H50 + 0x00, // not used // ---- + 0x4C, // L"K_L", // &H4C + 0x4A, // L"K_J", // &H4A + 0xDE, // L"K_QUOTE", // &HDE (222) + 0x4B, // L"K_K", // &H4B + 0xBA, // L"K_COLON", // &HBA (186) + 0xDC, // L"K_BKSLASH", // &HDC (220) + 0xBC, // L"K_COMMA", // &HBC (188) + 0xBF, // L"K_SLASH", // &HBF (191) + 0x4E, // L"K_N", // &H4E + 0x4D, // L"K_M", // &H4D + 0xBE, // L"K_PERIOD", // &HBE (190) + 0x00, // not used // ---- + 0x20, // L"K_SPACE", // &H1 + 0xE2, // L"K_oE2", // &HE2 (226) + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used + 0x00, // not used +}; + +/** + * @brief map a shiftstate used on Windows to a shiftstate suitable for UCKeyTranslate() on the mac + * Windows: (Base: 00000000 (0); Shift 00010000 (16); AltGr 00001001 (9); Shift+AltGr 00011001 (25)) + * mac: (Base: 0; Shift 2; OPT 8; Shift+OPT 10 ) + * @param shiftState shiftstate used on Windows + * @return a shiftstate usable for UCKeyTranslate() on mac if available + * if shiftState is a Windows ShiftState: convert the Windows ShiftState (0,16,9,25) to a mac ShiftState (0,2,8,10) + * if shiftState is NOT a Windows ShiftState (then in_ShiftState is already a mac shiftstate): return the entered shiftstate + */ +int mac_convert_Shiftstate_to_MacShiftstate(int shiftState) { + if (shiftState == 0) return MAC_BASE; // Win ss 0 -> mac ss 0 + else if (shiftState == K_SHIFTFLAG) return MAC_SHIFT; // Win ss 16 -> mac ss 2 + else if (shiftState == (LCTRLFLAG | RALTFLAG)) return MAC_OPT; // Win ss 9 -> mac ss 8 + else if (shiftState == (K_SHIFTFLAG | LCTRLFLAG | RALTFLAG)) return MAC_SHIFT_OPT; // Win ss 25 -> mac ss 10 + else return shiftState; // Win ss x -> mac ss x +} + +/** + * @brief map a shiftstate used in rgkey (a vector of VirtualKey*) to a shiftstate suitable for UCKeyTranslate() on the mac + * rgkey: (Base: 0; Shift 1; OPT 6; Shift+OPT 7 ) + * mac: (Base: 0; Shift 2; OPT 8; Shift+OPT 10) + * @param rgkey_ShiftState shiftstate used in rgkey + * @return a shiftstate usable for UCKeyTranslate() on mac if available + * if shiftState is a Windows ShiftState: convert the Windows ShiftState (0,16,9,25) to a mac ShiftState (0,2,8,10) + * if shiftState is NOT a Windows ShiftState (then in_ShiftState is already a mac shiftstate): return the entered shiftstate + */ +int mac_convert_rgkey_Shiftstate_to_MacShiftstate(int rgkey_ShiftState) { + if (rgkey_ShiftState == 0) return MAC_BASE; + else if (rgkey_ShiftState == 1) return MAC_SHIFT; + else if (rgkey_ShiftState == 6) return MAC_OPT; + else if (rgkey_ShiftState == 7) return MAC_SHIFT_OPT; + else return rgkey_ShiftState; + } + +/** + * @brief check for correct input parameter that will later be used in UCKeyTranslate() + * @param shiftstate the currently used shiftstate + * @param keycode the code of the key in question + * @return true if all parameters are OK; + * false if not + */ +bool ensureValidInputForKeyboardTranslation(int shiftstate, int keycode) { + if (!(std::find(std::begin(ss_mac), std::end(ss_mac), shiftstate) != std::end(ss_mac))) + return false; + + if (keycode > keycode_max) + return false; + +return true; +} + +/** + * @brief create a 3D-Vector containing data of the US keyboard and the currently used (underlying) keyboard + * all_vector [ US_Keyboard ] + * [KeyCode_US ] + * [Keyval unshifted ] + * [Keyval shifted ] + * [Underlying Kbd] + * [KeyCode_underlying] + * [Keyval unshifted ] + * [Keyval shifted ] + * @param[in,out] all_vector Vector that holds the data of the US keyboard as well as the currently used (underlying) keyboard + * @param keyboard_layout pointer to currently used (underlying) keyboard layout + * @return 0 on success; + * 1 if data of US keyboard was not written; + * 2 if data of underlying keyboard was not written + */ +int mac_createOneVectorFromBothKeyboards(vec_dword_3D& all_vector, const UCKeyboardLayout* keyboard_layout) { + // store contents of the English (US) keyboard in all_vector + if (mac_write_US_ToVector(all_vector)) { + printf("ERROR: can't write full US to Vector \n"); + return 1; + } + + // add contents of underlying keyboard to all_vector + if (mac_append_underlying_ToVector(all_vector, keyboard_layout)) { + printf("ERROR: can't append underlying ToVector \n"); + return 2; + } + return 0; +} + +/** + * @brief write data of the US keyboard into a 3D-Vector which later will contain + * data of the US keyboard and the currently used (underlying) keyboard + * @param[in,out] vec_us Vector that holds the data of the US keyboard + * @return 0 on success; + * 1 if data of US keyboard was not written; + */ +int mac_write_US_ToVector(vec_dword_3D& vec_us) { + vec_dword_1D values; + vec_dword_2D key; + + for (int i = 0; i < us_Base.size(); i++) { + values.push_back(i); + values.push_back(us_Base[i]); + values.push_back(us_Shift[i]); + key.push_back(values); + values.clear(); + } + vec_us.push_back(key); + + if (key.size() == 0) { + printf("ERROR: can't Create Vector for US keyboard\n"); + return 1; + } else if (key.size() < 48) { + printf("ERROR: keyboard not created completely\n"); + return 1; + } else + return 0; +} + +/** + * @brief create an 2D-Vector with all fields initialized to INVALID_NAME + * @param dim_rows number of rows in vector + * @param dim_ss number of columns in vector + * @return the 2D-Vector + */ +vec_dword_2D mac_create_empty_2D_Vector(int dim_rows, int dim_ss) { + vec_dword_1D shifts; + vec_dword_2D vector_2D; + + for (int j = 0; j < dim_ss; j++) { + shifts.push_back(INVALID_NAME); + } + + for (int i = 0; i < dim_rows; i++) { + vector_2D.push_back(shifts); + } + return vector_2D; +} + +/** + * @brief append a 2D-vector containing data of the currently used (underlying) keyboard to the 3D-vector all_vector + * @param[in,out] all_vector 3D-vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param keyboard_layout pointer to currently used (underlying) keybord layout + * @return 0 on success; + * 1 if the initialization of the underlying vector fails; + * 2 if data of less than 2 keyboards is contained in all_vector; + */ +int mac_append_underlying_ToVector(vec_dword_3D& all_vector, const UCKeyboardLayout* keyboard_layout) { + if (all_vector.size() != 1) { + printf("ERROR: data for US keyboard not correct\n"); + return 1; + } + + // create a 2D vector all filled with " " and push to 3D-Vector + vec_dword_2D underlying_Vector2D = mac_create_empty_2D_Vector(all_vector[0].size(), all_vector[0][0].size()); + + if (underlying_Vector2D.size() == 0) { + printf("ERROR: can't create empty 2D-Vector\n"); + return 1; + } + + all_vector.push_back(underlying_Vector2D); + if (all_vector.size() < 2) { + printf("ERROR: creation of 3D-Vector failed\n"); + return 2; + } + + for (int i = 0; i < (int)all_vector[1].size(); i++) { + // get key name US stored in [0][i][0] and copy to name in "underlying"-block[1][i][0] + all_vector[1][i][0] = all_vector[0][i][0]; + + for (int k = 0; k < 2; k++) { // use BASE and SHIFT only + all_vector[1][i][k + 1] = mac_KMX_get_KeyVal_From_KeyCode(keyboard_layout, all_vector[0][i][0], mac_convert_rgkey_Shiftstate_to_MacShiftstate(k), 0); + } + } + + return 0; +} + +/** + * @brief initializes GDK and return the current keyboard_layout for later use + * @param keyboard_layoutout[out] currently used (underlying) keyboard layout + * @return 0 on success; + * 1 if the display is not found; + * 2 if the keymap is not found + */ +bool mac_InitializeUCHR(const UCKeyboardLayout** keyboard_layout) { + TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); + if (!source) { + printf("ERROR: can't get source\n"); + return TRUE; + } + + CFDataRef layout_data = static_cast((TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData))); + *keyboard_layout = reinterpret_cast(CFDataGetBytePtr(layout_data)); + if (!keyboard_layout) { + printf("ERROR: Can't get keyboard_layout\n"); + return TRUE; + } + // intentionally leaking `source` in order to still be able to access `keyboard_layout` + return FALSE; +} + +/** + * @brief return the keyvalue for a given Keycode, shiftstate and caps of the + * currently used (underlying) keyboard layout + * "What character will be produced for a keypress of a key and modifier?" + * @param keyboard_layout pointer to the currently used (underlying) keyboard layout + * @param keycode a key of the currently used keyboard layout + * @param shiftstate_mac a shiftstate of the currently used keyboard layout + * @param caps state of the caps key of the currently used keyboard layout + * @return the keyval obtained from keycode, shiftstate and caps + */ +KMX_DWORD mac_KMX_get_KeyVal_From_KeyCode(const UCKeyboardLayout* keyboard_layout, int keycode, int shiftstate_mac, int caps) { + UInt32 deadkeystate; + const UniCharCount maxStringlength = 5; + UniCharCount actualStringlength = 0; + OptionBits keyTranslateOptions = 0; + UniChar unicodeString[maxStringlength]; + OSStatus status; + unicodeString[0] = 0; + + if (!ensureValidInputForKeyboardTranslation(shiftstate_mac, keycode)) + return 0; + + /* + UCKeyTranslate != 0 if a dk was found; then run UCKeyTranslate again with a SPACE (keycode_spacebar) to get the plain dk e.g.'^'. + If CAPS is used: always add 4 e.g. SHIFT = 2; SHIFT+CAPS = 6 + */ + status = UCKeyTranslate(keyboard_layout, keycode, kUCKeyActionDown, (shiftstate_mac + 4 * caps), LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + + // If this was a deadkey (deadkeystate != 0), append a space + if (deadkeystate != 0) + status = UCKeyTranslate(keyboard_layout, keycode_spacebar, kUCKeyActionDown, (shiftstate_mac + 4 * caps), LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + + if (status != noErr) // in case UCKeyTranslate returned an error + return 0; + + // if there is no character assigned to the Key+Shift+CAPS UCKeyTranslate writes 0x01 into unicodeString[0] + if (unicodeString[0] == 1) // impossible character + return 0; + else { + return unicodeString[0]; // combined char e.g. 'â' + } +} + +/** + * @brief return the keyvalue for a given Keycode, shiftstate and caps of the + * currently used (underlying) keyboard layout taking dk into account + * "What character will be produced for a keypress of a key and modifiers? + * @param keyboard_layout pointer to the currently used (underlying) keyboard layout + * @param keycode a key of the currently used keyboard layout + * @param shiftstate_mac a shiftstate of the currently used keyboard layout + * @param caps state of the caps key of the currently used keyboard layout + * @param[in,out] deadkeystate states wheter a deadkey was used or not + * @return the keyval obtained from keycode, shiftstate and caps + */ +KMX_DWORD mac_KMX_get_KeyVal_From_KeyCode_dk(const UCKeyboardLayout* keyboard_layout, int keycode, int shiftstate_mac, int caps, UInt32& deadkeystate) { + const UniCharCount maxStringlength = 5; + UniCharCount actualStringlength = 0; + OptionBits keyTranslateOptions = 0; + UniChar unicodeString[maxStringlength]; + unicodeString[0] = 0; + OSStatus status; + + if (!ensureValidInputForKeyboardTranslation(shiftstate_mac, keycode)) + return 0; + + /* + UCKeyTranslate != 0 if a dk was found; then run UCKeyTranslate again with a SPACE (keycode_spacebar) to get the plain dk e.g.'^'. + If CAPS is used: always add 4 e.g. SHIFT = 2; SHIFT+CAPS = 6 + */ + status = UCKeyTranslate(keyboard_layout, keycode, kUCKeyActionDown, (shiftstate_mac + 4 * caps), LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + + // If this was a deadkey, append a space + if (deadkeystate != 0) + status = UCKeyTranslate(keyboard_layout, keycode_spacebar, kUCKeyActionDown,(shiftstate_mac + 4 * caps), LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + + if (status != noErr) // in case UCKeyTranslate returned an error + return 0; + + // if there is no character assigned to the Key+Shift+CAPS UCKeyTranslate writes 0x01 into unicodeString[0] + if (unicodeString[0] == 1) // impossible character + return 0; + else + return unicodeString[0]; // combined char e.g. 'â' +} + +/** + * @brief return the keyvalue for a given Keycode and shiftstate of the currently used (underlying) keyboard layout. + * "What character will be produced for a keypress of a key and modifiers on the underlying keyboard? + * If a deadkey was found return 0xFFFF and copy the deadkey into deadKey + * @param keyboard_layout a pointer to the currently used (underlying) keyboard layout + * @param kc_underlying a key of the currently used keyboard + * @param vk_ShiftState a shiftstate of the currently used keyboard layout + * @param deadKey pointer to keyvalue if a deadkey was found; if not NULL + * @return 0xFFFF in case a deadkey was found, then the deadkey is stored in deadKey; + * or else the keyval obtained from Keycode and shiftstate and caps; + */ +KMX_DWORD mac_KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(const UCKeyboardLayout* keyboard_layout, KMX_DWORD kc_underlying, KMX_DWORD vk_ShiftState, PKMX_WCHAR deadKey) { + UInt32 isdk = 0; + KMX_DWORD keyV; + int caps = 0; + + if (!ensureValidInputForKeyboardTranslation(mac_convert_Shiftstate_to_MacShiftstate(vk_ShiftState), kc_underlying)) + return 0; + + keyV = mac_KMX_get_KeyVal_From_KeyCode_dk(keyboard_layout, kc_underlying, (mac_convert_Shiftstate_to_MacShiftstate(vk_ShiftState)), caps, isdk); + + // if there was a deadkey return 0xFFFF and copy deadkey into dky; else return the keyvalue + if (isdk != 0) { + PKMX_WCHAR dky = NULL; + std::u16string keyVS(1, keyV); + dky = (PKMX_WCHAR)keyVS.c_str(); + *deadKey = *dky; + return 0xFFFF; + } + *deadKey = 0; + return keyV; +} + +/** + * @brief return the keyvalue of a key of the the currently used (underlying) keyboard for a given keyvalue of the US keyboard + * "What character is on the same position/shiftstats/caps on the currently used (underlying) keyboard as on the US keyboard?" + * @param all_vector 3D-vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param kv_us a keyvalue on the US keyboard + * @return keyval of the underlying keyboard if available; + * else the keyval of the US keyboard + */ +KMX_DWORD mac_KMX_get_KeyValUnderlying_From_KeyValUS(vec_dword_3D& all_vector, KMX_DWORD kv_us) { + // look for kv_us for any shiftstate of US keyboard + for (int i = 0; i < (int)all_vector[0].size() - 1; i++) { + for (int j = 1; j < (int)all_vector[0][0].size(); j++) { + if (all_vector[0][i][j] == kv_us) + return all_vector[1][i][j]; + } + } + return kv_us; +} + +/** + * @brief return the keycode of the currently used (underlying) keyboard for a given keyvalue of the underlying keyboard + * On what key of the underlying keyboard do we find a certain character? + * @param all_vector 3D-vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param kv_underlying a keyvalue on the currently used (underlying) keyboard + * @return keycode of the underlying keyboard if foundf; + * else the keyval of the underlying keyboard + */ +KMX_DWORD mac_KMX_get_KeyCodeUnderlying_From_KeyValUnderlying(vec_dword_3D& all_vector, KMX_DWORD kv_underlying) { + // look for kv_us for any shiftstate of US keyboard + for (int i = 0; i < all_vector[1].size() - 1; i++) { + for (int j = 1; j < all_vector[1][0].size(); j++) { + if (all_vector[1][i][j] == kv_underlying) { + return all_vector[1][i][0]; + } + } + } + return kv_underlying; +} + +/** + * @brief return the keycode of the currently used (underlying) keyboard for a given keycode of a character on the US keyboard + * "Where on an underlying keyboard do we find a character that is on a certain key on a US keyboard?" + * @param keyboard_layout the currently used (underlying) keyboard layout + * @param all_vector 3D-vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param kc_us a key of the US keyboard + * @param ss_win a Windows-type shiftstate + * @param caps state of the caps key + * @return the keycode of the underlying keyboard if found; + * else the keycode of the US keyboard + */ +KMX_DWORD mac_KMX_get_KeyCodeUnderlying_From_KeyCodeUS(const UCKeyboardLayout* keyboard_layout, vec_dword_3D& all_vector, KMX_DWORD kc_us, ShiftState ss_win, int caps) { + // first get the keyvalue kv of the key on the US keyboard (kc_us) + KMX_DWORD kv = mac_KMX_get_KeyVal_From_KeyCode(keyboard_layout, kc_us, mac_convert_rgkey_Shiftstate_to_MacShiftstate(ss_win), caps); + + // then find the same keyvalue on the underlying keyboard and return the keycode of that key on the underlying keyboard + for (int i = 0; i < (int)all_vector[1].size() - 1; i++) { + for (int j = 1; j < (int)all_vector[1][0].size(); j++) { + if (all_vector[1][i][j] == kv) + return all_vector[1][i][0]; + } + } + return kc_us; +} + +/** + * @brief return the keycode of the currently used (underlying) keyboard for a given virtual key of the US keyboard + * "Where on an underlying keyboard do we find a character of a US keyboard?" + * @param virtualKeyUS a virtual key of the US keyboard + * @return the keycode of the currently used (underlying) keyboard + * 0xFFFF if the key is not used + */ +KMX_DWORD mac_KMX_get_KeyCodeUnderlying_From_VKUS(KMX_DWORD virtualKeyUS) { + // on the mac virtual keys do not exist. Nevertheless we can use this mapping to obtain an 'artificial' us virtual key from a keycode + return (mac_USVirtualKeyToScanCode[virtualKeyUS]); +} + +/** + * @brief return a virtual key of the US keyboard for a given keycode of the currently used (underlying) keyboard + * "Which character is found on a key of the US keyboard?" + * @param keycode a keycode of the currently used (underlying) keyboard + * @return the virtual key of the US keyboard + * 0 if the key is not used + */ +KMX_DWORD mac_KMX_get_VKUS_From_KeyCodeUnderlying(KMX_DWORD keycode) { + // on the mac virtual keys do not exist. Nevertheless we can use this mapping to obtain a keycode from an 'artificial' us virtual key + return mac_ScanCodeToUSVirtualKey[keycode]; +} + +/** + * @brief return the keyvalue of a combination of deadkey + character if there is a combination available + * "What character will be produced for a deadkey + a character?" e.g. '^' + 'a' -> 'â' + * @param keyboard_layout the currently used (underlying)keyboard Layout + * @param vk_dk a keycode of a deadkey of the currently used (underlying) keyboard + * @param ss_dk a shiftstate of a deadkey of the currently used (underlying) keyboard + * @param vk_us a keycode of a character key on the currently used (underlying) keyboard to be combined to a dk + * @param shiftstate_mac a shiftstate of a character key on the currently used (underlying) keyboard + * @param caps state of the caps key of a character key on the currently used (underlying) keyboard + * @return the combination of deadkey + character if it is available; + * if not return 0 + */ + KMX_DWORD mac_get_CombinedChar_From_DK(const UCKeyboardLayout* keyboard_layout, int vk_dk, KMX_DWORD ss_dk, KMX_DWORD vk_us, KMX_DWORD shiftstate_mac, int caps) { + UInt32 deadkeystate; + const UniCharCount maxStringlength = 5; + UniCharCount actualStringlength = 0; + OptionBits keyTranslateOptions = 0; + UniChar unicodeString[maxStringlength]; + unicodeString[0] = 0; + OSStatus status; + + /* + UCKeyTranslate != 0 if a dk was found; then run UCKeyTranslate again with a base character (vk_us) to get the combined dk e.g. '^' + 'A' -> 'Â' + If CAPS is used: always add 4 e.g. SHIFT = 2; SHIFT+CAPS = 6 + */ + status = UCKeyTranslate(keyboard_layout, vk_dk, kUCKeyActionDown, ss_dk, LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + + // If this was a deadkey, append a character + if (deadkeystate != 0) + status = UCKeyTranslate(keyboard_layout, vk_us, kUCKeyActionDown, shiftstate_mac + 4 * caps, LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + + if (status != noErr) // in case UCKeyTranslate returned an error + return 0; + + if (unicodeString[0] == 1) // impossible character + return 0; + else + return unicodeString[0]; // combined char e.g. 'â' +} diff --git a/mac/mcompile/keymap.h b/mac/mcompile/keymap.h new file mode 100644 index 00000000000..99e563b4c01 --- /dev/null +++ b/mac/mcompile/keymap.h @@ -0,0 +1,134 @@ +#pragma once +#ifndef KEYMAP_H +#define KEYMAP_H + +#include +#include +#include +#include + +#include "../../common/include/km_u16.h" + +enum ShiftState { + Base = 0, // 0 + Shft = 1, // 1 + Ctrl = 2, // 2 + ShftCtrl = Shft | Ctrl, // 3 + Menu = 4, // 4 -- NOT USED + ShftMenu = Shft | Menu, // 5 -- NOT USED + MenuCtrl = Menu | Ctrl, // 6 + ShftMenuCtrl = Shft | Menu | Ctrl, // 7 + Xxxx = 8, // 8 + ShftXxxx = Shft | Xxxx, // 9 +}; + +#define VK_SPACE 0x20 +#define VK_COLON 0xBA +#define VK_EQUAL 0xBB +#define VK_COMMA 0xBC +#define VK_HYPHEN 0xBD +#define VK_PERIOD 0xBE +#define VK_SLASH 0xBF +#define VK_ACCENT 0xC0 +#define VK_LBRKT 0xDB +#define VK_BKSLASH 0xDC +#define VK_RBRKT 0xDD +#define VK_QUOTE 0xDE +#define VK_xDF 0xDF +#define VK_OEM_102 0xE2 // "<>" or "\|" on RT 102-key kbd. + +#define VK_DIVIDE 0x6F +#define VK_CANCEL 3 +#define VK_DECIMAL 0x2E + +#define VK_OEM_CLEAR 0xFE +#define VK_LSHIFT 0xA0 +#define VK_RSHIFT 0xA1 +#define VK_LCONTROL 0xA2 +#define VK_RCONTROL 0xA3 +#define VK_LMENU 0xA4 +#define VK_RMENU 0xA5 + +#define VK_SHIFT 0x10 +#define VK_CONTROL 0x11 +#define VK_MENU 0x12 +#define VK_PAUSE 0x13 +#define VK_CAPITAL 0x14 + +typedef std::vector vec_string_1D; +typedef std::vector vec_dword_1D; +typedef std::vector > vec_dword_2D; +typedef std::vector > > vec_dword_3D; + +extern const KMX_DWORD max_shiftstate; +extern const KMX_DWORD INVALID_NAME; +extern const KMX_DWORD keycode_max; +extern const int keycode_spacebar; + +// shiftstates we can use for mac +extern const int MAC_BASE; +extern const int MAC_SHIFT; +extern const int MAC_OPT; +extern const int MAC_SHIFT_OPT; + +extern const KMX_DWORD KMX_VKMap[]; +const int ss_mac[] = {MAC_BASE, MAC_SHIFT, MAC_OPT, MAC_SHIFT_OPT}; + +/** @brief map a shiftstate used on Windows to a shiftstate suitable for UCKeyTranslate() on the mac */ +int mac_convert_Shiftstate_to_MacShiftstate(int shiftState); + +/** @brief map a shiftstate used in rgkey (a vector of VirtualKey*) to a shiftstate suitable for UCKeyTranslate() on the mac */ +int mac_convert_rgkey_Shiftstate_to_MacShiftstate(int rgkey_ShiftState); + +/** @brief check for correct input parameter that will later be used in UCKeyTranslate() */ +bool ensureValidInputForKeyboardTranslation(int shiftstate, int keycode); + +/** @brief create a 3D-Vector containing data of the US keyboard and the currently used (underlying) keyboard */ +int mac_createOneVectorFromBothKeyboards(vec_dword_3D& all_vector, const UCKeyboardLayout* keyboard_layout); + +/** @brief write data of the US keyboard into a 3D-Vector */ +int mac_write_US_ToVector(vec_dword_3D& vec_us); + +/** @brief create an 2D-Vector with all fields initialized to INVALID_NAME */ +vec_dword_2D mac_create_empty_2D_Vector(int dim_rows, int dim_ss); + +/** @brief append a 2D-vector containing data of the currently used (underlying) keyboard to the 3D-vector */ +int mac_append_underlying_ToVector(vec_dword_3D& all_vector, const UCKeyboardLayout* keyboard_layout); + +/** @brief create a pointer to pointer of the current keymap for later use */ +bool mac_InitializeUCHR(const UCKeyboardLayout** keyboard_layout); + +/** @brief array of USVirtualKey-ScanCode-pairs */ +extern const KMX_DWORD mac_USVirtualKeyToScanCode[256]; + +/** @brief array of ScanCode-USVirtualKey-pairs */ +extern const KMX_DWORD mac_ScanCodeToUSVirtualKey[128]; + +/** @brief return the keyvalue for a given Keycode, shiftstate and caps */ +KMX_DWORD mac_KMX_get_KeyVal_From_KeyCode(const UCKeyboardLayout* keyboard_layout, int keycode, int shiftstate_mac, int caps); + +/** @brief return the keyvalue for a given Keycode, shiftstate and caps */ +KMX_DWORD mac_KMX_get_KeyVal_From_KeyCode_dk(const UCKeyboardLayout* keyboard_layout, int keycode, int shiftstate_mac, int caps, UInt32& deadkeystate); + +/** @brief return the keyvalue for a given Keycode and shiftstate of the currently used (underlying) keyboard layout. */ +KMX_DWORD mac_KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(const UCKeyboardLayout* keyboard_layout, KMX_DWORD kc_underlying, KMX_DWORD vk_ShiftState, PKMX_WCHAR deadKey); + +/** @brief return the keyvalue of a key of the the currently used (underlying) keyboard for a given keyvalue of the US keyboard */ +KMX_DWORD mac_KMX_get_KeyValUnderlying_From_KeyValUS(vec_dword_3D& all_vector, KMX_DWORD kv_us); + +/** @brief return the keycode of the currently used (underlying) keyboard for a given keyvalue of the underlying keyboard */ +KMX_DWORD mac_KMX_get_KeyCodeUnderlying_From_KeyValUnderlying(vec_dword_3D& all_vector, KMX_DWORD kv_underlying); + +/** @brief return the keycode of the currently used (underlying) keyboard for a given keycode of a character on the US keyboard */ +KMX_DWORD mac_KMX_get_KeyCodeUnderlying_From_KeyCodeUS(const UCKeyboardLayout* keyboard_layout, vec_dword_3D& all_vector, KMX_DWORD kc_us, ShiftState ss_win, int caps); + +/** @brief return the keycode of the currently used (underlying) keyboard for a given virtual key of the US keyboard */ +KMX_DWORD mac_KMX_get_KeyCodeUnderlying_From_VKUS(KMX_DWORD virtualKeyUS); + +/** @brief return a virtual key of the US keyboard for a given keycode of the currently used (underlying) keyboard */ +KMX_DWORD mac_KMX_get_VKUS_From_KeyCodeUnderlying(KMX_DWORD keycode); + +/** @brief return the keyvalue of a combination of deadkey + character if there is a combination available */ +KMX_DWORD mac_get_CombinedChar_From_DK(const UCKeyboardLayout* keyboard_layout, int vk_dk, KMX_DWORD ss_dk, KMX_DWORD vk_us, KMX_DWORD shiftstate_mac, int caps); + +#endif /*KEYMAP_H*/ \ No newline at end of file diff --git a/mac/mcompile/mc_import_rules.cpp b/mac/mcompile/mc_import_rules.cpp new file mode 100644 index 00000000000..73e1f25dd93 --- /dev/null +++ b/mac/mcompile/mc_import_rules.cpp @@ -0,0 +1,697 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for mac + */ + +#include +#include +#include +#include "mc_kmxfile.h" +#include "keymap.h" + +const int KMX_ShiftStateMap[] = { + ISVIRTUALKEY, + ISVIRTUALKEY | K_SHIFTFLAG, + ISVIRTUALKEY | K_CTRLFLAG, + ISVIRTUALKEY | K_SHIFTFLAG | K_CTRLFLAG, + 0, + 0, + ISVIRTUALKEY | RALTFLAG, + ISVIRTUALKEY | RALTFLAG | K_SHIFTFLAG, + 0, + 0}; + + /** + * @brief Constructor + * @param deadCharacter a deadkey + */ +DeadKey::DeadKey(KMX_WCHAR deadCharacter) { + this->m_deadchar = deadCharacter; +} + + /** + * @brief return dead character + * @return deadkey character + */ +KMX_WCHAR DeadKey::KMX_DeadCharacter() { + return this->m_deadchar; +} + + /** + * @brief set member variable with base and combined character + * @param baseCharacter the base character + * @param combinedCharacter the combined character + */ +void DeadKey::KMX_AddDeadKeyRow(KMX_WCHAR baseCharacter, KMX_WCHAR combinedCharacter) { + this->m_rgbasechar.push_back(baseCharacter); + this->m_rgcombchar.push_back(combinedCharacter); +} + +/** + * @brief check if character exists in DeadKey + * @param baseCharacter a character to be found + * @return true if found; false if not found + */ +bool DeadKey::KMX_ContainsBaseCharacter(KMX_WCHAR baseCharacter) { + std::vector::iterator it; + for (it = this->m_rgbasechar.begin(); it < m_rgbasechar.end(); it++) { + if (*it == baseCharacter) { + return true; + } + } + return false; +} + +/** + * @brief Find a keyvalue for given keycode, shiftstate and caps. A function similar to Window`s ToUnicodeEx() function. + * + * Contrary to what the function name might suggest, the function the mac_KMX_ToUnicodeEx does NOT process surrogate pairs. + * This is because it is used in mcompile only which only deals with latin scripts. + * In case this function is used for surrogate pairs, they will be ignored and a message will be printed out + * + * @param keycode a key of the currently used keyboard Layout + * @param pwszBuff Buffer to store resulting character + * @param ss_rgkey a Windows-style shiftstate of the currently used keyboard Layout + * @param caps state of the caps key of the currently used keyboard Layout + * @param keyboard_layout the currently used (underlying)keyboard Layout + * @return -1 if a deadkey was found; + * 0 if no translation is available; + * +1 if character was found and written to pwszBuff + */ +int mac_KMX_ToUnicodeEx(int keycode, PKMX_WCHAR pwszBuff, ShiftState ss, int caps, const UCKeyboardLayout* keyboard_layout) { + KMX_DWORD keyval; + UInt32 isdk = 0; + + if (!ensureValidInputForKeyboardTranslation(mac_convert_rgkey_Shiftstate_to_MacShiftstate(ss), keycode)) + return 0; + + keyval = mac_KMX_get_KeyVal_From_KeyCode_dk(keyboard_layout, keycode, mac_convert_rgkey_Shiftstate_to_MacShiftstate(ss), caps, isdk); + std::u16string str = std::u16string(1, keyval); + KMX_WCHAR firstchar = *(PKMX_WCHAR)str.c_str(); + + if ((firstchar >= 0xD800) && (firstchar <= 0xDFFF)) { + wprintf(L"Surrogate pair found that is not processed in KMX_ToUnicodeEx\n"); + return 0; + } + + pwszBuff[0] = firstchar; + + if (u16len(pwszBuff) < 1) + return 0; + + if ((isdk) && (keycode != 0xFFFF)) // deadkeys + return -1; + if (keyval == 0) // no character + return 0; + else // usable char + return 1; +} + +KMX_WCHAR mac_KMX_DeadKeyMap(int index, std::vector*deadkeys, int deadkeyBase, std::vector*deadkeyMappings) { // I4327 // I4353 + for (size_t i = 0; i < deadkeyMappings->size(); i++) { + if ((*deadkeyMappings)[i].deadkey == index) { + return (*deadkeyMappings)[i].dkid; + } + } + + for (size_t i = 0; i < deadkeys->size(); i++) { + if ((*deadkeys)[i]->KMX_DeadCharacter() == index) { + return (KMX_WCHAR)(deadkeyBase + i); + } + } + return 0xFFFF; +} + +/** + * @brief Base class for dealing with rgkey +*/ +class mac_KMX_VirtualKey { +private: + KMX_DWORD m_vk; + KMX_DWORD m_sc; + bool m_rgfDeadKey[10][2]; + std::u16string m_rgss[10][2]; + +public: + mac_KMX_VirtualKey(KMX_DWORD scanCode) { + this->m_vk = mac_KMX_get_VKUS_From_KeyCodeUnderlying(scanCode); + this->m_sc = scanCode; + memset(this->m_rgfDeadKey, 0, sizeof(this->m_rgfDeadKey)); + } + +/** @brief return member variable virtual key */ + KMX_DWORD VK() { + return this->m_vk; + } + +/** @brief return member variable scancode */ + KMX_DWORD SC() { + return this->m_sc; + } + + std::u16string mac_KMX_GetShiftState(ShiftState shiftState, bool capsLock) { + return this->m_rgss[(KMX_DWORD)shiftState][(capsLock ? 1 : 0)]; + } + + void mac_KMX_SetShiftState(ShiftState shiftState, std::u16string value, bool isDeadKey, bool capsLock) { + this->m_rgfDeadKey[(KMX_DWORD)shiftState][(capsLock ? 1 : 0)] = isDeadKey; + this->m_rgss[(KMX_DWORD)shiftState][(capsLock ? 1 : 0)] = value; + } + + bool mac_KMX_IsSGCAPS() { + std::u16string stBase = this->mac_KMX_GetShiftState(Base, false); + std::u16string stShift = this->mac_KMX_GetShiftState(Shft, false); + std::u16string stCaps = this->mac_KMX_GetShiftState(Base, true); + std::u16string stShiftCaps = this->mac_KMX_GetShiftState(Shft, true); + return ( + ((stCaps.size() > 0) && + (stBase.compare(stCaps) != 0) && + (stShift.compare(stCaps) != 0)) || + ((stShiftCaps.size() > 0) && + (stBase.compare(stShiftCaps) != 0) && + (stShift.compare(stShiftCaps) != 0))); + } + + bool mac_KMX_IsCapsEqualToShift() { + std::u16string stBase = this->mac_KMX_GetShiftState(Base, false); + std::u16string stShift = this->mac_KMX_GetShiftState(Shft, false); + std::u16string stCaps = this->mac_KMX_GetShiftState(Base, true); + return ( + (stBase.size() > 0) && + (stShift.size() > 0) && + (stBase.compare(stShift) != 0) && + (stShift.compare(stCaps) == 0)); + } + + bool mac_KMX_IsAltGrCapsEqualToAltGrShift() { + std::u16string stBase = this->mac_KMX_GetShiftState(MenuCtrl, false); + std::u16string stShift = this->mac_KMX_GetShiftState(ShftMenuCtrl, false); + std::u16string stCaps = this->mac_KMX_GetShiftState(MenuCtrl, true); + return ( + (stBase.size() > 0) && + (stShift.size() > 0) && + (stBase.compare(stShift) != 0) && + (stShift.compare(stCaps) == 0)); + } + + bool mac_KMX_IsXxxxGrCapsEqualToXxxxShift() { + std::u16string stBase = this->mac_KMX_GetShiftState(Xxxx, false); + std::u16string stShift = this->mac_KMX_GetShiftState(ShftXxxx, false); + std::u16string stCaps = this->mac_KMX_GetShiftState(Xxxx, true); + return ( + (stBase.size() > 0) && + (stShift.size() > 0) && + (stBase.compare(stShift) != 0) && + (stShift.compare(stCaps) == 0)); + } + + bool mac_KMX_IsEmpty() { + for (int i = 0; i < 10; i++) { + for (int j = 0; j <= 1; j++) { + if (this->mac_KMX_GetShiftState((ShiftState)i, (j == 1)).size() > 0) { + return (false); + } + } + } + return true; + } + +/** @brief check if we use only keys used in mcompile */ + bool mac_KMX_IsKeymanUsedKey() { + return (this->m_vk >= 0x20 && this->m_vk <= 0x5F) || (this->m_vk >= 0x88); + } + + KMX_DWORD KMX_GetShiftStateValue(int capslock, int caps, ShiftState ss) { + return KMX_ShiftStateMap[(int)ss] | (capslock ? (caps ? CAPITALFLAG : NOTCAPITALFLAG) : 0); + } + +/** @brief count the number of keys */ + int mac_KMX_GetKeyCount(int MaxShiftState) { + int nkeys = 0; + + // Get the CAPSLOCK value + for (int ss = 0; ss <= MaxShiftState; ss++) { + if (ss == Menu || ss == ShftMenu) { + // Alt and Shift+Alt don't work, so skip them + continue; + } + for (int caps = 0; caps <= 1; caps++) { + std::u16string st = this->mac_KMX_GetShiftState((ShiftState)ss, (caps == 1)); + // ctrl and shift+ctrl will be skipped since rgkey has no entries in m_rgss[2] m_rgss[3] + if (st.size() == 0) { + // No character assigned here + } else if (this->m_rgfDeadKey[(int)ss][caps]) { + // It's a dead key, append an @ sign. + nkeys++; + } else { + bool isvalid = true; + for (size_t ich = 0; ich < st.size(); ich++) { + if (st[ich] < 0x20 || st[ich] == 0x7F) { + isvalid = false; + wprintf(L"invalid for: %i\n", st[ich]); + break; + } + } + if (isvalid) { + nkeys++; + } + } + } + } + return nkeys; + } + +/** @brief edit the row of kmx-file */ + bool mac_KMX_LayoutRow(int MaxShiftState, LPKMX_KEY key, std::vector* deadkeys, int deadkeyBase, bool bDeadkeyConversion, vec_dword_3D& all_vector, const UCKeyboardLayout* keyboard_layout) { // I4552 + // Get the CAPSLOCK value + /*int capslock = + (this->mac_KMX_IsCapsEqualToShift() ? 1 : 0) | + (this->mac_KMX_IsSGCAPS() ? 2 : 0) | + (this->mac_KMX_IsAltGrCapsEqualToAltGrShift() ? 4 : 0) | + (this->mac_KMX_IsXxxxGrCapsEqualToXxxxShift() ? 8 : 0);*/ + + int capslock = 1; // on mcompile-mac we do not use the equation to obtain capslock. Here we set capslock = 1 + + for (int ss = 0; ss <= MaxShiftState; ss++) { + if (ss == Menu || ss == ShftMenu) { + // Alt and Shift+Alt don't work, so skip them + continue; + } + for (int caps = 0; caps <= 1; caps++) { + std::u16string st = this->mac_KMX_GetShiftState((ShiftState)ss, (caps == 1)); + + PKMX_WCHAR p; + + if (st.size() == 0) { + // No character assigned here + } else if (this->m_rgfDeadKey[(int)ss][caps]) { + // It's a dead key, append an @ sign. + key->dpContext = new KMX_WCHAR[1]; + *key->dpContext = 0; + + key->ShiftFlags = this->KMX_GetShiftStateValue(capslock, caps, (ShiftState)ss); + // we already use VK_US so no need to convert it as we do on Windows + key->Key = this->VK(); + key->Line = 0; + + if (bDeadkeyConversion) { // I4552 + p = key->dpOutput = new KMX_WCHAR[2]; + *p++ = st[0]; + *p = 0; + } else { + p = key->dpOutput = new KMX_WCHAR[4]; + *p++ = UC_SENTINEL; + *p++ = CODE_DEADKEY; + *p++ = mac_KMX_DeadKeyMap(st[0], deadkeys, deadkeyBase, &KMX_FDeadkeys); // I4353 + *p = 0; + } + key++; + } else { + bool isvalid = true; + for (size_t ich = 0; ich < st.size(); ich++) { + if (st[ich] < 0x20 || st[ich] == 0x7F) { + isvalid = false; + wprintf(L"invalid 16 for: %i\n", st[ich]); + break; + } + } + if (isvalid) { + /* + * this is different to mcompile Windows !!!! + * this->m_sc stores SC-US = SCUnderlying + * this->m_vk stores VK-US ( not VK underlying !!) + * key->Key stores VK-US ( not VK underlying !!) + * key->dpOutput stores character Underlying + */ + KMX_DWORD sc_underlying = mac_KMX_get_KeyCodeUnderlying_From_KeyCodeUS(keyboard_layout, all_vector, this->SC(), (ShiftState)ss, caps); + + key->Key = mac_KMX_get_VKUS_From_KeyCodeUnderlying(sc_underlying); + + key->Line = 0; + key->ShiftFlags = this->KMX_GetShiftStateValue(capslock, caps, (ShiftState)ss); + + key->dpContext = new KMX_WCHAR; + *key->dpContext = 0; + p = key->dpOutput = new KMX_WCHAR[st.size() + 1]; + for (size_t ich = 0; ich < st.size(); ich++) { + *p++ = st[ich]; + } + *p = 0; + key++; + } + } + } + } + return true; + } +}; + +/** + * @brief Base class for KMX_loader +*/ +class mac_KMX_Loader { +private: + KMX_BYTE lpKeyStateNull[256]; + KMX_DWORD m_XxxxVk; + +public: + mac_KMX_Loader() { + m_XxxxVk = 0; + memset(lpKeyStateNull, 0, sizeof(lpKeyStateNull)); + } + + KMX_DWORD Get_XxxxVk() { + return m_XxxxVk; + } + + void Set_XxxxVk(KMX_DWORD value) { + m_XxxxVk = value; + } + + ShiftState KMX_MaxShiftState() { + return (Get_XxxxVk() == 0 ? ShftMenuCtrl : ShftXxxx); + } + + bool KMX_IsControlChar(char16_t ch) { + return (ch < 0x0020) || (ch >= 0x007F && ch <= 0x009F); + } + + DeadKey* ProcessDeadKey( + KMX_DWORD iKeyDead, // The index into the VirtualKey of the dead key + ShiftState shiftStateDead, // The shiftstate that contains the dead key + std::vector rgKey, // Our array of dead keys + bool fCapsLock, // Was the caps lock key pressed? + const UCKeyboardLayout* keyboard_layout) { // The keyboard layout + + int max_shiftstate_pos = 1; // use BASE + SHIFT only + DeadKey* deadKey = new DeadKey(rgKey[iKeyDead]->mac_KMX_GetShiftState(shiftStateDead, fCapsLock)[0]); + + int ss_dead = mac_convert_rgkey_Shiftstate_to_MacShiftstate(shiftStateDead); + KMX_DWORD keyval_underlying_dk = mac_KMX_get_KeyVal_From_KeyCode(keyboard_layout, mac_USVirtualKeyToScanCode[iKeyDead], ss_dead, 0); + + for (int i = 0; i < keycode_spacebar + 1; i++) { + for (int j = 0; j <= max_shiftstate_pos; j++) { + for (int caps = 0; caps < 1; caps++) { + // e.g. a basechar + KMX_DWORD basechar = mac_KMX_get_KeyVal_From_KeyCode(keyboard_layout, i, ss_mac[j], caps); + + // e.g. â combchar + KMX_DWORD kc_Underlying_dk = mac_KMX_get_KeyCodeUnderlying_From_VKUS(iKeyDead); + KMX_DWORD combchar = mac_get_CombinedChar_From_DK(keyboard_layout, kc_Underlying_dk, ss_dead, i, ss_mac[j], caps); + + if (combchar == 0) + continue; + + // push only for if combchar is not dk or combchar is dk with space + if ((!(combchar == keyval_underlying_dk)) || (basechar == mac_KMX_get_KeyVal_From_KeyCode(keyboard_layout, keycode_spacebar, ss_mac[j], caps))) { + deadKey->KMX_AddDeadKeyRow(basechar, combchar); + } + } + } + } + return deadKey; + } +}; + +/** + * @brief find the maximum index of a deadkey + * @param p pointer to deadkey + * @return index of deadkey + */ +int mac_KMX_GetMaxDeadkeyIndex(KMX_WCHAR* p) { + int n = 0; + while (p && *p) { + if (*p == UC_SENTINEL && *(p + 1) == CODE_DEADKEY) + n = std::max(n, (int)*(p + 2)); + p = KMX_incxstr(p); + } + return n; +} + +/** + * @brief Collect the key data, translate it to kmx and append to the existing keyboard + * It is important to understand that this function has different sorting order in rgkey compared to mcompile-windows! + * On Windows the values of rgkey are sorted according to the VK of the underlying keyboard + * On Linux the values of rgkey are sorted according to the VK of the the US keyboard + * Since Linux Keyboards do not use a VK mcompile uses the VK of the the US keyboard because + * these are available in mcompile through USVirtualKeyToScanCode/ScanCodeToUSVirtualKey and an offset of 8 + * @param kp pointer to keyboard + * @param all_vector vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param keyboard_layout the currently used (underlying)keyboard Layout + * @param FDeadkeys vector of all deadkeys for the currently used (underlying)keyboard Layout + * @param bDeadkeyConversion 1 to convert a deadkey to a character; 0 no conversion + * @return true in case of success + */ +bool mac_KMX_ImportRules(LPKMX_KEYBOARD kp, vec_dword_3D& all_vector, const UCKeyboardLayout** keyboard_layout, std::vector* FDeadkeys, KMX_BOOL bDeadkeyConversion) { // I4353 // I4327 + mac_KMX_Loader loader; + + std::vector rgKey; //= new VirtualKey[256]; + std::vector alDead; + + rgKey.resize(256); + + // Scroll through the Scan Code (SC) values and get the valid Virtual Key (VK) + // values in it. Then, store the SC in each valid VK so it can act as both a + // flag that the VK is valid, and it can store the SC value. + + // Windows and Linux Keycodes start with 1; Mac keycodes start with 0 + for (KMX_DWORD sc = 0x00; sc <= 0x7f; sc++) { + /* HERE IS A BIG DIFFERENCE COMPARED TO MCOMPILE FOR WINDOWS: + * mcompile on Windows fills rgkey.m_vk with the VK of the Underlying keyboard + * mcompile for macOS fills rgkey.m_vk with the VK of the US keyboard + * this results in a different sorting order in rgkey[] ! + + * macOS cannot get a VK for the underling Keyboard since this does not exist + * macOS can only get a VK for the US Keyboard (by using USVirtualKeyToScanCode/ScanCodeToUSVirtualKey) + * therefore we use VK_US in rgkey[ ] which we get from all_vector + */ + mac_KMX_VirtualKey* key = new mac_KMX_VirtualKey(sc); + + if ((key->VK() != 0)) { + rgKey[key->VK()] = key; + } else { + delete key; + } + } + + // in this part we skip shiftstates 4, 5, 8, 9 + for (KMX_DWORD iKey = 0; iKey < rgKey.size(); iKey++) { + if (rgKey[iKey] != NULL) { + KMX_WCHAR sbBuffer[256]; // Scratchpad we use many places + for (ShiftState ss = Base; ss <= loader.KMX_MaxShiftState(); ss = (ShiftState)((int)ss + 1)) { + if (ss == Menu || ss == ShftMenu) { + // Alt and Shift+Alt don't work, so skip them (ss 4+5) + continue; + } + + KMX_DWORD kc_underlying = mac_KMX_get_KeyCodeUnderlying_From_VKUS(iKey); + + for (int caps = 0; caps <= 1; caps++) { + int rc = mac_KMX_ToUnicodeEx(kc_underlying, sbBuffer, ss, caps, *keyboard_layout); + + if (rc > 0) { + if (*sbBuffer == 0) { + rgKey[iKey]->mac_KMX_SetShiftState(ss, u"", false, (caps)); // different to Windows since behavior on the mac is different + } else { + if ((ss == Ctrl || ss == ShftCtrl)) { + continue; + } + sbBuffer[rc] = 0; + rgKey[iKey]->mac_KMX_SetShiftState(ss, sbBuffer, false, (caps)); // different to Windows since behavior on the mac is different + } + } else if (rc < 0) { + sbBuffer[2] = 0; + rgKey[iKey]->mac_KMX_SetShiftState(ss, sbBuffer, true, (caps)); // different to Windows since behavior on the mac is different + + sbBuffer[2] = 0; + rgKey[iKey]->mac_KMX_SetShiftState(ss, sbBuffer, true, (caps == 0)); + DeadKey* dk = NULL; + for (KMX_DWORD iDead = 0; iDead < alDead.size(); iDead++) { + dk = alDead[iDead]; + if (dk->KMX_DeadCharacter() == rgKey[iKey]->mac_KMX_GetShiftState(ss, caps == 0)[0]) { + break; + } + dk = NULL; + } + if (dk == NULL) { + alDead.push_back(loader.ProcessDeadKey(iKey, ss, rgKey, caps == 0, *keyboard_layout)); + } + } + } + } + } + } + + //------------------------------------------------------------- + // Now that we've collected the key data, we need to + // translate it to kmx and append to the existing keyboard + //------------------------------------------------------------- + + int nDeadkey = 0; + LPKMX_GROUP gp = new KMX_GROUP[kp->cxGroupArray + 4]; // leave space for old + memcpy(gp, kp->dpGroupArray, sizeof(KMX_GROUP) * kp->cxGroupArray); + + // + // Find the current highest deadkey index + // + + kp->dpGroupArray = gp; + for (KMX_DWORD i = 0; i < kp->cxGroupArray; i++, gp++) { + LPKMX_KEY kkp = gp->dpKeyArray; + + for (KMX_DWORD j = 0; j < gp->cxKeyArray; j++, kkp++) { + nDeadkey = std::max(nDeadkey, mac_KMX_GetMaxDeadkeyIndex(kkp->dpContext)); + nDeadkey = std::max(nDeadkey, mac_KMX_GetMaxDeadkeyIndex(kkp->dpOutput)); + } + } + + kp->cxGroupArray++; + gp = &kp->dpGroupArray[kp->cxGroupArray - 1]; + + // calculate the required size of `gp->dpKeyArray` + + KMX_DWORD nkeys = 0; + for (KMX_DWORD iKey = 0; iKey < rgKey.size(); iKey++) { + if ((rgKey[iKey] != NULL) && rgKey[iKey]->mac_KMX_IsKeymanUsedKey() && (!rgKey[iKey]->mac_KMX_IsEmpty())) { + nkeys += rgKey[iKey]->mac_KMX_GetKeyCount(loader.KMX_MaxShiftState()); + } + } + + gp->fUsingKeys = TRUE; + gp->dpMatch = NULL; + gp->dpName = NULL; + gp->dpNoMatch = NULL; + gp->cxKeyArray = nkeys; + gp->dpKeyArray = new KMX_KEY[gp->cxKeyArray]; + + nDeadkey++; // ensure a 1-based index above the max deadkey value already in the keyboard + + // + // Fill in the new rules + // + nkeys = 0; + for (KMX_DWORD iKey = 0; iKey < rgKey.size(); iKey++) { + if ((rgKey[iKey] != NULL) && rgKey[iKey]->mac_KMX_IsKeymanUsedKey() && (!rgKey[iKey]->mac_KMX_IsEmpty())) { + if (rgKey[iKey]->mac_KMX_LayoutRow(loader.KMX_MaxShiftState(), &gp->dpKeyArray[nkeys], &alDead, nDeadkey, bDeadkeyConversion, all_vector, *keyboard_layout)) { // I4552 + nkeys += rgKey[iKey]->mac_KMX_GetKeyCount(loader.KMX_MaxShiftState()); + } + } + } + + gp->cxKeyArray = nkeys; + + // + // Add nomatch control to each terminating 'using keys' group // I4550 + // + LPKMX_GROUP gp2 = kp->dpGroupArray; + for (KMX_DWORD i = 0; i < kp->cxGroupArray - 1; i++, gp2++) { + if (gp2->fUsingKeys && gp2->dpNoMatch == NULL) { + KMX_WCHAR* p = gp2->dpNoMatch = new KMX_WCHAR[4]; + *p++ = UC_SENTINEL; + *p++ = CODE_USE; + *p++ = (KMX_WCHAR)(kp->cxGroupArray); + *p = 0; + + // I4550 - Each place we have a nomatch > use(baselayout) (this last group), we need to add all + // the AltGr and ShiftAltGr combinations as rules to allow them to be matched as well. Yes, this + // loop is not very efficient but it's not worthy of optimisation. + // + KMX_DWORD j; + LPKMX_KEY kkp; + for (j = 0, kkp = gp->dpKeyArray; j < gp->cxKeyArray; j++, kkp++) { + if ((kkp->ShiftFlags & (K_CTRLFLAG | K_ALTFLAG | LCTRLFLAG | LALTFLAG | RCTRLFLAG | RALTFLAG)) != 0) { + gp2->cxKeyArray++; + LPKMX_KEY kkp2 = new KMX_KEY[gp2->cxKeyArray]; + memcpy(kkp2, gp2->dpKeyArray, sizeof(KMX_KEY) * (gp2->cxKeyArray - 1)); + gp2->dpKeyArray = kkp2; + kkp2 = &kkp2[gp2->cxKeyArray - 1]; + kkp2->dpContext = new KMX_WCHAR; + *kkp2->dpContext = 0; + kkp2->Key = kkp->Key; + kkp2->ShiftFlags = kkp->ShiftFlags; + kkp2->Line = 0; + KMX_WCHAR* p = kkp2->dpOutput = new KMX_WCHAR[4]; + *p++ = UC_SENTINEL; + *p++ = CODE_USE; + *p++ = (KMX_WCHAR)(kp->cxGroupArray); + *p = 0; + } + } + } + } + + // If we have deadkeys, then add a new group to translate the deadkeys per the deadkey tables + // We only do this if not in deadkey conversion mode + // + + if (alDead.size() > 0 && !bDeadkeyConversion) { // I4552 + kp->cxGroupArray++; + + KMX_WCHAR* p = gp->dpMatch = new KMX_WCHAR[4]; + *p++ = UC_SENTINEL; + *p++ = CODE_USE; + *p++ = (KMX_WCHAR)kp->cxGroupArray; + *p = 0; + + gp++; + + gp->fUsingKeys = FALSE; + gp->dpMatch = NULL; + gp->dpName = NULL; + gp->dpNoMatch = NULL; + gp->cxKeyArray = alDead.size(); + LPKMX_KEY kkp = gp->dpKeyArray = new KMX_KEY[alDead.size()]; + + LPKMX_STORE sp = new KMX_STORE[kp->cxStoreArray + alDead.size() * 2]; + memcpy(sp, kp->dpStoreArray, sizeof(KMX_STORE) * kp->cxStoreArray); + + kp->dpStoreArray = sp; + + sp = &sp[kp->cxStoreArray]; + int nStoreBase = kp->cxStoreArray; + kp->cxStoreArray += alDead.size() * 2; + + for (KMX_DWORD i = 0; i < alDead.size(); i++) { + DeadKey* dk = alDead[i]; + + sp->dpName = NULL; + sp->dwSystemID = 0; + sp->dpString = new KMX_WCHAR[dk->KMX_Count() + 1]; + for (int j = 0; j < dk->KMX_Count(); j++) + sp->dpString[j] = dk->KMX_GetBaseCharacter(j); + sp->dpString[dk->KMX_Count()] = 0; + sp++; + + sp->dpName = NULL; + sp->dwSystemID = 0; + sp->dpString = new KMX_WCHAR[dk->KMX_Count() + 1]; + for (int j = 0; j < dk->KMX_Count(); j++) + sp->dpString[j] = dk->KMX_GetCombinedCharacter(j); + sp->dpString[dk->KMX_Count()] = 0; + sp++; + + kkp->Line = 0; + kkp->ShiftFlags = 0; + kkp->Key = 0; + KMX_WCHAR* p = kkp->dpContext = new KMX_WCHAR[8]; + *p++ = UC_SENTINEL; + *p++ = CODE_DEADKEY; + *p++ = mac_KMX_DeadKeyMap(dk->KMX_DeadCharacter(), &alDead, nDeadkey, FDeadkeys); // I4353 + // *p++ = nDeadkey+i; + *p++ = UC_SENTINEL; + *p++ = CODE_ANY; + *p++ = nStoreBase + i * 2 + 1; + *p = 0; + + p = kkp->dpOutput = new KMX_WCHAR[5]; + *p++ = UC_SENTINEL; + *p++ = CODE_INDEX; + *p++ = nStoreBase + i * 2 + 2; + *p++ = 2; + *p = 0; + kkp++; + } + } +return true; +} diff --git a/mac/mcompile/mc_import_rules.h b/mac/mcompile/mc_import_rules.h new file mode 100644 index 00000000000..ff1cbbe7679 --- /dev/null +++ b/mac/mcompile/mc_import_rules.h @@ -0,0 +1,51 @@ + +#pragma once +#ifndef MC_IMPORT_RULES_H +#define MC_IMPORT_RULES_H + +/** @brief Find a keyvalue for given keycode, shiftstate and caps. A function similar to Window`s ToUnicodeEx() function. */ +int mac_KMX_ToUnicodeEx(int keycode, PKMX_WCHAR pwszBuff, int ss_rgkey, int caps, const UCKeyboardLayout* keyboard_layout); + +/** @brief Base class for Deadkey*/ +class DeadKey { +private: + KMX_WCHAR m_deadchar; + std::vector m_rgbasechar; + std::vector m_rgcombchar; + +public: + + /** @brief Constructor */ + DeadKey(KMX_WCHAR deadCharacter); + + /** @brief return dead character */ + KMX_WCHAR KMX_DeadCharacter(); + + /** @brief set member variable with base and combined character */ + void KMX_AddDeadKeyRow(KMX_WCHAR baseCharacter, KMX_WCHAR combinedCharacter); + + /** @brief return size of array of basecharacters */ + int KMX_Count() { + return this->m_rgbasechar.size(); + } + + /** @brief get member variable m_deadchar */ + KMX_WCHAR KMX_GetDeadCharacter() { + return this->m_deadchar; + } + + /** @brief return base character at index */ + KMX_WCHAR KMX_GetBaseCharacter(int index) { + return this->m_rgbasechar[index]; + } + + /** @brief return combined character at index */ + KMX_WCHAR KMX_GetCombinedCharacter(int index) { + return this->m_rgcombchar[index]; + } + + /** @brief check if character exists in DeadKey */ + bool KMX_ContainsBaseCharacter(KMX_WCHAR baseCharacter); +}; + +#endif /*MC_IMPORT_RULES_H*/ diff --git a/mac/mcompile/mc_kmxfile.cpp b/mac/mcompile/mc_kmxfile.cpp new file mode 100644 index 00000000000..78aa40615cc --- /dev/null +++ b/mac/mcompile/mc_kmxfile.cpp @@ -0,0 +1,583 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for mac + */ + +#include "mc_kmxfile.h" +#include + +#define CERR_None 0x00000000 +#define CERR_CannotAllocateMemory 0x00008004 +#define CERR_UnableToWriteFully 0x00008007 +#define CERR_SomewhereIGotItWrong 0x00008009 + +const int CODE__SIZE[] = { + -1, // undefined 0x00 + 1, // CODE_ANY 0x01 + 2, // CODE_INDEX 0x02 + 0, // CODE_CONTEXT 0x03 + 0, // CODE_NUL 0x04 + 1, // CODE_USE 0x05 + 0, // CODE_RETURN 0x06 + 0, // CODE_BEEP 0x07 + 1, // CODE_DEADKEY 0x08 + -1, // unused 0x09 + 2, // CODE_EXTENDED 0x0A + -1, // CODE_EXTENDEDEND 0x0B (unused) + 1, // CODE_SWITCH 0x0C + -1, // CODE_KEY 0x0D (never used) + 0, // CODE_CLEARCONTEXT 0x0E + 1, // CODE_CALL 0x0F + -1, // UC_SENTINEL_EXTENDEDEND 0x10 (not valid with UC_SENTINEL) + 1, // CODE_CONTEXTEX 0x11 + 1, // CODE_NOTANY 0x12 + 2, // CODE_SETOPT 0x13 + 3, // CODE_IFOPT 0x14 + 1, // CODE_SAVEOPT 0x15 + 1, // CODE_RESETOPT 0x16 + 3, // CODE_IFSYSTEMSTORE 0x17 + 2 // CODE_SETSYSTEMSTORE 0x18 +}; + +/** @brief check if the file has correct version */ +KMX_BOOL KMX_VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD file_size); + +/** @brief Fixup the keyboard by expanding pointers. */ +LPKMX_KEYBOARD KMX_FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFileSize); + +/** + * @brief Save a Keyboard to a file + * @param fk pointer to the keyboard + * @param hOutfile pointer to the output file + * @param FSaveDebug + * @return an Error in case of failure + */ +KMX_DWORD KMX_WriteCompiledKeyboardToFile(LPKMX_KEYBOARD fk, FILE* hOutfile, KMX_BOOL FSaveDebug) { + LPKMX_GROUP fgp; + LPKMX_STORE fsp; + LPKMX_KEY fkp; + + PCOMP_KEYBOARD ck; + PCOMP_GROUP gp; + PCOMP_STORE sp; + PCOMP_KEY kp; + PKMX_BYTE buf; + KMX_DWORD size, offset; + KMX_DWORD i, j; + + // Calculate how much memory to allocate + size = sizeof(COMP_KEYBOARD) + + fk->cxGroupArray * sizeof(COMP_GROUP) + + fk->cxStoreArray * sizeof(COMP_STORE) + + //wcslen(fk->szName)*2 + 2 + + //wcslen(fk->szCopyright)*2 + 2 + + //wcslen(fk->szLanguageName)*2 + 2 + + //wcslen(fk->szMessage)*2 + 2 + + fk->dwBitmapSize; + + for (i = 0, fgp = fk->dpGroupArray; i < fk->cxGroupArray; i++, fgp++) { + if (fgp->dpName) + size += (u16len(fgp->dpName) + 1) * sizeof(KMX_WCHAR); + size += fgp->cxKeyArray * sizeof(COMP_KEY); + for (j = 0, fkp = fgp->dpKeyArray; j < fgp->cxKeyArray; j++, fkp++) { + size += (u16len(fkp->dpOutput) + 1) * sizeof(KMX_WCHAR); + size += (u16len(fkp->dpContext) + 1) * sizeof(KMX_WCHAR); + } + + if (fgp->dpMatch)size += (u16len(fgp->dpMatch) + 1) * sizeof(KMX_WCHAR); + if (fgp->dpNoMatch)size += (u16len(fgp->dpNoMatch) + 1) * sizeof(KMX_WCHAR); + } + + for (i = 0; i < fk->cxStoreArray; i++) { + size += (u16len(fk->dpStoreArray[i].dpString) + 1) * sizeof(KMX_WCHAR); + if (fk->dpStoreArray[i].dpName) + size += (u16len(fk->dpStoreArray[i].dpName) + 1) * sizeof(KMX_WCHAR); + } + + buf = new KMX_BYTE[size]; + if (!buf) + return CERR_CannotAllocateMemory; + memset(buf, 0, size); + + ck = (PCOMP_KEYBOARD)buf; + + ck->dwIdentifier = FILEID_COMPILED; + + ck->dwFileVersion = fk->dwFileVersion; + ck->dwCheckSum = 0; // No checksum in 16.0, see #7276 + ck->KeyboardID = fk->xxkbdlayout; + ck->IsRegistered = fk->IsRegistered; + ck->cxStoreArray = fk->cxStoreArray; + ck->cxGroupArray = fk->cxGroupArray; + ck->StartGroup[0] = fk->StartGroup[0]; + ck->StartGroup[1] = fk->StartGroup[1]; + ck->dwHotKey = fk->dwHotKey; + + ck->dwFlags = fk->dwFlags; + + offset = sizeof(COMP_KEYBOARD); + + ck->dpStoreArray = offset; + sp = (PCOMP_STORE)(buf + offset); + fsp = fk->dpStoreArray; + offset += sizeof(COMP_STORE) * ck->cxStoreArray; + for (i = 0; i < ck->cxStoreArray; i++, sp++, fsp++) { + sp->dwSystemID = fsp->dwSystemID; + sp->dpString = offset; + u16ncpy((PKMX_WCHAR)(buf + offset), fsp->dpString, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + + offset += (u16len(fsp->dpString) + 1) * sizeof(KMX_WCHAR); + if (!fsp->dpName) { + sp->dpName = 0; + } else { + sp->dpName = offset; + u16ncpy((PKMX_WCHAR)(buf + offset), fsp->dpName, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + offset += (u16len(fsp->dpName) + 1) * sizeof(KMX_WCHAR); + } + } + + ck->dpGroupArray = offset; + gp = (PCOMP_GROUP)(buf + offset); + + offset += sizeof(COMP_GROUP) * ck->cxGroupArray; + + for (i = 0, fgp = fk->dpGroupArray; i < ck->cxGroupArray; i++, gp++, fgp++) { + gp->cxKeyArray = fgp->cxKeyArray; + gp->fUsingKeys = fgp->fUsingKeys; + + gp->dpMatch = gp->dpNoMatch = 0; + + if (fgp->dpMatch) { + gp->dpMatch = offset; + u16ncpy((PKMX_WCHAR)(buf + offset), fgp->dpMatch, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + offset += (u16len(fgp->dpMatch) + 1) * sizeof(KMX_WCHAR); + } + if (fgp->dpNoMatch) { + gp->dpNoMatch = offset; + u16ncpy((PKMX_WCHAR)(buf + offset), fgp->dpNoMatch, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + offset += (u16len(fgp->dpNoMatch) + 1) * sizeof(KMX_WCHAR); + } + + if (fgp->dpName) { + gp->dpName = offset; + u16ncpy((PKMX_WCHAR)(buf + offset), fgp->dpName, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + offset += (u16len(fgp->dpName) + 1) * sizeof(KMX_WCHAR); + } else { + gp->dpName = 0; + } + + gp->dpKeyArray = offset; + kp = (PCOMP_KEY)(buf + offset); + offset += gp->cxKeyArray * sizeof(COMP_KEY); + + for (j = 0, fkp = fgp->dpKeyArray; j < gp->cxKeyArray; j++, kp++, fkp++) { + kp->Key = fkp->Key; + kp->Line = fkp->Line; + kp->ShiftFlags = fkp->ShiftFlags; + kp->dpOutput = offset; + + u16ncpy((PKMX_WCHAR)(buf + offset), fkp->dpOutput, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + offset += (u16len(fkp->dpOutput) + 1) * sizeof(KMX_WCHAR); + + kp->dpContext = offset; + u16ncpy((PKMX_WCHAR)(buf + offset), fkp->dpContext, (size - offset) / sizeof(KMX_WCHAR)); // I3481 // I3641 + offset += (u16len(fkp->dpContext) + 1) * sizeof(KMX_WCHAR); + } + } + + if (fk->dwBitmapSize > 0) { + ck->dwBitmapSize = fk->dwBitmapSize; + ck->dpBitmapOffset = offset; + memcpy(buf + offset, ((PKMX_BYTE)fk) + fk->dpBitmapOffset, fk->dwBitmapSize); + offset += fk->dwBitmapSize; + } else { + ck->dwBitmapSize = 0; + ck->dpBitmapOffset = 0; + } + + size_t nr_elements = fwrite(buf, size, 1, hOutfile); + + if (nr_elements < 1) { + delete[] buf; + return CERR_SomewhereIGotItWrong; + } + + if (offset != size) { + delete[] buf; + return CERR_UnableToWriteFully; + } + + delete[] buf; + + return CERR_None; +} + +/** + * @brief save keyboard to file + * @param kbd pointer to the keyboard + * @param fileName pointer to fileName of a kmx-file + * @return TRUE on success; else FALSE + */ +KMX_BOOL KMX_SaveKeyboard(LPKMX_KEYBOARD kbd, KMX_CHAR* fileName) { + FILE* fp; + fp = Open_File(fileName, "wb"); + + if (fp == NULL) { + mac_KMX_LogError(L"Failed to create output file (%d)", errno); + return FALSE; + } + + KMX_DWORD err = KMX_WriteCompiledKeyboardToFile(kbd, fp, FALSE); + fclose(fp); + + if (err != CERR_None) { + mac_KMX_LogError(L"Failed to write compiled keyboard with error %d", err); + std::string s(fileName); + remove(s.c_str()); + return FALSE; + } + + return TRUE; +} + +/** + * @brief add an offset + * @param base pointer to starting point + * @param offset a given offset + * @return pointer to base + offset + */ +PKMX_WCHAR KMX_StringOffset(PKMX_BYTE base, KMX_DWORD offset) { + if (offset == 0) + return NULL; + return (PKMX_WCHAR)(base + offset); +} + +#ifdef KMX_64BIT + +/** + * @brief CopyKeyboard will copy the data into bufp from x86-sized structures into + * x64-sized structures starting at `base`. After this function finishes, we still + * need to keep the original data because we don't copy the strings. The method is + * used on 64-bit architectures. + * @param bufp pointer to buffer where data is copied into + * @param base pointer to starting point + * @return pointer to the keyboard + */ +LPKMX_KEYBOARD KMX_CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) { + PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD)base; + + // Copy keyboard structure // + + LPKMX_KEYBOARD kbp = (LPKMX_KEYBOARD)bufp; + bufp += sizeof(KMX_KEYBOARD); + + kbp->dwIdentifier = ckbp->dwIdentifier; + kbp->dwFileVersion = ckbp->dwFileVersion; + kbp->dwCheckSum = ckbp->dwCheckSum; + kbp->xxkbdlayout = ckbp->KeyboardID; + kbp->IsRegistered = ckbp->IsRegistered; + kbp->version = ckbp->version; + kbp->cxStoreArray = ckbp->cxStoreArray; + kbp->cxGroupArray = ckbp->cxGroupArray; + kbp->StartGroup[0] = ckbp->StartGroup[0]; + kbp->StartGroup[1] = ckbp->StartGroup[1]; + kbp->dwFlags = ckbp->dwFlags; + kbp->dwHotKey = ckbp->dwHotKey; + + kbp->dpBitmapOffset = ckbp->dpBitmapOffset; + kbp->dwBitmapSize = ckbp->dwBitmapSize; + + kbp->dpStoreArray = (LPKMX_STORE)bufp; + bufp += sizeof(KMX_STORE) * kbp->cxStoreArray; + + kbp->dpGroupArray = (LPKMX_GROUP)bufp; + bufp += sizeof(KMX_GROUP) * kbp->cxGroupArray; + + PCOMP_STORE csp; + LPKMX_STORE sp; + KMX_DWORD i; + + for (csp = (PCOMP_STORE)(base + ckbp->dpStoreArray), sp = kbp->dpStoreArray, i = 0; i < kbp->cxStoreArray; i++, sp++, csp++) { + sp->dwSystemID = csp->dwSystemID; + sp->dpName = KMX_StringOffset(base, csp->dpName); + sp->dpString = KMX_StringOffset(base, csp->dpString); + } + + PCOMP_GROUP cgp; + LPKMX_GROUP gp; + + for (cgp = (PCOMP_GROUP)(base + ckbp->dpGroupArray), gp = kbp->dpGroupArray, i = 0; i < kbp->cxGroupArray; i++, gp++, cgp++) { + gp->dpName = KMX_StringOffset(base, cgp->dpName); + gp->dpKeyArray = cgp->cxKeyArray > 0 ? (LPKMX_KEY)bufp : NULL; + gp->cxKeyArray = cgp->cxKeyArray; + bufp += sizeof(KMX_KEY) * gp->cxKeyArray; + gp->dpMatch = KMX_StringOffset(base, cgp->dpMatch); + gp->dpNoMatch = KMX_StringOffset(base, cgp->dpNoMatch); + gp->fUsingKeys = cgp->fUsingKeys; + + PCOMP_KEY ckp; + LPKMX_KEY kp; + KMX_DWORD j; + + for (ckp = (PCOMP_KEY)(base + cgp->dpKeyArray), kp = gp->dpKeyArray, j = 0; j < gp->cxKeyArray; j++, kp++, ckp++) { + kp->Key = ckp->Key; + kp->Line = ckp->Line; + kp->ShiftFlags = ckp->ShiftFlags; + kp->dpOutput = KMX_StringOffset(base, ckp->dpOutput); + kp->dpContext = KMX_StringOffset(base, ckp->dpContext); + } + } + return kbp; +} + +// else KMX_FixupKeyboard +#else + +/** + * @brief Fixup the keyboard by expanding pointers. On disk the pointers are stored relative to the + * beginning of the file, but we need real pointers. This method is used on 32-bit architectures. + * @param bufp pointer to buffer where data will be copied into + * @param base pointer to starting point + * @param dwFileSize size of the file + * @return pointer to the keyboard + */ +LPKMX_KEYBOARD KMX_FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFileSize) { + UNREFERENCED_PARAMETER(dwFileSize); + + KMX_DWORD i, j; + PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD)base; + PCOMP_GROUP cgp; + PCOMP_STORE csp; + PCOMP_KEY ckp; + LPKMX_KEYBOARD kbp = (LPKMX_KEYBOARD)bufp; + LPKMX_STORE sp; + LPKMX_GROUP gp; + LPKMX_KEY kp; + + kbp->dpStoreArray = (LPKMX_STORE)(base + ckbp->dpStoreArray); + kbp->dpGroupArray = (LPKMX_GROUP)(base + ckbp->dpGroupArray); + + for (sp = kbp->dpStoreArray, csp = (PCOMP_STORE)sp, i = 0; i < kbp->cxStoreArray; i++, sp++, csp++) { + sp->dpName = KMX_StringOffset(base, csp->dpName); + sp->dpString = KMX_StringOffset(base, csp->dpString); + } + + for (gp = kbp->dpGroupArray, cgp = (PCOMP_GROUP)gp, i = 0; i < kbp->cxGroupArray; i++, gp++, cgp++) { + gp->dpName = KMX_StringOffset(base, cgp->dpName); + gp->dpKeyArray = (LPKMX_KEY)(base + cgp->dpKeyArray); + if (cgp->dpMatch != NULL)gp->dpMatch = (PKMX_WCHAR)(base + cgp->dpMatch); + if (cgp->dpNoMatch != NULL)gp->dpNoMatch = (PKMX_WCHAR)(base + cgp->dpNoMatch); + + for (kp = gp->dpKeyArray, ckp = (PCOMP_KEY)kp, j = 0; j < gp->cxKeyArray; j++, kp++, ckp++) { + kp->dpOutput = (PKMX_WCHAR)(base + ckp->dpOutput); + kp->dpContext = (PKMX_WCHAR)(base + ckp->dpContext); + } + } + + return kbp; +} + +#endif + +/** + * @brief load a keyboard kmx-file + * @param fileName pointer to fileName of kmx-file + * @param[in,out] lpKeyboard pointer to pointer to keyboard + * @return TRUE on success; else FALSE + */ +KMX_BOOL KMX_LoadKeyboard(KMX_CHAR* fileName, LPKMX_KEYBOARD* lpKeyboard) { + *lpKeyboard = NULL; + PKMX_BYTE buf; + FILE* fp; + LPKMX_KEYBOARD kbp; + PKMX_BYTE filebase; + + if (!fileName || !lpKeyboard) { + mac_KMX_LogError(L"Bad Filename\n"); + return FALSE; + } + + fp = Open_File((const KMX_CHAR*)fileName, "rb"); + + if (fp == NULL) { + mac_KMX_LogError(L"Could not open file\n"); + return FALSE; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + mac_KMX_LogError(L"Could not fseek file\n"); + return FALSE; + } + + auto file_size = ftell(fp); + if (file_size <= 0) { + fclose(fp); + return FALSE; + } + + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + mac_KMX_LogError(L"Could not fseek(set) file\n"); + return FALSE; + } + + #ifdef KMX_64BIT +/** + * allocate enough memory for expanded data structure + original data. + * Expanded data structure is double the size of data on disk (8-byte + * pointers) - on disk the "pointers" are relative to the beginning of + * the file. + * We save the original data at the end of buf; we don't copy strings, so + * those will remain in the location at the end of the buffer. + */ + buf = new KMX_BYTE[file_size * 3]; + #else + buf = new KMX_BYTE[file_size]; + #endif + + if (!buf) { + delete[] buf; + fclose(fp); + mac_KMX_LogError(L"Not allocmem\n"); + return FALSE; + } + + #ifdef KMX_64BIT + filebase = buf + file_size * 2; + #else + filebase = buf; + #endif + + if (fread(filebase, 1, file_size, fp) < (size_t)file_size) { + mac_KMX_LogError(L"Could not read file\n"); + delete[] buf; + fclose(fp); + return FALSE; + } + fclose(fp); + + if (*((PKMX_DWORD)filebase) != (KMX_DWORD)FILEID_COMPILED) { + delete[] buf; + mac_KMX_LogError(L"Invalid file - signature is invalid\n"); + return FALSE; + } + + if (!KMX_VerifyKeyboard(filebase, file_size)) { + delete[] buf; + mac_KMX_LogError(L"errVerifyKeyboard\n"); + return FALSE; + } + +#ifdef KMX_64BIT + kbp = KMX_CopyKeyboard(buf, filebase); +#else + kbp = KMX_FixupKeyboard(buf, filebase, file_size); +#endif + + if (!kbp) { + delete[] buf; + mac_KMX_LogError(L"errFixupKeyboard\n"); + return FALSE; + } + + if (kbp->dwIdentifier != FILEID_COMPILED) { + delete[] buf; + mac_KMX_LogError(L"errNotFileID\n"); + return FALSE; + } + *lpKeyboard = kbp; + return TRUE; +} + +/** + * @brief check if the file has correct version + * @param filebase containing data of the input file + * @param file_size a size + * @return true if successful; + * false if not + */ +KMX_BOOL KMX_VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD file_size) { + KMX_DWORD i; + PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD)filebase; + PCOMP_STORE csp; + + // Check file version // + + if (ckbp->dwFileVersion < VERSION_MIN || ckbp->dwFileVersion > VERSION_MAX) { + // Old or new version -- identify the desired program version // + for (csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) { + if (csp->dwSystemID == TSS_COMPILEDVERSION) { + if (csp->dpString == 0) { + mac_KMX_LogError(L"errWrongFileVersion:NULL"); + } else { + mac_KMX_LogError(L"errWrongFileVersion:%10.10ls", (const PKMX_WCHAR)KMX_StringOffset((PKMX_BYTE)filebase, csp->dpString)); + } + return FALSE; + } + } + mac_KMX_LogError(L"errWrongFileVersion"); + return FALSE; + } + return TRUE; +} + +/** + * @brief increment in a string + * @param p pointer to a character + * @return pointer to the incremented character + */ +PKMX_WCHAR KMX_incxstr(PKMX_WCHAR p) { + if (p == NULL || *p == 0) + return p; + if (*p != UC_SENTINEL) { + if (*p >= 0xD800 && *p <= 0xDBFF && *(p + 1) >= 0xDC00 && *(p + 1) <= 0xDFFF) + return p + 2; + return p + 1; + } + // UC_SENTINEL(FFFF) with UC_SENTINEL_EXTENDEDEND(0x10) ==> variable length + if (*(p + 1) == CODE_EXTENDED) { + p += 2; + while (*p && *p != UC_SENTINEL_EXTENDEDEND) + p++; + + if (*p == 0) + return p; + return p + 1; + } + + if (*(p + 1) > CODE_LASTCODE || CODE__SIZE[*(p + 1)] == -1) { + return p + 1; + } + + int deltaptr = 2 + CODE__SIZE[*(p + 1)]; + + // check for \0 between UC_SENTINEL(FFFF) and next printable character + for (int i = 0; i < deltaptr; i++) { + if (*p == 0) + return p; + p++; + } + return p; +} + +/** + * @brief open a file + * @param Filename name of the file + * @param mode same as mode in fopen + * @return pointer to file. On error, returns a null pointer + */ +FILE* Open_File(const KMX_CHAR* Filename, const KMX_CHAR* mode) { +#ifdef _MSC_VER + std::string cpath = Filename; //, cmode = mode; + std::replace(cpath.begin(), cpath.end(), '/', '\\'); + return fopen(cpath.c_str(), (const KMX_CHAR*)mode); +#else + return fopen(Filename, mode); + std::string cpath, cmode; + cpath = (const KMX_CHAR*)Filename; + cmode = (const KMX_CHAR*)mode; + return fopen(cpath.c_str(), cmode.c_str()); +#endif +}; diff --git a/mac/mcompile/mc_kmxfile.h b/mac/mcompile/mc_kmxfile.h new file mode 100644 index 00000000000..014a8146bf9 --- /dev/null +++ b/mac/mcompile/mc_kmxfile.h @@ -0,0 +1,82 @@ +#pragma once +#ifndef MC_KMXFILE_H +#define MC_KMXFILE_H + +#include "../../common/include/km_types.h" +#include "../../common/include/kmx_file.h" +#include "mcompile.h" + +#ifndef _KMXFILE_H +#define _KMXFILE_H + +typedef struct KMX_tagSTORE { + KMX_DWORD dwSystemID; + PKMX_WCHAR dpName; + PKMX_WCHAR dpString; +} KMX_STORE, *LPKMX_STORE; + +typedef struct KMX_tagKEY { + KMX_WCHAR Key; + KMX_DWORD Line; + KMX_DWORD ShiftFlags; + PKMX_WCHAR dpOutput; + PKMX_WCHAR dpContext; +} KMX_KEY, *LPKMX_KEY; + +typedef struct KMX_tagGROUP { + KMX_WCHAR* dpName; + LPKMX_KEY dpKeyArray; // [LPKEY] address of first item in key array + PKMX_WCHAR dpMatch; + PKMX_WCHAR dpNoMatch; + KMX_DWORD cxKeyArray; // in array entries + int32_t fUsingKeys; // group(xx) [using keys] <-- specified or not +} KMX_GROUP, *LPKMX_GROUP; + +typedef struct KMX_tagKEYBOARD { + KMX_DWORD dwIdentifier; // Keyman compiled keyboard id + + KMX_DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 + + KMX_DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 + KMX_DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + KMX_DWORD IsRegistered; // layout id, from same registry key + KMX_DWORD version; // keyboard version + + KMX_DWORD cxStoreArray; // in array entries + KMX_DWORD cxGroupArray; // in array entries + + LPKMX_STORE dpStoreArray; // [LPSTORE] address of first item in store array, from start of file + LPKMX_GROUP dpGroupArray; // [LPGROUP] address of first item in group array, from start of file + + KMX_DWORD StartGroup[2]; // index of starting groups [2 of them] + // Ansi=0, Unicode=1 + + KMX_DWORD dwFlags; // Flags for the keyboard file + + KMX_DWORD dwHotKey; // standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + + // PWSTR dpName; // offset of name + // PWSTR dpLanguageName; // offset of language name; + // PWSTR dpCopyright; // offset of copyright + // PWSTR dpMessage; // offset of message in Keyboard About box + + KMX_DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file + KMX_DWORD dwBitmapSize; // 003C size in bytes of the bitmaps + //HBITMAP hBitmap; // handle to the bitmap in the file; +} KMX_KEYBOARD, *LPKMX_KEYBOARD; + +/** @brief load a keyboard kmx-file */ +KMX_BOOL KMX_LoadKeyboard(KMX_CHAR* fileName, LPKMX_KEYBOARD* lpKeyboard); + +/** @brief save keyboard to file */ +KMX_BOOL KMX_SaveKeyboard(LPKMX_KEYBOARD kbd, KMX_CHAR* fileName); + +/** @brief increment in a string */ +PKMX_WCHAR KMX_incxstr(PKMX_WCHAR p); + +/** @brief open a file */ +FILE* Open_File(const KMX_CHAR* Filename, const KMX_CHAR* mode); + +#endif // _KMXFILE_H + +#endif /*MC_KMXFILE_H*/ diff --git a/mac/mcompile/mcompile.cpp b/mac/mcompile/mcompile.cpp new file mode 100644 index 00000000000..fedd3a3a810 --- /dev/null +++ b/mac/mcompile/mcompile.cpp @@ -0,0 +1,597 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Mnemonic layout support for mac + * + * 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 +#include +#include +#include +#include "mcompile.h" +#include "../../common/include/km_u16.h" + +/** @brief convert mnemonic keyboard layout to positional keyboard layout and translate keyboard */ +KMX_BOOL mac_KMX_DoConvert(LPKMX_KEYBOARD kbd, KMX_BOOL bDeadkeyConversion); + +/** @brief Collect the key data, translate it to kmx and append to the existing keyboard */ +bool mac_KMX_ImportRules(LPKMX_KEYBOARD kp, vec_dword_3D &all_vector, const UCKeyboardLayout** keyboard_layout, 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 mac_KMX_GetDeadkeys(const UCKeyboardLayout* keyboard_layout, vec_dword_3D& all_vector, KMX_WCHAR deadkey, KMX_DWORD shift_dk, std::vector &dk_vec); + +std::vector KMX_FDeadkeys; // I4353 + +#define _countof(a) (sizeof(a) / sizeof(*(a))) + +/** + * @brief main function for mcompile for Windows, Linux, Mac + * @param argc number of commandline arguments + * @param argv commandline arguments + * @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" + " \tmac keyboard layout\n" + " \t(-d convert deadkeys to plain keys) \n \n"); // I4552 + } + + // -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)) { + mac_KMX_LogError(L"Failed to load keyboard (%d)\n", errno); + return 3; + } + + if (mac_KMX_DoConvert(kmxfile, bDeadkeyConversion)) { // I4552F + if(!KMX_SaveKeyboard(kmxfile, outfile)) { + mac_KMX_LogError(L"Failed to save keyboard (%d)\n", errno); + return 3; + } + } else { + mac_KMX_LogError(L"Failed to convert 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 mac_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. + // mac_KMX_LogError(L"Converted mnemonic rule on line %d, + '%c' TO + [%x K_%d]", key->Line, key->Key, shift, vk);// 1 + 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. + // mac_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 mac_KMX_TranslateGroup(LPKMX_GROUP group, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { + for (unsigned int i = 0; i < group->cxKeyArray; i++) { + mac_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 mac_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) { + mac_KMX_TranslateGroup(&kbd->dpGroupArray[i], vk, shift, ch); + } + } +} + + /** + * @brief check key for unconverted key rules + * @param key pointer to a key + */ +void mac_KMX_ReportUnconvertedKeyRule(LPKMX_KEY key) { + if (key->ShiftFlags == 0) { + mac_KMX_LogError(L"Did not find a match for mnemonic rule on line %d, + '%c' > ...", key->Line, key->Key); + } else if (key->ShiftFlags & VIRTUALCHARKEY) { + mac_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 mac_KMX_ReportUnconvertedGroupRules(LPKMX_GROUP group) { + for (unsigned int i = 0; i < group->cxKeyArray; i++) { + mac_KMX_ReportUnconvertedKeyRule(&group->dpKeyArray[i]); + } +} + +/** + * @brief check a keyboard for unconverted rules + * @param kbd pointer to the US keyboard + */ +void mac_KMX_ReportUnconvertedKeyboardRules(LPKMX_KEYBOARD kbd) { + for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { + if (kbd->dpGroupArray[i].fUsingKeys) { + mac_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 mac_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) { + // mac_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 { + // mac_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 a deadkey to be remapped + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param character of the underlying keyboard + */ +void mac_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++) { + mac_KMX_TranslateDeadkeyKey(&group->dpKeyArray[i], deadkey, vk, shift, ch); + } +} + +/** + * @brief translate a keyboard + * @param kbd pointer to the US keyboard + * @param a deadkey to be remapped + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param character of the underlying keyboard + */ +void mac_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) { + mac_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 mac_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++; + mac_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 mac_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 mac_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, mac_KMX_ScanXStringForMaxDeadkeyID(kp->dpContext)); + dkid = std::max(dkid, mac_KMX_ScanXStringForMaxDeadkeyID(kp->dpOutput)); + } + dkid = std::max(dkid, mac_KMX_ScanXStringForMaxDeadkeyID(gp->dpMatch)); + dkid = std::max(dkid, mac_KMX_ScanXStringForMaxDeadkeyID(gp->dpNoMatch)); + } + + for (i = 0, sp = kbd->dpStoreArray; i < kbd->cxStoreArray; i++, sp++) { + dkid = std::max(dkid, mac_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 mac_KMX_ConvertDeadkey(LPKMX_KEYBOARD kbd, KMX_WORD vk_US, KMX_DWORD shift, KMX_WCHAR deadkey, vec_dword_3D& all_vector, const UCKeyboardLayout* keyboard_layout) { + 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 = mac_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 + mac_KMX_AddDeadkeyRule(kbd, dkid, vk_US, shift); + + mac_KMX_GetDeadkeys(keyboard_layout, all_vector, deadkey, shift, vec_deadkeys); // returns vector of [usvk, ch_out] pairs + + int n = 0; + while (n < vec_deadkeys.size()) { + // Look up the ch + KMX_DWORD KeyValUnderlying = mac_KMX_get_KeyValUnderlying_From_KeyValUS(all_vector, vec_deadkeys[n]); + mac_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 mac_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) { + mac_KMX_LogError(L"Invalid &mnemoniclayout system store"); + return FALSE; + } + if (u16cmp((const KMX_WCHAR*)sp->dpString, u"1") != 0) { + mac_KMX_LogError(L"Keyboard is not a mnemonic layout keyboard"); + return FALSE; + } + *sp->dpString = '0'; + return TRUE; + } + } + mac_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 + * @return TRUE if conversion was successful; + * FALSE if not + */ +KMX_BOOL mac_KMX_DoConvert(LPKMX_KEYBOARD kbd, KMX_BOOL bDeadkeyConversion) { + KMX_WCHAR DeadKey = 0; + if (!mac_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. + + // Sadly it`s not e.g.: on a German WINDOWS keyboard one will get '~' with ALTGR + K_221(+) which is the usual combination to get ~. + // on a German MAC keyboard one will get '~' with either OPT + K_221(+) or OPT + K_84(T) or CAPS + OPT + K_78(N) + // K_84(T) will be caught first, so one of the the least obvious version for creating the '~' is found and processed. + // -> meeting with @MD May 21 2024: We leave it as it is; it is OK if different combinations are found. + + const UCKeyboardLayout* keyboard_layout; + if (mac_InitializeUCHR(&keyboard_layout)) { + 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 (mac_createOneVectorFromBothKeyboards(all_vector, keyboard_layout)) { + printf("ERROR: can't create one vector from both keyboards\n"); + return FALSE; + } + + 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 as sorting order in rgkey[], Linux and macOS use SC/Keycode as sorting order + KMX_DWORD scUnderlying = mac_KMX_get_KeyCodeUnderlying_From_VKUS(KMX_VKMap[i]); + KMX_WCHAR ch = mac_KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(keyboard_layout, scUnderlying, VKShiftState[j], &DeadKey); + + // wprintf(L"--- 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: mac_KMX_ConvertDeadkey(kbd, KMX_VKMap[i], VKShiftState[j], DeadKey, all_vector, keyboard_layout); break; + default: mac_KMX_TranslateKeyboard(kbd, KMX_VKMap[i], VKShiftState[j], ch); + } + } + } + + mac_KMX_ReportUnconvertedKeyboardRules(kbd); + + if (!mac_KMX_ImportRules(kbd, all_vector, &keyboard_layout, &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 keyboard_layout the currently used (underlying) keyboard Layout + * @param all_vector Vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param deadkey deadkey character + * @param shift_dk shiftstate of the deadkey + * @param[out] dk_vec vector of [usvk, ch_out] pairs + * @return size of array of [usvk, ch_out] pairs + */ +int mac_KMX_GetDeadkeys(const UCKeyboardLayout* keyboard_layout, vec_dword_3D& all_vector, KMX_WCHAR deadkey, KMX_DWORD shift_dk, std::vector &dk_vec) { + UInt32 deadkeystate; + const UniCharCount maxStringlength = 5; + UniCharCount actualStringlength = 0; + OptionBits keyTranslateOptions = 0; + UniChar unicodeString[maxStringlength]; + OSStatus status; + unicodeString[0] = 0; + + KMX_DWORD sc_dk = mac_KMX_get_KeyCodeUnderlying_From_KeyValUnderlying(all_vector, deadkey); + + for (int j = 0; j < _countof(ss_mac); j++) { + /* + we start with SPACE (keycode_spacebar=49) because all deadkeys occur in combinations with space. + Since mcompile finds only the first occurance of a dk combination, this makes sure that we always + find the dk+SPACE combinations for a deadkey. + If there are additional combinations to create a specific character they will not be found. + (See comment at the top of mac_KMX_DoConvert()) + */ + for (int i = keycode_spacebar; i >= 0; i--) { + status = UCKeyTranslate(keyboard_layout, sc_dk, kUCKeyActionDown, mac_convert_Shiftstate_to_MacShiftstate(shift_dk), LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + if ((deadkeystate == 0) && (status != noErr)) // in case UCKeyTranslate returned an error + return 0; + + // UCKeyTranslate: deadkeystate != 0 if a dk was found; then run UCKeyTranslate again with a SPACE (keycode_spacebar) to get the character e.g.'^'. + if (deadkeystate != 0) { + status = UCKeyTranslate(keyboard_layout, i, kUCKeyActionDown, ss_mac[j], LMGetKbdType(), keyTranslateOptions, &deadkeystate, maxStringlength, &actualStringlength, unicodeString); + if (status != noErr) // in case UCKeyTranslate returned an error + return 0; + + // deadkeystate might be changed again therefore a new if-clause + if (deadkeystate != 0) { + KMX_WORD vk = mac_KMX_get_KeyVal_From_KeyCode(keyboard_layout, i, ss_mac[1], 0); + + // ensure to NOT get key combinations like '^a' but only combined characters like 'â' (exception for '^' + space) + if ((unicodeString[0] != deadkey) || (vk == VK_SPACE)) { + dk_vec.push_back(vk); + dk_vec.push_back(ss_mac[j]); + dk_vec.push_back(unicodeString[0]); + } + } + } + } + } + return dk_vec.size(); +} + +/** + * @brief print (error) messages + * @param fmt text to print + */ + void mac_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); +} diff --git a/mac/mcompile/mcompile.h b/mac/mcompile/mcompile.h new file mode 100644 index 00000000000..0e3744925c1 --- /dev/null +++ b/mac/mcompile/mcompile.h @@ -0,0 +1,26 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Mnemonic layout support for mac + */ + +#ifndef MCOMPILE_H +#define MCOMPILE_H +#include +#include "keymap.h" +#include "mc_kmxfile.h" +#include +#include "mc_import_rules.h" + +struct KMX_DeadkeyMapping { // I4353 + KMX_WCHAR deadkey, dkid; + KMX_DWORD shift; + KMX_WORD vk; +}; + +extern std::vector KMX_FDeadkeys; // I4353 + +/** @brief print (error) messages */ +void mac_KMX_LogError(const wchar_t* fmt, ...); + +#endif /*MCOMPILE_H*/ diff --git a/mac/mcompile/meson.build b/mac/mcompile/meson.build new file mode 100644 index 00000000000..6a359f9f5c8 --- /dev/null +++ b/mac/mcompile/meson.build @@ -0,0 +1,32 @@ +project('mcompile', 'c', 'cpp', + license: 'MIT', + meson_version: '>=1.0') + +# see https://github.com/keymanapp/keyman/pull/11334#issuecomment-2290784399 +add_project_arguments('-O2', language : 'cpp') + +carbon = dependency('Carbon') + +deps = [carbon] + +subdir('resources') + +cpp_files = files( + 'keymap.cpp', + 'mcompile.cpp', + 'mc_kmxfile.cpp', + 'mc_import_rules.cpp', + '../../common/cpp/km_u16.cpp', + ) + +comon_include_dir = [ + include_directories('../../common/include') +] + +mcompile = executable( + 'mcompile', + cpp_args: ['-std=c++17'], # this should be defined from resources/standard.meson.build + sources: [cpp_files], + dependencies: deps, + include_directories : comon_include_dir + ) \ No newline at end of file diff --git a/resources/build/meson-utils.inc.sh b/resources/build/meson-utils.inc.sh new file mode 100644 index 00000000000..24375a840a7 --- /dev/null +++ b/resources/build/meson-utils.inc.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# This script contains common meson functions + + +# ---------------------------------------------------------------------------- +# clean +# ---------------------------------------------------------------------------- + +do_meson_clean() { + rm -rf "$THIS_SCRIPT_PATH/resources" + rm -rf "$TARGET_PATH" +} + +# ---------------------------------------------------------------------------- +# configure +# ---------------------------------------------------------------------------- + +do_meson_configure() { + # Import our standard compiler defines; this is copied from + # /resources/build/meson/standard.meson.build by build.sh, because meson doesn't + # allow us to reference a file outside its root + mkdir -p "$THIS_SCRIPT_PATH/resources" + cp "$KEYMAN_ROOT/resources/build/meson/standard.meson.build" "$THIS_SCRIPT_PATH/resources/meson.build" + + pushd "$THIS_SCRIPT_PATH" > /dev/null + # Additional arguments are used by Linux build, e.g. -Dprefix=${INSTALLDIR} + meson setup build --buildtype $BUILDER_CONFIGURATION "${builder_extra_params[@]}" + popd > /dev/null + +} + +# ---------------------------------------------------------------------------- +# build +# ---------------------------------------------------------------------------- + +do_meson_build() { + pushd "$TARGET_PATH" > /dev/null + ninja + popd > /dev/null +} + +# ---------------------------------------------------------------------------- +# test +# ---------------------------------------------------------------------------- + +do_meson_test() { + pushd "$TARGET_PATH" > /dev/null + meson test "${builder_extra_params[@]}" + popd > /dev/null +} \ No newline at end of file