diff --git a/.clang-format b/.clang-format index 81ca2c82a3..50df9fee12 100644 --- a/.clang-format +++ b/.clang-format @@ -134,4 +134,3 @@ TabWidth: 8 UseCRLF: false UseTab: Never ... - diff --git a/.gitignore b/.gitignore index 8534d172a9..772784fdb8 100644 --- a/.gitignore +++ b/.gitignore @@ -183,4 +183,3 @@ lcov.info /keyman*.buildinfo /keyman*.changes /keyman*.tar.?z - diff --git a/HISTORY.md b/HISTORY.md index f45e1e85cd..f911703834 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,24 @@ # Keyman Version History +## 18.0.90 alpha 2024-08-15 + +* refactor(web): move parts of `keyboard-processor` → `js-processor` (#12111) +* fix(web): allow `lm-worker` to build on Linux (#12181) +* refactor(web): move remaining parts of `keyboard-processor` → `keyboard` (#12131) +* docs: update .kmx documentation around bitmaps, modifier state (#12183) +* refactor(web): rename `package-cache` → `keyboard-storage` (#12135) + +## 18.0.89 alpha 2024-08-14 + +* feat(web): test skipped prediction round handling (#12169) +* fix(web): support live configuration of longpress delay (#12175) +* feat(web): add osk.gestureParams for better gesture-config persistence (#12176) +* refactor(core): move utfcodec to common (#12171) +* refactor: move kmx_u16 to common and rename to km_u16 (#12177) +* refactor(developer): use npm or local source for server addons instead of github references (#12090) +* fix(developer): fix crash with Windows Clipboard by ignoring zero scan code in debugger (#12166) +* chore(developer): update SIL logo (#12168) + ## 18.0.88 alpha 2024-08-13 * docs: add .kmx specification (#12163) diff --git a/VERSION.md b/VERSION.md index 06ebe83646..8f233fffc0 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.89 \ No newline at end of file +18.0.91 \ No newline at end of file diff --git a/common/predictive-text/.build-builder b/common/predictive-text/.build-builder deleted file mode 100644 index 4e15741a62..0000000000 --- a/common/predictive-text/.build-builder +++ /dev/null @@ -1 +0,0 @@ -The presence of this file tells CI to use the new builder_ style parameters for build.sh and unit_tests/test.sh. \ No newline at end of file diff --git a/common/web/keyboard-processor/build.sh b/common/web/keyboard-processor/build.sh deleted file mode 100755 index 3921063bb9..0000000000 --- a/common/web/keyboard-processor/build.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -# -# Compile KeymanWeb's 'keyboard-processor' module, one of the components of Web's 'core' module. -# -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" - -BUNDLE_CMD="node ${KEYMAN_ROOT}/common/web/es-bundling/build/common-bundle.mjs" - -################################ Main script ################################ - -builder_describe \ - "Compiles the web-oriented utility function module." \ - "@/web/src/tools/testing/recorder-core test" \ - "@/common/web/keyman-version" \ - "@/common/web/es-bundling" \ - "@/common/web/types" \ - "@/common/web/utils" \ - configure \ - clean \ - build \ - test \ - "--ci For use with action $(builder_term test) - emits CI-friendly test reports" - -builder_describe_outputs \ - configure /node_modules \ - build /common/web/keyboard-processor/build/lib/index.mjs - -builder_parse "$@" - -function do_configure() { - verify_npm_setup - - # Configure Web browser-engine testing environments. As is, this should only - # make changes when we update the dependency, even on our CI build agents. - playwright install -} - -function do_build() { - tsc --build "${THIS_SCRIPT_PATH}/tsconfig.all.json" - - # Base product - the main keyboard processor - builder_echo "Bundle base product - the main keyboard processor" - ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/index.js" \ - --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/index.mjs" \ - --format esm - - # The DOM-oriented keyboard loader - builder_echo "Bundle the DOM-oriented keyboard loader" - ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/keyboards/loaders/dom-keyboard-loader.js" \ - --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/dom-keyboard-loader.mjs" \ - --format esm - - # The Node-oriented keyboard loader - builder_echo "Bundle the Node-oriented keyboard loader" - ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/obj/keyboards/loaders/node-keyboard-loader.js" \ - --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/lib/node-keyboard-loader.mjs" \ - --format esm \ - --platform node - - # Tests - builder_echo "Bundle tests" - ${BUNDLE_CMD} "${KEYMAN_ROOT}/common/web/keyboard-processor/build/tests/dom/cases/domKeyboardLoader.spec.js" \ - --out "${KEYMAN_ROOT}/common/web/keyboard-processor/build/tests/dom/domKeyboardLoader.spec.mjs" \ - --format esm - - # Declaration bundling. - builder_echo "Declaration bundling" - tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts - tsc --emitDeclarationOnly --outFile ./build/lib/dom-keyboard-loader.d.ts -p src/keyboards/loaders/tsconfig.dom.json - tsc --emitDeclarationOnly --outFile ./build/lib/node-keyboard-loader.d.ts -p src/keyboards/loaders/tsconfig.node.json -} - -function do_test() { - local MOCHA_FLAGS= - local WTR_CONFIG= - if builder_has_option --ci; then - echo "Replacing user-friendly test reports with CI-friendly versions." - MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter" - WTR_CONFIG=.CI - fi - - c8 mocha --recursive $MOCHA_FLAGS ./tests/node/ - web-test-runner --config tests/dom/web-test-runner${WTR_CONFIG}.config.mjs -} - -builder_run_action configure do_configure -builder_run_action clean rm -rf ./build -builder_run_action build do_build -builder_run_action test do_test diff --git a/common/web/keyboard-processor/package.json b/common/web/keyboard-processor/package.json deleted file mode 100644 index 3f0a595897..0000000000 --- a/common/web/keyboard-processor/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@keymanapp/keyboard-processor", - "description": "Core module for Keyman keyboard support in KeymanWeb.", - "repository": { - "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" - }, - "keywords": [ - "input", - "languages", - "keyboards" - ], - "author": "SIL International", - "license": "MIT", - "bugs": { - "url": "https://github.com/keymanapp/keyman/issues" - }, - "homepage": "https://github.com/keymanapp/keyman#readme", - "devDependencies": { - "@keymanapp/resources-gosh": "*", - "c8": "^7.12.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^5.4.5" - }, - "scripts": { - "build": "gosh build.sh", - "clean": "gosh build.sh clean", - "test": "gosh build.sh test" - }, - "dependencies": { - "@keymanapp/common-types": "*", - "@keymanapp/models-types": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/web-utils": "*" - }, - "type": "module", - "main": "./build/obj/index.js", - "types": "./build/obj/index.d.ts", - "exports": { - ".": { - "es6-bundling": "./src/index.ts", - "default": "./build/obj/index.js" - }, - "./node-keyboard-loader": { - "es6-bundling": "./src/keyboards/loaders/node-keyboard-loader.ts", - "types": "./build/obj/keyboards/loaders/node-keyboard-loader.d.ts", - "import": "./build/obj/keyboards/loaders/node-keyboard-loader.js" - }, - "./dom-keyboard-loader": { - "es6-bundling": "./src/keyboards/loaders/dom-keyboard-loader.ts", - "types": "./build/obj/keyboards/loaders/dom-keyboard-loader.d.ts", - "import": "./build/obj/keyboards/loaders/dom-keyboard-loader.js" - }, - "./lib": { - "types": "./build/lib/index.d.ts", - "import": "./build/lib/index.mjs" - }, - "./lib/node-keyboard-loader": { - "types": "./build/lib/node-keyboard-loader.d.ts", - "import": "./build/lib/node-keyboard-loader.mjs" - }, - "./lib/dom-keyboard-loader": { - "types": "./build/lib/dom-keyboard-loader.d.ts", - "import": "./build/lib/dom-keyboard-loader.mjs" - }, - "./obj/*.js": "./build/obj/*.js" - } -} diff --git a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json deleted file mode 100644 index 7ba2abe026..0000000000 --- a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../../../tsconfig.kmw-main-base.json", - "compilerOptions": { - "baseUrl": "../../../", - "outDir": "../../../build/obj/keyboards/loaders/", - "tsBuildInfoFile": "../../../build/obj/keyboards/loaders/tsconfig.dom.tsbuildinfo", - "rootDir": "." - }, - "references": [ - { "path": "../../../tsconfig.json" } - ], - "include": ["dom-keyboard-loader.ts", "domKeyboardLoader.ts"], -} diff --git a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.node.json b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.node.json deleted file mode 100644 index 0be3f169ad..0000000000 --- a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.node.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../../../tsconfig.kmw-main-base.json", - "compilerOptions": { - "types": [ "node" ], - "baseUrl": "../../../", - "outDir": "../../../build/obj/keyboards/loaders/", - "tsBuildInfoFile": "../../../build/obj/keyboards/loaders/tsconfig.node.tsbuildinfo", - "rootDir": "." - }, - "references": [ - { "path": "../../../tsconfig.json" } - ], - "include": ["node-keyboard-loader.ts", "nodeKeyboardLoader.ts"], -} diff --git a/common/web/keyboard-processor/tests/dom/readme.md b/common/web/keyboard-processor/tests/dom/readme.md deleted file mode 100644 index 153b7f4a1e..0000000000 --- a/common/web/keyboard-processor/tests/dom/readme.md +++ /dev/null @@ -1,5 +0,0 @@ -Automated tests in this subfolder and its children are designed to facilitate simple, browser-independent -unit tests that are DOM-reliant. - -Tests for anything that may reasonably vary depending upon the browser used to run the code should go under -the "integrated" folder instead. \ No newline at end of file diff --git a/common/web/keyboard-processor/tests/dom/web-test-runner.CI.config.mjs b/common/web/keyboard-processor/tests/dom/web-test-runner.CI.config.mjs deleted file mode 100644 index d18a8a9cb0..0000000000 --- a/common/web/keyboard-processor/tests/dom/web-test-runner.CI.config.mjs +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-check -import BASE_CONFIG from './web-test-runner.config.mjs'; -import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; -import { sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; - -/** @type {import('@web/test-runner').TestRunnerConfig} */ -export default { - ...BASE_CONFIG, - reporters: [ - teamcityReporter(), /* custom-written, for CI-friendly reports */ - sessionStabilityReporter({ciMode: true}) - ] -} \ No newline at end of file diff --git a/common/web/keyboard-processor/tests/dom/web-test-runner.config.mjs b/common/web/keyboard-processor/tests/dom/web-test-runner.config.mjs deleted file mode 100644 index 76651182ac..0000000000 --- a/common/web/keyboard-processor/tests/dom/web-test-runner.config.mjs +++ /dev/null @@ -1,62 +0,0 @@ -// @ts-check -import { devices, playwrightLauncher } from '@web/test-runner-playwright'; -import { esbuildPlugin } from '@web/dev-server-esbuild'; -import { defaultReporter, summaryReporter } from '@web/test-runner'; -import { LauncherWrapper, sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; -import { importMapsPlugin } from '@web/dev-server-import-maps'; -import { dirname, resolve } from 'path'; -import { fileURLToPath } from 'url'; - -const dir = dirname(fileURLToPath(import.meta.url)); -const KEYMAN_ROOT = resolve(dir, '../../../../..'); - -/** @type {import('@web/test-runner').TestRunnerConfig} */ -export default { - // debug: true, - browsers: [ - new LauncherWrapper(playwrightLauncher({ product: 'chromium' })), - new LauncherWrapper(playwrightLauncher({ product: 'firefox' })), - new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1 })), - ], - concurrency: 10, - nodeResolve: true, - files: [ - 'build/tests/dom/**/*.spec.mjs' - ], - middleware: [ - // Rewrites short-hand paths for test resources, making them fully relative to the repo root. - function rewriteResourcePath(context, next) { - if(context.url.startsWith('/resources/')) { - context.url = '/common/test' + context.url; - } - - return next(); - } - ], - plugins: [ - esbuildPlugin({ts: true, target: 'auto'}), - importMapsPlugin({ - inject: { - importMap: { - // Redirects `eventemitter3` imports to the bundled ESM library. The standard import is an - // ESM wrapper around the CommonJS implementation, and WTR fails when it hits the CommonJS. - imports: { - 'eventemitter3': '/node_modules/eventemitter3/dist/eventemitter3.esm.js' - } - } - } - }) - ], - reporters: [ - summaryReporter({}), /* local-dev mocha-style */ - sessionStabilityReporter({}), - defaultReporter({}) - ], - /* - Un-comment the next two lines for easy interactive debugging; it'll launch the - test page in your preferred browser. - */ - // open: true, - // manual: true, - rootDir: KEYMAN_ROOT -} diff --git a/common/web/keyboard-processor/tests/tsconfig.json b/common/web/keyboard-processor/tests/tsconfig.json deleted file mode 100644 index 0e978545b8..0000000000 --- a/common/web/keyboard-processor/tests/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "baseUrl": "../", - "outDir": "../build/tests/", - "tsBuildInfoFile": "../build/tests/tsconfig.tsbuildinfo", - "rootDir": "./" - }, - "include": [ "./dom/**/*.ts"], - "exclude": [] -} diff --git a/common/web/keyboard-processor/tsconfig.json b/common/web/keyboard-processor/tsconfig.json deleted file mode 100644 index 728935b74f..0000000000 --- a/common/web/keyboard-processor/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -// Is used by the keyboard-loader submodules. -{ - "extends": "../tsconfig.kmw-main-base.json", - "compilerOptions": { - "baseUrl": "./", - "outDir": "build/obj/", - "tsBuildInfoFile": "build/obj/tsconfig.tsbuildinfo", - "rootDir": "./src/" - }, - "references": [ - { "path": "../types" }, - { "path": "../../models/types" }, - { "path": "../keyman-version/" }, - { "path": "../utils/" } - ], - "include": [ "./src/**/*.ts"], - "exclude": ["./src/keyboards/loaders/**/*.ts"] -} diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 22ffa9208c..0343d151c8 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -98,7 +98,7 @@ function do_test() { if builder_has_option --ci; then MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter" - WTR_CONFIG=.ci + WTR_CONFIG=.CI fi if builder_has_option --debug; then diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts index 757316a70a..786d9213e2 100644 --- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts @@ -58,7 +58,7 @@ export const PRIVATE_USE_IDS = [ ] as const; /* A map of key field names with values matching the `typeof` the corresponding property - * exists in common/web/keyboard-processor, keyboards/activeLayout.ts. + * exists in /web/src/engine/keyboard/src/keyboards/activeLayout.ts. * * Make sure that when one is updated, the other also is. TS types are compile-time only, * so the run-time-accessible mapping in activeLayout.ts cannot be auto-generated by TS. */ diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 7902ce7a68..75530e6bde 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -3,30 +3,30 @@ # # @darcywong00 @mcdurdin @ermshiperete @rc-swag @SabineSIL @sgschantz -/android/ @darcywong00 @sgschantz +/android/ @darcywong00 @sgschantz -/common/ @mcdurdin @rc-swag -/core/ @mcdurdin @rc-swag -/common/lexical-model-types/ @jahorton @mcdurdin -/common/models/ @jahorton @mcdurdin -/common/predictive-text/ @jahorton @mcdurdin -/common/schemas/ @mcdurdin @jahorton -/common/test/ @mcdurdin @ermshiperete -/common/web/ @jahorton @mcdurdin +/common/ @mcdurdin @rc-swag +/core/ @mcdurdin @rc-swag +/common/lexical-model-types/ @jahorton @mcdurdin +/common/models/ @jahorton @mcdurdin +/common/schemas/ @mcdurdin @jahorton +/common/test/ @mcdurdin @ermshiperete +/common/web/ @jahorton @mcdurdin -/developer/ @mcdurdin @darcywong00 -/docs/ @mcdurdin @jahorton -/ios/ @sgschantz @jahorton -/linux/ @ermshiperete @darcywong00 -/mac/ @sgschantz @SabineSIL +/developer/ @mcdurdin @darcywong00 +/docs/ @mcdurdin @jahorton +/ios/ @sgschantz @jahorton +/linux/ @ermshiperete @darcywong00 +/mac/ @sgschantz @SabineSIL -/oem/firstvoices/android/ @darcywong00 @sgschantz -/oem/firstvoices/common/ @mcdurdin @rc-swag -/oem/firstvoices/ios/ @sgschantz @jahorton -/oem/firstvoices/windows/ @rc-swag @ermshiperete -/resources/ @mcdurdin @jahorton +/oem/firstvoices/android/ @darcywong00 @sgschantz +/oem/firstvoices/common/ @mcdurdin @rc-swag +/oem/firstvoices/ios/ @sgschantz @jahorton +/oem/firstvoices/windows/ @rc-swag @ermshiperete +/resources/ @mcdurdin @jahorton # Web is currently shared between Eberhard and Joshua: -/web/ @ermshiperete @jahorton +/web/ @ermshiperete @jahorton +/web/src/engine/predictive-text/ @jahorton @mcdurdin -/windows/ @rc-swag @ermshiperete +/windows/ @rc-swag @ermshiperete diff --git a/docs/file-formats/kmx-file-format.md b/docs/file-formats/kmx-file-format.md index 6de14df56f..67288bc123 100644 --- a/docs/file-formats/kmx-file-format.md +++ b/docs/file-formats/kmx-file-format.md @@ -44,12 +44,14 @@ The very first section is a header that describes the remainder of the file. |44 | 32 | `StartGroup_Unicode` | index of starting Unicode `COMP_GROUP`, `0xFFFFFFFF` means unused | |48 | 32 | `dwFlags` | global flags for the keyboard, see description below | |52 | 32 | `dwHotKey` | default hotkey for keyboard, from [`&Hotkey`], see description below | -|56 | 32 | `dpBitmapOffset` | offset of keyboard icon, `0x0` if not present | +|56 | 32 | `dpBitmapOffset` | offset of keyboard icon | |60 | 32 | `dwBitmapSize` | size in bytes of keyboard icon, `0x0` if not present | This structure is present at the start of every .kmx file. The `KXTS` identifier can be used as a 'magic' to determine whether a binary file is a .kmx file. +Note that `dpBitmapOffset` should be ignored if `dwBitmapSize` is `0x0` -- it may be a non-zero value. + ### `dwFileVersion`: Minimum version of Keyman required to read the file The `dwFileVersion` property should be checked before attempting to read any @@ -220,7 +222,7 @@ file and is never written to the binary format. | ∆ | Bits | Name | Description | |----|------|--------------|------------------------------------------------------------------------------------------------| -| 0 | 16 | `Key` | Keyman virtual key for the rule, 0 if unused | +| 0 | 16 | `Key` | [Keyman virtual key], virtual character key, or character, for the rule, 0 if unused | | 2 | 16 | (unused) | padding, reserved | | 4 | 32 | `Line` | Line number for the rule, 0 if not compiled with debug information | | 8 | 32 | `ShiftFlags` | Modifier flags for the key, see description below | @@ -232,6 +234,58 @@ compiler expands `any()` statements found in the key part of a rule into multiple rules -- so the key part of the rule is always only ever matching a single key combination. +`ShiftFlags` contains a set of modifier and state key flags, and some additional +flags that determine how to interpret `Key`. + +### Modifier and state key flags + +The modifier and state key flags are: + +| Bit mask | Name | Description | +|--------------|------------------|-----------------------------------------------------------| +| `0x00000001` | `LCTRLFLAG` | Left Control key | +| `0x00000002` | `RCTRLFLAG` | Right Control key | +| `0x00000004` | `LALTFLAG` | Left Alt key (Option on macOS) | +| `0x00000008` | `RALTFLAG` | Right Alt key | +| `0x00000010` | `K_SHIFTFLAG` | Either Shift key | +| `0x00000020` | `K_CTRLFLAG` | Either Ctrl key | +| `0x00000040` | `K_ALTFLAG` | Either Alt key | +| `0x00000080` | `K_METAFLAG` | Either Meta key. Reserved, not for use in .kmx | +| `0x00000100` | `CAPITALFLAG` | Caps lock on | +| `0x00000200` | `NOTCAPITALFLAG` | Caps lock NOT on | +| `0x00000400` | `NUMLOCKFLAG` | Num lock on | +| `0x00000800` | `NOTNUMLOCKFLAG` | Num lock NOT on | +| `0x00001000` | `SCROLLFLAG` | Scroll lock on | +| `0x00002000` | `NOTSCROLLFLAG` | Scroll lock NOT on | + +* The chiral (left/right) modifier flags should not be used together with the + non-chiral flags. +* If neither `CAPITALFLAG` nor `NOTCAPITALFLAG` are set, then the state of the + Caps Lock key is ignored, and the same principle applies for Num lock and + Scroll lock. +* The Meta key may be the Windows key on Windows, or the + Command key on macOS. This flag is reserved for internal use, and + is not valid in a .kmx file. + +### Additional flags + +The meaning of `Key` is determined by the flags `ISVIRTUALKEY` and +`VIRTUALCHARKEY` in `ShiftFlags` (mask `0x0000C000`): + +* `0`: `Key` is a character on a key cap (US English unless the keyboard is a + mnemonic layout). All other shift flags are ignord +* `ISVIRTUALKEY`: `Key` is a US English virtual key +* `VIRTUALCHARKEY`: `Key` is a character on a key cap, combined with the shift + flags (US English unless the keyboard is a mnemonic layout) + +| Bit mask | Name | Description | +|--------------|------------------|-----------------------------------------------| +| `0x00004000` | `ISVIRTUALKEY` | `Key` is a virtual key | +| `0x00008000` | `VIRTUALCHARKEY` | Keyman 6.0+: `Key` is a Virtual character key | + +Note that the shift flags for key rules do not use the same values as hotkeys +for historical reasons. + ## Debug information for keyboards When debug information is present, it will be found in four places in compiled diff --git a/linux/mcompile/Readme.md b/linux/mcompile/Readme.md new file mode 100644 index 0000000000..f45bdbe1fb --- /dev/null +++ b/linux/mcompile/Readme.md @@ -0,0 +1,3 @@ +This is a proposal to rewrite mcompile for Linux. For this we need to query the base keyboard data from the Linux platform, then rewriting the keyboard .kmx using the same approach as is done in mcompile for Windows, but working from the data from the x11 keyboard on Linux. + +Ideally, we'd rewrite mcompile to be cross-platform (Windows, Linux, macOS), so that the keyboard interrogation would be separated from the .kmx rewriting, at least to some degree. Nevertheless it would probably be easiest to start from a standalone implementation. diff --git a/linux/mcompile/keymap/.gitignore b/linux/mcompile/keymap/.gitignore new file mode 100644 index 0000000000..fcea0ffb39 --- /dev/null +++ b/linux/mcompile/keymap/.gitignore @@ -0,0 +1,2 @@ +resources/ +build/ diff --git a/linux/mcompile/keymap/README.md b/linux/mcompile/keymap/README.md new file mode 100644 index 0000000000..d12a0820e0 --- /dev/null +++ b/linux/mcompile/keymap/README.md @@ -0,0 +1,7 @@ +This is a proposal to rewrite mcompile for Linux. For this we need to query the base keyboard data from the Linux platform, then rewriting the keyboard .kmx using the same approach as is done in mcompile for Windows, but working from the data from the x11 keyboard on Linux. + +Ideally, we'd rewrite mcompile to be cross-platform (Windows, Linux, macOS), so that the keyboard interrogation would be separated from the .kmx rewriting, at least to some degree. Nevertheless it would probably be easiest to start from a standalone implementation. +Sample program that reads US basic keyboard and compares to key value group + + +# Keymap diff --git a/linux/mcompile/keymap/build.sh b/linux/mcompile/keymap/build.sh new file mode 100755 index 0000000000..7c66e4b0b4 --- /dev/null +++ b/linux/mcompile/keymap/build.sh @@ -0,0 +1,63 @@ +#!/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" +## END STANDARD BUILD SCRIPT INCLUDE + +#. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +################################ Main script ################################ + +builder_describe \ + "Mnemonic layout recompiler for Linux" \ + "@/common/include" \ + "clean" \ + "configure" \ + "build" \ + "test" + +builder_parse "$@" + +builder_describe_outputs \ + configure build/build.ninja \ + build build/mcompile + +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 diff --git a/linux/mcompile/keymap/deadkey.cpp b/linux/mcompile/keymap/deadkey.cpp new file mode 100644 index 0000000000..325082026b --- /dev/null +++ b/linux/mcompile/keymap/deadkey.cpp @@ -0,0 +1,331 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for Linux + */ + +#include "keymap.h" +#include "deadkey.h" + +/** + * @brief create a Vector of DeadKey containing all combinations of deadkey + character for ALL possible Linux keyboards + * @return vector of Deadkey* that holds all combinations of deadkey + character +*/ +std::vector create_deadkeys_by_basechar() { + std::vector alDead; + vec_dword_2D dk_ComposeTable; + + create_DKTable(dk_ComposeTable); + + for (int i = 0; i < (int)dk_ComposeTable.size() - 1; i++) { + DeadKey* dk2 = new DeadKey(dk_ComposeTable[i][0]); + for (int j = i; j < (int)dk_ComposeTable.size(); j++) { + if ((dk_ComposeTable[i][0] == dk_ComposeTable[j][0]) && (IsKeymanUsedChar(dk_ComposeTable[j][1]))) + dk2->KMX_AddDeadKeyRow(dk_ComposeTable[j][1], dk_ComposeTable[j][2]); + } + alDead.push_back(dk2); + } + return alDead; +} + +/** + * @brief filter entries for the currently used Linux Keyboard out of a vector of all existing deadKey combinations + * @param dk the deadkey for which all combinations will be found + * @param[in,out] dkVec combinations of deadkey + character for the currently used Linux Keyboard + * @param r_All_Vec all existing combinations of deadkey + character for ALL possible Linux keyboards +*/ +void refine_alDead(KMX_WCHAR dk, std::vector& dkVec, std::vector& r_All_Vec) { + if (dk == 0) + return; + + for (int j = 0; j < (int)r_All_Vec.size(); j++) { + if (dk == r_All_Vec[j]->KMX_GetDeadCharacter()) { + if (!found_dk_inVector(dk, dkVec)) { + dkVec.push_back(r_All_Vec[j]); + } + return; + } + } +} + +/** + * @brief check whether a deadkey already exists in the deadkey vector + * @param dk the deadkey to be found + * @param dkVec vector containing combinations of deadkey + character + * @return true if deadkey alredy exists; + * false if not +*/ +bool found_dk_inVector(KMX_WCHAR dk, std::vector& dkVec) { + for (int i = 0; i < dkVec.size(); i++) { + if (dk == dkVec[i]->KMX_GetDeadCharacter()) + return true; + } + return false; +} + +/** + * @brief find all deadkey combinations for a certain deadkey in a vector of all deadkey combinations + * @param r_dk_ComposeTable vector containing all possible deadkey combinations + * @param dk deadkey of interest + * @param[in,out] dk_SingleTable vector containing all dk-character combinations for a specific deadkey dk + * @return true if successful; + * false if not +*/ +bool query_dk_combinations_for_specific_dk(vec_dword_2D& r_dk_ComposeTable, KMX_DWORD dk, vec_dword_2D& dk_SingleTable) { + vec_dword_1D row; + + for (int i = 0; i < (int)r_dk_ComposeTable.size(); i++) { + if (r_dk_ComposeTable[i][0] == dk && IsKeymanUsedChar(r_dk_ComposeTable[i][1])) { + row.push_back(r_dk_ComposeTable[i][0]); + row.push_back(r_dk_ComposeTable[i][1]); + row.push_back(r_dk_ComposeTable[i][2]); + dk_SingleTable.push_back(row); + row.clear(); + } + } + + if (dk_SingleTable.size() > 0) + return true; + else + return false; +} + +/** + * @brief convert a character to the upper-case equivalent and find the corresponding shiftstate + * of the entered keyval: a(97) -> A(65) + Base A(65) -> A(65) + Shift + * @param kval keyval that might be changed + * @param[in,out] shift the shiftstate of the entered keyval + * @param keymap a pointer to the currently used (underlying) keyboard layout + * @return the upper case equivalent of the keyval +*/ +KMX_DWORD KMX_change_keyname_to_capital(KMX_DWORD kVal, KMX_DWORD& shift, GdkKeymap* keymap) { + guint keyval = (guint)kVal; + GdkKeymapKey* keys; + gint n_keys; + + KMX_DWORD capitalKeyval = (KMX_DWORD)gdk_keyval_to_upper(kVal); + if (keyval != 0) { + gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys); + for (int i = 0; i < n_keys; i++) { + if (keys[i].group == 0) { + shift = keys[i].level; + return capitalKeyval; + } + } + } + return capitalKeyval; +} + +/** + * @brief append a 1D-vector containing name, base character and unicode_value to a 2D-Vector + * holding all possible combinations of deadkey + character for all Linux keyboards + * @param[in,out] dk_ComposeTable 2D-Vector holding all possible combinations of deadkey + character + * @param diacritic_name the name of a diacritic + * @param base_char base character + * @param unicode_value Unicode-value of the combined character +*/ +void add_deadkey_combination(vec_dword_2D& dk_ComposeTable, std::string diacritic_name, std::string base_char, KMX_DWORD unicode_value) { + vec_dword_1D line; + line.push_back(convertNamesTo_DWORD_Value(diacritic_name)); + line.push_back(convertNamesTo_DWORD_Value(base_char)); + line.push_back(unicode_value); + dk_ComposeTable.push_back(line); +} + +/** + * @brief create a 2D-Vector containing all possible combinations of deadkey + character for all Linux keyboards + * the values are taken from from: https://help.ubuntu.com/community/GtkDeadKeyTable#Accents + * dk_ComposeTable[i][0] : diacritic_name (e.g. dead_circumflex) + * dk_ComposeTable[i][1] : base_char (e.g. a) + * dk_ComposeTable[i][2] : unicode_value-Value (e.g. 0x00E2) + * @param[in,out] dk_ComposeTable +*/ +void create_DKTable(vec_dword_2D& dk_ComposeTable) { + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "a", 0x00E2); // small A with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "A", 0x00C2); // capital A with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "e", 0x00EA); // small E with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "E", 0x00CA); // capital E with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "i", 0x00EE); // small I with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "I", 0x00CE); // capital I with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "o", 0x00F4); // small O with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "O", 0x00D4); // capital O with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "u", 0x00FB); // small U with circumflex + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "U", 0x00DB); // capital U with circumflex + + add_deadkey_combination(dk_ComposeTable, "dead_acute", "a", 0x00E1); // small A with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "A", 0x00C1); // capital A with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "c", 0x0107); // small C with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "C", 0x0106); // capital C with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "e", 0x00E9); // small E with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "E", 0x00C9); // capital E with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "i", 0x00ED); // small I with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "I", 0x00CD); // capital I with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "l", 0x013A); // small L with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "L", 0x0139); // capital L with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "n", 0x0144); // small N with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "N", 0x0143); // capital N with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "o", 0x00F3); // small O with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "O", 0x00D3); // capital O with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "r", 0x0155); // small R with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "R", 0x0154); // capital R with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "s", 0x015B); // small S with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "S", 0x015A); // capital S with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "u", 0x00FA); // small U with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "U", 0x00DA); // capital U with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "y", 0x00FD); // small Y with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "Y", 0x00DD); // capital Y with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "z", 0x017A); // small Z with acute + add_deadkey_combination(dk_ComposeTable, "dead_acute", "Z", 0x0179); // capital Z with acute + + add_deadkey_combination(dk_ComposeTable, "dead_grave", "a", 0x00E0); // small A with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "A", 0x00C0); // capital A with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "e", 0x00E8); // small E with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "E", 0x00C8); // capital E with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "i", 0x00EC); // small I with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "I", 0x00CC); // capital I with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "o", 0x00F2); // small O with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "O", 0x00D2); // capital O with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "u", 0x00F9); // small U with grave + add_deadkey_combination(dk_ComposeTable, "dead_grave", "U", 0x00D9); // capital U with grave + + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "a", 0x00E3); // small A with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "A", 0x00C3); // capital A with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "i", 0x0129); // small I with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "I", 0x0128); // capital I with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "n", 0x00F1); // small N with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "N", 0x00D1); // capital N with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "o", 0x00F5); // small O with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "O", 0x00D5); // capital O with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "u", 0x0169); // small U with tilde + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "U", 0x0168); // capital U with tilde + + add_deadkey_combination(dk_ComposeTable, "dead_macron", "a", 0x0101); // small A with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "A", 0x0100); // capital A with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "e", 0x0113); // small E with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "E", 0x0112); // capital E with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "i", 0x012B); // small I with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "I", 0x012A); // capital I with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "o", 0x014D); // small O with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "O", 0x014C); // capital O with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "u", 0x016B); // small U with macron + add_deadkey_combination(dk_ComposeTable, "dead_macron", "U", 0x016A); // capital U with macron + + add_deadkey_combination(dk_ComposeTable, "dead_breve", "a", 0x0103); // small A with breve + add_deadkey_combination(dk_ComposeTable, "dead_breve", "A", 0x0102); // capital A with breve + add_deadkey_combination(dk_ComposeTable, "dead_breve", "g", 0x011F); // small G with breve + add_deadkey_combination(dk_ComposeTable, "dead_breve", "G", 0x011E); // capital G with breve + + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "e", 0x0117); // small E with dot above + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "E", 0x0116); // capital E with dot above + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "i", 0x0131); // small DOTLESS_I + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "I", 0x0130); // capital I with dot above + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "z", 0x017C); // small Z with dot above + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "Z", 0x017B); // capital Z with dot above + + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "a", 0x00E4); // small A with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "A", 0x00C4); // capital A with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "e", 0x00EB); // small E with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "E", 0x00CB); // capital E with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "i", 0x00EF); // small I with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "I", 0x00CF); // capital I with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "o", 0x00F6); // small O with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "O", 0x00D6); // capital O with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "u", 0x00FC); // small U with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "U", 0x00DC); // capital U with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "y", 0x00FF); // small Y with diaeresis + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "Y", 0x0178); // capital Y with diaeresis + + add_deadkey_combination(dk_ComposeTable, "dead_abovering", "a", 0x00E5); // small A with ring above + add_deadkey_combination(dk_ComposeTable, "dead_abovering", "A", 0x00C5); // capital A with ring above + add_deadkey_combination(dk_ComposeTable, "dead_abovering", "u", 0x016F); // small U with ring above + add_deadkey_combination(dk_ComposeTable, "dead_abovering", "U", 0x016E); // capital U with ring above + + add_deadkey_combination(dk_ComposeTable, "dead_doubleacute", "o", 0x0151); // small O with double acute + add_deadkey_combination(dk_ComposeTable, "dead_doubleacute", "O", 0x0150); // capital O with double acute + add_deadkey_combination(dk_ComposeTable, "dead_doubleacute", "u", 0x0171); // small U with double acute + add_deadkey_combination(dk_ComposeTable, "dead_doubleacute", "U", 0x0170); // capital U with double acute + + add_deadkey_combination(dk_ComposeTable, "dead_caron", "c", 0x010D); // small C with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "C", 0x010C); // capital C with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "d", 0x010F); // small D with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "D", 0x010E); // capital D with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "e", 0x011B); // small E with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "E", 0x011A); // capital E with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "l", 0x013E); // small L with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "L", 0x013D); // capital L with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "n", 0x0148); // small N with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "N", 0x0147); // capital N with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "r", 0x0159); // small R with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "R", 0x0158); // capital R with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "s", 0x0161); // small S with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "S", 0x0160); // capital S with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "t", 0x0165); // small T with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "T", 0x0164); // capital T with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "z", 0x017E); // small Z with caron + add_deadkey_combination(dk_ComposeTable, "dead_caron", "Z", 0x017D); // capital Z with caron + + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "c", 0x00E7); // small C with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "C", 0x00C7); // capital C with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "g", 0x0123); // small G with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "G", 0x0122); // capital G with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "k", 0x0137); // small K with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "K", 0x0136); // capital K with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "l", 0x013C); // small L with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "L", 0x013B); // capital L with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "n", 0x0146); // small N with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "N", 0x0145); // capital N with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "r", 0x0157); // small R with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "R", 0x0156); // capital R with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "s", 0x015F); // small S with cedilla + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "S", 0x015E); // capital S with cedilla + + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "a", 0x0105); // small A with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "A", 0x0104); // capital A with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "e", 0x0119); // small E with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "E", 0x0118); // capital E with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "i", 0x012F); // small I with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "I", 0x012E); // capital I with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "u", 0x0173); // small U with ogonek + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "U", 0x0172); // capital U with ogonek + + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "space", 0x005E); // CIRCUMFLEX_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_acute", "space", 0x0027); // APOSTROPHE + add_deadkey_combination(dk_ComposeTable, "dead_grave", "space", 0x0060); // GRAVE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_breve", "space", 0x02D8); // BREVE + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "space", 0x02D9); // DOT_ABOVE + add_deadkey_combination(dk_ComposeTable, "dead_abovering", "space", 0x02DA); // RING_ABOVE + add_deadkey_combination(dk_ComposeTable, "dead_doubleacute", "space", 0x02DD); // DOUBLE_ACUTE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_caron", "space", 0x02C7); // CARON + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "space", 0x00B8); // CEDILLA + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "space", 0x02DB); // OGONEK + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "space", 0x007E); // TILDE + + add_deadkey_combination(dk_ComposeTable, "dead_breve", "dead_breve", 0x02D8); // BREVE + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "abovedot", 0x02D9); // DOT_ABOVE + add_deadkey_combination(dk_ComposeTable, "dead_abovedot", "dead_abovedot", 0x02D9); // DOT_ABOVE + add_deadkey_combination(dk_ComposeTable, "dead_abovering", "dead_abovering", 0x02DA); // RING_ABOVE + add_deadkey_combination(dk_ComposeTable, "dead_acute", "apostrophe", 0x00B4); // ACUTE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_acute", "acute", 0x00B4); // ACUTE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_acute", "dead_acute", 0x00B4); // ACUTE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_doubleacute", "dead_doubleacute", 0x02DD); // DOUBLE_ACUTE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_caron", "caron", 0x02C7); // CARON + add_deadkey_combination(dk_ComposeTable, "dead_caron", "dead_caron", 0x02C7); // CARON + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "comma", 0x00B8); // CEDILLA + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "cedilla", 0x00B8); // CEDILLA + add_deadkey_combination(dk_ComposeTable, "dead_cedilla", "dead_cedilla", 0x00B8); // CEDILLA + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "minus", 0x00AF); // MACRON + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "asciicircum", 0x005E); // CIRCUMFLEX_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "underscore", 0x00AF); // MACRON + add_deadkey_combination(dk_ComposeTable, "dead_circumflex", "dead_circumflex", 0x005E); // CIRCUMFLEX_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "quotedbl", 0x00A8); // DIAERESIS + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "diaeresis", 0x00A8); // DIAERESIS + add_deadkey_combination(dk_ComposeTable, "dead_diaeresis", "dead_diaeresis", 0x00A8); // DIAERESIS + add_deadkey_combination(dk_ComposeTable, "dead_grave", "grave", 0x0060); // GRAVE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_grave", "dead_grave", 0x0060); // GRAVE_ACCENT + add_deadkey_combination(dk_ComposeTable, "dead_macron", "macron", 0x00AF); // MACRON + add_deadkey_combination(dk_ComposeTable, "dead_macron", "dead_macron", 0x00AF); // MACRON + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "ogonek", 0x02DB); // OGONEK + add_deadkey_combination(dk_ComposeTable, "dead_ogonek", "dead_ogonek", 0x02DB); // OGONEK + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "asciitilde", 0x007E); // TILDE + add_deadkey_combination(dk_ComposeTable, "dead_tilde", "dead_tilde", 0x007E); // TILDE +} diff --git a/linux/mcompile/keymap/deadkey.h b/linux/mcompile/keymap/deadkey.h new file mode 100644 index 0000000000..5c52768ecf --- /dev/null +++ b/linux/mcompile/keymap/deadkey.h @@ -0,0 +1,30 @@ + +#pragma once +#ifndef DEADKEY_H +#define DEADKEY_H + +#include "mc_import_rules.h" +#include + +/** @brief create a Vector of DeadKey containing all combinations of deadkey + character for ALL possible Linux keyboards */ +std::vector create_deadkeys_by_basechar(); + +/** @brief filter entries for the currently used Linux Keyboard out of a vector of all existing deadKey combinations */ +void refine_alDead(KMX_WCHAR dk, std::vector& dkVec, std::vector& r_All_Vec); + +/** @brief check whether a deadkey already exists in the deadkey vector */ +bool found_dk_inVector(KMX_WCHAR dk, std::vector& dkVec); + +/** @brief find all deadkey combinations for a certain deadkey in a vector of all deadkey combinations */ +bool query_dk_combinations_for_specific_dk(vec_dword_2D& r_dk_ComposeTable, KMX_DWORD dk, vec_dword_2D& dk_SingleTable); + +/** @brief convert a character to the upper-case equivalent and find the corresponding shiftstate of the entered keyval */ +KMX_DWORD KMX_change_keyname_to_capital(KMX_DWORD kVal, KMX_DWORD& shift, GdkKeymap* keymap); + +/** @brief append a 1D-vector containing name, base character and unicode_value to a 2D-Vector */ +void add_deadkey_combination(vec_dword_2D& dk_ComposeTable, std::string diacritic_name, std::string base_char, KMX_DWORD unicode_value); + +/** @brief create a 2D-Vector containing all possible combinations of deadkey + character for all Linux keyboards */ +void create_DKTable(vec_dword_2D& dk_ComposeTable); + +#endif /*DEADKEY_H*/ diff --git a/linux/mcompile/keymap/keymap.cpp b/linux/mcompile/keymap/keymap.cpp new file mode 100644 index 0000000000..25d7721482 --- /dev/null +++ b/linux/mcompile/keymap/keymap.cpp @@ -0,0 +1,1042 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for Linux + * + * 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" +#include "/usr/include/xcb/xproto.h" +#include + +/** + * @brief map a shiftstate used on Windows to a shiftstate suitable for gdk_keymap_translate_keyboard_state() on Linux + * Windows: (Base: 00000000 (0); Shift 00010000 (16); AltGr 00001001 (9); Shift+AltGr 00011001 (25)) + * Linux: (Base: 0; Shift 1; ALTGr 2; Shift+ALTGr 3 ) + * @param shiftState shiftstate used on Windows + * @return a shiftstate usable for gdk_keymap_translate_keyboard_state() on linux if available + * if shiftState is a windows ShiftState: convert the windows ShiftState (0,16,9,25) to a Linux ShiftState (0,1,2,3) that is then used as "Level" in gdk + * if shiftState is NOT a windows ShiftState (then in_ShiftState is already a Linux shiftstate): return the entered shiftstate + */ +int convert_Shiftstate_to_LinuxShiftstate(int shiftState) { + if (shiftState == 0) return 0; // Win ss 0 -> Lin ss 0 + else if (shiftState == K_SHIFTFLAG) return XCB_MOD_MASK_SHIFT; // Win ss 16 -> Lin ss 1 + else if (shiftState == (LCTRLFLAG | RALTFLAG)) return XCB_MOD_MASK_LOCK; // Win ss 9 -> Lin ss 2 + else if (shiftState == (K_SHIFTFLAG | LCTRLFLAG | RALTFLAG)) return (XCB_MOD_MASK_SHIFT | XCB_MOD_MASK_LOCK); // Win ss 25 -> Lin ss 3 + else return shiftState; // Lin ss x -> Lin ss x +} + +/** + * @brief map a shiftstate used for rgkey to a shiftstate suitable for gdk_keymap_translate_keyboard_state() on Linux + * rgkey: (Base: 0; Shift1 ; AltGr 6; Shift+AltGr 7) + * Linux: (Base: 0; Shift 1; ALTGr 2; Shift+ALTGr 3 ) + * @param shiftState shiftstate used for rgkey + * @return a shiftstate usable for gdk_keymap_translate_keyboard_state() on linux if available + * if shiftState is a rgkey ShiftState: convert the rgkey ShiftState (0,1,6,7) to a Linux ShiftState (0,1,2,3) that is then used as "Level" in gdk + * if shiftState is NOT a rgkey ShiftState (then in_ShiftState is already a Linux shiftstate): return the entered shiftstate + */ +int convert_rgkey_Shiftstate_to_LinuxShiftstate(ShiftState shiftState) { + if (shiftState == Base) return 0; // rgkey ss 0 -> Lin ss 0 + else if (shiftState == Shft) return XCB_MOD_MASK_SHIFT; // rgkey ss 1 -> Lin ss 1 + else if (shiftState == MenuCtrl) return XCB_MOD_MASK_LOCK; // rgkey ss 6 -> Lin ss 2 + else if (shiftState == ShftMenuCtrl) return (XCB_MOD_MASK_SHIFT | XCB_MOD_MASK_LOCK); // rgkey ss 7 -> Lin ss 3 + else return shiftState; // Lin ss x -> Lin ss x +} + +/** + * @brief check for correct input parameter that will later be used in gdk_keymap_translate_keyboard_state() + * @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, gint keycode) { + + // We're dealing with shiftstates 0,1,2,3 + if (shiftstate < 0 || shiftstate > 3) + return false; + + // For K_Space (keycode = 65) only Base and Shift are allowed + if (keycode == 65 && shiftstate > 1) + return false; + + if (keycode > keycode_max) + return false; + + return true; +} + +/** + * @brief convert names of keys stated in a symbol file to a keyvalue + * @param tok_str the name stated in symbol file + * @return the keyvalue + */ +KMX_DWORD convertNamesTo_DWORD_Value(std::string tok_str) { + // more on https://manpages.ubuntu.com/manpages/jammy/man3/keysyms.3tk.html + std::map key_values; + + key_values["ampersand"] = 38; + key_values["apostrophe"] = 39; + key_values["asciicircum"] = 136; + key_values["asciitilde"] = 126; + key_values["asterisk"] = 42; + key_values["at"] = 64; + key_values["backslash"] = 92; + key_values["BackSpace"] = 65288; + key_values["bar"] = 124; + key_values["braceleft"] = 123; + key_values["braceright"] = 125; + key_values["bracketleft"] = 91; + key_values["bracketright"] = 93; + key_values["colon"] = 58; + key_values["comma"] = 44; + key_values["diaeresis"] = 168; + key_values["dollar"] = 36; + key_values["equal"] = 61; + key_values["exclam"] = 33; + key_values["grave"] = 96; + key_values["greater"] = 62; + key_values["less"] = 60; + key_values["minus"] = 45; + key_values["numbersign"] = 35; + key_values["parenleft"] = 40; + key_values["parenright"] = 41; + key_values["percent"] = 37; + key_values["period"] = 46; + key_values["plus"] = 43; + key_values["question"] = 63; + key_values["quotedbl"] = 34; + key_values["semicolon"] = 59; + key_values["slash"] = 47; + key_values["space"] = 32; + key_values["ssharp"] = 223; + key_values["underscore"] = 95; + + key_values["nobreakspace"] = 160; + key_values["exclamdown"] = 161; + key_values["cent"] = 162; + key_values["sterling"] = 163; + key_values["currency"] = 164; + key_values["yen"] = 165; + key_values["brokenbar"] = 166; + key_values["section"] = 167; + key_values["copyright"] = 169; + key_values["ordfeminine"] = 170; + key_values["guillemotleft"] = 171; + key_values["notsign"] = 172; + key_values["hyphen"] = 173; + key_values["registered"] = 174; + key_values["macron"] = 175; + key_values["degree"] = 176; + key_values["plusminus"] = 177; + key_values["twosuperior"] = 178; + key_values["threesuperior"] = 179; + key_values["acute"] = 180; + key_values["mu"] = 181; + key_values["paragraph"] = 182; + key_values["periodcentered"] = 183; + key_values["cedilla"] = 184; + key_values["onesuperior"] = 185; + key_values["masculine"] = 186; + key_values["guillemotright"] = 187; + key_values["onequarter"] = 188; + key_values["onehalf"] = 189; + key_values["threequarters"] = 190; + key_values["questiondown"] = 191; + key_values["Agrave"] = 192; + key_values["Aacute"] = 193; + key_values["Acircumflex"] = 194; + key_values["Atilde"] = 195; + key_values["Adiaeresis"] = 196; + key_values["Aring"] = 197; + key_values["AE"] = 198; + key_values["Ccedilla"] = 199; + key_values["Egrave"] = 200; + key_values["Eacute"] = 201; + key_values["Ecircumflex"] = 202; + key_values["Ediaeresis"] = 203; + key_values["Igrave"] = 204; + key_values["Iacute"] = 205; + key_values["Icircumflex"] = 206; + key_values["Idiaeresis"] = 207; + key_values["ETH"] = 208; + key_values["Ntilde"] = 209; + key_values["Ograve"] = 210; + key_values["Oacute"] = 211; + key_values["Ocircumflex"] = 212; + key_values["Otilde"] = 213; + key_values["Odiaeresis"] = 214; + key_values["multiply"] = 215; + key_values["Oslash"] = 216; + key_values["Ugrave"] = 217; + key_values["Uacute"] = 218; + key_values["Ucircumflex"] = 219; + key_values["Udiaeresis"] = 220; + key_values["Yacute"] = 221; + key_values["THORN"] = 222; + key_values["agrave"] = 224; + key_values["aacute"] = 225; + key_values["acircumflex"] = 226; + key_values["atilde"] = 227; + key_values["adiaeresis"] = 228; + key_values["aring"] = 229; + key_values["ae"] = 230; + key_values["ccedilla"] = 231; + key_values["egrave"] = 232; + key_values["eacute"] = 233; + key_values["ecircumflex"] = 234; + key_values["ediaeresis"] = 235; + key_values["igrave"] = 236; + key_values["iacute"] = 237; + key_values["icircumflex"] = 238; + key_values["idiaeresis"] = 239; + key_values["eth"] = 240; + key_values["ntilde"] = 241; + key_values["ograve"] = 242; + key_values["oacute"] = 243; + key_values["ocircumflex"] = 244; + key_values["otilde"] = 245; + key_values["odiaeresis"] = 246; + key_values["division"] = 247; + key_values["oslash"] = 248; + key_values["ugrave"] = 249; + key_values["uacute"] = 250; + key_values["ucircumflex"] = 251; + key_values["udiaeresis"] = 252; + key_values["yacute"] = 253; + key_values["thorn"] = 254; + key_values["ydiaeresis"] = 255; + key_values["Aogonek"] = 417; + key_values["breve"] = 418; + key_values["Lstroke"] = 419; + key_values["Lcaron"] = 421; + key_values["Sacute"] = 422; + key_values["Scaron"] = 425; + key_values["Scedilla"] = 426; + key_values["Tcaron"] = 427; + key_values["Zacute"] = 428; + key_values["Zcaron"] = 430; + key_values["Zabovedot"] = 431; + key_values["aogonek"] = 433; + key_values["ogonek"] = 434; + key_values["lstroke"] = 435; + key_values["lcaron"] = 437; + key_values["sacute"] = 438; + key_values["caron"] = 439; + key_values["scaron"] = 441; + key_values["scedilla"] = 442; + key_values["tcaron"] = 443; + key_values["zacute"] = 444; + key_values["doubleacute"] = 445; + key_values["zcaron"] = 446; + key_values["zabovedot"] = 447; + key_values["Racute"] = 448; + key_values["Abreve"] = 451; + key_values["Lacute"] = 453; + key_values["Cacute"] = 454; + key_values["Ccaron"] = 456; + key_values["Eogonek"] = 458; + key_values["Ecaron"] = 460; + key_values["Dcaron"] = 463; + key_values["Dstroke"] = 464; + key_values["Nacute"] = 465; + key_values["Ncaron"] = 466; + key_values["Odoubleacute"] = 469; + key_values["Rcaron"] = 472; + key_values["Uring"] = 473; + key_values["Udoubleacute"] = 475; + key_values["Tcedilla"] = 478; + key_values["racute"] = 480; + key_values["abreve"] = 483; + key_values["lacute"] = 485; + key_values["cacute"] = 486; + key_values["ccaron"] = 488; + key_values["eogonek"] = 490; + key_values["ecaron"] = 492; + key_values["dcaron"] = 495; + key_values["dstroke"] = 496; + key_values["nacute"] = 497; + key_values["ncaron"] = 498; + key_values["odoubleacute"] = 501; + key_values["rcaron"] = 504; + key_values["uring"] = 505; + key_values["udoubleacute"] = 507; + key_values["tcedilla"] = 510; + key_values["abovedot"] = 511; + key_values["Hstroke"] = 673; + key_values["Hcircumflex"] = 678; + key_values["Iabovedot"] = 681; + key_values["Gbreve"] = 683; + key_values["Jcircumflex"] = 684; + key_values["hstroke"] = 689; + key_values["hcircumflex"] = 694; + key_values["idotless"] = 697; + key_values["gbreve"] = 699; + key_values["jcircumflex"] = 700; + key_values["Cabovedot"] = 709; + key_values["Ccircumflex"] = 710; + key_values["Gabovedot"] = 725; + key_values["Gcircumflex"] = 728; + key_values["Ubreve"] = 733; + key_values["Scircumflex"] = 734; + key_values["cabovedot"] = 741; + key_values["ccircumflex"] = 742; + key_values["gabovedot"] = 757; + key_values["gcircumflex"] = 760; + key_values["ubreve"] = 765; + key_values["scircumflex"] = 766; + key_values["kra"] = 930; + key_values["Rcedilla"] = 931; + key_values["Itilde"] = 933; + key_values["Lcedilla"] = 934; + key_values["Emacron"] = 938; + key_values["Gcedilla"] = 939; + key_values["Tslash"] = 940; + key_values["rcedilla"] = 947; + key_values["itilde"] = 949; + key_values["lcedilla"] = 950; + key_values["emacron"] = 954; + key_values["gcedilla"] = 955; + key_values["tslash"] = 956; + key_values["ENG"] = 957; + key_values["eng"] = 959; + key_values["Amacron"] = 960; + key_values["Iogonek"] = 967; + key_values["Eabovedot"] = 972; + key_values["Imacron"] = 975; + key_values["Ncedilla"] = 977; + key_values["Omacron"] = 978; + key_values["Kcedilla"] = 979; + key_values["Uogonek"] = 985; + key_values["Utilde"] = 989; + key_values["Umacron"] = 990; + key_values["amacron"] = 992; + key_values["iogonek"] = 999; + key_values["eabovedot"] = 1004; + key_values["imacron"] = 1007; + key_values["ncedilla"] = 1009; + key_values["omacron"] = 1010; + key_values["kcedilla"] = 1011; + key_values["uogonek"] = 1017; + key_values["utilde"] = 1021; + key_values["umacron"] = 1022; + key_values["overline"] = 1150; + + key_values["dead_abovedot"] = 729; + key_values["dead_abovering"] = 730; + key_values["dead_acute"] = 180; + key_values["dead_breve"] = 728; + key_values["dead_caron"] = 711; + key_values["dead_cedilla"] = 184; + key_values["dead_circumflex"] = 94; + key_values["dead_diaeresis"] = 168; + key_values["dead_doubleacute"] = 733; + key_values["dead_grave"] = 96; + key_values["dead_ogonek"] = 731; + key_values["dead_perispomeni"] = 126; + key_values["dead_tilde"] = 126; + + key_values["acute accent"] = 0xB4; + + if (tok_str.size() == 1) { + return (KMX_DWORD)(*tok_str.c_str()); + } else { + std::map::iterator it; + for (it = key_values.begin(); it != key_values.end(); ++it) { + if (it->first == tok_str) + return it->second; + } + } + return INVALID_NAME; +} + +/** + * @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 keymap 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 createOneVectorFromBothKeyboards(vec_dword_3D& all_vector, GdkKeymap* keymap) { + // store contents of the English (US) keyboard in all_vector + if (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 (append_underlying_ToVector(all_vector, keymap)) { + 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 write_US_ToVector(vec_dword_3D& vec_us) { + // create 1D-vector of the complete line + vec_string_1D vector_completeUS; + if (createCompleteVector_US(vector_completeUS)) { + printf("ERROR: can't create complete row US \n"); + return 1; + } + + // split contents of 1D Vector to 3D vector + if (split_US_To_3D_Vector(vec_us, vector_completeUS)) { + return 1; + } + + if (vector_completeUS.size() < 2) { + printf("ERROR: several keys of the US keyboard are not processed \n"); + return 1; + } + + if (vector_completeUS.size() != 48) { + printf("WARNING: the wrong keyboard input might have been chosen.\n"); + return 0; + } + + return 0; +} + +/** + * @brief create a 1D-Vector containing all relevant entries of the symbol file us basic + * @param[in,out] complete_List the 1D-Vector + * @return FALSE on success; + * TRUE if file could not be opened +*/ +bool createCompleteVector_US(vec_string_1D& complete_List) { + // in the Configuration file we find the appopriate paragraph between "xkb_symbol " and the next xkb_symbol + // then copy all rows starting with "key <" to a 1D-Vector + + bool create_row = false; + const char* key = "key <"; + std::string line; + std::string str_us_kbd_name("xkb_symbols \"basic\""); + std::string xbk_mark = "xkb_symbol"; + std::ifstream inputFile("/usr/share/X11/xkb/symbols/us"); + + if (!inputFile.is_open()) { + printf("ERROR: could not open file!\n"); + return TRUE; + } + + else { + while (getline(inputFile, line)) { + // stop when finding the mark xkb_symbol + if (line.find(xbk_mark) != std::string::npos) + create_row = false; + + // start when finding the mark xkb_symbol + correct layout + if (line.find(str_us_kbd_name) != std::string::npos) + create_row = true; + + // as long as we are in the same xkb_symbol layout block and find "key <" we push the whole line into a 1D-vector + else if (create_row && (line.find(key) != std::string::npos)) { + complete_List.push_back(line); + } + } + } + complete_List.push_back(" key { [ space, space] };"); + + inputFile.close(); + return FALSE; +} + +/** + * @brief convert the key name obtained from symbol file to the matching keycode + * e.g. name of Key ) --> Keycode 15 + * @param key_name as stated in the symbol file + * @return the equivalent keycode +*/ +int get_keycode_from_keyname(std::string key_name) { + int out = INVALID_NAME; + + if (key_name == "key") + out = 49; /* VK_ BKQUOTE */ + else if (key_name == "key") + out = 10; /* VK_1 */ + else if (key_name == "key") + out = 11; /* VK_2 */ + else if (key_name == "key") + out = 12; /* VK_3 */ + else if (key_name == "key") + out = 13; /* VK_4 */ + else if (key_name == "key") + out = 14; /* VK_5 */ + else if (key_name == "key") + out = 15; /* VK_6 */ + else if (key_name == "key") + out = 16; /* VK_7 */ + else if (key_name == "key") + out = 17; /* VK_8 */ + else if (key_name == "key") + out = 18; /* VK_9 */ + else if (key_name == "key") + out = 19; /* VK_0 */ + else if (key_name == "key") + out = 20; /* VK_MINUS K_HYPHEN */ + else if (key_name == "key") + out = 21; /* VK_EQUAL */ + + else if (key_name == "key") + out = 24; /* VK_Q */ + else if (key_name == "key") + out = 25; /* VK_W */ + else if (key_name == "key") + out = 26; /* VK_E */ + else if (key_name == "key") + out = 27; /* VK_R */ + else if (key_name == "key") + out = 28; /* VK_T */ + else if (key_name == "key") + out = 29; /* VK_Y */ + else if (key_name == "key") + out = 30; /* VK_U */ + else if (key_name == "key") + out = 31; /* VK_I */ + else if (key_name == "key") + out = 32; /* VK_O */ + else if (key_name == "key") + out = 33; /* VK_P */ + else if (key_name == "key") + out = 34; /* VK_LEFTBRACE */ + else if (key_name == "key") + out = 35; /* VK_RIGHTBRACE */ + + else if (key_name == "key") + out = 38; /* VK_A */ + else if (key_name == "key") + out = 39; /* VK_S */ + else if (key_name == "key") + out = 40; /* VK_D */ + else if (key_name == "key") + out = 41; /* VK_F */ + else if (key_name == "key") + out = 42; /* VK_G */ + else if (key_name == "key") + out = 43; /* VK_H */ + else if (key_name == "key") + out = 44; /* VK_J */ + else if (key_name == "key") + out = 45; /* VK_K */ + else if (key_name == "key") + out = 46; /* VK_L */ + else if (key_name == "key") + out = 47; /* VK_SEMICOLON */ + else if (key_name == "key") + out = 48; /* VK_APOSTROPHE */ + + else if (key_name == "key") + out = 52; /* VK_Z */ + else if (key_name == "key") + out = 53; /* VK_X */ + else if (key_name == "key") + out = 54; /* VK_C */ + else if (key_name == "key") + out = 55; /* VK_V */ + else if (key_name == "key") + out = 56; /* VK_B */ + else if (key_name == "key") + out = 57; /* VK_N */ + else if (key_name == "key") + out = 58; /* VK_M */ + else if (key_name == "key") + out = 59; /* VK_ COMMA */ + else if (key_name == "key") + out = 60; /* VK_DOT */ + else if (key_name == "key") + out = 61; /* VK_SLASH */ + else if (key_name == "key") + out = 51; /* VK_BKSLASH */ + else if (key_name == "key") + out = 63; /* VK_RIGHTSHIFT */ + else if (key_name == "key") + out = 65; /* VK_SPACE */ + + return out; +} + +/** + * @brief process each element of a 1D-Vector, split and write to a 3D-Vector + * @param[in,out] all_US a 3D_Vector containing all keyvalues of the US keyboard + * @param completeList a 1D-Vector containing all relevant entries copied from the symbol file us basic + * @return 0 on success if entry can be split +*/ +int split_US_To_3D_Vector(vec_dword_3D& all_US, vec_string_1D completeList) { + // 1: take the whole line of the 1D-Vector and remove unwanted characters. + // 2: seperate the name e.g. key from the shiftstates + // 3: convert to KMX_DWORD + // 4: push Names/Shiftstates to shift_states and then shift_states to All_US, our 3D-Vector holding all Elements + + std::vector delim{' ', '[', ']', '}', ';', '\t', '\n'}; + int keyCode; + vec_string_1D tokens; + vec_dword_1D tokens_dw; + vec_dword_2D shift_states; + + // loop through the whole vector + for (int k = 0; k < (int)completeList.size(); k++) { + // remove all unwanted char + for (int i = 0; i < (int)delim.size(); i++) { + completeList[k].erase(remove(completeList[k].begin(), completeList[k].end(), delim[i]), completeList[k].end()); + } + + // only lines with ("key<.. are of interest + if (completeList[k].find("key<") != std::string::npos) { + // split off the key names + std::istringstream split_Keyname(completeList[k]); + for (std::string each; std::getline(split_Keyname, each, '{'); tokens.push_back(each)) { + // empty + } + + // replace keys names with Keycode ( with 21,...) + keyCode = get_keycode_from_keyname(tokens[0]); + tokens[0] = std::to_string(keyCode); + + // seperate rest of the vector to its elements and push to 'tokens' + std::istringstream split_Characters(tokens[1]); + tokens.pop_back(); + + for (std::string each; std::getline(split_Characters, each, ','); + tokens.push_back(each)); + + // now convert all to KMX_DWORD and fill tokens + tokens_dw.push_back((KMX_DWORD)keyCode); + + for (int i = 1; i < (int)tokens.size(); i++) { + // replace a name with a single character ( a -> a ; equal -> = ) + KMX_DWORD tokens_int = convertNamesTo_DWORD_Value(tokens[i]); + tokens_dw.push_back(tokens_int); + } + + shift_states.push_back(tokens_dw); + tokens_dw.clear(); + tokens.clear(); + } + } + all_US.push_back(shift_states); + + if (all_US.size() == 0) { + printf("ERROR: Can't split US to 3D-Vector\n"); + return 1; + } + 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 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 + * @param[in,out] all_vector 3D-vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param keymap 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 append_underlying_ToVector(vec_dword_3D& all_vector, GdkKeymap* keymap) { + 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 = 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]; + + // get Keyvals of this key and copy to unshifted/shifted in "underlying"-block[1][i][1] / block[1][i][2] + all_vector[1][i][0 + 1] = KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(keymap, all_vector[0][i][0], convert_rgkey_Shiftstate_to_LinuxShiftstate(ShiftState::Base)); // shift state: unshifted:0 + all_vector[1][i][1 + 1] = KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(keymap, all_vector[0][i][0], convert_rgkey_Shiftstate_to_LinuxShiftstate(ShiftState::Shft)); // shift state: shifted:1 + } + + return 0; +} + +/** + * @brief initializes GDK and return the current keymap for later use + * @param keymap [out] currently used (underlying) keyboard layout + * @return FALSE on success; + * TRUE if the display or keymap is not found +*/ +bool InitializeGDK(GdkKeymap** keymap, int argc, gchar* argv[]) { + // get keymap of underlying keyboard + + gdk_init(&argc, &argv); + GdkDisplay* display = gdk_display_get_default(); + if (!display) { + printf("ERROR: can't get display\n"); + return TRUE; + } + + *keymap = gdk_keymap_get_for_display(display); + if (!keymap) { + printf("ERROR: Can't get keymap\n"); + gdk_display_close(display); + return TRUE; + } + // intentionally leaking `display` in order to still be able to access `keymap` + return FALSE; +} + +/** + * @brief check if keyval correponds to a character we use in mcompile + * @param kv the keyval to be checked + * @return true if keyval is used in mcompile; + * false if not +*/ +bool IsKeymanUsedChar(int kv) { + // 32 A-Z a-z + if ((kv == 0x20) || (kv >= 65 && kv <= 90) || (kv >= 97 && kv <= 122)) + return true; + else + return false; +} + +/** + * @brief convert a deadkey-value to a u16string if it is in the range of deadkeys used for mcompile. + * deadkeys used for mcompile e.g. 65106 -> '^' + * @param in value to be converted + * @return on success a u16string holding the converted value; + * else u"\0" +*/ +std::u16string convert_DeadkeyValues_To_U16str(KMX_DWORD in) { + if (in == 0) + return u"\0"; + + if ((int)in < (int)deadkey_min) { // no deadkey; no Unicode + return std::u16string(1, in); + } + + std::string long_name((const char*)gdk_keyval_name(in)); // e.g. "dead_circumflex", "U+017F", "t" + + if (long_name.substr(0, 2) == "U+") // U+... Unicode value + return CodePointToU16String(in - 0x1000000); // GDK's gdk_keymap_translate_keyboard_state() returns (Keyvalue | 0x01000000) + // since we never have a carry-over we can just subtract 0x01000000 + + KMX_DWORD lname = convertNamesTo_DWORD_Value(long_name); // 65106 => "dead_circumflex" => 94 => "^" + + if (lname != INVALID_NAME) { + return std::u16string(1, lname); + } else + return u"\0"; +} + +/** + * @brief return the keyvalue for a given Keycode, shiftstate and caps + * currently used (underlying) keyboard layout + * "What character will be produced for a keypress of a key and modifier?" + * @param keymap pointer to the currently used (underlying) keyboard layout + * @param keycode a key of the currently used keyboard layout + * @param ss a (windows-)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 KMX_get_KeyVal_From_KeyCode(GdkKeymap* keymap, guint keycode, ShiftState ss, int caps) { + GdkModifierType consumed; + GdkKeymapKey* maps; + guint* keyvals; + gint count; + if (!gdk_keymap_get_entries_for_keycode(keymap, keycode, &maps, &keyvals, &count)) + return 0; + + if (!(ensureValidInputForKeyboardTranslation(convert_rgkey_Shiftstate_to_LinuxShiftstate(ss), keycode))) { + g_free(keyvals); + g_free(maps); + return 0; + } + + // BASE (shiftstate: 0) + if ((ss == Base) && (caps == 0)) { + GdkModifierType MOD_base = (GdkModifierType)(~GDK_MODIFIER_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_base, 0, keyvals, NULL, NULL, &consumed); + } + + // BASE + CAPS (shiftstate: 0) + else if ((ss == Base) && (caps == 1)) { + GdkModifierType MOD_Caps = (GdkModifierType)(GDK_LOCK_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_Caps, 0, keyvals, NULL, NULL, &consumed); + } + + // SHIFT (shiftstate: 1) + else if ((ss == Shft) && (caps == 0)) { + GdkModifierType MOD_Shift = (GdkModifierType)(GDK_SHIFT_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_Shift, 0, keyvals, NULL, NULL, &consumed); + } + + // SHIFT + CAPS (shiftstate: 1) + else if ((ss == Shft) && (caps == 1)) { + GdkModifierType MOD_ShiftCaps = (GdkModifierType)((GDK_SHIFT_MASK | GDK_LOCK_MASK)); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_ShiftCaps, 0, keyvals, NULL, NULL, &consumed); + } + + // Ctrl (shiftstate: 2) + else if ((ss == Ctrl) && (caps == 0)) { + GdkModifierType MOD_Ctrl = (GdkModifierType)(GDK_MOD5_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_Ctrl, 0, keyvals, NULL, NULL, &consumed); + } + + // Ctrl + CAPS (shiftstate: 2) + else if ((ss == Ctrl) && (caps == 1)) { + GdkModifierType MOD_CtrlCaps = (GdkModifierType)(GDK_MOD5_MASK | GDK_LOCK_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_CtrlCaps, 0, keyvals, NULL, NULL, &consumed); + } + + // SHIFT+Ctrl (shiftstate: 3) + else if ((ss == ShftCtrl) && (caps == 0)) { + GdkModifierType MOD_Ctrl = (GdkModifierType)(GDK_SHIFT_MASK | GDK_MOD5_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_Ctrl, 0, keyvals, NULL, NULL, &consumed); + } + + // SHIFT+Ctrl + CAPS (shiftstate: 3) + else if ((ss == ShftCtrl) && (caps == 1)) { + GdkModifierType MOD_CtrlCaps = (GdkModifierType)(GDK_SHIFT_MASK | GDK_MOD5_MASK | GDK_LOCK_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_CtrlCaps, 0, keyvals, NULL, NULL, &consumed); + } + + // ALT-GR (shiftstate: 6) + else if ((ss == MenuCtrl) && (caps == 0)) { + GdkModifierType MOD_AltGr = (GdkModifierType)(GDK_MOD2_MASK | GDK_MOD5_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_AltGr, 0, keyvals, NULL, NULL, &consumed); + } + + // ALT-GR + CAPS (shiftstate: 6) + else if ((ss == MenuCtrl) && (caps == 1)) { + GdkModifierType MOD_AltGr = (GdkModifierType)(GDK_MOD2_MASK | GDK_MOD5_MASK | GDK_LOCK_MASK); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_AltGr, 0, keyvals, NULL, NULL, &consumed); + } + + // ALT-GR (shiftstate: 7) + else if ((ss == ShftMenuCtrl) && (caps == 0)) { + GdkModifierType MOD_AltGr = (GdkModifierType)((GDK_SHIFT_MASK | GDK_MOD2_MASK | GDK_MOD5_MASK)); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_AltGr, 0, keyvals, NULL, NULL, &consumed); + } + + // ALT-GR +CAPS (shiftstate: 7) + else if ((ss == ShftMenuCtrl) && (caps == 1)) { + GdkModifierType MOD_AltGr = (GdkModifierType)((GDK_SHIFT_MASK | GDK_MOD2_MASK | GDK_MOD5_MASK | GDK_LOCK_MASK)); + gdk_keymap_translate_keyboard_state(keymap, keycode, MOD_AltGr, 0, keyvals, NULL, NULL, &consumed); + } else + return 0; + + return (int)*keyvals; +} + +/** + * @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?" + * @param keymap a pointer to the currently used (underlying) keyboard layout + * @param keycode a key of the currently used keyboard + * @param shiftState a shiftstate of the currently used keyboard layout + * @return the keyval obtained from Keycode and shiftstate; + */ +KMX_DWORD KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(GdkKeymap* keymap, guint keycode, int shiftState) { + GdkKeymapKey* maps; + guint* keyvals; + gint count; + KMX_DWORD kVal; + + if (!gdk_keymap_get_entries_for_keycode(keymap, keycode, &maps, &keyvals, &count)) + return 0; + + + if (!(ensureValidInputForKeyboardTranslation(shiftState, keycode))) { + g_free(keyvals); + g_free(maps); + return 0; + } + + kVal = KMX_get_KeyVal_From_KeyCode(keymap, keycode, (ShiftState)shiftState, 0); + + g_free(keyvals); + g_free(maps); + + return kVal; +} + +/** + * @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 + * This function is similar to KMX_DWORD KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(GdkKeymap* keymap, guint keycode, int shiftState) + * but processes deadkeys + * @param keymap a pointer to the currently used (underlying) keyboard layout + * @param keycode a key of the currently used keyboard + * @param 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 + * 0xFFFE in case a deadkey is out of range + * the keyval obtained from Keycode and shiftstate and caps; +*/ +KMX_DWORD KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(GdkKeymap* keymap, guint keycode, KMX_DWORD shiftState, PKMX_WCHAR deadkey) { + GdkKeymapKey* maps; + guint* keyvals; + gint count; + PKMX_WCHAR dky = NULL; + + if (!gdk_keymap_get_entries_for_keycode(keymap, keycode, &maps, &keyvals, &count)) + return 0; + + if (!(ensureValidInputForKeyboardTranslation(convert_Shiftstate_to_LinuxShiftstate(shiftState), keycode))) { + g_free(keyvals); + g_free(maps); + return 0; + } + + KMX_DWORD keyV = KMX_get_KeyVal_From_KeyCode(keymap, keycode, ShiftState(convert_Shiftstate_to_LinuxShiftstate(shiftState)), 0); + + g_free(keyvals); + g_free(maps); + + if ((keyV >= deadkey_min) && (keyV <= deadkey_max)) { // deadkey + dky = (PKMX_WCHAR)(convert_DeadkeyValues_To_U16str(keyV)).c_str(); + *deadkey = *dky; + return 0xFFFF; + } else if ((keyV > deadkey_max) || ((keyV < deadkey_min) && (keyV > 0xFF))) // out of range + return 0xFFFE; + else // usable char + 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 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 keycode of the US keyboard + * "Where on an underlying keyboard do we find a character that is on a certain key on a US keyboard?" + * @param keymap 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 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 KMX_get_KeyCodeUnderlying_From_KeyCodeUS(GdkKeymap* keymap, vec_dword_3D& all_vector, KMX_DWORD kc_us, ShiftState ss, int caps) { + std::u16string u16str = convert_DeadkeyValues_To_U16str(KMX_get_KeyVal_From_KeyCode(keymap, kc_us, ss, caps)); + + 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] == (KMX_DWORD)*u16str.c_str())) + 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 +*/ +KMX_DWORD KMX_get_KeyCodeUnderlying_From_VKUS(KMX_DWORD virtualKeyUS) { + // Linux virtualKeys are always 8 different to Windows virtualKeys + return (KMX_DWORD)(8 + USVirtualKeyToScanCode[virtualKeyUS]); +} + +/** + * @brief return a virtual key of the US keyboard for a given keycode of the currently used (underlying) keyboard + * "Which key of a underlying keyboard will be mapped to a virtual key of a US keyboard?" + * @param keycode a keycode of the currently used (underlying) keyboard + * @return the virtual key of the US keyboard or + * 0 if the key is not used +*/ +KMX_DWORD KMX_get_VKUS_From_KeyCodeUnderlying(KMX_DWORD keycode) { + // Linux virtualKeys are always 8 different to Windows virtualKeys + if (keycode > 7) + return (KMX_DWORD)ScanCodeToUSVirtualKey[keycode - 8]; + + return 0; +} + +/** + * @brief convert a codepoint to a u16string + * @param codepoint to be converted + * @return a u16string holding the converted value; +*/ +std::u16string CodePointToU16String(unsigned int codepoint) { + std::u16string str; + + if (codepoint <= 0xFFFF) { + str = static_cast(codepoint); + } else { + assert(codepoint < 0x10FFFF); + assert(isLittleEndianSystem()); + + codepoint -= 0x10000; + str.resize(2); + str[0] = static_cast(0xDC00 + (codepoint & 0x3FF)); + str[1] = static_cast(0xD800 + ((codepoint >> 10) & 0x3FF)); + } + return str; +} diff --git a/linux/mcompile/keymap/keymap.h b/linux/mcompile/keymap/keymap.h new file mode 100644 index 0000000000..11c437378d --- /dev/null +++ b/linux/mcompile/keymap/keymap.h @@ -0,0 +1,574 @@ +#pragma once +#ifndef KEYMAP_H +#define KEYMAP_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "km_u16.h" +#include + +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 + + +// 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}; + +typedef std::vector vec_string_1D; +typedef std::vector vec_dword_1D; +typedef std::vector > vec_dword_2D; +typedef std::vector > > vec_dword_3D; + +static KMX_DWORD INVALID_NAME = 0; +static gint keycode_max = 94; +static KMX_DWORD deadkey_min = 0xfe50; // X11's keysymdef.h defines deadkeys between 0xfe50-0xfe93 +static KMX_DWORD deadkey_max = 0xfe93; // https://fossies.org/linux/tk/xlib/X11/keysymdef.h + +/** @brief check if current machine uses little endian + * @return true if little endian is used; + * else false */ +inline bool isLittleEndianSystem() { + char16_t test = 0x0102; + return (reinterpret_cast(&test))[0] == 0x02; +} + +/** @brief map a shiftstate used on windows to a shiftstate suitable for gdk_keymap_translate_keyboard_state() on Linux */ +int convert_Shiftstate_to_LinuxShiftstate(int shiftState); + +/** @brief map a shiftstate used for rgkey to a shiftstate suitable for gdk_keymap_translate_keyboard_state() on Linux */ +int convert_rgkey_Shiftstate_to_LinuxShiftstate(ShiftState shiftState); + +/** @brief check for correct input parameter that will later be used in gdk_keymap_translate_keyboard_state() */ +bool ensureValidInputForKeyboardTranslation(int shiftstate, gint keycode); + +/** @brief convert names of keys stated in a symbol file to a keyvalue */ +KMX_DWORD convertNamesTo_DWORD_Value(std::string tok_str); + +/** @brief create a 3D-Vector containing data of the US keyboard and the currently used (underlying) keyboard */ +int createOneVectorFromBothKeyboards(vec_dword_3D& all_vector, GdkKeymap* keymap); + +/** @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 */ +int write_US_ToVector(vec_dword_3D& vec_us); + +/** @brief create a 1D-Vector containing all relevant entries of the symbol file us basic */ +bool createCompleteVector_US(vec_string_1D& complete_List); + +/** @brief convert the key name obtained from symbol file to the matching keycode */ +int get_keycode_from_keyname(std::string key_name); + +/** @brief process each element of a 1D-Vector, split and write to a 3D-Vector */ +int split_US_To_3D_Vector(vec_dword_3D& all_US, vec_string_1D completeList); + +/** @brief create an 2D-Vector with all fields containing INVALID_NAME */ +vec_dword_2D 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 append_underlying_ToVector(vec_dword_3D& all_vector, GdkKeymap* keymap); + +/** @brief create a pointer to pointer of the current keymap for later use */ +bool InitializeGDK(GdkKeymap** keymap, int argc, gchar* argv[]); + +const KMX_DWORD USVirtualKeyToScanCode[256] = { + 0x00, // L"K_?00", // &H0 + 0x00, // L"K_LBUTTON", // &H1 + 0x00, // L"K_RBUTTON", // &H2 + 0x46, // L"K_CANCEL", // &H3 + 0x00, // L"K_MBUTTON", // &H4 + 0x00, // L"K_?05", // &H5 + 0x00, // L"K_?06", // &H6 + 0x00, // L"K_?07", // &H7 + 0x0E, // L"K_BKSP", // &H8 + 0x0F, // L"K_TAB", // &H9 + 0x00, // L"K_?0A", // &HA + 0x00, // L"K_?0B", // &HB + 0x4C, // L"K_KP5", // &HC + 0x1C, // L"K_ENTER", // &HD + 0x00, // L"K_?0E", // &HE + 0x00, // L"K_?0F", // &HF + 0x2A, // L"K_SHIFT", // &H10 + 0x1D, // L"K_CONTROL", // &H11 + 0x38, // L"K_ALT", // &H12 + 0x00, // L"K_PAUSE", // &H13 + 0x3A, // L"K_CAPS", // &H14 + 0x00, // L"K_KANJI?15", // &H15 + 0x00, // L"K_KANJI?16", // &H16 + 0x00, // L"K_KANJI?17", // &H17 + 0x00, // L"K_KANJI?18", // &H18 + 0x00, // L"K_KANJI?19", // &H19 + 0x00, // L"K_?1A", // &H1A + 0x01, // L"K_ESC", // &H1B + 0x00, // L"K_KANJI?1C", // &H1C + 0x00, // L"K_KANJI?1D", // &H1D + 0x00, // L"K_KANJI?1E", // &H1E + 0x00, // L"K_KANJI?1F", // &H1F + 0x39, // L"K_SPACE", // &H20 + 0x49, // L"K_PGUP", // &H21 + 0x51, // L"K_PGDN", // &H22 + 0x4F, // L"K_END", // &H23 + 0x47, // L"K_HOME", // &H24 + 0x4B, // L"K_LEFT", // &H25 + 0x48, // L"K_UP", // &H26 + 0x4D, // L"K_RIGHT", // &H27 + 0x50, // L"K_DOWN", // &H28 + 0x00, // L"K_SEL", // &H29 + 0x00, // L"K_PRINT", // &H2A + 0x00, // L"K_EXEC", // &H2B + 0x54, // L"K_PRTSCN", // &H2C + 0x52, // L"K_INS", // &H2D + 0x53, // L"K_DEL", // &H2E + 0x63, // L"K_HELP", // &H2F + 0x0B, // L"K_0", // &H30 + 0x02, // L"K_1", // &H31 + 0x03, // L"K_2", // &H32 + 0x04, // L"K_3", // &H33 + 0x05, // L"K_4", // &H34 + 0x06, // L"K_5", // &H35 + 0x07, // L"K_6", // &H36 + 0x08, // L"K_7", // &H37 + 0x09, // L"K_8", // &H38 + 0x0A, // L"K_9", // &H39 + 0x00, // L"K_?3A", // &H3A + 0x00, // L"K_?3B", // &H3B + 0x00, // L"K_?3C", // &H3C + 0x00, // L"K_?3D", // &H3D + 0x00, // L"K_?3E", // &H3E + 0x00, // L"K_?3F", // &H3F + 0x00, // L"K_?40", // &H40 + 0x1E, // L"K_A", // &H41 + 0x30, // L"K_B", // &H42 + 0x2E, // L"K_C", // &H43 + 0x20, // L"K_D", // &H44 + 0x12, // L"K_E", // &H45 + 0x21, // L"K_F", // &H46 + 0x22, // L"K_G", // &H47 + 0x23, // L"K_H", // &H48 + 0x17, // L"K_I", // &H49 + 0x24, // L"K_J", // &H4A + 0x25, // L"K_K", // &H4B + 0x26, // L"K_L", // &H4C + 0x32, // L"K_M", // &H4D + 0x31, // L"K_N", // &H4E + 0x18, // L"K_O", // &H4F + 0x19, // L"K_P", // &H50 + 0x10, // L"K_Q", // &H51 + 0x13, // L"K_R", // &H52 + 0x1F, // L"K_S", // &H53 + 0x14, // L"K_T", // &H54 + 0x16, // L"K_U", // &H55 + 0x2F, // L"K_V", // &H56 + 0x11, // L"K_W", // &H57 + 0x2D, // L"K_X", // &H58 + 0x15, // L"K_Y", // &H59 + 0x2C, // L"K_Z", // &H5A + 0x5B, // L"K_?5B", // &H5B + 0x5C, // L"K_?5C", // &H5C + 0x5D, // L"K_?5D", // &H5D + 0x00, // L"K_?5E", // &H5E + 0x5F, // L"K_?5F", // &H5F + 0x52, // L"K_NP0", // &H60 + 0x4F, // L"K_NP1", // &H61 + 0x50, // L"K_NP2", // &H62 + 0x51, // L"K_NP3", // &H63 + 0x4B, // L"K_NP4", // &H64 + 0x4C, // L"K_NP5", // &H65 + 0x4D, // L"K_NP6", // &H66 + 0x47, // L"K_NP7", // &H67 + 0x48, // L"K_NP8", // &H68 + 0x49, // L"K_NP9", // &H69 + 0x37, // L"K_NPSTAR", // &H6A + 0x4E, // L"K_NPPLUS", // &H6B + 0x7E, // L"K_SEPARATOR", // &H6C // MCD 01-11-02: Brazilian Fix, 00 -> 7E + 0x4A, // L"K_NPMINUS", // &H6D + 0x53, // L"K_NPDOT", // &H6E + 0x135, // L"K_NPSLASH", // &H6F + 0x3B, // L"K_F1", // &H70 + 0x3C, // L"K_F2", // &H71 + 0x3D, // L"K_F3", // &H72 + 0x3E, // L"K_F4", // &H73 + 0x3F, // L"K_F5", // &H74 + 0x40, // L"K_F6", // &H75 + 0x41, // L"K_F7", // &H76 + 0x42, // L"K_F8", // &H77 + 0x43, // L"K_F9", // &H78 + 0x44, // L"K_F10", // &H79 + 0x57, // L"K_F11", // &H7A + 0x58, // L"K_F12", // &H7B + 0x64, // L"K_F13", // &H7C + 0x65, // L"K_F14", // &H7D + 0x66, // L"K_F15", // &H7E + 0x67, // L"K_F16", // &H7F + 0x68, // L"K_F17", // &H80 + 0x69, // L"K_F18", // &H81 + 0x6A, // L"K_F19", // &H82 + 0x6B, // L"K_F20", // &H83 + 0x6C, // L"K_F21", // &H84 + 0x6D, // L"K_F22", // &H85 + 0x6E, // L"K_F23", // &H86 + 0x76, // L"K_F24", // &H87 + + 0x00, // L"K_?88", // &H88 + 0x00, // L"K_?89", // &H89 + 0x00, // L"K_?8A", // &H8A + 0x00, // L"K_?8B", // &H8B + 0x00, // L"K_?8C", // &H8C + 0x00, // L"K_?8D", // &H8D + 0x00, // L"K_?8E", // &H8E + 0x00, // L"K_?8F", // &H8F + + 0x45, // L"K_NUMLOCK", // &H90 + 0x46, // L"K_SCROL", // &H91 + + 0x00, // L"K_?92", // &H92 + 0x00, // L"K_?93", // &H93 + 0x00, // L"K_?94", // &H94 + 0x00, // L"K_?95", // &H95 + 0x00, // L"K_?96", // &H96 + 0x00, // L"K_?97", // &H97 + 0x00, // L"K_?98", // &H98 + 0x00, // L"K_?99", // &H99 + 0x00, // L"K_?9A", // &H9A + 0x00, // L"K_?9B", // &H9B + 0x00, // L"K_?9C", // &H9C + 0x00, // L"K_?9D", // &H9D + 0x00, // L"K_?9E", // &H9E + 0x00, // L"K_?9F", // &H9F + 0x2A, // L"K_?A0", // &HA0 + 0x36, // L"K_?A1", // &HA1 + 0x1D, // L"K_?A2", // &HA2 + 0x1D, // L"K_?A3", // &HA3 + 0x38, // L"K_?A4", // &HA4 + 0x38, // L"K_?A5", // &HA5 + 0x6A, // L"K_?A6", // &HA6 + 0x69, // L"K_?A7", // &HA7 + 0x67, // L"K_?A8", // &HA8 + 0x68, // L"K_?A9", // &HA9 + 0x65, // L"K_?AA", // &HAA + 0x66, // L"K_?AB", // &HAB + 0x32, // L"K_?AC", // &HAC + 0x20, // L"K_?AD", // &HAD + 0x2E, // L"K_?AE", // &HAE + 0x30, // L"K_?AF", // &HAF + 0x19, // L"K_?B0", // &HB0 + 0x10, // L"K_?B1", // &HB1 + 0x24, // L"K_?B2", // &HB2 + 0x22, // L"K_?B3", // &HB3 + 0x6C, // L"K_?B4", // &HB4 + 0x6D, // L"K_?B5", // &HB5 + 0x6B, // L"K_?B6", // &HB6 + 0x21, // L"K_?B7", // &HB7 + 0x00, // L"K_?B8", // &HB8 + 0x00, // L"K_?B9", // &HB9 + 0x27, // L"K_COLON", // &HBA + 0x0D, // L"K_EQUAL", // &HBB + 0x33, // L"K_COMMA", // &HBC + 0x0C, // L"K_HYPHEN", // &HBD + 0x34, // L"K_PERIOD", // &HBE + 0x35, // L"K_SLASH", // &HBF + 0x29, // L"K_BKQUOTE", // &HC0 + + 0x73, // L"K_?C1", // &HC1 + 0x7E, // L"K_?C2", // &HC2 + 0x00, // L"K_?C3", // &HC3 + 0x00, // L"K_?C4", // &HC4 + 0x00, // L"K_?C5", // &HC5 + 0x00, // L"K_?C6", // &HC6 + 0x00, // L"K_?C7", // &HC7 + 0x00, // L"K_?C8", // &HC8 + 0x00, // L"K_?C9", // &HC9 + 0x00, // L"K_?CA", // &HCA + 0x00, // L"K_?CB", // &HCB + 0x00, // L"K_?CC", // &HCC + 0x00, // L"K_?CD", // &HCD + 0x00, // L"K_?CE", // &HCE + 0x00, // L"K_?CF", // &HCF + 0x00, // L"K_?D0", // &HD0 + 0x00, // L"K_?D1", // &HD1 + 0x00, // L"K_?D2", // &HD2 + 0x00, // L"K_?D3", // &HD3 + 0x00, // L"K_?D4", // &HD4 + 0x00, // L"K_?D5", // &HD5 + 0x00, // L"K_?D6", // &HD6 + 0x00, // L"K_?D7", // &HD7 + 0x00, // L"K_?D8", // &HD8 + 0x00, // L"K_?D9", // &HD9 + 0x00, // L"K_?DA", // &HDA + 0x1A, // L"K_LBRKT", // &HDB + 0x2B, // L"K_BKSLASH", // &HDC + 0x1B, // L"K_RBRKT", // &HDD + 0x28, // L"K_QUOTE", // &HDE + 0x73, // L"K_oDF", // &HDF // MCD 01-11-02: Brazilian fix: 00 -> 73 + 0x00, // L"K_oE0", // &HE0 + 0x00, // L"K_oE1", // &HE1 + 0x56, // L"K_oE2", // &HE2 + 0x00, // L"K_oE3", // &HE3 + 0x00, // L"K_oE4", // &HE4 + + 0x00, // L"K_?E5", // &HE5 + + 0x00, // L"K_oE6", // &HE6 + + 0x00, // L"K_?E7", // &HE7 + 0x00, // L"K_?E8", // &HE8 + + 0x71, // L"K_oE9", // &HE9 + 0x5C, // L"K_oEA", // &HEA + 0x7B, // L"K_oEB", // &HEB + 0x00, // L"K_oEC", // &HEC + 0x6F, // L"K_oED", // &HED + 0x5A, // L"K_oEE", // &HEE + 0x00, // L"K_oEF", // &HEF + 0x00, // L"K_oF0", // &HF0 + 0x5B, // L"K_oF1", // &HF1 + 0x00, // L"K_oF2", // &HF2 + 0x5F, // L"K_oF3", // &HF3 + 0x00, // L"K_oF4", // &HF4 + 0x5E, // L"K_oF5", // &HF5 + + 0x00, // L"K_?F6", // &HF6 + 0x00, // L"K_?F7", // &HF7 + 0x00, // L"K_?F8", // &HF8 + 0x5D, // L"K_?F9", // &HF9 + 0x00, // L"K_?FA", // &HFA + 0x62, // L"K_?FB", // &HFB + 0x00, // L"K_?FC", // &HFC + 0x00, // L"K_?FD", // &HFD + 0x00, // L"K_?FE", // &HFE + 0x00 // L"K_?FF" // &HFF +}; + +const KMX_DWORD ScanCodeToUSVirtualKey[128] = { + 0x01, // 0x00 => K_LBUTTON + 0x1b, // 0x01 => K_ESC + 0x31, // 0x02 => K_1 + 0x32, // 0x03 => K_2 + 0x33, // 0x04 => K_3 + 0x34, // 0x05 => K_4 + 0x35, // 0x06 => K_5 + 0x36, // 0x07 => K_6 + 0x37, // 0x08 => K_7 + 0x38, // 0x09 => K_8 + 0x39, // 0x0a => K_9 + 0x30, // 0x0b => K_0 + 0xbd, // 0x0c => K_HYPHEN + 0xbb, // 0x0d => K_EQUAL + 0x08, // 0x0e => K_BKSP + 0x09, // 0x0f => K_TAB + 0x51, // 0x10 => K_Q + 0x57, // 0x11 => K_W + 0x45, // 0x12 => K_E + 0x52, // 0x13 => K_R + 0x54, // 0x14 => K_T + 0x59, // 0x15 => K_Y + 0x55, // 0x16 => K_U + 0x49, // 0x17 => K_I + 0x4f, // 0x18 => K_O + 0x50, // 0x19 => K_P + 0xdb, // 0x1a => K_LBRKT + 0xdd, // 0x1b => K_RBRKT + 0x0d, // 0x1c => K_ENTER + 0x11, // 0x1d => K_CONTROL + 0x41, // 0x1e => K_A + 0x53, // 0x1f => K_S + 0x44, // 0x20 => K_D + 0x46, // 0x21 => K_F + 0x47, // 0x22 => K_G + 0x48, // 0x23 => K_H + 0x4a, // 0x24 => K_J + 0x4b, // 0x25 => K_K + 0x4c, // 0x26 => K_L + 0xba, // 0x27 => K_COLON + 0xde, // 0x28 => K_QUOTE + 0xc0, // 0x29 => K_BKQUOTE + 0x10, // 0x2a => K_SHIFT + 0xdc, // 0x2b => K_BKSLASH + 0x5a, // 0x2c => K_Z + 0x58, // 0x2d => K_X + 0x43, // 0x2e => K_C + 0x56, // 0x2f => K_V + 0x42, // 0x30 => K_B + 0x4e, // 0x31 => K_N + 0x4d, // 0x32 => K_M + 0xbc, // 0x33 => K_COMMA + 0xbe, // 0x34 => K_PERIOD + 0xbf, // 0x35 => K_SLASH + 0xa1, // 0x36 => K_?A1 + 0x6a, // 0x37 => K_NPSTAR + 0x12, // 0x38 => K_ALT + 0x20, // 0x39 => K_SPACE + 0x14, // 0x3a => K_CAPS + 0x70, // 0x3b => K_F1 + 0x71, // 0x3c => K_F2 + 0x72, // 0x3d => K_F3 + 0x73, // 0x3e => K_F4 + 0x74, // 0x3f => K_F5 + 0x75, // 0x40 => K_F6 + 0x76, // 0x41 => K_F7 + 0x77, // 0x42 => K_F8 + 0x78, // 0x43 => K_F9 + 0x79, // 0x44 => K_F10 + 0x90, // 0x45 => K_NUMLOCK + 0x03, // 0x46 => K_CANCEL + 0x24, // 0x47 => K_HOME + 0x26, // 0x48 => K_UP + 0x21, // 0x49 => K_PGUP + 0x6d, // 0x4a => K_NPMINUS + 0x25, // 0x4b => K_LEFT + 0x0c, // 0x4c => K_KP5 + 0x27, // 0x4d => K_RIGHT + 0x6b, // 0x4e => K_NPPLUS + 0x23, // 0x4f => K_END + 0x28, // 0x50 => K_DOWN + 0x22, // 0x51 => K_PGDN + 0x2d, // 0x52 => K_INS + 0x2e, // 0x53 => K_DEL + 0x2c, // 0x54 => K_PRTSCN + 0x00, // 0x55 => No match + 0xe2, // 0x56 => K_oE2 + 0x7a, // 0x57 => K_F11 + 0x7b, // 0x58 => K_F12 + 0x00, // 0x59 => No match + 0xee, // 0x5a => K_oEE + 0x5b, // 0x5b => K_?5B + 0x5c, // 0x5c => K_?5C + 0x5d, // 0x5d => K_?5D + 0xf5, // 0x5e => K_oF5 + 0x5f, // 0x5f => K_?5F + 0x00, // 0x60 => No match + 0x00, // 0x61 => No match + 0xfb, // 0x62 => K_?FB + 0x2f, // 0x63 => K_HELP + 0x7c, // 0x64 => K_F13 + 0x7d, // 0x65 => K_F14 + 0x7e, // 0x66 => K_F15 + 0x7f, // 0x67 => K_F16 + 0x80, // 0x68 => K_F17 + 0x81, // 0x69 => K_F18 + 0x82, // 0x6a => K_F19 + 0x83, // 0x6b => K_F20 + 0x84, // 0x6c => K_F21 + 0x85, // 0x6d => K_F22 + 0x86, // 0x6e => K_F23 + 0xed, // 0x6f => K_oED + 0x00, // 0x70 => No match + 0xe9, // 0x71 => K_oE9 + 0x00, // 0x72 => No match + 0xc1, // 0x73 => K_?C1 + 0x00, // 0x74 => No match + 0x00, // 0x75 => No match + 0x87, // 0x76 => K_F24 + 0x00, // 0x77 => No match + 0x00, // 0x78 => No match + 0x00, // 0x79 => No match + 0x00, // 0x7a => No match + 0xeb, // 0x7b => K_oEB + 0x00, // 0x7c => No match + 0x00, // 0x7d => No match + 0x6c, // 0x7e => K_SEPARATOR + 0x00 // 0x7f => No match +}; + +/** @brief check if keyval correponds to a character used in mcompile */ +bool IsKeymanUsedChar(int kv); + +/** @brief convert a deadkey-value to a u16string if it is in the range of deadkeys used for mcompile */ +std::u16string convert_DeadkeyValues_To_U16str(KMX_DWORD in); + +/** @brief return the keyvalue for a given Keycode, shiftstate and caps of the currently used (underlying) keyboard layout. */ +KMX_DWORD KMX_get_KeyVal_From_KeyCode(GdkKeymap* keymap, guint keycode, ShiftState ss, int caps); + +/** @brief return the keyvalue for a given Keycode and shiftstate of the currently used (underlying) keyboard layout. */ +KMX_DWORD KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(GdkKeymap* keymap, guint keycode, int shiftState); + +/** @brief return the keyvalue for a given Keycode and shiftstate of the currently used (underlying) keyboard layout. */ +KMX_DWORD KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(GdkKeymap* keymap, guint keycode, KMX_DWORD 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 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 keycode of the US keyboard */ +KMX_DWORD KMX_get_KeyCodeUnderlying_From_KeyCodeUS(GdkKeymap* keymap, vec_dword_3D& all_vector, KMX_DWORD kc_us, ShiftState ss, int caps); + +/** @brief return the keycode of the currently used (underlying) keyboard for a given virtual key of the US keyboard */ +KMX_DWORD 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 KMX_get_VKUS_From_KeyCodeUnderlying(KMX_DWORD keycode); + +/** @brief convert a codepoint to a u16string */ +std::u16string CodePointToU16String(unsigned int codepoint); + +#endif /*KEYMAP_H*/ diff --git a/linux/mcompile/keymap/mc_import_rules.cpp b/linux/mcompile/keymap/mc_import_rules.cpp new file mode 100644 index 0000000000..9c376dafd6 --- /dev/null +++ b/linux/mcompile/keymap/mc_import_rules.cpp @@ -0,0 +1,669 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for Linux + */ + + +#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 Deadkey with values + * @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 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 should be 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 a shiftstate of the currently used keyboard Layout + * @param caps state of the caps key of the currently used keyboard Layout + * @param keymap 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 KMX_ToUnicodeEx(guint keycode, PKMX_WCHAR pwszBuff, ShiftState rgkey_ss, int caps, GdkKeymap* keymap) { + + GdkKeymapKey* maps; + guint* keyvals; + gint count; + + if (!gdk_keymap_get_entries_for_keycode(keymap, keycode, &maps, &keyvals, &count)) + return 0; + + if (!(ensureValidInputForKeyboardTranslation(convert_rgkey_Shiftstate_to_LinuxShiftstate(rgkey_ss), keycode))){ + g_free(keyvals); + g_free(maps); + return 0; + } + + KMX_DWORD keyVal = (KMX_DWORD)KMX_get_KeyVal_From_KeyCode(keymap, keycode, rgkey_ss, caps); + std::u16string str = convert_DeadkeyValues_To_U16str(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; + + g_free(keyvals); + g_free(maps); + + if (u16len(pwszBuff) < 1) + return 0; + + if ((keyVal >= deadkey_min) && (keyVal <= deadkey_max)) // deadkeys + return -1; + else if (gdk_keyval_to_unicode(keyVal) == 0) // NO UNICODE + return 0; + else // usable char + return 1; +} + +KMX_WCHAR 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 KMX_VirtualKey { +private: + KMX_DWORD m_vk; + KMX_DWORD m_sc; + bool m_rgfDeadKey[10][2]; + std::u16string m_rgss[10][2]; + +public: + KMX_VirtualKey(KMX_DWORD scanCode) { + this->m_vk = 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 KMX_GetShiftState(ShiftState shiftState, bool capsLock) { + return this->m_rgss[(KMX_DWORD)shiftState][(capsLock ? 1 : 0)]; + } + + void 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 KMX_IsSGCAPS() { + std::u16string stBase = this->KMX_GetShiftState(Base, false); + std::u16string stShift = this->KMX_GetShiftState(Shft, false); + std::u16string stCaps = this->KMX_GetShiftState(Base, true); + std::u16string stShiftCaps = this->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 KMX_IsCapsEqualToShift() { + std::u16string stBase = this->KMX_GetShiftState(Base, false); + std::u16string stShift = this->KMX_GetShiftState(Shft, false); + std::u16string stCaps = this->KMX_GetShiftState(Base, true); + return ( + (stBase.size() > 0) && + (stShift.size() > 0) && + (stBase.compare(stShift) != 0) && + (stShift.compare(stCaps) == 0)); + } + + bool KMX_IsAltGrCapsEqualToAltGrShift() { + std::u16string stBase = this->KMX_GetShiftState(MenuCtrl, false); + std::u16string stShift = this->KMX_GetShiftState(ShftMenuCtrl, false); + std::u16string stCaps = this->KMX_GetShiftState(MenuCtrl, true); + return ( + (stBase.size() > 0) && + (stShift.size() > 0) && + (stBase.compare(stShift) != 0) && + (stShift.compare(stCaps) == 0)); + } + + bool KMX_IsXxxxGrCapsEqualToXxxxShift() { + std::u16string stBase = this->KMX_GetShiftState(Xxxx, false); + std::u16string stShift = this->KMX_GetShiftState(ShftXxxx, false); + std::u16string stCaps = this->KMX_GetShiftState(Xxxx, true); + return ( + (stBase.size() > 0) && + (stShift.size() > 0) && + (stBase.compare(stShift) != 0) && + (stShift.compare(stCaps) == 0)); + } + + bool KMX_IsEmpty() { + for (int i = 0; i < 10; i++) { + for (int j = 0; j <= 1; j++) { + if (this->KMX_GetShiftState((ShiftState)i, (j == 1)).size() > 0) { + return (false); + } + } + } + return true; + } +/** + * @brief check if we use only keys used in mcompile +*/ + bool 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 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->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; + printf("invalid for: %i\n", st[ich]); + break; + } + } + if (isvalid) { + nkeys++; + } + } + } + } + return nkeys; + } + + bool KMX_LayoutRow(int MaxShiftState, LPKMX_KEY key, std::vector* deadkeys, int deadkeyBase, bool bDeadkeyConversion, vec_dword_3D& all_vector, GdkKeymap* keymap) { // I4552 + // Get the CAPSLOCK value + /*int capslock = + (this->KMX_IsCapsEqualToShift() ? 1 : 0) | + (this->KMX_IsSGCAPS() ? 2 : 0) | + (this->KMX_IsAltGrCapsEqualToAltGrShift() ? 4 : 0) | + (this->KMX_IsXxxxGrCapsEqualToXxxxShift() ? 8 : 0);*/ + + int capslock = 1; // we do not use the equation to obtain capslock. On Linux 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->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++ = 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; + printf("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 = KMX_get_KeyCodeUnderlying_From_KeyCodeUS(keymap, all_vector, this->SC(), (ShiftState)ss, caps); + + key->Key = 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 KMX_Loader { +private: + KMX_BYTE lpKeyStateNull[256]; + KMX_DWORD m_XxxxVk; + +public: + 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); + } +}; + +/** + * @brief find the maximum index of a deadkey + @param p pointer to deadkey + * @return index of deadkey +*/ +int 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 keymap 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 KMX_ImportRules(LPKMX_KEYBOARD kp, vec_dword_3D& all_vector, GdkKeymap** keymap, std::vector* FDeadkeys, KMX_BOOL bDeadkeyConversion) { // I4353 // I4552 + KMX_Loader loader; + + std::vector rgKey; //= new VirtualKey[256]; + std::vector alDead; + std::vector alDead_byBasechar = create_deadkeys_by_basechar(); + + 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 = 0x01; 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 Linux fills rgkey.m_vk with the VK of the US keyboard + * this results in a different sorting order in rgkey[] ! + + * Linux cannot get a VK for the underling Keyboard since this does not exist + * Linux 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 + */ + KMX_VirtualKey* key = new 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 = KMX_get_KeyCodeUnderlying_From_VKUS(iKey); + + for (int caps = 0; caps <= 1; caps++) { + int rc = KMX_ToUnicodeEx(kc_underlying, sbBuffer, ss, caps, *keymap); + + if (rc > 0) { + if (*sbBuffer == 0) { + rgKey[iKey]->KMX_SetShiftState(ss, u"", false, (caps)); // different to windows since behavior on Linux is different (see above) + } else { + if ((ss == Ctrl || ss == ShftCtrl)) { + continue; + } + sbBuffer[rc] = 0; + rgKey[iKey]->KMX_SetShiftState(ss, sbBuffer, false, (caps)); // different to windows since behavior on Linux is different (see above) + } + } else if (rc < 0) { + sbBuffer[2] = 0; + rgKey[iKey]->KMX_SetShiftState(ss, sbBuffer, true, (caps)); // different to windows since behavior on Linux is different (see above) + + refine_alDead(sbBuffer[0], alDead, alDead_byBasechar); + } + } + } + } + } + + //------------------------------------------------------------- + // 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, KMX_GetMaxDeadkeyIndex(kkp->dpContext)); + nDeadkey = std::max(nDeadkey, 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]->KMX_IsKeymanUsedKey() && (!rgKey[iKey]->KMX_IsEmpty())) { + nkeys += rgKey[iKey]->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]->KMX_IsKeymanUsedKey() && (!rgKey[iKey]->KMX_IsEmpty())) { + if (rgKey[iKey]->KMX_LayoutRow(loader.KMX_MaxShiftState(), &gp->dpKeyArray[nkeys], &alDead, nDeadkey, bDeadkeyConversion, all_vector, *keymap)) { // I4552 + nkeys += rgKey[iKey]->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++ = 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/linux/mcompile/keymap/mc_import_rules.h b/linux/mcompile/keymap/mc_import_rules.h new file mode 100644 index 0000000000..f955830eac --- /dev/null +++ b/linux/mcompile/keymap/mc_import_rules.h @@ -0,0 +1,43 @@ + +#pragma once +#ifndef MC_IMPORT_RULES_H +#define MC_IMPORT_RULES_H + +/** @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 Deadkey with values */ + void KMX_AddDeadKeyRow(KMX_WCHAR baseCharacter, KMX_WCHAR combinedCharacter); + + int KMX_Count() { + return this->m_rgbasechar.size(); + } + + KMX_WCHAR KMX_GetDeadCharacter() { + return this->m_deadchar; + } + + KMX_WCHAR KMX_GetBaseCharacter(int index) { + return this->m_rgbasechar[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/linux/mcompile/keymap/mc_kmxfile.cpp b/linux/mcompile/keymap/mc_kmxfile.cpp new file mode 100644 index 0000000000..f30ff5daef --- /dev/null +++ b/linux/mcompile/keymap/mc_kmxfile.cpp @@ -0,0 +1,581 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for Linux + */ + +#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) { + 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) { + 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 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->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 */ +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) { + KMX_LogError(L"Bad Filename\n"); + return FALSE; + } + + fp = Open_File((const KMX_CHAR*)fileName, "rb"); + + if (fp == NULL) { + KMX_LogError(L"Could not open file\n"); + return FALSE; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + 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); + 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); + 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) { + 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; + KMX_LogError(L"Invalid file - signature is invalid\n"); + return FALSE; + } + + if (!KMX_VerifyKeyboard(filebase, file_size)) { + delete[] buf; + KMX_LogError(L"errVerifyKeyboard\n"); + return FALSE; + } + +#ifdef KMX_64BIT + kbp = CopyKeyboard(buf, filebase); +#else + kbp = KMX_FixupKeyboard(buf, filebase, file_size); +#endif + + if (!kbp) { + delete[] buf; + KMX_LogError(L"errFixupKeyboard\n"); + return FALSE; + } + + if (kbp->dwIdentifier != FILEID_COMPILED) { + delete[] buf; + 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) { + KMX_LogError(L"errWrongFileVersion:NULL"); + } else { + KMX_LogError(L"errWrongFileVersion:%10.10ls", (const PKMX_WCHAR)KMX_StringOffset((PKMX_BYTE)filebase, csp->dpString)); + } + return FALSE; + } + } + 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/linux/mcompile/keymap/mc_kmxfile.h b/linux/mcompile/keymap/mc_kmxfile.h new file mode 100644 index 0000000000..f89a240a77 --- /dev/null +++ b/linux/mcompile/keymap/mc_kmxfile.h @@ -0,0 +1,83 @@ +#pragma once +#ifndef MC_KMXFILE_H +#define MC_KMXFILE_H + +#include "km_types.h" +#include +#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/linux/mcompile/keymap/mcompile.cpp b/linux/mcompile/keymap/mcompile.cpp new file mode 100644 index 0000000000..8c8d8b7c17 --- /dev/null +++ b/linux/mcompile/keymap/mcompile.cpp @@ -0,0 +1,601 @@ +/* + * Keyman is copyright (C) 2004 - 2024 SIL International. MIT License. + * + * Mnemonic layout support for Linux + * + * Defines the entry point for the console application. + * + * Note: this program deliberately leaks memory as it has a very short life cycle and managing the memory allocations + * for the subcomponents of the compiled keyboard is an unnecessary optimisation. Just so you know. + */ + +#include "mcompile.h" + +/** @brief convert mnemonic keyboard layout to positional keyboard layout and translate keyboard */ +KMX_BOOL KMX_DoConvert(LPKMX_KEYBOARD kbd, KMX_BOOL bDeadkeyConversion, gint argc, gchar* argv[]); + +/** @brief Collect the key data, translate it to kmx and append to the existing keyboard */ +bool KMX_ImportRules(LPKMX_KEYBOARD kp, vec_dword_3D& all_vector, GdkKeymap** keymap, std::vector* KMX_FDeadkeys, KMX_BOOL bDeadkeyConversion); // I4353 // I4327 + +/** @brief start of mcompile; load, convert and save keyboard */ +int run(int argc, char* argv[]); + +/** @brief return an array of [usvk, ch_out] pairs: all existing combinations of a deadkey + character for the underlying keyboard */ +int KMX_GetDeadkeys(vec_dword_2D& dk_Table, KMX_WORD deadkey, KMX_WORD* outputPairs, GdkKeymap* keymap); + +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 pointer to commandline arguments: executable, inputfile, outputfile + * @return 0 on success + */ +#if defined(_WIN32) || defined(_WIN64) + int wmain(int argc, wchar_t* argv[]) { +/** + * TODO for cross platform use: if we want to use wmain instead of main: + * inside wmain convert wchar_t* argv[] to char* argv_ch[] + * to be able to use run(int argc, char* argv[]) + */ +#else // LINUX + int main(int argc, char* argv[]) { +#endif + + run(argc, argv); +} + + +/** + * @brief start of mcompile; load, convert and save keyboard + * @param argc number of commandline arguments + * @param argv pointer to commandline arguments: executable, inputfile, outputfile + * @return 0 on success, + * 1 for wrong usage of calling parameters, + * 3 if unable to load keyboard + */ +int run(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 -u ... (not available for Linux)\n " + " \tmcompile converts a Keyman mnemonic layout to\n" + " \ta positional one based on the currently used \n" + " \tLinux keyboard layout\n" + " \t(-d convert deadkeys to plain keys) \n \n"); // I4552 + + return 1; + } + + // -u option is not available for Linux and macOS + + KMX_CHAR* infile = argv[n]; + KMX_CHAR* outfile = argv[n + 1]; + + printf("mcompile%s \"%s\" \"%s\"\n", bDeadkeyConversion ? " -d" : "", infile, outfile); // I4174 + + // 1. Load the keyman keyboard file + + // 2. For each key on the system layout, determine its output character and perform a + // 1-1 replacement on the keyman keyboard of that character with the base VK + shift + // state. This fixup will transform the char to a vk, which will avoid any issues + // with the key. + // + // --> deadkeys we will attack after the POC + // + // For each deadkey, we need to determine its possible outputs. Then we generate a VK + // rule for that deadkey, e.g. [K_LBRKT] > dk(c101) + // + // Next, update each rule that references the output from that deadkey to add an extra + // context deadkey at the end of the context match, e.g. 'a' dk(c101) + [K_SPACE] > 'b'. + // This will require a memory layout change for the .kmx file, plus fixups on the + // context+output index offsets + // + // --> virtual character keys + // + // [CTRL ' '] : we look at the character, and replace it in the same way, but merely + // switch the shift state from the VIRTUALCHARKEY to VIRTUALKEY, without changing any + // other properties of the key. + // + // 3. Write the new keyman keyboard file + + LPKMX_KEYBOARD kmxfile; + + if (!KMX_LoadKeyboard(infile, &kmxfile)) { + KMX_LogError(L"Failed to load keyboard (%d)\n", errno); + return 3; + } + +#if defined(_WIN32) || defined(_WIN64) + /*if (DoConvert(kmxfile, kbid, bDeadkeyConversion)) { // I4552F + KMX_SaveKeyboard(kmxfile, outfile); + }*/ + +#else // LINUX + if (KMX_DoConvert(kmxfile, bDeadkeyConversion, argc, (gchar**)argv)) { // I4552F + KMX_SaveKeyboard(kmxfile, outfile); + } + +#endif + // DeleteReallocatedPointers(kmxfile); :TODO // _S2 not my ToDo :-) + delete kmxfile; + return 0; +} + +// Map of all shift states that we will work with +const KMX_DWORD VKShiftState[] = {0, K_SHIFTFLAG, LCTRLFLAG | RALTFLAG, K_SHIFTFLAG | LCTRLFLAG | RALTFLAG, 0xFFFF}; + +// +// TranslateKey +// +// For each key rule on the keyboard, remap its key to the +// correct shift state and key. Adjust the LCTRL+RALT -> RALT if necessary +// + +/** + * @brief translate each key of a group: remap the content of a key (key->Key) of the US keyboard to a character (ch) + * @param key pointer to a key + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param ch character of the underlying keyboard to be remapped + */ +void KMX_TranslateKey(LPKMX_KEY key, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { + // The weird LCTRL+RALT is Windows' way of mapping the AltGr key. + // We store that as just RALT, and use the option "Simulate RAlt with Ctrl+Alt" + // to provide an alternate.. + if ((shift & (LCTRLFLAG | RALTFLAG)) == (LCTRLFLAG | RALTFLAG)) + shift &= ~LCTRLFLAG; + + if (key->ShiftFlags == 0 && key->Key == ch) { + // Key is a mnemonic key with no shift state defined. + // Remap the key according to the character on the key cap. + // KMX_LogError(L"Converted mnemonic rule on line %d, + '%c' TO + [%x K_%d]", key->Line, key->Key, shift, vk); + key->ShiftFlags = ISVIRTUALKEY | shift; + key->Key = vk; + } else if (key->ShiftFlags & VIRTUALCHARKEY && key->Key == ch) { + // Key is a virtual character key with a hard-coded shift state. + // Do not remap the shift state, just move the key. + // This will not result in 100% wonderful mappings as there could + // be overlap, depending on how keys are arranged on the target layout. + // But that is up to the designer. + // KMX_LogError(L"Converted mnemonic virtual char key rule on line %d, + [%x '%c'] TO + [%x K_%d]", key->Line, key->ShiftFlags, key->Key, key->ShiftFlags & ~VIRTUALCHARKEY, vk); + key->ShiftFlags &= ~VIRTUALCHARKEY; + key->Key = vk; + } +} + +/** + * @brief translate a group of a keyboard + * @param group pointer to a keyboard group + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param ch character of the underlying keyboard to be remapped + */ +void KMX_TranslateGroup(LPKMX_GROUP group, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { + for (unsigned int i = 0; i < group->cxKeyArray; i++) { + KMX_TranslateKey(&group->dpKeyArray[i], vk, shift, ch); + } +} + +/** + * @brief translate a keyboard + * @param kbd pointer to the US keyboard + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param ch character of the underlying keyboard to be remapped + */ +void KMX_TranslateKeyboard(LPKMX_KEYBOARD kbd, KMX_WORD vk, KMX_DWORD shift, KMX_WCHAR ch) { + for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { + if (kbd->dpGroupArray[i].fUsingKeys) { + KMX_TranslateGroup(&kbd->dpGroupArray[i], vk, shift, ch); + } + } +} + +/** + * @brief check key for unconverted key rules + * @param key pointer to a key + */ +void KMX_ReportUnconvertedKeyRule(LPKMX_KEY key) { + if (key->ShiftFlags == 0) { + // KMX_LogError(L"Did not find a match for mnemonic rule on line %d, + '%c' > ...", key->Line, key->Key); + } else if (key->ShiftFlags & VIRTUALCHARKEY) { + KMX_LogError(L"Did not find a match for mnemonic virtual character key rule on line %d, + [%x '%c'] > ...", key->Line, key->ShiftFlags, key->Key); + } +} + +/** + * @brief check a group for unconverted rules + * @param group pointer to a keyboard group + */ +void KMX_ReportUnconvertedGroupRules(LPKMX_GROUP group) { + for (unsigned int i = 0; i < group->cxKeyArray; i++) { + KMX_ReportUnconvertedKeyRule(&group->dpKeyArray[i]); + } +} + +/** + * @brief check a keyboard for unconverted rules + * @param kbd pointer to the US keyboard + */ +void KMX_ReportUnconvertedKeyboardRules(LPKMX_KEYBOARD kbd) { + for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { + if (kbd->dpGroupArray[i].fUsingKeys) { + KMX_ReportUnconvertedGroupRules(&kbd->dpGroupArray[i]); + } + } +} + +/** + * @brief remap the content of a key (key->dpContext) of the US keyboard to a deadkey sequence + * @param key pointer to a key + * @param deadkey a deadkey to be remapped + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param ch character of the underlying keyboard + */ +void KMX_TranslateDeadkeyKey(LPKMX_KEY key, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift, KMX_WORD ch) { + + if ((key->ShiftFlags == 0 || key->ShiftFlags & VIRTUALCHARKEY) && key->Key == ch) { + // The weird LCTRL+RALT is Windows' way of mapping the AltGr key. + // We store that as just RALT, and use the option "Simulate RAlt with Ctrl+Alt" + // to provide an alternate.. + if ((shift & (LCTRLFLAG | RALTFLAG)) == (LCTRLFLAG | RALTFLAG)) // I4327 + shift &= ~LCTRLFLAG; + + if (key->ShiftFlags == 0) { + // KMX_LogError(L"Converted mnemonic rule on line %d, + '%c' TO dk(%d) + [%x K_%d]", key->Line, key->Key, deadkey, shift, vk); + key->ShiftFlags = ISVIRTUALKEY | shift; + } else { + // KMX_LogError(L"Converted mnemonic virtual char key rule on line %d, + [%x '%c'] TO dk(%d) + [%x K_%d]", key->Line, key->ShiftFlags, key->Key, deadkey, key->ShiftFlags & ~VIRTUALCHARKEY, vk); + key->ShiftFlags &= ~VIRTUALCHARKEY; + } + + int len = u16len(key->dpContext); + + PKMX_WCHAR context = new KMX_WCHAR[len + 4]; + memcpy(context, key->dpContext, len * sizeof(KMX_WCHAR)); + context[len] = UC_SENTINEL; + context[len + 1] = CODE_DEADKEY; + context[len + 2] = deadkey; + context[len + 3] = 0; + key->dpContext = context; + key->Key = vk; + } +} + +/** + * @brief translate a group + * @param group pointer to a keyboard group + * @param deadkey deadkey to be remapped + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param ch character of the underlying keyboard + */ +void KMX_TranslateDeadkeyGroup(LPKMX_GROUP group, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift, KMX_WORD ch) { + for (unsigned int i = 0; i < group->cxKeyArray; i++) { + KMX_TranslateDeadkeyKey(&group->dpKeyArray[i], deadkey, vk, shift, ch); + } +} + +/** + * @brief translate a keyboard + * @param kbd pointer to the US keyboard + * @param deadkey a deadkey to be remapped + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + * @param ch character of the underlying keyboard + */ +void KMX_TranslateDeadkeyKeyboard(LPKMX_KEYBOARD kbd, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift, KMX_WORD ch) { + for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { + if (kbd->dpGroupArray[i].fUsingKeys) { + KMX_TranslateDeadkeyGroup(&kbd->dpGroupArray[i], deadkey, vk, shift, ch); + } + } +} + +/** + * @brief add a deadkey rule + * @param kbd pointer to the US keyboard + * @param deadkey a deadkey to be added + * @param vk a keyvalue of the US keyboard + * @param shift shiftstate + */ +void KMX_AddDeadkeyRule(LPKMX_KEYBOARD kbd, KMX_WCHAR deadkey, KMX_WORD vk, KMX_DWORD shift) { + // The weird LCTRL+RALT is Windows' way of mapping the AltGr key. + // We store that as just RALT, and use the option "Simulate RAlt with Ctrl+Alt" + // to provide an alternate.. + if ((shift & (LCTRLFLAG | RALTFLAG)) == (LCTRLFLAG | RALTFLAG)) // I4549 + shift &= ~LCTRLFLAG; + // If the first group is not a matching-keys group, then we need to add into + // each subgroup, otherwise just the match group + for (unsigned int i = 0; i < kbd->cxGroupArray; i++) { + if (kbd->dpGroupArray[i].fUsingKeys) { + LPKMX_KEY keys = new KMX_KEY[kbd->dpGroupArray[i].cxKeyArray + 1]; + memcpy(keys + 1, kbd->dpGroupArray[i].dpKeyArray, kbd->dpGroupArray[i].cxKeyArray * sizeof(KMX_KEY)); + keys[0].dpContext = new KMX_WCHAR[1]; + keys[0].dpContext[0] = 0; + keys[0].dpOutput = new KMX_WCHAR[4]; + keys[0].dpOutput[0] = UC_SENTINEL; + keys[0].dpOutput[1] = CODE_DEADKEY; + keys[0].dpOutput[2] = deadkey; // TODO: translate to unique index + keys[0].dpOutput[3] = 0; + keys[0].Key = vk; + keys[0].Line = 0; + keys[0].ShiftFlags = shift | ISVIRTUALKEY; + kbd->dpGroupArray[i].dpKeyArray = keys; + kbd->dpGroupArray[i].cxKeyArray++; + KMX_LogError(L"Add deadkey rule: + [%d K_%d] > dk(%d)", shift, vk, deadkey); + if (i == kbd->StartGroup[1]) + break; // If this is the initial group, that's all we need to do. + } + } +} + +/** + * @brief find the maximal deadkey id + * @param str the deadkey + * @return the maximum deadkey id + */ +KMX_WCHAR KMX_ScanXStringForMaxDeadkeyID(PKMX_WCHAR str) { + KMX_WCHAR dkid = 0; + while (str && *str) { + if (*str == UC_SENTINEL && *(str + 1) == CODE_DEADKEY) { + dkid = std::max(dkid, *(str + 2)); + } + str = KMX_incxstr(str); + } + return dkid; +} + +struct KMX_dkidmap { + KMX_WCHAR src_deadkey, dst_deadkey; +}; + +/** + * @brief find the deadkey id for a given deadkey + * @param kbd pointer to the keyboard + * @param deadkey for which an id is to be found + * @return 0 if failed; + * otherwise a deadkey-id + */ +KMX_WCHAR KMX_GetUniqueDeadkeyID(LPKMX_KEYBOARD kbd, KMX_WCHAR deadkey) { + LPKMX_GROUP gp; + LPKMX_KEY kp; + LPKMX_STORE sp; + KMX_DWORD i, j; + KMX_WCHAR dkid = 0; + static KMX_WCHAR s_next_dkid = 0; + static KMX_dkidmap* s_dkids = NULL; + static int s_ndkids = 0; + + if (!kbd) { + if (s_dkids) { + delete s_dkids; + } + s_dkids = NULL; + s_ndkids = 0; + s_next_dkid = 0; + return 0; + } + + for (int i = 0; i < s_ndkids; i++) { + if (s_dkids[i].src_deadkey == deadkey) { + return s_dkids[i].dst_deadkey; + } + } + + if (s_next_dkid != 0) { + s_dkids = (KMX_dkidmap*)realloc(s_dkids, sizeof(KMX_dkidmap) * (s_ndkids + 1)); + s_dkids[s_ndkids].src_deadkey = deadkey; + return s_dkids[s_ndkids++].dst_deadkey = ++s_next_dkid; + } + + for (i = 0, gp = kbd->dpGroupArray; i < kbd->cxGroupArray; i++, gp++) { + for (j = 0, kp = gp->dpKeyArray; j < gp->cxKeyArray; j++, kp++) { + dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(kp->dpContext)); + dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(kp->dpOutput)); + } + dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(gp->dpMatch)); + dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(gp->dpNoMatch)); + } + + for (i = 0, sp = kbd->dpStoreArray; i < kbd->cxStoreArray; i++, sp++) { + dkid = std::max(dkid, KMX_ScanXStringForMaxDeadkeyID(sp->dpString)); + } + + s_dkids = (KMX_dkidmap*)realloc(s_dkids, sizeof(KMX_dkidmap) * (s_ndkids + 1)); + s_dkids[s_ndkids].src_deadkey = deadkey; + return s_dkids[s_ndkids++].dst_deadkey = s_next_dkid = ++dkid; +} + +/** + * @brief Lookup the deadkey table for the deadkey in the physical keyboard. Then for each character, go through and map it through + * @param kbd pointer to the keyboard + * @param vk_US virtual key of the us keyboard + * @param shift shiftstate + * @param deadkey character produced by a deadkey + * @param all_vector vector that holds the data of the US keyboard and the currently used (underlying) keyboard + * @param keymap pointer to the currently used (underlying) keyboard Layout + * @param dk_Table a vector of all possible deadkey combinations for all Linux keyboards + */ +void KMX_ConvertDeadkey(LPKMX_KEYBOARD kbd, KMX_WORD vk_US, KMX_DWORD shift, KMX_WCHAR deadkey, vec_dword_3D& all_vector, GdkKeymap* keymap, vec_dword_2D dk_Table) { + KMX_WORD deadkeys[512], *pdk; + + // Lookup the deadkey table for the deadkey in the physical keyboard + // Then for each character, go through and map it through + KMX_WCHAR dkid = KMX_GetUniqueDeadkeyID(kbd, deadkey); + + // Add the deadkey to the mapping table for use in the import rules phase + KMX_DeadkeyMapping KMX_deadkeyMapping = {deadkey, dkid, shift, vk_US}; // I4353 + + KMX_FDeadkeys.push_back(KMX_deadkeyMapping); // dkid, vk, shift); // I4353 + KMX_AddDeadkeyRule(kbd, dkid, vk_US, shift); + + KMX_GetDeadkeys(dk_Table, deadkey, pdk = deadkeys, keymap); // returns array of [usvk, ch_out] pairs + + while (*pdk) { + // Look up the ch + KMX_DWORD KeyValUnderlying = (KMX_DWORD) KMX_get_KeyValUnderlying_From_KeyValUS(all_vector, *pdk); + KMX_TranslateDeadkeyKeyboard(kbd, dkid, KeyValUnderlying, *(pdk + 1), *(pdk + 2)); + pdk += 3; + } +} + +/** + * @brief convert a mnemonic keyboard to a positional keyboard + * (i.e. setting *sp->dpString = '0' / TSS_MNEMONIC=0) + * @param kbd pointer to keyboard + * @return TRUE if conversion was successful; + * FALSE otherwise + */ +KMX_BOOL KMX_SetKeyboardToPositional(LPKMX_KEYBOARD kbd) { + LPKMX_STORE sp; + KMX_DWORD i; + for (i = 0, sp = kbd->dpStoreArray; i < kbd->cxStoreArray; i++, sp++) { + if (sp->dwSystemID == TSS_MNEMONIC) { + if (!sp->dpString) { + KMX_LogError(L"Invalid &mnemoniclayout system store"); + return FALSE; + } + if (u16cmp((const KMX_WCHAR*)sp->dpString, u"1") != 0) { + KMX_LogError(L"Keyboard is not a mnemonic layout keyboard"); + return FALSE; + } + *sp->dpString = '0'; + return TRUE; + } + } + KMX_LogError(L"Keyboard is not a mnemonic layout keyboard"); + return FALSE; +} + +/** + * @brief convert mnemonic keyboard layout to positional keyboard layout and translate keyboard + * @param kbd pointer to US keyboard + * @param bDeadkeyConversion option for converting a deadkey to a character: 1 = dk conversion; 0 = no dk conversion + * @param argc number of command line arguments + * @param argv pointer to command line arguments + * @return TRUE if conversion was successful; + * FALSE if not + */ +KMX_BOOL KMX_DoConvert(LPKMX_KEYBOARD kbd, KMX_BOOL bDeadkeyConversion, gint argc, gchar* argv[]) { + KMX_WCHAR DeadKey = 0; + + if (!KMX_SetKeyboardToPositional(kbd)) + return FALSE; + + // Go through each of the shift states - base, shift, ctrl+alt, ctrl+alt+shift, [caps vs ncaps?] + // Currently, we go in this order so the 102nd key works. But this is not ideal for keyboards without 102nd key: // I4651 + // it catches only the first key that matches a given rule, but multiple keys may match that rule. This is particularly + // evident for the 102nd key on UK, for example, where \ can be generated with VK_OEM_102 or AltGr+VK_QUOTE. + // For now, we get the least shifted version, which is hopefully adequate. + + GdkKeymap* keymap; + if (InitializeGDK(&keymap, argc, argv)) { + printf("ERROR: can't Initialize GDK\n"); + return FALSE; + } + + // create vector that contains Keycode, base, shift for US-KEyboard and underlying keyboard + vec_dword_3D all_vector; + if (createOneVectorFromBothKeyboards(all_vector, keymap)) { + printf("ERROR: can't create one vector from both keyboards\n"); + return FALSE; + } + + vec_dword_2D dk_Table; + create_DKTable(dk_Table); + + for (int j = 0; VKShiftState[j] != 0xFFFF; j++) { // I4651 + + // Loop through each possible key on the keyboard + for (int i = 0; KMX_VKMap[i]; i++) { // I4651 + + // windows uses VK, Linux uses SC/Keycode + KMX_DWORD scUnderlying = (KMX_DWORD)KMX_get_KeyCodeUnderlying_From_VKUS(KMX_VKMap[i]); + KMX_WCHAR ch = KMX_get_KeyValUnderlying_From_KeyCodeUnderlying(keymap, scUnderlying, VKShiftState[j], &DeadKey); + + // printf("--- VK_%d -> SC_ [%c] dk=%d ( ss %i) \n", KMX_VKMap[i], ch == 0 ? 32 : ch, DeadKey, VKShiftState[j]); + + if (bDeadkeyConversion) { // I4552 + if (ch == 0xFFFF) { + ch = DeadKey; + } + } + + switch (ch) { + case 0x0000: break; + case 0xFFFF: KMX_ConvertDeadkey(kbd, KMX_VKMap[i], VKShiftState[j], DeadKey, all_vector, keymap, dk_Table); break; + default: KMX_TranslateKeyboard(kbd, KMX_VKMap[i], VKShiftState[j], ch); + } + } + } + + KMX_ReportUnconvertedKeyboardRules(kbd); + + if (!KMX_ImportRules(kbd, all_vector, &keymap, &KMX_FDeadkeys, bDeadkeyConversion)) { // I4353 // I4552 + return FALSE; + } + return TRUE; +} + +/** + * @brief return an array of [usvk, ch_out] pairs: all existing combinations of a deadkey + character for the underlying keyboard + * @param dk_Table shiftstate of the deadkey + * @param deadkey deadkey character + * @param[out] outputPairs pointer to array of [usvk, ch_out] pairs + * @param keymap pointer to the currently used (underlying) keyboard Layout + * @return size of array of [usvk, ch_out] pairs + */ +int KMX_GetDeadkeys(vec_dword_2D& dk_Table, KMX_WORD deadkey, KMX_WORD* outputPairs, GdkKeymap* keymap) { + KMX_WORD* p = outputPairs; + KMX_DWORD shift; + vec_dword_2D dk_SingleTable; + + query_dk_combinations_for_specific_dk(dk_Table, deadkey, dk_SingleTable); + for (int i = 0; i < (int)dk_SingleTable.size(); i++) { + KMX_WORD vk = KMX_change_keyname_to_capital(dk_SingleTable[i][1], shift, keymap); + if (vk != 0) { + *p++ = vk; + *p++ = shift; + *p++ = dk_SingleTable[i][2]; + } else { + KMX_LogError(L"Warning: complex deadkey not supported."); + } + } + *p = 0; + return (p - outputPairs); +} + +/** + * @brief print (error) messages + * @param fmt text to print + */ +void KMX_LogError(const wchar_t* fmt, ...) { + wchar_t fmtbuf[256]; + const wchar_t* end = L"\0"; + const wchar_t* nl = L"\n"; + va_list vars; + int j = 0; + + va_start(vars, fmt); + vswprintf(fmtbuf, _countof(fmtbuf), fmt, vars); + fmtbuf[255] = 0; + + do { + putwchar(fmtbuf[j]); + j++; + } while (fmtbuf[j] != *end); + putwchar(*nl); +} diff --git a/linux/mcompile/keymap/mcompile.h b/linux/mcompile/keymap/mcompile.h new file mode 100644 index 0000000000..ca3cd78395 --- /dev/null +++ b/linux/mcompile/keymap/mcompile.h @@ -0,0 +1,38 @@ +/* + Name: mcompile + Copyright: Copyright (C) 2003-2017 SIL International. + Documentation: + Description: + Create Date: 3 Aug 2014 + + Modified Date: 3 Aug 2014 + Authors: mcdurdin + Related Files: + Dependencies: + + Bugs: + Todo: + Notes: + History: 03 Aug 2014 - mcdurdin - I4353 - V9.0 - mnemonic layout recompiler mixes up deadkey rules + +*/ + +#ifndef MCOMPILE_H +#define MCOMPILE_H +#include +#include "keymap.h" +#include "deadkey.h" +#include "mc_kmxfile.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 KMX_LogError(const wchar_t* fmt, ...); + +#endif /*MCOMPILE_H*/ diff --git a/linux/mcompile/keymap/meson.build b/linux/mcompile/keymap/meson.build new file mode 100644 index 0000000000..3866ca34f1 --- /dev/null +++ b/linux/mcompile/keymap/meson.build @@ -0,0 +1,31 @@ +project('mcompile', 'c', 'cpp', + license: 'MIT', + meson_version: '>=1.0') + +gtk = dependency('gtk+-3.0', version: '>= 2.4') +xkb = dependency('xkbcommon') +libxklavier = dependency('libxklavier') + +deps = [gtk, xkb,libxklavier] + +subdir('resources') + +cpp_files = files( + 'keymap.cpp', + 'deadkey.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', + sources: [cpp_files], + dependencies: deps, + include_directories : comon_include_dir + ) diff --git a/linux/scripts/dist.sh b/linux/scripts/dist.sh index 0038034d42..6ef0be7d8b 100755 --- a/linux/scripts/dist.sh +++ b/linux/scripts/dist.sh @@ -30,28 +30,37 @@ cp -a debian ../ cd .. echo "3.0 (native)" > debian/source/format dch keyman --newversion "${VERSION}" --force-bad-version --nomultimaint -dpkg-source --tar-ignore=*~ --tar-ignore=.git --tar-ignore=.gitattributes \ - --tar-ignore=.gitignore --tar-ignore=experiments --tar-ignore=debian \ - --tar-ignore=.github --tar-ignore=.vscode --tar-ignore=android \ +dpkg-source --tar-ignore=*~ \ + --tar-ignore=.git \ + --tar-ignore=.gitattributes \ + --tar-ignore=.gitignore \ + --tar-ignore=experiments \ + --tar-ignore=debian \ + --tar-ignore=.github \ + --tar-ignore=.vscode \ + --tar-ignore=android \ --tar-ignore=.devcontainer \ --tar-ignore=artifacts \ \ --tar-ignore=common/models \ - --tar-ignore=common/predictive-text \ --tar-ignore=common/resources \ --tar-ignore=common/schemas \ --tar-ignore=common/test/keyboards/build.* \ - --tar-ignore=common/test/predictive-text \ --tar-ignore=common/test/resources \ --tar-ignore=common/web \ --tar-ignore=common/windows \ \ --tar-ignore=core/build \ - --tar-ignore=developer --tar-ignore=docs --tar-ignore=ios \ + --tar-ignore=developer \ + --tar-ignore=docs \ + --tar-ignore=ios \ --tar-ignore=linux/keyman-config/keyman_config/version.py \ - --tar-ignore=linux/keyman-config/buildtools/build-langtags.py --tar-ignore=__pycache__ \ + --tar-ignore=linux/keyman-config/buildtools/build-langtags.py \ + --tar-ignore=__pycache__ \ --tar-ignore=linux/help \ - --tar-ignore=mac --tar-ignore=node_modules --tar-ignore=oem \ + --tar-ignore=mac \ + --tar-ignore=node_modules \ + --tar-ignore=oem \ --tar-ignore=linux/build \ --tar-ignore=linux/builddebs \ --tar-ignore=linux/ibus-keyman/build \ @@ -60,11 +69,17 @@ dpkg-source --tar-ignore=*~ --tar-ignore=.git --tar-ignore=.gitattributes \ --tar-ignore=resources/environment.sh \ --tar-ignore=resources/git-hooks \ --tar-ignore=resources/scopes \ - --tar-ignore=resources/build/*.lua --tar-ignore=resources/build/jq* \ + --tar-ignore=resources/build/*.lua \ + --tar-ignore=resources/build/jq* \ --tar-ignore=results \ --tar-ignore=tmp \ - --tar-ignore=web --tar-ignore=windows --tar-ignore=keyman_1* \ - --tar-ignore=dist --tar-ignore=VERSION -Zgzip -b . + --tar-ignore=web \ + --tar-ignore=windows \ + --tar-ignore=keyman_1* \ + --tar-ignore=dist \ + --tar-ignore=VERSION \ + \ + -Zgzip -b . mv ../keyman_"${VERSION}".tar.gz linux/dist/keyman-"${VERSION}".tar.gz echo "3.0 (quilt)" > debian/source/format cd "$BASEDIR" diff --git a/package-lock.json b/package-lock.json index 9094794831..ad53581202 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,9 +25,9 @@ "common/test/resources", "common/tools/*", "common/web/*", - "common/predictive-text", "common/tools/hextobin", - "web" + "web", + "web/src/engine/predictive-text/*" ], "dependencies": { "@keymanapp/common-types": "file:common/web/types", @@ -108,7 +108,7 @@ "typescript": "^5.4.5" } }, - "common/predictive-text": { + "web/src/engine/predictive-text/worker-main": { "name": "@keymanapp/lexical-model-layer", "license": "MIT", "dependencies": { @@ -191,23 +191,6 @@ "typescript": "^5.4.5" } }, - "common/web/keyboard-processor": { - "name": "@keymanapp/keyboard-processor", - "license": "MIT", - "dependencies": { - "@keymanapp/common-types": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "@keymanapp/web-utils": "*" - }, - "devDependencies": { - "@keymanapp/resources-gosh": "*", - "c8": "^7.12.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^5.4.5" - } - }, "common/web/keyman-version": { "name": "@keymanapp/keyman-version", "license": "MIT", @@ -2868,10 +2851,6 @@ "resolved": "common/tools/hextobin", "link": true }, - "node_modules/@keymanapp/keyboard-processor": { - "resolved": "common/web/keyboard-processor", - "link": true - }, "node_modules/@keymanapp/keyman-version": { "resolved": "common/web/keyman-version", "link": true @@ -2913,7 +2892,7 @@ "link": true }, "node_modules/@keymanapp/lexical-model-layer": { - "resolved": "common/predictive-text", + "resolved": "web/src/engine/predictive-text/worker-main", "link": true }, "node_modules/@keymanapp/lm-message-types": { @@ -14821,7 +14800,6 @@ "name": "keyman", "license": "MIT", "dependencies": { - "@keymanapp/keyboard-processor": "*", "@keymanapp/keyman-version": "*", "@keymanapp/lexical-model-layer": "*", "@keymanapp/models-types": "*", diff --git a/package.json b/package.json index 638f2b2fad..3f0012db10 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,9 @@ "common/test/resources", "common/tools/*", "common/web/*", - "common/predictive-text", "common/tools/hextobin", - "web" + "web", + "web/src/engine/predictive-text/*" ], "dependencies": { "@keymanapp/common-types": "file:common/web/types", diff --git a/tsconfig.base.json b/tsconfig.base.json index e34839a52c..c4e0599eef 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -26,7 +26,6 @@ "paths": { "@keymanapp/common-types": ["./common/web/types/src/main"], - "@keymanapp/keyboard-processor": ["./common/web/keyboard-processor/src"], "@keymanapp/keyman": ["./web" ], "@keymanapp/models-types": ["./common/models/types"], "@keymanapp/models-templates": ["./common/models/templates"], diff --git a/tsconfig.json b/tsconfig.json index 093cec46f4..42b3fd1a99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,12 +5,10 @@ { "path": "./common/models/templates/tsconfig.json" }, { "path": "./common/models/types/tsconfig.json" }, { "path": "./common/models/wordbreakers/tsconfig.json" }, - { "path": "./common/predictive-text/tsconfig.all.json" }, { "path": "./common/tools/hextobin/" }, { "path": "./common/web/gesture-recognizer/tsconfig.json" }, { "path": "./common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json" }, - { "path": "./common/web/keyboard-processor/tsconfig.json" }, { "path": "./common/web/keyman-version" }, { "path": "./common/web/lm-message-types/" }, { "path": "./common/web/lm-worker/" }, @@ -44,6 +42,7 @@ { "path": "./resources/build/version/" }, { "path": "./web/src/tsconfig.all.json" }, + { "path": "./web/src/engine/predictive-text/worker-main/tsconfig.all.json" }, // { "path": "./web/tools/recorder/tsconfig.json" }, // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, ] diff --git a/web/README.md b/web/README.md index 1249805165..1e41497657 100644 --- a/web/README.md +++ b/web/README.md @@ -81,7 +81,8 @@ title: Dependency Graph %%{init: {"flowchart": {"htmlLabels": false}} }%% graph TD; OSK["/web/src/engine/osk"]; - KP["@keymanapp/keyboard-processor
(/common/web/keyboard-processor)"]; + KP["/web/src/engine/keyboard"]; + JSProc["/web/src/engine/js-processor"]; OSK-->KP; WebUtils["@keymanapp/web-utils
(/common/web/utils)"]; KP---->WebUtils; @@ -91,7 +92,7 @@ graph TD; LMWorker["@keymanapp/lm-worker
(/common/web/lm-worker)"]; LMWorker-->Models; LMWorker-->Wordbreakers; - LMLayer["@keymanapp/lexical-model-layer
(/common/predictive-text)"]; + LMLayer["@keymanapp/lexical-model-layer
(/web/src/engine/predictive-text/worker-main)"]; LMLayer-->LMWorker; Gestures["@keymanapp/gesture-recognizer
(/common/web/gesture-recognizer)"]; Gestures-->WebUtils; @@ -107,6 +108,7 @@ graph TD; Fully headless components`"] direction LR KP; + JSProc-->KP; WebUtils; PredText; Gestures; @@ -117,9 +119,9 @@ graph TD; Device["/web/src/engine/device-detect"]; Device----->WebUtils; Elements["/web/src/engine/element-wrappers"]; - Elements-->KP; - KeyboardCache["/web/src/engine/package-cache"]; - KeyboardCache-->Interfaces; + Elements-->JSProc; + KeyboardStorage["/web/src/engine/keyboard-storage"]; + KeyboardStorage-->Interfaces; DomUtils["/web/src/engine/dom-utils"]; DomUtils-->WebUtils; DomUtils-->KP; @@ -127,11 +129,11 @@ graph TD; OSK-->Gestures; Interfaces["/web/src/engine/interfaces"]; Interfaces-->KP; + Interfaces-->JSProc; OSK-->Interfaces; CommonEngine["/web/src/engine/main"]; - CommonEngine-->Interfaces; CommonEngine-->Device; - CommonEngine-->KeyboardCache; + CommonEngine-->KeyboardStorage; CommonEngine-->OSK; Attachment["/web/src/engine/attachment"]; Attachment-->DomUtils; diff --git a/web/build.sh b/web/build.sh index 7248f5c35a..f0dd201bc8 100755 --- a/web/build.sh +++ b/web/build.sh @@ -27,10 +27,13 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ ":engine/dom-utils A common subset of function used for DOM calculations, layout, etc" \ ":engine/events Specialized classes utilized to support KMW API events" \ ":engine/element-wrappers Subset used to integrate with website elements" \ + ":engine/interfaces Subset used to configure KMW" \ + ":engine/js-processor Build JS processor for KMW" \ + ":engine/keyboard Builds KMW's keyboard-loading and caching code" \ + ":engine/keyboard-storage Subset used to collate keyboards and request them from the cloud" \ ":engine/main Builds all common code used by KMW's app/-level targets" \ ":engine/osk Builds the Web OSK module" \ - ":engine/package-cache Subset used to collate keyboards and request them from the cloud" \ - ":engine/interfaces Subset used to configure KMW" \ + ":engine/predictive-text=src/engine/predictive-text/worker-main Builds KMW's predictive text module" \ ":samples Builds all needed resources for the KMW sample-page set" \ ":tools Builds engine-related development resources" \ ":test-pages=src/test/manual Builds resources needed for the KMW manual testing pages" \ @@ -57,10 +60,13 @@ builder_describe_outputs \ build:engine/dom-utils "/web/build/engine/dom-utils/obj/index.js" \ build:engine/events "/web/build/engine/events/lib/index.mjs" \ build:engine/element-wrappers "/web/build/engine/element-wrappers/lib/index.mjs" \ + build:engine/interfaces "/web/build/engine/interfaces/lib/index.mjs" \ + build:engine/js-processor "/web/build/engine/js-processor/lib/index.mjs" \ + build:engine/keyboard "/web/build/engine/keyboard/lib/index.mjs" \ + build:engine/keyboard-storage "/web/build/engine/keyboard-storage/lib/index.mjs" \ build:engine/main "/web/build/engine/main/lib/index.mjs" \ build:engine/osk "/web/build/engine/osk/lib/index.mjs" \ - build:engine/package-cache "/web/build/engine/package-cache/lib/index.mjs" \ - build:engine/interfaces "/web/build/engine/interfaces/lib/index.mjs" \ + build:engine/predictive-text "/web/src/engine/predictive-text/worker-main/build/lib/web/index.mjs" \ build:samples "/web/src/samples/simplest/keymanweb.js" \ build:tools "/web/build/tools/building/sourcemap-root/index.js" \ build:test-pages "/web/build/test-resources/sentry-manager.js" @@ -148,9 +154,9 @@ builder_run_child_actions build:engine/osk builder_run_child_actions build:engine/attachment # Uses engine/interfaces (due to resource-path config interface) -builder_run_child_actions build:engine/package-cache +builder_run_child_actions build:engine/keyboard-storage -# Uses engine/interfaces, engine/device-detect, engine/package-cache, & engine/osk +# Uses engine/interfaces, engine/device-detect, engine/keyboard-storage, & engine/osk builder_run_child_actions build:engine/main # Uses all but engine/element-wrappers and engine/attachment diff --git a/web/ci.sh b/web/ci.sh index 6e5bc3b06a..5e26a1e30e 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -85,7 +85,7 @@ if builder_start_action test; then # No --reporter option exists yet for the headless modules. - "$KEYMAN_ROOT/common/web/keyboard-processor/build.sh" test $OPTIONS + "$KEYMAN_ROOT/web/src/engine/keyboard/build.sh" test $OPTIONS "$KEYMAN_ROOT/common/web/gesture-recognizer/test.sh" $OPTIONS ./build.sh test $OPTIONS diff --git a/web/package.json b/web/package.json index 458ea85de0..96a11c0cd3 100644 --- a/web/package.json +++ b/web/package.json @@ -37,21 +37,41 @@ "types": "./build/engine/events/obj/index.d.ts", "import": "./build/engine/events/obj/index.js" }, - "./engine/package-cache": { - "es6-bundling": "./src/engine/package-cache/src/index.ts", - "types": "./build/engine/package-cache/obj/index.d.ts", - "import": "./build/engine/package-cache/obj/index.js", - "require": "./build/engine/package-cache/obj/index.js" + "./engine/js-processor": { + "es6-bundling": "./src/engine/js-processor/src/index.ts", + "types": "./build/engine/js-processor/obj/index.d.ts", + "import": "./build/engine/js-processor/obj/index.js" }, - "./engine/package-cache/dom-requester": { - "es6-bundling": "./src/engine/package-cache/src/domCloudRequester.ts", - "types": "./build/engine/package-cache/obj/domCloudRequester.d.ts", - "import": "./build/engine/package-cache/obj/domCloudRequester.js" + "./engine/keyboard": { + "es6-bundling": "./src/engine/keyboard/src/index.ts", + "types": "./build/engine/keyboard/obj/index.d.ts", + "import": "./build/engine/keyboard/obj/index.js" }, - "./engine/package-cache/node-requester": { - "es6-bundling": "./src/engine/package-cache/src/nodeCloudRequester.ts", - "types": "./build/engine/package-cache/obj/nodeCloudRequester.d.ts", - "import": "./build/engine/package-cache/obj/nodeCloudRequester.js" + "./engine/keyboard/node-keyboard-loader": { + "es6-bundling": "./src/engine/keyboard/src/keyboards/loaders/node-keyboard-loader.ts", + "types": "./build/engine/keyboard/obj/keyboards/loaders/node-keyboard-loader.d.ts", + "import": "./build/engine/keyboard/obj/keyboards/loaders/node-keyboard-loader.js" + }, + "./engine/keyboard/dom-keyboard-loader": { + "es6-bundling": "./src/engine/keyboard/src/keyboards/loaders/dom-keyboard-loader.ts", + "types": "./build/engine/keyboard/obj/keyboards/loaders/dom-keyboard-loader.d.ts", + "import": "./build/engine/keyboard/obj/keyboards/loaders/dom-keyboard-loader.js" + }, + "./engine/keyboard-storage": { + "es6-bundling": "./src/engine/keyboard-storage/src/index.ts", + "types": "./build/engine/keyboard-storage/obj/index.d.ts", + "import": "./build/engine/keyboard-storage/obj/index.js", + "require": "./build/engine/keyboard-storage/obj/index.js" + }, + "./engine/keyboard-storage/dom-requester": { + "es6-bundling": "./src/engine/keyboard-storage/src/domCloudRequester.ts", + "types": "./build/engine/keyboard-storage/obj/domCloudRequester.d.ts", + "import": "./build/engine/keyboard-storage/obj/domCloudRequester.js" + }, + "./engine/keyboard-storage/node-requester": { + "es6-bundling": "./src/engine/keyboard-storage/src/nodeCloudRequester.ts", + "types": "./build/engine/keyboard-storage/obj/nodeCloudRequester.d.ts", + "import": "./build/engine/keyboard-storage/obj/nodeCloudRequester.js" }, "./engine/main": { "es6-bundling": "./src/engine/main/src/index.ts", @@ -99,7 +119,6 @@ "test": "gosh ./test.sh" }, "dependencies": { - "@keymanapp/keyboard-processor": "*", "@keymanapp/keyman-version": "*", "@keymanapp/lexical-model-layer": "*", "@keymanapp/models-types": "*", diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts index 3659f8d7fd..eb6d6583ee 100644 --- a/web/src/app/browser/src/beepHandler.ts +++ b/web/src/app/browser/src/beepHandler.ts @@ -1,4 +1,4 @@ -import { type KeyboardInterface } from '@keymanapp/keyboard-processor'; +import { type KeyboardInterface } from 'keyman/engine/js-processor'; import { DesignIFrame, OutputTarget } from 'keyman/engine/element-wrappers'; // Utility object used to handle beep (keyboard error response) operations. diff --git a/web/src/app/browser/src/configuration.ts b/web/src/app/browser/src/configuration.ts index 07be8264e7..df2153df82 100644 --- a/web/src/app/browser/src/configuration.ts +++ b/web/src/app/browser/src/configuration.ts @@ -1,7 +1,7 @@ import { EngineConfiguration, InitOptionSpec, InitOptionDefaults } from "keyman/engine/main"; import { OutputTarget as DOMOutputTarget } from 'keyman/engine/element-wrappers'; -import { isEmptyTransform, OutputTarget, RuleBehavior } from '@keymanapp/keyboard-processor'; +import { isEmptyTransform, OutputTarget, RuleBehavior } from 'keyman/engine/js-processor'; import { AlertHost } from "./utils/alertHost.js"; import { whenDocumentReady } from "./utils/documentReady.js"; diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 1511269bbe..1fbc905203 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -1,5 +1,5 @@ -import { type Keyboard, KeyboardScriptError } from '@keymanapp/keyboard-processor'; -import { type KeyboardStub } from 'keyman/engine/package-cache'; +import { type Keyboard, KeyboardScriptError } from 'keyman/engine/keyboard'; +import { type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { CookieSerializer } from 'keyman/engine/dom-utils'; import { eventOutputTarget, outputTargetForElement, PageContextAttachment } from 'keyman/engine/attachment'; import { DomEventTracker, LegacyEventEmitter } from 'keyman/engine/events'; diff --git a/web/src/app/browser/src/defaultBrowserRules.ts b/web/src/app/browser/src/defaultBrowserRules.ts index 1ac4801689..4f0ffbe012 100644 --- a/web/src/app/browser/src/defaultBrowserRules.ts +++ b/web/src/app/browser/src/defaultBrowserRules.ts @@ -2,9 +2,9 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { Codes, DefaultRules, - type KeyEvent, - type OutputTarget -} from '@keymanapp/keyboard-processor'; + type KeyEvent +} from 'keyman/engine/keyboard'; +import { type OutputTarget } from 'keyman/engine/js-processor'; import ContextManager from './contextManager.js'; diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index 29e867aa13..0bb594e897 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -1,4 +1,5 @@ -import { Codes, DeviceSpec, KeyEvent, KeyMapping, Keyboard, KeyboardProcessor } from '@keymanapp/keyboard-processor'; +import { Codes, DeviceSpec, KeyEvent, KeyMapping, Keyboard } from 'keyman/engine/keyboard'; +import { KeyboardProcessor } from 'keyman/engine/js-processor'; import { ModifierKeyConstants } from '@keymanapp/common-types'; import { HardKeyboard, processForMnemonicsAndLegacy } from 'keyman/engine/main'; diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index d807c415eb..324394bae2 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -6,8 +6,8 @@ import { TwoStateActivator, VisualKeyboard } from 'keyman/engine/osk'; -import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/package-cache'; -import { DeviceSpec, Keyboard, KeyboardObject } from "@keymanapp/keyboard-processor"; +import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/keyboard-storage'; +import { DeviceSpec, Keyboard, KeyboardObject } from "keyman/engine/keyboard"; import * as views from './viewsAnchorpoint.js'; import { BrowserConfiguration, BrowserInitOptionDefaults, BrowserInitOptionSpec } from './configuration.js'; diff --git a/web/src/app/browser/src/languageMenu.ts b/web/src/app/browser/src/languageMenu.ts index eb9361ccba..d3144de6e5 100644 --- a/web/src/app/browser/src/languageMenu.ts +++ b/web/src/app/browser/src/languageMenu.ts @@ -1,6 +1,6 @@ // Manages the language selection UI for touch-form factors, which is triggered by an OSK key. import { getAbsoluteX, landscapeView } from "keyman/engine/dom-utils"; -import { KeyboardStub } from "keyman/engine/package-cache"; +import { KeyboardStub } from "keyman/engine/keyboard-storage"; import KeymanEngine from "./keymanEngine.js"; import * as util from "./utils/index.js"; diff --git a/web/src/app/webview/src/contextManager.ts b/web/src/app/webview/src/contextManager.ts index b0c322ea29..b0b90b4d96 100644 --- a/web/src/app/webview/src/contextManager.ts +++ b/web/src/app/webview/src/contextManager.ts @@ -1,5 +1,6 @@ -import { type Keyboard, Mock, OutputTarget, Transcription, findCommonSubstringEndIndex, isEmptyTransform, TextTransform } from '@keymanapp/keyboard-processor'; -import { KeyboardStub } from 'keyman/engine/package-cache'; +import { type Keyboard } from 'keyman/engine/keyboard'; +import { Mock, OutputTarget, Transcription, findCommonSubstringEndIndex, isEmptyTransform, TextTransform } from 'keyman/engine/js-processor'; +import { KeyboardStub } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from 'keyman/engine/main'; import { WebviewConfiguration } from './configuration.js'; diff --git a/web/src/app/webview/src/keymanEngine.ts b/web/src/app/webview/src/keymanEngine.ts index ba98f8c477..d390a6ce1b 100644 --- a/web/src/app/webview/src/keymanEngine.ts +++ b/web/src/app/webview/src/keymanEngine.ts @@ -1,8 +1,9 @@ -import { DefaultRules, DeviceSpec, RuleBehavior } from '@keymanapp/keyboard-processor' +import { DeviceSpec, DefaultRules } from 'keyman/engine/keyboard'; +import { RuleBehavior } from 'keyman/engine/js-processor'; import { KeymanEngine as KeymanEngineBase, KeyboardInterface } from 'keyman/engine/main'; import { AnchoredOSKView, ViewConfiguration, StaticActivator } from 'keyman/engine/osk'; import { getAbsoluteX, getAbsoluteY } from 'keyman/engine/dom-utils'; -import { toPrefixedKeyboardId, toUnprefixedKeyboardId } from 'keyman/engine/package-cache'; +import { toPrefixedKeyboardId, toUnprefixedKeyboardId } from 'keyman/engine/keyboard-storage'; import { WebviewConfiguration, WebviewInitOptionDefaults, WebviewInitOptionSpec } from './configuration.js'; import ContextManager, { ContextHost } from './contextManager.js'; diff --git a/web/src/app/webview/src/oskConfiguration.ts b/web/src/app/webview/src/oskConfiguration.ts index 26ceeb2872..9250b5a2a1 100644 --- a/web/src/app/webview/src/oskConfiguration.ts +++ b/web/src/app/webview/src/oskConfiguration.ts @@ -1,5 +1,5 @@ import { OSKView } from "keyman/engine/osk"; -import { DeviceSpec } from "@keymanapp/keyboard-processor"; +import { DeviceSpec } from "keyman/engine/keyboard"; import { type EmbeddedGestureConfig } from "keyman/engine/osk"; import { GlobeHint } from './osk/globeHint.js'; diff --git a/web/src/app/webview/src/passthroughKeyboard.ts b/web/src/app/webview/src/passthroughKeyboard.ts index d74ab7f99a..25dec7a8e6 100644 --- a/web/src/app/webview/src/passthroughKeyboard.ts +++ b/web/src/app/webview/src/passthroughKeyboard.ts @@ -1,4 +1,4 @@ -import { DeviceSpec, Keyboard, KeyEvent, ManagedPromise } from '@keymanapp/keyboard-processor'; +import { DeviceSpec, Keyboard, KeyEvent, ManagedPromise } from 'keyman/engine/keyboard'; import { HardKeyboard, processForMnemonicsAndLegacy } from 'keyman/engine/main'; diff --git a/web/src/engine/attachment/src/pageContextAttachment.ts b/web/src/engine/attachment/src/pageContextAttachment.ts index d63c113d5e..4325c88f15 100644 --- a/web/src/engine/attachment/src/pageContextAttachment.ts +++ b/web/src/engine/attachment/src/pageContextAttachment.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'eventemitter3'; -import { DeviceSpec, InternalKeyboardFont } from "@keymanapp/keyboard-processor"; +import { DeviceSpec, InternalKeyboardFont } from "keyman/engine/keyboard"; import { Input, nestedInstanceOf, wrapElement } from "keyman/engine/element-wrappers"; import { arrayFromNodeList, diff --git a/web/src/engine/dom-utils/build.sh b/web/src/engine/dom-utils/build.sh index 74b46051c4..d2e424db20 100755 --- a/web/src/engine/dom-utils/build.sh +++ b/web/src/engine/dom-utils/build.sh @@ -16,7 +16,7 @@ SUBPROJECT_NAME=engine/dom-utils builder_describe "Builds DOM-utility modules used by the Keyman Engine for Web (KMW)." \ "@/common/web/utils" \ - "@/common/web/keyboard-processor" \ + "@/web/src/engine/keyboard" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/dom-utils/src/stylesheets.ts b/web/src/engine/dom-utils/src/stylesheets.ts index 7312a62f05..977f253ff7 100644 --- a/web/src/engine/dom-utils/src/stylesheets.ts +++ b/web/src/engine/dom-utils/src/stylesheets.ts @@ -1,5 +1,5 @@ import { DeviceSpec, ManagedPromise } from '@keymanapp/web-utils'; -import { type InternalKeyboardFont as KeyboardFont } from '@keymanapp/keyboard-processor'; +import { type InternalKeyboardFont as KeyboardFont } from 'keyman/engine/keyboard'; type FontFamilyStyleMap = {[family: string]: HTMLStyleElement}; diff --git a/web/src/engine/dom-utils/tsconfig.json b/web/src/engine/dom-utils/tsconfig.json index c0b4a7d79d..dadf4db5c8 100644 --- a/web/src/engine/dom-utils/tsconfig.json +++ b/web/src/engine/dom-utils/tsconfig.json @@ -11,7 +11,7 @@ "include": [ "src/**/*.ts" ], "references": [ - { "path": "../../../../common/web/keyboard-processor" }, + { "path": "../keyboard" }, { "path": "../../../../common/web/utils" } ] } diff --git a/web/src/engine/element-wrappers/build.sh b/web/src/engine/element-wrappers/build.sh index cfcc58a44f..e188c7dd41 100755 --- a/web/src/engine/element-wrappers/build.sh +++ b/web/src/engine/element-wrappers/build.sh @@ -13,7 +13,7 @@ SUBPROJECT_NAME=engine/element-wrappers # ################################ Main script ################################ builder_describe "Builds DOM-based OutputTarget subclasses used by the Keyman Engine for Web (KMW)." \ - "@/common/web/keyboard-processor" \ + "@/web/src/engine/js-processor" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/element-wrappers/readme.md b/web/src/engine/element-wrappers/readme.md index 1237237c7b..2a7685edbc 100644 --- a/web/src/engine/element-wrappers/readme.md +++ b/web/src/engine/element-wrappers/readme.md @@ -1,4 +1,4 @@ ## engine/element-wrappers This submodule provides a subset of the main engine's Web-oriented code that's used to 'wrap' webpage -elements as part of KMW attachment and interface the element with the `keyboard-processor` submodule. \ No newline at end of file +elements as part of KMW attachment and interface the element with the `keyboard` submodule. \ No newline at end of file diff --git a/web/src/engine/element-wrappers/src/outputTarget.ts b/web/src/engine/element-wrappers/src/outputTarget.ts index 3a288c1347..43623af781 100644 --- a/web/src/engine/element-wrappers/src/outputTarget.ts +++ b/web/src/engine/element-wrappers/src/outputTarget.ts @@ -1,4 +1,4 @@ -import { OutputTarget as OutputTargetBase } from "@keymanapp/keyboard-processor"; +import { OutputTarget as OutputTargetBase } from "keyman/engine/js-processor"; import { EventEmitter } from 'eventemitter3'; export default abstract class OutputTarget extends OutputTargetBase { diff --git a/web/src/engine/element-wrappers/tsconfig.json b/web/src/engine/element-wrappers/tsconfig.json index ab6aa238a8..15a58bbf2a 100644 --- a/web/src/engine/element-wrappers/tsconfig.json +++ b/web/src/engine/element-wrappers/tsconfig.json @@ -11,6 +11,6 @@ "include": [ "src/**/*.ts" ], "references": [ - { "path": "../../../../common/web/keyboard-processor" } + { "path": "../keyboard" } ] } diff --git a/web/src/engine/events/build.sh b/web/src/engine/events/build.sh index 438d075c2c..1b8993f570 100755 --- a/web/src/engine/events/build.sh +++ b/web/src/engine/events/build.sh @@ -14,7 +14,7 @@ SUBPROJECT_NAME=engine/events builder_describe "Builds specialized event-related modules utilized by Keyman Engine for Web." \ "@/common/web/utils build" \ - "@/common/web/keyboard-processor build" \ + "@/web/src/engine/keyboard build" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/events/src/index.ts b/web/src/engine/events/src/index.ts index 2d95307bc9..ee5674b66c 100644 --- a/web/src/engine/events/src/index.ts +++ b/web/src/engine/events/src/index.ts @@ -1,4 +1,3 @@ export { DomEventTracker } from './domEventTracker.js'; export { EmitterListenerSpy } from './emitterListenerSpy.js'; -export * from './keyEventSource.interface.js'; export * from './legacyEventEmitter.js'; \ No newline at end of file diff --git a/web/src/engine/events/tsconfig.json b/web/src/engine/events/tsconfig.json index bb25ac9c99..5d0a45bbab 100644 --- a/web/src/engine/events/tsconfig.json +++ b/web/src/engine/events/tsconfig.json @@ -12,6 +12,6 @@ "references": [ { "path": "../../../../common/web/utils" }, - { "path": "../../../../common/web/keyboard-processor" } + { "path": "../keyboard" } ] } diff --git a/web/src/engine/interfaces/build.sh b/web/src/engine/interfaces/build.sh index ca61e64865..568bd7940d 100755 --- a/web/src/engine/interfaces/build.sh +++ b/web/src/engine/interfaces/build.sh @@ -14,7 +14,8 @@ SUBPROJECT_NAME=engine/interfaces builder_describe "Builds configuration subclasses used by the Keyman Engine for Web (KMW)." \ "@/common/web/es-bundling" \ - "@/common/web/keyboard-processor" \ + "@/web/src/engine/keyboard" \ + "@/web/src/engine/js-processor" \ "clean" \ "configure" \ "build" \ diff --git a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts index 7265d51f46..df5ebae896 100644 --- a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts +++ b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts @@ -1,7 +1,7 @@ /// import { EventEmitter } from "eventemitter3"; -import { OutputTarget } from "@keymanapp/keyboard-processor"; +import { OutputTarget } from "keyman/engine/js-processor"; export class ReadySuggestions { suggestions: Suggestion[]; diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index d47bf4b8e2..221875e62c 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { type LanguageProcessorSpec , ReadySuggestions, type InvalidateSourceEnum, StateChangeHandler } from './languageProcessor.interface.js'; -import { type KeyboardProcessor, type OutputTarget } from "@keymanapp/keyboard-processor"; +import { type KeyboardProcessor, type OutputTarget } from 'keyman/engine/js-processor'; interface PredictionContextEventMap { update: (suggestions: Suggestion[]) => void; diff --git a/web/src/engine/interfaces/tsconfig.json b/web/src/engine/interfaces/tsconfig.json index d5a193e021..e53a808682 100644 --- a/web/src/engine/interfaces/tsconfig.json +++ b/web/src/engine/interfaces/tsconfig.json @@ -12,6 +12,7 @@ "include": [ "**/*.ts" ], "references": [ - { "path": "../../../../common/web/keyboard-processor" }, + { "path": "../keyboard" }, + { "path": "../js-processor" } ] } diff --git a/web/src/engine/js-processor/build.sh b/web/src/engine/js-processor/build.sh new file mode 100755 index 0000000000..db00593166 --- /dev/null +++ b/web/src/engine/js-processor/build.sh @@ -0,0 +1,42 @@ +#!/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" +## END STANDARD BUILD SCRIPT INCLUDE + +SUBPROJECT_NAME=engine/js-processor + +. "${KEYMAN_ROOT}/web/common.inc.sh" +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" + +# ################################ Main script ################################ + +builder_describe "Builds configuration subclasses used by the Keyman Engine for Web (KMW)." \ + "clean" \ + "configure" \ + "build" \ + "test" \ + "--ci+ Set to utilize CI-based test configurations & reporting." + +builder_describe_outputs \ + configure "/node_modules" \ + build "/web/build/${SUBPROJECT_NAME}/lib/index.mjs" + +builder_parse "$@" + +#### Build action definitions #### + +do_build () { + compile "${SUBPROJECT_NAME}" + + ${BUNDLE_CMD} "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/obj/index.js" \ + --out "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}/lib/index.mjs" \ + --format esm +} + +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}" +builder_run_action build do_build +builder_run_action test test-headless "${SUBPROJECT_NAME}" "" diff --git a/common/web/keyboard-processor/src/text/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts similarity index 100% rename from common/web/keyboard-processor/src/text/deadkeys.ts rename to web/src/engine/js-processor/src/deadkeys.ts diff --git a/web/src/engine/js-processor/src/index.ts b/web/src/engine/js-processor/src/index.ts new file mode 100644 index 0000000000..1755499119 --- /dev/null +++ b/web/src/engine/js-processor/src/index.ts @@ -0,0 +1,11 @@ +export { default as KeyboardProcessor } from "./keyboardProcessor.js"; +export * from "./keyboardProcessor.js"; +export { default as RuleBehavior } from "./ruleBehavior.js"; +export * from './kbdInterface.js'; +export { default as KeyboardInterface } from "./kbdInterface.js"; +export * from "./systemStores.js"; +export * from "./deadkeys.js"; +export { default as OutputTarget } from "./outputTarget.js"; +export * from "./outputTarget.js"; +export { Mock } from "./mock.js"; +export * from "./stringDivergence.js"; diff --git a/common/web/keyboard-processor/src/text/kbdInterface.ts b/web/src/engine/js-processor/src/kbdInterface.ts similarity index 96% rename from common/web/keyboard-processor/src/text/kbdInterface.ts rename to web/src/engine/js-processor/src/kbdInterface.ts index 438d328f0d..b39b2baea2 100644 --- a/common/web/keyboard-processor/src/text/kbdInterface.ts +++ b/web/src/engine/js-processor/src/kbdInterface.ts @@ -7,18 +7,12 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; import { ModifierKeyConstants } from '@keymanapp/common-types'; - -import Codes from "./codes.js"; -import type KeyEvent from "./keyEvent.js"; -import type { Deadkey } from "./deadkeys.js"; -import KeyMapping from "./keyMapping.js"; -import { SystemStore, MutableSystemStore, PlatformSystemStore } from "./systemStores.js"; -import type { VariableStoreSerializer } from "./keyboardProcessor.js"; -import type OutputTarget from "./outputTarget.js"; -import { Mock } from "./outputTarget.js"; +import { Codes, type KeyEvent, KeyMapping, Keyboard, KeyboardHarness, KeyboardKeymanGlobal, VariableStoreDictionary } from "keyman/engine/keyboard"; +import type OutputTarget from './outputTarget.js'; +import { type Deadkey } from './deadkeys.js'; +import { Mock } from "./mock.js"; import RuleBehavior from "./ruleBehavior.js"; -import Keyboard, { VariableStoreDictionary } from "../keyboards/keyboard.js"; -import { KeyboardHarness, KeyboardKeymanGlobal } from "../keyboards/keyboardHarness.js"; +import { ComplexKeyboardStore, type KeyboardStore, KeyboardStoreElement, SystemStoreIDs, SystemStore, MutableSystemStore, PlatformSystemStore, VariableStore, VariableStoreSerializer } from "./systemStores.js"; //#endregion @@ -30,20 +24,6 @@ export class KeyInformation { modifiers: number; } -/* -* Type alias definitions to reflect the parameters of the fullContextMatch() callback (KMW 10+). -* No constructors or methods since keyboards will not utilize the same backing prototype, and -* property names are shorthanded to promote minification. -*/ -type PlainKeyboardStore = string; - -export type KeyboardStoreElement = (string|StoreNonCharEntry); -export type ComplexKeyboardStore = KeyboardStoreElement[]; - -type KeyboardStore = PlainKeyboardStore | ComplexKeyboardStore; - -export type VariableStore = {[name: string]: string}; - type RuleChar = string; class RuleDeadkey { @@ -70,7 +50,7 @@ class ContextAny { /** * If set to true, negates the 'any'. */ - ['n']: boolean|0|1; + ['n']: boolean | 0 | 1; } class RuleIndex { @@ -115,7 +95,7 @@ class StoreBeep { type ContextNonCharEntry = RuleDeadkey | ContextAny | RuleIndex | ContextEx | ContextNul; type ContextEntry = RuleChar | ContextNonCharEntry; -type StoreNonCharEntry = RuleDeadkey | StoreBeep; +export type StoreNonCharEntry = RuleDeadkey | StoreBeep; /** * Cache of context storing and retrieving return values from KC @@ -183,13 +163,6 @@ class CachedContextEx { } }; -export enum SystemStoreIDs { - TSS_LAYER = 33, - TSS_PLATFORM = 31, - TSS_NEWLAYER = 42, - TSS_OLDLAYER = 43 -} - //#endregion export default class KeyboardInterface extends KeyboardHarness { @@ -1177,6 +1150,6 @@ export default class KeyboardInterface extends KeyboardHarness { } (function() { - // This will be the only call within the keyboard-processor module. + // This will be the only call within the keyboard module. KeyboardInterface.__publishShorthandAPI(); }()); diff --git a/common/web/keyboard-processor/src/text/keyboardProcessor.ts b/web/src/engine/js-processor/src/keyboardProcessor.ts similarity index 95% rename from common/web/keyboard-processor/src/text/keyboardProcessor.ts rename to web/src/engine/js-processor/src/keyboardProcessor.ts index 836c9912a6..dea537dfdf 100644 --- a/common/web/keyboard-processor/src/text/keyboardProcessor.ts +++ b/web/src/engine/js-processor/src/keyboardProcessor.ts @@ -1,23 +1,23 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Implementation of the JavaScript keyboard processor + */ + // #region Big ol' list of imports import { EventEmitter } from 'eventemitter3'; - -import Codes from "./codes.js"; -import type Keyboard from "../keyboards/keyboard.js"; -import { MinimalKeymanGlobal } from '../keyboards/keyboardHarness.js'; -import KeyEvent from "./keyEvent.js"; -import { Layouts } from "../keyboards/defaultLayouts.js"; -import type { MutableSystemStore } from "./systemStores.js"; - -import DefaultRules, { EmulationKeystrokes } from "./defaultRules.js"; -import type OutputTarget from "./outputTarget.js"; -import { Mock } from "./outputTarget.js"; - -import KeyboardInterface, { SystemStoreIDs, VariableStore } from "./kbdInterface.js"; -import RuleBehavior from "./ruleBehavior.js"; - -import { DeviceSpec, globalObject } from "@keymanapp/web-utils"; import { ModifierKeyConstants } from '@keymanapp/common-types'; +import { + Codes, type Keyboard, MinimalKeymanGlobal, KeyEvent, Layouts, + DefaultRules, EmulationKeystrokes +} from "keyman/engine/keyboard"; +import { Mock } from "./mock.js"; +import type OutputTarget from "./outputTarget.js"; +import RuleBehavior from "./ruleBehavior.js"; +import KeyboardInterface from './kbdInterface.js'; +import { DeviceSpec, globalObject } from "@keymanapp/web-utils"; +import { type MutableSystemStore, SystemStoreIDs } from "./systemStores.js"; // #endregion @@ -26,11 +26,6 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; export type BeepHandler = (outputTarget: OutputTarget) => void; export type LogMessageHandler = (str: string) => void; -export interface VariableStoreSerializer { - loadStore(keyboardID: string, storeName: string): VariableStore; - saveStore(keyboardID: string, storeName: string, storeMap: VariableStore): void; -} - export interface ProcessorInitOptions { baseLayout?: string; keyboardInterface?: KeyboardInterface; @@ -174,7 +169,7 @@ export default class KeyboardProcessor extends EventEmitter { let isMnemonic = this.activeKeyboard && this.activeKeyboard.isMnemonic; if(!matched) { - if((char = this.defaultRules.forAny(Lkc, isMnemonic)) != null) { + if((char = this.defaultRules.forAny(Lkc, isMnemonic, ruleBehavior)) != null) { special = this.defaultRules.forSpecialEmulation(Lkc) if(special == EmulationKeystrokes.Backspace) { // A browser's default backspace may fail to delete both parts of an SMP character. @@ -277,7 +272,8 @@ export default class KeyboardProcessor extends EventEmitter { const lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK'] as const; const lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL'] as const; - const lockModifiers = [ ModifierKeyConstants.CAPITALFLAG, ModifierKeyConstants.NUMLOCKFLAG, ModifierKeyConstants.SCROLLFLAG] as const; + const lockModifiers = [ModifierKeyConstants.CAPITALFLAG, ModifierKeyConstants.NUMLOCKFLAG, ModifierKeyConstants.SCROLLFLAG] as const; + if(!this.activeKeyboard) { return true; @@ -329,6 +325,8 @@ export default class KeyboardProcessor extends EventEmitter { const lockModifiers = [ModifierKeyConstants.CAPITALFLAG, ModifierKeyConstants.NUMLOCKFLAG, ModifierKeyConstants.SCROLLFLAG] as const; const noLockModifers = [ModifierKeyConstants.NOTCAPITALFLAG, ModifierKeyConstants.NOTNUMLOCKFLAG, ModifierKeyConstants.NOTSCROLLFLAG] as const; + + for(let i=0; i < lockKeys.length; i++) { const key = lockKeys[i]; const flag = this.stateKeys[key]; diff --git a/web/src/engine/js-processor/src/mock.ts b/web/src/engine/js-processor/src/mock.ts new file mode 100644 index 0000000000..151d8313ca --- /dev/null +++ b/web/src/engine/js-processor/src/mock.ts @@ -0,0 +1,157 @@ +import OutputTarget from './outputTarget.js'; + +// Due to some interesting requirements on compile ordering in TS, +// this needs to be in the same file as OutputTarget now. +export class Mock extends OutputTarget { + text: string; + + selStart: number; + selEnd: number; + selForward: boolean = true; + + constructor(text?: string, caretPos?: number); + constructor(text?: string, selStart?: number, selEnd?: number); + constructor(text?: string, selStart?: number, selEnd?: number) { + super(); + + this.text = text ? text : ""; + var defaultLength = this.text._kmwLength(); + + // Ensures that `caretPos == 0` is handled correctly. + this.selStart = typeof selStart == "number" ? selStart : defaultLength; + + // If no selection-end is set, selection length is implied to be 0. + this.selEnd = typeof selEnd == "number" ? selEnd : this.selStart; + + this.selForward = this.selEnd >= this.selStart; + } + + // Clones the state of an existing EditableElement, creating a Mock version of its state. + static from(outputTarget: OutputTarget, readonly?: boolean) { + let clone: Mock; + + if (outputTarget instanceof Mock) { + // Avoids the need to run expensive kmwstring.ts / `_kmwLength()` + // calculations when deep-copying Mock instances. + let priorMock = outputTarget as Mock; + clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); + } else { + const text = outputTarget.getText(); + const textLen = text._kmwLength(); + + // If !hasSelection() + let selectionStart: number = textLen; + let selectionEnd: number = 0; + + if (outputTarget.hasSelection()) { + let beforeText = outputTarget.getTextBeforeCaret(); + let afterText = outputTarget.getTextAfterCaret(); + selectionStart = beforeText._kmwLength(); + selectionEnd = textLen - afterText._kmwLength(); + } + + // readonly group or not, the returned Mock remains the same. + // New-context events should act as if the caret were at the earlier-in-context + // side of the selection, same as standard keyboard rules. + clone = new Mock(text, selectionStart, selectionEnd); + } + + // Also duplicate deadkey state! (Needed for fat-finger ops.) + clone.setDeadkeys(outputTarget.deadkeys()); + + return clone; + } + + clearSelection(): void { + this.text = this.getTextBeforeCaret() + this.getTextAfterCaret(); + this.selEnd = this.selStart; + this.selForward = true; + } + + invalidateSelection(): void { + return; + } + + isSelectionEmpty(): boolean { + return this.selStart == this.selEnd; + } + + hasSelection(): boolean { + return true; + } + + getDeadkeyCaret(): number { + return this.selStart; + } + + setSelection(start: number, end?: number) { + this.selStart = start; + this.selEnd = typeof end == 'number' ? end : start; + + this.selForward = end >= start; + if (!this.selForward) { + let temp = this.selStart; + this.selStart = this.selEnd; + this.selEnd = temp; + } + } + + getTextBeforeCaret(): string { + return this.text.kmwSubstr(0, this.selStart); + } + + getSelectedText(): string { + return this.text.kmwSubstr(this.selStart, this.selEnd - this.selStart); + } + + getTextAfterCaret(): string { + return this.text.kmwSubstr(this.selEnd); + } + + getText(): string { + return this.text; + } + + deleteCharsBeforeCaret(dn: number): void { + if (dn >= 0) { + if (dn > this.selStart) { + dn = this.selStart; + } + this.adjustDeadkeys(-dn); + this.text = this.text.kmwSubstr(0, this.selStart - dn) + this.text.kmwSubstr(this.selStart); + this.selStart -= dn; + this.selEnd -= dn; + } + } + + insertTextBeforeCaret(s: string): void { + this.adjustDeadkeys(s._kmwLength()); + this.text = this.getTextBeforeCaret() + s + this.text.kmwSubstr(this.selStart); + this.selStart += s.kmwLength(); + this.selEnd += s.kmwLength(); + } + + handleNewlineAtCaret(): void { + this.insertTextBeforeCaret('\n'); + } + + protected setTextAfterCaret(s: string): void { + this.text = this.getTextBeforeCaret() + s; + } + + /** + * Indicates if this Mock represents an identical context to that of another Mock. + * @param other + * @returns + */ + isEqual(other: Mock) { + return this.text == other.text + && this.selStart == other.selStart + && this.selEnd == other.selEnd + && this.deadkeys().equal(other.deadkeys()); + } + + doInputEvent() { + // Mock isn't backed by an element, so it won't have any event listeners. + } +} diff --git a/common/web/keyboard-processor/src/text/outputTarget.ts b/web/src/engine/js-processor/src/outputTarget.ts similarity index 69% rename from common/web/keyboard-processor/src/text/outputTarget.ts rename to web/src/engine/js-processor/src/outputTarget.ts index 63c4c8a042..f48647092c 100644 --- a/common/web/keyboard-processor/src/text/outputTarget.ts +++ b/web/src/engine/js-processor/src/outputTarget.ts @@ -2,11 +2,12 @@ import { extendString } from "@keymanapp/web-utils"; import { findCommonSubstringEndIndex } from "./stringDivergence.js"; +import { Mock } from "./mock.js"; extendString(); // Defines deadkey management in a manner attachable to each element interface. -import type KeyEvent from "./keyEvent.js"; +import { type KeyEvent } from 'keyman/engine/keyboard'; import { Deadkey, DeadkeyTracker } from "./deadkeys.js"; // Also relies on string-extensions provided by the web-utils package. @@ -307,159 +308,3 @@ export default abstract class OutputTarget { */ abstract doInputEvent(): void; } - -// Due to some interesting requirements on compile ordering in TS, -// this needs to be in the same file as OutputTarget now. -export class Mock extends OutputTarget { - text: string; - - selStart: number; - selEnd: number; - selForward: boolean = true; - - constructor(text?: string, caretPos?: number); - constructor(text?: string, selStart?: number, selEnd?: number); - constructor(text?: string, selStart?: number, selEnd?: number) { - super(); - - this.text = text ? text : ""; - var defaultLength = this.text._kmwLength(); - - // Ensures that `caretPos == 0` is handled correctly. - this.selStart = typeof selStart == "number" ? selStart : defaultLength; - - // If no selection-end is set, selection length is implied to be 0. - this.selEnd = typeof selEnd == "number" ? selEnd : this.selStart; - - this.selForward = this.selEnd >= this.selStart; - } - - // Clones the state of an existing EditableElement, creating a Mock version of its state. - static from(outputTarget: OutputTarget, readonly?: boolean) { - let clone: Mock; - - if(outputTarget instanceof Mock) { - // Avoids the need to run expensive kmwstring.ts / `_kmwLength()` - // calculations when deep-copying Mock instances. - let priorMock = outputTarget as Mock; - clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); - } else { - const text = outputTarget.getText(); - const textLen = text._kmwLength(); - - // If !hasSelection() - let selectionStart: number = textLen; - let selectionEnd: number = 0; - - if(outputTarget.hasSelection()) { - let beforeText = outputTarget.getTextBeforeCaret(); - let afterText = outputTarget.getTextAfterCaret(); - selectionStart = beforeText._kmwLength(); - selectionEnd = textLen - afterText._kmwLength(); - } - - // readonly group or not, the returned Mock remains the same. - // New-context events should act as if the caret were at the earlier-in-context - // side of the selection, same as standard keyboard rules. - clone = new Mock(text, selectionStart, selectionEnd); - } - - // Also duplicate deadkey state! (Needed for fat-finger ops.) - clone.setDeadkeys(outputTarget.deadkeys()); - - return clone; - } - - clearSelection(): void { - this.text = this.getTextBeforeCaret() + this.getTextAfterCaret(); - this.selEnd = this.selStart; - this.selForward = true; - } - - invalidateSelection(): void { - return; - } - - isSelectionEmpty(): boolean { - return this.selStart == this.selEnd; - } - - hasSelection(): boolean { - return true; - } - - getDeadkeyCaret(): number { - return this.selStart; - } - - setSelection(start: number, end?: number) { - this.selStart = start; - this.selEnd = typeof end == 'number' ? end : start; - - this.selForward = end >= start; - if(!this.selForward) { - let temp = this.selStart; - this.selStart = this.selEnd; - this.selEnd = temp; - } - } - - getTextBeforeCaret(): string { - return this.text.kmwSubstr(0, this.selStart); - } - - getSelectedText(): string { - return this.text.kmwSubstr(this.selStart, this.selEnd - this.selStart); - } - - getTextAfterCaret(): string { - return this.text.kmwSubstr(this.selEnd); - } - - getText(): string { - return this.text; - } - - deleteCharsBeforeCaret(dn: number): void { - if(dn >= 0) { - if(dn > this.selStart) { - dn = this.selStart; - } - this.adjustDeadkeys(-dn); - this.text = this.text.kmwSubstr(0, this.selStart - dn) + this.text.kmwSubstr(this.selStart); - this.selStart -= dn; - this.selEnd -= dn; - } - } - - insertTextBeforeCaret(s: string): void { - this.adjustDeadkeys(s._kmwLength()); - this.text = this.getTextBeforeCaret() + s + this.text.kmwSubstr(this.selStart); - this.selStart += s.kmwLength(); - this.selEnd += s.kmwLength(); - } - - handleNewlineAtCaret(): void { - this.insertTextBeforeCaret('\n'); - } - - protected setTextAfterCaret(s: string): void { - this.text = this.getTextBeforeCaret() + s; - } - - /** - * Indicates if this Mock represents an identical context to that of another Mock. - * @param other - * @returns - */ - isEqual(other: Mock) { - return this.text == other.text - && this.selStart == other.selStart - && this.selEnd == other.selEnd - && this.deadkeys().equal(other.deadkeys()); - } - - doInputEvent() { - // Mock isn't backed by an element, so it won't have any event listeners. - } -} \ No newline at end of file diff --git a/common/web/keyboard-processor/src/text/ruleBehavior.ts b/web/src/engine/js-processor/src/ruleBehavior.ts similarity index 95% rename from common/web/keyboard-processor/src/text/ruleBehavior.ts rename to web/src/engine/js-processor/src/ruleBehavior.ts index e5d669e909..a6b2c9973a 100644 --- a/common/web/keyboard-processor/src/text/ruleBehavior.ts +++ b/web/src/engine/js-processor/src/ruleBehavior.ts @@ -1,9 +1,10 @@ /// import KeyboardProcessor from "./keyboardProcessor.js"; -import OutputTarget, { Mock, type Transcription } from "./outputTarget.js"; -import { VariableStoreDictionary } from "../keyboards/keyboard.js"; -import type { VariableStore } from "./kbdInterface.js"; +import { VariableStoreDictionary } from "keyman/engine/keyboard"; +import OutputTarget, { type Transcription } from './outputTarget.js'; +import { Mock } from "./mock.js"; +import { type VariableStore } from "./systemStores.js"; /** * Represents the commands and state changes that result from a matched keyboard rule. diff --git a/common/web/keyboard-processor/src/text/stringDivergence.ts b/web/src/engine/js-processor/src/stringDivergence.ts similarity index 100% rename from common/web/keyboard-processor/src/text/stringDivergence.ts rename to web/src/engine/js-processor/src/stringDivergence.ts diff --git a/common/web/keyboard-processor/src/text/systemStores.ts b/web/src/engine/js-processor/src/systemStores.ts similarity index 74% rename from common/web/keyboard-processor/src/text/systemStores.ts rename to web/src/engine/js-processor/src/systemStores.ts index 13184baafe..3288c9feff 100644 --- a/common/web/keyboard-processor/src/text/systemStores.ts +++ b/web/src/engine/js-processor/src/systemStores.ts @@ -1,5 +1,31 @@ -import type KeyboardInterface from "./kbdInterface.js"; -import { SystemStoreIDs } from "./kbdInterface.js"; +import { type KeyboardHarness } from 'keyman/engine/keyboard'; +import { StoreNonCharEntry } from './kbdInterface.js'; + +export enum SystemStoreIDs { + TSS_LAYER = 33, + TSS_PLATFORM = 31, + TSS_NEWLAYER = 42, + TSS_OLDLAYER = 43 +} + +/* +* Type alias definitions to reflect the parameters of the fullContextMatch() callback (KMW 10+). +* No constructors or methods since keyboards will not utilize the same backing prototype, and +* property names are shorthanded to promote minification. +*/ +type PlainKeyboardStore = string; + +export type KeyboardStoreElement = (string | StoreNonCharEntry); +export type ComplexKeyboardStore = KeyboardStoreElement[]; + +export type KeyboardStore = PlainKeyboardStore | ComplexKeyboardStore; + +export type VariableStore = { [name: string]: string }; + +export interface VariableStoreSerializer { + loadStore(keyboardID: string, storeName: string): VariableStore; + saveStore(keyboardID: string, storeName: string, storeMap: VariableStore): void; +} /** * Defines common behaviors associated with system stores. @@ -61,9 +87,9 @@ export class MutableSystemStore extends SystemStore { * Handles checks against the current platform. */ export class PlatformSystemStore extends SystemStore { - private readonly kbdInterface: KeyboardInterface; + private readonly kbdInterface: KeyboardHarness; - constructor(keyboardInterface: KeyboardInterface) { + constructor(keyboardInterface: KeyboardHarness) { super(SystemStoreIDs.TSS_PLATFORM); this.kbdInterface = keyboardInterface; @@ -131,4 +157,4 @@ export class PlatformSystemStore extends SystemStore { // Everything we checked against was valid and had matches - it's a match! return true; } -} \ No newline at end of file +} diff --git a/web/src/engine/js-processor/tsconfig.json b/web/src/engine/js-processor/tsconfig.json new file mode 100644 index 0000000000..a57467139b --- /dev/null +++ b/web/src/engine/js-processor/tsconfig.json @@ -0,0 +1,13 @@ +{ + // While the actual references themselves are headless, it compiles against the DOM-reliant OSK module. + "extends": "../../tsconfig.dom.json", + + "compilerOptions": { + "baseUrl": "./", + "outDir": "../../../build/engine/js-processor/obj/", + "tsBuildInfoFile": "../../../build/engine/js-processor/obj/tsconfig.tsbuildinfo", + "rootDir": "./src" + }, + + "include": [ "**/*.ts" ], +} diff --git a/web/src/engine/package-cache/.c8rc.json b/web/src/engine/keyboard-storage/.c8rc.json similarity index 100% rename from web/src/engine/package-cache/.c8rc.json rename to web/src/engine/keyboard-storage/.c8rc.json diff --git a/web/src/engine/package-cache/README.md b/web/src/engine/keyboard-storage/README.md similarity index 100% rename from web/src/engine/package-cache/README.md rename to web/src/engine/keyboard-storage/README.md diff --git a/web/src/engine/package-cache/build.sh b/web/src/engine/keyboard-storage/build.sh similarity index 97% rename from web/src/engine/package-cache/build.sh rename to web/src/engine/keyboard-storage/build.sh index b413dd5be8..33873355ff 100755 --- a/web/src/engine/package-cache/build.sh +++ b/web/src/engine/keyboard-storage/build.sh @@ -6,7 +6,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE -SUBPROJECT_NAME=engine/package-cache +SUBPROJECT_NAME=engine/keyboard-storage . "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" diff --git a/web/src/engine/package-cache/src/cloud/index.ts b/web/src/engine/keyboard-storage/src/cloud/index.ts similarity index 100% rename from web/src/engine/package-cache/src/cloud/index.ts rename to web/src/engine/keyboard-storage/src/cloud/index.ts diff --git a/web/src/engine/package-cache/src/cloud/queryEngine.ts b/web/src/engine/keyboard-storage/src/cloud/queryEngine.ts similarity index 99% rename from web/src/engine/package-cache/src/cloud/queryEngine.ts rename to web/src/engine/keyboard-storage/src/cloud/queryEngine.ts index eb989d2f00..5f38c32aea 100644 --- a/web/src/engine/package-cache/src/cloud/queryEngine.ts +++ b/web/src/engine/keyboard-storage/src/cloud/queryEngine.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'eventemitter3'; import { PathConfiguration } from 'keyman/engine/interfaces'; import { default as KeyboardStub, ErrorStub, KeyboardAPISpec, mergeAndResolveStubPromises } from '../keyboardStub.js'; -import { LanguageAPIPropertySpec, ManagedPromise, Version } from '@keymanapp/keyboard-processor'; +import { LanguageAPIPropertySpec, ManagedPromise, Version } from 'keyman/engine/keyboard'; import CloudRequesterInterface from './requesterInterface.js'; // For when the API call straight-up times out. diff --git a/web/src/engine/package-cache/src/cloud/requesterInterface.ts b/web/src/engine/keyboard-storage/src/cloud/requesterInterface.ts similarity index 68% rename from web/src/engine/package-cache/src/cloud/requesterInterface.ts rename to web/src/engine/keyboard-storage/src/cloud/requesterInterface.ts index c0f8e6baf1..6c612d9cb2 100644 --- a/web/src/engine/package-cache/src/cloud/requesterInterface.ts +++ b/web/src/engine/keyboard-storage/src/cloud/requesterInterface.ts @@ -1,4 +1,4 @@ -import { ManagedPromise } from '@keymanapp/keyboard-processor'; +import { ManagedPromise } from 'keyman/engine/keyboard'; export default interface CloudRequesterInterface { request(query: string): { diff --git a/web/src/engine/package-cache/src/domCloudRequester.ts b/web/src/engine/keyboard-storage/src/domCloudRequester.ts similarity index 97% rename from web/src/engine/package-cache/src/domCloudRequester.ts rename to web/src/engine/keyboard-storage/src/domCloudRequester.ts index ee80b0f063..fe671d101f 100644 --- a/web/src/engine/package-cache/src/domCloudRequester.ts +++ b/web/src/engine/keyboard-storage/src/domCloudRequester.ts @@ -1,4 +1,4 @@ -import { ManagedPromise } from '@keymanapp/keyboard-processor'; +import { ManagedPromise } from 'keyman/engine/keyboard'; import CloudRequesterInterface from './cloud/requesterInterface.js'; import { CLOUD_MALFORMED_OBJECT_ERR, CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/queryEngine.js'; diff --git a/web/src/engine/package-cache/src/index.ts b/web/src/engine/keyboard-storage/src/index.ts similarity index 100% rename from web/src/engine/package-cache/src/index.ts rename to web/src/engine/keyboard-storage/src/index.ts diff --git a/web/src/engine/package-cache/src/keyboardRequisitioner.ts b/web/src/engine/keyboard-storage/src/keyboardRequisitioner.ts similarity index 99% rename from web/src/engine/package-cache/src/keyboardRequisitioner.ts rename to web/src/engine/keyboard-storage/src/keyboardRequisitioner.ts index 13cc7ecef8..fb2807de6f 100644 --- a/web/src/engine/package-cache/src/keyboardRequisitioner.ts +++ b/web/src/engine/keyboard-storage/src/keyboardRequisitioner.ts @@ -3,7 +3,7 @@ import { KeyboardLoaderBase as KeyboardLoader, LanguageAPIPropertySpec, RawKeyboardMetadata -} from "@keymanapp/keyboard-processor"; +} from "keyman/engine/keyboard"; import { PathConfiguration } from "keyman/engine/interfaces"; // TODO: is cleanup needed here, to use local paths instead? diff --git a/web/src/engine/package-cache/src/keyboardStub.ts b/web/src/engine/keyboard-storage/src/keyboardStub.ts similarity index 99% rename from web/src/engine/package-cache/src/keyboardStub.ts rename to web/src/engine/keyboard-storage/src/keyboardStub.ts index 474d4cc9ef..a2b1d87d6f 100644 --- a/web/src/engine/package-cache/src/keyboardStub.ts +++ b/web/src/engine/keyboard-storage/src/keyboardStub.ts @@ -3,7 +3,7 @@ import { type KeyboardAPIPropertyMultilangSpec as APICompoundKeyboard, KeyboardProperties, type LanguageAPIPropertySpec, -} from '@keymanapp/keyboard-processor'; +} from 'keyman/engine/keyboard'; import { toPrefixedKeyboardId as prefixed } from './stubAndKeyboardCache.js'; diff --git a/web/src/engine/package-cache/src/modelCache.ts b/web/src/engine/keyboard-storage/src/modelCache.ts similarity index 100% rename from web/src/engine/package-cache/src/modelCache.ts rename to web/src/engine/keyboard-storage/src/modelCache.ts diff --git a/web/src/engine/package-cache/src/nodeCloudRequester.ts b/web/src/engine/keyboard-storage/src/nodeCloudRequester.ts similarity index 97% rename from web/src/engine/package-cache/src/nodeCloudRequester.ts rename to web/src/engine/keyboard-storage/src/nodeCloudRequester.ts index bd9ff85159..e8461ff20d 100644 --- a/web/src/engine/package-cache/src/nodeCloudRequester.ts +++ b/web/src/engine/keyboard-storage/src/nodeCloudRequester.ts @@ -1,4 +1,4 @@ -import { ManagedPromise } from '@keymanapp/keyboard-processor'; +import { ManagedPromise } from 'keyman/engine/keyboard'; import CloudRequesterInterface from './cloud/requesterInterface.js'; import { CLOUD_TIMEOUT_ERR, diff --git a/web/src/engine/package-cache/src/stubAndKeyboardCache.ts b/web/src/engine/keyboard-storage/src/stubAndKeyboardCache.ts similarity index 99% rename from web/src/engine/package-cache/src/stubAndKeyboardCache.ts rename to web/src/engine/keyboard-storage/src/stubAndKeyboardCache.ts index 9fd70e7472..67d62446be 100644 --- a/web/src/engine/package-cache/src/stubAndKeyboardCache.ts +++ b/web/src/engine/keyboard-storage/src/stubAndKeyboardCache.ts @@ -1,4 +1,4 @@ -import { Keyboard, KeyboardLoaderBase as KeyboardLoader } from "@keymanapp/keyboard-processor"; +import { Keyboard, KeyboardLoaderBase as KeyboardLoader } from "keyman/engine/keyboard"; import { EventEmitter } from "eventemitter3"; import KeyboardStub from "./keyboardStub.js"; diff --git a/web/src/engine/package-cache/tsconfig.json b/web/src/engine/keyboard-storage/tsconfig.json similarity index 57% rename from web/src/engine/package-cache/tsconfig.json rename to web/src/engine/keyboard-storage/tsconfig.json index 7a2ab5f235..f093be2cbc 100644 --- a/web/src/engine/package-cache/tsconfig.json +++ b/web/src/engine/keyboard-storage/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "baseUrl": "./", - "outDir": "../../../build/engine/package-cache/obj/", - "tsBuildInfoFile": "../../../build/engine/package-cache/obj/tsconfig.tsbuildinfo", + "outDir": "../../../build/engine/keyboard-storage/obj/", + "tsBuildInfoFile": "../../../build/engine/keyboard-storage/obj/tsconfig.tsbuildinfo", "rootDir": "./src" }, diff --git a/common/web/keyboard-processor/.c8rc.json b/web/src/engine/keyboard/.c8rc.json similarity index 100% rename from common/web/keyboard-processor/.c8rc.json rename to web/src/engine/keyboard/.c8rc.json diff --git a/common/web/keyboard-processor/.gitignore b/web/src/engine/keyboard/.gitignore similarity index 100% rename from common/web/keyboard-processor/.gitignore rename to web/src/engine/keyboard/.gitignore diff --git a/common/web/keyboard-processor/README.md b/web/src/engine/keyboard/README.md similarity index 92% rename from common/web/keyboard-processor/README.md rename to web/src/engine/keyboard/README.md index 8ac9b8f9d0..a2a4eb8994 100644 --- a/common/web/keyboard-processor/README.md +++ b/web/src/engine/keyboard/README.md @@ -1,5 +1,6 @@ # Keyman: Keyboard Processor Module -The Original Code is (C) 2017-2020 SIL International + +The Original Code is (C) 2017-2024 SIL International The Keyboard Processor module is an internal component of KeymanWeb, seen within this repo at /web/. diff --git a/web/src/engine/keyboard/build.sh b/web/src/engine/keyboard/build.sh new file mode 100755 index 0000000000..226a7d9d75 --- /dev/null +++ b/web/src/engine/keyboard/build.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# +# Compile KeymanWeb's 'keyboard' module, one of the components of Web's 'core' module. +# +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +SUBPROJECT_NAME=engine/keyboard + +. "${KEYMAN_ROOT}/web/common.inc.sh" +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" + +################################ Main script ################################ + +builder_describe \ + "Compiles the web-oriented utility function module." \ + "@/web/src/tools/testing/recorder-core test" \ + "@/common/web/keyman-version" \ + "@/common/web/es-bundling" \ + "@/common/web/types" \ + "@/common/web/utils" \ + configure \ + clean \ + build \ + test \ + "--ci For use with action $(builder_term test) - emits CI-friendly test reports" + +builder_describe_outputs \ + configure /node_modules \ + build /web/build/engine/keyboard/lib/index.mjs + +builder_parse "$@" + +function do_configure() { + verify_npm_setup + + # Configure Web browser-engine testing environments. As is, this should only + # make changes when we update the dependency, even on our CI build agents. + playwright install +} + +BUILD_DIR="${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}" + +function do_build() { + tsc --build "${THIS_SCRIPT_PATH}/tsconfig.all.json" + + # Base product - the main keyboard processor + builder_echo "Bundle base product - the main keyboard processor" + ${BUNDLE_CMD} "${BUILD_DIR}/obj/index.js" \ + --out "${BUILD_DIR}/lib/index.mjs" \ + --format esm + + # The DOM-oriented keyboard loader + builder_echo "Bundle the DOM-oriented keyboard loader" + ${BUNDLE_CMD} "${BUILD_DIR}/obj/keyboards/loaders/dom-keyboard-loader.js" \ + --out "${BUILD_DIR}/lib/dom-keyboard-loader.mjs" \ + --format esm + + # The Node-oriented keyboard loader + builder_echo "Bundle the Node-oriented keyboard loader" + ${BUNDLE_CMD} "${BUILD_DIR}/obj/keyboards/loaders/node-keyboard-loader.js" \ + --out "${BUILD_DIR}/lib/node-keyboard-loader.mjs" \ + --format esm \ + --platform node + + # # Tests + # builder_echo "Bundle tests" + # ${BUNDLE_CMD} "${BUILD_DIR}/tests/dom/cases/domKeyboardLoader.spec.js" \ + # --out "${BUILD_DIR}/tests/dom/domKeyboardLoader.spec.mjs" \ + # --format esm + + # Declaration bundling. + builder_echo "Declaration bundling" + tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/lib/index.d.ts" + tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/lib/dom-keyboard-loader.d.ts" -p src/keyboards/loaders/tsconfig.dom.json + tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/lib/node-keyboard-loader.d.ts" -p src/keyboards/loaders/tsconfig.node.json +} + +builder_run_action configure do_configure +builder_run_action clean rm -rf "${BUILD_DIR}" +builder_run_action build do_build +builder_run_action test test-headless "${SUBPROJECT_NAME}" "" diff --git a/common/web/keyboard-processor/src/text/codes.ts b/web/src/engine/keyboard/src/codes.ts similarity index 100% rename from common/web/keyboard-processor/src/text/codes.ts rename to web/src/engine/keyboard/src/codes.ts diff --git a/common/web/keyboard-processor/src/text/defaultRules.ts b/web/src/engine/keyboard/src/defaultRules.ts similarity index 81% rename from common/web/keyboard-processor/src/text/defaultRules.ts rename to web/src/engine/keyboard/src/defaultRules.ts index 931eb5200b..5512114d93 100644 --- a/common/web/keyboard-processor/src/text/defaultRules.ts +++ b/web/src/engine/keyboard/src/defaultRules.ts @@ -1,32 +1,28 @@ -// TODO: Move to separate folder: 'codes' -// We should start splitting off code needed by keyboards even without a KeyboardProcessor active. -// There's an upcoming `/common/web/types` package that 'codes' and 'keyboards' may fit well within. +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Implementation of default rules + */ -import { ModifierKeyConstants} from '@keymanapp/common-types'; -import Codes from "./codes.js"; -import type KeyEvent from "./keyEvent.js"; -import type OutputTarget from "./outputTarget.js"; - -// The only members referenced are to produce warning and error logs. A little abstraction -// via an optional 'logger' interface can maintain it while facilitating a the split alluded -// to above. -// -// Alternatively, we could just... not take in the parameter at all, which'd also facilitate -// the future modularization effort. -import RuleBehavior from "./ruleBehavior.js"; +import { ModifierKeyConstants } from '@keymanapp/common-types'; +import Codes from './codes.js'; +import type KeyEvent from './keyEvent.js'; +import { type OutputTarget } from './outputTarget.interface.js'; export enum EmulationKeystrokes { Enter = '\n', Backspace = '\b' } +export class LogMessages { + errorLog?: string; + warningLog?: string; +} + /** * Defines a collection of static library functions that define KeymanWeb's default (implied) keyboard rule behaviors. */ export default class DefaultRules { - public constructor() { - } - codeForEvent(Lkc: KeyEvent) { return Codes.keyCodes[Lkc.kName] || Lkc.Lcode;; } @@ -35,7 +31,7 @@ export default class DefaultRules { * Serves as a default keycode lookup table. This may be referenced safely by mnemonic handling without fear of side-effects. * Also used by Processor.defaultRuleBehavior to generate output after filtering for special cases. */ - public forAny(Lkc: KeyEvent, isMnemonic: boolean, ruleBehavior?: RuleBehavior) { + public forAny(Lkc: KeyEvent, isMnemonic: boolean, logMessages?: LogMessages): string { var char = ''; // A pretty simple table of lookups, corresponding VERY closely to the original defaultKeyOutput. @@ -43,9 +39,9 @@ export default class DefaultRules { return char; } else if(!isMnemonic && ((char = this.forNumpadKeys(Lkc)) != null)) { return char; - } else if((char = this.forUnicodeKeynames(Lkc, ruleBehavior)) != null) { + } else if((char = this.forUnicodeKeynames(Lkc, logMessages)) != null) { return char; - } else if((char = this.forBaseKeys(Lkc, ruleBehavior)) != null) { + } else if((char = this.forBaseKeys(Lkc, logMessages)) != null) { return char; } else { // // For headless and embeddded, we may well allow '\t'. It's DOM mode that has other uses. @@ -152,7 +148,7 @@ export default class DefaultRules { // Test for fall back to U_xxxxxx key id // For this first test, we ignore the keyCode and use the keyName - public forUnicodeKeynames(Lkc: KeyEvent, ruleBehavior?: RuleBehavior) { + public forUnicodeKeynames(Lkc: KeyEvent, logMessages?: LogMessages) { const keyName = Lkc.kName; // Test for fall back to U_xxxxxx key id @@ -169,8 +165,8 @@ export default class DefaultRules { // Code points [U_0000 - U_001F] and [U_0080 - U_009F] refer to Unicode C0 and C1 control codes. // Check the codePoint number and do not allow output of these codes via U_xxxxxx shortcuts. // Also handles invalid identifiers (e.g. `U_ghij`) for which parseInt returns NaN - if(ruleBehavior) { - ruleBehavior.errorLog = ("Suppressing Unicode control code in " + keyName); + if(logMessages) { + logMessages.errorLog = ("Suppressing Unicode control code in " + keyName); } // We'll attempt to add valid chars continue; @@ -185,17 +181,17 @@ export default class DefaultRules { // Test for otherwise unimplemented keys on the the base default & shift layers. // Those keys must be blocked by keyboard rules if intentionally unimplemented; otherwise, this function will trigger. - public forBaseKeys(Lkc: KeyEvent, ruleBehavior?: RuleBehavior) { + public forBaseKeys(Lkc: KeyEvent, logMessages?: LogMessages) { let n = Lkc.Lcode; let keyShiftState = Lkc.Lmodifiers; // check if exact match to SHIFT's code. Only the 'default' and 'shift' layers should have default key outputs. // TODO: Extend to allow AltGr as well - better mnemonic support. - if(keyShiftState == ModifierKeyConstants.K_SHIFTFLAG) { + if (keyShiftState == ModifierKeyConstants.K_SHIFTFLAG) { keyShiftState = 1; } else if(keyShiftState != 0) { - if(ruleBehavior) { - ruleBehavior.warningLog = "KMW only defines default key output for the 'default' and 'shift' layers!"; + if(logMessages) { + logMessages.warningLog = "KMW only defines default key output for the 'default' and 'shift' layers!"; } return null; } @@ -216,8 +212,8 @@ export default class DefaultRules { return keyShiftState ? '|' : '\\'; } } catch (e) { - if(ruleBehavior) { - ruleBehavior.errorLog = "Error detected with default mapping for key: code = " + n + ", shift state = " + (keyShiftState == 1 ? 'shift' : 'default'); + if(logMessages) { + logMessages.errorLog = "Error detected with default mapping for key: code = " + n + ", shift state = " + (keyShiftState == 1 ? 'shift' : 'default'); } } diff --git a/common/web/keyboard-processor/src/index.ts b/web/src/engine/keyboard/src/index.ts similarity index 57% rename from common/web/keyboard-processor/src/index.ts rename to web/src/engine/keyboard/src/index.ts index f800691cfa..f5a4edea71 100644 --- a/common/web/keyboard-processor/src/index.ts +++ b/web/src/engine/keyboard/src/index.ts @@ -24,23 +24,13 @@ export { export { default as SpacebarText } from "./keyboards/spacebarText.js"; export { default as StateKeyMap } from "./keyboards/stateKeyMap.js"; -export { default as Codes } from "./text/codes.js"; -export * from "./text/codes.js"; -export * from "./text/deadkeys.js"; -export { default as DefaultRules } from "./text/defaultRules.js"; -export * from "./text/defaultRules.js"; -export { default as KeyboardInterface } from "./text/kbdInterface.js"; -export * from "./text/kbdInterface.js"; -export { default as KeyboardProcessor } from "./text/keyboardProcessor.js"; -export * from "./text/keyboardProcessor.js"; -export { default as KeyEvent } from "./text/keyEvent.js"; -export * from "./text/keyEvent.js"; -export { default as KeyMapping } from "./text/keyMapping.js"; -export { default as OutputTarget } from "./text/outputTarget.js"; -export * from "./text/outputTarget.js"; -export { default as RuleBehavior } from "./text/ruleBehavior.js"; -export * from "./text/stringDivergence.js"; -export * from "./text/systemStores.js"; +export { default as Codes } from "./codes.js"; +export * from "./codes.js"; +export { default as DefaultRules } from "./defaultRules.js"; +export * from "./defaultRules.js"; +export { default as KeyEvent } from "./keyEvent.js"; +export * from "./keyEvent.js"; +export { default as KeyMapping } from "./keyMapping.js"; export * from "@keymanapp/web-utils"; @@ -48,4 +38,4 @@ export * from "@keymanapp/web-utils"; // Without the line below... OutputTarget would likely be aliased there, as it's // the last `export { default as _ }` => `export * from` pairing seen above. -export default undefined; \ No newline at end of file +export default undefined; diff --git a/common/web/keyboard-processor/src/text/keyEvent.ts b/web/src/engine/keyboard/src/keyEvent.ts similarity index 96% rename from common/web/keyboard-processor/src/text/keyEvent.ts rename to web/src/engine/keyboard/src/keyEvent.ts index a38992a3a1..c359558b1f 100644 --- a/common/web/keyboard-processor/src/text/keyEvent.ts +++ b/web/src/engine/keyboard/src/keyEvent.ts @@ -6,16 +6,16 @@ // a key event. The most straightforward way to integrate Web OSK events on other platforms is to have // other platforms recognize and utilize this type. -import type Keyboard from "../keyboards/keyboard.js"; +import type Keyboard from "./keyboards/keyboard.js"; import { type DeviceSpec } from "@keymanapp/web-utils"; import Codes from './codes.js'; -import DefaultRules from './defaultRules.js'; -import { ActiveKeyBase } from "../index.js"; +import DefaultRules from "./defaultRules.js"; +import { ActiveKeyBase } from './keyboards/activeLayout.js'; // Represents a probability distribution over a keyboard's keys. // Defined here to avoid compilation issues. -export type KeyDistribution = {keySpec: ActiveKeyBase, p: number}[]; +export type KeyDistribution = { keySpec: ActiveKeyBase, p: number }[]; /** * A simple instance of the standard 'default rules' for keystroke processing from the @@ -188,4 +188,4 @@ export default class KeyEvent implements KeyEventSpec { } } } -}; \ No newline at end of file +}; diff --git a/common/web/keyboard-processor/src/text/keyMapping.ts b/web/src/engine/keyboard/src/keyMapping.ts similarity index 100% rename from common/web/keyboard-processor/src/text/keyMapping.ts rename to web/src/engine/keyboard/src/keyMapping.ts diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/web/src/engine/keyboard/src/keyboards/activeLayout.ts similarity index 99% rename from common/web/keyboard-processor/src/keyboards/activeLayout.ts rename to web/src/engine/keyboard/src/keyboards/activeLayout.ts index 7b293db93c..c440511f17 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/web/src/engine/keyboard/src/keyboards/activeLayout.ts @@ -1,6 +1,6 @@ -import Codes from "../text/codes.js"; -import KeyEvent, { KeyEventSpec } from "../text/keyEvent.js"; -import KeyMapping from "../text/keyMapping.js"; +import Codes from "../codes.js"; +import KeyEvent, { KeyEventSpec } from "../keyEvent.js"; +import KeyMapping from "../keyMapping.js"; import { ButtonClasses, Layouts } from "./defaultLayouts.js"; import type { LayoutKey, LayoutSubKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from "./defaultLayouts.js"; import type Keyboard from "./keyboard.js"; @@ -105,7 +105,7 @@ export class ActiveKeyBase { pad: ActiveKeyBase.DEFAULT_PAD }; - /** WARNING - DO NOT USE DIRECTLY outside of @keymanapp/keyboard-processor! */ + /** WARNING - DO NOT USE DIRECTLY outside of keyman/engine/keyboard! */ id: TouchLayout.TouchLayoutKeyId; text: string; hint?: string; diff --git a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts b/web/src/engine/keyboard/src/keyboards/defaultLayouts.ts similarity index 99% rename from common/web/keyboard-processor/src/keyboards/defaultLayouts.ts rename to web/src/engine/keyboard/src/keyboards/defaultLayouts.ts index 109da53a0e..3b97659d4b 100644 --- a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts +++ b/web/src/engine/keyboard/src/keyboards/defaultLayouts.ts @@ -16,7 +16,7 @@ import ButtonClasses = TouchLayout.TouchLayoutKeySp; export { ButtonClasses }; -import Codes from "../text/codes.js"; +import Codes from "../codes.js"; import type Keyboard from "./keyboard.js"; export interface EncodedVisualKeyboard { diff --git a/common/web/keyboard-processor/src/keyboards/keyboard.ts b/web/src/engine/keyboard/src/keyboards/keyboard.ts similarity index 99% rename from common/web/keyboard-processor/src/keyboards/keyboard.ts rename to web/src/engine/keyboard/src/keyboards/keyboard.ts index 5312d825a6..42463cd6e7 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/web/src/engine/keyboard/src/keyboards/keyboard.ts @@ -1,16 +1,16 @@ -import Codes from "../text/codes.js"; +import Codes from "../codes.js"; import { EncodedVisualKeyboard, LayoutSpec, Layouts } from "./defaultLayouts.js"; import { ActiveKey, ActiveLayout, ActiveSubKey } from "./activeLayout.js"; -import KeyEvent from "../text/keyEvent.js"; -import type OutputTarget from "../text/outputTarget.js"; +import KeyEvent from "../keyEvent.js"; +import { type OutputTarget } from "../outputTarget.interface.js"; import { ModifierKeyConstants, TouchLayout } from "@keymanapp/common-types"; type TouchLayoutSpec = TouchLayout.TouchLayoutPlatform & { isDefault?: boolean}; -import type { ComplexKeyboardStore } from "../text/kbdInterface.js"; - import { Version, DeviceSpec } from "@keymanapp/web-utils"; import StateKeyMap from "./stateKeyMap.js"; +type ComplexKeyboardStore = ( string | { t: 'd', d: number } | { ['t']: 'b' })[]; + /** * Stores preprocessed properties of a keyboard for quick retrieval later. */ diff --git a/common/web/keyboard-processor/src/keyboards/keyboardHarness.ts b/web/src/engine/keyboard/src/keyboards/keyboardHarness.ts similarity index 97% rename from common/web/keyboard-processor/src/keyboards/keyboardHarness.ts rename to web/src/engine/keyboard/src/keyboards/keyboardHarness.ts index 671c8fefd7..bb2e2d5ccb 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboardHarness.ts +++ b/web/src/engine/keyboard/src/keyboards/keyboardHarness.ts @@ -1,5 +1,6 @@ import Keyboard from "./keyboard.js"; -import Codes from "../text/codes.js"; +import Codes from "../codes.js"; +import { DeviceSpec } from '@keymanapp/web-utils'; /** * Defines members of the top-level `keyman` global object necessary to guarantee @@ -40,6 +41,8 @@ export const MinimalKeymanGlobal: KeyboardKeymanGlobal = { export class KeyboardHarness { public readonly _jsGlobal: any; public readonly keymanGlobal: KeyboardKeymanGlobal; + activeDevice: DeviceSpec; + /** * Constructs and configures a harness for receiving dynamically-loaded Keyman keyboards. diff --git a/common/web/keyboard-processor/src/keyboards/keyboardLoaderBase.ts b/web/src/engine/keyboard/src/keyboards/keyboardLoaderBase.ts similarity index 100% rename from common/web/keyboard-processor/src/keyboards/keyboardLoaderBase.ts rename to web/src/engine/keyboard/src/keyboards/keyboardLoaderBase.ts diff --git a/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts b/web/src/engine/keyboard/src/keyboards/keyboardProperties.ts similarity index 100% rename from common/web/keyboard-processor/src/keyboards/keyboardProperties.ts rename to web/src/engine/keyboard/src/keyboards/keyboardProperties.ts diff --git a/common/web/keyboard-processor/src/keyboards/loaders/dom-keyboard-loader.ts b/web/src/engine/keyboard/src/keyboards/loaders/dom-keyboard-loader.ts similarity index 100% rename from common/web/keyboard-processor/src/keyboards/loaders/dom-keyboard-loader.ts rename to web/src/engine/keyboard/src/keyboards/loaders/dom-keyboard-loader.ts diff --git a/common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts b/web/src/engine/keyboard/src/keyboards/loaders/domKeyboardLoader.ts similarity index 97% rename from common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts rename to web/src/engine/keyboard/src/keyboards/loaders/domKeyboardLoader.ts index ea9cddecd4..fcb2c764cd 100644 --- a/common/web/keyboard-processor/src/keyboards/loaders/domKeyboardLoader.ts +++ b/web/src/engine/keyboard/src/keyboards/loaders/domKeyboardLoader.ts @@ -2,7 +2,7 @@ /// -import { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; +import { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; import { ManagedPromise } from '@keymanapp/web-utils'; diff --git a/common/web/keyboard-processor/src/keyboards/loaders/node-keyboard-loader.ts b/web/src/engine/keyboard/src/keyboards/loaders/node-keyboard-loader.ts similarity index 100% rename from common/web/keyboard-processor/src/keyboards/loaders/node-keyboard-loader.ts rename to web/src/engine/keyboard/src/keyboards/loaders/node-keyboard-loader.ts diff --git a/common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts b/web/src/engine/keyboard/src/keyboards/loaders/nodeKeyboardLoader.ts similarity index 95% rename from common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts rename to web/src/engine/keyboard/src/keyboards/loaders/nodeKeyboardLoader.ts index 143e1c16c1..b9f6a7cdfe 100644 --- a/common/web/keyboard-processor/src/keyboards/loaders/nodeKeyboardLoader.ts +++ b/web/src/engine/keyboard/src/keyboards/loaders/nodeKeyboardLoader.ts @@ -1,4 +1,4 @@ -import { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; +import { Keyboard, KeyboardHarness, KeyboardLoaderBase, KeyboardLoadErrorBuilder, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; import vm from 'vm'; import fs from 'fs'; diff --git a/web/src/engine/keyboard/src/keyboards/loaders/tsconfig.dom.json b/web/src/engine/keyboard/src/keyboards/loaders/tsconfig.dom.json new file mode 100644 index 0000000000..1dd122f548 --- /dev/null +++ b/web/src/engine/keyboard/src/keyboards/loaders/tsconfig.dom.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "../../../", + "outDir": "../../../../../../build/engine/keyboard/obj/keyboards/loaders/", + "tsBuildInfoFile": "../../../../../../build/engine/keyboard/obj/keyboards/loaders/tsconfig.dom.tsbuildinfo", + "rootDir": "." + }, + "references": [ + { "path": "../../../tsconfig.json" } + ], + "include": ["dom-keyboard-loader.ts", "domKeyboardLoader.ts"], +} diff --git a/web/src/engine/keyboard/src/keyboards/loaders/tsconfig.node.json b/web/src/engine/keyboard/src/keyboards/loaders/tsconfig.node.json new file mode 100644 index 0000000000..4a06decf4c --- /dev/null +++ b/web/src/engine/keyboard/src/keyboards/loaders/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../../../tsconfig.base.json", + "compilerOptions": { + "types": [ "node" ], + "baseUrl": "../../../", + "outDir": "../../../../../../build/engine/keyboard/obj/keyboards/loaders/", + "tsBuildInfoFile": "../../../../../../build/engine/keyboard/obj/keyboards/loaders/tsconfig.node.tsbuildinfo", + "rootDir": "." + }, + "references": [ + { "path": "../../../tsconfig.json" } + ], + "include": ["node-keyboard-loader.ts", "nodeKeyboardLoader.ts"], +} diff --git a/common/web/keyboard-processor/src/keyboards/spacebarText.ts b/web/src/engine/keyboard/src/keyboards/spacebarText.ts similarity index 100% rename from common/web/keyboard-processor/src/keyboards/spacebarText.ts rename to web/src/engine/keyboard/src/keyboards/spacebarText.ts diff --git a/common/web/keyboard-processor/src/keyboards/stateKeyMap.ts b/web/src/engine/keyboard/src/keyboards/stateKeyMap.ts similarity index 65% rename from common/web/keyboard-processor/src/keyboards/stateKeyMap.ts rename to web/src/engine/keyboard/src/keyboards/stateKeyMap.ts index c8e9ee8b76..e82ff417c4 100644 --- a/common/web/keyboard-processor/src/keyboards/stateKeyMap.ts +++ b/web/src/engine/keyboard/src/keyboards/stateKeyMap.ts @@ -1,5 +1,5 @@ /** - * Provided by @keymanapp/keyboard-processor's `KeyboardProcessor` class and utilized by the OSK + * Provided by keyman/engine/keyboard's `KeyboardProcessor` class and utilized by the OSK * to provide state feedback on any corresponding keys visible in the OSK. */ export default interface StateKeyMap { diff --git a/web/src/engine/keyboard/src/outputTarget.interface.ts b/web/src/engine/keyboard/src/outputTarget.interface.ts new file mode 100644 index 0000000000..410094c812 --- /dev/null +++ b/web/src/engine/keyboard/src/outputTarget.interface.ts @@ -0,0 +1,104 @@ +export interface OutputTarget { + /** + * Signifies that this OutputTarget has no default key processing behaviors. This should be false + * for OutputTargets backed by web elements like HTMLInputElement or HTMLTextAreaElement. + */ + get isSynthetic(): boolean; + + resetContext(): void; + + hasDeadkeyMatch(n: number, d: number): boolean; + + insertDeadkeyBeforeCaret(d: number): void; + + /** + * Clears any selected text within the wrapper's element(s). + * Silently does nothing if no such text exists. + */ + clearSelection(): void; + + /** + * Clears any cached selection-related state values. + */ + invalidateSelection(): void; + + /** + * Indicates whether or not the underlying element has its own selection (input, textarea) + * or is part of (or possesses) the DOM's active selection. Don't confuse with isSelectionEmpty(). + * + * TODO: rename to supportsOwnSelection + */ + hasSelection(): boolean; + + /** + * Returns true if there is no current selection -- that is, the selection range is empty + */ + isSelectionEmpty(): boolean; + + /** + * Returns an index corresponding to the caret's position for use with deadkeys. + */ + getDeadkeyCaret(): number; + + /** + * Relative to the caret, gets the current context within the wrapper's element. + */ + getTextBeforeCaret(): string; + + /** + * Gets the element's-currently selected text. + */ + getSelectedText(): string; + + /** + * Relative to the caret (and/or active selection), gets the element's text after the caret, + * excluding any actively selected text that would be immediately replaced upon text entry. + */ + getTextAfterCaret(): string; + + /** + * Gets the element's full text, including any text that is actively selected. + */ + getText(): string; + + /** + * Performs context deletions (from the left of the caret) as needed by the KeymanWeb engine and + * corrects the location of any affected deadkeys. + * + * Does not delete deadkeys (b/c KMW 1 & 2 behavior maintenance). + * @param dn The number of characters to delete. If negative, context will be left unchanged. + */ + deleteCharsBeforeCaret(dn: number): void; + + /** + * Inserts text immediately before the caret's current position, moving the caret after the + * newly inserted text in the process along with any affected deadkeys. + * + * @param s Text to insert before the caret's current position. + */ + insertTextBeforeCaret(s: string): void; + + /** + * Allows element-specific handling for ENTER key inputs. Conceptually, this should usually + * correspond to `insertTextBeforeCaret('\n'), but actual implementation will vary greatly among + * elements. + */ + handleNewlineAtCaret(): void; + + /** + * Saves element-specific state properties prone to mutation, enabling restoration after + * text-output operations. + */ + saveProperties(): void; + + /** + * Restores previously-saved element-specific state properties. Designed for use after text-output + * ops to facilitate more-seamless web-dev and user interactions. + */ + restoreProperties(): void; + + /** + * Generates a synthetic event on the underlying element, signalling that its value has changed. + */ + doInputEvent(): void; +} diff --git a/common/web/keyboard-processor/tsconfig.all.json b/web/src/engine/keyboard/tsconfig.all.json similarity index 66% rename from common/web/keyboard-processor/tsconfig.all.json rename to web/src/engine/keyboard/tsconfig.all.json index dabb262d9c..1ee44d3e70 100644 --- a/common/web/keyboard-processor/tsconfig.all.json +++ b/web/src/engine/keyboard/tsconfig.all.json @@ -1,15 +1,14 @@ { - "extends": "../tsconfig.kmw-main-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "baseUrl": "./", - "outDir": "build/obj/", - "tsBuildInfoFile": "build/obj/tsconfig.all.tsbuildinfo", + "outDir": "../../../build/engine/keyboard/obj/", + "tsBuildInfoFile": "../../../build/engine/keyboard/obj/tsconfig.all.tsbuildinfo", "rootDir": "./src/" }, "references": [ { "path": "./src/keyboards/loaders/tsconfig.dom.json" }, { "path": "./src/keyboards/loaders/tsconfig.node.json" }, - { "path": "./tests/tsconfig.json" }, ], // Actual main-body compilation is in tsconfig.json. This config is just a wrapper // to trigger all three components at once. diff --git a/web/src/engine/keyboard/tsconfig.json b/web/src/engine/keyboard/tsconfig.json new file mode 100644 index 0000000000..84d96c8591 --- /dev/null +++ b/web/src/engine/keyboard/tsconfig.json @@ -0,0 +1,12 @@ +// Is used by the keyboard-loader submodules. +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "../../../build/engine/keyboard/obj/", + "tsBuildInfoFile": "../../../build/engine/keyboard/obj/tsconfig.tsbuildinfo", + "rootDir": "./src/" + }, + "include": [ "./src/**/*.ts"], + "exclude": ["./src/keyboards/loaders/**/*.ts"] +} diff --git a/web/src/engine/main/build.sh b/web/src/engine/main/build.sh index cceb9b4b8b..77673626e0 100755 --- a/web/src/engine/main/build.sh +++ b/web/src/engine/main/build.sh @@ -14,12 +14,13 @@ SUBPROJECT_NAME=engine/main builder_describe "Builds the Keyman Engine for Web's common top-level base classes." \ "@/common/web/keyman-version" \ - "@/common/web/keyboard-processor" \ - "@/common/predictive-text" \ + "@/web/src/engine/keyboard" \ "@/web/src/engine/interfaces build" \ "@/web/src/engine/device-detect build" \ - "@/web/src/engine/package-cache build" \ + "@/web/src/engine/js-processor build" \ + "@/web/src/engine/keyboard-storage build" \ "@/web/src/engine/osk build" \ + "@/web/src/engine/predictive-text/worker-main" \ "@/developer/src/kmc-model test" \ "clean" \ "configure" \ diff --git a/web/src/engine/main/src/contextManagerBase.ts b/web/src/engine/main/src/contextManagerBase.ts index c3f9581003..17fed71bd2 100644 --- a/web/src/engine/main/src/contextManagerBase.ts +++ b/web/src/engine/main/src/contextManagerBase.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'eventemitter3'; -import { ManagedPromise, type Keyboard, type KeyboardInterface, type OutputTarget } from '@keymanapp/keyboard-processor'; -import { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/package-cache'; +import { ManagedPromise, type Keyboard } from 'keyman/engine/keyboard'; +import { type KeyboardInterface, type OutputTarget } from 'keyman/engine/js-processor'; +import { StubAndKeyboardCache, type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { PredictionContext } from 'keyman/engine/interfaces'; import { EngineConfiguration } from './engineConfiguration.js'; diff --git a/web/src/engine/main/src/engineConfiguration.ts b/web/src/engine/main/src/engineConfiguration.ts index c66d3bd805..aac559acab 100644 --- a/web/src/engine/main/src/engineConfiguration.ts +++ b/web/src/engine/main/src/engineConfiguration.ts @@ -1,9 +1,10 @@ import { EventEmitter } from "eventemitter3"; -import { DeviceSpec, KeyboardProperties, ManagedPromise, OutputTarget, physicalKeyDeviceAlias, RuleBehavior, SpacebarText } from "@keymanapp/keyboard-processor"; +import { DeviceSpec, KeyboardProperties, ManagedPromise, physicalKeyDeviceAlias, SpacebarText } from "keyman/engine/keyboard"; +import { OutputTarget, RuleBehavior } from 'keyman/engine/js-processor'; import { PathConfiguration, PathOptionDefaults, PathOptionSpec } from "keyman/engine/interfaces"; import { Device } from "keyman/engine/device-detect"; -import { KeyboardStub } from "keyman/engine/package-cache"; +import { KeyboardStub } from "keyman/engine/keyboard-storage"; interface EventMap { 'spacebartext': (mode: SpacebarText) => void; diff --git a/web/src/engine/main/src/hardKeyboard.ts b/web/src/engine/main/src/hardKeyboard.ts index a07904f4a6..60f2b36200 100644 --- a/web/src/engine/main/src/hardKeyboard.ts +++ b/web/src/engine/main/src/hardKeyboard.ts @@ -1,6 +1,7 @@ import { EventEmitter } from "eventemitter3"; -import { Keyboard, KeyMapping, KeyEvent, type RuleBehavior, Codes } from "@keymanapp/keyboard-processor"; -import { KeyEventSourceInterface } from 'keyman/engine/events'; +import { Keyboard, KeyMapping, KeyEvent, Codes } from "keyman/engine/keyboard"; +import { type RuleBehavior } from 'keyman/engine/js-processor'; +import { KeyEventSourceInterface } from 'keyman/engine/osk'; import { ModifierKeyConstants } from '@keymanapp/common-types'; interface EventMap { diff --git a/web/src/engine/main/src/headless/contextWindow.ts b/web/src/engine/main/src/headless/contextWindow.ts index 2adb4f0b6a..9fcd1458b2 100644 --- a/web/src/engine/main/src/headless/contextWindow.ts +++ b/web/src/engine/main/src/headless/contextWindow.ts @@ -1,4 +1,4 @@ -import { Mock } from "@keymanapp/keyboard-processor"; +import { Mock } from "keyman/engine/js-processor"; export default class ContextWindow implements Context { // Used to limit the range of context replicated for use of keyboard rules within diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 7b89a0e242..6d8904c563 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -7,20 +7,19 @@ import { LanguageProcessor } from "./languageProcessor.js"; import type { ModelSpec } from "keyman/engine/interfaces"; import { globalObject, DeviceSpec } from "@keymanapp/web-utils"; +import { Codes, type Keyboard, type KeyEvent } from "keyman/engine/keyboard"; import { type Alternate, - Codes, isEmptyTransform, - type Keyboard, KeyboardInterface, KeyboardProcessor, - type KeyEvent, Mock, type OutputTarget, - type ProcessorInitOptions, RuleBehavior, - SystemStoreIDs, -} from "@keymanapp/keyboard-processor"; + type ProcessorInitOptions, + SystemStoreIDs +} from 'keyman/engine/js-processor'; + import { TranscriptionCache } from "./transcriptionCache.js"; export class InputProcessor { diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index 30a676bb6a..c1c1721484 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "eventemitter3"; import { LMLayer } from "@keymanapp/lexical-model-layer/web"; -import { OutputTarget, Transcription, Mock } from "@keymanapp/keyboard-processor"; +import { OutputTarget, Transcription, Mock } from "keyman/engine/js-processor"; import { LanguageProcessorEventMap, ModelSpec, StateChangeEnum, ReadySuggestions } from 'keyman/engine/interfaces'; import ContextWindow from "./contextWindow.js"; import { TranscriptionCache } from "./transcriptionCache.js"; diff --git a/web/src/engine/main/src/headless/transcriptionCache.ts b/web/src/engine/main/src/headless/transcriptionCache.ts index c173009171..af05853e17 100644 --- a/web/src/engine/main/src/headless/transcriptionCache.ts +++ b/web/src/engine/main/src/headless/transcriptionCache.ts @@ -1,4 +1,4 @@ -import { Transcription } from "@keymanapp/keyboard-processor"; +import { Transcription } from "keyman/engine/js-processor"; const TRANSCRIPTION_BUFFER_SIZE = 10; diff --git a/web/src/engine/main/src/keyboardInterface.ts b/web/src/engine/main/src/keyboardInterface.ts index a18bafecd0..1019478594 100644 --- a/web/src/engine/main/src/keyboardInterface.ts +++ b/web/src/engine/main/src/keyboardInterface.ts @@ -1,7 +1,6 @@ -import { - KeyboardInterface as KeyboardInterfaceBase, KeyboardObject, -} from "@keymanapp/keyboard-processor"; -import { KeyboardStub, RawKeyboardStub, toUnprefixedKeyboardId as unprefixed } from 'keyman/engine/package-cache'; +import { KeyboardObject } from "keyman/engine/keyboard"; +import { KeyboardInterface as KeyboardInterfaceBase } from 'keyman/engine/js-processor'; +import { KeyboardStub, RawKeyboardStub, toUnprefixedKeyboardId as unprefixed } from 'keyman/engine/keyboard-storage'; import { ContextManagerBase } from './contextManagerBase.js'; import { VariableStoreCookieSerializer } from "./variableStoreCookieSerializer.js"; diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index eaa54e5994..30160e683c 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -1,8 +1,9 @@ -import { type Keyboard, KeyboardKeymanGlobal, ProcessorInitOptions } from "@keymanapp/keyboard-processor"; -import { DOMKeyboardLoader as KeyboardLoader } from "@keymanapp/keyboard-processor/dom-keyboard-loader"; +import { type KeyEvent, type Keyboard, KeyboardKeymanGlobal } from "keyman/engine/keyboard"; +import { ProcessorInitOptions, RuleBehavior } from 'keyman/engine/js-processor'; +import { DOMKeyboardLoader as KeyboardLoader } from "keyman/engine/keyboard/dom-keyboard-loader"; import { InputProcessor } from './headless/inputProcessor.js'; import { OSKView } from "keyman/engine/osk"; -import { KeyboardRequisitioner, ModelCache, toUnprefixedKeyboardId as unprefixed } from "keyman/engine/package-cache"; +import { KeyboardRequisitioner, ModelCache, toUnprefixedKeyboardId as unprefixed } from "keyman/engine/keyboard-storage"; import { ModelSpec, PredictionContext } from "keyman/engine/interfaces"; import { EngineConfiguration, InitOptionSpec } from "./engineConfiguration.js"; @@ -10,8 +11,8 @@ import KeyboardInterface from "./keyboardInterface.js"; import { ContextManagerBase } from "./contextManagerBase.js"; import HardKeyboardBase from "./hardKeyboard.js"; import { LegacyAPIEvents } from "./legacyAPIEvents.js"; -import { KeyEventHandler, EventNames, EventListener, LegacyEventEmitter } from "keyman/engine/events"; -import DOMCloudRequester from "keyman/engine/package-cache/dom-requester"; +import { EventNames, EventListener, LegacyEventEmitter } from "keyman/engine/events"; +import DOMCloudRequester from "keyman/engine/keyboard-storage/dom-requester"; import KEYMAN_VERSION from "@keymanapp/keyman-version"; // From https://stackoverflow.com/a/69328045 @@ -29,6 +30,9 @@ function determineBaseLayout(): string { } } +export type KeyEventFullResultCallback = (result: RuleBehavior, error?: Error) => void; +export type KeyEventFullHandler = (event: KeyEvent, callback?: KeyEventFullResultCallback) => void; + export default class KeymanEngine< Configuration extends EngineConfiguration, ContextManager extends ContextManagerBase, @@ -47,7 +51,7 @@ export default class KeymanEngine< protected keyEventRefocus?: () => void; - private keyEventListener: KeyEventHandler = (event, callback) => { + private keyEventListener: KeyEventFullHandler = (event, callback) => { const outputTarget = this.contextManager.activeTarget; if(!this.contextManager.activeKeyboard || !outputTarget) { @@ -240,7 +244,7 @@ export default class KeymanEngine< this.contextManager.configure({ resetContext: (target) => { // Could reset the target's deadkeys here, but it's really more of a 'core' task. - // So we delegate that to keyboard-processor. + // So we delegate that to keyboard. if(this.osk) { this.osk.batchLayoutAfter(() => { this.core.resetContext(target); diff --git a/web/src/engine/main/src/variableStoreCookieSerializer.ts b/web/src/engine/main/src/variableStoreCookieSerializer.ts index 35b51150c0..62c250329a 100644 --- a/web/src/engine/main/src/variableStoreCookieSerializer.ts +++ b/web/src/engine/main/src/variableStoreCookieSerializer.ts @@ -1,4 +1,4 @@ -import { VariableStore, VariableStoreSerializer } from "@keymanapp/keyboard-processor"; +import { VariableStore, VariableStoreSerializer } from 'keyman/engine/js-processor'; import { CookieSerializer } from "keyman/engine/dom-utils"; // While there's little reason we couldn't store all of a keyboard's store values within diff --git a/web/src/engine/main/tsconfig.json b/web/src/engine/main/tsconfig.json index 9711d06a77..d2e0580161 100644 --- a/web/src/engine/main/tsconfig.json +++ b/web/src/engine/main/tsconfig.json @@ -13,7 +13,8 @@ "references": [ { "path": "../device-detect" }, { "path": "../osk" }, - { "path": "../package-cache" }, + { "path": "../keyboard-storage" }, { "path": "../interfaces" }, + { "path": "../js-processor" } ] } diff --git a/web/src/engine/osk/build.sh b/web/src/engine/osk/build.sh index 4538b79d92..0d676a2d3e 100755 --- a/web/src/engine/osk/build.sh +++ b/web/src/engine/osk/build.sh @@ -13,7 +13,7 @@ SUBPROJECT_NAME=engine/osk # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's On-Screen Keyboard package (OSK)." \ - "@/common/web/keyboard-processor build" \ + "@/web/src/engine/keyboard build" \ "@/common/web/gesture-recognizer build" \ "@/web/src/engine/interfaces build" \ "@/web/src/engine/dom-utils build" \ diff --git a/web/src/engine/osk/src/banner/banner.ts b/web/src/engine/osk/src/banner/banner.ts index 39b3ddc708..e38b6b6625 100644 --- a/web/src/engine/osk/src/banner/banner.ts +++ b/web/src/engine/osk/src/banner/banner.ts @@ -1,4 +1,4 @@ -import { Keyboard, KeyboardProperties } from '@keymanapp/keyboard-processor'; +import { Keyboard, KeyboardProperties } from 'keyman/engine/keyboard'; import { createUnselectableElement } from 'keyman/engine/dom-utils'; // Base class for a banner above the keyboard in the OSK diff --git a/web/src/engine/osk/src/banner/bannerController.ts b/web/src/engine/osk/src/banner/bannerController.ts index a53ea2f9e9..7fbc8ceb9b 100644 --- a/web/src/engine/osk/src/banner/bannerController.ts +++ b/web/src/engine/osk/src/banner/bannerController.ts @@ -6,7 +6,7 @@ import { BannerView } from './bannerView.js'; import { Banner } from './banner.js'; import { BlankBanner } from './blankBanner.js'; import { HTMLBanner } from './htmlBanner.js'; -import { Keyboard, KeyboardProperties } from '@keymanapp/keyboard-processor'; +import { Keyboard, KeyboardProperties } from 'keyman/engine/keyboard'; export class BannerController { private container: BannerView; diff --git a/web/src/engine/osk/src/banner/suggestionBanner.ts b/web/src/engine/osk/src/banner/suggestionBanner.ts index adf6a5ea20..34455fd061 100644 --- a/web/src/engine/osk/src/banner/suggestionBanner.ts +++ b/web/src/engine/osk/src/banner/suggestionBanner.ts @@ -13,7 +13,7 @@ import { import { BANNER_GESTURE_SET } from './bannerGestureSet.js'; -import { DeviceSpec, Keyboard, KeyboardProperties, timedPromise } from '@keymanapp/keyboard-processor'; +import { DeviceSpec, Keyboard, KeyboardProperties, timedPromise } from 'keyman/engine/keyboard'; import { Banner } from './banner.js'; import { ParsedLengthStyle } from '../lengthStyle.js'; import { getFontSizeStyle } from '../fontSizeUtils.js'; diff --git a/web/src/engine/osk/src/components/helpPageView.ts b/web/src/engine/osk/src/components/helpPageView.ts index 7c66708be9..af489a2686 100644 --- a/web/src/engine/osk/src/components/helpPageView.ts +++ b/web/src/engine/osk/src/components/helpPageView.ts @@ -1,4 +1,4 @@ -import { Keyboard } from '@keymanapp/keyboard-processor'; +import { Keyboard } from 'keyman/engine/keyboard'; import KeyboardView from './keyboardView.interface.js'; import { ParsedLengthStyle } from "../lengthStyle.js"; diff --git a/web/src/engine/osk/src/components/titleBar.ts b/web/src/engine/osk/src/components/titleBar.ts index 815b8a1e4e..dd4eba3df7 100644 --- a/web/src/engine/osk/src/components/titleBar.ts +++ b/web/src/engine/osk/src/components/titleBar.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'eventemitter3'; -import { Keyboard } from '@keymanapp/keyboard-processor'; +import { Keyboard } from 'keyman/engine/keyboard'; import OSKViewComponent from './oskViewComponent.interface.js'; import { ParsedLengthStyle } from '../lengthStyle.js'; diff --git a/web/src/engine/osk/src/correctionLayout.ts b/web/src/engine/osk/src/correctionLayout.ts index c3c0ffa886..ba5f902ff4 100644 --- a/web/src/engine/osk/src/correctionLayout.ts +++ b/web/src/engine/osk/src/correctionLayout.ts @@ -1,4 +1,4 @@ -import { ActiveKey, ActiveKeyBase, ActiveLayer, ActiveRow, Codes } from "@keymanapp/keyboard-processor"; +import { ActiveKey, ActiveKeyBase, ActiveLayer, ActiveRow, Codes } from "keyman/engine/keyboard"; /** * Defines correction-layout mappings for keys to be considered by diff --git a/web/src/engine/osk/src/corrections.ts b/web/src/engine/osk/src/corrections.ts index 39c5ae2ef3..808e1c6dce 100644 --- a/web/src/engine/osk/src/corrections.ts +++ b/web/src/engine/osk/src/corrections.ts @@ -1,4 +1,4 @@ -import { ActiveKeyBase, KeyDistribution } from "@keymanapp/keyboard-processor"; +import { ActiveKeyBase, KeyDistribution } from "keyman/engine/keyboard"; import { CorrectionLayout } from "./correctionLayout.js"; /** diff --git a/web/src/engine/osk/src/index.ts b/web/src/engine/osk/src/index.ts index 388ea32750..f010697d96 100644 --- a/web/src/engine/osk/src/index.ts +++ b/web/src/engine/osk/src/index.ts @@ -1,9 +1,10 @@ -export { Codes, DeviceSpec, Keyboard, KeyboardProperties, SpacebarText } from '@keymanapp/keyboard-processor'; +export { Codes, DeviceSpec, Keyboard, KeyboardProperties, SpacebarText } from 'keyman/engine/keyboard'; export { default as OSKView } from './views/oskView.js'; export { default as FloatingOSKView, FloatingOSKViewConfiguration } from './views/floatingOskView.js'; export { default as AnchoredOSKView } from './views/anchoredOskView.js'; export { default as InlinedOSKView } from './views/inlinedOskView.js'; +export { type KeyEventResultCallback, type KeyEventHandler, KeyEventSourceInterface } from './views/keyEventSource.interface.js'; export { BannerController } from './banner/bannerController.js'; // Is referenced by at least one desktop UI module. export { FloatingOSKCookie as FloatingOSKViewCookie } from './views/floatingOskCookie.js'; diff --git a/web/src/engine/osk/src/input/gestures/browser/flick.ts b/web/src/engine/osk/src/input/gestures/browser/flick.ts index c83a85121e..91d2071871 100644 --- a/web/src/engine/osk/src/input/gestures/browser/flick.ts +++ b/web/src/engine/osk/src/input/gestures/browser/flick.ts @@ -1,7 +1,7 @@ import { type KeyElement } from '../../../keyElement.js'; import VisualKeyboard from '../../../visualKeyboard.js'; -import { ActiveKey, ActiveKeyBase, ActiveSubKey, KeyDistribution, KeyEvent } from '@keymanapp/keyboard-processor'; +import { ActiveKey, ActiveKeyBase, ActiveSubKey, KeyDistribution, KeyEvent } from 'keyman/engine/keyboard'; import { ConfigChangeClosure, CumulativePathStats, GestureRecognizerConfiguration, GestureSequence, GestureSource, GestureSourceSubview, InputSample, RecognitionZoneSource } from '@keymanapp/gesture-recognizer'; import { GestureHandler } from '../gestureHandler.js'; import { distributionFromDistanceMaps } from '../../../corrections.js'; diff --git a/web/src/engine/osk/src/input/gestures/browser/modipress.ts b/web/src/engine/osk/src/input/gestures/browser/modipress.ts index f3670944a4..3ffd4c206c 100644 --- a/web/src/engine/osk/src/input/gestures/browser/modipress.ts +++ b/web/src/engine/osk/src/input/gestures/browser/modipress.ts @@ -1,7 +1,7 @@ import { type KeyElement } from '../../../keyElement.js'; import VisualKeyboard from '../../../visualKeyboard.js'; -import { KeyDistribution, ActiveKeyBase } from '@keymanapp/keyboard-processor'; +import { KeyDistribution, ActiveKeyBase } from 'keyman/engine/keyboard'; import { GestureSequence } from '@keymanapp/gesture-recognizer'; import { GestureHandler } from '../gestureHandler.js'; diff --git a/web/src/engine/osk/src/input/gestures/browser/multitap.ts b/web/src/engine/osk/src/input/gestures/browser/multitap.ts index 23c4898ff0..99e50810f9 100644 --- a/web/src/engine/osk/src/input/gestures/browser/multitap.ts +++ b/web/src/engine/osk/src/input/gestures/browser/multitap.ts @@ -1,7 +1,7 @@ import { type KeyElement } from '../../../keyElement.js'; import VisualKeyboard from '../../../visualKeyboard.js'; -import { ActiveSubKey, ActiveKey, KeyDistribution, ActiveKeyBase } from '@keymanapp/keyboard-processor'; +import { ActiveSubKey, ActiveKey, KeyDistribution, ActiveKeyBase } from 'keyman/engine/keyboard'; import { GestureSequence, GestureStageReport } from '@keymanapp/gesture-recognizer'; import { GestureHandler } from '../gestureHandler.js'; import { distributionFromDistanceMaps } from '../../../corrections.js'; diff --git a/web/src/engine/osk/src/input/gestures/browser/oskSubKey.ts b/web/src/engine/osk/src/input/gestures/browser/oskSubKey.ts index 7640c3b20e..abf3a9fb80 100644 --- a/web/src/engine/osk/src/input/gestures/browser/oskSubKey.ts +++ b/web/src/engine/osk/src/input/gestures/browser/oskSubKey.ts @@ -1,4 +1,4 @@ -import { ActiveSubKey } from '@keymanapp/keyboard-processor'; +import { ActiveSubKey } from 'keyman/engine/keyboard'; import OSKKey from '../../../keyboard-layout/oskKey.js'; import { KeyData, KeyElement, link } from '../../../keyElement.js'; import VisualKeyboard from '../../../visualKeyboard.js'; diff --git a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts index e9388e96f2..d33d319211 100644 --- a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts +++ b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts @@ -3,7 +3,7 @@ import { type KeyElement } from '../../../keyElement.js'; import OSKBaseKey from '../../../keyboard-layout/oskBaseKey.js'; import VisualKeyboard from '../../../visualKeyboard.js'; -import { DeviceSpec, ActiveSubKey, KeyDistribution, ActiveKeyBase } from '@keymanapp/keyboard-processor'; +import { DeviceSpec, ActiveSubKey, KeyDistribution, ActiveKeyBase } from 'keyman/engine/keyboard'; import { ConfigChangeClosure, GestureRecognizerConfiguration, GestureSequence, PaddedZoneSource, RecognitionZoneSource } from '@keymanapp/gesture-recognizer'; import { GestureHandler } from '../gestureHandler.js'; import { CorrectionLayout, CorrectionLayoutEntry } from '../../../correctionLayout.js'; diff --git a/web/src/engine/osk/src/input/gestures/gestureHandler.ts b/web/src/engine/osk/src/input/gestures/gestureHandler.ts index 4c45c39e77..3c585de646 100644 --- a/web/src/engine/osk/src/input/gestures/gestureHandler.ts +++ b/web/src/engine/osk/src/input/gestures/gestureHandler.ts @@ -1,4 +1,4 @@ -import { ActiveKeyBase, KeyDistribution } from "@keymanapp/keyboard-processor"; +import { ActiveKeyBase, KeyDistribution } from "keyman/engine/keyboard"; export interface GestureHandler { /** diff --git a/web/src/engine/osk/src/input/gestures/heldRepeater.ts b/web/src/engine/osk/src/input/gestures/heldRepeater.ts index 0f74677dda..025d898912 100644 --- a/web/src/engine/osk/src/input/gestures/heldRepeater.ts +++ b/web/src/engine/osk/src/input/gestures/heldRepeater.ts @@ -1,5 +1,5 @@ import { GestureSequence } from "@keymanapp/gesture-recognizer"; -import { KeyDistribution } from "@keymanapp/keyboard-processor"; +import { KeyDistribution } from "keyman/engine/keyboard"; import { KeyElement } from "../../keyElement.js"; import { GestureHandler } from './gestureHandler.js'; diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 34d5576e23..5e53a443f5 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -12,7 +12,7 @@ import ButtonClasses = TouchLayout.TouchLayoutKeySp; import { ActiveLayout, deepCopy -} from '@keymanapp/keyboard-processor'; +} from 'keyman/engine/keyboard'; import { type KeyElement } from '../../keyElement.js'; @@ -20,20 +20,8 @@ import { calcLockedDistance, lockedAngleForDir, MAX_TOLERANCE_ANGLE_SKEW, type O import specs = gestures.specs; -export interface GestureParams { +export interface GestureParams { readonly longpress: { - /** - * Allows enabling or disabling the longpress up-flick shortcut for - * keyboards that do not include any defined flick gestures. - * - * Will be ignored (in favor of `false`) for keyboards that do have defined - * flicks. - * - * Note: this is automatically overwritten during keyboard initialization - * to match the keyboard's properties. - */ - permitsFlick: (item?: Item) => boolean, - /** * The minimum _net_ distance traveled before a longpress flick-shortcut will cancel any * conflicting flick models. @@ -98,19 +86,31 @@ export interface GestureParams { * Is currently also used as the max radius for valid flick-reset recentering targets. */ dirLockDist: number + } +} + +export interface FullGestureParams extends GestureParams { + readonly longpress: GestureParams["longpress"] & { + /** + * Allows enabling or disabling the longpress up-flick shortcut for + * keyboards that do not include any defined flick gestures. + * + * Will be ignored (in favor of `false`) for keyboards that do have defined + * flicks. + * + * Note: this is automatically overwritten during keyboard initialization + * to match the keyboard's properties. + */ + permitsFlick: (item?: Item) => boolean }, /** * Indicates whether roaming-touch oriented behaviors should be enabled. - * - * Note that run-time adjustments to this property after initialization will - * not take affect, unlike the other properties of the overall parameter object. */ roamingEnabled?: boolean; } export const DEFAULT_GESTURE_PARAMS: GestureParams = { longpress: { - permitsFlick: () => true, // Note: actual runtime value is determined at runtime based upon row height. // See `VisualKeyboard.refreshLayout`, CTRL-F "Step 3". flickDistStart: 8, @@ -206,17 +206,16 @@ let dummy: ActiveLayout; let dummy2: LayoutGestureSupportFlags = dummy; /** - * Defines the set of gestures appropriate for use with the specified Keyman keyboard. + * Defines the set of gestures appropriate for use with the specified Keyman + * keyboard. * @param layerGroup The active keyboard's layer group - * @param params A set of tweakable gesture parameters. It will be closure-captured - * and referred to by reference; changes to its values will take - * immediate effect during gesture processing. - * - * If params.roamingEnabled is unset, it will be initialized by this - * method based upon layout properties. + * @param paramObj A set of tweakable gesture parameters. It will be + * closure-captured and referred to by reference; changes to + * its values will take immediate effect during gesture + * processing. * @returns */ -export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: GestureParams): GestureModelDefs { +export function gestureSetForLayout(flags: LayoutGestureSupportFlags, paramObj: GestureParams): GestureModelDefs { // To be used among the `allowsInitialState` contact-model specifications as needed. const gestureKeyFilter = (key: KeyElement, gestureId: string) => { if(!key) { @@ -248,18 +247,33 @@ export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: Ge } }; - const doRoaming = params.roamingEnabled ||= !flags.hasFlicks; + const params = paramObj as FullGestureParams; + + // Override any prior entries for keyboard-specific configuration. + params.longpress.permitsFlick = (key) => { + const flickSpec = key?.key.spec.flick; + return !flickSpec || !(flickSpec.n || flickSpec.nw || flickSpec.ne); + }; + const doRoaming = params.roamingEnabled = !flags.hasFlicks; const _initialTapModel: GestureModel = deepCopy(!doRoaming ? initialTapModel(params) : initialTapModelWithReset(params)); const _simpleTapModel: GestureModel = deepCopy(!doRoaming ? simpleTapModel(params) : simpleTapModelWithReset(params)); // Ensure all deep-copy operations for longpress modeling occur before the property-redefining block. const _longpressModel: GestureModel = withKeySpecFiltering(deepCopy(longpressModel(params, true, doRoaming)), 0); + const _multitapStartModel: GestureModel = withKeySpecFiltering(multitapStartModel(params), 0); + const _modipressMultitapStartModel: GestureModel = withKeySpecFiltering(modipressMultitapStartModel(params), 0); // `deepCopy` does not preserve property definitions, instead raw-copying its value. // We need to re-instate the longpress delay property here. Object.defineProperty(_longpressModel.contacts[0].model.timer, 'duration', { get: () => params.longpress.waitLength }); + Object.defineProperty(_multitapStartModel.sustainTimer, 'duration', { + get: () => params.multitap.waitLength + }); + Object.defineProperty(_modipressMultitapStartModel.sustainTimer, 'duration', { + get: () => params.multitap.waitLength + }); // #region Functions for implementing and/or extending path initial-state checks function withKeySpecFiltering(model: GestureModel, contactIndices: number | number[]) { @@ -293,7 +307,7 @@ export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: Ge const _modipressStartModel = modipressStartModel(); const gestureModels: GestureModel[] = [ _longpressModel, - withKeySpecFiltering(multitapStartModel(params), 0), + _multitapStartModel, multitapEndModel(params), _initialTapModel, _simpleTapModel, @@ -304,7 +318,7 @@ export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: Ge modipressHoldModel(params), modipressEndModel(), modipressMultitapTransitionModel(), - withKeySpecFiltering(modipressMultitapStartModel(params), 0), + _modipressMultitapStartModel, modipressMultitapEndModel(params), modipressMultitapLockModel() ]; @@ -367,7 +381,7 @@ export function instantContactResolutionModel(): ContactModel { }; } -export function flickStartContactModel(params: GestureParams): gestures.specs.ContactModel { +export function flickStartContactModel(params: FullGestureParams): gestures.specs.ContactModel { const flickParams = params.flick; return { @@ -426,7 +440,7 @@ function determineLockFromStats(pathStats: CumulativePathStats, base } } -export function flickMidContactModel(params: GestureParams): gestures.specs.ContactModel { +export function flickMidContactModel(params: FullGestureParams): gestures.specs.ContactModel { return { itemPriority: 1, pathModel: { @@ -461,7 +475,7 @@ export function flickMidContactModel(params: GestureParams): gestures.specs.Cont } -export function flickEndContactModel(params: GestureParams): ContactModel { +export function flickEndContactModel(params: FullGestureParams): ContactModel { return { itemPriority: 1, pathModel: { @@ -485,7 +499,7 @@ export function flickEndContactModel(params: GestureParams): ContactModel { } } -export function longpressContactModel(params: GestureParams, enabledFlicks: boolean, resetForRoaming: boolean): ContactModel { +export function longpressContactModel(params: FullGestureParams, enabledFlicks: boolean, resetForRoaming: boolean): ContactModel { const spec = params.longpress; return { @@ -581,7 +595,7 @@ export function modipressContactEndModel(): ContactModel { }; } -export function simpleTapContactModel(params: GestureParams, isNotInitial?: boolean): ContactModel { +export function simpleTapContactModel(params: FullGestureParams, isNotInitial?: boolean): ContactModel { // Snapshot at model construction; do not update if changed. const roamingEnabled = params?.roamingEnabled ?? true; // ?? true - used by the banner. @@ -650,7 +664,7 @@ export function specialKeyStartModel(): GestureModel { }; } -export function specialKeyEndModel(params: GestureParams): GestureModel { +export function specialKeyEndModel(params: FullGestureParams): GestureModel { return { id: 'special-key-end', resolutionPriority: 0, @@ -682,7 +696,7 @@ export function specialKeyEndModel(params: GestureParams): GestureModel { * - the common gesture configuration permits the shortcut where supported * @param allowRoaming Indicates whether "roaming touch" mode should be supported. */ -export function longpressModel(params: GestureParams, allowShortcut: boolean, allowRoaming: boolean): GestureModel { +export function longpressModel(params: FullGestureParams, allowShortcut: boolean, allowRoaming: boolean): GestureModel { const base: GestureModel = { id: 'longpress', // Needs to beat flick-start priority. @@ -732,7 +746,7 @@ export function longpressModel(params: GestureParams, allowShortcut: boolean, al /** * For use for transitioning out of roaming-touch. */ -export function longpressModelAfterRoaming(params: GestureParams): GestureModel { +export function longpressModelAfterRoaming(params: FullGestureParams): GestureModel { // The longpress-shortcut is always disabled for keys reached by roaming (param 2) // Only used when roaming is permitted; continued roaming should be allowed. (param 3) const base = longpressModel(params, false, true); @@ -782,7 +796,7 @@ export function longpressRoamRestoration(): GestureModel { } } -export function flickStartModel(params: GestureParams): GestureModel { +export function flickStartModel(params: FullGestureParams): GestureModel { return { id: 'flick-start', resolutionPriority: 3, @@ -799,7 +813,7 @@ export function flickStartModel(params: GestureParams): GestureModel { } } -export function flickRestartModel(params: GestureParams): GestureModel { +export function flickRestartModel(params: FullGestureParams): GestureModel { const base = flickStartModel(params); return { ...base, @@ -859,7 +873,7 @@ export function flickRestartModel(params: GestureParams): GestureModel { +export function flickMidModel(params: FullGestureParams): GestureModel { return { id: 'flick-mid', resolutionPriority: 0, @@ -889,7 +903,7 @@ export function flickMidModel(params: GestureParams): GestureModel { } // Clears existing flick-scrolling & primes the flick-reset recentering mechanism. -export function flickResetModel(params: GestureParams): GestureModel { +export function flickResetModel(params: FullGestureParams): GestureModel { return { id: 'flick-reset', resolutionPriority: 1, @@ -909,7 +923,7 @@ export function flickResetModel(params: GestureParams): GestureModel { }; } -export function flickResetCenteringModel(params: GestureParams): GestureModel { +export function flickResetCenteringModel(params: FullGestureParams): GestureModel { return { id: 'flick-reset-centering', resolutionPriority: 1, @@ -963,7 +977,7 @@ export function flickResetEndModel(): GestureModel { }; }; -export function flickEndModel(params: GestureParams): GestureModel { +export function flickEndModel(params: FullGestureParams): GestureModel { return { id: 'flick-end', resolutionPriority: 0, @@ -990,7 +1004,7 @@ export function flickEndModel(params: GestureParams): GestureModel { } } -export function multitapStartModel(params: GestureParams): GestureModel { +export function multitapStartModel(params: FullGestureParams): GestureModel { return { id: 'multitap-start', resolutionPriority: 2, @@ -1019,7 +1033,7 @@ export function multitapStartModel(params: GestureParams): GestureModel { } } -export function multitapEndModel(params: GestureParams): GestureModel { +export function multitapEndModel(params: FullGestureParams): GestureModel { return { id: 'multitap-end', resolutionPriority: 2, @@ -1053,7 +1067,7 @@ export function multitapEndModel(params: GestureParams): GestureModel { } } -export function initialTapModel(params: GestureParams): GestureModel { +export function initialTapModel(params: FullGestureParams): GestureModel { return { id: 'initial-tap', resolutionPriority: 1, @@ -1089,7 +1103,7 @@ export function initialTapModel(params: GestureParams): GestureModel { } } -export function simpleTapModel(params: GestureParams): GestureModel { +export function simpleTapModel(params: FullGestureParams): GestureModel { return { id: 'simple-tap', resolutionPriority: 1, @@ -1113,7 +1127,7 @@ export function simpleTapModel(params: GestureParams): GestureModel { }; } -export function initialTapModelWithReset(params: GestureParams): GestureModel { +export function initialTapModelWithReset(params: FullGestureParams): GestureModel { const base = initialTapModel(params); return { ...base, @@ -1127,7 +1141,7 @@ export function initialTapModelWithReset(params: GestureParams): GestureModel { +export function simpleTapModelWithReset(params: FullGestureParams): GestureModel { const simpleModel = simpleTapModel(params); return { ...simpleModel, @@ -1189,7 +1203,7 @@ export function modipressStartModel(): GestureModel { } } -export function modipressHoldModel(params: GestureParams): GestureModel { +export function modipressHoldModel(params: FullGestureParams): GestureModel { return { id: 'modipress-hold', resolutionPriority: 5, @@ -1281,7 +1295,7 @@ export function modipressEndModel(): GestureModel { } } -export function modipressMultitapStartModel(params: GestureParams): GestureModel { +export function modipressMultitapStartModel(params: FullGestureParams): GestureModel { return { id: 'modipress-multitap-start', resolutionPriority: 6, @@ -1316,7 +1330,7 @@ export function modipressMultitapStartModel(params: GestureParams): GestureModel } } -export function modipressMultitapEndModel(params: GestureParams): GestureModel { +export function modipressMultitapEndModel(params: FullGestureParams): GestureModel { return { id: 'modipress-multitap-end', resolutionPriority: 5, diff --git a/web/src/engine/osk/src/keyElement.ts b/web/src/engine/osk/src/keyElement.ts index 01ae3a0c99..c913bdfbff 100644 --- a/web/src/engine/osk/src/keyElement.ts +++ b/web/src/engine/osk/src/keyElement.ts @@ -1,4 +1,4 @@ -import { ActiveSubKey } from '@keymanapp/keyboard-processor'; +import { ActiveSubKey } from 'keyman/engine/keyboard'; import OSKKey from "./keyboard-layout/oskKey.js"; export class KeyData { diff --git a/web/src/engine/osk/src/keyboard-layout/gesturePreviewHost.ts b/web/src/engine/osk/src/keyboard-layout/gesturePreviewHost.ts index 321de328b8..32a30c3f5d 100644 --- a/web/src/engine/osk/src/keyboard-layout/gesturePreviewHost.ts +++ b/web/src/engine/osk/src/keyboard-layout/gesturePreviewHost.ts @@ -1,4 +1,4 @@ -import { ActiveKeyBase } from "@keymanapp/keyboard-processor"; +import { ActiveKeyBase } from "keyman/engine/keyboard"; import { EventEmitter } from "eventemitter3"; import { KeyElement } from "../keyElement.js"; diff --git a/web/src/engine/osk/src/keyboard-layout/oskBaseKey.ts b/web/src/engine/osk/src/keyboard-layout/oskBaseKey.ts index e48253df60..3d70ed4010 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskBaseKey.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskBaseKey.ts @@ -1,4 +1,4 @@ -import { ActiveKey, Codes } from '@keymanapp/keyboard-processor'; +import { ActiveKey, Codes } from 'keyman/engine/keyboard'; import OSKKey, { KeyLayoutParams, renameSpecialKey } from './oskKey.js'; import { KeyData, KeyElement, link } from '../keyElement.js'; diff --git a/web/src/engine/osk/src/keyboard-layout/oskKey.ts b/web/src/engine/osk/src/keyboard-layout/oskKey.ts index 6404658ef6..2b9381afe3 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskKey.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskKey.ts @@ -1,4 +1,4 @@ -import { ActiveKey, ActiveSubKey, ButtonClass, ButtonClasses, DeviceSpec } from '@keymanapp/keyboard-processor'; +import { ActiveKey, ActiveSubKey, ButtonClass, ButtonClasses, DeviceSpec } from 'keyman/engine/keyboard'; // At present, we don't use @keymanapp/keyman. Just `keyman`. (Refer to /web/package.json.) import specialChars from '../specialCharacters.js'; diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayer.ts b/web/src/engine/osk/src/keyboard-layout/oskLayer.ts index 61c43f22cf..a08d2921a5 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskLayer.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskLayer.ts @@ -1,4 +1,4 @@ -import { ActiveLayer, ActiveLayout } from '@keymanapp/keyboard-processor'; +import { ActiveLayer, ActiveLayout } from 'keyman/engine/keyboard'; import OSKRow from './oskRow.js'; import OSKBaseKey from './oskBaseKey.js'; diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts index 5f4a3f45a3..4bf4bc01b4 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts @@ -1,4 +1,4 @@ -import { type DeviceSpec, Keyboard, ActiveLayout, ButtonClasses } from '@keymanapp/keyboard-processor'; +import { type DeviceSpec, Keyboard, ActiveLayout, ButtonClasses } from 'keyman/engine/keyboard'; import { InputSample } from '@keymanapp/gesture-recognizer'; diff --git a/web/src/engine/osk/src/keyboard-layout/oskRow.ts b/web/src/engine/osk/src/keyboard-layout/oskRow.ts index 8012e8438a..91c6409cd7 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskRow.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskRow.ts @@ -1,4 +1,4 @@ -import { ActiveKey, ActiveLayer, ActiveRow } from '@keymanapp/keyboard-processor'; +import { ActiveKey, ActiveLayer, ActiveRow } from 'keyman/engine/keyboard'; import OSKBaseKey from './oskBaseKey.js'; import { ParsedLengthStyle } from '../lengthStyle.js'; diff --git a/web/src/engine/osk/src/views/anchoredOskView.ts b/web/src/engine/osk/src/views/anchoredOskView.ts index fe2670c0b9..25ab819e3c 100644 --- a/web/src/engine/osk/src/views/anchoredOskView.ts +++ b/web/src/engine/osk/src/views/anchoredOskView.ts @@ -1,4 +1,4 @@ -import { DeviceSpec } from '@keymanapp/keyboard-processor'; +import { DeviceSpec } from 'keyman/engine/keyboard'; import { landscapeView } from 'keyman/engine/dom-utils'; import OSKView, { OSKPos, OSKRect } from './oskView.js'; diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index c296bd374b..ba186fc99e 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -1,4 +1,4 @@ -import { DeviceSpec, ManagedPromise, Version } from '@keymanapp/keyboard-processor'; +import { DeviceSpec, ManagedPromise, Version } from 'keyman/engine/keyboard'; import { getAbsoluteX, getAbsoluteY, landscapeView } from 'keyman/engine/dom-utils'; import { EmitterListenerSpy } from 'keyman/engine/events'; diff --git a/web/src/engine/osk/src/views/inlinedOskView.ts b/web/src/engine/osk/src/views/inlinedOskView.ts index 06f89c0a92..d4dc48fd66 100644 --- a/web/src/engine/osk/src/views/inlinedOskView.ts +++ b/web/src/engine/osk/src/views/inlinedOskView.ts @@ -1,4 +1,4 @@ -import { DeviceSpec } from '@keymanapp/keyboard-processor'; +import { DeviceSpec } from 'keyman/engine/keyboard'; import OSKView, { OSKPos, OSKRect } from './oskView.js'; import VisualKeyboard from '../visualKeyboard.js'; diff --git a/web/src/engine/events/src/keyEventSource.interface.ts b/web/src/engine/osk/src/views/keyEventSource.interface.ts similarity index 79% rename from web/src/engine/events/src/keyEventSource.interface.ts rename to web/src/engine/osk/src/views/keyEventSource.interface.ts index d3c39fa49d..827a0b9af8 100644 --- a/web/src/engine/events/src/keyEventSource.interface.ts +++ b/web/src/engine/osk/src/views/keyEventSource.interface.ts @@ -1,5 +1,6 @@ import { EventEmitter } from "eventemitter3"; -import { type KeyEvent, type RuleBehavior } from "@keymanapp/keyboard-processor"; +import { type KeyEvent } from 'keyman/engine/keyboard'; +import { type RuleBehavior } from 'keyman/engine/js-processor'; export type KeyEventResultCallback = (result: RuleBehavior, error?: Error) => void; export type KeyEventHandler = (event: KeyEvent, callback?: KeyEventResultCallback) => void; diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts index d55eb8a8f8..b51c18fbcd 100644 --- a/web/src/engine/osk/src/views/oskView.ts +++ b/web/src/engine/osk/src/views/oskView.ts @@ -16,16 +16,16 @@ import { Keyboard, KeyboardProperties, ManagedPromise, - type MinimalCodesInterface, - type MutableSystemStore, - type SystemStoreMutationHandler -} from '@keymanapp/keyboard-processor'; + type MinimalCodesInterface +} from 'keyman/engine/keyboard'; import { createUnselectableElement, getAbsoluteX, getAbsoluteY, StylesheetManager } from 'keyman/engine/dom-utils'; -import { EventListener, KeyEventHandler, KeyEventSourceInterface, LegacyEventEmitter } from 'keyman/engine/events'; +import { EventListener, LegacyEventEmitter } from 'keyman/engine/events'; +import { type MutableSystemStore, type SystemStoreMutationHandler } from 'keyman/engine/js-processor'; import Configuration from '../config/viewConfiguration.js'; import Activator, { StaticActivator } from './activator.js'; import TouchEventPromiseMap from './touchEventPromiseMap.js'; +import { KeyEventHandler, KeyEventSourceInterface } from './keyEventSource.interface.js'; import { DEFAULT_GESTURE_PARAMS, GestureParams } from '../input/gestures/specsForLayout.js'; // These will likely be eliminated from THIS file at some point.\ diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index a8152a0db0..26b269d7f2 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -13,9 +13,9 @@ import { StateKeyMap, ActiveSubKey, timedPromise, - ActiveKeyBase, - isEmptyTransform -} from '@keymanapp/keyboard-processor'; + ActiveKeyBase +} from 'keyman/engine/keyboard'; +import { isEmptyTransform } from 'keyman/engine/js-processor'; import { buildCorrectiveLayout } from './correctionLayout.js'; import { distributionFromDistanceMaps, keyTouchDistances } from './corrections.js'; @@ -31,7 +31,7 @@ import { import { createStyleSheet, StylesheetManager } from 'keyman/engine/dom-utils'; -import { KeyEventHandler, KeyEventResultCallback } from 'keyman/engine/events'; +import { KeyEventHandler, KeyEventResultCallback } from './views/keyEventSource.interface.js'; import GlobeHint from './globehint.interface.js'; import KeyboardView from './components/keyboardView.interface.js'; @@ -151,7 +151,7 @@ export default class VisualKeyboard extends EventEmitter implements Ke /** * Tweakable gesture parameters referenced by supported gestures and the gesture engine. */ - get gestureParams(): GestureParams { + get gestureParams(): GestureParams { return this.config.gestureParams; }; @@ -423,11 +423,6 @@ export default class VisualKeyboard extends EventEmitter implements Ke historyLength: DEBUG_HISTORY_COUNT }; - this.gestureParams.longpress.permitsFlick = (key) => { - const flickSpec = key?.key.spec.flick; - return !flickSpec || !(flickSpec.n || flickSpec.nw || flickSpec.ne); - }; - const recognizer = new GestureRecognizer(gestureSetForLayout(this.kbdLayout, this.gestureParams), config); recognizer.stateToken = this.layerId; diff --git a/common/predictive-text/.gitignore b/web/src/engine/predictive-text/worker-main/.gitignore similarity index 100% rename from common/predictive-text/.gitignore rename to web/src/engine/predictive-text/worker-main/.gitignore diff --git a/common/predictive-text/README.md b/web/src/engine/predictive-text/worker-main/README.md similarity index 100% rename from common/predictive-text/README.md rename to web/src/engine/predictive-text/worker-main/README.md diff --git a/common/predictive-text/build.sh b/web/src/engine/predictive-text/worker-main/build.sh similarity index 84% rename from common/predictive-text/build.sh rename to web/src/engine/predictive-text/worker-main/build.sh index b9ab601691..4120fce53b 100755 --- a/common/predictive-text/build.sh +++ b/web/src/engine/predictive-text/worker-main/build.sh @@ -8,7 +8,7 @@ ## 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/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" @@ -31,7 +31,7 @@ builder_describe "Builds the lm-layer module" \ builder_describe_outputs \ configure /node_modules \ - build /common/predictive-text/build/lib/web/index.mjs # is built by the final step. + build /web/src/engine/predictive-text/worker-main/build/lib/web/index.mjs # is built by the final step. builder_parse "$@" @@ -48,8 +48,8 @@ function do_build() { tsc -b ./tsconfig.all.json # esbuild-bundled products at this level are not intended to be used for anything but testing. - $BUNDLE_CMD "${KEYMAN_ROOT}/common/predictive-text/build/obj/web/index.js" \ - --out "${KEYMAN_ROOT}/common/predictive-text/build/lib/web/index.mjs" \ + $BUNDLE_CMD "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-main/build/obj/web/index.js" \ + --out "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-main/build/lib/web/index.mjs" \ --format esm } diff --git a/common/predictive-text/docs/build.sh b/web/src/engine/predictive-text/worker-main/docs/build.sh similarity index 100% rename from common/predictive-text/docs/build.sh rename to web/src/engine/predictive-text/worker-main/docs/build.sh diff --git a/common/predictive-text/docs/lmlayer-states.dot b/web/src/engine/predictive-text/worker-main/docs/lmlayer-states.dot similarity index 100% rename from common/predictive-text/docs/lmlayer-states.dot rename to web/src/engine/predictive-text/worker-main/docs/lmlayer-states.dot diff --git a/common/predictive-text/docs/lmlayer-states.png b/web/src/engine/predictive-text/worker-main/docs/lmlayer-states.png similarity index 100% rename from common/predictive-text/docs/lmlayer-states.png rename to web/src/engine/predictive-text/worker-main/docs/lmlayer-states.png diff --git a/common/predictive-text/docs/predictive-text-sequence.png b/web/src/engine/predictive-text/worker-main/docs/predictive-text-sequence.png similarity index 100% rename from common/predictive-text/docs/predictive-text-sequence.png rename to web/src/engine/predictive-text/worker-main/docs/predictive-text-sequence.png diff --git a/common/predictive-text/docs/worker-communication-protocol.md b/web/src/engine/predictive-text/worker-main/docs/worker-communication-protocol.md similarity index 100% rename from common/predictive-text/docs/worker-communication-protocol.md rename to web/src/engine/predictive-text/worker-main/docs/worker-communication-protocol.md diff --git a/common/predictive-text/package.json b/web/src/engine/predictive-text/worker-main/package.json similarity index 100% rename from common/predictive-text/package.json rename to web/src/engine/predictive-text/worker-main/package.json diff --git a/common/predictive-text/src/index.ts b/web/src/engine/predictive-text/worker-main/src/index.ts similarity index 100% rename from common/predictive-text/src/index.ts rename to web/src/engine/predictive-text/worker-main/src/index.ts diff --git a/common/predictive-text/src/lmlayer.ts b/web/src/engine/predictive-text/worker-main/src/lmlayer.ts similarity index 100% rename from common/predictive-text/src/lmlayer.ts rename to web/src/engine/predictive-text/worker-main/src/lmlayer.ts diff --git a/common/predictive-text/src/node/index.ts b/web/src/engine/predictive-text/worker-main/src/node/index.ts similarity index 100% rename from common/predictive-text/src/node/index.ts rename to web/src/engine/predictive-text/worker-main/src/node/index.ts diff --git a/common/predictive-text/src/node/mappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts similarity index 100% rename from common/predictive-text/src/node/mappedWorker.ts rename to web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts diff --git a/common/predictive-text/src/node/sourcemappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/node/sourcemappedWorker.ts similarity index 100% rename from common/predictive-text/src/node/sourcemappedWorker.ts rename to web/src/engine/predictive-text/worker-main/src/node/sourcemappedWorker.ts diff --git a/common/predictive-text/src/node/tsconfig.json b/web/src/engine/predictive-text/worker-main/src/node/tsconfig.json similarity index 89% rename from common/predictive-text/src/node/tsconfig.json rename to web/src/engine/predictive-text/worker-main/src/node/tsconfig.json index f95364c417..a1ab0f2f7c 100644 --- a/common/predictive-text/src/node/tsconfig.json +++ b/web/src/engine/predictive-text/worker-main/src/node/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../web/tsconfig.kmw-main-base.json", + "extends": "../../../../../../tsconfig.base.json", "compilerOptions": { // The Worker class needs access to the `new` keyword, else Node will throw an // error when constructing the Worker for unit tests. ES6 is sufficient. diff --git a/common/predictive-text/src/node/worker.ts b/web/src/engine/predictive-text/worker-main/src/node/worker.ts similarity index 100% rename from common/predictive-text/src/node/worker.ts rename to web/src/engine/predictive-text/worker-main/src/node/worker.ts diff --git a/common/predictive-text/src/promise-store.ts b/web/src/engine/predictive-text/worker-main/src/promise-store.ts similarity index 100% rename from common/predictive-text/src/promise-store.ts rename to web/src/engine/predictive-text/worker-main/src/promise-store.ts diff --git a/common/predictive-text/src/tsconfig.json b/web/src/engine/predictive-text/worker-main/src/tsconfig.json similarity index 54% rename from common/predictive-text/src/tsconfig.json rename to web/src/engine/predictive-text/worker-main/src/tsconfig.json index 675bf7cd53..9c7a1f47ee 100644 --- a/common/predictive-text/src/tsconfig.json +++ b/web/src/engine/predictive-text/worker-main/src/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../web/tsconfig.kmw-main-base.json", + "extends": "../../../../../tsconfig.base.json", "compilerOptions": { "baseUrl": "./", @@ -8,11 +8,9 @@ "rootDir": "./" }, "references": [ - { "path": "../../web/utils" }, - { "path": "../../models/types"}, - { "path": "../../models/wordbreakers"}, - { "path": "../../web/lm-message-types"}, - { "path": "../../models/templates"} + { "path": "../../../../../../common/web/utils" }, + { "path": "../../../../../../common/models/types"}, + { "path": "../../../../../../common/web/lm-message-types"} ], "include" : [ "./*.ts" ], "exclude" : [ diff --git a/common/predictive-text/src/unwrap.ts b/web/src/engine/predictive-text/worker-main/src/unwrap.ts similarity index 100% rename from common/predictive-text/src/unwrap.ts rename to web/src/engine/predictive-text/worker-main/src/unwrap.ts diff --git a/common/predictive-text/src/web/index.ts b/web/src/engine/predictive-text/worker-main/src/web/index.ts similarity index 100% rename from common/predictive-text/src/web/index.ts rename to web/src/engine/predictive-text/worker-main/src/web/index.ts diff --git a/common/predictive-text/src/web/sourcemappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/web/sourcemappedWorker.ts similarity index 100% rename from common/predictive-text/src/web/sourcemappedWorker.ts rename to web/src/engine/predictive-text/worker-main/src/web/sourcemappedWorker.ts diff --git a/common/predictive-text/src/web/tsconfig.json b/web/src/engine/predictive-text/worker-main/src/web/tsconfig.json similarity index 83% rename from common/predictive-text/src/web/tsconfig.json rename to web/src/engine/predictive-text/worker-main/src/web/tsconfig.json index c16936db12..769c97d589 100644 --- a/common/predictive-text/src/web/tsconfig.json +++ b/web/src/engine/predictive-text/worker-main/src/web/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../web/tsconfig.kmw-main-base.json", + "extends": "../../../../../../tsconfig.base.json", "compilerOptions": { "lib": ["es6", "DOM"], "baseUrl": "../", diff --git a/common/predictive-text/src/web/worker.ts b/web/src/engine/predictive-text/worker-main/src/web/worker.ts similarity index 100% rename from common/predictive-text/src/web/worker.ts rename to web/src/engine/predictive-text/worker-main/src/web/worker.ts diff --git a/common/predictive-text/src/worker-interface.d.ts b/web/src/engine/predictive-text/worker-main/src/worker-interface.d.ts similarity index 100% rename from common/predictive-text/src/worker-interface.d.ts rename to web/src/engine/predictive-text/worker-main/src/worker-interface.d.ts diff --git a/common/predictive-text/tsconfig.all.json b/web/src/engine/predictive-text/worker-main/tsconfig.all.json similarity index 70% rename from common/predictive-text/tsconfig.all.json rename to web/src/engine/predictive-text/worker-main/tsconfig.all.json index f47b208eda..f86c081542 100644 --- a/common/predictive-text/tsconfig.all.json +++ b/web/src/engine/predictive-text/worker-main/tsconfig.all.json @@ -3,7 +3,7 @@ // require separate sub-tsconfigs, both of which require common references. The multi-config // setup in this project and its subfolders ensure all modules can compile cleanly and in the // necessary compilation order. - "extends": "../web/tsconfig.kmw-main-base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "baseUrl": "./", @@ -13,11 +13,9 @@ }, "files": [], "references": [ - { "path": "../web/utils" }, - { "path": "../models/types"}, - { "path": "../models/wordbreakers"}, - { "path": "../web/lm-message-types"}, - { "path": "../models/templates"}, + { "path": "../../../../../common/web/utils" }, + { "path": "../../../../../common/models/types"}, + { "path": "../../../../../common/web/lm-message-types"}, { "path": "src/node" }, { "path": "src/web" } ], diff --git a/common/predictive-text/unit_tests/headless/promise-store.js b/web/src/engine/predictive-text/worker-main/unit_tests/headless/promise-store.js similarity index 100% rename from common/predictive-text/unit_tests/headless/promise-store.js rename to web/src/engine/predictive-text/worker-main/unit_tests/headless/promise-store.js diff --git a/common/predictive-text/unit_tests/headless/top-level-lmlayer.js b/web/src/engine/predictive-text/worker-main/unit_tests/headless/top-level-lmlayer.js similarity index 100% rename from common/predictive-text/unit_tests/headless/top-level-lmlayer.js rename to web/src/engine/predictive-text/worker-main/unit_tests/headless/top-level-lmlayer.js diff --git a/common/predictive-text/unit_tests/headless/worker-dummy-integration.js b/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-dummy-integration.js similarity index 100% rename from common/predictive-text/unit_tests/headless/worker-dummy-integration.js rename to web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-dummy-integration.js diff --git a/common/predictive-text/unit_tests/headless/worker-trie-integration.js b/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-trie-integration.js similarity index 100% rename from common/predictive-text/unit_tests/headless/worker-trie-integration.js rename to web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-trie-integration.js diff --git a/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts similarity index 100% rename from common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts rename to web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts similarity index 100% rename from common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts rename to web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.spec.ts b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-trie-integration.spec.ts similarity index 100% rename from common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.spec.ts rename to web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-trie-integration.spec.ts diff --git a/common/predictive-text/unit_tests/in_browser/helpers.mjs b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/helpers.mjs similarity index 100% rename from common/predictive-text/unit_tests/in_browser/helpers.mjs rename to web/src/engine/predictive-text/worker-main/unit_tests/in_browser/helpers.mjs diff --git a/common/predictive-text/unit_tests/in_browser/web-test-runner.CI.config.mjs b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/web-test-runner.CI.config.mjs similarity index 100% rename from common/predictive-text/unit_tests/in_browser/web-test-runner.CI.config.mjs rename to web/src/engine/predictive-text/worker-main/unit_tests/in_browser/web-test-runner.CI.config.mjs diff --git a/common/predictive-text/unit_tests/in_browser/web-test-runner.config.mjs b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/web-test-runner.config.mjs similarity index 96% rename from common/predictive-text/unit_tests/in_browser/web-test-runner.config.mjs rename to web/src/engine/predictive-text/worker-main/unit_tests/in_browser/web-test-runner.config.mjs index 523cd25457..6f9df64ddb 100644 --- a/common/predictive-text/unit_tests/in_browser/web-test-runner.config.mjs +++ b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/web-test-runner.config.mjs @@ -7,7 +7,7 @@ import { dirname, resolve } from 'path'; import { sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; const dir = dirname(fileURLToPath(import.meta.url)); -const KEYMAN_ROOT = resolve(dir, '../../../../'); +const KEYMAN_ROOT = resolve(dir, '../../../../../../../'); /** @type {import('@web/test-runner').TestRunnerConfig} */ export default { diff --git a/common/predictive-text/unit_tests/test.sh b/web/src/engine/predictive-text/worker-main/unit_tests/test.sh similarity index 98% rename from common/predictive-text/unit_tests/test.sh rename to web/src/engine/predictive-text/worker-main/unit_tests/test.sh index 3eb87bae56..007bf36357 100755 --- a/common/predictive-text/unit_tests/test.sh +++ b/web/src/engine/predictive-text/worker-main/unit_tests/test.sh @@ -3,7 +3,7 @@ ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" +. "${THIS_SCRIPT%/*}/../../../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" diff --git a/web/src/test/auto/dom/cases/browser/contextManager.spec.ts b/web/src/test/auto/dom/cases/browser/contextManager.spec.ts index f31cc45318..eabf6eefdd 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.spec.ts +++ b/web/src/test/auto/dom/cases/browser/contextManager.spec.ts @@ -3,10 +3,10 @@ import { outputTargetForElement } from 'keyman/engine/attachment'; import { LegacyEventEmitter } from 'keyman/engine/events'; -import { StubAndKeyboardCache, toPrefixedKeyboardId as prefixed } from 'keyman/engine/package-cache'; +import { StubAndKeyboardCache, toPrefixedKeyboardId as prefixed } from 'keyman/engine/keyboard-storage'; -import { KeyboardHarness, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { DOMKeyboardLoader } from '@keymanapp/keyboard-processor/dom-keyboard-loader'; +import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader'; import { loadKeyboardsFromStubs } from '../../kbdLoader.js'; import { DeviceSpec, ManagedPromise, timedPromise } from '@keymanapp/web-utils'; diff --git a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts index cee86cc35e..cdfc8814af 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/element_interfaces.spec.ts @@ -1,6 +1,7 @@ import { assert } from 'chai'; -import { extendString, Mock } from '@keymanapp/keyboard-processor'; +import { extendString } from 'keyman/engine/keyboard'; +import { Mock } from 'keyman/engine/js-processor'; import * as wrappers from 'keyman/engine/element-wrappers'; import { DynamicElements } from '../../test_utils.js'; @@ -1385,7 +1386,7 @@ describe('Element Input/Output Interfacing', function () { // Unique to the Mock type - element interface cloning tests. Is element state properly copied? // As those require a very different setup, they're in the target_mocks.js test case file instead. - // Basic text-retrieval unit tests are now done headlessly in @keymanapp/keyboard-processor. + // Basic text-retrieval unit tests are now done headlessly in keyman/engine/keyboard. describe('Text Mutation', function () { describe('deleteCharsBeforeCaret', function () { diff --git a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts index e850efaa55..707d863899 100644 --- a/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts +++ b/web/src/test/auto/dom/cases/element-wrappers/target_mocks.spec.ts @@ -1,6 +1,7 @@ import { assert } from 'chai'; -import { extendString, Mock } from '@keymanapp/keyboard-processor'; +import { extendString } from 'keyman/engine/keyboard'; +import { Mock } from 'keyman/engine/js-processor'; import { Input } from 'keyman/engine/element-wrappers'; import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; diff --git a/web/src/test/auto/dom/cases/packages/cloudQueries.spec.ts b/web/src/test/auto/dom/cases/keyboard-storage/cloudQueries.spec.ts similarity index 98% rename from web/src/test/auto/dom/cases/packages/cloudQueries.spec.ts rename to web/src/test/auto/dom/cases/keyboard-storage/cloudQueries.spec.ts index 9a2fe93c45..4af873b23c 100644 --- a/web/src/test/auto/dom/cases/packages/cloudQueries.spec.ts +++ b/web/src/test/auto/dom/cases/keyboard-storage/cloudQueries.spec.ts @@ -1,9 +1,9 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import { CloudQueryEngine, type KeyboardStub } from 'keyman/engine/package-cache'; +import { CloudQueryEngine, type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { PathConfiguration } from 'keyman/engine/interfaces'; -import DOMCloudRequester from 'keyman/engine/package-cache/dom-requester'; +import DOMCloudRequester from 'keyman/engine/keyboard-storage/dom-requester'; import { ManagedPromise } from '@keymanapp/web-utils'; const pathConfig = new PathConfiguration({ diff --git a/web/src/test/auto/dom/cases/packages/domCloudRequester.spec.ts b/web/src/test/auto/dom/cases/keyboard-storage/domCloudRequester.spec.ts similarity index 97% rename from web/src/test/auto/dom/cases/packages/domCloudRequester.spec.ts rename to web/src/test/auto/dom/cases/keyboard-storage/domCloudRequester.spec.ts index 840acf9d99..7101bfd9f7 100644 --- a/web/src/test/auto/dom/cases/packages/domCloudRequester.spec.ts +++ b/web/src/test/auto/dom/cases/keyboard-storage/domCloudRequester.spec.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import DOMCloudRequester from 'keyman/engine/package-cache/dom-requester'; +import DOMCloudRequester from 'keyman/engine/keyboard-storage/dom-requester'; describe("Mocked cloud query results ('canary' testing)", () => { function performMockedRequest(mockedResultsPath: string) { diff --git a/web/src/test/auto/dom/cases/packages/keyboardRequisitioner.spec.ts b/web/src/test/auto/dom/cases/keyboard-storage/keyboardRequisitioner.spec.ts similarity index 92% rename from web/src/test/auto/dom/cases/packages/keyboardRequisitioner.spec.ts rename to web/src/test/auto/dom/cases/keyboard-storage/keyboardRequisitioner.spec.ts index ab585bc9e0..bada557bf8 100644 --- a/web/src/test/auto/dom/cases/packages/keyboardRequisitioner.spec.ts +++ b/web/src/test/auto/dom/cases/keyboard-storage/keyboardRequisitioner.spec.ts @@ -1,11 +1,11 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import { KeyboardHarness, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { DOMKeyboardLoader } from '@keymanapp/keyboard-processor/dom-keyboard-loader'; +import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader'; import { PathConfiguration } from 'keyman/engine/interfaces'; -import { CloudQueryEngine, KeyboardRequisitioner, type KeyboardStub } from 'keyman/engine/package-cache'; -import DOMCloudRequester from 'keyman/engine/package-cache/dom-requester'; +import { CloudQueryEngine, KeyboardRequisitioner, type KeyboardStub } from 'keyman/engine/keyboard-storage'; +import DOMCloudRequester from 'keyman/engine/keyboard-storage/dom-requester'; const pathConfig = new PathConfiguration({ root: '', @@ -14,7 +14,7 @@ const pathConfig = new PathConfiguration({ fonts: '', // The primary / top-level module here is the keyboard-cache bundle. // So, we set up our path config based upon that module for verification purposes. -}, 'http://localhost:9876/@keymanapp/keyman/build/engine/package-cache/lib'); // TODO: set up the current path like actual KMW does it. +}, 'http://localhost:9876/@keymanapp/keyman/build/engine/keyboard-storage/lib'); // TODO: set up the current path like actual KMW does it. /** * Performs mocking setup to facilitate unit testing for the `CloudQueryEngine` class. diff --git a/common/web/keyboard-processor/tests/dom/cases/domKeyboardLoader.spec.ts b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts similarity index 93% rename from common/web/keyboard-processor/tests/dom/cases/domKeyboardLoader.spec.ts rename to web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts index 1965adc853..da610402eb 100644 --- a/common/web/keyboard-processor/tests/dom/cases/domKeyboardLoader.spec.ts +++ b/web/src/test/auto/dom/cases/keyboard/domKeyboardLoader.spec.ts @@ -1,7 +1,8 @@ import { assert } from 'chai'; -import { DOMKeyboardLoader } from '@keymanapp/keyboard-processor/dom-keyboard-loader'; -import { extendString, KeyboardHarness, Keyboard, KeyboardInterface, MinimalKeymanGlobal, Mock, DeviceSpec, KeyboardKeymanGlobal } from '@keymanapp/keyboard-processor'; +import { DOMKeyboardLoader } from 'keyman/engine/keyboard/dom-keyboard-loader'; +import { extendString, KeyboardHarness, Keyboard, MinimalKeymanGlobal, DeviceSpec, KeyboardKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; declare let window: typeof globalThis; // KeymanEngine from the web/ folder... when available. diff --git a/web/src/test/auto/dom/kbdLoader.ts b/web/src/test/auto/dom/kbdLoader.ts index 629ade205e..b0d092c12d 100644 --- a/web/src/test/auto/dom/kbdLoader.ts +++ b/web/src/test/auto/dom/kbdLoader.ts @@ -1,15 +1,15 @@ import { DOMKeyboardLoader -} from '@keymanapp/keyboard-processor/dom-keyboard-loader'; +} from 'keyman/engine/keyboard/dom-keyboard-loader'; import { Keyboard, - KeyboardInterface, KeyboardProperties, MinimalKeymanGlobal -} from '@keymanapp/keyboard-processor'; +} from 'keyman/engine/keyboard'; -import { KeyboardStub } from 'keyman/engine/package-cache'; +import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { KeyboardStub } from 'keyman/engine/keyboard-storage'; const loader = new DOMKeyboardLoader(new KeyboardInterface(window, MinimalKeymanGlobal)); diff --git a/web/src/test/auto/dom/web-test-runner.config.mjs b/web/src/test/auto/dom/web-test-runner.config.mjs index 1cd8ed0ca2..62edfc7dbf 100644 --- a/web/src/test/auto/dom/web-test-runner.config.mjs +++ b/web/src/test/auto/dom/web-test-runner.config.mjs @@ -56,9 +56,9 @@ export default { files: ['build/test/dom/cases/osk/**/*.spec.mjs'] }, { - name: 'engine/package-cache', + name: 'engine/keyboard-storage', // Relative, from the containing package.json - files: ['build/test/dom/cases/packages/**/*.spec.mjs'] + files: ['build/test/dom/cases/keyboard-storage/**/*.spec.mjs'] } ], middleware: [ diff --git a/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts b/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts index 447b2b1806..d00425362d 100644 --- a/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts +++ b/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts @@ -4,14 +4,14 @@ import { preprocessKeyboardEvent } from 'keyman/app/browser'; import { processForMnemonicsAndLegacy } from 'keyman/engine/main'; import { PhysicalInputEventSpec } from '@keymanapp/recorder-core'; import { DeviceSpec } from '@keymanapp/web-utils'; -import { Codes, Keyboard, KeyEvent } from '@keymanapp/keyboard-processor'; +import { Codes, Keyboard, KeyEvent } from 'keyman/engine/keyboard'; const ModifierCodes = Codes.modifierCodes; const KeyCodes = Codes.keyCodes; const DUMMY_DEVICE = new DeviceSpec('chrome', 'desktop', 'windows', false); -// Compare and contrast the unit tests here with those for keyboard-processor unit testing +// Compare and contrast the unit tests here with those for keyboard unit testing // in the non-positional-rules set; the output objects here should have the same format // as the inputs for rules as used there. diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js index 6bd0f7555c..f0d5a5a6db 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js @@ -4,7 +4,8 @@ import sinon from 'sinon'; import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { PredictionContext } from 'keyman/engine/interfaces'; import { Worker as LMWorker } from "@keymanapp/lexical-model-layer/node"; -import { DeviceSpec, KeyboardProcessor, Mock } from '@keymanapp/keyboard-processor'; +import { DeviceSpec } from 'keyman/engine/keyboard'; +import { KeyboardProcessor, Mock } from 'keyman/engine/js-processor'; function compileDummyModel(suggestionSets) { return ` diff --git a/common/web/keyboard-processor/tests/node/basic-engine.js b/web/src/test/auto/headless/engine/js-processor/basic-engine.js similarity index 87% rename from common/web/keyboard-processor/tests/node/basic-engine.js rename to web/src/test/auto/headless/engine/js-processor/basic-engine.js index 1e7ff3a205..68eb5136e3 100644 --- a/common/web/keyboard-processor/tests/node/basic-engine.js +++ b/web/src/test/auto/headless/engine/js-processor/basic-engine.js @@ -4,8 +4,9 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardInterface, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core'; describe('Engine - Basic Simulation', function() { @@ -24,7 +25,7 @@ describe('Engine - Basic Simulation', function() { before(async function() { // -- START: Standard Recorder-based unit test loading boilerplate -- let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal)); - let keyboard = await keyboardLoader.loadKeyboardFromPath('../../test/' + testSuite.keyboard.filename); + let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename); keyboardWithHarness = keyboardLoader.harness; keyboardWithHarness.activeKeyboard = keyboard; diff --git a/common/web/keyboard-processor/tests/node/basic-init.js b/web/src/test/auto/headless/engine/js-processor/basic-init.js similarity index 92% rename from common/web/keyboard-processor/tests/node/basic-init.js rename to web/src/test/auto/headless/engine/js-processor/basic-init.js index 8f04a6a6e8..cabdb9d432 100644 --- a/common/web/keyboard-processor/tests/node/basic-init.js +++ b/web/src/test/auto/headless/engine/js-processor/basic-init.js @@ -3,8 +3,8 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardProcessor } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { KeyboardProcessor } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; global.keyman = {}; // So that keyboard-based checks against the global `keyman` succeed. // 10.0+ dependent keyboards, like khmer_angkor, will otherwise fail to load. diff --git a/common/web/keyboard-processor/tests/node/bundled-module.js b/web/src/test/auto/headless/engine/js-processor/bundled-module.js similarity index 74% rename from common/web/keyboard-processor/tests/node/bundled-module.js rename to web/src/test/auto/headless/engine/js-processor/bundled-module.js index bfff937b62..a6b80d5004 100644 --- a/common/web/keyboard-processor/tests/node/bundled-module.js +++ b/web/src/test/auto/headless/engine/js-processor/bundled-module.js @@ -1,5 +1,6 @@ import { assert } from "chai"; -import * as Package from "../../build/lib/index.mjs"; +import * as Package from "keyman/engine/js-processor"; +import * as Package2 from "keyman/engine/keyboard"; // A few small tests to ensure that the ES Module bundle was successfully constructed and is usable. @@ -12,7 +13,7 @@ var toSupplementaryPairString = function(code){ let u = toSupplementaryPairString; -describe('Bundled ES Module', function() { +describe('Bundled ES Module for js-processor', function() { describe('KeyboardProcessor', function () { it('should initialize without errors', function () { let kp = new Package.KeyboardProcessor(); @@ -40,11 +41,20 @@ describe('Bundled ES Module', function() { } }); }); +}); - describe("Imported `utils`", function() { +describe('Bundled ES Module for keyboard', function () { + describe('Keyboard', function () { + it('should initialize without errors', function () { + let kp = new Package2.Keyboard(); + assert.isNotNull(kp); + }); + }); + + describe("Imported `utils`", function () { it("should include `utils` package's Version class", () => { - let v16 = new Package.Version([16, 1]); + let v16 = new Package2.Version([16, 1]); assert.equal(v16.toString(), "16.1"); }); - }) -}); \ No newline at end of file + }); +}); diff --git a/common/web/keyboard-processor/tests/node/chirality.js b/web/src/test/auto/headless/engine/js-processor/chirality.js similarity index 97% rename from common/web/keyboard-processor/tests/node/chirality.js rename to web/src/test/auto/headless/engine/js-processor/chirality.js index 70dca3d47f..c2220b7f5d 100644 --- a/common/web/keyboard-processor/tests/node/chirality.js +++ b/web/src/test/auto/headless/engine/js-processor/chirality.js @@ -4,8 +4,9 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardInterface, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core'; import { ModifierKeyConstants } from '@keymanapp/common-types'; @@ -25,7 +26,7 @@ describe('Engine - Chirality', function() { before(async function() { // -- START: Standard Recorder-based unit test loading boilerplate -- let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal)); - let keyboard = await keyboardLoader.loadKeyboardFromPath('../../test/' + testSuite.keyboard.filename); + let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename); keyboardWithHarness = keyboardLoader.harness; keyboardWithHarness.activeKeyboard = keyboard; diff --git a/common/web/keyboard-processor/tests/node/deadkeys.js b/web/src/test/auto/headless/engine/js-processor/deadkeys.js similarity index 87% rename from common/web/keyboard-processor/tests/node/deadkeys.js rename to web/src/test/auto/headless/engine/js-processor/deadkeys.js index e2fe70837e..fd5d1457d3 100644 --- a/common/web/keyboard-processor/tests/node/deadkeys.js +++ b/web/src/test/auto/headless/engine/js-processor/deadkeys.js @@ -4,8 +4,9 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardInterface, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core'; describe('Engine - Deadkeys', function() { @@ -24,7 +25,7 @@ describe('Engine - Deadkeys', function() { before(async function() { // -- START: Standard Recorder-based unit test loading boilerplate -- let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal)); - let keyboard = await keyboardLoader.loadKeyboardFromPath('../../test/' + testSuite.keyboard.filename); + let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename); keyboardWithHarness = keyboardLoader.harness; keyboardWithHarness.activeKeyboard = keyboard; diff --git a/common/web/keyboard-processor/tests/node/engine/context.js b/web/src/test/auto/headless/engine/js-processor/engine/context.js similarity index 99% rename from common/web/keyboard-processor/tests/node/engine/context.js rename to web/src/test/auto/headless/engine/js-processor/engine/context.js index a1fe5863df..a355ead265 100644 --- a/common/web/keyboard-processor/tests/node/engine/context.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/context.js @@ -3,8 +3,9 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardInterface, KeyboardProcessor, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, KeyboardProcessor, Mock } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core'; diff --git a/common/web/keyboard-processor/tests/node/engine/notany_context.js b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.js similarity index 94% rename from common/web/keyboard-processor/tests/node/engine/notany_context.js rename to web/src/test/auto/headless/engine/js-processor/engine/notany_context.js index 2329463d46..ecc7dd92d0 100644 --- a/common/web/keyboard-processor/tests/node/engine/notany_context.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/notany_context.js @@ -3,8 +3,9 @@ import { assert } from 'chai'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardInterface, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core'; import { extendString } from '@keymanapp/web-utils'; diff --git a/common/web/keyboard-processor/tests/node/engine/stores.js b/web/src/test/auto/headless/engine/js-processor/engine/stores.js similarity index 94% rename from common/web/keyboard-processor/tests/node/engine/stores.js rename to web/src/test/auto/headless/engine/js-processor/engine/stores.js index ade1c2bd47..8174cbe077 100644 --- a/common/web/keyboard-processor/tests/node/engine/stores.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/stores.js @@ -1,6 +1,7 @@ import { assert } from 'chai'; -import { Keyboard, KeyboardProcessor } from '@keymanapp/keyboard-processor'; +import { Keyboard } from 'keyman/engine/keyboard'; +import { KeyboardProcessor } from 'keyman/engine/js-processor'; import { extendString } from '@keymanapp/web-utils'; extendString(); diff --git a/common/web/keyboard-processor/tests/node/engine/unmatched_final_group.js b/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.js similarity index 84% rename from common/web/keyboard-processor/tests/node/engine/unmatched_final_group.js rename to web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.js index 81f9765da4..8bfee041eb 100644 --- a/common/web/keyboard-processor/tests/node/engine/unmatched_final_group.js +++ b/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.js @@ -4,8 +4,9 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardInterface, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core'; describe('Engine - Unmatched Final Groups', function() { @@ -23,7 +24,7 @@ describe('Engine - Unmatched Final Groups', function() { before(async function() { // -- START: Standard Recorder-based unit test loading boilerplate -- let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal)); - const keyboard = await keyboardLoader.loadKeyboardFromPath('../../test/' + testSuite.keyboard.filename); + const keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename); keyboardWithHarness = keyboardLoader.harness; keyboardWithHarness.activeKeyboard = keyboard; diff --git a/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js new file mode 100644 index 0000000000..0d08496442 --- /dev/null +++ b/web/src/test/auto/headless/engine/js-processor/kbdInterface.tests.js @@ -0,0 +1,91 @@ +import { assert } from 'chai'; + +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +import { MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; + +describe('Headless keyboard loading', function () { + const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js'); + const khmerPath = require.resolve('@keymanapp/common-test-resources/keyboards/khmer_angkor.js'); + const nonKeyboardPath = require.resolve('@keymanapp/common-test-resources/index.mjs'); + const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js'); + // Common test suite setup. + + let device = { + formFactor: 'desktop', + OS: 'windows', + browser: 'native' + } + + describe('Full harness loading', () => { + it('successfully loads', async function () { + // -- START: Standard Recorder-based unit test loading boilerplate -- + let harness = new KeyboardInterface({}, MinimalKeymanGlobal); + let keyboardLoader = new NodeKeyboardLoader(harness); + let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath); + harness.activeKeyboard = keyboard; + // -- END: Standard Recorder-based unit test loading boilerplate -- + + // This part provides assurance that the keyboard properly loaded. + assert.equal(keyboard.id, "Keyboard_lao_2008_basic"); + }); + + it('can evaluate rules', async function () { + // -- START: Standard Recorder-based unit test loading boilerplate -- + let harness = new KeyboardInterface({}, MinimalKeymanGlobal); + let keyboardLoader = new NodeKeyboardLoader(harness); + let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath); + harness.activeKeyboard = keyboard; + // -- END: Standard Recorder-based unit test loading boilerplate -- + + // Runs a blank KeyEvent through the keyboard's rule processing. + harness.processKeystroke(new Mock(), keyboard.constructNullKeyEvent(device)); + }); + + it('does not change the active kehboard', async function () { + let harness = new KeyboardInterface({}, MinimalKeymanGlobal); + let keyboardLoader = new NodeKeyboardLoader(harness); + const lao_keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath); + assert.isNotOk(harness.activeKeyboard); + assert.isOk(lao_keyboard); + + // This part provides assurance that the keyboard properly loaded. + assert.equal(lao_keyboard.id, "Keyboard_lao_2008_basic"); + + harness.activeKeyboard = lao_keyboard; + + const khmer_keyboard = await keyboardLoader.loadKeyboardFromPath(khmerPath); + assert.strictEqual(lao_keyboard, harness.activeKeyboard); + + assert.equal(khmer_keyboard.id, "Keyboard_khmer_angkor"); + }); + + it('throws distinct errors', async function () { + const invalidPath = 'totally_invalid_path.js'; + + let harness = new KeyboardInterface({}, MinimalKeymanGlobal); + let keyboardLoader = new NodeKeyboardLoader(harness); + let missingError; + try { + await keyboardLoader.loadKeyboardFromPath(invalidPath); + } catch (err) { + missingError = err; + } + assert.isOk(missingError); + + let scriptLoadError; + try { + await keyboardLoader.loadKeyboardFromPath(nonKeyboardPath); + } catch (err) { + scriptLoadError = err; + } + assert.isOk(scriptLoadError); + + // The main test: do the errors match? + assert.notEqual(scriptLoadError.message, missingError.message); + }); + }) +}); diff --git a/common/web/keyboard-processor/tests/node/mocks.js b/web/src/test/auto/headless/engine/js-processor/mocks.js similarity index 98% rename from common/web/keyboard-processor/tests/node/mocks.js rename to web/src/test/auto/headless/engine/js-processor/mocks.js index 9a84e2a8cc..bad81bfcca 100644 --- a/common/web/keyboard-processor/tests/node/mocks.js +++ b/web/src/test/auto/headless/engine/js-processor/mocks.js @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import { Mock } from '@keymanapp/keyboard-processor'; +import { Mock } from 'keyman/engine/js-processor'; describe('Mocks', function() { describe('app|les', () => { diff --git a/common/web/keyboard-processor/tests/node/non-positional-rules.js b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.js similarity index 97% rename from common/web/keyboard-processor/tests/node/non-positional-rules.js rename to web/src/test/auto/headless/engine/js-processor/non-positional-rules.js index aba54643ae..a65cec6708 100644 --- a/common/web/keyboard-processor/tests/node/non-positional-rules.js +++ b/web/src/test/auto/headless/engine/js-processor/non-positional-rules.js @@ -4,8 +4,9 @@ import { ModifierKeyConstants } from '@keymanapp/common-types'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { Codes, KeyboardInterface, KeyEvent, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { Codes, KeyEvent, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; // Compare and contrast the unit tests here with those for app/browser key-event unit testing // in the hardware-event-processing set; the output objects there should have the same format diff --git a/common/web/keyboard-processor/tests/node/specialized-backspace.js b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.js similarity index 98% rename from common/web/keyboard-processor/tests/node/specialized-backspace.js rename to web/src/test/auto/headless/engine/js-processor/specialized-backspace.js index db2be071e8..7e35817f07 100644 --- a/common/web/keyboard-processor/tests/node/specialized-backspace.js +++ b/web/src/test/auto/headless/engine/js-processor/specialized-backspace.js @@ -4,8 +4,9 @@ import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { Codes, KeyboardInterface, KeyboardProcessor, KeyEvent, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { Codes, KeyEvent, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, KeyboardProcessor, Mock } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { ModifierKeyConstants } from '@keymanapp/common-types'; diff --git a/common/web/keyboard-processor/tests/node/transcriptions.js b/web/src/test/auto/headless/engine/js-processor/transcriptions.js similarity index 99% rename from common/web/keyboard-processor/tests/node/transcriptions.js rename to web/src/test/auto/headless/engine/js-processor/transcriptions.js index b071532bf2..45c09b3b80 100644 --- a/common/web/keyboard-processor/tests/node/transcriptions.js +++ b/web/src/test/auto/headless/engine/js-processor/transcriptions.js @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { Mock, findCommonSubstringEndIndex } from '@keymanapp/keyboard-processor'; +import { Mock, findCommonSubstringEndIndex } from 'keyman/engine/js-processor'; import { extendString } from '@keymanapp/web-utils'; extendString(); // Ensure KMW's string-extension functionality is available. diff --git a/web/src/test/auto/headless/engine/package-cache/cloudQueries.js b/web/src/test/auto/headless/engine/keyboard-storage/cloudQueries.js similarity index 98% rename from web/src/test/auto/headless/engine/package-cache/cloudQueries.js rename to web/src/test/auto/headless/engine/keyboard-storage/cloudQueries.js index 4f379ea563..eb078c1768 100644 --- a/web/src/test/auto/headless/engine/package-cache/cloudQueries.js +++ b/web/src/test/auto/headless/engine/keyboard-storage/cloudQueries.js @@ -2,9 +2,9 @@ import { assert } from 'chai'; import sinon from 'sinon'; import { ManagedPromise } from '@keymanapp/web-utils'; -import { CloudQueryEngine, StubAndKeyboardCache, toPrefixedKeyboardId as prefixed } from 'keyman/engine/package-cache'; +import { CloudQueryEngine, StubAndKeyboardCache, toPrefixedKeyboardId as prefixed } from 'keyman/engine/keyboard-storage'; import { PathConfiguration } from 'keyman/engine/interfaces'; -import NodeCloudRequester from 'keyman/engine/package-cache/node-requester'; +import NodeCloudRequester from 'keyman/engine/keyboard-storage/node-requester'; import path from 'path'; import { fileURLToPath } from 'url'; diff --git a/web/src/test/auto/headless/engine/package-cache/keyboardRequisitioner.js b/web/src/test/auto/headless/engine/keyboard-storage/keyboardRequisitioner.js similarity index 97% rename from web/src/test/auto/headless/engine/package-cache/keyboardRequisitioner.js rename to web/src/test/auto/headless/engine/keyboard-storage/keyboardRequisitioner.js index b2d64c0553..b158f4ef9a 100644 --- a/web/src/test/auto/headless/engine/package-cache/keyboardRequisitioner.js +++ b/web/src/test/auto/headless/engine/keyboard-storage/keyboardRequisitioner.js @@ -2,14 +2,14 @@ import { assert } from 'chai'; import sinon from 'sinon'; import fs from 'fs'; -import { KeyboardHarness, ManagedPromise, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { KeyboardHarness, ManagedPromise, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardRequisitioner, toPrefixedKeyboardId as prefixed -} from 'keyman/engine/package-cache'; +} from 'keyman/engine/keyboard-storage'; import { PathConfiguration } from 'keyman/engine/interfaces'; -import NodeCloudRequester from 'keyman/engine/package-cache/node-requester'; +import NodeCloudRequester from 'keyman/engine/keyboard-storage/node-requester'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -33,7 +33,7 @@ const pathConfig = new PathConfiguration({ // Keyboard paths in fixtures are relative to the repository root. keyboards: `file:///${resolvedResourcePackage}/../../..`, // the one part NEEDED for unit tests below. fonts: '', -}, `file:///${path.dirname(require.resolve('keyman/engine/package-cache'))}`); +}, `file:///${path.dirname(require.resolve('keyman/engine/keyboard-storage'))}`); /** * Performs mocking setup to facilitate unit testing for the `CloudQueryEngine` class. diff --git a/web/src/test/auto/headless/engine/package-cache/keyboardStub.js b/web/src/test/auto/headless/engine/keyboard-storage/keyboardStub.js similarity index 96% rename from web/src/test/auto/headless/engine/package-cache/keyboardStub.js rename to web/src/test/auto/headless/engine/keyboard-storage/keyboardStub.js index 6b81e187a4..af7d6b6a57 100644 --- a/web/src/test/auto/headless/engine/package-cache/keyboardStub.js +++ b/web/src/test/auto/headless/engine/keyboard-storage/keyboardStub.js @@ -1,8 +1,8 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import { KeyboardStub } from 'keyman/engine/package-cache'; -import NodeCloudRequester from 'keyman/engine/package-cache/node-requester'; +import { KeyboardStub } from 'keyman/engine/keyboard-storage'; +import NodeCloudRequester from 'keyman/engine/keyboard-storage/node-requester'; import path from 'path'; import { fileURLToPath } from 'url'; diff --git a/web/src/test/auto/headless/engine/package-cache/nodeCloudRequester.js b/web/src/test/auto/headless/engine/keyboard-storage/nodeCloudRequester.js similarity index 97% rename from web/src/test/auto/headless/engine/package-cache/nodeCloudRequester.js rename to web/src/test/auto/headless/engine/keyboard-storage/nodeCloudRequester.js index 708781932c..07ac9e98fb 100644 --- a/web/src/test/auto/headless/engine/package-cache/nodeCloudRequester.js +++ b/web/src/test/auto/headless/engine/keyboard-storage/nodeCloudRequester.js @@ -1,7 +1,7 @@ import { assert } from 'chai'; import sinon from 'sinon'; -import NodeCloudRequester from 'keyman/engine/package-cache/node-requester'; +import NodeCloudRequester from 'keyman/engine/keyboard-storage/node-requester'; import path from 'path'; import { fileURLToPath } from 'url'; diff --git a/web/src/test/auto/headless/engine/package-cache/stubAndKeyboardCache.js b/web/src/test/auto/headless/engine/keyboard-storage/stubAndKeyboardCache.js similarity index 97% rename from web/src/test/auto/headless/engine/package-cache/stubAndKeyboardCache.js rename to web/src/test/auto/headless/engine/keyboard-storage/stubAndKeyboardCache.js index 03f6fbf5c5..7260b9982e 100644 --- a/web/src/test/auto/headless/engine/package-cache/stubAndKeyboardCache.js +++ b/web/src/test/auto/headless/engine/keyboard-storage/stubAndKeyboardCache.js @@ -2,10 +2,10 @@ import { assert } from 'chai'; import sinon from 'sinon'; import fs from 'fs'; -import { KeyboardStub, StubAndKeyboardCache } from 'keyman/engine/package-cache'; +import { KeyboardStub, StubAndKeyboardCache } from 'keyman/engine/keyboard-storage'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; -import { KeyboardHarness, MinimalKeymanGlobal } from '@keymanapp/keyboard-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; +import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; import path from 'path'; diff --git a/common/web/keyboard-processor/tests/node/keyboard-loading.js b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.js similarity index 56% rename from common/web/keyboard-processor/tests/node/keyboard-loading.js rename to web/src/test/auto/headless/engine/keyboard/keyboard-loading.js index 500780dfed..0dd89a3614 100644 --- a/common/web/keyboard-processor/tests/node/keyboard-loading.js +++ b/web/src/test/auto/headless/engine/keyboard/keyboard-loading.js @@ -1,11 +1,11 @@ import { assert } from 'chai'; -import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardHarness, KeyboardInterface, KeyboardLoaderBase, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; +import { KeyboardInterface, Mock } from 'keyman/engine/js-processor'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; describe('Headless keyboard loading', function() { const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js'); @@ -40,7 +40,7 @@ describe('Headless keyboard loading', function() { it('successfully loads (has variable stores)', async () => { // -- START: Standard Recorder-based unit test loading boilerplate -- - let harness = new KeyboardInterface({}, MinimalKeymanGlobal); + let harness = new KeyboardHarness({}, MinimalKeymanGlobal); let keyboardLoader = new NodeKeyboardLoader(harness); let keyboard = await keyboardLoader.loadKeyboardFromPath(ipaPath); // -- END: Standard Recorder-based unit test loading boilerplate -- @@ -91,73 +91,4 @@ describe('Headless keyboard loading', function() { assert.isFalse(mobileLayout.hasMultitaps); }); }); - - describe('Full harness loading', () => { - it('successfully loads', async function() { - // -- START: Standard Recorder-based unit test loading boilerplate -- - let harness = new KeyboardInterface({}, MinimalKeymanGlobal); - let keyboardLoader = new NodeKeyboardLoader(harness); - let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath); - harness.activeKeyboard = keyboard; - // -- END: Standard Recorder-based unit test loading boilerplate -- - - // This part provides assurance that the keyboard properly loaded. - assert.equal(keyboard.id, "Keyboard_lao_2008_basic"); - }); - - it('can evaluate rules', async function() { - // -- START: Standard Recorder-based unit test loading boilerplate -- - let harness = new KeyboardInterface({}, MinimalKeymanGlobal); - let keyboardLoader = new NodeKeyboardLoader(harness); - let keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath); - harness.activeKeyboard = keyboard; - // -- END: Standard Recorder-based unit test loading boilerplate -- - - // Runs a blank KeyEvent through the keyboard's rule processing. - harness.processKeystroke(new Mock(), keyboard.constructNullKeyEvent(device)); - }); - - it('does not change the active kehboard', async function() { - let harness = new KeyboardInterface({}, MinimalKeymanGlobal); - let keyboardLoader = new NodeKeyboardLoader(harness); - const lao_keyboard = await keyboardLoader.loadKeyboardFromPath(laoPath); - assert.isNotOk(harness.activeKeyboard); - assert.isOk(lao_keyboard); - - // This part provides assurance that the keyboard properly loaded. - assert.equal(lao_keyboard.id, "Keyboard_lao_2008_basic"); - - harness.activeKeyboard = lao_keyboard; - - const khmer_keyboard = await keyboardLoader.loadKeyboardFromPath(khmerPath); - assert.strictEqual(lao_keyboard, harness.activeKeyboard); - - assert.equal(khmer_keyboard.id, "Keyboard_khmer_angkor"); - }); - - it('throws distinct errors', async function() { - const invalidPath = 'totally_invalid_path.js'; - - let harness = new KeyboardInterface({}, MinimalKeymanGlobal); - let keyboardLoader = new NodeKeyboardLoader(harness); - let missingError; - try { - await keyboardLoader.loadKeyboardFromPath(invalidPath); - } catch (err) { - missingError = err; - } - assert.isOk(missingError); - - let scriptLoadError; - try { - await keyboardLoader.loadKeyboardFromPath(nonKeyboardPath); - } catch (err) { - scriptLoadError = err; - } - assert.isOk(scriptLoadError); - - // The main test: do the errors match? - assert.notEqual(scriptLoadError.message, missingError.message); - }); - }) -}); \ No newline at end of file +}); diff --git a/common/web/keyboard-processor/tests/node/keyboard-properties.js b/web/src/test/auto/headless/engine/keyboard/keyboard-properties.js similarity index 98% rename from common/web/keyboard-processor/tests/node/keyboard-properties.js rename to web/src/test/auto/headless/engine/keyboard/keyboard-properties.js index b6ec2dbf8d..f23d56e3b5 100644 --- a/common/web/keyboard-processor/tests/node/keyboard-properties.js +++ b/web/src/test/auto/headless/engine/keyboard/keyboard-properties.js @@ -5,7 +5,7 @@ import path from 'path'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -import { KeyboardProperties, SpacebarText } from '@keymanapp/keyboard-processor'; +import { KeyboardProperties, SpacebarText } from 'keyman/engine/keyboard'; describe('Keyboard Properties', function() { let commonResourcesPackage = '@keymanapp/common-test-resources'; diff --git a/web/src/test/auto/headless/engine/main/headless/inputProcessor.spec.js b/web/src/test/auto/headless/engine/main/headless/inputProcessor.spec.js index d1b728e625..56aa2f3e5e 100644 --- a/web/src/test/auto/headless/engine/main/headless/inputProcessor.spec.js +++ b/web/src/test/auto/headless/engine/main/headless/inputProcessor.spec.js @@ -5,8 +5,8 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { InputProcessor } from 'keyman/engine/main'; -import { KeyboardInterface, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; -import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; +import { KeyboardInterface, MinimalKeymanGlobal, Mock } from 'keyman/engine/keyboard'; +import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'; import { KeyboardTest } from '@keymanapp/recorder-core'; import { Worker } from '@keymanapp/lexical-model-layer/node'; @@ -48,8 +48,8 @@ describe('InputProcessor', function() { assert.isUndefined(core.activeKeyboard); // No keyboard should be loaded yet. assert.isUndefined(core.activeModel); // Same for the model. - // These checks are lifted from the keyboard-processor init checks found in - // common/web/keyboard-processor/tests/cases/basic-init.js. + // These checks are lifted from the keyboard init checks found in + // web/src/test/auto/headless/engine/js-processor/basic-init.js. assert.equal('us', core.keyboardProcessor.baseLayout, 'KeyboardProcessor has unexpected base layout') assert.isNotNull(global.KeymanWeb, 'KeymanWeb global was not automatically installed'); assert.equal('default', core.keyboardProcessor.layerId, 'Default layer is not set to "default"'); diff --git a/web/src/test/auto/headless/engine/main/headless/languageProcessor.spec.js b/web/src/test/auto/headless/engine/main/headless/languageProcessor.spec.js index a0eec4c531..e4572de21d 100644 --- a/web/src/test/auto/headless/engine/main/headless/languageProcessor.spec.js +++ b/web/src/test/auto/headless/engine/main/headless/languageProcessor.spec.js @@ -2,7 +2,7 @@ import { assert } from 'chai'; import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main'; import { SourcemappedWorker as LMWorker } from "@keymanapp/lexical-model-layer/node"; -import { Mock } from '@keymanapp/keyboard-processor'; +import { Mock } from 'keyman/engine/keyboard'; /* * Unit tests for the Dummy prediction model. @@ -43,8 +43,8 @@ describe('LanguageProcessor', function() { it('has expected default values after initialization', function () { let languageProcessor = new LanguageProcessor(worker, new TranscriptionCache()); - // These checks are lifted from the keyboard-processor init checks found in - // common/web/keyboard-processor/tests/cases/basic-init.js. + // These checks are lifted from the keyboard init checks found in + // web/src/test/auto/headless/engine/js-processor/basic-init.js. assert.isDefined(languageProcessor.lmEngine); assert.isUndefined(languageProcessor.activeModel); assert.isFalse(languageProcessor.isActive); diff --git a/web/src/test/auto/headless/engine/osk/input/gestures/browser/subkeyPopup.tests.ts b/web/src/test/auto/headless/engine/osk/input/gestures/browser/subkeyPopup.tests.ts index b70c17d7dc..72679897f3 100644 --- a/web/src/test/auto/headless/engine/osk/input/gestures/browser/subkeyPopup.tests.ts +++ b/web/src/test/auto/headless/engine/osk/input/gestures/browser/subkeyPopup.tests.ts @@ -4,7 +4,7 @@ import { JSDOM } from 'jsdom'; import sinon from 'sinon'; import { GesturePath, GestureSequence, GestureSource, GestureSourceSubview } from '@keymanapp/gesture-recognizer'; -import { ActiveSubKey, DeviceSpec } from '@keymanapp/keyboard-processor'; +import { ActiveSubKey, DeviceSpec } from 'keyman/engine/keyboard'; import { DEFAULT_GESTURE_PARAMS, KeyElement, VisualKeyboard } from 'keyman/engine/osk'; import { OSKBaseKey, OSKRow, SubkeyPopup, link } from 'keyman/engine/osk/internals'; diff --git a/web/src/test/auto/integrated/cases/engine.spec.ts b/web/src/test/auto/integrated/cases/engine.spec.ts index 1ef78358a2..a7e624cbf4 100644 --- a/web/src/test/auto/integrated/cases/engine.spec.ts +++ b/web/src/test/auto/integrated/cases/engine.spec.ts @@ -9,7 +9,7 @@ import { import * as KMWRecorder from '#recorder'; import { type KeymanEngine, type KeyboardInterface } from 'keyman/app/browser'; -import { type KeyboardStub } from 'keyman/engine/package-cache'; +import { type KeyboardStub } from 'keyman/engine/keyboard-storage'; import { type OSKInputEventSpec } from '#recorder'; type WindowKey = keyof typeof window; diff --git a/web/src/test/auto/integrated/test_utils.ts b/web/src/test/auto/integrated/test_utils.ts index e3c49ef0ee..6e04885b1a 100644 --- a/web/src/test/auto/integrated/test_utils.ts +++ b/web/src/test/auto/integrated/test_utils.ts @@ -3,7 +3,7 @@ import Device from 'keyman/engine/device-detect'; import * as KMWRecorder from '#recorder'; import { type BrowserInitOptionSpec, type KeymanEngine } from 'keyman/app/browser'; -import { ErrorStub, type KeyboardAPISpec, type KeyboardStub } from 'keyman/engine/package-cache'; +import { ErrorStub, type KeyboardAPISpec, type KeyboardStub } from 'keyman/engine/keyboard-storage'; type WindowKey = keyof typeof window; const keyman_window = 'keyman' as WindowKey; diff --git a/common/test/predictive-text/.gitignore b/web/src/test/manual/predictive-text/.gitignore similarity index 100% rename from common/test/predictive-text/.gitignore rename to web/src/test/manual/predictive-text/.gitignore diff --git a/common/test/predictive-text/README.md b/web/src/test/manual/predictive-text/README.md similarity index 96% rename from common/test/predictive-text/README.md rename to web/src/test/manual/predictive-text/README.md index ddf89bb8be..0ab038ca11 100644 --- a/common/test/predictive-text/README.md +++ b/web/src/test/manual/predictive-text/README.md @@ -9,10 +9,9 @@ Install **NOTE**: Requires Node >= 10.0 First, ensure that Keyman web and the LMLayer are built. You can run the -build script in `/web/source` to do this for you: +build script in `/web` to do this for you: - cd ../../../web/source - ./build.sh + ../../../../web/build.sh Then, you can install locally with `npm`: diff --git a/common/test/predictive-text/index.js b/web/src/test/manual/predictive-text/index.js similarity index 99% rename from common/test/predictive-text/index.js rename to web/src/test/manual/predictive-text/index.js index 934dcf67a6..605ed2939c 100755 --- a/common/test/predictive-text/index.js +++ b/web/src/test/manual/predictive-text/index.js @@ -13,7 +13,7 @@ const {EventIterator} = require('event-iterator'); const program = require('commander'); // Load the most recent LMLayer code locally. -const LMLayer = require('../../predictive-text'); +const LMLayer = require('../../../engine/predictive-text/worker-main'); ///////////////////////////////// Constants ///////////////////////////////// @@ -331,7 +331,7 @@ async function asyncRepl(modelFile) { function createAsyncWorker() { // XXX: import the LMLayerWorker directly -- I know where it is built. - const LMLayerWorker = require('../../predictive-text/build/intermediate'); + const LMLayerWorker = require('../../../engine/predictive-text/build/intermediate'); const vm = require('vm'); let worker = { diff --git a/common/test/predictive-text/package.json b/web/src/test/manual/predictive-text/package.json similarity index 100% rename from common/test/predictive-text/package.json rename to web/src/test/manual/predictive-text/package.json diff --git a/web/src/test/manual/web/chirality/chirality.js b/web/src/test/manual/web/chirality/chirality.js index eaf349463c..68740ffc30 100644 --- a/web/src/test/manual/web/chirality/chirality.js +++ b/web/src/test/manual/web/chirality/chirality.js @@ -10,10 +10,10 @@ */ function Keyboard_chirality() { - // Refer to $KEYMAN_ROOT/common/web/keyboard-processor/src/text/codes.ts, same method name. + // Refer to $KEYMAN_ROOT/web/src/engine/keyboard/src/text/codes.ts, same method name. // May be moved within common/web/types at some point in the future. var getModifierState = function(layerId) { - // Refer to C:\keymanapp\keyman\common\web\keyboard-processor\src\keyboards\keyboardHarness.ts, + // Refer to C:\keymanapp\keyman\web\src\engine\keyboard\src\keyboards\keyboardHarness.ts, // `MinimalKeyboardHarness`. let osk = keyman.osk; // Codes endpoint, as part of the standard keyboard harness. @@ -86,7 +86,7 @@ function Keyboard_chirality() { this.g0 = function (t, e) { var k = KeymanWeb, r = 0, m = 0; - // Refer to C:\keymanapp\keyman\common\web\keyboard-processor\src\keyboards\keyboardHarness.ts, + // Refer to C:\keymanapp\keyman\web\src\engine\keyboard\src\keyboards\keyboardHarness.ts, // `MinimalKeyboardHarness`. var Constants = keyman.osk; // Holds anchor points for relevant Codes properties. diff --git a/web/src/test/manual/web/osk/kbdLoader.mjs b/web/src/test/manual/web/osk/kbdLoader.mjs index a9db786c33..edf2d3a648 100644 --- a/web/src/test/manual/web/osk/kbdLoader.mjs +++ b/web/src/test/manual/web/osk/kbdLoader.mjs @@ -1,16 +1,16 @@ import { Keyboard, KeyboardProperties, Codes } from '../../../../../build/engine/osk/lib/index.mjs'; // // The following block would be sufficient to replace the `loadKeyboardFromPath` func below... -// // were it not for common/web/keyboard-processor being outside of the standard `localhost` config +// // were it not for web/src/engine/keyboard/ being outside of the standard `localhost` config // // used for manual Web testing. // import { // DOMKeyboardLoader -// } from '../../../../../../common/web/keyboard-processor/build/lib/dom-keyboard-loader.mjs'; +// } from '../../../keyboard/build/lib/dom-keyboard-loader.mjs'; // import { // KeyboardInterface, // MinimalKeymanGlobal -// } from '../../../../../../common/web/keyboard-processor/build/lib/index.mjs'; +// } from '../../../keyboard/build/lib/index.mjs'; // // This script may or may not be temporary; the KMW "KeyboardManager" class may be spun off in a manner // // that could replace this. diff --git a/web/src/tools/build.sh b/web/src/tools/build.sh index 24d455bc8f..b7670418ab 100755 --- a/web/src/tools/build.sh +++ b/web/src/tools/build.sh @@ -14,7 +14,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Builds the Keyman Engine for Web's development & unit-testing tools" \ "@/common/web/keyman-version" \ - "@/common/web/keyboard-processor" \ + "@/web/src/engine/keyboard" \ "configure" \ "clean" \ "build" \ diff --git a/web/src/tools/testing/recorder-core/package.json b/web/src/tools/testing/recorder-core/package.json index 4812d8a778..426595e567 100644 --- a/web/src/tools/testing/recorder-core/package.json +++ b/web/src/tools/testing/recorder-core/package.json @@ -19,7 +19,6 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "*", "@keymanapp/models-types": "*", "@keymanapp/keyman-version": "*", "@keymanapp/web-utils": "*" diff --git a/web/src/tools/testing/recorder-core/src/index.ts b/web/src/tools/testing/recorder-core/src/index.ts index 94c2c9dc4d..2a73971023 100644 --- a/web/src/tools/testing/recorder-core/src/index.ts +++ b/web/src/tools/testing/recorder-core/src/index.ts @@ -1,5 +1,5 @@ -import { type OutputTarget } from "@keymanapp/keyboard-processor"; -import { KeyDistribution, KeyEvent, Mock } from "@keymanapp/keyboard-processor"; +import { Mock, type OutputTarget } from "keyman/engine/js-processor"; +import { KeyDistribution, KeyEvent } from "keyman/engine/keyboard"; import Proctor from "./proctor.js"; diff --git a/web/src/tools/testing/recorder-core/src/nodeProctor.ts b/web/src/tools/testing/recorder-core/src/nodeProctor.ts index 6f6d7fd6a0..204ef21939 100644 --- a/web/src/tools/testing/recorder-core/src/nodeProctor.ts +++ b/web/src/tools/testing/recorder-core/src/nodeProctor.ts @@ -8,8 +8,10 @@ import { RecordedSyntheticKeystroke } from "./index.js"; -import { KeyboardInterface, KeyEvent, KeyEventSpec, KeyboardProcessor, Mock, type OutputTarget, KeyboardHarness } from "@keymanapp/keyboard-processor"; +import { KeyEvent, KeyEventSpec, KeyboardHarness } from "keyman/engine/keyboard"; +import { Mock, type OutputTarget } from "keyman/engine/js-processor"; import { DeviceSpec } from "@keymanapp/web-utils"; +import { KeyboardInterface, KeyboardProcessor } from 'keyman/engine/js-processor'; export default class NodeProctor extends Proctor { private keyboardWithHarness: KeyboardHarness; diff --git a/web/src/tools/testing/recorder-core/src/proctor.ts b/web/src/tools/testing/recorder-core/src/proctor.ts index 940fa4a76b..ec4f584b04 100644 --- a/web/src/tools/testing/recorder-core/src/proctor.ts +++ b/web/src/tools/testing/recorder-core/src/proctor.ts @@ -1,5 +1,5 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; -import { type OutputTarget } from "@keymanapp/keyboard-processor"; +import { type OutputTarget } from "keyman/engine/js-processor"; import type { KeyboardTest, TestSet, TestSequence } from "./index.js"; @@ -9,7 +9,7 @@ export type AssertCallback = (s1: any, s2: any, msg?: string) => void; * Facilitates running Recorder-generated tests on various platforms. * * Note that DOM-aware KeymanWeb will implement a Browser-based version, while - * keyboard-processor and input-processor will use a Node-based version instead. + * keyboard and input-processor will use a Node-based version instead. */ export default abstract class Proctor { device: DeviceSpec; diff --git a/web/src/tools/testing/recorder-core/tsconfig.json b/web/src/tools/testing/recorder-core/tsconfig.json index a7bd08c374..920536eeee 100644 --- a/web/src/tools/testing/recorder-core/tsconfig.json +++ b/web/src/tools/testing/recorder-core/tsconfig.json @@ -16,7 +16,8 @@ "references": [ { "path": "../../../../../common/web/keyman-version" }, { "path": "../../../../../common/web/utils/" }, - { "path": "../../../../../common/web/keyboard-processor/" }, - { "path": "../../../../../common/web/lm-message-types" } + { "path": "../../../../../common/web/lm-message-types" }, + { "path": "../../../engine/js-processor" }, + { "path": "../../../engine/keyboard" }, ], } diff --git a/web/src/tools/testing/recorder/browserProctor.ts b/web/src/tools/testing/recorder/browserProctor.ts index 40e7247423..a48e5572cb 100644 --- a/web/src/tools/testing/recorder/browserProctor.ts +++ b/web/src/tools/testing/recorder/browserProctor.ts @@ -4,7 +4,7 @@ import { type DeviceSpec } from "@keymanapp/web-utils"; -import { type OutputTarget } from "@keymanapp/keyboard-processor"; +import { type OutputTarget } from "keyman/engine/js-processor"; import { type KeymanEngine } from 'keyman/app/browser'; diff --git a/web/src/tools/testing/recorder/build.sh b/web/src/tools/testing/recorder/build.sh index 28cc43a767..91f27d09e5 100755 --- a/web/src/tools/testing/recorder/build.sh +++ b/web/src/tools/testing/recorder/build.sh @@ -16,7 +16,7 @@ SUBPROJECT_NAME=tools/testing/recorder builder_describe "Builds the Keyman Engine for Web's test-sequence recording tool" \ "@/common/web/keyman-version" \ - "@/common/web/keyboard-processor" \ + "@/web/src/engine/keyboard" \ "@../recorder-core" \ "clean" \ "configure" \ diff --git a/web/src/tools/testing/recorder/scribe.ts b/web/src/tools/testing/recorder/scribe.ts index 371afadeef..c266f1a5cc 100644 --- a/web/src/tools/testing/recorder/scribe.ts +++ b/web/src/tools/testing/recorder/scribe.ts @@ -1,7 +1,7 @@ import { EventEmitter } from "eventemitter3"; -import type { KeyEvent } from "@keymanapp/keyboard-processor"; +import type { KeyEvent } from "keyman/engine/keyboard"; import { Constraint, diff --git a/web/src/tools/testing/recorder/tsconfig.json b/web/src/tools/testing/recorder/tsconfig.json index 63cb543efa..f5974b5738 100644 --- a/web/src/tools/testing/recorder/tsconfig.json +++ b/web/src/tools/testing/recorder/tsconfig.json @@ -12,9 +12,9 @@ "references": [ { "path": "../../../../../common/web/keyman-version" }, { "path": "../../../../../common/web/utils" }, - { "path": "../../../../../common/web/keyboard-processor" }, { "path": "../../../../../common/web/lm-message-types" }, { "path": "../../../../../web/src/app/browser" }, { "path": "../recorder-core" }, + { "path": "../../../engine/keyboard" }, ] }