diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 2d72e01764..8cc431f178 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -105,10 +105,7 @@ jobs: strategy: fail-fast: true matrix: - # Currently not building mantic until ibus version on mantic stabilizied - # and we can provide a patched version - # dist: [focal, jammy, lunar, mantic] - dist: [focal, jammy, lunar] + dist: [focal, jammy, lunar, mantic] arch: [amd64] runs-on: ubuntu-latest @@ -222,7 +219,7 @@ jobs: echo "::endgroup::" api_verification: - name: Verify API for libkmnkbp0.so + name: Verify API for libkeymancore.so needs: [sourcepackage, binary_packages] runs-on: ubuntu-latest @@ -245,16 +242,17 @@ jobs: - name: Verify API run: | cd linux + PKG_NAME=libkeymancore SRC_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-srcpkg/keyman_${{ needs.sourcepackage.outputs.VERSION }}-1.debian.tar.xz" \ - BIN_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-binarypkgs/libkmnkbp0-0_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+jammy1_amd64.deb" \ + BIN_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-binarypkgs/${PKG_NAME}_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+jammy1_amd64.deb" \ PKG_VERSION="${{ needs.sourcepackage.outputs.VERSION }}" \ ./scripts/deb-packaging.sh --gha verify 2>> $GITHUB_STEP_SUMMARY - name: Archive .symbols file uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: - name: libkmnkbp0-0.symbols - path: linux/debian/libkmnkbp0-0.symbols + name: libkeymancore.symbols + path: linux/debian/libkeymancore.symbols if: always() set_status: diff --git a/HISTORY.md b/HISTORY.md index 7b2d722074..a779c84f55 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,76 @@ # Keyman Version History +## 17.0.199 alpha 2023-10-26 + +* fix(developer): handle xml errors in package compiler (#9821) +* fix(developer): server download Keyman link (#9822) +* chore(common): handle invalid XML in kpj-file-reader (#9824) +* fix(developer): reduce confusion in Unicode fields in touch layout editor (#9839) + +## 17.0.198 alpha 2023-10-25 + +* chore(common): Add entries from 16.0 HISTORY.md (#9826) +* feat(core): new actions APIs (#9828) + +## 17.0.197 alpha 2023-10-24 + +* chore(linux): Rename (lib)kmnkbp to (lib)keymancore ️ (#9793) +* chore(linux): Rename `namespace kbp` to `core` ️ (#9792) +* chore(linux): rename keyboardprocessor_ldml.* to keyman_core_ldml ️ (#9791) +* chore(linux): Rename libkmnkbp0-0 package ️ (#9795) +* fix(web): proper disabling of prediction timeout for prediction unit tests (#9835) + +## 17.0.196 alpha 2023-10-20 + +* fix(mac): move keyboard menu items to main Input Menu from submenu (#9777) +* (#9728) +* (#9759) +* docs(common): macos build update (#9809) +* fix(web): enhances integrated test stability (#9718) +* chore(linux): Update packaging GHA ️ (#9812) +* chore(linux): Rename KBP to CORE ️ (#9794) +* chore(linux): Fix build by adding one character (#9817) + +## 17.0.195 alpha 2023-10-19 + +* chore(linux): Allow to collect coverage on TC (#9790) +* fix(common): don't use URL in common/web/types (#9798) +* chore: update kmp.schema.json and docs for kps schema (#9800) +* docs(windows): update text and images for windows 11 (#9689) + +## 17.0.194 alpha 2023-10-18 + +* chore(linux): Re-enable building for Ubuntu 23.10 Mantic (#9780) +* chore(linux): Add missing tests (#9783) +* fix(web): fixes touch form-factor default kbd on cookieless keymanweb.com page load (#9786) +* fix(developer): three kmc .keyboard_info generation bugs (#9784) +* fix(developer): handle invalid project folders cleanly (#9785) +* chore(linux): Fix build scripts (#9781) + +## 17.0.193 alpha 2023-10-17 + +* fix(developer): kmc crash on start (#9771) +* fix(core): don't use double newlines in debuglog (#9258) +* chore(linux): Add code coverage index page (#9758) +* feat(linux): Allow installing keyboards with arbitrary language ️ (#9756) +* fix(linux): Fix crash initializing Sentry with Python < 3.10 (#9774) +* chore(linux): Rename `kbp_state_get_intermediate_context` to `km_core…` ️ (#9775) + +## 17.0.192 alpha 2023-10-16 + +* fix(ios): missing backslash in build script (#9765) +* chore(linux): Speed up ibus-util tests (#9754) +* feat(linux): Allow loading of keyboards with arbitrary language ️ (#9735) + +## 17.0.191 alpha 2023-10-15 + +* chore: update readme for keyboard_info schema (#9746) + +## 17.0.190 alpha 2023-10-12 + +* chore(web): web build streamlining (#9743) +* chore(linux): Add code coverage reports for keyman-config and keyman-system-service (#9753) + ## 17.0.189 alpha 2023-10-11 * refactor(linux): Use auto cleanup (and fix some memory leaks) (#9648) @@ -1099,6 +1170,22 @@ * chore: move to 17.0-alpha (#7577) * chore: Move to 17.0 alpha +## 16.0.142 stable 2023-10-20 + +* chore(linux): Update debian changelog (#9326) +* chore(linux): Remove obsolete dist from uploading to launchpad (#9340) +* chore(linux): Stop building Kinetic on Jenkins (#9354) +* chore(linux): Work around build failure on Debian mips64el (#9342) +* chore(linux): Update debian changelog (#9359) +* chore(android): Update targetSDKVersion to 33 (#9494) +* chore(linux): Remove workaround for mips64el (#9519) +* chore(linux): Workaround: Don't run Wayland tests (#9530) +* chore(linux): Add `clean` target to `rules` (#9532) +* fix(linux): Prevent exception if neither USER, LOGNAME nor SUDO_USER set (#9544) +* chore(linux): Ignore tests that fail on ba-jammy-64-ta (#9556) +* chore(ios): renew certificate (#9811) +* fix(mac): move keyboard menu items to main Input Menu from submenu (#9810) + ## 16.0.141 stable 2023-07-25 * chore(mac): fix corrupt installer (#9332) @@ -1486,6 +1573,7 @@ * fix(linux): Fix upload script (#7494) * docs(linux): Update sample vscode settings (#7491) * fix(android/engine): Dismiss key preview and subkeys on globe action (#7472) +* refactor(linux): Cleanup whitespace (#7500) * chore(linux): Improve debug output (#7499) ## 16.0.84 alpha 2022-10-19 @@ -1773,7 +1861,7 @@ ## 16.0.34 alpha 2022-07-15 * fix(developer): set contextDevice in web debugger ️ (#6906) -* fix(web): postkeystroke processing should ignore key-event source ️ (#6901) +* fix(web): postkeystroke processing should ignore key-event source (#6901) * fix(web): layer-setting ops should not trigger for hardware keystroke processing ️ (#6902) * fix(developer): kmdecomp virtual character key output (#6940) * fix(developer): crash on exit when checking for updates (#6941) diff --git a/VERSION.md b/VERSION.md index c92d133b7a..0de5a09ae5 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.190 \ No newline at end of file +17.0.200 \ No newline at end of file diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index 243e5e0d63..7dbc2261d0 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -25,10 +25,10 @@ typedef KMX_WORD __attribute__((aligned(1))) KMX_WORD_unaligned; #define KMX_WORD_unaligned KMX_WORD #endif -#ifdef KMN_KBP +#ifdef KM_CORE_LIBRARY // TODO: move this to a common namespace keyman::common::kmx_file or similar in the future namespace km { -namespace kbp { +namespace core { namespace kmx { #endif @@ -402,8 +402,8 @@ static_assert(sizeof(COMP_KEY) == KEYBOARDFILEKEY_SIZE, "COMP_KEY must be KEYBOA static_assert(sizeof(COMP_GROUP) == KEYBOARDFILEGROUP_SIZE, "COMP_GROUP must be KEYBOARDFILEGROUP_SIZE bytes"); static_assert(sizeof(COMP_KEYBOARD) == KEYBOARDFILEHEADER_SIZE, "COMP_KEYBOARD must be KEYBOARDFILEHEADER_SIZE bytes"); -#ifdef KMN_KBP +#ifdef KM_CORE_LIBRARY } // namespace kmx -} // namespace kbp +} // namespace core } // namespace km #endif diff --git a/common/schemas/keyboard_info/README.md b/common/schemas/keyboard_info/README.md index 1c318ff593..f41457f93e 100644 --- a/common/schemas/keyboard_info/README.md +++ b/common/schemas/keyboard_info/README.md @@ -12,7 +12,19 @@ Documentation at https://help.keyman.com/developer/cloud/keyboard_info # .keyboard_info version history ## 2023-08-11 2.0 stable -* Removed legacyId, documentationFilename, documentationFileSize. Source vs distribution keyboard_info distinction is removed. Example key sequences are simplified. +* Removed: + - `.documentationFilename` + - `.documentationFileSize` + - `.legacyId` + `.links` + `.related[].note` + `.languages[].example` + Added: + - `.languages[].examples[]` + Modified: + - `.languages[].font`, `.languages[].oskFont`: `.source` is `[string]` + - Source .keyboard_info files are no longer needed, so source vs distribution + keyboard_info distinction is removed ## 2019-09-06 1.0.6 stable * No changes (see api.keyman.com#36 and api.keyman.com#59. Reverted in 2020-06-10.). diff --git a/common/schemas/kmp/README.md b/common/schemas/kmp/README.md new file mode 100644 index 0000000000..6c8372b779 --- /dev/null +++ b/common/schemas/kmp/README.md @@ -0,0 +1,24 @@ +# kmp.schema.json + +* kmp.json file format, metadata included in Keyman .kmp package files + +Documentation at https://help.keyman.com/developer/current-version/reference/file-types/metadata + +# kmp.schema.json version history + +## 2023-10-19 2.0 +* Add relatedPackages, options.licenseFile, options.welcomeFile, + keyboard.examples, keyboard.webOskFonts, keyboard.webDisplayFonts, + info.description (all of these formerly were stored in .keyboard_info) + +## 2019-01-31 1.1.0 +* Add lexicalModels properties (note: `version` is optional and currently unused) + +## 2018-02-13 1.0.2 +* Add rtl property for keyboard layouts + +## 2018-01-22 1.0.1 +* Remove id field as it is derived from the filename anyway + +## 2017-11-30 1.0 beta +* Initial version diff --git a/common/schemas/kmp/kmp.schema.json b/common/schemas/kmp/kmp.schema.json new file mode 100644 index 0000000000..a28aa43195 --- /dev/null +++ b/common/schemas/kmp/kmp.schema.json @@ -0,0 +1,353 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$ref": "#/definitions/package", + "definitions": { + "package": { + "type": "object", + "properties": { + "system": { + "$ref": "#/definitions/system" + }, + "options": { + "$ref": "#/definitions/options" + }, + "startMenu": { + "$ref": "#/definitions/startMenu" + }, + "strings": { + "$ref": "#/definitions/strings" + }, + "files": { + "$ref": "#/definitions/files" + }, + "keyboards": { + "$ref": "#/definitions/keyboards" + }, + "lexicalModels": { + "$ref": "#/definitions/lexicalModels" + }, + "info": { + "$ref": "#/definitions/info" + }, + "relatedPackages": { + "type": "array", + "items": { + "$ref": "#/definitions/relatedPackage" + } + } + }, + "additionalProperties": false, + "required": [ + "options", + "system" + ] + }, + "system": { + "type": "object", + "properties": { + "keymanDeveloperVersion": { + "type": "string" + }, + "fileVersion": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "fileVersion" + ] + }, + "options": { + "type": "object", + "properties": { + "readmeFile": { + "type": "string" + }, + "graphicFile": { + "type": "string" + }, + "licenseFile": { + "type": "string" + }, + "welcomeFile": { + "type": "string" + }, + "executeProgram": { + "type": "string" + }, + "msiFilename": { + "type": "string" + }, + "msiOptions": { + "type": "string" + } + }, + "additionalProperties": false + }, + "startMenu": { + "type": "object", + "properties": { + "folder": { + "type": "string" + }, + "addUninstallEntry": { + "type": "boolean" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/startMenuItem" + } + } + }, + "additionalProperties": false + }, + "startMenuItem": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "arguments": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "location": { + "type": "number" + } + }, + "additionalProperties": false, + "required": [ + "name", + "filename" + ] + }, + "strings": { + "type": "object", + "patternProperties": { + ".": { + "type": "string" + } + }, + "additionalProperties": false + }, + "files": { + "type": "array", + "items": { + "$ref": "#/definitions/file" + } + }, + "file": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "copyLocation": { + "type": "number" + } + }, + "additionalProperties": false, + "required": [ + "name", + "description" + ] + }, + "keyboards": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboard" + } + }, + "keyboard": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "oskFont": { + "type": "string" + }, + "displayFont": { + "type": "string" + }, + "rtl": { + "type": "boolean" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboardLanguage" + } + }, + "examples": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboardExample" + } + }, + "webOskFonts": { + "type": "array", + "items": { + "type": "string" + } + }, + "webDisplayFonts": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "name", + "id", + "version" + ] + }, + "keyboardLanguage": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id", + "name" + ] + }, + "info": { + "type": "object", + "properties": { + "website": { + "$ref": "#/definitions/infoItem" + }, + "version": { + "$ref": "#/definitions/infoItem" + }, + "name": { + "$ref": "#/definitions/infoItem" + }, + "copyright": { + "$ref": "#/definitions/infoItem" + }, + "author": { + "$ref": "#/definitions/infoItem" + }, + "description": { + "$ref": "#/definitions/infoItem" + } + }, + "additionalProperties": false + }, + "infoItem": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "description" + ] + }, + "lexicalModels": { + "type": "array", + "items": { + "$ref": "#/definitions/lexicalModel" + } + }, + "lexicalModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "rtl": { + "type": "boolean" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboardLanguage" + } + } + }, + "additionalProperties": false, + "required": [ + "name", + "id", + "languages" + ] + }, + "keyboardExample": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "keys": { + "type": "string" + }, + "text": { + "type": "string" + }, + "note": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id", + "keys" + ] + }, + "relatedPackage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "relationship": { + "type": "string", + "enum": ["deprecates", "related"] + } + }, + "additionalProperties": false, + "required": [ + "id", + "relationship" + ] + } + } +} \ No newline at end of file diff --git a/common/schemas/kps/README.md b/common/schemas/kps/README.md index e4aacf97d8..aea03f2a5b 100644 --- a/common/schemas/kps/README.md +++ b/common/schemas/kps/README.md @@ -1,9 +1,22 @@ # kps.xsd -Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/7.0/kps.xsd +Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/17.0/kps.xsd + +## 2023-10-19 17.0 +* Version 17.0 adds: + - LicenseFile - a .md file, usually named LICENSE.md + - WelcomeFile - a .htm file, usually named welcome.htm (later versions will support .md) + - Info/Description - a short Markdown description of the content of the package, e.g. shown in search results on keyman.com + - RelatedPackages - a list of other packages which relate to this one, or are deprecated by it + - Keyboards/Keyboard/Examples - a list of typing examples for the keyboard + - Keyboarsd/Keyboard/WebOSKFonts - a list of font filenames (not necessarily in package) suitable for rendering the on screen keyboard + - Keyboarsd/Keyboard/WebDisplayFonts - a list of font filenames (not necessarily in package) suitable for use with the keyboard +* Version 17.0 removes: + - LexicalModels/LexicalModel/Version - version information is not stored in the models, but only in the package metadata (was unused) + +## 2023-04-21 7.0.1 +* Removes LexicalModel.Version, as it was never read or written ## 2021-07-19 7.0 * Initial version 7.0 -## 2023-04-21 7.0.1 -* Removes LexicalModel.Version, as it was never read or written diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureSequence.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureSequence.ts index b06cd404b8..a8b8cedf8c 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureSequence.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureSequence.ts @@ -96,6 +96,7 @@ export class GestureSequence extends EventEmitter; private gestureConfig: GestureModelDefs; + private markedComplete: boolean = false; // Note: the first stage will be available under `stageReports` after awaiting a simple Promise.resolve(). constructor( @@ -110,6 +111,16 @@ export class GestureSequence extends EventEmitter { + if(this.pushedSelector) { + // The `popSelector` method is responsible for triggering cascading cancellations if + // there are nested GestureSequences. + // + // As this tends to affect which gestures are permitted, it's important this is done + // any time the GestureSequence is cancelled or completed, for any reason. + this.touchpointCoordinator?.popSelector(this.pushedSelector); + this.pushedSelector = null; + } + this.selector.off('rejectionwithaction', this.modelResetHandler); this.selector.dropSourcesWithIds(this.allSourceIds); @@ -193,13 +204,10 @@ export class GestureSequence extends EventEmitter extends EventEmitter extends EventEmitter src.terminate(true)); + sources.forEach((src) => src.baseSource.isPathComplete || src.baseSource.terminate(true)); + if(!this.markedComplete) { + this.markedComplete = true; + this.emit('complete'); + } } } diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts index b36ecdff4f..4d1398fdb2 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/matcherSelector.ts @@ -428,22 +428,10 @@ export class MatcherSelector extends EventEmitter) => { // Note: is only called by GestureMatcher Promises that are resolving. - /* - * If we already had a gesture stage match, this will have already been fulfilled; - * bypass all match-handling. Capturing `matchSynchronization` in a closure in this - * manner is important to ensure that the returned handler is "locked" to the - * currently-processing gesture stage. - */ - for(let synchronizer of matchSynchronizers) { - if(synchronizer.isFulfilled) { - return; - } - } + // Do not bypass match handling just because a synchronization promise is fulfilled. + // If a source was force-cancelled, cascading to a call of this handler, we still + // need to perform internal state cleanup. - /* If cancellation was requested but not pre-filtered by the synchronizer setup, replace - * the result object. The matcher's Promise may have resolved simultaneously with the - * winner but 'lost', a scenario that may require careful handling to clean up. - */ if(matcher.isCancelled) { result = { matched: false, @@ -474,11 +462,6 @@ export class MatcherSelector extends EventEmitter extends EventEmitter extends Even touchpoint.setGestureMatchInspector(buildGestureMatchInspector(selector)); - // We wait for the source to fully pass through the gesture-model spin-up phase; there's a chanc + /* We wait for the source to fully pass through the gesture-model spin-up phase; there's + * a chance that the new source will complete an existing gesture instantly without being + * locked to it, resulting in activation of a different `stateToken`. + * + * This, in turn, can affect what the initial 'item' for the new gesture will be. + */ const modelingSpinupPromise = selector.matchGesture(touchpoint, getGestureModelSet(modelDefs, selector.baseGestureSetId)); modelingSpinupPromise.then(async (selectionPromiseHost) => { this.emit('inputstart', touchpoint); @@ -181,6 +186,15 @@ export class TouchpointCoordinator extends Even touchpoint.path.on('invalidated', () => { // GestureSequence _should_ handle any other cleanup internally as fallout // from the path being cancelled. + // + // That said, it's handled asynchronously... but we can give a synchronous signal + // through the next block of code, allowing cleanup to occur earlier during + // recovery states. + + const owningSequence = this.activeGestures.find((entry) => entry.allSourceIds.includes(touchpoint.identifier)); + if(owningSequence) { + owningSequence.cancel(); + } // To consider: should it specially mark if it 'completed' due to cancellation, // or is that safe to infer from the tracked GestureSource(s)? @@ -191,8 +205,6 @@ export class TouchpointCoordinator extends Even this._activeSources = this._activeSources.splice(i, 1); }); touchpoint.path.on('complete', () => { - // TODO: on cancellation, is there any other cleanup to be done? - // Also mark the touchpoint as no longer active. let i = this._activeSources.indexOf(touchpoint); this._activeSources = this._activeSources.splice(i, 1); diff --git a/common/web/gesture-recognizer/src/engine/touchEventEngine.ts b/common/web/gesture-recognizer/src/engine/touchEventEngine.ts index fcf554cb71..9d6ac4995f 100644 --- a/common/web/gesture-recognizer/src/engine/touchEventEngine.ts +++ b/common/web/gesture-recognizer/src/engine/touchEventEngine.ts @@ -105,7 +105,7 @@ export class TouchEventEngine extends InputEv const newTouches = touchListToArray(event.changedTouches); // Maintain all touches in the `.touches` array that are NOT marked as `.changedTouches` (and therefore, new) this.maintainTouchpointsWithIds(allTouches - .filter((touch) => (newTouches.indexOf(touch) == -1)) + .filter((touch1) => newTouches.findIndex(touch2 => touch1.identifier == touch2.identifier) == -1) .map((touch) => touch.identifier) ); diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 7875999a80..35f4264683 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -1,7 +1,8 @@ import Codes from "../text/codes.js"; import KeyEvent, { KeyEventSpec } from "../text/keyEvent.js"; import KeyMapping from "../text/keyMapping.js"; -import { Layouts } from "./defaultLayouts.js"; +import type { KeyDistribution } from "../text/keyEvent.js"; +import { ButtonClasses, Layouts } from "./defaultLayouts.js"; import type { LayoutKey, LayoutSubKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from "./defaultLayouts.js"; import type Keyboard from "./keyboard.js"; @@ -69,7 +70,7 @@ export class ActiveKeyBase { static readonly DEFAULT_KEY = { text: '', width: ActiveKeyBase.DEFAULT_KEY_WIDTH, - sp: Layouts.buttonClasses.DEFAULT, + sp: ButtonClasses.normal, pad: ActiveKeyBase.DEFAULT_PAD }; @@ -139,7 +140,7 @@ export class ActiveKeyBase { public get isPadding(): boolean { // Does not include 9 (class: blank) as that may be an intentional 'catch' for misplaced // keystrokes. - return this.sp == Layouts.buttonClasses.HIDDEN; // Button class: hidden. + return this.sp == ButtonClasses.spacer; // Button class: hidden. } /** @@ -544,17 +545,17 @@ export class ActiveRow implements LayoutRow { } // Modify the key type for special keys with non-standard labels - // to allow the keyboard font to ovveride the SpecialOSK font. + // to allow the keyboard font to overide the SpecialOSK font. // Blank keys are no longer reclassed - can use before/after CSS to add text switch(key['sp']) { - case Layouts.buttonClasses.SHIFT: + case ButtonClasses.special: if(!ActiveRow.SPECIAL_LABEL.test(key['text']) && key['text'] != '') { - key.sp=Layouts.buttonClasses.SPECIAL; + key.sp=ButtonClasses.customSpecial; } break; - case Layouts.buttonClasses['SHIFT-ON']: + case ButtonClasses.specialActive: if(!ActiveRow.SPECIAL_LABEL.test(key['text']) && key['text'] != '') { - key.sp=Layouts.buttonClasses['SPECIAL-ON']; + key.sp=ButtonClasses.customSpecialActive; } break; } diff --git a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts index eb1bf29d79..8a5aff7787 100644 --- a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts +++ b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts @@ -12,6 +12,9 @@ export type LayoutRow = TouchLayout.TouchLayoutRow; export type LayoutKey = TouchLayout.TouchLayoutKey; export type LayoutSubKey = TouchLayout.TouchLayoutSubKey; +import ButtonClasses = TouchLayout.TouchLayoutKeySp; + +export { ButtonClasses }; import Codes from "../text/codes.js"; import type Keyboard from "./keyboard.js"; @@ -20,7 +23,6 @@ export type KLS = {[layerName: string]: string[]}; // The following types provide type definitions for the full JSON format we use for visual keyboard definitions. export type ButtonClass = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; -export type ButtonClassString = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10"; export interface LayoutLayer extends LayoutLayerBase { // Post-processing elements. @@ -61,19 +63,7 @@ export class Layouts { static readonly DEFAULT_RAW_SPEC = {'F':'Tahoma', 'BK': Layouts.dfltText} as const; - // Cross-reference with the ids in osk.setButtonClass. - static readonly buttonClasses: {[name: string]: ButtonClass} = { - 'DEFAULT':0, - 'SHIFT':1, // special-key / frame key styling: uses our custom, PUA OSK font - 'SHIFT-ON':2, - 'SPECIAL':3, // special-key / frame key styling: uses the keyboard's font - 'SPECIAL-ON':4, - 'DEADKEY':8, - 'BLANK':9, - 'HIDDEN':10 - }; - - static readonly modifierSpecials = { + static modifierSpecials = { 'leftalt': '*LAlt*', 'rightalt': '*RAlt*', 'alt': '*Alt*', @@ -323,7 +313,7 @@ export class Layouts { // Set modifier key appearance and behaviour for non-desktop devices using the default layout if(formFactor != 'desktop') { if(n > 0 && shiftKey != null) { - shiftKey['sp']=Layouts.buttonClasses['SHIFT-ON']; + shiftKey['sp']=ButtonClasses.specialActive; shiftKey['sk']=null; shiftKey['text'] = Layouts.modifierSpecials[layers[n].id] ? Layouts.modifierSpecials[layers[n].id] : "*Shift*"; } @@ -407,7 +397,6 @@ export class Layouts { */ static formatDefaultLayer(layer: LayoutLayer, chiral: boolean, formFactor: string, key102: boolean) { var layerId = layer['id']; - let buttonClasses = Layouts.buttonClasses; // Correct appearance of state-dependent modifier keys according to group for(var i=0; i { data = r as KPJFile }); + parser.parseString(file, (e: unknown, r: unknown) => { + if(e) { + throw e; + } + data = r as KPJFile; + }); data = this.boxArrays(data); for(let file of data.KeymanDeveloperProject?.Files?.File) { // xml2js imports
as '' so we will just delete the empty string diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts index 86a05e3893..acbcf0ead4 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts @@ -41,34 +41,41 @@ export interface LKTTests { export interface LKTTest { name?: string; startContext?: LKTStartContext; - actions?: LKTAction[]; // differs from XML, to represent order of actions + actions?: LKTAnyAction[]; // differs from XML, to represent order of actions }; export interface LKTStartContext { to?: string; }; -export interface LKTCheck { - result?: string; -}; - -export interface LKTEmit { - to?: string; -}; - -export interface LKTKeystroke { - key?: string; - flick?: string; - longPress?: string; - tapCount?: string; -}; - /** * Test Actions. * The expectation is that each LKTAction object will have exactly one non-falsy field. */ export interface LKTAction { - check?: LKTCheck; - emit?: LKTEmit; - keystroke?: LKTKeystroke; + type?: "check" | "emit" | "keystroke" | "backspace"; }; + +export interface LKTCheck extends LKTAction { + type: "check"; + result?: string; +}; + +export interface LKTEmit extends LKTAction { + type: "emit"; + to?: string; +}; + +export interface LKTKeystroke extends LKTAction { + type: "keystroke"; + key?: string; + flick?: string; + longPress?: string; + tapCount?: string; +}; + +export interface LKTBackspace extends LKTAction { + type: "backspace"; +} + +export type LKTAnyAction = LKTCheck | LKTEmit | LKTKeystroke | LKTBackspace; diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 80b6959149..3d0b2c7131 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -393,15 +393,12 @@ export class LDMLKeyboardXMLSourceFileReader { r.stuffBoxes(test, $$, 'startContext'); // singleton // now the actions test.actions = $$.map(v => { - const subtag = v['#name']; - const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r); - switch(subtag) { - case 'keystroke': return { keystroke: subv }; - case 'check': return { check: subv }; - case 'emit': return { emit: subv }; - case 'startContext': return null; // handled above - default: this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedAction({ subtag })); return null; + const type = v['#name']; // element name + if (type === 'startContext') { + return null; // handled above } + const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r); + return Object.assign({ type }, subv); }).filter(v => v !== null); return test; }); diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 108a2d57c2..06671a13d7 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -36,7 +36,7 @@ export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './keyman-to export * as KPJ from './kpj/kpj-file.js'; export { KPJFileReader } from './kpj/kpj-file-reader.js'; -export { KeymanDeveloperProject, KeymanDeveloperProjectFile, } from './kpj/keyman-developer-project.js'; +export { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, } from './kpj/keyman-developer-project.js'; export * as KpsFile from './package/kps-file.js'; export * as KmpJsonFile from './package/kmp-json-file.js'; diff --git a/common/web/types/src/util/common-events.ts b/common/web/types/src/util/common-events.ts index b8b3300550..65743d686d 100644 --- a/common/web/types/src/util/common-events.ts +++ b/common/web/types/src/util/common-events.ts @@ -43,8 +43,4 @@ export class CommonTypesMessages { m(this.ERROR_TestDataUnexpectedArray, `Problem reading test data: expected single ${o.subtag} element, found multiple`); static ERROR_TestDataUnexpectedArray = SevError | 0x0007; - static Error_TestDataUnexpectedAction = (o: {subtag: string}) => - m(this.ERROR_TestDataUnexpectedAction, - `Problem reading test data: unexpected action element ${o.subtag}`); - static ERROR_TestDataUnexpectedAction = SevError | 0x0008; }; diff --git a/common/web/types/test/fixtures/test-fr.xml b/common/web/types/test/fixtures/test-fr.xml index cfd63c4f75..c705605521 100644 --- a/common/web/types/test/fixtures/test-fr.xml +++ b/common/web/types/test/fixtures/test-fr.xml @@ -1,6 +1,12 @@ + @@ -17,6 +23,8 @@ + + diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts index 80a78a1187..a0e1202f85 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts @@ -2,12 +2,14 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; import { assert } from 'chai'; import 'mocha'; import { testTestdataReaderCases } from '../helpers/reader-callback-test.js'; +import { LKTAnyAction } from './ldml-keyboard-testdata-xml.js'; describe('ldml keyboard xml reader tests', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while testTestdataReaderCases([ { + // Note! There's another test case against similar data, in developer/src/kmc-ldml/test/test-testdata-e2e.ts using test-fr.json subpath: 'test-fr.xml', callback: (data, source) => { assert.ok(source); @@ -35,16 +37,19 @@ describe('ldml keyboard xml reader tests', function () { const test0 = source.keyboardTest3.tests[0].test[0]; assert.equal('key-test', test0.name); assert.equal('abc\\u0022...', test0.startContext?.to); - assert.sameDeepOrderedMembers([ - { keystroke: { key: 's' } }, - { check: { result: 'abc\\u0022...s' } }, - { keystroke: { key: 't' } }, - { check: { result: 'abc\\u0022...st' } }, - { keystroke: { key: 'u' } }, - { check: { result: 'abc\\u0022...stu' } }, - { emit: { to: 'v' } }, - { check: { result: 'abc\\u0022...stuv' } }, - ], test0.actions); + const expectedActions : LKTAnyAction[] = [ + { type: "keystroke", key: 's' }, + { type: "check", result: 'abc\\u0022...s' }, + { type: "keystroke", key: 't' }, + { type: "check", result: 'abc\\u0022...st' }, + { type: "keystroke", key: 'u' }, + { type: "check", result: 'abc\\u0022...stu' }, + { type: "emit", to: 'v' }, + { type: "check", result: 'abc\\u0022...stuv' }, + { type: "backspace" }, + { type: "check", result: 'abc\\u0022...stu' }, + ]; + assert.sameDeepOrderedMembers(expectedActions, test0.actions, 'Static data in .ts file should match parsed test-fr.xml'); }, } ]); diff --git a/core/build.sh b/core/build.sh index 9779419202..646a3731c2 100755 --- a/core/build.sh +++ b/core/build.sh @@ -107,13 +107,13 @@ builder_describe_outputs \ configure:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/build.ninja \ configure:arch /core/build/arch/$BUILDER_CONFIGURATION/build.ninja \ configure:wasm /core/build/wasm/$BUILDER_CONFIGURATION/build.ninja \ - build:x86 /core/build/x86/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:x64 /core/build/x64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:mac /core/build/mac/$BUILDER_CONFIGURATION/libkmnkbp0.a \ - build:mac-x86_64 /core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:arch /core/build/arch/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:wasm /core/build/wasm/$BUILDER_CONFIGURATION/src/libkmnkbp0.a + build:x86 /core/build/x86/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:x64 /core/build/x64/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:mac /core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a \ + build:mac-x86_64 /core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:arch /core/build/arch/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:wasm /core/build/wasm/$BUILDER_CONFIGURATION/src/libkeymancore.a # Import our standard compiler defines; this is copied from # /resources/build/meson/standard.meson.build by build.sh, because meson doesn't @@ -157,9 +157,9 @@ do_action build if builder_start_action build:mac; then lipo -create \ - "$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a" \ - "$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a" \ - -output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkmnkbp0.a" + "$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \ + "$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \ + -output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a" builder_finish_action success build:mac fi diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index 86358edeeb..309fdb20e1 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -1,6 +1,6 @@ /* Copyright: © 2018 SIL International. - Description: Cross platform API C/C++ declarations for libkmnkbp keyboard + Description: Cross platform API C/C++ declarations for libkeymancore keyboard processor. Create Date: 2 Oct 2018 Authors: Tim Eves (TSE) @@ -253,8 +253,8 @@ km_core_context_items_from_utf8(char const *text, Convert a context item array into a UTF-16 encoded string placing it into the supplied buffer of specified size, and return the number of code units actually used in the conversion. If null is passed as the buffer the -number codeunits required is returned. This will strip markers from the -context during the conversion. +number of codeunits required is returned. Any markers in the context will +not be included in the output buffer. ##### Return status: - `KM_CORE_STATUS_OK`: On success. - `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -285,8 +285,8 @@ km_core_context_items_to_utf16(km_core_context_item const *item, Convert a context item array into a UTF-8 encoded string placing it into the supplied buffer of specified size, and return the number of code units actually used in the conversion. If null is passed as the buffer the -number codeunits required is returned. This will strip markers from the -context during the conversion. +number of codeunits required is returned. Any markers in the context will +not be included in the output buffer. ##### Return status: - `KM_CORE_STATUS_OK`: On success. - `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -310,6 +310,38 @@ km_core_context_items_to_utf8(km_core_context_item const *item, char *buf, size_t *buf_size); +/* +``` +### `km_core_context_items_to_utf32` +##### Description: +Convert a context item array into a UTF-32 encoded string placing it into +the supplied buffer of specified size, and return the number of codepoints +actually used in the conversion. If null is passed as the buffer the +number of codepoints required is returned. Any markers in the context will +not be included in the output buffer. +##### Return status: +- `KM_CORE_STATUS_OK`: On success. +- `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_CORE_STATUS_INSUFFICENT_BUFFER`: If the buffer is not large enough. + `buf_size` will contain the space required. The contents of the buffer are + undefined. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_core_context_item`. + Must be terminated with a type of `KM_CORE_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-32 string into. + May be null to request size calculation. +- __buf_size__: a pointer to the result variable: + The size of the supplied buffer in codepoints if `buf` is given. + On return will be the size required if `buf` is null. + +```c +*/ +KMN_API +km_core_status +km_core_context_items_to_utf32(km_core_context_item const *item, + km_core_usv *buf, + size_t *buf_size); + /* ``` ### `km_core_context_items_dispose` @@ -501,10 +533,10 @@ typedef struct { uint8_t type; uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)]; union { - uintptr_t marker; // MARKER type + uint32_t marker; // MARKER type km_core_option_item const * option; // OPT types km_core_usv character; // CHAR type - uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off + uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off; re name see #9833 km_core_backspace_item backspace; // BACKSPACE type }; } km_core_action_item; @@ -526,6 +558,161 @@ enum km_core_action_type { KM_CORE_IT_MAX_TYPE_ID }; +/* +``` +### Actions +This structure provides the results of processing a key event to the Platform layer and +should be processed by the Platform layer to issue commands to the os text +services framework to transform the text store in the Client Application, among +other actions. + +This API replaces the Action items APIs, which is now deprecated and will be +removed in the future. +```c +*/ + +typedef enum { KM_CORE_FALSE = 0, KM_CORE_TRUE = 1 } km_core_bool; +typedef enum { KM_CORE_CAPS_UNCHANGED = -1, KM_CORE_CAPS_OFF = 0, KM_CORE_CAPS_ON = 1 } km_core_caps_state; + +typedef struct { + // number of codepoints (not codeunits!) to delete from app context. + unsigned int code_points_to_delete; + + // null-term string of characters to insert into document + km_core_usv* output; + + // list of options to persist, terminated with KM_CORE_OPTIONS_END + km_core_option_item* persist_options; + + // issue a beep, 0 = no, 1 = yes + km_core_bool do_alert; + + // emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes + km_core_bool emit_keystroke; + + // -1=unchanged, 0=off, 1=on + km_core_caps_state new_caps_lock_state; +} km_core_actions; + +/* +``` +### `km_core_state_get_actions` +##### Description: +Returns a pointer to an actions object which details all the actions +that the Platform layer must take after a keystroke. The `code_points_to_delete` +action must be performed before the `output` action, but the other +actions may be performed in any order. +##### Return: +A pointer to a `km_core_actions` object, which must be freed with +`km_core_actions_dispose`. +##### Parameters: +- __state__: An opaque pointer to a state object. + +```c +*/ +KMN_API +km_core_actions* +km_core_state_get_actions( + km_core_state const *state +); + +/* +``` +### `km_core_actions_dispose` +##### Description: +Free the allocated memory belonging to an actions object previously +returned by `km_core_state_get_actions`. +##### Parameters: +- __actions__: A pointer to the actions object to be disposed of. + +```c +*/ +KMN_API +km_core_status +km_core_actions_dispose( + km_core_actions* actions +); + +/* +``` +### `km_core_context_status` +##### Description: +Return values for `km_core_state_context_set_if_needed`. + +```c +*/ + +typedef enum { + KM_CORE_CONTEXT_STATUS_UNCHANGED = 0, // Cached context change was not needed + KM_CORE_CONTEXT_STATUS_UPDATED = 1, // Cached context was set to application context + KM_CORE_CONTEXT_STATUS_CLEARED = 2, // Application context was invalid, context was cleared + KM_CORE_CONTEXT_STATUS_ERROR = 3, // Internal error + KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT = 4, // Invalid arguments +} km_core_context_status; + +/* +``` +### `km_core_state_context_set_if_needed` +##### Description: +Sets the internal cached context for the state object, to the passed-in +application context string, if it differs from the codepoints in the +cached context. For the purposes of comparison, (1) cached markers are +ignored, (2) if the cached context is shorter than the application +context, it is considered identical, but (3) if the cached context is +longer, then it is considered different. + +If a difference is found, then the cached context will be set to the +application context, and thus any cached markers will be cleared. + +`km_core_state_context_set_if_needed` and `km_core_state_context_clear` +will replace most uses of the existing Core context APIs. + +##### Parameters: +- __state__: An opaque pointer to a state object. +- __application_context__: A pointer to an null-terminated `km_core_cp` + string representing the current context from the application. +##### Return status: +- `KM_CORE_CONTEXT_STATUS_UNCHANGED`: Cached context change was not needed +- `KM_CORE_CONTEXT_STATUS_UPDATED`: Cached context was set to application + context +- `KM_CORE_CONTEXT_STATUS_CLEARED`: Application context was invalid, perhaps + had unpaired surrogates, and so cached context was cleared instead +- `KM_CORE_CONTEXT_STATUS_ERROR`: Internal error +- `KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT`: One or more parameters was null + +```c +*/ + +KMN_API +km_core_context_status +km_core_state_context_set_if_needed( + km_core_state *state, + km_core_cp const *application_context +); + +/* +``` +### `km_core_state_context_clear` +##### Description: +Clears the internal cached context for the state. This is the same as +`km_core_context_clear(km_core_state_context(&state))`. + +`km_core_state_context_set_if_needed` and `km_core_state_context_clear` +will replace most uses of the existing Core context APIs. + +##### Parameters: +- __state__: An opaque pointer to a state object. +##### Return status: +- `KM_CORE_STATUS_OK`: On success. +- `KM_CORE_STATUS_INVALID_ARGUMENT`: If any parameters are null. + +```c +*/ +KMN_API +km_core_status +km_core_state_context_clear( + km_core_state *state +); /* ``` @@ -936,7 +1123,7 @@ km_core_state_context(km_core_state *state); /* ``` -### `kbp_state_get_intermediate_context` +### `km_core_state_get_intermediate_context` ##### Description: Get access to the state object's keyboard processor's intermediate context. This context is used during an IMX callback, part way through processing a keystroke. @@ -950,7 +1137,7 @@ to `km_core_context_items_dispose`. */ KMN_API km_core_status -kbp_state_get_intermediate_context(km_core_state *state, km_core_context_item ** context_items); +km_core_state_get_intermediate_context(km_core_state *state, km_core_context_item ** context_items); /* ``` diff --git a/core/include/keyman/keyman_core_api_bits.h b/core/include/keyman/keyman_core_api_bits.h index be2886d058..e00f4698a8 100644 --- a/core/include/keyman/keyman_core_api_bits.h +++ b/core/include/keyman/keyman_core_api_bits.h @@ -28,8 +28,8 @@ #if defined _WIN32 || defined __CYGWIN__ typedef wchar_t const * km_core_path_name; - #define _KM_KBP_PATH_SEPARATOR (L'\\') - #define _KM_KBP_EXT_SEPARATOR (L'.') + #define _KM_CORE_PATH_SEPARATOR (L'\\') + #define _KM_CORE_EXT_SEPARATOR (L'.') #if defined __GNUC__ // These three will be redefined for Windows #undef _kmn_export_flag #undef _kmn_import_flag @@ -44,14 +44,14 @@ #define _kmn_static_flag #else typedef char const * km_core_path_name; - #define _KM_KBP_PATH_SEPARATOR ('/') - #define _KM_KBP_EXT_SEPARATOR ('.') + #define _KM_CORE_PATH_SEPARATOR ('/') + #define _KM_CORE_EXT_SEPARATOR ('.') #endif -#if defined KMN_KBP_STATIC +#if defined KM_CORE_LIBRARY_STATIC #define KMN_API _kmn_tag_fn(_kmn_static_flag) #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_static_flag) -#elif defined KMN_KBP_EXPORTING +#elif defined KM_CORE_LIBRARY_EXPORTING #define KMN_API _kmn_tag_fn(_kmn_export_flag) #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_export_flag) #else @@ -59,8 +59,8 @@ #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_import_flag) #endif -#ifndef KMN_KBP -#define KMN_KBP +#ifndef KM_CORE_LIBRARY +#define KM_CORE_LIBRARY #endif #ifndef USE_CHAR16_T #define USE_CHAR16_T diff --git a/core/include/ldml/build.sh b/core/include/ldml/build.sh index 2f4b76ab8e..8787ba0e1c 100755 --- a/core/include/ldml/build.sh +++ b/core/include/ldml/build.sh @@ -28,7 +28,7 @@ builder_describe "Build Keyman ldml-keyboard-constants package" \ builder_describe_outputs \ configure /node_modules \ - build /core/include/ldml/build/keyboardprocessor_ldml.js + build /core/include/ldml/build/keyman_core_ldml.js builder_parse "$@" diff --git a/core/include/ldml/keyboardprocessor_ldml.h b/core/include/ldml/keyman_core_ldml.h similarity index 98% rename from core/include/ldml/keyboardprocessor_ldml.h rename to core/include/ldml/keyman_core_ldml.h index 45837b6385..41f87a0d50 100644 --- a/core/include/ldml/keyboardprocessor_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -10,7 +10,7 @@ // Generated File - do not edit // // This file is generated by core/tools/ldml-const-builder/build.sh -// based on core/include/ldml/keyboardprocessor_ldml.ts +// based on core/include/ldml/keyman_core_ldml.ts // #pragma once diff --git a/core/include/ldml/keyboardprocessor_ldml.ts b/core/include/ldml/keyman_core_ldml.ts similarity index 99% rename from core/include/ldml/keyboardprocessor_ldml.ts rename to core/include/ldml/keyman_core_ldml.ts index 4e9aaedbb1..b404a05bcc 100644 --- a/core/include/ldml/keyboardprocessor_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -12,7 +12,7 @@ // // core/tools/ldml-const-builder/build.sh clean build run // -// To update keyboardprocessor_ldml.h, and commit the result. +// To update keyman_core_ldml.h, and commit the result. // // It is not updated automatically. diff --git a/core/include/ldml/ldml-const-builder.ts b/core/include/ldml/ldml-const-builder.ts index c08e74dddc..eced81b85a 100644 --- a/core/include/ldml/ldml-const-builder.ts +++ b/core/include/ldml/ldml-const-builder.ts @@ -1,10 +1,10 @@ /* Copyright: Copyright (C) 2022 SIL International. Authors: srl295 - This tool generates a .h version of the keyboardprocessor_ldml.ts file + This tool generates a .h version of the keyman_core_ldml.ts file */ -import { constants } from './keyboardprocessor_ldml.js'; +import { constants } from './keyman_core_ldml.js'; const keys = Object.keys(constants); keys.sort(); @@ -20,7 +20,7 @@ console.log(` // Generated File - do not edit // // This file is generated by core/tools/ldml-const-builder/build.sh -// based on core/include/ldml/keyboardprocessor_ldml.ts +// based on core/include/ldml/keyman_core_ldml.ts // #pragma once diff --git a/core/include/ldml/package.json b/core/include/ldml/package.json index 76da479930..1e9a26c1c0 100644 --- a/core/include/ldml/package.json +++ b/core/include/ldml/package.json @@ -8,11 +8,11 @@ "unicode" ], "files": [ - "/build/keyboardprocessor_ldml.*" + "/build/keyman_core_ldml.*" ], "license": "MIT", "type": "module", - "main": "build/keyboardprocessor_ldml.js", + "main": "build/keyman_core_ldml.js", "repository": { "type": "git", "url": "git+https://github.com/keymanapp/keyman.git" diff --git a/core/include/ldml/tsconfig.build.json b/core/include/ldml/tsconfig.build.json index fd0f003272..60565eda3a 100644 --- a/core/include/ldml/tsconfig.build.json +++ b/core/include/ldml/tsconfig.build.json @@ -10,7 +10,7 @@ "node_modules" ], "files": [ - "keyboardprocessor_ldml.ts", + "keyman_core_ldml.ts", "ldml-const-builder.ts" ] } diff --git a/core/include/ldml/tsconfig.json b/core/include/ldml/tsconfig.json index fa4135b021..e73b5777da 100644 --- a/core/include/ldml/tsconfig.json +++ b/core/include/ldml/tsconfig.json @@ -10,6 +10,6 @@ "node_modules" ], "files": [ - "keyboardprocessor_ldml.ts" + "keyman_core_ldml.ts" ] } diff --git a/core/meson.build b/core/meson.build index 81d994124c..2061942d61 100644 --- a/core/meson.build +++ b/core/meson.build @@ -1,11 +1,11 @@ # Copyright: © 2018-2022 SIL International. -# Description: Cross platform build script to compile libkmnkbp, documentation +# Description: Cross platform build script to compile libkeymancore, documentation # and tests. # Create Date: 2 Oct 2018 # Authors: Tim Eves (TSE) # -project('keyboardprocessor', 'cpp', 'c', +project('keyman_core', 'cpp', 'c', version: run_command(find_program('getversion.bat', 'getversion.sh'), check:true).stdout().strip(), license: 'MIT', default_options : ['buildtype=release', @@ -19,14 +19,14 @@ project('keyboardprocessor', 'cpp', 'c', # meson doesn't allow us to reference a file outside its root subdir('resources') -lib_version = '0.0.0' +lib_version = '1.0.0' py = import('python') python = py.find_installation() # TODO: Shared includes may use namespaces, etc which need future tidyup. -# For now, we use KMN_KBP to inject the km::kbp::kmx namespace -defns += ['-DKMN_KBP'] +# For now, we use KM_CORE_LIBRARY to inject the km::core::kmx namespace +defns += ['-DKM_CORE_LIBRARY'] # #define DEBUG when we are on a debug build if get_option('buildtype') == 'debug' @@ -36,4 +36,4 @@ endif subdir('doc') subdir('include') subdir('src') -subdir('tests') \ No newline at end of file +subdir('tests') diff --git a/core/src/action.cpp b/core/src/action.cpp new file mode 100644 index 0000000000..3387d8f85d --- /dev/null +++ b/core/src/action.cpp @@ -0,0 +1,138 @@ +/* + Copyright: © 2023 SIL International. + Description: Implementation of the action API functions using internal + data structures and functions. + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation from #9720 +*/ +#include +#include +#include +#include + +#include + +#include "action.hpp" +#include "state.hpp" +#include "option.hpp" + +km_core_actions * km::core::action_item_list_to_actions_object( + km_core_action_item const *action_items +) { + assert(action_items != nullptr); + if(action_items == nullptr) { + return nullptr; + } + + km_core_status status = KM_CORE_STATUS_OK; + + std::unique_ptr actions(new km_core_actions); + + // Set actions default values + std::vector output; + std::vector options; + actions->code_points_to_delete = 0; + actions->do_alert = KM_CORE_FALSE; + actions->emit_keystroke = KM_CORE_FALSE; + actions->new_caps_lock_state = KM_CORE_CAPS_UNCHANGED; + + // Clear output pointers, will be set later once we have sizes + actions->output = nullptr; + actions->persist_options = nullptr; + + for (; action_items->type != KM_CORE_IT_END; ++action_items) { + assert(action_items->type < KM_CORE_IT_MAX_TYPE_ID); + + switch(action_items->type) { + case KM_CORE_IT_ALERT: + actions->do_alert = KM_CORE_TRUE; + break; + case KM_CORE_IT_BACK: + switch(action_items->backspace.expected_type) { + case KM_CORE_BT_UNKNOWN: + // this is equivalent to emit_keystroke, because the only time we + // are allowed to do an unknown bksp is when a bksp is passed in + actions->emit_keystroke = KM_CORE_TRUE; + break; + case KM_CORE_BT_CHAR: + if(output.empty()) { + actions->code_points_to_delete++; + } else { + auto last_context_item = output.back(); + output.pop_back(); + assert(last_context_item.type == KM_CORE_CT_CHAR); + assert(last_context_item.character == action_items->backspace.expected_value); + } + break; + case KM_CORE_BT_MARKER: + if(output.empty()) { + // deleting a marker has no effect on the application + } else { + auto last_context_item = output.back(); + output.pop_back(); + assert(last_context_item.type == KM_CORE_CT_MARKER); + assert(last_context_item.marker == action_items->backspace.expected_value); + } + break; + default: + assert(false); + } + break; + case KM_CORE_IT_CAPSLOCK: + actions->new_caps_lock_state = action_items->capsLock ? KM_CORE_CAPS_ON : KM_CORE_CAPS_OFF; + break; + case KM_CORE_IT_CHAR: + output.push_back({KM_CORE_CT_CHAR,{0},{action_items->character}}); + break; + case KM_CORE_IT_EMIT_KEYSTROKE: + actions->emit_keystroke = KM_CORE_TRUE; + break; + case KM_CORE_IT_INVALIDATE_CONTEXT: + // no-op + break; + case KM_CORE_IT_MARKER: + output.push_back({KM_CORE_CT_MARKER,{0},{action_items->marker}}); + break; + case KM_CORE_IT_PERSIST_OPT: + // TODO: lowpri: replace existing item if already present in options vector? + options.push_back(km::core::option( + static_cast(action_items->option->scope), + action_items->option->key, + action_items->option->value + )); + break; + default: + assert(false); + } + } + + + // Strip the markers from the output, and convert to an string of UTF-32 + + output.push_back(KM_CORE_CONTEXT_ITEM_END); + + size_t buf_size; + + if((status = km_core_context_items_to_utf32(output.data(), nullptr, &buf_size)) != KM_CORE_STATUS_OK) { + return nullptr; + } + + std::unique_ptr output_usv(new km_core_usv[buf_size]); + + if((status = km_core_context_items_to_utf32(output.data(), output_usv.get(), &buf_size)) != KM_CORE_STATUS_OK) { + return nullptr; + } + + actions->output = output_usv.release(); + + // Create an array of the persisted options + + options.push_back(KM_CORE_OPTIONS_END); + actions->persist_options = new km_core_option_item[options.size()]; + std::copy(options.begin(), options.end(), actions->persist_options); + + // We now have a complete set of actions + + return actions.release(); +} diff --git a/core/src/action.hpp b/core/src/action.hpp new file mode 100644 index 0000000000..4cc0aabdf2 --- /dev/null +++ b/core/src/action.hpp @@ -0,0 +1,20 @@ +/* + Copyright: © 2023 SIL International. + Description: Internal actions methods for Keyman Core + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation +*/ + +#pragma once + +#include + +namespace km { +namespace core +{ + km_core_actions* action_item_list_to_actions_object( + km_core_action_item const *action_items + ); +} // namespace core +} // namespace km diff --git a/core/src/context.hpp b/core/src/context.hpp index 08edc86163..dba7bec060 100644 --- a/core/src/context.hpp +++ b/core/src/context.hpp @@ -15,7 +15,7 @@ class json; namespace km { -namespace kbp +namespace core { // This will likely be replaced with a class implementing a more space @@ -39,13 +39,13 @@ void context::push_marker(uint32_t marker) { emplace_back(km_core_context_item { KM_CORE_CT_MARKER, {0,}, {marker} }); } -} // namespace kbp +} // namespace core } // namespace km -json & operator << (json &, km::kbp::context const &); +json & operator << (json &, km::core::context const &); json & operator << (json &, km_core_context_item const &); -struct km_core_context : public km::kbp::context +struct km_core_context : public km::core::context { }; diff --git a/core/src/debug.hpp b/core/src/debug.hpp index 935fad155c..a68d9f1e57 100644 --- a/core/src/debug.hpp +++ b/core/src/debug.hpp @@ -13,7 +13,7 @@ #include namespace km { -namespace kbp +namespace core { class debug_items : public std::vector @@ -66,6 +66,6 @@ void debug_items::set_enabled(bool value) noexcept { _is_enabled = value; } -} // namespace kbp +} // namespace core } // namespace km diff --git a/core/src/debuglog.cpp b/core/src/debuglog.cpp index dc3dd95cd5..a4f8847892 100644 --- a/core/src/debuglog.cpp +++ b/core/src/debuglog.cpp @@ -9,7 +9,7 @@ #include "debuglog.h" namespace km { -namespace kbp { +namespace core { namespace kmx { #define TAB "\t" @@ -358,7 +358,7 @@ int DebugLog_1(const char *file, int line, const char *function, const char *fmt "%ld" TAB //"TickCount" TAB "%s:%d" TAB //"SourceFile" TAB "%s" TAB //"Function" - "%s" NL, //"Message" + "%s", //"Message" GetTickCount(), //"TickCount" TAB file, line, //"SourceFile" TAB diff --git a/core/src/debuglog.h b/core/src/debuglog.h index 1ecbaca612..67e6ce601f 100644 --- a/core/src/debuglog.h +++ b/core/src/debuglog.h @@ -1,9 +1,11 @@ /* Debugging */ +#pragma once + #include namespace km { -namespace kbp { +namespace core { namespace kmx { extern KMX_BOOL g_debug_ToConsole, g_debug_KeymanLog, g_silent; @@ -17,11 +19,13 @@ extern const struct modifier_names s_modifier_names[]; extern const char *s_key_names[]; #ifdef _MSC_VER -#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) +#define DebugLog(msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) +#define DebugLog2(file,line,function,msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(file, line, function, (msg),__VA_ARGS__) : 0) #define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__) #define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__) #else -#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) +#define DebugLog(msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) +#define DebugLog2(file,line,function,msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(file, line, function, (msg), ##__VA_ARGS__) : 0) #define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__) #define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__) #endif diff --git a/core/src/keyboard.cpp b/core/src/keyboard.cpp index 47693b9b66..2c3a0c11b2 100644 --- a/core/src/keyboard.cpp +++ b/core/src/keyboard.cpp @@ -8,7 +8,7 @@ #include "keyboard.hpp" #include "jsonpp.hpp" -using namespace km::kbp; +using namespace km::core; inline @@ -54,7 +54,7 @@ keyboard_attributes & keyboard_attributes::operator = (keyboard_attributes &&rhs } -json & km::kbp::operator << (json & j, km::kbp::keyboard_attributes const & kb) +json & km::core::operator << (json & j, km::core::keyboard_attributes const & kb) { j << json::object << "id" << kb.id diff --git a/core/src/keyboard.hpp b/core/src/keyboard.hpp index 97329b7e76..6dd8517ce1 100644 --- a/core/src/keyboard.hpp +++ b/core/src/keyboard.hpp @@ -20,13 +20,13 @@ class json; namespace km { -namespace kbp +namespace core { class keyboard_attributes : public km_core_keyboard_attrs { std::u16string _keyboard_id; std::u16string _version_string; - kbp::path _folder_path; + core::path _folder_path; std::vector