diff --git a/.github/multi-labeler.yml b/.github/multi-labeler.yml index a06a0b23e6..1278db2a28 100644 --- a/.github/multi-labeler.yml +++ b/.github/multi-labeler.yml @@ -64,4 +64,4 @@ labels: - label: 'cherry-pick' matcher: - title: '\(๐Ÿ’|:cherries:\)' + title: '(๐Ÿ’|:cherries:)' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 38e27cad8c..1c8704da25 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,7 +12,7 @@ jobs: repo-token: "${{ secrets.GITHUB_TOKEN }}" - name: Update labels based on PR title id: labeler - uses: fuxingloh/multi-labeler@8afa186ed03230c98fe24ebf9fe35093072ad46e + uses: fuxingloh/multi-labeler@8afa186ed03230c98fe24ebf9fe35093072ad46e # v1.4.0 with: github-token: ${{secrets.GITHUB_TOKEN}} config-path: .github/multi-labeler.yml diff --git a/HISTORY.md b/HISTORY.md index 0be1d88ab8..377de392e8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,24 @@ # Keyman Version History +## 15.0.123 alpha 2021-10-01 + +* fix(common): Fix cherry-pick labeling (#5782) +* chore(ios,android,windows): Update crowdin strings for Amharic (#5722) + +## 15.0.122 alpha 2021-09-30 + +* fix(developer): move ampersand to shift+7 on touch (#5746) +* feat(windows): Keyman Core integration (#5443) +* chore(windows): Keyman Core UI and Settings (#5769) +* fix(linux): Fix debian package script (#5771) +* docs(mac): Add note about installing with homebrew (#5767) + +## 15.0.121 alpha 2021-09-29 + +* fix(developer): remove empty touch rows on save (#5720) +* chore(common): Check in crowdin strings for Shuwa Latin (#5734) +* chore(windows): fix broken links in help (#5765) + ## 15.0.120 alpha 2021-09-28 * fix: help.keyman.com script file cleanup (#5751) diff --git a/VERSION.md b/VERSION.md index d805a0328c..80a9265582 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -15.0.121 \ No newline at end of file +15.0.124 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/res/values-am-rET/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-am-rET/strings.xml index 817d731403..0ba117dd7c 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-am-rET/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-am-rET/strings.xml @@ -18,6 +18,10 @@ แˆ›แˆปแˆปแ‹ซแ‹Žแ‰ฝแŠ• แ‹ญแŒซแŠ‘ แˆตแˆชแ‰ต %1$s + + แŠชแ‹ญแˆ›แŠ• แ‹จแŠญแˆฎแˆ แˆตแˆชแ‰ตแŠ• 57 แ‹ˆแ‹ญแˆ แŠ แ‹ฒแˆต แ‹ญแˆแˆแŒ‹แˆแข + + แŠญแˆฎแˆแŠ• แˆ›แ‹˜แˆ˜แŠ• แŠฅแ‹šแˆ… แŒ‹แˆญ แˆ˜แŒปแ แ‹ญแŒ€แˆแˆฉ … @@ -37,11 +41,11 @@ แŠชแ‹ญแˆ›แŠ‘แŠ• แˆˆแˆฒแˆตแ‰ฐแˆ แŠชแ‰ฆแˆญแ‹ต แŠฅแŠ•แ‹ฒแˆ†แŠ• แ‹ซแ‹ตแˆญแŒ‰ - แŠชแˆ›แŠ‘แŠ• แˆ˜แ‹ฐแ‰ แŠ“ แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แ‹ญแˆฐแ‹ญแˆ™ + แŠชแˆ›แŠ‘แŠ• แˆ˜แ‹ฐแ‰ แŠ› แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แ‹ญแˆฐแ‹ญแˆ™ แ‰ฐแŒจแˆ›แˆช แˆ˜แˆจแŒƒ - แ‰ แˆšแАแˆณแ‰ แ‰ต แˆฐแŠ แ‰ต แ‹ญแ‰ณแ‹ญ\"%1$s\" + แ‰ แˆšแАแˆณแ‰ แ‰ต แˆฐแŠ แ‰ต แ‹ญแ‰ณแ‹ญ \"%1$s\" แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณแ‹แŠ• แˆฐแˆŒแ‹ณ แŒฅแ‰…แˆŽแ‰ฝแŠ• แˆˆแˆ˜แŒซแŠ• แˆˆแŠชแ‹ญแˆ›แŠ‘ แ‹แŒซแ‹Š แˆ›แŠจแˆ›แ‰ป แˆˆแˆ›แŠ•แ‰ แ‰ฅ แˆแ‰ƒแ‹ต แ‹ญแˆตแŒกแ‰ตแข @@ -50,13 +54,33 @@ แŠ แ‹ฐแˆจแŒƒแŒ€แ‰ต - แ‹จแ‰ฐแŒซแŠ‘ แ‰‹แŠ•แ‹Žแ‰ฝ (%1$d) + แ‹จแ‰ฐแŒซแŠ‘ แ‰‹แŠ•แ‰‹แ‹Žแ‰ฝ (%1$d) แ‹จแ‰ฐแŒซแŠ‘ แ‰‹แŠ•แ‹Žแ‰ฝ (%1$d) แ‹จแ‰ฐแŒซแА แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แ‹ˆแ‹ญแˆ แˆ˜แ‹แŒˆแ‰  แ‰ƒแˆ‹แ‰ต - แ‹จแŠชแˆ›แŠ‘แŠ• แ‹จแŒˆแŒฝแ‰ณ แ‰‹แŠ•แ‰‹ แ‹ญแ‰€แ‹ญแˆฉ + แ‹จแŒˆแŒฝแ‰ณ แ‰‹แŠ•แ‰‹ + + แ‹จแ‰‹แŠ•แ‰‹ แŒˆแ‰ แ‰ณแ‹แŠ• แ‰แˆ˜แ‰ต แˆ›แˆตแ‰ฐแŠซแŠจแˆ + + แ‹จแˆตแ”แˆตแ‰ฃแˆฉ แˆ˜แŒแˆˆแŒซ แŒฝแˆแ + + แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ + + แ‰‹แŠ•แ‰‹ + + แ‰‹แŠ•แ‰‹แŠ“ แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ + + แ‰ฃแ‹ถ + + แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณแ‹แŠ• แ‰ แˆตแ”แˆต แ‰ฃแˆฉ แˆ‹แ‹ญ แŠ แˆณแ‹ญ + + แ‹จแ‰‹แŠ•แ‰‹แ‹แŠ• แˆตแˆ แ‰ แˆตแ”แˆต แ‰ฃแˆฉ แˆ‹แ‹ญ แŠ แˆณแ‹ญ + + แ‰‹แŠ•แ‰‹แŠ“ แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณแ‹แŠ• แ‰ แˆตแ”แˆต แ‰ฃแˆฉ แˆ‹แ‹ญ + + แ‹จแˆตแ”แˆตแ‰ฃแˆฉ แˆ˜แŒแˆˆแŒซ แŒฝแˆแ แŠ แ‰ณแˆณแ‹ญ แˆแˆแŒŠแ‹œ แ‰ฃแАแˆฉแŠ• แŠ แˆณแ‹ญ @@ -87,6 +111,12 @@ %1$s แ‹จแ‰ฐแŒจแˆ˜แˆจ แ‰‹แŠ•แ‰‹ %2$s แˆแˆ‰แˆ แ‰‹แŠ•แ‰‹แ‹Žแ‰ฝ แ‰€แ‹ฐแˆ แˆฒแˆ แ‰ฐแŒญแŠ—แˆ + + แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณแ‹แŠ• แ‰ แˆ˜แŒŽแ‰ฐแ‰ต แ‰แˆ˜แ‰ฑแŠ• แŠ แˆตแ‰ฐแŠซแŠญแˆ + + แ‹ฒแ‰ซแ‹ญแˆฑแŠ• แ‰ แˆ›แˆฝแŠจแˆญแŠจแˆญ แ–แˆญแ‰ตแˆฌแ‰ฑแŠ•แŠ“ แˆ‹แŠ•แ‹ตแˆตแŠฌแ‘แŠ• แŠ แˆตแ‰ฐแŠซแŠญแˆ + + แ‹ˆแ‹ฐ แ‰€แ‹ฐแˆ˜แ‹ แˆดแ‰ฒแŠ•แŒ แˆ˜แˆแˆต แˆแˆแŒ แ‹ˆแ‹ญแˆ URL แŒปแ diff --git a/android/KMEA/app/src/main/res/values-am-rET/strings.xml b/android/KMEA/app/src/main/res/values-am-rET/strings.xml index 417d02d5b5..49620d7315 100644 --- a/android/KMEA/app/src/main/res/values-am-rET/strings.xml +++ b/android/KMEA/app/src/main/res/values-am-rET/strings.xml @@ -24,6 +24,10 @@ แŠชแ‹ญแˆ›แŠ‘แŠ• แ‹แŒ‹ + แ‹ˆแ‹ฐแŠแ‰ต + + แ‹จแˆšแ‰€แŒฅแˆˆแ‹แŠ• แ‹จแˆ˜แ‰€แ‰ แ‹ซ แ‹˜แ‹ด + แ‹ˆแˆญแ‹ทแˆ แŒซแŠ• @@ -69,6 +73,8 @@ แ‹จแŠฅแŒˆแ‹› แ‹แ‹ญแˆแŠ• แˆˆแˆ›แ‹จแ‰ต แ‹แ‹ญแˆ แ•แˆฎแ‰ซแ‹ญแ‹ฐแˆฉ แ‰คแ‰ฐ แˆ˜แŒฝแˆแแ‰ต แ‹ซแˆตแˆแˆแŒˆแ‹‹แˆ %1$s แ‹จแŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณ แ‹แ‰ณแˆ แ‰ฝแŒแˆญ แ‹แˆญแ‹แˆญ %1$s:%2$s แˆˆ %3$s แ‰‹แŠ•แ‰‹แค แˆ˜แ‹ฐแ‰ แŠ› แ‰‹แŠ•แ‰‹ แŒญแŠ—แˆแข + + แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณแ‹ แ‰ฝแŒแˆญ %1$s:%2$s แˆˆ %3$s แ‰‹แŠ•แ‰‹แข แ‰ฐแ‹ซแ‹ซแ‹ฅแАแ‰ต แ‹ซแˆˆแ‹แŠ• แˆ˜แ‹แŒˆแ‰  แ‰ƒแˆ‹แ‰ต แˆˆแˆ˜แŒซแŠ• แŠฅแ‹จแˆแ‰ฐแˆธ แАแ‹แข diff --git a/common/core/desktop/build.bat b/common/core/desktop/build.bat index fdcc18a7e6..856db992da 100644 --- a/common/core/desktop/build.bat +++ b/common/core/desktop/build.bat @@ -70,14 +70,7 @@ cd %KEYMAN_ROOT%\common\core\desktop set BUILDTYPE=%2 -if "%3" == "--static" ( - set STATIC_LIBRARY=--default-library static - shift -) else ( - set STATIC_LIBRARY= -) - -echo "Is static build = !STATIC_LIBRARY! (%3)" +set STATIC_LIBRARY=--default-library both if "%3" == "build" ( echo === Calling meson build for Windows !ARCH! !BUILDTYPE! === diff --git a/common/core/desktop/meson.build b/common/core/desktop/meson.build index aec0bb12f2..dea4c55703 100644 --- a/common/core/desktop/meson.build +++ b/common/core/desktop/meson.build @@ -9,7 +9,8 @@ project('keyboardprocessor', 'cpp', 'c', version: run_command(find_program('getversion.bat', 'getversion.sh')).stdout().strip(), license: 'MIT', default_options : ['buildtype=release', - 'cpp_std=c++14']) + 'cpp_std=c++14', + 'b_vscrt=static_from_buildtype']) message('meson.project_version(): ' + meson.project_version() + '\n') compiler = meson.get_compiler('cpp') diff --git a/common/core/desktop/src/kmx/kmx_file.cpp b/common/core/desktop/src/kmx/kmx_file.cpp index 1a6bdf64f3..dfcac5f890 100644 --- a/common/core/desktop/src/kmx/kmx_file.cpp +++ b/common/core/desktop/src/kmx/kmx_file.cpp @@ -59,7 +59,7 @@ const unsigned long CRCTable[256] = { * and returns the updated value. */ -unsigned long CalculateBufferCRC(unsigned long count, KMX_BYTE *p) +static unsigned long CalculateBufferCRC(unsigned long count, KMX_BYTE *p) { unsigned long temp1; unsigned long temp2; diff --git a/common/core/web/input-processor/package-lock.json b/common/core/web/input-processor/package-lock.json index dd83bff172..acf36f841b 100644 --- a/common/core/web/input-processor/package-lock.json +++ b/common/core/web/input-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/input-processor/package.json b/common/core/web/input-processor/package.json index 5f33938636..1edd2c4696 100644 --- a/common/core/web/input-processor/package.json +++ b/common/core/web/input-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/input-processor", - "version": "15.0.121", + "version": "15.0.124", "description": "The core text and prediction processing engine for KeymanWeb", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "^15.0.121", - "@keymanapp/resources-gosh": "^15.0.121", + "@keymanapp/lexical-model-compiler": "^15.0.124", + "@keymanapp/resources-gosh": "^15.0.124", "@types/node": "^11.9.4", "chai": "^4.3.4", "mocha": "^8.4.0", @@ -34,11 +34,11 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.121", - "@keymanapp/lexical-model-layer": "^15.0.121", - "@keymanapp/models-types": "^15.0.121", - "@keymanapp/web-environment": "^15.0.121", - "@keymanapp/web-utils": "^15.0.121", + "@keymanapp/keyboard-processor": "^15.0.124", + "@keymanapp/lexical-model-layer": "^15.0.124", + "@keymanapp/models-types": "^15.0.124", + "@keymanapp/web-environment": "^15.0.124", + "@keymanapp/web-utils": "^15.0.124", "eventemitter3": "^4.0.0" } } diff --git a/common/core/web/keyboard-processor/package-lock.json b/common/core/web/keyboard-processor/package-lock.json index cb4e711ce0..6d1e5b37f7 100644 --- a/common/core/web/keyboard-processor/package-lock.json +++ b/common/core/web/keyboard-processor/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/keyboard-processor/package.json b/common/core/web/keyboard-processor/package.json index 18b73c1c9b..ed74c02e1e 100644 --- a/common/core/web/keyboard-processor/package.json +++ b/common/core/web/keyboard-processor/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/keyboard-processor", - "version": "15.0.121", + "version": "15.0.124", "description": "Core module for Keyman keyboard support in KeymanWeb.", "repository": { "type": "git", @@ -18,7 +18,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.121", + "@keymanapp/resources-gosh": "^15.0.124", "chai": "^4.3.4", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", @@ -32,9 +32,9 @@ "mocha": "mocha" }, "dependencies": { - "@keymanapp/models-types": "^15.0.121", - "@keymanapp/web-environment": "^15.0.121", - "@keymanapp/web-utils": "^15.0.121", + "@keymanapp/models-types": "^15.0.124", + "@keymanapp/web-environment": "^15.0.124", + "@keymanapp/web-utils": "^15.0.124", "@types/node": "^11.9.4" } } diff --git a/common/core/web/keyboard-processor/src/keyboards/keyboard.ts b/common/core/web/keyboard-processor/src/keyboards/keyboard.ts index bedfadfb00..857d2c223d 100644 --- a/common/core/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/common/core/web/keyboard-processor/src/keyboards/keyboard.ts @@ -20,9 +20,14 @@ namespace com.keyman.keyboards { CALIBRATED = 2 } + export interface VariableStoreDictionary { + [name: string]: string; + }; + + /** * Acts as a wrapper class for Keyman keyboards compiled to JS, providing type information - * and keyboard-centered functionality in an object-oriented way without modifying the + * and keyboard-centered functionality in an object-oriented way without modifying the * wrapped keyboard itself. */ export class Keyboard { @@ -37,7 +42,7 @@ namespace com.keyman.keyboards { /** * This is the object provided to KeyboardInterface.registerKeyboard - that is, the keyboard * being wrapped. - * + * * TODO: Make this private instead. But there are a LOT of references that must be rooted out first. */ public readonly scriptObject: any; @@ -71,6 +76,51 @@ namespace com.keyman.keyboards { return this.scriptObject['KN']; } + /** + * Cache variable store values + * + * Primarily used for predictive text to prevent variable store + * values from being changed in 'fat finger' processing. + * + * KVS is available in keyboards compiled with Keyman Developer 15 + * and later versions. See #2924. + * + * @returns an object with each property referencing a variable store + */ + get variableStores(): VariableStoreDictionary { + const storeNames = this.scriptObject['KVS']; + let values = {}; + if(Array.isArray(storeNames)) { + for(let store of storeNames) { + values[store] = this.scriptObject[store]; + } + } + return values; + } + + /** + * Restore variable store values from cache + * + * KVS is available in keyboards compiled with Keyman Developer 15 + * and later versions. See #2924. + * + * @param values name-value pairs for each store value + */ + set variableStores(values: VariableStoreDictionary) { + const storeNames = this.scriptObject['KVS']; + if(Array.isArray(storeNames)) { + for(let store of storeNames) { + // If the value is not present in the cache, don't overwrite it; + // while this is not used in initial implementation, we could use + // it in future to update a single variable store value rather than + // the whole cache. + if(typeof values[store] == 'string') { + this.scriptObject[store] = values[store]; + } + } + } + } + // TODO: Better typing. private get _legacyLayoutSpec(): any { return this.scriptObject['KV']; // used with buildDefaultLayout; layout must be constructed at runtime. @@ -99,7 +149,7 @@ namespace com.keyman.keyboards { /** * HTML help text, as specified by either the &kmw_helptext or &kmw_helpfile system stores. - * + * * Reference: https://help.keyman.com/developer/language/reference/kmw_helptext, * https://help.keyman.com/developer/language/reference/kmw_helpfile */ @@ -121,7 +171,7 @@ namespace com.keyman.keyboards { * Embeds a custom script for use by the OSK, which may be interactive (like with sil_euro_latin). * Note: this must be called AFTER any contents of `helpText` have been inserted into the DOM. * (See sil_euro_latin's source -> sil_euro_latin_js.txt) - * + * * Reference: https://help.keyman.com/developer/language/reference/kmw_embedjs */ embedScript(e: any) { @@ -136,10 +186,10 @@ namespace com.keyman.keyboards { /** * true if this keyboard uses a (legacy) pick list (Chinese, Japanese, Korean, etc.) - * + * * TODO: Make a property on keyboards (say, `isPickList` / `KPL`) to signal this when we * get around to better, generalized picker-list support. - */ + */ get isCJK(): boolean { // I3363 (Build 301) var lg: string; if(typeof(this.scriptObject['KLC']) != 'undefined') { @@ -147,7 +197,7 @@ namespace com.keyman.keyboards { } else if(typeof(this.scriptObject['LanguageCode']) != 'undefined') { lg = this.scriptObject['LanguageCode']; } - + // While some of these aren't proper BCP-47 language codes, the CJK keyboards predate our use of BCP-47. // So, we preserve the old ISO 639-3 codes, as that's what the keyboards are matching against. return ((lg == 'cmn') || (lg == 'jpn') || (lg == 'kor')); @@ -186,7 +236,7 @@ namespace com.keyman.keyboards { this.scriptObject['_kmw'] = tag; } - return tag; + return tag; } get explodedStores(): {[storeName: string]: text.ComplexKeyboardStore} { @@ -209,7 +259,7 @@ namespace com.keyman.keyboards { if(this._legacyLayoutSpec == null) { return false; } - + // Only exists in KMW 10.0+, but before that Web had no chirality support, so... return false. let layers = this._legacyLayoutSpec['KLS']; if(!layers) { @@ -219,18 +269,18 @@ namespace com.keyman.keyboards { var emulationMask = modifierCodes['LCTRL'] | modifierCodes['LALT']; var unshiftedEmulationLayer = layers[Layouts.getLayerId(emulationMask)]; var shiftedEmulationLayer = layers[Layouts.getLayerId(modifierCodes['SHIFT'] | emulationMask)]; - + // buildDefaultLayout ensures that these are aliased to the original modifier set being emulated. // As a result, we can directly test for reference equality. // // This allows us to still return `true` after creating the layers for emulation; during keyboard // construction, the two layers should be null for AltGr emulation to succeed. - if(unshiftedEmulationLayer != null && + if(unshiftedEmulationLayer != null && unshiftedEmulationLayer != layers[Layouts.getLayerId(modifierCodes['RALT'])]) { return false; } - if(shiftedEmulationLayer != null && + if(shiftedEmulationLayer != null && shiftedEmulationLayer != layers[Layouts.getLayerId(modifierCodes['RALT'] | modifierCodes['SHIFT'])]) { return false; } @@ -273,9 +323,9 @@ namespace com.keyman.keyboards { /** * @param {number} _PCommand event code (16,17,18) or 0 * @param {Object} _PTarget target element - * @param {number} _PData 1 or 0 + * @param {number} _PData 1 or 0 * Notifies keyboard of keystroke or other event - */ + */ notify(_PCommand: number, _PTarget: text.OutputTarget, _PData: number) { // I2187 // Good example use case - the Japanese CJK-picker keyboard if(typeof(this.scriptObject['KNS']) == 'function') { @@ -318,7 +368,7 @@ namespace com.keyman.keyboards { rawSpecifications = {'F':'Tahoma', 'BK': Layouts.dfltText}; } - // Regardless of success, we'll want to initialize the field that backs the property; + // Regardless of success, we'll want to initialize the field that backs the property; // may as well cache the default layout we just built, or a 'null' if it shouldn't exist.. if(!this._layouts) { this._layouts = {}; @@ -339,7 +389,7 @@ namespace com.keyman.keyboards { /** * Returns an ActiveLayout object representing the keyboard's layout for this form factor. May return null if a custom desktop "help" OSK is defined, as with sil_euro_latin. - * + * * In such cases, please use either `helpText` or `insertHelpHTML` instead. * @param formFactor {string} The desired form factor for the layout. */ diff --git a/common/core/web/keyboard-processor/src/text/kbdInterface.ts b/common/core/web/keyboard-processor/src/text/kbdInterface.ts index 1324a9f167..fb729c0468 100644 --- a/common/core/web/keyboard-processor/src/text/kbdInterface.ts +++ b/common/core/web/keyboard-processor/src/text/kbdInterface.ts @@ -972,6 +972,9 @@ namespace com.keyman.text { // Capture the initial state of the OutputTarget before any rules are matched. let preInput = Mock.from(outputTarget); + // Capture the initial state of any variable stores + const cachedVariableStores = this.activeKeyboard.variableStores; + // Establishes the results object, allowing corresponding commands to set values here as appropriate. this.ruleBehavior = new RuleBehavior(); @@ -987,6 +990,12 @@ namespace com.keyman.text { // Finalize the rule's results. this.ruleBehavior.transcription = outputTarget.buildTranscriptionFrom(preInput, keystroke); + // We always backup the changes to variable stores to the RuleBehavior, to + // be applied during finalization, then restore them to the cached initial + // values to avoid side-effects with predictive text mocks. + this.ruleBehavior.variableStores = this.activeKeyboard.variableStores; + this.activeKeyboard.variableStores = cachedVariableStores; + // `matched` refers to whether or not the FINAL rule (from any group) matched, rather than // whether or not ANY rule matched. If the final rule doesn't match, we trigger the key's // default behavior (if appropriate). @@ -1001,6 +1010,19 @@ namespace com.keyman.text { return behavior; } + /** + * Applies the dictionary of variable store values to the active keyboard + * + * Has no effect on keyboards compiled with 14.0 or earlier; system store + * names are not exposed unless compiled with Developer 15.0 or later. + * + * @param stores A dictionary of stores which should be found in the + * keyboard + */ + applyVariableStores(stores: com.keyman.keyboards.VariableStoreDictionary): void { + this.activeKeyboard.variableStores = stores; + } + /** * Publishes the KeyboardInterface's shorthand API names. As this assigns the current functions * held by the longform versions, note that this should be called after replacing any of them via diff --git a/common/core/web/keyboard-processor/src/text/ruleBehavior.ts b/common/core/web/keyboard-processor/src/text/ruleBehavior.ts index 2ce6df5d77..27eadcfe3e 100644 --- a/common/core/web/keyboard-processor/src/text/ruleBehavior.ts +++ b/common/core/web/keyboard-processor/src/text/ruleBehavior.ts @@ -24,6 +24,11 @@ namespace com.keyman.text { */ saveStore: {[name: string]: VariableStore} = {}; + /** + * A set of variable stores with possible changes to be applied during finalization. + */ + variableStores: keyboards.VariableStoreDictionary = {}; + /** * Denotes a non-output default behavior; this should be evaluated later, against the true keystroke. */ @@ -46,9 +51,9 @@ namespace com.keyman.text { /** * In reference to https://github.com/keymanapp/keyman/pull/4350#issuecomment-768753852: - * - * If the final group processed is a context and keystroke group (using keys), - * and there is no nomatch rule, and the keystroke is not matched in the group, + * + * If the final group processed is a context and keystroke group (using keys), + * and there is no nomatch rule, and the keystroke is not matched in the group, * the keystroke's default behavior should trigger, regardless of whether or not any * rules in prior groups matched. */ @@ -78,6 +83,8 @@ namespace com.keyman.text { } } + processor.keyboardInterface.applyVariableStores(this.variableStores); + if(processor.keyboardInterface.variableStoreSerializer) { for(let storeID in this.saveStore) { processor.keyboardInterface.variableStoreSerializer.saveStore(processor.activeKeyboard.id, storeID, this.saveStore[storeID]); @@ -99,13 +106,13 @@ namespace com.keyman.text { /** * Merges default-related behaviors from another RuleBehavior into this one. Assumes that the current instance * "came first" chronologically. Both RuleBehaviors must be sourced from the same keystroke. - * + * * Intended use: merging rule-based behavior with default key behavior during scenarios like those described * at https://github.com/keymanapp/keyman/pull/4350#issuecomment-768753852. - * + * * This function does not attempt a "complete" merge for two fully-constructed RuleBehaviors! Things * WILL break for unintended uses. - * @param other + * @param other */ mergeInDefaults(other: RuleBehavior) { let keystroke = this.transcription.keystroke; @@ -115,7 +122,7 @@ namespace com.keyman.text { } this.triggersDefaultCommand = this.triggersDefaultCommand || other.triggersDefaultCommand; - + let mergingMock = Mock.from(this.transcription.preInput); mergingMock.apply(this.transcription.transform); mergingMock.apply(other.transcription.transform); diff --git a/common/core/web/tools/recorder/package-lock.json b/common/core/web/tools/recorder/package-lock.json index ac0c8d95b1..5e0568b4f6 100644 --- a/common/core/web/tools/recorder/package-lock.json +++ b/common/core/web/tools/recorder/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/core/web/tools/recorder/package.json b/common/core/web/tools/recorder/package.json index e8d6bbce66..9123cb3f6d 100644 --- a/common/core/web/tools/recorder/package.json +++ b/common/core/web/tools/recorder/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/recorder-core", - "version": "15.0.121", + "version": "15.0.124", "description": "Core classes used to develop KeymanWeb test cases based on keystrokes", "main": "index.js", "scripts": { @@ -17,10 +17,10 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "dependencies": { - "@keymanapp/keyboard-processor": "^15.0.121", - "@keymanapp/models-types": "^15.0.121", - "@keymanapp/web-environment": "^15.0.121", - "@keymanapp/web-utils": "^15.0.121", + "@keymanapp/keyboard-processor": "^15.0.124", + "@keymanapp/models-types": "^15.0.124", + "@keymanapp/web-environment": "^15.0.124", + "@keymanapp/web-utils": "^15.0.124", "@types/node": "^11.9.4" }, "devDependencies": { diff --git a/common/core/web/tools/sentry-manager/package-lock.json b/common/core/web/tools/sentry-manager/package-lock.json index 15c05ef78e..dcb0e19252 100644 --- a/common/core/web/tools/sentry-manager/package-lock.json +++ b/common/core/web/tools/sentry-manager/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/tools/sentry-manager/package.json b/common/core/web/tools/sentry-manager/package.json index a4e36fc5c6..1fda2f123d 100644 --- a/common/core/web/tools/sentry-manager/package.json +++ b/common/core/web/tools/sentry-manager/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-sentry-manager", - "version": "15.0.121", + "version": "15.0.124", "description": "Common setup for KeymanWeb's Sentry-based error reporting", "main": "dist/index.js", "scripts": { @@ -20,7 +20,7 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/web-environment": "^15.0.121", + "@keymanapp/web-environment": "^15.0.124", "@sentry/browser": "^5.27.4" } } diff --git a/common/core/web/utils/package-lock.json b/common/core/web/utils/package-lock.json index 0ae7ed2778..e6bd801152 100644 --- a/common/core/web/utils/package-lock.json +++ b/common/core/web/utils/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/core/web/utils/package.json b/common/core/web/utils/package.json index cb77f098e0..d1dbe167de 100644 --- a/common/core/web/utils/package.json +++ b/common/core/web/utils/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-utils", - "version": "15.0.121", + "version": "15.0.124", "description": "Common utility functions used throughout other Keyman packages", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -20,8 +20,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.121", - "@keymanapp/web-environment": "^15.0.121", + "@keymanapp/resources-gosh": "^15.0.124", + "@keymanapp/web-environment": "^15.0.124", "@types/node": "^14.0.5", "typescript": "^3.8.3" } diff --git a/common/models/templates/package-lock.json b/common/models/templates/package-lock.json index 2d33f3e9fb..5d0fa0670c 100644 --- a/common/models/templates/package-lock.json +++ b/common/models/templates/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/templates/package.json b/common/models/templates/package.json index 0e8e0895fd..94da18b688 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-templates", - "version": "15.0.121", + "version": "15.0.124", "description": "Backing model implementations (templates) for Keyman's modelling layer", "keywords": [ "lm", @@ -42,8 +42,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.121", - "@keymanapp/web-utils": "^15.0.121", + "@keymanapp/models-types": "^15.0.124", + "@keymanapp/web-utils": "^15.0.124", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "@types/node": "^14.0.4", @@ -52,6 +52,6 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-wordbreakers": "^15.0.121" + "@keymanapp/models-wordbreakers": "^15.0.124" } } diff --git a/common/models/types/package-lock.json b/common/models/types/package-lock.json index 481f13e65f..d1bb3374d4 100644 --- a/common/models/types/package-lock.json +++ b/common/models/types/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/common/models/types/package.json b/common/models/types/package.json index 7502298fc4..703f43f83d 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-types", - "version": "15.0.121", + "version": "15.0.124", "description": "Type definitions in used in the modeling (lexical model/predictive text) component of Keyman.", "types": "./index.d.ts", "scripts": { diff --git a/common/models/wordbreakers/package-lock.json b/common/models/wordbreakers/package-lock.json index 41f552fa4c..8cb6c98709 100644 --- a/common/models/wordbreakers/package-lock.json +++ b/common/models/wordbreakers/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index b0808fe26b..7130127573 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/models-wordbreakers", - "version": "15.0.121", + "version": "15.0.124", "description": "Word breakers bundled in the modeling layer", "keywords": [ "lexical model", @@ -40,7 +40,7 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/models-types": "^15.0.121", + "@keymanapp/models-types": "^15.0.124", "@types/chai": "^4.2.11", "@types/mocha": "^7.0.2", "chai": "^4.3.4", diff --git a/common/predictive-text/package-lock.json b/common/predictive-text/package-lock.json index 4136d44a9e..d01ea25722 100644 --- a/common/predictive-text/package-lock.json +++ b/common/predictive-text/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index a0b9cbafcc..d42f85e2af 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-layer", - "version": "15.0.121", + "version": "15.0.124", "description": "Keyman/Predictive Text integration layer", "main": "build/index.js", "scripts": { @@ -21,8 +21,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/models-types": "^15.0.121", - "@keymanapp/resources-gosh": "^15.0.121", + "@keymanapp/models-types": "^15.0.124", + "@keymanapp/resources-gosh": "^15.0.124", "@types/node": "^10.17.21", "chai": "^4.3.4", "karma": "^6.3.4", @@ -46,10 +46,10 @@ "typescript": "^3.8.3" }, "dependencies": { - "@keymanapp/models-templates": "^15.0.121", - "@keymanapp/models-wordbreakers": "^15.0.121", - "@keymanapp/web-environment": "^15.0.121", - "@keymanapp/web-utils": "^15.0.121", + "@keymanapp/models-templates": "^15.0.124", + "@keymanapp/models-wordbreakers": "^15.0.124", + "@keymanapp/web-environment": "^15.0.124", + "@keymanapp/web-utils": "^15.0.124", "es6-shim": "^0.35.5", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0" diff --git a/developer/js/package-lock.json b/developer/js/package-lock.json index ddb4725e1b..fc6f4b19d0 100644 --- a/developer/js/package-lock.json +++ b/developer/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/developer/js/package.json b/developer/js/package.json index c90646ce02..30e4ece169 100644 --- a/developer/js/package.json +++ b/developer/js/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/lexical-model-compiler", - "version": "15.0.121", + "version": "15.0.124", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", @@ -37,14 +37,14 @@ "node": ">=12.0.0" }, "dependencies": { - "@keymanapp/models-types": "^15.0.121", + "@keymanapp/models-types": "^15.0.124", "commander": "^3.0.0", "typescript": "^3.8.3", "xml2js": "^0.4.19" }, "devDependencies": { - "@keymanapp/models-templates": "^15.0.121", - "@keymanapp/models-wordbreakers": "^15.0.121", + "@keymanapp/models-templates": "^15.0.124", + "@keymanapp/models-wordbreakers": "^15.0.124", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", diff --git a/ios/engine/KMEI/KeymanEngine/am.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/am.lproj/Localizable.strings index efd3bd7e52..f694d584a3 100644 --- a/ios/engine/KMEI/KeymanEngine/am.lproj/Localizable.strings +++ b/ios/engine/KMEI/KeymanEngine/am.lproj/Localizable.strings @@ -49,6 +49,12 @@ /* Confirmation text to display before uninstalling a lexical model */ "command-uninstall-lexical-model-confirm" = "แ‹ญแˆ…แŠ•แŠ• แˆ˜แ‹แŒˆแ‰  แ‰ƒแˆ‹แ‰ต แˆแ‰ณแŒ แ‹แ‹ แ‰ตแˆแˆแŒ‹แˆˆแˆ…?"; +/* Text for error when a keyboard cannot load properly */ +"error-loading-keyboard" = "แ‹จแ‰ฐแŒ แ‹จแ‰€แ‹ แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณแ‹แŠ• แˆŠแŒซแŠ• แŠ แˆแ‰ฐแ‰ปแˆˆแˆ"; + +/* Text for error when a lexical model cannot load properly */ +"error-loading-lexical-model" = "แ‹จแ‰ฐแŒ แ‹จแ‰€แ‹ แ‹ฒแŠญแˆฝแŠ“แˆช แˆŠแŒซแŠ• แŠ แˆแ‰ฐแ‰ปแˆˆแˆ"; + /* Text for error when an installed file is unexpectedly missing */ "error-missing-file" = "แ‹จแŠฅแˆญแ‹ณแ‰ณ แˆฐแАแ‹ฑแŠ• แˆ›แŒแŠ˜แ‰ต แŠ แˆแ‰ฐแ‰ปแˆˆแˆแข"; @@ -181,6 +187,36 @@ /* Title for the main Settings menu */ "menu-settings-title" = "แ‹จแŠชแ‹ญแˆ›แŠ• แ‰…แŠ•แ‰ฅแˆฎแ‰ฝ"; +/* Secondary text showing current setting for spacebar caption - blank */ +"menu-settings-spacebar-hint-blank" = "แ‹จแˆตแ”แˆตแ‰ฃแˆฉ แˆ˜แŒแˆˆแŒซ แŒฝแˆแ แŠ แ‰ณแˆณแ‹ญ"; + +/* Secondary text showing current setting for spacebar caption - keyboard */ +"menu-settings-spacebar-hint-keyboard" = "แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณแ‹แŠ• แˆตแˆ แ‰ แˆตแ”แˆต แ‰ฃแˆฉ แˆ‹แ‹ญ แŠ แˆณแ‹ญ"; + +/* Secondary text showing current setting for spacebar caption - language */ +"menu-settings-spacebar-hint-language" = "แ‹จแ‰‹แŠ•แ‰‹แ‹แŠ• แˆตแˆ แ‰ แˆตแ”แˆต แ‰ฃแˆฉ แˆ‹แ‹ญ แŠ แˆณแ‹ญ"; + +/* Secondary text showing current setting for spacebar caption - language + keyboard */ +"menu-settings-spacebar-hint-languageKeyboard" = "แ‰‹แŠ•แ‰‹แŠ“ แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณแ‹แŠ• แ‰ แˆตแ”แˆต แ‰ฃแˆฉ แˆ‹แ‹ญ"; + +/* Label for the "Spacebar Caption" item on the main settings screen */ +"menu-settings-spacebar-text" = "แ‹จแˆตแ”แˆตแ‰ฃแˆฉ แˆ˜แŒแˆˆแŒซ แŒฝแˆแ"; + +/* Title for the "Spacebar Caption" settings screen */ +"menu-settings-spacebar-title" = "แ‹จแˆตแ”แˆตแ‰ฃแˆฉ แˆ˜แŒแˆˆแŒซ แŒฝแˆแ"; + +/* Text showing name of spacebar caption - blank */ +"menu-settings-spacebar-item-blank" = "แ‰ฃแ‹ถ"; + +/* Text showing name of spacebar caption - keyboard */ +"menu-settings-spacebar-item-keyboard" = "แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณ"; + +/* Text showing name of spacebar caption - language */ +"menu-settings-spacebar-item-language" = "แ‰‹แŠ•แ‰‹"; + +/* Text showing name of spacebar caption - language + keyboard */ +"menu-settings-spacebar-item-languageKeyboard" = "แ‰‹แŠ•แ‰‹แŠ“ แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ"; + /* Short text for notification: download failure for keyboard */ "notification-download-failure-keyboard" = "แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณ แˆ˜แ‹แˆจแ‹ต แŠ แˆแ‰ปแˆˆแˆ"; diff --git a/lerna.json b/lerna.json index 576ecc9c1e..e062c20e3c 100644 --- a/lerna.json +++ b/lerna.json @@ -20,5 +20,5 @@ "ts-node", "typescript" ], - "version": "15.0.121" + "version": "15.0.124" } diff --git a/linux/scripts/debian.sh b/linux/scripts/debian.sh index c81ba49efe..ab27887c66 100755 --- a/linux/scripts/debian.sh +++ b/linux/scripts/debian.sh @@ -31,7 +31,7 @@ for proj in ${projects}; do downloadSource debianpackage cd ${proj}-${version} - if [ -n $DIST ]; then + if [ -n "$DIST" ]; then EXTRA_ARGS="--distribution $DIST --force-distribution" fi dch --newversion ${version}-1 ${EXTRA_ARGS} "Re-release to Debian" diff --git a/mac/help/start/install-keyman.md b/mac/help/start/install-keyman.md index 1296a58673..a3bbdb0733 100644 --- a/mac/help/start/install-keyman.md +++ b/mac/help/start/install-keyman.md @@ -32,6 +32,18 @@ Apple Disk Image (.dmg) file. It should be installed according to the following 5. Now you can start typing with the selected keyboard in any text editor or application that uses standard macOS input methods. +--- + +If you're using Homebrew, you can also install Keyman with the command: + +1. +``` +brew install --cask keyman +``` + +2. Configure the macOS [security permissions](../troubleshooting/configure-security) for Keyman to run. + +---- ## How To - Updating Keyman for macOS When a new version of Keyman is released, you can update by following the instructions diff --git a/resources/gosh/package-lock.json b/resources/gosh/package-lock.json index 5feba92c70..63518a6743 100644 --- a/resources/gosh/package-lock.json +++ b/resources/gosh/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 1 } diff --git a/resources/gosh/package.json b/resources/gosh/package.json index d9e6f251a7..1b32bb03e4 100644 --- a/resources/gosh/package.json +++ b/resources/gosh/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/resources-gosh", - "version": "15.0.121", + "version": "15.0.124", "description": "Good Ol' Shell", "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ diff --git a/resources/web-environment/package-lock.json b/resources/web-environment/package-lock.json index 2731c1c514..f7d69dba86 100644 --- a/resources/web-environment/package-lock.json +++ b/resources/web-environment/package-lock.json @@ -1,5 +1,5 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 1 } diff --git a/resources/web-environment/package.json b/resources/web-environment/package.json index 7119795fb3..a866727f55 100644 --- a/resources/web-environment/package.json +++ b/resources/web-environment/package.json @@ -1,6 +1,6 @@ { "name": "@keymanapp/web-environment", - "version": "15.0.121", + "version": "15.0.124", "description": "A compilation utility for passing select environment variables into our TS builds", "author": "Joshua Horton (https://github.com/jahorton)", "homepage": "https://github.com/keymanapp/keyman", @@ -17,6 +17,6 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.121" + "@keymanapp/resources-gosh": "^15.0.124" } } diff --git a/web/package-lock.json b/web/package-lock.json index 12256bd7b6..957989ef76 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.121", + "version": "15.0.124", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/web/package.json b/web/package.json index a00a78847b..d02db532eb 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "keyman", - "version": "15.0.121", + "version": "15.0.124", "description": "Facilitates text input in any language.", "repository": { "type": "git", @@ -18,8 +18,8 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "^15.0.121", - "@keymanapp/web-sentry-manager": "^15.0.121", + "@keymanapp/resources-gosh": "^15.0.124", + "@keymanapp/web-sentry-manager": "^15.0.124", "@sentry/cli": "^1.52.3", "chai": "^4.3.4", "google-closure-compiler-java": "^20200224.0.0", @@ -52,13 +52,13 @@ "sentry-cli": "sentry-cli" }, "dependencies": { - "@keymanapp/input-processor": "^15.0.121", - "@keymanapp/keyboard-processor": "^15.0.121", - "@keymanapp/lexical-model-layer": "^15.0.121", - "@keymanapp/models-types": "^15.0.121", - "@keymanapp/recorder-core": "^15.0.121", - "@keymanapp/web-environment": "^15.0.121", - "@keymanapp/web-utils": "^15.0.121", + "@keymanapp/input-processor": "^15.0.124", + "@keymanapp/keyboard-processor": "^15.0.124", + "@keymanapp/lexical-model-layer": "^15.0.124", + "@keymanapp/models-types": "^15.0.124", + "@keymanapp/recorder-core": "^15.0.124", + "@keymanapp/web-environment": "^15.0.124", + "@keymanapp/web-utils": "^15.0.124", "@types/node": "^11.9.4", "es6-shim": "^0.35.5", "eventemitter3": "^4.0.0" diff --git a/web/testing/common/test.css b/web/testing/common/test.css new file mode 100644 index 0000000000..88eacbf7e4 --- /dev/null +++ b/web/testing/common/test.css @@ -0,0 +1,22 @@ +body { + font-family: Tahoma,helvetica; +} + +h3 { + font-size: 1em; + font-weight:normal; + color: darkred; + margin-bottom: 4px +} + +.test { + font-size: 1.5em; + width:80%; + min-height:30px; + border: 1px solid gray; +} + +#KeymanWebControl { + width:50%; + min-width:600px; +} diff --git a/web/testing/index.html b/web/testing/index.html index ff0ee91c11..c8821e4ade 100644 --- a/web/testing/index.html +++ b/web/testing/index.html @@ -59,6 +59,7 @@

Test page for context() and notany() interaction

Test SpacebarText APIs (#949)

Test inline OSK (#5665)

+

Test variable stores and predictive text (#2924)

Return to main index. diff --git a/web/testing/issue2924/dmg.dv.test.model.js b/web/testing/issue2924/dmg.dv.test.model.js new file mode 100644 index 0000000000..59d6e71cd5 --- /dev/null +++ b/web/testing/issue2924/dmg.dv.test.model.js @@ -0,0 +1,55 @@ +(function() { +'use strict'; +var definitions = { + defaults: { + version: "undefined", + applyCasing: function defaultApplyCasing(casing, text) { + switch (casing) { + case 'lower': + return text.toLowerCase(); + case 'upper': + return text.toUpperCase(); + case 'initial': + var headCode = text.charCodeAt(0); + // The length of the first code unit, as measured in code points. + var headUnitLength = 1; + // Is the first character a high surrogate, indicating possible use of UTF-16 + // surrogate pairs? Also, is the string long enough for there to BE a pair? + if (text.length > 1 && headCode >= 0xD800 && headCode <= 0xDBFF) { + // It's possible, so now we check for low surrogates. + var lowSurrogateCode = text.charCodeAt(1); + if (lowSurrogateCode >= 0xDC00 && lowSurrogateCode <= 0xDFFF) { + // We have a surrogate pair; this pair is the 'first' character. + headUnitLength++; + } + } + // Capitalizes the first code unit of the string, leaving the rest intact. + return text.substring(0, headUnitLength).toUpperCase() // head - uppercased + .concat(text.substring(headUnitLength)); // tail - lowercased + } +} + }, model: { + searchTermToKey: function defaultCasedSearchTermToKey(wordform, applyCasing) { + // While this is a bit WET, as the basic `defaultSearchTermToKey` exists and performs some of + // the same functions, repetition is the easiest way to allow the function to be safely compiled + // with ease by use of `.toString()`. + return Array.from(wordform + .normalize('NFKD') + // Remove any combining diacritics (if input is in NFKD) + .replace(/[\u0300-\u036F]/g, '')) // end of `Array.from` + .map(function (c) { return applyCasing('lower', c); }) + .join(''); +} + }, + applyCasing: function(caseToApply, text) { + return definitions.defaults.applyCasing(caseToApply, text); + }, + searchTermToKey: function(text) { + return definitions.model.searchTermToKey(text, definitions.applyCasing); + } +}; +LMLayerWorker.loadModel(new models.TrieModel({"totalWeight":126,"root":{"type":"internal","weight":100,"values":["t","h","w","e"],"children":{"t":{"type":"leaf","weight":100,"entries":[{"key":"the","weight":100,"content":"the"}]},"e":{"type":"leaf","weight":5,"entries":[{"key":"example","weight":5,"content":"example"}]},"h":{"type":"leaf","weight":10,"entries":[{"key":"hello","weight":10,"content":"hello"}]},"w":{"type":"internal","weight":8,"values":["o"],"children":{"o":{"type":"internal","weight":8,"values":["r"],"children":{"r":{"type":"internal","weight":8,"values":["l","d"],"children":{"l":{"type":"leaf","weight":8,"entries":[{"key":"world","weight":8,"content":"world"}]},"d":{"type":"leaf","weight":3,"entries":[{"key":"wordlist","weight":3,"content":"wordlist"}]}}}}}}}}}}, { + wordBreaker: wordBreakers['default'], + searchTermToKey: definitions.searchTermToKey, +})); +})(); \ No newline at end of file diff --git a/web/testing/issue2924/header.js b/web/testing/issue2924/header.js new file mode 100644 index 0000000000..8e3ecb745a --- /dev/null +++ b/web/testing/issue2924/header.js @@ -0,0 +1,43 @@ +keyman.init({ + attachType: 'auto' +}); + +window.addEventListener('load', function() { + keyman.addKeyboards({ + id: 'testvariable', + name: 'testvariable', + languages: [{ + id:'en', name:'English' + }], + filename: './testvariable.js' + }); + + keyman.addKeyboards({ + id: 'oldtestvariable', + name: 'oldtestvariable', + languages: [{ + id:'en', name:'English' + }], + filename: './oldtestvariable.js' + }); + + keyman.addKeyboards({ + id: 'us', + name: 'English US', + languages: [{ + id: 'en', + name: 'English' + }], + filename: '../us-1.0.js' + }); + + const pageRef = (window.location.protocol == 'file:') + ? window.location.href.substr(0, window.location.href.lastIndexOf('/')+1) + : window.location.href; + + keyman.modelManager.register({ + id: 'nrc.en.mtnt', + languages: ['en'], + path: pageRef + 'dmg.dv.test.model.js' + }); +}, false); diff --git a/web/testing/issue2924/index.html b/web/testing/issue2924/index.html new file mode 100644 index 0000000000..8f51a81270 --- /dev/null +++ b/web/testing/issue2924/index.html @@ -0,0 +1,25 @@ + + + + + + KeymanWeb Issue 2924 - Variable Stores and Predictive Text + + + + + + + +

KeymanWeb Test Page - Variable Stores and Predictive Text

+

Ref: issue #2924

+
+
+
+

Type in your language in this text area:

+ +
+
+

Return to testing home page

+ + diff --git a/web/testing/issue2924/oldtestvariable.js b/web/testing/issue2924/oldtestvariable.js new file mode 100644 index 0000000000..0b7c3bc135 --- /dev/null +++ b/web/testing/issue2924/oldtestvariable.js @@ -0,0 +1,136 @@ +if(typeof keyman === 'undefined') { + console.log('Keyboard requires KeymanWeb 10.0 or later'); + if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later"); +} else { +KeymanWeb.KR(new Keyboard_oldtestvariable()); +} +function Keyboard_oldtestvariable() +{ + var modCodes = keyman.osk.modifierCodes; + var keyCodes = keyman.osk.keyCodes; + + this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9; + this.KI="Keyboard_oldtestvariable"; + this.KN="oldtestvariable"; + this.KMINVER="10.0"; + this.KV={F:' 1em "Arial"',K102:0}; + this.KV.KLS={ + "default": ["","","","","","","","","","","","","","","","","","","","Reveal","","","","","","","","","","","","","","Set","","","","","","","","","","","","","","","","","","Clear","","","","","","","","","","","","",""] + }; + this.KV.BK=(function(x){ + var + empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""), + result=[], v, i, + modifiers=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt']; + for(i=modifiers.length-1;i>=0;i--) { + v = x[modifiers[i]]; + if(v || result.length > 0) { + result=(v ? v : empty).slice().concat(result); + } + } + return result; + })(this.KV.KLS); + this.KDU=0; + this.KH=''; + this.KM=0; + this.KBVER="1.0"; + this.KMBM=0 /* 0x0000 */; + this.KVKL={ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_S", + "text": "Set" + }, + { + "id": "K_C", + "text": "Clear" + }, + { + "id": "K_R", + "text": "Reveal" + }, + { + "width": "100", + "id": "K_BKSP", + "sp": "1", + "text": "*BkSp*" + } + ] + }, + { + "id": "2", + "key": [ + { + "width": "140", + "id": "K_LOPT", + "sp": "1", + "text": "*Menu*" + }, + { + "id": "K_SPACE" + }, + { + "width": "145", + "id": "K_ENTER", + "sp": "1", + "text": "*Enter*" + } + ] + } + ] + } + ] + } +} +; + this.s_myVar_9=KeymanWeb.KLOAD(this.KI,"myVar","1"); + this.s13="1"; + this.s14="0"; + this.s15="1"; + this.s16="1"; + this.KVER="15.0.105.0"; + //this.KVS=['s_myVar_9']; <-- manually stubbed out to demonstrate old behaviour + this.gs=function(t,e) { + return this.g_main_0(t,e); + }; + this.g_main_0=function(t,e) { + var k=KeymanWeb,r=0,m=0; + if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 16 + k.KDC(0,t); + k.KO(-1,t,"(-)"); + this.s_myVar_9=this.s14; + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_R /* 0x52 */)) { + if(this.s_myVar_9===this.s15){ + r=m=1; // Line 17 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + else if(this.s_myVar_9!==this.s16){ + r=m=1; // Line 18 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 15 + k.KDC(0,t); + k.KO(-1,t,"(+)"); + this.s_myVar_9=this.s13; + } + } + return r; + }; +} diff --git a/web/testing/issue2924/source/dmg.dv.test/HISTORY.md b/web/testing/issue2924/source/dmg.dv.test/HISTORY.md new file mode 100644 index 0000000000..1be9a3fd18 --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/HISTORY.md @@ -0,0 +1,6 @@ +test Change History +==================== + +1.0 (2020-05-27) +---------------- +* Created by dmg diff --git a/web/testing/issue2924/source/dmg.dv.test/LICENSE.md b/web/testing/issue2924/source/dmg.dv.test/LICENSE.md new file mode 100644 index 0000000000..ebf6171968 --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +ยฉ 2020 dmg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/testing/issue2924/source/dmg.dv.test/README.md b/web/testing/issue2924/source/dmg.dv.test/README.md new file mode 100644 index 0000000000..5aa6013c38 --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/README.md @@ -0,0 +1,29 @@ +test lexical model +=================== + +ยฉ 2020 dmg + +Version 1.0 + +Description +----------- + +test generated from template + +Links +----- + +Supported Platforms +------------------- + * Windows + * macOS + * Linux + * Web + * iPhone + * iPad + * Android phone + * Android tablet + * Mobile devices + * Desktop devices + * Tablet devices + diff --git a/web/testing/issue2924/source/dmg.dv.test/dmg.dv.test.kpj b/web/testing/issue2924/source/dmg.dv.test/dmg.dv.test.kpj new file mode 100644 index 0000000000..e4d5c9c567 --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/dmg.dv.test.kpj @@ -0,0 +1,83 @@ + + + + $PROJECTPATH\build + True + True + True + lexicalmodel + + + + id_6d8c9bbe8c11c08cca045e5e87c342c6 + dmg.dv.test.model.ts + source\dmg.dv.test.model.ts + 1.0 + .ts + + + id_521f7de068a80527c98d0c04c05faa94 + dmg.dv.test.model.kps + source\dmg.dv.test.model.kps + 1.0 + .kps +
+ test + ยฉ 2020 dmg + 1.0 +
+
+ + id_ede98e4633e239f933cbfd1f4e1b766c + HISTORY.md + HISTORY.md + + .md + + + id_53e892b8b41cc4caece1cfd5ef21d6e7 + LICENSE.md + LICENSE.md + + .md + + + id_0730bb7c2e8f9ea2438b52e419dd86c9 + README.md + README.md + + .md + + + id_4b12eec2a4d0bfc7b5441a4a59691eab + dmg.dv.test.model_info + dmg.dv.test.model_info + + .model_info + + + id_6ee5fa96871fb96ecebf2cbe9ec7425b + dmg.dv.test.model.js + source\..\build\dmg.dv.test.model.js + + .js + id_521f7de068a80527c98d0c04c05faa94 + + + id_356e5d149c1e539356d72698c1e401a6 + welcome.htm + source\welcome.htm + + .htm + id_521f7de068a80527c98d0c04c05faa94 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_521f7de068a80527c98d0c04c05faa94 + +
+
diff --git a/web/testing/issue2924/source/dmg.dv.test/dmg.dv.test.model_info b/web/testing/issue2924/source/dmg.dv.test/dmg.dv.test.model_info new file mode 100644 index 0000000000..03399a9f8e --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/dmg.dv.test.model_info @@ -0,0 +1,7 @@ +{ + "license": "mit", + "languages": [ + "dv" + ], + "description": "test generated from template" +} diff --git a/web/testing/issue2924/source/dmg.dv.test/source/dmg.dv.test.model.kps b/web/testing/issue2924/source/dmg.dv.test/source/dmg.dv.test.model.kps new file mode 100644 index 0000000000..1aad80f3ff --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/source/dmg.dv.test.model.kps @@ -0,0 +1,54 @@ + + + + 13.0.108.0 + 12.0 + + + + readme.htm + + + + + + + + + test + ยฉ 2020 dmg + dmg + 1.0 + + + + ..\build\dmg.dv.test.model.js + + 0 + .js + + + welcome.htm + + 0 + .htm + + + readme.htm + + 0 + .htm + + + + + + test + dmg.dv.test + + Dhivehi + + + + + diff --git a/web/testing/issue2924/source/dmg.dv.test/source/dmg.dv.test.model.ts b/web/testing/issue2924/source/dmg.dv.test/source/dmg.dv.test.model.ts new file mode 100644 index 0000000000..69c3e6acbd --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/source/dmg.dv.test.model.ts @@ -0,0 +1,14 @@ +/* + test 1.0 generated from template. + + This is a minimal lexical model source that uses a tab delimited wordlist. + See documentation online at https://help.keyman.com/developer/ for + additional parameters. +*/ + +const source: LexicalModelSource = { + format: 'trie-1.0', + wordBreaker: 'default', + sources: ['wordlist.tsv'], +}; +export default source; \ No newline at end of file diff --git a/web/testing/issue2924/source/dmg.dv.test/source/readme.htm b/web/testing/issue2924/source/dmg.dv.test/source/readme.htm new file mode 100644 index 0000000000..ded2822a56 --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/source/readme.htm @@ -0,0 +1,23 @@ + + + + + test + + + + +

test

+ +

+ test 1.0 generated from template. +

+ +

ยฉ 2020 dmg

+ + + diff --git a/web/testing/issue2924/source/dmg.dv.test/source/welcome.htm b/web/testing/issue2924/source/dmg.dv.test/source/welcome.htm new file mode 100644 index 0000000000..592fb47d7e --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/source/welcome.htm @@ -0,0 +1,27 @@ + + + + + Start Using test + + + + +

Start Using test

+ +

+ test 1.0 generated from template. +

+ +

Wordlist Model Documentation

+ + + +

ยฉ 2020 dmg

+ + + \ No newline at end of file diff --git a/web/testing/issue2924/source/dmg.dv.test/source/wordlist.tsv b/web/testing/issue2924/source/dmg.dv.test/source/wordlist.tsv new file mode 100644 index 0000000000..73d581f275 --- /dev/null +++ b/web/testing/issue2924/source/dmg.dv.test/source/wordlist.tsv @@ -0,0 +1,16 @@ +# +# test 1.0 generated from template. +# +# This is an example tab-separated wordlist file that can be edited in a spreadsheet +# program or regenerated from a wordlist tool. See lexical model documentation at +# https://help.keyman.com/developer/ for tools. +# +# Columns +# ======= +# WORD FREQUENCY (Optional)NOTES +# +the 100 +example 5 +hello 10 +world 8 +wordlist 3 \ No newline at end of file diff --git a/web/testing/issue2924/source/testvariable/HISTORY.md b/web/testing/issue2924/source/testvariable/HISTORY.md new file mode 100644 index 0000000000..c9cc218cd7 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/HISTORY.md @@ -0,0 +1,6 @@ +testvariable Change History +==================== + +1.0 (2020-05-26) +---------------- +* Created by diff --git a/web/testing/issue2924/source/testvariable/LICENSE.md b/web/testing/issue2924/source/testvariable/LICENSE.md new file mode 100644 index 0000000000..714cb32594 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +ยฉ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/testing/issue2924/source/testvariable/README.md b/web/testing/issue2924/source/testvariable/README.md new file mode 100644 index 0000000000..ef79c3b972 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/README.md @@ -0,0 +1,29 @@ +testvariable keyboard +============== + +ยฉ + +Version 1.0 + +Description +----------- + +testvariable generated from template + +Links +----- + +Supported Platforms +------------------- + * Windows + * macOS + * Linux + * Web + * iPhone + * iPad + * Android phone + * Android tablet + * Mobile devices + * Desktop devices + * Tablet devices + diff --git a/web/testing/issue2924/source/testvariable/source/readme.htm b/web/testing/issue2924/source/testvariable/source/readme.htm new file mode 100644 index 0000000000..716786bf70 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/source/readme.htm @@ -0,0 +1,23 @@ + + + + + testvariable + + + + +

testvariable

+ +

+ testvariable 1.0 generated from template. +

+ +

ยฉ

+ + + diff --git a/web/testing/issue2924/source/testvariable/source/testvariable.keyman-touch-layout b/web/testing/issue2924/source/testvariable/source/testvariable.keyman-touch-layout new file mode 100644 index 0000000000..2bc510c34c --- /dev/null +++ b/web/testing/issue2924/source/testvariable/source/testvariable.keyman-touch-layout @@ -0,0 +1,57 @@ +{ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_S", + "text": "Set" + }, + { + "id": "K_C", + "text": "Clear" + }, + { + "id": "K_R", + "text": "Reveal" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "140", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "145", + "sp": "1" + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/web/testing/issue2924/source/testvariable/source/testvariable.kmn b/web/testing/issue2924/source/testvariable/source/testvariable.kmn new file mode 100644 index 0000000000..3e6b8fe234 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/source/testvariable.kmn @@ -0,0 +1,18 @@ +๏ปฟstore(&VERSION) '10.0' +store(&NAME) 'testvariable' +store(©RIGHT) 'ยฉ' +store(&KEYBOARDVERSION) '1.0' +store(&TARGETS) 'any' +store(&LAYOUTFILE) 'testvariable.keyman-touch-layout' + +store (myVar) "1" +store(&VISUALKEYBOARD) 'testvariable.kvks' + +begin Unicode > use(main) + +group(main) using keys + ++ [K_S] > "(+)" set(myVar="1") c S will Set the variable, and output (+) ++ [K_C] > "(-)" set(myVar="0") c C will Clear the variable, and output (-) +if (myVar = "1") + [K_R] > "+" c R will Reveal the variable's state with + or - +if (myVar!= "1") + [K_R] > "-" diff --git a/web/testing/issue2924/source/testvariable/source/testvariable.kps b/web/testing/issue2924/source/testvariable/source/testvariable.kps new file mode 100644 index 0000000000..72de7413e6 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/source/testvariable.kps @@ -0,0 +1,67 @@ + + + + 13.0.108.0 + 7.0 + + + + readme.htm + + + + + + + + + + testvariable + ยฉ + + + + + + ..\build\testvariable.kmx + + 0 + .kmx + + + ..\build\testvariable.js + + 0 + .js + + + ..\build\testvariable.kvk + + 0 + .kvk + + + welcome.htm + + 0 + .htm + + + readme.htm + + 0 + .htm + + + + + testvariable + testvariable + 1.0 + + Dhivehi + + + + + diff --git a/web/testing/issue2924/source/testvariable/source/testvariable.kvks b/web/testing/issue2924/source/testvariable/source/testvariable.kvks new file mode 100644 index 0000000000..3a8b604523 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/source/testvariable.kvks @@ -0,0 +1,15 @@ + + +
+ 10.0 + testvariable + +
+ + + Clear + Reveal + Set + + +
diff --git a/web/testing/issue2924/source/testvariable/source/welcome.htm b/web/testing/issue2924/source/testvariable/source/welcome.htm new file mode 100644 index 0000000000..4fc9b434de --- /dev/null +++ b/web/testing/issue2924/source/testvariable/source/welcome.htm @@ -0,0 +1,27 @@ + + + + + Start Using testvariable + + + + +

Start Using testvariable

+ +

+ testvariable 1.0 generated from template. +

+ +

Keyboard Layout

+ + + +

ยฉ

+ + + \ No newline at end of file diff --git a/web/testing/issue2924/source/testvariable/testvariable.keyboard_info b/web/testing/issue2924/source/testvariable/testvariable.keyboard_info new file mode 100644 index 0000000000..5e54349207 --- /dev/null +++ b/web/testing/issue2924/source/testvariable/testvariable.keyboard_info @@ -0,0 +1,7 @@ +{ + "license": "mit", + "languages": [ + "en" + ], + "description": "testvariable generated from template" +} diff --git a/web/testing/issue2924/source/testvariable/testvariable.kpj b/web/testing/issue2924/source/testvariable/testvariable.kpj new file mode 100644 index 0000000000..a138eda11b --- /dev/null +++ b/web/testing/issue2924/source/testvariable/testvariable.kpj @@ -0,0 +1,102 @@ + + + + $PROJECTPATH\build + True + True + True + keyboard + + + + id_a8c52d62d9f15eb62fde81d1a591d3b7 + testvariable.kmn + source\testvariable.kmn + 1.0 + .kmn +
+ testvariable + ยฉ +
+
+ + id_25e4c084428d0ee657e7a56c3ad5dc3c + testvariable.kps + source\testvariable.kps + + .kps +
+ testvariable + ยฉ +
+
+ + id_ede98e4633e239f933cbfd1f4e1b766c + HISTORY.md + HISTORY.md + + .md + + + id_53e892b8b41cc4caece1cfd5ef21d6e7 + LICENSE.md + LICENSE.md + + .md + + + id_0730bb7c2e8f9ea2438b52e419dd86c9 + README.md + README.md + + .md + + + id_efaa79249320cda8d8e2886ddbf0475e + testvariable.keyboard_info + testvariable.keyboard_info + + .keyboard_info + + + id_0c280146c90477c06029a32b48581f90 + testvariable.kmx + source\..\build\testvariable.kmx + + .kmx + id_25e4c084428d0ee657e7a56c3ad5dc3c + + + id_694dbba5eb4cd5c4f122133b889f7eb4 + testvariable.js + source\..\build\testvariable.js + + .js + id_25e4c084428d0ee657e7a56c3ad5dc3c + + + id_6c3d7ac2bff7cba3383a3d6ca5e2a2ef + testvariable.kvk + source\..\build\testvariable.kvk + + .kvk + id_25e4c084428d0ee657e7a56c3ad5dc3c + + + id_356e5d149c1e539356d72698c1e401a6 + welcome.htm + source\welcome.htm + + .htm + id_25e4c084428d0ee657e7a56c3ad5dc3c + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_25e4c084428d0ee657e7a56c3ad5dc3c + +
+
diff --git a/web/testing/issue2924/testvariable.js b/web/testing/issue2924/testvariable.js new file mode 100644 index 0000000000..0958c59f70 --- /dev/null +++ b/web/testing/issue2924/testvariable.js @@ -0,0 +1,136 @@ +if(typeof keyman === 'undefined') { + console.log('Keyboard requires KeymanWeb 10.0 or later'); + if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later"); +} else { +KeymanWeb.KR(new Keyboard_testvariable()); +} +function Keyboard_testvariable() +{ + var modCodes = keyman.osk.modifierCodes; + var keyCodes = keyman.osk.keyCodes; + + this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9; + this.KI="Keyboard_testvariable"; + this.KN="testvariable"; + this.KMINVER="10.0"; + this.KV={F:' 1em "Arial"',K102:0}; + this.KV.KLS={ + "default": ["","","","","","","","","","","","","","","","","","","","Reveal","","","","","","","","","","","","","","Set","","","","","","","","","","","","","","","","","","Clear","","","","","","","","","","","","",""] + }; + this.KV.BK=(function(x){ + var + empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""), + result=[], v, i, + modifiers=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt']; + for(i=modifiers.length-1;i>=0;i--) { + v = x[modifiers[i]]; + if(v || result.length > 0) { + result=(v ? v : empty).slice().concat(result); + } + } + return result; + })(this.KV.KLS); + this.KDU=0; + this.KH=''; + this.KM=0; + this.KBVER="1.0"; + this.KMBM=0 /* 0x0000 */; + this.KVKL={ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_S", + "text": "Set" + }, + { + "id": "K_C", + "text": "Clear" + }, + { + "id": "K_R", + "text": "Reveal" + }, + { + "width": "100", + "id": "K_BKSP", + "sp": "1", + "text": "*BkSp*" + } + ] + }, + { + "id": "2", + "key": [ + { + "width": "140", + "id": "K_LOPT", + "sp": "1", + "text": "*Menu*" + }, + { + "id": "K_SPACE" + }, + { + "width": "145", + "id": "K_ENTER", + "sp": "1", + "text": "*Enter*" + } + ] + } + ] + } + ] + } +} +; + this.s_myVar_9=KeymanWeb.KLOAD(this.KI,"myVar","1"); + this.s13="1"; + this.s14="0"; + this.s15="1"; + this.s16="1"; + this.KVER="15.0.105.0"; + this.KVS=['s_myVar_9']; + this.gs=function(t,e) { + return this.g_main_0(t,e); + }; + this.g_main_0=function(t,e) { + var k=KeymanWeb,r=0,m=0; + if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 16 + k.KDC(0,t); + k.KO(-1,t,"(-)"); + this.s_myVar_9=this.s14; + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_R /* 0x52 */)) { + if(this.s_myVar_9===this.s15){ + r=m=1; // Line 17 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + else if(this.s_myVar_9!==this.s16){ + r=m=1; // Line 18 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 15 + k.KDC(0,t); + k.KO(-1,t,"(+)"); + this.s_myVar_9=this.s13; + } + } + return r; + }; +} diff --git a/windows/src/desktop/branding/core.ico b/windows/src/desktop/branding/core.ico new file mode 100644 index 0000000000..1b6fb8ae64 Binary files /dev/null and b/windows/src/desktop/branding/core.ico differ diff --git a/windows/src/desktop/inst/keymandesktop.wxs b/windows/src/desktop/inst/keymandesktop.wxs index 4c01e708e2..01ec175bde 100644 --- a/windows/src/desktop/inst/keymandesktop.wxs +++ b/windows/src/desktop/inst/keymandesktop.wxs @@ -125,6 +125,10 @@ + + + + @@ -242,6 +246,7 @@ + diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas index fb5f912dc5..775c33c059 100644 --- a/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas +++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas @@ -33,7 +33,11 @@ type class function GetFirstLanguage(Keyboard: IKeymanKeyboardFile): string; overload; /// Get the BCP47 tag for the user's default language - class function GetUserDefaultLanguage: string; static; + class function GetUserDefaultLanguage: string; overload; static; + class procedure GetUserDefaultLanguage(var BCP47: string; var LangID: Integer); overload; static; + + /// Get the -default-lang parameter string for kmshell + class function GetUserDefaultLangParameterString: string; static; private class function GetKeyboardLanguage(const KeyboardID, BCP47Tag: string): IKeymanKeyboardLanguageInstalled; static; @@ -48,6 +52,7 @@ uses Keyman.System.LanguageCodeUtils, + KLog, BCP47Tag, glossary, kmint, @@ -63,9 +68,14 @@ var i: Integer; begin Result := True; - for i := 0 to Packages.Count - 1 do - // We'll attempt every package but return failure if any of them have issues - Result := InstallTipForPackage(Packages.Names[i], Packages.ValueFromIndex[i]) and Result; + KL.MethodEnter(nil, 'TTIPMaintenance.InstallTipsForPackages', [Packages.Text]); + try + for i := 0 to Packages.Count - 1 do + // We'll attempt every package but return failure if any of them have issues + Result := InstallTipForPackage(Packages.Names[i], Packages.ValueFromIndex[i]) and Result; + finally + KL.MethodExit(nil, 'TTIPMaintenance.InstallTipsForPackages', [Result]); + end; end; class function TTIPMaintenance.InstallTipForPackage(const PackageFilename, BCP47Tag: string): Boolean; @@ -74,23 +84,29 @@ var n: Integer; pack: IKeymanPackageInstalled; begin - // This function has a known limitation: if a package contains more than one keyboard, - // then the BCP47 association will be made only for the first keyboard. This is - // considered an acceptable limitation at this time + Result := False; + KL.MethodEnter(nil, 'TTIPMaintenance.InstallTipForPackage', [PackageFilename, BCP47Tag]); + try + // This function has a known limitation: if a package contains more than one keyboard, + // then the BCP47 association will be made only for the first keyboard. This is + // considered an acceptable limitation at this time - PackageID := ChangeFileExt(ExtractFileName(PackageFilename), ''); - n := kmcom.Packages.IndexOf(PackageID); - if n < 0 then - Exit(False); + PackageID := ChangeFileExt(ExtractFileName(PackageFilename), ''); + n := kmcom.Packages.IndexOf(PackageID); + if n < 0 then + Exit(False); - pack := kmcom.Packages[n]; + pack := kmcom.Packages[n]; - if pack.Keyboards.Count = 0 then - Exit(False); + if pack.Keyboards.Count = 0 then + Exit(False); - if BCP47Tag = '' - then Result := DoInstall(pack.Keyboards[0].ID, GetFirstLanguage(pack.Keyboards[0] as IKeymanKeyboardInstalled)) - else Result := DoInstall(pack.Keyboards[0].ID, BCP47Tag); + if BCP47Tag = '' + then Result := DoInstall(pack.Keyboards[0].ID, GetFirstLanguage(pack.Keyboards[0] as IKeymanKeyboardInstalled)) + else Result := DoInstall(pack.Keyboards[0].ID, BCP47Tag); + finally + KL.MethodExit(nil, 'TTIPMaintenance.InstallTipForPackage', [Result]); + end; end; class function TTIPMaintenance.InstallTip(LangID: Integer; const KeyboardID, BCP47Tag, @@ -98,13 +114,19 @@ class function TTIPMaintenance.InstallTip(LangID: Integer; const KeyboardID, BCP var lang: IKeymanKeyboardLanguageInstalled; begin - lang := GetKeyboardLanguage(KeyboardID, BCP47Tag); - if lang = nil then - Exit(False); + Result := False; + KL.MethodEnter(nil, 'TTIPMaintenance.InstallTip', [LangID, KeyboardID, BCP47Tag, KeyboardToRemove]); + try + lang := GetKeyboardLanguage(KeyboardID, BCP47Tag); + if lang = nil then + Exit(False); - // TODO: can this fail? - (lang as IKeymanKeyboardLanguageInstalled2).InstallTip(LangID, KeyboardToRemove); - Result := True; + // TODO: can this fail? + (lang as IKeymanKeyboardLanguageInstalled2).InstallTip(LangID, KeyboardToRemove); + Result := True; + finally + KL.MethodExit(nil, 'TTIPMaintenance.InstallTip', [Result]); + end; end; class function TTIPMaintenance.RegisterTip(LangID: Integer; const KeyboardID, @@ -112,13 +134,19 @@ class function TTIPMaintenance.RegisterTip(LangID: Integer; const KeyboardID, var lang: IKeymanKeyboardLanguageInstalled; begin - lang := GetKeyboardLanguage(KeyboardID, BCP47Tag); - if lang = nil then - Exit(False); + Result := False; + KL.MethodEnter(nil, 'TTIPMaintenance.RegisterTip', [KeyboardID, BCP47Tag]); + try + lang := GetKeyboardLanguage(KeyboardID, BCP47Tag); + if lang = nil then + Exit(False); - // TODO: can this fail? - (lang as IKeymanKeyboardLanguageInstalled2).RegisterTip(LangID); - Result := True; + // TODO: can this fail? + (lang as IKeymanKeyboardLanguageInstalled2).RegisterTip(LangID); + Result := True; + finally + KL.MethodExit(nil, 'TTIPMaintenance.RegisterTip', [Result]); + end; end; class function TTIPMaintenance.DoInstall(const KeyboardID, @@ -129,44 +157,59 @@ var TemporaryKeyboardID: WideString; LangID: Integer; childExitCode: Cardinal; + CanonicalTag, Command: string; begin - lang := GetKeyboardLanguage(KeyboardID, (kmcom as IKeymanBCP47Canonicalization).GetCanonicalTag(BCP47Tag)); - if lang = nil then - // The keyboard was not found - Exit(False); - - if lang.IsInstalled then - // After canonicalization, we may find the language is already installed - Exit(True); - - TemporaryKeyboardID := ''; - LangID := 0; - RegistrationRequired := False; - - if not (lang as IKeymanKeyboardLanguageInstalled2).FindInstallationLangID(LangID, TemporaryKeyboardID, RegistrationRequired, kifInstallTransientLanguage) then - begin - // We were not able to find a TIP, perhaps all transient TIPs have been used - Exit(False); - end; - - if RegistrationRequired then - begin - // This calls back into TTIPMaintenance.RegisterTip - if WaitForElevatedConfiguration(0, '-register-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'"') <> 0 then + Result := False; + KL.MethodEnter(nil, 'TTIPMaintenance.DoInstall', [KeyboardID, BCP47Tag]); + try + CanonicalTag := (kmcom as IKeymanBCP47Canonicalization).GetCanonicalTag(BCP47Tag); + lang := GetKeyboardLanguage(KeyboardID, CanonicalTag); + KL.Log('BCP47Tag = %s, CanonicalTag = %s, lang.BCP47Code = %s', [BCP47Tag, CanonicalTag, lang.BCP47Code]); + if lang = nil then + // The keyboard was not found Exit(False); - end; - // This calls back into TTIPMaintenance.InstallTip - if not TUtilExecute.WaitForProcess('"'+ParamStr(0)+'" -install-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'" "'+TemporaryKeyboardID+'"', - GetCurrentDir, childExitCode) or - (childExitCode <> 0) then - begin + if lang.IsInstalled then + // After canonicalization, we may find the language is already installed + Exit(True); + + TemporaryKeyboardID := ''; + LangID := 0; + RegistrationRequired := False; + + if not (lang as IKeymanKeyboardLanguageInstalled2).FindInstallationLangID(LangID, TemporaryKeyboardID, RegistrationRequired, kifInstallTransientLanguage) then + begin + KL.Log('Failed to find installation langid'); + // We were not able to find a TIP, perhaps all transient TIPs have been used + Exit(False); + end; + + KL.Log('LangID=%x, TemporaryKeyboardID=%s, RegistrationRequired=%s', [LangID, TemporaryKeyboardID, BoolToStr(RegistrationRequired, True)]); + + if RegistrationRequired then + begin + Command := '-register-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'" '+GetUserDefaultLangParameterString; + KL.Log('Calling elevated kmshell %s', [Command]); + // This calls back into TTIPMaintenance.RegisterTip + if WaitForElevatedConfiguration(0, Command) <> 0 then + Exit(False); + end; + + Command := '-install-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'" "'+TemporaryKeyboardID+'"'; + KL.Log('Calling user kmshell %s', [Command]); + // This calls back into TTIPMaintenance.InstallTip + if not TUtilExecute.WaitForProcess('"'+ParamStr(0)+'" '+Command, GetCurrentDir, childExitCode) or + (childExitCode <> 0) then + begin + kmcom.Refresh; + Exit(False); + end; + kmcom.Refresh; - Exit(False); + Result := True; + finally + KL.MethodExit(nil, 'TTIPMaintenance.DoInstall', [Result]); end; - - kmcom.Refresh; - Result := True; end; function GetDefaultHKL: HKL; @@ -183,79 +226,149 @@ var TemporaryKeyboardID: WideString; RegistrationRequired: WordBool; begin - lang := GetKeyboardLanguage(KeyboardID, (kmcom as IKeymanBCP47Canonicalization).GetCanonicalTag(BCP47Tag)); - if lang = nil then - // The keyboard was not found - Exit(False); + Result := False; + KL.MethodEnter(nil, 'TTIPMaintenance.DoRegister', [KeyboardID, BCP47Tag]); + try + lang := GetKeyboardLanguage(KeyboardID, (kmcom as IKeymanBCP47Canonicalization).GetCanonicalTag(BCP47Tag)); + if lang = nil then + // The keyboard was not found + Exit(False); - if lang.IsInstalled or (lang as IKeymanKeyboardLanguageInstalled2).IsRegistered then - // After canonicalization, we may find the language is already installed - Exit(True); + if lang.IsInstalled or (lang as IKeymanKeyboardLanguageInstalled2).IsRegistered then + // After canonicalization, we may find the language is already installed + Exit(True); - TemporaryKeyboardID := ''; - LangID := 0; - RegistrationRequired := False; + TemporaryKeyboardID := ''; + LangID := 0; + RegistrationRequired := False; - if (lang as IKeymanKeyboardLanguageInstalled2).FindInstallationLangID(LangID, TemporaryKeyboardID, RegistrationRequired, 0) then - begin - Result := not RegistrationRequired or RegisterTip(LangID, KeyboardID, lang.BCP47Code); - end - else - Result := False; + if (lang as IKeymanKeyboardLanguageInstalled2).FindInstallationLangID(LangID, TemporaryKeyboardID, RegistrationRequired, 0) then + begin + Result := not RegistrationRequired or RegisterTip(LangID, KeyboardID, lang.BCP47Code); + end + else + Result := False; + finally + KL.MethodExit(nil, 'TTIPMaintenance.DoRegister', [Result]); + end; +end; + +class function TTIPMaintenance.GetUserDefaultLangParameterString: string; +var + LangID: Integer; + BCP47: string; +begin + KL.MethodEnter(nil, 'TTIPMaintenance.GetUserDefaultLangParameterString', []); + try + GetUserDefaultLanguage(BCP47, LangID); + Result := '-default-lang '+BCP47+' '+IntToHex(LangID,4); + finally + KL.MethodExit(nil, 'TTIPMaintenance.GetUserDefaultLangParameterString', [Result]); + end; end; class function TTIPMaintenance.GetUserDefaultLanguage: string; +var + LangID: Integer; +begin + KL.MethodEnter(nil, 'TTIPMaintenance.GetUserDefaultLanguage', []); + try + GetUserDefaultLanguage(Result, LangID); + finally + KL.MethodExit(nil, 'TTIPMaintenance.GetUserDefaultLanguage', [Result]); + end; +end; + +class procedure TTIPMaintenance.GetUserDefaultLanguage(var BCP47: string; var LangID: Integer); var r: TRegistry; tags: TStringList; v: string; keys: TStringList; key: string; -begin - // Fallback result - Result := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(HKLToLanguageID(GetDefaultHKL)); - // For Win10, look in CPL/International/UserProfile - r := TRegistry.Create; + function GetLangIDFromValueName: Integer; + var + values: TStringList; + v: string; + begin + // In HKCU\Control Panel\International\User Profile\, already opened + // by the caller, look for a value name such as '0453:00000453' or + // '0804:{81D4E9C9-1D3B-41BC-9E6C-4B40BF79E35E}{FA550B04-5AD7-411F-A5AC-CA038EC515D7}' + // and grab the LangID from there. + values := TStringList.Create; + try + r.GetValueNames(values); + for v in values do + begin + if Copy(v, 5, 1) = ':' then + begin + Result := StrToIntDef('$' + Copy(v, 1, 4), 0); + if Result > 0 then + Exit; + end; + end; + finally + values.Free; + end; + Result := 0; + end; + +begin + KL.MethodEnter(nil, 'TTIPMaintenance.GetUserDefaultLanguage', []); try - if not r.OpenKeyReadOnly(SRegKey_ControlPanelInternationalUserProfile) then - Exit; - if r.ValueExists(SRegValue_CPIUP_InputMethodOverride) then - begin - // Lookup the override input method BCP 47 tag - v := r.ReadString(SRegValue_CPIUP_InputMethodOverride); - keys := TStringList.Create; - try - r.GetKeyNames(keys); - for key in keys do - begin - if r.OpenKeyReadOnly('\' + SRegKey_ControlPanelInternationalUserProfile + '\' + key) and - r.ValueExists(v) then + // Fallback result + LangID := HKLToLanguageID(GetDefaultHKL); + BCP47 := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(LangID); + + // For Win10, look in CPL/International/UserProfile + r := TRegistry.Create; + try + if not r.OpenKeyReadOnly(SRegKey_ControlPanelInternationalUserProfile) then + Exit; + if r.ValueExists(SRegValue_CPIUP_InputMethodOverride) then + begin + // Lookup the override input method BCP 47 tag + v := r.ReadString(SRegValue_CPIUP_InputMethodOverride); + keys := TStringList.Create; + try + r.GetKeyNames(keys); + for key in keys do begin - Result := key; - Break; + if r.OpenKeyReadOnly('\' + SRegKey_ControlPanelInternationalUserProfile + '\' + key) and + r.ValueExists(v) then + begin + BCP47 := key; + LangID := GetLangIDFromValueName; + Break; + end; end; + finally + keys.Free; end; - finally - keys.Free; - end; - end - else if r.ValueExists(SRegValue_CPIUP_Languages) then - begin - // The first tag is the default language tag - tags := TStringList.Create; - try - r.ReadMultiString(SRegValue_CPIUP_Languages, tags); - if tags.Count > 0 then - begin - Result := tags[0].Trim; + end + else if r.ValueExists(SRegValue_CPIUP_Languages) then + begin + // The first tag is the default language tag + tags := TStringList.Create; + try + r.ReadMultiString(SRegValue_CPIUP_Languages, tags); + if tags.Count > 0 then + begin + BCP47 := tags[0].Trim; + if r.OpenKeyReadOnly('\' + SRegKey_ControlPanelInternationalUserProfile + '\' + BCP47) then + LangID := GetLangIDFromValueName; + end; + finally + tags.Free; end; - finally - tags.Free; end; + finally + r.Free; end; finally - r.Free; + KL.Log('TTIPMaintenance.GetUserDefaultLanguage = BCP47:%s LangID:%d', [BCP47, LangID]); + KL.MethodExit(nil, 'TTIPMaintenance.GetUserDefaultLanguage'); end; end; diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas index 00bf0e44a7..2636eba7ea 100644 --- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas +++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas @@ -169,12 +169,12 @@ begin if Length(FilenameBCP47) > 1 then RegisterKeyboardPackageLanguage(FPackage, FilenameBCP47[1]) else RegisterKeyboardPackageLanguage(FPackage, ''); - // The keyboard will be installed for current user as a separate step + // The keyboard will be installed for current user as a separate step end else begin FKeyboard := (kmcom.Keyboards as IKeymanKeyboardsInstalled2).Install2(FileName, True); - if Length(FilenameBCP47) > 1 + if (Length(FilenameBCP47) > 1) and (Trim(FilenameBCP47[1]) <> '') then BCP47Tag := FilenameBCP47[1] else BCP47Tag := TTIPMaintenance.GetFirstLanguage(FKeyboard); TTIPMaintenance.DoRegister(FKeyboard.ID, BCP47Tag); diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas index 6250db1030..34421ca37e 100644 --- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas +++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas @@ -118,6 +118,7 @@ uses System.Win.ComObj, Winapi.ShellApi, + KLog, custinterfaces, GetOSVersion, MessageIdentifierConsts, @@ -270,7 +271,6 @@ end; procedure TfrmInstallKeyboard.FireCommand(const command: WideString; params: TStringList); var - t: TTempFile; BCP47Tag: string; begin BCP47Tag := ''; @@ -297,27 +297,38 @@ begin BCP47Tag := params.ValueFromIndex[0]; TfrmProgress.Execute(Self, function(Manager: IProgressManager): Boolean + var + t: TTempFile; + ExecParams: string; begin - Manager.Title := 'Installing Keyboard'; - Manager.CanCancel := False; - Manager.UpdateProgress('Installing Keyboard', 0, 0); - t := TTempFileManager.Get('.log'); + KL.MethodEnter(Self, '"keyboard_install"', [params.Text]); try - if WaitForElevatedConfiguration(GetForegroundWindow, '-log "'+t.Name+'" -s -i "'+FInstallFile+'='+BCP47Tag+'" -nowelcome') = 0 then - begin - // install the keyboard tip - if not InstallTipForKeyboard(BCP47Tag) then - Exit(False); + Manager.Title := 'Installing Keyboard'; + Manager.CanCancel := False; + Manager.UpdateProgress('Installing Keyboard', 0, 0); + t := TTempFileManager.Get('.log'); + try + ExecParams := '-log "'+t.Name+'" -s -i "'+FInstallFile+'='+BCP47Tag+'"'+ + ' -nowelcome '+TTIPMaintenance.GetUserDefaultLangParameterString; + KL.Log('Calling elevated kmshell %s', [ExecParams]); + if WaitForElevatedConfiguration(GetForegroundWindow, ExecParams) = 0 then + begin + // install the keyboard tip + if not InstallTipForKeyboard(BCP47Tag) then + Exit(False); - CheckForMitigationWarningFor_Win10_1803(False, ''); - ModalResult := mrOk; - end - else - ModalResult := mrCancel; + CheckForMitigationWarningFor_Win10_1803(False, ''); + ModalResult := mrOk; + end + else + ModalResult := mrCancel; - CheckLogFileForWarnings(t.Name, False); + CheckLogFileForWarnings(t.Name, False); + finally + t.Free; + end; finally - t.Free; + KL.MethodExit(Self, '"keyboard_install"'); end; Result := True; end diff --git a/windows/src/desktop/kmshell/locale/am-ET/strings.xml b/windows/src/desktop/kmshell/locale/am-ET/strings.xml index 259b2a781a..0894c84b74 100644 --- a/windows/src/desktop/kmshell/locale/am-ET/strings.xml +++ b/windows/src/desktop/kmshell/locale/am-ET/strings.xml @@ -215,7 +215,7 @@ - แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณแŠ• แŒซแŠ•... + แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณ แˆฐแˆŒแ‹ณ แŒซแŠ•... @@ -276,7 +276,7 @@ - แ‹จแˆƒแˆญแ‹ตแ‹Œแˆญ แ‹ดแ‹ตแŠชแ‹Žแ‰ฝแŠ• แŠฅแŠ•แ‹ฐ แ‰ฐแˆซ แ‰แˆแ แŠ แ‹ตแˆญแŒˆแ‹ แ‹ญแ‹ญแ‹Ÿแ‰ธแ‹ + แ‹จแˆƒแˆญแ‹ตแ‹Œแˆญ แ‹ดแ‹ตแŠชแ‹Žแ‰ฝแŠ• แŠฅแŠ•แ‹ฐ แ‰ฐแˆซ แ‰แˆแ แŠ แ‹ตแˆญแŒˆแ‹ แ‹ญแ‹ซแ‹Ÿแ‰ธแ‹ @@ -312,7 +312,7 @@ - แ‹จแˆตแŠญแˆชแŠ• แˆ‹แ‹ญ แŠชแ‰ฆแˆญแ‹ฑแŠ• แŠแ‹ฐแˆ‰แŠ• แŠจแ‰ฐแŒซแŠ‘ แ‰ แŠ‹แˆ‹ Shift/Ctrl/Alt แ‹ญแˆแ‰€แ‰ + แ‹จแˆตแŠญแˆชแŠ• แŠชแ‰ฆแˆญแ‹ต แˆ‹แ‹ญ แ‹ซแˆˆแ‹แŠ• แŠแ‹ฐแˆ แŠจแ‰ฐแŒซแŠ‘ แ‰ แŠ‹แˆ‹ Shift/Ctrl/Alt แ‹ญแˆแ‰€แ‰ @@ -365,11 +365,11 @@ - แŠชแ‹ญแˆ›แŠ‘แŠ• แ‹ซแŒฅแ‰ + แŠชแ‹ญแˆ›แŠ‘แŠ• แŠ แŒฅแ‹ - แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณแ‹แŠ• แ‹แˆญแ‹แˆญ แ‹ญแŠญแˆแ‰ฑ + แ‹จแŠแ‹ฐแˆ แŒˆแ‰ แ‰ณแ‹แŠ• แ‹แˆญแ‹แˆญ แŠญแˆแ‰ต @@ -629,7 +629,7 @@ - แ‰ แ‹แˆตแŒฅ แŠ แˆณแ‹ญ + แ‰‹แŠ•แ‰‹ แ‰  @@ -733,7 +733,7 @@ - แŠชแ‹ญแˆ›แŠ‘แŠ• แ‹ซแŒฅแ‰ + แŠชแ‹ญแˆ›แŠ‘แŠ• แŠ แŒฅแ‹ (&O) diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index d77ca982f0..c22591e79c 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -195,7 +195,8 @@ begin end; function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, FForce, FNoWelcome: Boolean; - var FLogFile, FQuery: string; var FDisablePackages, FDefaultUILanguage: string; var FStartWithConfiguration: Boolean; var FParentWindow: THandle): Boolean; + var FLogFile, FQuery: string; var FDisablePackages, FDefaultUILanguage: string; var FStartWithConfiguration: Boolean; + var FParentWindow: THandle; var FDefaultBCP47: string; var FDefaultLangID: Integer): Boolean; var s: string; i: Integer; @@ -269,7 +270,13 @@ begin FQuery := Trim(FQuery); end else if Copy(s, 1, 3) = '-ur' then UnRegCRC := StrToIntDef('$'+Copy(s, 4, 8), 0) - + else if s = '-default-lang' then + begin + Inc(i); + FDefaultBCP47 := ParamStr(i); + Inc(i); + FDefaultLangID := StrToIntDef('$'+ParamStr(i), 0); + end // Controls from Keyman Engine else if s = '-showhint' then FMode := fmShowHint else if s = '-parentwindow' then @@ -301,7 +308,8 @@ begin end; procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FForce, FNoWelcome: Boolean; - FLogFile, FQuery: string; FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean; FParentWindow: THandle); forward; + FLogFile, FQuery: string; FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean; FParentWindow: THandle; + const FDefaultBCP47: string; FDefaultLangID: Integer); forward; procedure Run; var @@ -312,7 +320,8 @@ var FForce: Boolean; FParentWindow: THandle; FLogFile: string; - FDisablePackages, FDefaultUILanguage: string; + FDefaultLangID: Integer; + FDefaultBCP47, FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean; begin RegisterControlClasses; @@ -320,7 +329,7 @@ begin KeyboardFileNames := TStringList.Create; try FParentWindow := 0; - if not Init(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow) then + if not Init(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID) then begin //TODO: TUtilExecute.Shell(PChar('hh.exe mk:@MSITStore:'+ExtractFilePath(KMShellExe)+'keyman.chm::/context/keyman_usage.html'), SW_SHOWNORMAL); Exit; @@ -328,7 +337,7 @@ begin if not LoadKMCOM then Exit; try - RunKMCOM(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow); + RunKMCOM(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID); finally kmcom := nil; end; @@ -366,8 +375,10 @@ begin end; procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FForce, FNoWelcome: Boolean; - FLogFile, FQuery: string; FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean; FParentWindow: THandle); + FLogFile, FQuery: string; FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean; + FParentWindow: THandle; const FDefaultBCP47: string; FDefaultLangID: Integer); var + kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 function FirstKeyboardFileName: WideString; @@ -440,6 +451,12 @@ begin if FileExists(FIcon) then Application.Icon.LoadFromFile(FIcon); + if (FDefaultBCP47 <> '') or (FDefaultLangID <> 0) then + begin + if Supports(kmcom, IKeymanDefaultLanguage, kdl) then + kdl.SetDefaultLanguage(FDefaultBCP47, FDefaultLangID); + end; + case FMode of fmKeyboardWelcome: // I2569 ShowKeyboardWelcome(FirstKeyboardFileName); diff --git a/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas b/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas index 469052c962..086700df0d 100644 --- a/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas +++ b/windows/src/developer/TIKE/compile/CompileKeymanWeb.pas @@ -1856,6 +1856,7 @@ var HasRules: Boolean; sModifierBitmask: string; fDisplayUnderlying: Boolean; + FOptionStores: string; begin Result := '';//UTF16SignatureW; // + '// compiled by Keyman Developer'+nl; // I3474 { Locate the name of the keyboard } @@ -2067,6 +2068,7 @@ begin Result := Result + Format('%sthis.KCSS="%s";%s', [FTabStop, RequotedString(sEmbedCSS), nl]); { Write the stores out } + FOptionStores := ''; fsp := fk.dpStoreArray; for i := 0 to fk.cxStoreArray - 1 do begin @@ -2081,18 +2083,26 @@ begin //else if fsp.dwSystemID = TSS_VKDICTIONARY then // I3438, required for vkdictionary // Result := Result + Format('%sthis.s%s=%s;%s', [FTabStop, JavaScript_Name(i, fsp.szName), JavaScript_Store(fsp.line, fsp.dpString), nl]) else if fsp.fIsOption and not fsp.fIsReserved then + begin Result := Result + Format('%sthis.s%s=KeymanWeb.KLOAD(this.KI,"%s",%s);%s', [FTabstop, JavaScript_Name(i,fsp.szName), JavaScript_Name(i,fsp.szName,True), JavaScript_Store(fsp.line, fsp.dpString), - nl]) // I3429 - else if fsp.dwSystemID = TSS_NONE then + nl]); // I3429 + + if FOptionStores <> '' then + FOptionStores := FOptionStores + ','; + FOptionStores := FOptionStores + Format('''s%s''', [JavaScript_Name(i,fsp.szName)]); + end + else if fsp.dwSystemID = TSS_NONE {aka not fsp.fIsReserved} then Result := Result + Format('%sthis.s%s=%s;%s', [FTabStop, JavaScript_Name(i, fsp.szName), JavaScript_Store(fsp.line, fsp.dpString), nl]); // I3681 end; Inc(fsp); end; + Result := Result + Format('%sthis.KVS=[%s];%s', [FTabStop, FOptionStores, nl]); + { Write the groups out } // I853 - begin unicode missing causes crash diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout b/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout index 967ba6e179..2887e3b0a6 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout +++ b/windows/src/developer/TIKE/xml/layoutbuilder/template-basic.keyman-touch-layout @@ -377,7 +377,7 @@ {"id": "K_2","layer":"shift","text": "@"}, {"id": "K_3","layer":"shift","text": "#"}, {"id": "K_5","layer":"shift","text": "%"}, - {"id": "K_6","layer":"shift","text": "&"}, + {"id": "K_7","layer":"shift","text": "&"}, {"id": "K_HYPHEN","layer":"shift","text": "_"}, {"id": "K_EQUAL","text": "=","layer":"default"}, {"id": "K_BKSLASH","layer":"shift","text": "|"}, diff --git a/windows/src/developer/TIKE/xml/layoutbuilder/template-traditional.keyman-touch-layout b/windows/src/developer/TIKE/xml/layoutbuilder/template-traditional.keyman-touch-layout index 7ebac15051..09bc393bb9 100644 --- a/windows/src/developer/TIKE/xml/layoutbuilder/template-traditional.keyman-touch-layout +++ b/windows/src/developer/TIKE/xml/layoutbuilder/template-traditional.keyman-touch-layout @@ -22,7 +22,7 @@ {"id": "K_EQUAL","text": "="}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -38,7 +38,7 @@ {"id": "K_P","text":"p"}, {"id": "K_LBRKT","text":"["}, {"id": "K_RBRKT","text":"]"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -84,7 +84,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "1"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "1"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} ] } ] @@ -109,7 +109,7 @@ {"id": "K_EQUAL","text": "+"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -125,7 +125,7 @@ {"id": "K_P","text":"P"}, {"id": "K_LBRKT","text":"{"}, {"id": "K_RBRKT","text":"}"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -171,7 +171,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "1"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "1"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} ] } ] @@ -196,7 +196,7 @@ {"id": "K_EQUAL"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -212,7 +212,7 @@ {"id": "K_P"}, {"id": "K_LBRKT"}, {"id": "K_RBRKT"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -258,7 +258,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "1"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "1"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} ] } ] @@ -283,7 +283,7 @@ {"id": "K_EQUAL"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -299,7 +299,7 @@ {"id": "K_P"}, {"id": "K_LBRKT"}, {"id": "K_RBRKT"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -345,7 +345,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "1"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "1"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} ] } ] @@ -370,7 +370,7 @@ {"id": "K_EQUAL"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -386,7 +386,7 @@ {"id": "K_P"}, {"id": "K_LBRKT"}, {"id": "K_RBRKT"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -432,7 +432,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "2"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "2"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} ] } ] @@ -457,7 +457,7 @@ {"id": "K_EQUAL"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -473,7 +473,7 @@ {"id": "K_P"}, {"id": "K_LBRKT"}, {"id": "K_RBRKT"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -519,7 +519,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "2"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "2"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "1"} ] } ] @@ -544,7 +544,7 @@ {"id": "K_EQUAL"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -560,7 +560,7 @@ {"id": "K_P"}, {"id": "K_LBRKT"}, {"id": "K_RBRKT"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -606,7 +606,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "2"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "2"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} ] } ] @@ -631,7 +631,7 @@ {"id": "K_EQUAL"}, {"id": "K_BKSP","text": "*BkSp*","width": "100","sp": "1"} ] - }, + }, { "id": 2, "key": [ @@ -647,7 +647,7 @@ {"id": "K_P"}, {"id": "K_LBRKT"}, {"id": "K_RBRKT"}, - {"sp":"10","width":"10"} + {"sp":"10","width":"10"} ] }, { @@ -693,7 +693,7 @@ {"id": "K_LALT","text": "*Alt*","width": "130","sp": "2"}, {"id": "K_SPACE","text": "","width": "675","sp": "0"}, {"id": "K_RALT","text": "*AltGr*","width": "130","sp": "2"}, - {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} + {"id": "K_RCONTROL","text": "*Ctrl*","width": "130","sp": "2"} ] } ] @@ -733,7 +733,7 @@ {"id": "K_J","text":"j"}, {"id": "K_K","text":"k"}, {"id": "K_L","text":"l"}, - {"text": "","width": "10","sp": "10"} + {"text": "","width": "10","sp": "10"} ] }, { @@ -802,7 +802,7 @@ {"id": "K_J","text":"J"}, {"id": "K_K","text":"K"}, {"id": "K_L","text":"L"}, - {"text": "","width": "10","sp": "10"} + {"text": "","width": "10","sp": "10"} ] }, { @@ -866,7 +866,7 @@ {"id": "K_2","layer":"shift","text": "@"}, {"id": "K_3","layer":"shift","text": "#"}, {"id": "K_5","layer":"shift","text": "%"}, - {"id": "K_6","layer":"shift","text": "&"}, + {"id": "K_7","layer":"shift","text": "&"}, {"id": "K_HYPHEN","layer":"shift","text": "_"}, {"id": "K_EQUAL","text": "=","layer":"default"}, {"id": "K_BKSLASH","layer":"shift","text": "|"}, diff --git a/windows/src/developer/kmanalyze/kmanalyze.vcxproj b/windows/src/developer/kmanalyze/kmanalyze.vcxproj index d60e309d1e..2c0a3da3a4 100644 --- a/windows/src/developer/kmanalyze/kmanalyze.vcxproj +++ b/windows/src/developer/kmanalyze/kmanalyze.vcxproj @@ -179,8 +179,8 @@ - + @@ -207,4 +207,4 @@ - \ No newline at end of file + diff --git a/windows/src/developer/kmanalyze/kmanalyze.vcxproj.filters b/windows/src/developer/kmanalyze/kmanalyze.vcxproj.filters index 895a3532fa..cc0f741f89 100644 --- a/windows/src/developer/kmanalyze/kmanalyze.vcxproj.filters +++ b/windows/src/developer/kmanalyze/kmanalyze.vcxproj.filters @@ -18,15 +18,15 @@ Header Files - - Header Files - Header Files Header Files + + Header Files + diff --git a/windows/src/developer/kmdecomp/kmdecomp.vcxproj b/windows/src/developer/kmdecomp/kmdecomp.vcxproj index 6c72b02a6c..490a97f401 100644 --- a/windows/src/developer/kmdecomp/kmdecomp.vcxproj +++ b/windows/src/developer/kmdecomp/kmdecomp.vcxproj @@ -54,9 +54,11 @@ $(ProjectDir)bin\$(Platform)\$(Configuration)\ $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(IncludePath) $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(IncludePath) diff --git a/windows/src/engine/engine.sln b/windows/src/engine/engine.sln index f20492513d..13bdd83f00 100644 --- a/windows/src/engine/engine.sln +++ b/windows/src/engine/engine.sln @@ -15,7 +15,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mcompile", "mcompile\mcompi EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmrefresh", "kmrefresh\kmrefresh.vcxproj", "{A3B8F75F-E63E-434E-8A2A-034AD9B66571}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman32-tests", "keyman32\tests\keyman32-tests\keyman32-tests.vcxproj", "{46399ED7-79A1-4E7E-BD96-4B1AEEC838AE}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman-engine-tests", "keyman32\tests\keyman-engine-tests\keyman-engine-tests.vcxproj", "{46399ED7-79A1-4E7E-BD96-4B1AEEC838AE}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman-engine", "keyman32\keyman-engine.vcxproj", "{A7069186-5AE7-4504-8926-861B7A34BA9E}" EndProject diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 5a9cb2f16f..3edaa25559 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -208,6 +208,7 @@ type procedure tmrCheckInputPaneTimer(Sender: TObject); procedure tmrRefreshTimer(Sender: TObject); private + FCoreTrayIcon: TKeymanTrayIcon; InMenuLoop: Integer; // I1082 - Avoid menu nasty flicker with rapid click FClosingApp: Boolean; FRunningProduct: TRunningProduct; @@ -301,6 +302,7 @@ type procedure HotkeyWndProc(var Message: TMessage); procedure DoLanguageHotkey(Index: Integer); + procedure CreateCoreTrayIcon; protected procedure DoInterfaceHotkey(Target: Integer); @@ -502,6 +504,38 @@ begin end; end; +procedure TfrmKeyman7Main.CreateCoreTrayIcon; +var + cust: IKeymanCustomisation; + istrm: IStream; + olestrm: TOleStream; +begin + if not Reg_GetDebugFlag(SRegValue_Flag_UseKeymanCore, True) then + Exit; + + FCoreTrayIcon := TKeymanTrayIcon.Create(Self); + + cust := kmint.KeymanCustomisation; + istrm := cust.CustFile['core.ico']; + if istrm <> nil then + begin + olestrm := TOLEStream.Create(istrm); + try + // In some situations, launching the app multiple times rapidly can + // cause the icon to be loaded multiple times. Make sure we reset the + // stream position before we try and read. + olestrm.Position := 0; + FCoreTrayIcon.Icon.LoadFromStream(olestrm); + FCoreTrayIcon.Hint := 'Keyman Engine is using Keyman Core library ("engine.compatibility.use_keyman_core" system setting)'; + FCoreTrayIcon.Visible := True; + finally + olestrm.Free; + istrm := nil; + end; + end; + +end; + procedure TfrmKeyman7Main.FormCreate(Sender: TObject); begin if GetOs in [osVista, osWin7] then // I4576 @@ -1283,6 +1317,7 @@ begin end; end; + CreateCoreTrayIcon; //Windows.MessageBox(Handle, PChar(IntToStr(kmcom._AddRef)), 'RefCount+1', MB_OK); try diff --git a/windows/src/engine/keyman32/K32_load.cpp b/windows/src/engine/keyman32/K32_load.cpp index d166a3394b..64479c9743 100644 --- a/windows/src/engine/keyman32/K32_load.cpp +++ b/windows/src/engine/keyman32/K32_load.cpp @@ -75,12 +75,63 @@ BOOL GetKeyboardFileName(LPSTR kbname, LPSTR buf, int nbuf) return n; } +BOOL LoadlpKeyboardCore(int i) +{ + SendDebugMessageFormat(0, sdmLoad, 0, "LoadlpKeyboardCore: Enter ---"); + + + PKEYMAN64THREADDATA _td = ThreadGlobals(); + if (!_td) return FALSE; + if (_td->lpKeyboards[i].lpCoreKeyboard) return TRUE; + if (_td->lpActiveKeyboard == &_td->lpKeyboards[i]) _td->lpActiveKeyboard = NULL; // I822 TSF not working + + if (_td->lpKeyboards[i].lpCoreKeyboardState) { + SendDebugMessageFormat(0, sdmLoad, 0, "LoadlpKeyboardCore: a keyboard km_kbp_state exits without matching keyboard - disposing of state"); + km_kbp_state_dispose(_td->lpKeyboards[i].lpCoreKeyboardState); + _td->lpKeyboards[i].lpCoreKeyboardState = NULL; + } + + char buf[256]; + if (!GetKeyboardFileName(_td->lpKeyboards[i].Name, buf, 255)) return FALSE; + PWCHAR keyboardPath = strtowstr(buf); + km_kbp_status err_status = km_kbp_keyboard_load(keyboardPath, &_td->lpKeyboards[i].lpCoreKeyboard); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat(0, sdmLoad, 0, "LoadlpKeyboardCore: km_kbp_keyboard_load failed for %ls with error status [%d]", keyboardPath, err_status); + delete keyboardPath; + return FALSE; + } + delete keyboardPath; + + // TODO: 5650 handle dlls + //LoadDLLs(&_td->lpKeyboards[i]); + const km_kbp_option_item test_env_opts[] = + { + KM_KBP_OPTIONS_END + }; + + err_status = km_kbp_state_create(_td->lpKeyboards[i].lpCoreKeyboard, test_env_opts, &_td->lpKeyboards[i].lpCoreKeyboardState); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmLoad, 0, "LoadlpKeyboardCore: km_kbp_state_create failed with error status [%d]", err_status); + // Dispose of the keyboard to leave us in a consistent state + ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard); + return FALSE; + } + + LoadKeyboardOptionsREGCore(&_td->lpKeyboards[i], _td->lpKeyboards[i].lpCoreKeyboardState); + + return TRUE; +} + BOOL LoadlpKeyboard(int i) { + if (Globals::get_CoreIntegration()) + { + return LoadlpKeyboardCore(i); + } PKEYMAN64THREADDATA _td = ThreadGlobals(); if(!_td) return FALSE; if(_td->lpKeyboards[i].Keyboard) return TRUE; - if(_td->lpActiveKeyboard == &_td->lpKeyboards[i]) _td->lpActiveKeyboard = NULL; // I822 TSF not working char buf[256]; diff --git a/windows/src/engine/keyman32/Keyman32.cpp b/windows/src/engine/keyman32/Keyman32.cpp index 99965bcbe9..87c5eabfd6 100644 --- a/windows/src/engine/keyman32/Keyman32.cpp +++ b/windows/src/engine/keyman32/Keyman32.cpp @@ -565,18 +565,46 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_ForceKeyboard(PCSTR FileName _td->lpActiveKeyboard->IMDLLs = NULL; _td->lpActiveKeyboard->KeyboardOptions = NULL;*/ _splitpath_s(FileName, NULL, 0, NULL, 0, _td->lpActiveKeyboard->Name, sizeof(_td->lpActiveKeyboard->Name), NULL, 0); - - if(LoadKeyboard(_td->ForceFileName, &_td->lpActiveKeyboard->Keyboard)) - { - SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_ForceKeyboard: %s OK", FileName); - ResetCapsLock(); - LoadDLLs(_td->lpActiveKeyboard); - ActivateDLLs(_td->lpActiveKeyboard); - LoadKeyboardOptions(_td->lpActiveKeyboard); // I2437 - Crash unloading keyboard due to keyboard options not set + // TODO: 5442 - remove if/ else as there will no longer be the old LoadKeyboard option + if (Globals::get_CoreIntegration()) { + PWCHAR keyboardPath = strtowstr(_td->ForceFileName); + km_kbp_status err_status = km_kbp_keyboard_load(keyboardPath, &_td->lpActiveKeyboard->lpCoreKeyboard); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_ForceKeyboard Core: km_kbp_keyboard_load failed for %ls with error status [%d]", keyboardPath, err_status); // TODO: 5442 - remove word Core + delete keyboardPath; + return FALSE; + } + delete keyboardPath; + SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_ForceKeyboard Core: %s OK", FileName); // TODO: 5442 - remove word Core + // TODO: #5650 Equivalent for common core + // LoadDLLs(&_td->lpKeyboards[i]); + const km_kbp_option_item test_env_opts[] = {KM_KBP_OPTIONS_END}; + err_status = + km_kbp_state_create(_td->lpActiveKeyboard->lpCoreKeyboard, test_env_opts, &_td->lpActiveKeyboard->lpCoreKeyboardState); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmGlobal, 0, "Keyman_ForceKeyboard Core: km_kbp_state_create failed with error status [%d]", err_status); + // Dispose of the keyboard to leave us in a consitent state + ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard); + return FALSE; + } + // TODO: 5650 LoadDLLs + // TODO: 5652 verify - ResetCapsLock(); + ResetCapsLock(); + LoadKeyboardOptionsREGCore(_td->lpActiveKeyboard, _td->lpActiveKeyboard->lpCoreKeyboardState); RefreshPreservedKeys(TRUE); - return TRUE; - } - + return TRUE; + } else { + if (LoadKeyboard(_td->ForceFileName, &_td->lpActiveKeyboard->Keyboard)) { + SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_ForceKeyboard: %s OK", FileName); + ResetCapsLock(); + LoadDLLs(_td->lpActiveKeyboard); + ActivateDLLs(_td->lpActiveKeyboard); + LoadKeyboardOptions(_td->lpActiveKeyboard); // I2437 - Crash unloading keyboard due to keyboard options not set + RefreshPreservedKeys(TRUE); + return TRUE; + } + } SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_ForceKeyboard: %s FAIL", FileName); delete _td->lpActiveKeyboard; @@ -609,6 +637,8 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_StopForcingKeyboard() _td->ForceFileName[0] = 0; FreeKeyboardOptions(_td->lpActiveKeyboard); ReleaseKeyboardMemory(_td->lpActiveKeyboard->Keyboard); + ReleaseStateMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboardState); + ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard); RefreshPreservedKeys(FALSE); delete _td->lpActiveKeyboard; _td->lpActiveKeyboard = NULL; @@ -653,6 +683,21 @@ BOOL ReleaseKeyboardMemory(LPKEYBOARD kbd) return TRUE; } +BOOL ReleaseStateMemoryCore(km_kbp_state **state) { + if (!*state) return TRUE; + km_kbp_state_dispose(*state); + *state = NULL; + return TRUE; +} + +BOOL ReleaseKeyboardMemoryCore(km_kbp_keyboard **kbd) { + if (!*kbd) + return TRUE; + km_kbp_keyboard_dispose(*kbd); + *kbd = NULL; + return TRUE; +} + //--------------------------------------------------------------------------------------------------------- // // Selecting Keyman Keyboards @@ -982,6 +1027,9 @@ void ReleaseKeyboards(BOOL Lock) if(Lock) UnloadDLLs(&_td->lpKeyboards[i]); FreeKeyboardOptions(&_td->lpKeyboards[i]); ReleaseKeyboardMemory(_td->lpKeyboards[i].Keyboard); + ReleaseStateMemoryCore(&_td->lpKeyboards[i].lpCoreKeyboardState); + ReleaseKeyboardMemoryCore(&_td->lpKeyboards[i].lpCoreKeyboard); + if(_td->lpKeyboards[i].Profiles) delete _td->lpKeyboards[i].Profiles; // I3581 } diff --git a/windows/src/engine/keyman32/Keyman32.vcxproj b/windows/src/engine/keyman32/Keyman32.vcxproj index ec24837adf..ead16e3609 100644 --- a/windows/src/engine/keyman32/Keyman32.vcxproj +++ b/windows/src/engine/keyman32/Keyman32.vcxproj @@ -55,11 +55,11 @@ $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(IncludePath) + $(ProjectDir)..\..\..\..\common\core\desktop\include;$(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(IncludePath) $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(IncludePath) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\common\core\desktop\include;$(IncludePath) @@ -95,7 +95,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - kmnkbp0.lib;keyman_keyboard_processor_native.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + libkmnkbp0.a;keyman_keyboard_processor_native.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true keyman32.def true @@ -151,7 +151,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - kmnkbp0.lib;keyman_keyboard_processor_native.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + libkmnkbp0.a;keyman_keyboard_processor_native.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true keyman32.def true @@ -295,6 +295,7 @@ %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) + Create Create @@ -371,6 +372,7 @@ + diff --git a/windows/src/engine/keyman32/Keyman32.vcxproj.filters b/windows/src/engine/keyman32/Keyman32.vcxproj.filters index d3fbf2a430..e75258f2c5 100644 --- a/windows/src/engine/keyman32/Keyman32.vcxproj.filters +++ b/windows/src/engine/keyman32/Keyman32.vcxproj.filters @@ -135,6 +135,9 @@ Source Files + + Source Files + @@ -243,8 +246,11 @@ Header Files + + Header Files + - \ No newline at end of file + diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp index 0b24dadf11..c14ceee360 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.cpp +++ b/windows/src/engine/keyman32/appint/aiTIP.cpp @@ -198,6 +198,7 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM *Globals::ShiftState() = (LocalShiftState & K_NOTMODIFIERFLAG) | NewShiftState; // I3588 } + BOOL isUsingCoreProcessor = Globals::get_CoreIntegration(); _td->TIPFUpdateable = Updateable; _td->TIPFPreserved = Preserved; // I4290 @@ -206,35 +207,58 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM _td->state.msg.message = wm_keymankeydown; _td->state.vkey = (WORD) wParam; - _td->state.lpkb = _td->lpActiveKeyboard->Keyboard; + if (isUsingCoreProcessor) { + _td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard; + + } else { + _td->state.lpkb = _td->lpActiveKeyboard->Keyboard; + _td->state.startgroup = &_td->state.lpkb->dpGroupArray[_td->state.lpkb->StartGroup[BEGIN_UNICODE]]; + _td->state.NoMatches = TRUE; + _td->state.LoopTimes = 0; + _td->state.StopOutput = FALSE; + } _td->state.windowunicode = TRUE; - _td->state.startgroup = &_td->state.lpkb->dpGroupArray[_td->state.lpkb->StartGroup[BEGIN_UNICODE]]; - - _td->state.NoMatches = TRUE; - _td->state.LoopTimes = 0; - _td->state.StopOutput = FALSE; _td->state.charCode = CharFromVK(&_td->state.vkey, Globals::get_ShiftState()); // I4582 _td->TIPProcessOutput = outfunc; _td->TIPGetContext = ctfunc; - AppContextWithStores *savedContext = NULL; // I4370 // I4978 - if(!Updateable) { // I4370 - savedContext = new AppContextWithStores(_td->lpActiveKeyboard->Keyboard->cxStoreArray); // I4978 - _td->app->SaveContext(savedContext); + AppContextWithStores *savedContext = NULL; // I4370 // I4978 + AppContext *savedContextUsingCore = NULL; // used for common core + km_kbp_option_item *SavedKBDOptions = NULL; // used for common core + if (!Updateable) { + if (isUsingCoreProcessor) { + savedContextUsingCore = new AppContext(); + _td->app->CopyContext(savedContextUsingCore); + SavedKBDOptions = SaveKeyboardOptionsCore(_td->lpActiveKeyboard); + } else { // I4370 + savedContext = new AppContextWithStores(_td->lpActiveKeyboard->Keyboard->cxStoreArray); // I4978 + _td->app->SaveContext(savedContext); + } } - BOOL res = ProcessHook(); + BOOL res = ProcessHook(); - if(!Updateable) { // I4370 - if(res) { // I4585 - // Reset the context if match found - _td->app->RestoreContext(savedContext); + if (!Updateable) { + if (isUsingCoreProcessor) { + if (res) { + // Reset the context if match found + _td->app->RestoreContextOnly(savedContextUsingCore); + RestoreKeyboardOptionsCore(_td->lpActiveKeyboard->lpCoreKeyboardState, SavedKBDOptions); + DisposeKeyboardOptionsCore(&SavedKBDOptions); + delete savedContextUsingCore; + savedContextUsingCore = NULL; + } + } else { // I4370 + if (res) { // I4585 + // Reset the context if match found + _td->app->RestoreContext(savedContext); + delete savedContext; + savedContext = NULL; + } } - delete savedContext; - savedContext = NULL; } _td->TIPProcessOutput = NULL; @@ -454,6 +478,14 @@ void AITIP::RestoreContext(AppContextWithStores *savedContext) { // I4370 // } } +void AITIP::CopyContext(AppContext *savedContext) { + savedContext->CopyFrom(context); +} + +void AITIP::RestoreContextOnly(AppContext *savedContext) { + context->CopyFrom(savedContext); +} + /* Output actions */ BOOL AITIP::QueueAction(int ItemType, DWORD dwData) { diff --git a/windows/src/engine/keyman32/appint/aiTIP.h b/windows/src/engine/keyman32/appint/aiTIP.h index fd64847168..bb0c790d49 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.h +++ b/windows/src/engine/keyman32/appint/aiTIP.h @@ -1,18 +1,18 @@ /* Name: aiTIP Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 11 Dec 2009 Modified Date: 23 Feb 2016 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version 24 Jun 2010 - mcdurdin - I2436 - Add space to context for AIWin2000Unicode when not matched 24 Apr 2014 - mcdurdin - I4196 - V9.0 - wm_kmmoreposting must be refactored for TIP work as it is not sequential @@ -71,14 +71,29 @@ public: BOOL DebugControlled(); + // TODO: 5442 This would be better to called SaveContextWithStores or SaveContextWithKbdOptions + // Will be removed with 5442 when removing window core void SaveContext(AppContextWithStores *savedContext); // I4370 // I4978 void RestoreContext(AppContextWithStores *savedContext); // I4370 // I4978 + /** + * Copy the member context + * + * @param[out] savedContext the copied context + */ + void CopyContext(AppContext *savedContext); + + /** + * Restore the passed context to the member context + * + * @param savedContext the context to restore + */ + void RestoreContextOnly(AppContext *savedContext); virtual BOOL QueueAction(int ItemType, DWORD dwData); /* Information functions */ - + virtual BOOL CanHandleWindow(HWND ahwnd); virtual BOOL IsWindowHandled(HWND ahwnd); virtual BOOL HandleWindow(HWND ahwnd); @@ -89,7 +104,7 @@ public: virtual void ReadContext(); /* Queue and sending functions */ - + virtual BOOL SendActions(); // I4196 virtual BOOL QueueDebugInformation(int ItemType, LPGROUP Group, LPKEY Rule, PWSTR fcontext, PWSTR foutput, DWORD_PTR dwExtraFlags); diff --git a/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp b/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp index 4027e7a318..52f051c63c 100644 --- a/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp +++ b/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp @@ -116,6 +116,11 @@ WCHAR *AIWin2000Unicode::ContextBufMax(int n) { return context->BufMax(n); } + +void AIWin2000Unicode::SetContext(const WCHAR* buf) +{ + return context->Set(buf); +} BYTE SavedKbdState[256]; diff --git a/windows/src/engine/keyman32/appint/aiWin2000Unicode.h b/windows/src/engine/keyman32/appint/aiWin2000Unicode.h index 3866137074..73db7ac907 100644 --- a/windows/src/engine/keyman32/appint/aiWin2000Unicode.h +++ b/windows/src/engine/keyman32/appint/aiWin2000Unicode.h @@ -12,7 +12,7 @@ Bugs: Todo: - Notes: + Notes: History: 27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version 24 Jun 2010 - mcdurdin - I2436 - Add space to context for AIWin2000Unicode when not matched @@ -43,7 +43,7 @@ public: virtual BOOL QueueAction(int ItemType, DWORD dwData); /* Information functions */ - + virtual BOOL CanHandleWindow(HWND ahwnd); virtual BOOL IsWindowHandled(HWND ahwnd); virtual BOOL HandleWindow(HWND ahwnd); @@ -56,9 +56,10 @@ public: virtual void AddContext(WCHAR ch); //I2436 virtual WCHAR *ContextBuf(int n); virtual WCHAR *ContextBufMax(int n); + virtual void SetContext(const WCHAR* buf); /* Queue and sending functions */ - + virtual BOOL SendActions(); // I4196 virtual BOOL QueueDebugInformation(int ItemType, LPGROUP Group, LPKEY Rule, PWSTR fcontext, PWSTR foutput, DWORD_PTR dwExtraFlags); }; diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index 1ca6eff025..4e8e87941d 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -203,3 +203,33 @@ AppIntegration::AppIntegration() hwnd = NULL; FShiftFlags = 0; } + +BOOL ContextItemsFromAppContext(WCHAR const* buf, km_kbp_context_item** outPtr) +{ + assert(buf); + assert(outPtr); + km_kbp_context_item* context_items = new km_kbp_context_item[wcslen(buf) + 1]; + WCHAR const *p = buf; + uint8_t contextIndex = 0; + while (*p) { + if (*p == UC_SENTINEL) { + assert(*(p + 1) == CODE_DEADKEY); + // we know the only uc_sentinel code in the context is code_deadkey, which has only 1 parameter: uc_sentinel code_deadkey + // setup dead key context item + p += 2; + context_items[contextIndex++] = km_kbp_context_item{ KM_KBP_CT_MARKER, {0,}, {*p} }; + } else if (Uni_IsSurrogate1(*p) && Uni_IsSurrogate2(*(p + 1))) { + // handle surrogate + context_items[contextIndex++] = km_kbp_context_item{ KM_KBP_CT_CHAR, {0,}, {(char32_t)Uni_SurrogateToUTF32(*p, *(p + 1))} }; + p++; + } else { + context_items[contextIndex++] = km_kbp_context_item{ KM_KBP_CT_CHAR, {0,}, {*p} }; + } + p++; + } + // terminate the context_items array. + context_items[contextIndex] = km_kbp_context_item KM_KBP_CONTEXT_ITEM_END; + + *outPtr = context_items; + return true; +} diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index 55f6c4a693..3ce82edf2c 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -208,6 +208,16 @@ public: virtual BOOL SendActions() = 0; // I4196 }; +/** + * Convert AppContext array into an array of core context items. + * Caller is responsible for freeing the memory. + * + * @param buf appcontext character array + * @param outPtr The ouput array of context items. caller to free memory + * @return BOOL True if array created succesfully + */ +BOOL ContextItemsFromAppContext(WCHAR const* buf, km_kbp_context_item** outPtr); + extern const LPSTR ItemTypes[]; #endif diff --git a/windows/src/engine/keyman32/calldll.cpp b/windows/src/engine/keyman32/calldll.cpp index 2db3fd6643..27ecac7cb0 100644 --- a/windows/src/engine/keyman32/calldll.cpp +++ b/windows/src/engine/keyman32/calldll.cpp @@ -143,7 +143,8 @@ BOOL LoadDLLs(LPINTKEYBOARDINFO lpkbi) if(lpkbi->nIMDLLs > 0) if(!UnloadDLLs(lpkbi)) return FALSE; - if(!GetKeyboardFileName(lpkbi->Name, fullname, _MAX_PATH)) return FALSE; + if(!GetKeyboardFileName(lpkbi->Name, fullname, _MAX_PATH)) return FALSE; + if (!lpkbi->Keyboard) return FALSE; for(DWORD i = 0; i < lpkbi->Keyboard->cxStoreArray; i++) { @@ -224,8 +225,8 @@ BOOL DeactivateDLLs(LPINTKEYBOARDINFO lpkbi) void CallDLL(LPINTKEYBOARDINFO lpkbi, DWORD storenum) { //SendDebugMessageFormat(0, sdmKeyboard, 0, "CallDll: Enter"); - - if(storenum >= lpkbi->Keyboard->cxStoreArray) return; + if (!lpkbi->Keyboard) return; + if(storenum >= lpkbi->Keyboard->cxStoreArray) return; LPSTORE s = &lpkbi->Keyboard->dpStoreArray[storenum]; if(s->dwSystemID != TSS_CALLDEFINITION) return; diff --git a/windows/src/engine/keyman32/capsstate.cpp b/windows/src/engine/keyman32/capsstate.cpp index 3e35022c74..c9d283cde4 100644 --- a/windows/src/engine/keyman32/capsstate.cpp +++ b/windows/src/engine/keyman32/capsstate.cpp @@ -34,8 +34,8 @@ void ResetCapsLock(void) { PKEYMAN64THREADDATA _td = ThreadGlobals(); if (!_td) return; - if (!_td->lpActiveKeyboard) return; + if (!_td->lpActiveKeyboard->Keyboard) return; SendDebugMessageFormat(0, sdmGlobal, 0, "ResetCapsLock: enter"); @@ -58,6 +58,7 @@ void KeyCapsLockPress(BOOL FIsUp) // I3284 - void // I3529 PKEYMAN64THREADDATA _td = ThreadGlobals(); if (!_td) return; if (!_td->lpActiveKeyboard) return; // pass through to window + if (!_td->lpActiveKeyboard->Keyboard) return; if (_td->lpActiveKeyboard->Keyboard->dwFlags & KF_CAPSONONLY) { @@ -85,6 +86,7 @@ void KeyShiftPress(BOOL FIsUp) // I3284 - void // I3529 PKEYMAN64THREADDATA _td = ThreadGlobals(); if (!_td) return; if (!_td->lpActiveKeyboard) return; // pass through to window + if (!_td->lpActiveKeyboard->Keyboard) return; if (_td->lpActiveKeyboard->Keyboard->dwFlags & KF_SHIFTFREESCAPS) { diff --git a/windows/src/engine/keyman32/globals.h b/windows/src/engine/keyman32/globals.h index 8041cd55b2..03f82a4393 100644 --- a/windows/src/engine/keyman32/globals.h +++ b/windows/src/engine/keyman32/globals.h @@ -149,6 +149,7 @@ public: static BOOL get_debug_KeymanLog(); static BOOL get_debug_ToConsole(); + static BOOL get_CoreIntegration(); static void LoadDebugSettings(); }; @@ -191,6 +192,7 @@ typedef struct tagKEYMAN64THREADDATA { LPINTKEYBOARDINFO lpKeyboards; // keyboard definitions LPINTKEYBOARDINFO lpActiveKeyboard; + // I3616 LPMSG msgbuf; // Message buffer (alloc at runtime) diff --git a/windows/src/engine/keyman32/k32_dbg.cpp b/windows/src/engine/keyman32/k32_dbg.cpp index b58eb8df85..b2cd0edbcd 100644 --- a/windows/src/engine/keyman32/k32_dbg.cpp +++ b/windows/src/engine/keyman32/k32_dbg.cpp @@ -306,6 +306,18 @@ char *Debug_UnicodeString(PWSTR s, int x) { return bufout[x]; } +BOOL DebugAssert_1(BOOL condition, char *message, char *file, int line) +{ + if (!(condition)) { + SendDebugMessage_1(0, sdmGlobal, 0, file, line, message); + if (IsDebugAssertEnabled()) { + assert(condition); + } + return FALSE; + } + return TRUE; +} + #ifdef _DEBUG void _OutputThreadDebugString(char *s) { char buf[256]; @@ -314,6 +326,15 @@ void _OutputThreadDebugString(char *s) { } #endif +BOOL IsDebugAssertEnabled() +{ +#ifdef _DEBUG + return IsDebuggerPresent(); +#else + return FALSE; +#endif +} + void WINAPI Keyman_Diagnostic(int mode) { if (mode == 0) { RaiseException(0xDEADBEEF, EXCEPTION_NONCONTINUABLE, 0, NULL); diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp index 7c650e4283..f49e632714 100644 --- a/windows/src/engine/keyman32/k32_globals.cpp +++ b/windows/src/engine/keyman32/k32_globals.cpp @@ -287,6 +287,9 @@ static BOOL f_debug_KeymanLog = FALSE, f_debug_ToConsole = FALSE; +static BOOL + f_CoreIntegration = TRUE; + #pragma data_seg() /***************************************************************************/ @@ -361,6 +364,8 @@ BOOL Globals::get_MnemonicDeadkeyConversionMode() { return f_MnemonicDeadkeyConv BOOL Globals::get_debug_KeymanLog() { return f_debug_KeymanLog; } BOOL Globals::get_debug_ToConsole() { return f_debug_ToConsole; } +BOOL Globals::get_CoreIntegration() { return f_CoreIntegration; } + void Globals::SetBaseKeyboardName(wchar_t *baseKeyboardName, wchar_t *baseKeyboardNameAlt) { // I4583 wcscpy_s(f_BaseKeyboardName, baseKeyboardName); wcscpy_s(f_BaseKeyboardNameAlt, baseKeyboardNameAlt); @@ -379,6 +384,9 @@ void Globals::SetBaseKeyboardFlags(char *baseKeyboard, BOOL simulateAltGr, BOOL be changed until Keyman is restarted. */ BOOL Globals::InitSettings() { + /* Check for common core vs windows core */ + f_CoreIntegration = Reg_GetDebugFlag(REGSZ_Flag_UseKeymanCore, TRUE); + SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::InitSettings - Coreintegration set in '" REGSZ_Flag_UseKeymanCore "' to %x", f_CoreIntegration); f_vk_prefix = _VK_PREFIX_DEFAULT; RegistryReadOnly reg(HKEY_LOCAL_MACHINE); if (reg.OpenKeyReadOnly(REGSZ_KeymanLM) && diff --git a/windows/src/engine/keyman32/keyboardoptions.cpp b/windows/src/engine/keyman32/keyboardoptions.cpp index e0964cfd97..1b5c26f2c8 100644 --- a/windows/src/engine/keyman32/keyboardoptions.cpp +++ b/windows/src/engine/keyman32/keyboardoptions.cpp @@ -18,10 +18,18 @@ 28 Nov 2012 - mcdurdin - I3594 - V9.0 - Remove old SelectKeyboard code and related messages */ #include "pch.h" -#include "registry.h" void IntSaveKeyboardOption(LPCSTR key, LPINTKEYBOARDINFO kp, int nStoreToSave); BOOL IntLoadKeyboardOptions(LPCSTR key, LPINTKEYBOARDINFO kp); +BOOL IntLoadKeyboardOptionsCore(LPCSTR key, LPINTKEYBOARDINFO kp, km_kbp_state* const state); +void IntSaveKeyboardOptionREGCore(LPCSTR REGKey, LPINTKEYBOARDINFO kp, LPCWSTR key, LPWSTR value); + +static km_kbp_cp* CloneKMKBPCP(const km_kbp_cp* cp) { + LPCWSTR buf = reinterpret_cast(cp); + km_kbp_cp* clone = new km_kbp_cp[wcslen(buf) + 1]; + wcscpy_s(reinterpret_cast(clone), wcslen(buf) + 1, buf); + return clone; +} void LoadKeyboardOptions(LPINTKEYBOARDINFO kp) { // I3594 @@ -30,6 +38,9 @@ void LoadKeyboardOptions(LPINTKEYBOARDINFO kp) void LoadSharedKeyboardOptions(LPINTKEYBOARDINFO kp) { + if(!DebugAssert(!Globals::get_CoreIntegration(), "LoadSharedKeyboardOptions: Error called in core integration mode")) { + return; + } // Called when another thread changes keyboard options and we are sharing keyboard settings assert(kp != NULL); assert(kp->Keyboard != NULL); @@ -41,6 +52,9 @@ void LoadSharedKeyboardOptions(LPINTKEYBOARDINFO kp) void FreeKeyboardOptions(LPINTKEYBOARDINFO kp) { + if (!DebugAssert(!Globals::get_CoreIntegration(), "FreeKeyboardOptions: Error called in core integration mode")) { + return; + } // This is a cleanup routine; we don't want to precondition all calls to it // so we do not assert if (kp == NULL || kp->Keyboard == NULL || kp->KeyboardOptions == NULL) @@ -58,6 +72,9 @@ void FreeKeyboardOptions(LPINTKEYBOARDINFO kp) void SetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToSet, int nStoreToRead) { + if (!DebugAssert(!Globals::get_CoreIntegration(), "SetKeyboardOption: Error called in core integration mode")) { + return; + } assert(kp != NULL); assert(kp->Keyboard != NULL); assert(kp->KeyboardOptions != NULL); @@ -75,7 +92,7 @@ void SetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToSet, int nStoreToRead) { kp->KeyboardOptions[nStoreToSet].OriginalStore = kp->Keyboard->dpStoreArray[nStoreToSet].dpString; } - + kp->KeyboardOptions[nStoreToSet].Value = new WCHAR[wcslen(sp->dpString)+1]; wcscpy_s(kp->KeyboardOptions[nStoreToSet].Value, wcslen(sp->dpString)+1, sp->dpString); kp->Keyboard->dpStoreArray[nStoreToSet].dpString = kp->KeyboardOptions[nStoreToSet].Value; @@ -83,6 +100,9 @@ void SetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToSet, int nStoreToRead) void ResetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToReset) { + if (!DebugAssert(!Globals::get_CoreIntegration(), "ResetKeyboardOption: Error called in core integration mode")) { + return; + } assert(kp != NULL); assert(kp->Keyboard != NULL); assert(kp->KeyboardOptions != NULL); @@ -119,9 +139,30 @@ void ResetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToReset) void SaveKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToSave) { + if (!DebugAssert(!Globals::get_CoreIntegration(), "SaveKeyboardOption: Error called in core integration mode")) { + return; + } IntSaveKeyboardOption(REGSZ_KeyboardOptions, kp, nStoreToSave); } +void SaveKeyboardOptionREGCore(LPINTKEYBOARDINFO kp, LPCWSTR key, LPWSTR value) +{ + IntSaveKeyboardOptionREGCore(REGSZ_KeyboardOptions, kp, key, value); +} + +void IntSaveKeyboardOptionREGCore(LPCSTR REGKey, LPINTKEYBOARDINFO kp, LPCWSTR key, LPWSTR value) +{ + assert(REGKey != NULL); + assert(kp != NULL); + assert(key); + assert(value); + + RegistryFullAccess r(HKEY_CURRENT_USER); + if (r.OpenKey(REGSZ_KeymanActiveKeyboards, TRUE) && r.OpenKey(kp->Name, TRUE) && r.OpenKey(REGKey, TRUE)) + { + r.WriteString(key, value); + } +} BOOL IntLoadKeyboardOptions(LPCSTR key, LPINTKEYBOARDINFO kp) { @@ -129,7 +170,7 @@ BOOL IntLoadKeyboardOptions(LPCSTR key, LPINTKEYBOARDINFO kp) assert(kp != NULL); assert(kp->Keyboard != NULL); assert(kp->KeyboardOptions == NULL); - + kp->KeyboardOptions = new INTKEYBOARDOPTIONS[kp->Keyboard->cxStoreArray]; memset(kp->KeyboardOptions, 0, sizeof(INTKEYBOARDOPTIONS) * kp->Keyboard->cxStoreArray); RegistryReadOnly r(HKEY_CURRENT_USER); @@ -156,7 +197,7 @@ BOOL IntLoadKeyboardOptions(LPCSTR key, LPINTKEYBOARDINFO kp) break; } - } + } } n++; } @@ -182,3 +223,154 @@ void IntSaveKeyboardOption(LPCSTR key, LPINTKEYBOARDINFO kp, int nStoreToSave) r.WriteString(kp->Keyboard->dpStoreArray[nStoreToSave].dpName, kp->Keyboard->dpStoreArray[nStoreToSave].dpString); } } + +void LoadKeyboardOptionsREGCore(LPINTKEYBOARDINFO kp, km_kbp_state* const state) +{ + SendDebugMessageFormat(0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: Enter"); + IntLoadKeyboardOptionsCore(REGSZ_KeyboardOptions, kp, state); +} + +BOOL IntLoadKeyboardOptionsCore(LPCSTR key, LPINTKEYBOARDINFO kp, km_kbp_state* state) +{ + assert(key != NULL); + assert(kp != NULL); + + RegistryReadOnly r(HKEY_CURRENT_USER); + if (r.OpenKeyReadOnly(REGSZ_KeymanActiveKeyboards) && r.OpenKeyReadOnly(kp->Name) && r.OpenKeyReadOnly(key)) + { + WCHAR buf[256]; + int n = 0; + // Get the list of default options to determine size of list + const km_kbp_keyboard_attrs* keyboardAttrs; + km_kbp_status err_status = km_kbp_keyboard_get_attrs(kp->lpCoreKeyboard, &keyboardAttrs); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: km_kbp_keyboard_get_attrs failed with error status [%d]", err_status); + return FALSE; + } + size_t listSize = km_kbp_options_list_size(keyboardAttrs->default_options); + km_kbp_option_item* keyboardOpts = new km_kbp_option_item[listSize+1]; + + while ((r.GetValueNames(buf, sizeof(buf) / sizeof(buf[0]), n)) && (n < (int)listSize)) + { + buf[255] = 0; + WCHAR val[256]; + + if (r.ReadString(buf, val, sizeof(val) / sizeof(val[0])) && val[0]) + { + val[255] = 0; + keyboardOpts[n].scope = KM_KBP_OPT_KEYBOARD; + + km_kbp_cp* cp = new km_kbp_cp[wcslen(buf) + 1]; + wcscpy_s(reinterpret_cast(cp), wcslen(buf) + 1, buf); + keyboardOpts[n].key = cp; + + cp = new km_kbp_cp[wcslen(val) + 1]; + wcscpy_s(reinterpret_cast(cp), wcslen(val) + 1, val); + keyboardOpts[n].value = cp; + } + n++; + } + + keyboardOpts[n] = KM_KBP_OPTIONS_END; + // once we have the option list we can then update the options using the public api call + err_status = km_kbp_state_options_update(state, keyboardOpts); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "LoadKeyboardOptionsREGCore: km_kbp_state_options_update failed with error status [%d]", err_status); + } + for (int i = 0; i < (int)listSize + 1; i++) { + delete[] keyboardOpts[i].key; + delete[] keyboardOpts[i].value; + } + delete[] keyboardOpts; + return TRUE; + } + return FALSE; +} + +BOOL +UpdateKeyboardOptionsCore( + km_kbp_state* const lpCoreKeyboardState, + km_kbp_option_item *lpCoreKeyboardOptions) { + + int listSize = (int)km_kbp_options_list_size(lpCoreKeyboardOptions); + // Create a option list based on this size look up each key and store the return value in it. + // then at the end return this options list. + BOOL changed = FALSE; + km_kbp_cp const* retValue = nullptr; + for (int i = 0; i < listSize; i++) { + km_kbp_status err_status = km_kbp_state_option_lookup(lpCoreKeyboardState, lpCoreKeyboardOptions[i].scope, lpCoreKeyboardOptions[i].key, + &retValue); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "UpdateKeyboardOptionsCore: km_kbp_state_option_lookup failed with error status [%d]", err_status); + continue; + } + // compare to see if changed + if (wcscmp(reinterpret_cast(retValue), reinterpret_cast(lpCoreKeyboardOptions[i].value)) != 0) { + delete lpCoreKeyboardOptions[i].value; + lpCoreKeyboardOptions[i].value = CloneKMKBPCP(retValue); + changed = TRUE; + } + } + return changed; +} + +km_kbp_option_item* +SaveKeyboardOptionsCore(LPINTKEYBOARDINFO kp) { + + // Get the list of default options to determine size of list + const km_kbp_keyboard_attrs* keyboardAttrs; + km_kbp_status err_status = km_kbp_keyboard_get_attrs(kp->lpCoreKeyboard, &keyboardAttrs); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "SaveKeyboardOptionsCore: km_kbp_keyboard_get_attrs failed with error status [%d]", err_status); + return nullptr; + } + int listSize = (int)km_kbp_options_list_size(keyboardAttrs->default_options); + km_kbp_option_item* savedKeyboardOpts = new km_kbp_option_item[listSize + 1]; + km_kbp_cp const* retValue = nullptr; + + km_kbp_option_item const* kbDefaultOpts = keyboardAttrs->default_options; + for (int i = 0; i < listSize; i++, ++kbDefaultOpts) { + if (kbDefaultOpts->scope != KM_KBP_OPT_KEYBOARD) + continue; + err_status = + km_kbp_state_option_lookup(kp->lpCoreKeyboardState, KM_KBP_OPT_KEYBOARD, kbDefaultOpts->key, &retValue); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "SaveKeyboardOptionsCore: km_kbp_state_option_lookup failed with error status [%d]", err_status); + continue; + } + savedKeyboardOpts[i].key = CloneKMKBPCP(kbDefaultOpts->key); + savedKeyboardOpts[i].value = CloneKMKBPCP(retValue); + savedKeyboardOpts[i].scope = KM_KBP_OPT_KEYBOARD; + } + savedKeyboardOpts[listSize] = KM_KBP_OPTIONS_END; + return savedKeyboardOpts; +} + +BOOL +RestoreKeyboardOptionsCore( + km_kbp_state* const lpCoreKeyboardState, + km_kbp_option_item* lpCoreKeyboardOptions) { + km_kbp_status err_status = km_kbp_state_options_update(lpCoreKeyboardState, lpCoreKeyboardOptions); + if (err_status != KM_KBP_STATUS_OK) { + SendDebugMessageFormat( + 0, sdmKeyboard, 0, "RestoreKeyboardOptionsCore: km_kbp_state_options_update failed with error status [%d]", err_status); + return FALSE; + } + return TRUE; +} + +void +DisposeKeyboardOptionsCore(km_kbp_option_item** lpCoreKeyboardOptions) { + size_t listSize = km_kbp_options_list_size(*lpCoreKeyboardOptions); + for (int i = 0; i < (int)listSize; i++) { + delete[] (*lpCoreKeyboardOptions)[i].key; + delete[] (*lpCoreKeyboardOptions)[i].value; + } + delete[] *lpCoreKeyboardOptions; + *lpCoreKeyboardOptions = NULL; +} diff --git a/windows/src/engine/keyman32/keyboardoptions.h b/windows/src/engine/keyman32/keyboardoptions.h index d47e3adb13..cef9ae6df1 100644 --- a/windows/src/engine/keyman32/keyboardoptions.h +++ b/windows/src/engine/keyman32/keyboardoptions.h @@ -1,18 +1,18 @@ /* Name: keyboardoptions Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 25 May 2010 Modified Date: 25 May 2010 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 25 May 2010 - mcdurdin - I1632 - Keyboard Options */ @@ -22,3 +22,61 @@ void SetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToSet, int nStoreToRead); void ResetKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToReset); void SaveKeyboardOption(LPINTKEYBOARDINFO kp, int nStoreToSave); void LoadSharedKeyboardOptions(LPINTKEYBOARDINFO kp); +/** + * Updates the supplied Keyboard processor options list from the keyboard processor pointed + * to by the state pointer. + * + * @param lpCoreKeyboardState The core keyboardprocessor state which has the source options + * @param[in,out] lpCoreKeyboardOptions The core keyboard options to be updated + * @return BOOL True if one or more options were updated + */ +BOOL UpdateKeyboardOptionsCore(km_kbp_state* const lpCoreKeyboardState, km_kbp_option_item *lpCoreKeyboardOptions); + +/** + * Returns a copy of the core keyboard processors current keyboard options + * The caller is responsible for freeing the returned km_kbp_option_item's list. + * + * @param kp A pointer to the keyboard info object that contains the + * keyboardprocessor state and keyboard for the source options list. + * + * @return km_kbp_option_item* The copy of the options list or NULL if copy failed + */ +km_kbp_option_item* SaveKeyboardOptionsCore(LPINTKEYBOARDINFO kp); + +/** + * Restore the core keyboard processor options to the supplied keyboard + * list of `km_kbp_option_item`s + * + * @param lpCoreKeyboardState The state pointer for the keyboard processor + * @param lpCoreKeyboardOptions The list of `km_kbp_option_item`s to restore + * + * return BOOL TRUE when the call to update keyboard processor was successful + */ +BOOL RestoreKeyboardOptionsCore(km_kbp_state* const lpCoreKeyboardState, km_kbp_option_item* lpCoreKeyboardOptions); + +/* Common core integration functions */ + +/** + * Loads the keyboard options from the windows registry + * + * @param kp keyboard info object with options to be updated + * @param state core keyboard state used to update keyboard options + */ +void LoadKeyboardOptionsREGCore(LPINTKEYBOARDINFO kp, km_kbp_state* state); + +/** + * Saves the keyboard option to the windows registry + * + * @param kp keyboard info object with for which the option is to be updated + * @param key keyboard key to save + * @param value keyboard option value to save + */ +void SaveKeyboardOptionREGCore(LPINTKEYBOARDINFO kp, LPCWSTR key, LPWSTR value); + +/** + * Free the allocated resources belonging to a key_kbp_option_items object + * that was created on the heap most likely using SaveKeyboardOptionREGCore + * + * @param lpCoreKeyboardOptions keyboard options items to be freed + */ +void DisposeKeyboardOptionsCore(km_kbp_option_item** lpCoreKeyboardOptions); diff --git a/windows/src/engine/keyman32/keyman-engine.vcxproj b/windows/src/engine/keyman32/keyman-engine.vcxproj index 45718f5bff..49c69d7907 100644 --- a/windows/src/engine/keyman32/keyman-engine.vcxproj +++ b/windows/src/engine/keyman32/keyman-engine.vcxproj @@ -55,7 +55,7 @@ $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(IncludePath) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\common\core\desktop\include;$(IncludePath) $(ProjectDir)..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) @@ -307,6 +307,7 @@ %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) + Create Create @@ -383,6 +384,7 @@ + diff --git a/windows/src/engine/keyman32/keyman-engine.vcxproj.filters b/windows/src/engine/keyman32/keyman-engine.vcxproj.filters index d3fbf2a430..eedeaa07be 100644 --- a/windows/src/engine/keyman32/keyman-engine.vcxproj.filters +++ b/windows/src/engine/keyman32/keyman-engine.vcxproj.filters @@ -135,6 +135,9 @@ Source Files + + Source Files + @@ -243,6 +246,9 @@ Header Files + + Header Files + diff --git a/windows/src/engine/keyman32/kmhook_getmessage.cpp b/windows/src/engine/keyman32/kmhook_getmessage.cpp index 2ecec7f2ac..81026a84e9 100644 --- a/windows/src/engine/keyman32/kmhook_getmessage.cpp +++ b/windows/src/engine/keyman32/kmhook_getmessage.cpp @@ -264,6 +264,7 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) if(_td->lpActiveKeyboard) { _td->state.lpkb = _td->lpActiveKeyboard->Keyboard; + _td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard; } // I4412 if(mp->message == wm_keymanshift) @@ -350,6 +351,7 @@ void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam) if(_td->lpActiveKeyboard) { _td->state.lpkb = _td->lpActiveKeyboard->Keyboard; + _td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard; } SelectApplicationIntegration(); diff --git a/windows/src/engine/keyman32/kmprocess.cpp b/windows/src/engine/keyman32/kmprocess.cpp index 83b0bdc0a7..502779ca81 100644 --- a/windows/src/engine/keyman32/kmprocess.cpp +++ b/windows/src/engine/keyman32/kmprocess.cpp @@ -109,8 +109,8 @@ BOOL ProcessHook() LPGROUP gp = _td->state.startgroup; - fOutputKeystroke = FALSE; - + fOutputKeystroke = FALSE; // TODO: 5442 no longer needs to be global once we use core processor + BOOL isUsingCoreProcessor = Globals::get_CoreIntegration(); // // If we are running in the debugger, don't do a second run through // @@ -140,8 +140,32 @@ BOOL ProcessHook() else _td->app->QueueDebugInformation(QID_BEGIN_ANSI, NULL, NULL, NULL, NULL, (DWORD_PTR) &keyinfo); } + + if (isUsingCoreProcessor) { - ProcessGroup(gp); + SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessActions: "); + PWSTR contextBuf = _td->app->ContextBufMax(MAXCONTEXT); + km_kbp_context_item *citems = nullptr; + ContextItemsFromAppContext(contextBuf, &citems); + if (KM_KBP_STATUS_OK != + (km_kbp_status_codes)km_kbp_context_set( + km_kbp_state_context(_td->lpActiveKeyboard->lpCoreKeyboardState), citems)) { + km_kbp_context_items_dispose(citems); + return FALSE; + } + km_kbp_context_items_dispose(citems); + //_td->state.vkey == VK_DOWN + if (KM_KBP_STATUS_OK != + (km_kbp_status_codes)km_kbp_process_event( + _td->lpActiveKeyboard->lpCoreKeyboardState, _td->state.vkey, static_cast(Globals::get_ShiftState()), 1)) { + return FALSE; + } + + ProcessActions(&fOutputKeystroke); + } + else { + ProcessGroup(gp); // TODO: 5442 remove + } if (fOutputKeystroke && !_td->app->IsQueueEmpty()) { // @@ -195,6 +219,9 @@ BOOL ProcessHook() _td->app->SetCurrentShiftState(Globals::get_ShiftState()); _td->app->SendActions(); // I4196 } + // output context for debugging + // PWSTR contextBuf = _td->app->ContextBufMax(MAXCONTEXT); + // SendDebugMessageFormat(0, sdmAIDefault, 0, "Kmprocess::ProcessHook After cxt=%s", Debug_UnicodeString(contextBuf, 1)); _td->app->QueueDebugInformation(QID_END, NULL, NULL, NULL, NULL, 0); @@ -217,7 +244,10 @@ BOOL ProcessHook() BOOL ProcessGroup(LPGROUP gp) { - DWORD i; + if (!DebugAssert(!Globals::get_CoreIntegration(), "KMPROCESS:ProcessGroup: Error called in core integration mode")) { + return FALSE; + } + DWORD i; LPKEY kkp = NULL; PWSTR p; int sdmfI; @@ -515,7 +545,10 @@ BOOL ProcessGroup(LPGROUP gp) int PostString(PWSTR str, LPMSG mp, LPKEYBOARD lpkb, PWSTR endstr) { - PWSTR p, q, temp; + if (!DebugAssert(!Globals::get_CoreIntegration(), "KKMPROCESS:PostString: Error called in core integration mode")) { + return FALSE; + } + PWSTR p, q, temp; LPSTORE s; int n1, n2; int i, n, shift; @@ -680,6 +713,9 @@ BOOL IsMatchingPlatform(LPSTORE s) // I3432 BOOL ContextMatch(LPKEY kkp) { + if (!DebugAssert(!Globals::get_CoreIntegration(), "KMPROCESS:ContextMatch: Error called in core integration mode")) { + return FALSE; + } WORD /*i,*/ n; PWSTR p, q, qbuf, temp; LPWORD indexp; diff --git a/windows/src/engine/keyman32/kmprocessactions.cpp b/windows/src/engine/keyman32/kmprocessactions.cpp new file mode 100644 index 0000000000..9f13bc4afb --- /dev/null +++ b/windows/src/engine/keyman32/kmprocessactions.cpp @@ -0,0 +1,123 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Description: Used for apply core processor actions to + * Keyman for Windows engine. + */ +#include "pch.h" + +static BOOL processUnicodeChar(AITIP* app, const km_kbp_action_item* actionItem) { + if (Uni_IsSMP(actionItem->character)) { + app->QueueAction(QIT_CHAR, (Uni_UTF32ToSurrogate1(actionItem->character))); + app->QueueAction(QIT_CHAR, (Uni_UTF32ToSurrogate2(actionItem->character))); + } + else { + app->QueueAction(QIT_CHAR, actionItem->character); + } + return TRUE; +} + +static BOOL processMarker(AITIP* app, const km_kbp_action_item* actionItem) { + app->QueueAction(QIT_DEADKEY, (DWORD)actionItem->marker); + return TRUE; +} + +static BOOL processAlert(AITIP* app) { + app->QueueAction(QIT_BELL, 0); + return TRUE; +} + +static BOOL processBack(AITIP* app, const km_kbp_action_item* actionItem) { + if (actionItem->backspace.expected_type == KM_KBP_BT_MARKER) { + app->QueueAction(QIT_BACK, BK_DEADKEY); + } else /* actionItem->backspace.expected_type == KM_KBP_BT_CHAR, KM_KBP_BT_UNKNOWN */ { + app->QueueAction(QIT_BACK, 0); + } + return TRUE; +} + +static BOOL processPersistOpt( + const km_kbp_action_item* actionItem, + km_kbp_state* keyboardState, + LPINTKEYBOARDINFO activeKeyboard +) { + if (actionItem->option != NULL) + { + // Allocate for 1 option plus 1 pad struct of 0's for KM_KBP_IT_END + km_kbp_option_item keyboardOpts[2] = { 0 }; + keyboardOpts[0].key = actionItem->option->key; + keyboardOpts[0].value = actionItem->option->value; + km_kbp_status eventStatus = (km_kbp_status_codes)km_kbp_state_options_update(keyboardState, keyboardOpts); + if (eventStatus != KM_KBP_STATUS_OK) + { + // log warning "problem saving option for km_kbp_keyboard"); + SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessHook: Error %d saving option for keyboard [%s].", eventStatus, activeKeyboard->Name); + } + + // Put the keyboard option into Windows Registry + if (actionItem->option != NULL && actionItem->option->key != NULL && + actionItem->option->value != NULL) + { + // log"Saving keyboard option to registry"); + SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessHook: Saving option to registry for keyboard [%s].", activeKeyboard->Name); + LPWSTR value = new WCHAR[sizeof(actionItem->option->value) + 1]; + wcscpy_s(value, sizeof(actionItem->option->value) + 1, reinterpret_cast(actionItem->option->value)); + SaveKeyboardOptionREGCore(activeKeyboard, reinterpret_cast(actionItem->option->key), value); + } + } + return TRUE; +} + +static BOOL processInvalidateContext( + AITIP* app, + km_kbp_state* keyboardState +) { + km_kbp_context_clear(km_kbp_state_context(keyboardState)); + app->ResetContext(); + return TRUE; +} + +BOOL ProcessActions(BOOL* emitKeyStroke) +{ + PKEYMAN64THREADDATA _td = ThreadGlobals(); + if (!_td) return FALSE; + + // Process the action items from the core. This actions will modify the windows context (AppContext). + // Therefore it is not required to copy the context from the core to the windows context. + + for (auto act = km_kbp_state_action_items(_td->lpActiveKeyboard->lpCoreKeyboardState, nullptr); act->type != KM_KBP_IT_END; act++) { + BOOL continueProcessingActions = TRUE; + switch (act->type) { + case KM_KBP_IT_CHAR: + continueProcessingActions = processUnicodeChar(_td->app, act); + break; + case KM_KBP_IT_MARKER: + continueProcessingActions = processMarker(_td->app, act); + break; + case KM_KBP_IT_ALERT: + continueProcessingActions = processAlert(_td->app); + break; + case KM_KBP_IT_BACK: + continueProcessingActions = processBack(_td->app, act); + break; + case KM_KBP_IT_PERSIST_OPT: + continueProcessingActions = processPersistOpt(act, _td->lpActiveKeyboard->lpCoreKeyboardState, _td->lpActiveKeyboard); + break; + case KM_KBP_IT_EMIT_KEYSTROKE: + *emitKeyStroke = TRUE; + continueProcessingActions = TRUE; + break; + case KM_KBP_IT_INVALIDATE_CONTEXT: + continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState); + break; + case KM_KBP_IT_END: + // fallthrough + default: + assert(false); // NOT SUPPORTED + break; + } + if (!continueProcessingActions) + return FALSE; + } + return TRUE; +} diff --git a/windows/src/engine/keyman32/kmprocessactions.h b/windows/src/engine/keyman32/kmprocessactions.h new file mode 100644 index 0000000000..1bc0ba1d99 --- /dev/null +++ b/windows/src/engine/keyman32/kmprocessactions.h @@ -0,0 +1,13 @@ + +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Description: Used for apply core processor actions to + * Keyman for Windows engine. + */ +#ifndef _KMPROCESSACTIONS_H +#define _KMPROCESSACTIONS_h + +BOOL ProcessActions(BOOL* emitKeyStroke); + +#endif diff --git a/windows/src/engine/keyman32/selectkeyboard.cpp b/windows/src/engine/keyman32/selectkeyboard.cpp index 408a9c685f..02fd5bc5eb 100644 --- a/windows/src/engine/keyman32/selectkeyboard.cpp +++ b/windows/src/engine/keyman32/selectkeyboard.cpp @@ -53,9 +53,90 @@ // I3594 // I4220 +BOOL SelectKeyboardCore(DWORD KeymanID) +{ + int i; + HWND hwnd = GetFocus(); + + PKEYMAN64THREADDATA _td = ThreadGlobals(); + if (!_td) return FALSE; + + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ENTER SelectKeyboardCore-------------------------------------------"); + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ENTER SelectKeyboardCore: Current:(HKL=%x KeymanID=%x %s) New:(ID=%x)", //lpActiveKeyboard=%s ActiveKeymanID: %x sk: %x KeymanID: %d", + GetKeyboardLayout(0), + _td->ActiveKeymanID, + _td->lpActiveKeyboard == NULL ? "NULL" : _td->lpActiveKeyboard->Name, + //_td->NextKeyboardLayout, + KeymanID); + + __try + { + if (_td->ForceFileName[0]) + { + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboard: Ignored due to ForceFile"); + return FALSE; // Keyboard file is force-loaded + } + + KMHideIM(); + // TODO: 5650 Equivalent for core keyboard IDML dlls + if (_td->lpActiveKeyboard) DeactivateDLLs(_td->lpActiveKeyboard); + _td->lpActiveKeyboard = NULL; + _td->ActiveKeymanID = KEYMANID_NONKEYMAN; + + //SendDebugMessageFormat(hwnd,sdmGlobal,0,"SelectKeyboard: nKeyboards=%d", nKeyboards); + + for (i = 0; i < _td->nKeyboards; i++) + { + if (_td->lpKeyboards[i].KeymanID == KeymanID) + { + if (!_td->lpKeyboards[i].lpCoreKeyboard && !LoadlpKeyboard(i)) + { + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboardCore: Unable to load"); + return TRUE; + } + + _td->lpActiveKeyboard = &_td->lpKeyboards[i]; + _td->ActiveKeymanID = _td->lpActiveKeyboard->KeymanID; + + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboardCore: NewKeymanID: %x", _td->ActiveKeymanID); + + if (_td->app) _td->app->ResetContext(); + ResetCapsLock(); + + SelectApplicationIntegration(); // I4287 + if (_td->app && !_td->app->IsWindowHandled(hwnd)) _td->app->HandleWindow(hwnd); + _td->state.windowunicode = !_td->app || _td->app->IsUnicode(); + // TODO: 5650 Equivalent for corekeyboard + //ActivateDLLs(_td->lpActiveKeyboard); + + return TRUE; + } + if (IsFocusedThread()) + { + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboardCore: Keyboard Not Found"); + } + } + } + + __finally + { + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "EXIT SelectKeyboardCore: Current:(HKL=%x KeymanID=%x %s) New:(ID=%x)", //lpActiveKeyboard=%s ActiveKeymanID: %x sk: %x KeymanID: %d", + GetKeyboardLayout(0), + _td->ActiveKeymanID, + _td->lpActiveKeyboard == NULL ? "NULL" : _td->lpActiveKeyboard->Name, + KeymanID); + SendDebugMessageFormat(hwnd, sdmGlobal, 0, "EXIT SelectKeyboardCore-------------------------------------------"); + } + return TRUE; +} + BOOL SelectKeyboard(DWORD KeymanID) { - int i; + if (Globals::get_CoreIntegration()) + { + return SelectKeyboardCore(KeymanID); + } + int i; HWND hwnd = GetFocus(); PKEYMAN64THREADDATA _td = ThreadGlobals(); diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/appinttests.cpp similarity index 100% rename from windows/src/engine/keyman32/tests/keyman32-tests/appinttests.cpp rename to windows/src/engine/keyman32/tests/keyman-engine-tests/appinttests.cpp diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/gtest_main.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/gtest_main.cpp new file mode 100644 index 0000000000..0b5471de2f --- /dev/null +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/gtest_main.cpp @@ -0,0 +1,53 @@ +/** + * @file + * @copyright (c) 2013 Stephan Brenner + * @license This project is released under the MIT License. + * repo: https://github.com/stbrenner/gtest_mem + * Adapted in 2021 from the gtest_mem.cpp + * This file implements a main() function for Google Test that runs all tests + * and detects memory leaks. + */ +#include "pch.h" +#include +#include +#include + +using namespace std; +using namespace testing; + +namespace testing { +class MemoryLeakDetector : public EmptyTestEventListener { +#ifdef _DEBUG +public: + virtual void + OnTestStart(const TestInfo&) { + _CrtMemCheckpoint(&memState_); + } + + virtual void + OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Passed()) { + _CrtMemState stateNow, stateDiff; + _CrtMemCheckpoint(&stateNow); + int diffResult = _CrtMemDifference(&stateDiff, &memState_, &stateNow); + if (diffResult) { + FAIL() << "Memory leak of " << stateDiff.lSizes[1] << " byte(s) detected."; + } + } + } + +private: + _CrtMemState memState_; +#endif // _DEBUG +}; +} // namespace testing + +GTEST_API_ int +main(int argc, char** argv) { + cout << "Running main() from gtest_main.cpp" << endl; + + InitGoogleTest(&argc, argv); + UnitTest::GetInstance()->listeners().Append(new MemoryLeakDetector()); + + return RUN_ALL_TESTS(); +} diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyboardoptionstests.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyboardoptionstests.cpp new file mode 100644 index 0000000000..8bed42b510 --- /dev/null +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyboardoptionstests.cpp @@ -0,0 +1,78 @@ + +#include "pch.h" +#include +#include +#include +#include + +// Test UpdateKeyboardOptionsCore, also uses SaveKeyboardOptionsCore +TEST(KEYBOARDOPTIONS, UpdateKeyboardOptionsCore) { + LPINTKEYBOARDINFO kp = new INTKEYBOARDINFO; + memset(kp, 0, sizeof(INTKEYBOARDINFO)); + + km_kbp_option_item test_env_opts[] = { + {u"__test_point", u"not tiggered", KM_KBP_OPT_KEYBOARD}, {u"hello", u"-", KM_KBP_OPT_ENVIRONMENT}, KM_KBP_OPTIONS_END}; + + km_kbp_path_name dummyPath = L"dummyActions.mock"; + EXPECT_EQ(km_kbp_keyboard_load(dummyPath, &kp->lpCoreKeyboard), KM_KBP_STATUS_OK); + EXPECT_EQ(km_kbp_state_create(kp->lpCoreKeyboard, test_env_opts, &kp->lpCoreKeyboardState), KM_KBP_STATUS_OK); + + kp->lpCoreKeyboardOptions = SaveKeyboardOptionsCore(kp); + // No Change + EXPECT_FALSE(UpdateKeyboardOptionsCore(kp->lpCoreKeyboardState, kp->lpCoreKeyboardOptions)); + std::u16string value = kp->lpCoreKeyboardOptions[0].value; + std::u16string expectedValue = u"not tiggered"; + EXPECT_TRUE(value == expectedValue); + + km_kbp_option_item update_key_opts[] = {{u"__test_point", u"triggered", KM_KBP_OPT_KEYBOARD}, KM_KBP_OPTIONS_END}; + EXPECT_EQ(km_kbp_state_options_update(kp->lpCoreKeyboardState, update_key_opts), KM_KBP_STATUS_OK); + // Change value to triggered + EXPECT_TRUE(UpdateKeyboardOptionsCore(kp->lpCoreKeyboardState, kp->lpCoreKeyboardOptions)); + value = kp->lpCoreKeyboardOptions[0].value; + expectedValue = u"triggered"; + EXPECT_TRUE(value == expectedValue); + DisposeKeyboardOptionsCore(&kp->lpCoreKeyboardOptions); + ReleaseStateMemoryCore(&kp->lpCoreKeyboardState); + ReleaseKeyboardMemoryCore(&kp->lpCoreKeyboard); + delete kp; + +} + +// Test SaveKeyboardOptionsCore and RestoreKeyboardOptionsCORE +TEST(KEYBOARDOPTIONS, SaveRestoreKeyboardOptionsCore) { + + LPINTKEYBOARDINFO kp = new INTKEYBOARDINFO; + memset(kp, 0, sizeof(INTKEYBOARDINFO)); + + km_kbp_option_item test_env_opts[] = { + {u"__test_point", u"not tiggered", KM_KBP_OPT_KEYBOARD}, {u"hello", u"-", KM_KBP_OPT_ENVIRONMENT}, KM_KBP_OPTIONS_END}; + km_kbp_path_name dummyPath = L"dummyActions.mock"; + EXPECT_EQ(km_kbp_keyboard_load(dummyPath, &kp->lpCoreKeyboard), KM_KBP_STATUS_OK); + EXPECT_EQ(km_kbp_state_create(kp->lpCoreKeyboard, test_env_opts, &kp->lpCoreKeyboardState), KM_KBP_STATUS_OK); + + km_kbp_option_item *SavedKBDOptions = SaveKeyboardOptionsCore(kp); + + std::u16string value = SavedKBDOptions[0].value; + std::u16string expectedValue = u"not tiggered"; + km_kbp_option_item update_key_opts[] = {{u"__test_point", u"triggered", KM_KBP_OPT_KEYBOARD}, KM_KBP_OPTIONS_END}; + EXPECT_EQ(km_kbp_state_options_update(kp->lpCoreKeyboardState, update_key_opts), KM_KBP_STATUS_OK); + + km_kbp_option_item *NewKBDOptions = SaveKeyboardOptionsCore(kp); + value = NewKBDOptions[0].value; + expectedValue = u"triggered"; + EXPECT_TRUE(value == expectedValue); + km_kbp_cp const *retValue = nullptr; + EXPECT_TRUE(RestoreKeyboardOptionsCore(kp->lpCoreKeyboardState, SavedKBDOptions)); + EXPECT_EQ(km_kbp_state_option_lookup(kp->lpCoreKeyboardState, KM_KBP_OPT_KEYBOARD, test_env_opts[0].key, &retValue), KM_KBP_STATUS_OK); + + value = retValue; + expectedValue = u"not tiggered"; + EXPECT_TRUE(value == expectedValue); + + DisposeKeyboardOptionsCore(&NewKBDOptions); + DisposeKeyboardOptionsCore(&SavedKBDOptions); + + ReleaseStateMemoryCore(&kp->lpCoreKeyboardState); + ReleaseKeyboardMemoryCore(&kp->lpCoreKeyboard); + delete kp; +} diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj similarity index 88% rename from windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj rename to windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj index 7c41040c93..ba8ae6d7ae 100644 --- a/windows/src/engine/keyman32/tests/keyman32-tests/keyman32-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj @@ -25,6 +25,7 @@ Application v142 Unicode + keyman-engine-tests @@ -33,9 +34,11 @@ - $(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) + $(ProjectDir)..\..\..\..\..\..\common\core\desktop\include;$(ProjectDir)..\..\..\..\..\..\common\core\desktop\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) $(ProjectDir)bin\$(Platform)\$(Configuration)\ $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\..\..\..\..\common\core\desktop\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\..\..\common\core\desktop\build\rust\x86\$(Configuration);$(LibraryPath) + true $(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) @@ -57,6 +60,9 @@ + + + Create Create @@ -71,9 +77,6 @@ {a7069186-5ae7-4504-8926-861b7a34ba9e} - - {bd5564fb-35a5-4a3c-b96a-4a6578e2b593} - @@ -89,11 +92,12 @@ EnableFastChecks MultiThreadedDebug Level3 + %(AdditionalIncludeDirectories) true Console - version.lib;psapi.lib;imm32.lib;%(AdditionalDependencies) + version.lib;psapi.lib;imm32.lib;libkmnkbp0.a;keyman_keyboard_processor_native.lib;%(AdditionalDependencies) diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp new file mode 100644 index 0000000000..3df1d70be3 --- /dev/null +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp @@ -0,0 +1,160 @@ +#include "pch.h" +#include "kmprocessactions.cpp" + +// Test the Process Actions private functions +// TODO: The following actions are not tested KM_KBP_IT_ALERT, KM_KBP_IT_PERSIST_OPT, KM_KBP_IT_EMIT_KEYSTROKE + +// Fixture for kmprocessactons tests +class KMPROCESSACTIONS : public ::testing::Test { +public: + KMPROCESSACTIONS() {} + + void + SetUp() { + Globals_InitProcess(); + } + + void + TearDown() { + UninitialiseProcess(FALSE); + Globals_UninitProcess(); + } + + ~KMPROCESSACTIONS() {} +}; + +// KM_KBP_IT_CHAR - processUnicodeChar +TEST_F(KMPROCESSACTIONS, processUnicodeChartest) { + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR *expectedContext = L"A"; + km_kbp_action_item itemAddChar = { KM_KBP_IT_CHAR, {0,}, {'A'}}; + + processUnicodeChar(&testApp, &itemAddChar); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + km_kbp_usv testSurrogateChar = Uni_SurrogateToUTF32(0xD801, 0xDC37); //๐ท'; + km_kbp_action_item itemAddChar2 = {KM_KBP_IT_CHAR, {0,}, {testSurrogateChar}}; + WCHAR expectedStringSurrogate[] = {'A', 0xD801, 0xDC37, 0}; + processUnicodeChar(&testApp, &itemAddChar2); + contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, &expectedStringSurrogate[0]); +} + +// KM_KBP_IT_MARKER - processMarker Deadkey +TEST_F(KMPROCESSACTIONS, processMarkertest) { + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {UC_SENTINEL, CODE_DEADKEY, 2, 0}; + uintptr_t marker = 2; + km_kbp_action_item itemAddMarker = {KM_KBP_IT_MARKER, {0,}, {marker}}; + + processMarker(&testApp, &itemAddMarker); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); +} + +// KM_KBP_IT_BACK - processBack +// First test processing a backspace for a deadkey +TEST_F(KMPROCESSACTIONS, processBackDeadkeytest) { + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {'A', 0}; + km_kbp_action_item itemAddChar = {KM_KBP_IT_CHAR, {0,}, {'A'}}; + + processUnicodeChar(&testApp, &itemAddChar); + uintptr_t marker = 2; + km_kbp_action_item itemAddMarker = {KM_KBP_IT_MARKER, {0,}, {marker}}; + processMarker(&testApp, &itemAddMarker); + km_kbp_action_item itemBackSpace = {KM_KBP_IT_BACK}; + itemBackSpace.backspace.expected_type = KM_KBP_IT_MARKER; + itemBackSpace.backspace.expected_value = marker; + processBack(&testApp, &itemBackSpace); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); +} + +// KM_KBP_IT_BACK - processBack +// Press Backspace for a normal character +// Also test for Unknown Character +TEST_F(KMPROCESSACTIONS, processBackCharactertest) { + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {'A', 0}; + WCHAR expectedContextFinal[] = {0}; + km_kbp_action_item itemAddChar = {KM_KBP_IT_CHAR, {0,}, {'A'}}; + + processUnicodeChar(&testApp, &itemAddChar); + itemAddChar.character = 'B'; + processUnicodeChar(&testApp, &itemAddChar); + km_kbp_action_item itemBackSpace = {KM_KBP_IT_BACK}; + itemBackSpace.backspace.expected_type = KM_KBP_IT_CHAR; + itemBackSpace.backspace.expected_value = 'B'; + // backspace + processBack(&testApp, &itemBackSpace); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + // backspace for unknown it should backspace a character + itemBackSpace.type = KM_KBP_BT_UNKNOWN; + processBack(&testApp, &itemBackSpace); + contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContextFinal); +} + +// KM_KBP_IT_BACK - processBack +// Press Backspace for a character doesn't match expected character +// Note currently we don't check for a character match this should be updated + +TEST_F(KMPROCESSACTIONS, processBackUnexpectedChartest) { + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {'A', 0}; + km_kbp_action_item itemAddChar = {KM_KBP_IT_CHAR, {0,}, {'A'}}; + + processUnicodeChar(&testApp, &itemAddChar); + itemAddChar.character = 'C'; + processUnicodeChar(&testApp, &itemAddChar); + km_kbp_action_item itemBackSpace = {KM_KBP_IT_BACK}; + itemBackSpace.backspace.expected_type = KM_KBP_IT_CHAR; + itemBackSpace.backspace.expected_value = 'B'; + // backspace + processBack(&testApp, &itemBackSpace); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); +} + +// KM_KBP_IT_INVALIDATE_CONTEXT - processInvalidateContext +TEST_F(KMPROCESSACTIONS, processInvalidateContextTest) { + + WCHAR callbuf[MAXCONTEXT]; + AITIP testApp; + WCHAR expectedContext[] = {0}; + km_kbp_action_item itemAddChar = {KM_KBP_IT_CHAR, {0,}, {'A'}}; + + processUnicodeChar(&testApp, &itemAddChar); + itemAddChar.character = 'B'; + processUnicodeChar(&testApp, &itemAddChar); + + // A keyboard a state is need to test processInvalidateContext + km_kbp_option_item test_env_opts[] = {{u"hello", u"world", KM_KBP_OPT_KEYBOARD}, KM_KBP_OPTIONS_END}; + km_kbp_keyboard *testKB = nullptr; + km_kbp_state *testState = nullptr; + km_kbp_path_name dummyPath = L"dummyActions.mock"; + EXPECT_EQ(km_kbp_keyboard_load(dummyPath, &testKB), KM_KBP_STATUS_OK); + EXPECT_EQ(km_kbp_state_create(testKB, test_env_opts, &testState), KM_KBP_STATUS_OK); + + processInvalidateContext(&testApp,testState); + WCHAR *contextBuf = testApp.ContextBufMax(MAXCONTEXT); + EXPECT_STREQ(contextBuf, expectedContext); + + // dispose keyboard + km_kbp_state_dispose(testState); + km_kbp_keyboard_dispose(testKB); + +} diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/packages.config b/windows/src/engine/keyman32/tests/keyman-engine-tests/packages.config similarity index 100% rename from windows/src/engine/keyman32/tests/keyman32-tests/packages.config rename to windows/src/engine/keyman32/tests/keyman-engine-tests/packages.config diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/pch.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/pch.cpp similarity index 100% rename from windows/src/engine/keyman32/tests/keyman32-tests/pch.cpp rename to windows/src/engine/keyman32/tests/keyman-engine-tests/pch.cpp diff --git a/windows/src/engine/keyman32/tests/keyman32-tests/pch.h b/windows/src/engine/keyman32/tests/keyman-engine-tests/pch.h similarity index 100% rename from windows/src/engine/keyman32/tests/keyman32-tests/pch.h rename to windows/src/engine/keyman32/tests/keyman-engine-tests/pch.h diff --git a/windows/src/engine/keyman64/keyman64.vcxproj b/windows/src/engine/keyman64/keyman64.vcxproj index f1b91af99e..44c5f155b3 100644 --- a/windows/src/engine/keyman64/keyman64.vcxproj +++ b/windows/src/engine/keyman64/keyman64.vcxproj @@ -55,11 +55,11 @@ $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\include;$(IncludePath) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\common\core\desktop\include;$(IncludePath) $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\common\core\desktop\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\include;$(IncludePath) + $(ProjectDir)..\..\..\..\common\core\desktop\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\common\core\desktop\include;$(IncludePath) @@ -96,7 +96,7 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - keyman_keyboard_processor_native.lib;kmnkbp0.lib;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) + libkmnkbp0.a;keyman_keyboard_processor_native.lib;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) true C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories) false @@ -152,11 +152,12 @@ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - keyman_keyboard_processor_native.lib;kmnkbp0.lib;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) + libkmnkbp0.a;keyman_keyboard_processor_native.lib;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies) true - C:\Program Files\Microsoft SDKs\Windows\v7.0A\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories) + C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories) false - %(IgnoreSpecificDefaultLibraries) + + true @@ -243,6 +244,7 @@ $(IntDir)%(Filename)1.xdc + Create Create @@ -307,6 +309,7 @@ + @@ -343,4 +346,4 @@ - \ No newline at end of file + diff --git a/windows/src/engine/keyman64/keyman64.vcxproj.filters b/windows/src/engine/keyman64/keyman64.vcxproj.filters index 6f353e6489..3fa862afc9 100644 --- a/windows/src/engine/keyman64/keyman64.vcxproj.filters +++ b/windows/src/engine/keyman64/keyman64.vcxproj.filters @@ -37,6 +37,7 @@ + @@ -84,6 +85,7 @@ + diff --git a/windows/src/engine/kmcomapi/com/keyman_implementation.pas b/windows/src/engine/kmcomapi/com/keyman_implementation.pas index 74766bad76..fe0c117fa5 100644 --- a/windows/src/engine/kmcomapi/com/keyman_implementation.pas +++ b/windows/src/engine/kmcomapi/com/keyman_implementation.pas @@ -48,7 +48,7 @@ uses keymansysteminfo; type - TKeyman = class(TAutoObject, IKeyman, IIntKeyman, IKeymanBCP47Canonicalization) + TKeyman = class(TAutoObject, IKeyman, IIntKeyman, IKeymanBCP47Canonicalization, IKeymanDefaultLanguage) private FInitialized: Boolean; FContext: TKeymanContext; @@ -84,6 +84,8 @@ type // Reimplement as a special case for this interface function SerializeXML(Flags: TOleEnum; const ImagePath: WideString; out References: OleVariant): WideString; safecall; + { IKeymanDefaultLanguage } + procedure SetDefaultLanguage(const BCP47: WideString; LangID: Integer); safecall; public procedure Initialize; override; destructor Destroy; override; @@ -290,6 +292,12 @@ begin end; end; +procedure TKeyman.SetDefaultLanguage(const BCP47: WideString; LangID: Integer); +begin + FContext.DefaultBCP47 := BCP47; + FContext.DefaultLangID := LangID; +end; + procedure TKeyman.Set_AutoApply(Value: WordBool); begin if not FInitialized then raise Exception.Create(SErrorUninitialised); diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas index 56ec2f90e7..3ceb5b15cd 100644 --- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas +++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas @@ -12,7 +12,7 @@ unit keymanapi_TLB; // ************************************************************************ // // $Rev: 52393 $ -// File generated on 1/02/2021 8:52:41 AM from Type Library described below. +// File generated on 16/09/2021 6:54:44 PM from Type Library described below. // ************************************************************************ // // Type Lib: C:\Projects\keyman\app\windows\src\engine\kmcomapi\kmcomapi (1) @@ -93,6 +93,7 @@ const IID_IKeymanKeyboardLanguageInstalled2: TGUID = '{414C26E6-BFAC-4A70-9EA1-E525BA9BBA7E}'; IID_IKeymanKeyboardLanguagesInstalled2: TGUID = '{628FF2E6-B490-462E-8FC7-7AE53B9D392C}'; CLASS_Keyman: TGUID = '{CF46549D-4D2D-4679-A2E1-23A815F172F8}'; + IID_IKeymanDefaultLanguage: TGUID = '{77BAB934-B7DF-4304-AFA6-B8F6BEC16516}'; // *********************************************************************// // Declaration of Enumerations defined in Type Library @@ -257,6 +258,8 @@ type IKeymanKeyboardLanguageInstalled2Disp = dispinterface; IKeymanKeyboardLanguagesInstalled2 = interface; IKeymanKeyboardLanguagesInstalled2Disp = dispinterface; + IKeymanDefaultLanguage = interface; + IKeymanDefaultLanguageDisp = dispinterface; // *********************************************************************// // Declaration of CoClasses defined in Type Library @@ -1765,6 +1768,26 @@ type out References: OleVariant): WideString; dispid 401; end; +// *********************************************************************// +// Interface: IKeymanDefaultLanguage +// Flags: (4416) Dual OleAutomation Dispatchable +// GUID: {77BAB934-B7DF-4304-AFA6-B8F6BEC16516} +// *********************************************************************// + IKeymanDefaultLanguage = interface(IDispatch) + ['{77BAB934-B7DF-4304-AFA6-B8F6BEC16516}'] + procedure SetDefaultLanguage(const DefaultBCP47: WideString; DefaultLangID: Integer); safecall; + end; + +// *********************************************************************// +// DispIntf: IKeymanDefaultLanguageDisp +// Flags: (4416) Dual OleAutomation Dispatchable +// GUID: {77BAB934-B7DF-4304-AFA6-B8F6BEC16516} +// *********************************************************************// + IKeymanDefaultLanguageDisp = dispinterface + ['{77BAB934-B7DF-4304-AFA6-B8F6BEC16516}'] + procedure SetDefaultLanguage(const DefaultBCP47: WideString; DefaultLangID: Integer); dispid 201; + end; + // *********************************************************************// // The Class CoKeyman provides a Create and CreateRemote method to // create instances of the default interface IKeyman exposed by diff --git a/windows/src/engine/kmcomapi/keymancontext.pas b/windows/src/engine/kmcomapi/keymancontext.pas index 085810eb2d..fdd3e76567 100644 --- a/windows/src/engine/kmcomapi/keymancontext.pas +++ b/windows/src/engine/kmcomapi/keymancontext.pas @@ -32,6 +32,8 @@ type private FKeyman: TObject; FController: TKeymanController; + FDefaultBCP47: string; + FDefaultLangID: Integer; function GetErrors: IIntKeymanErrors; function GetKeyboards: IIntKeymanKeyboardsInstalled; function GetPackages: IIntKeymanPackagesInstalled; @@ -50,6 +52,9 @@ type property SystemInfo: IIntKeymanSystemInfo read GetSystemInfo; property Control: IIntKeymanControl read GetControl; property Controller: TKeymanController read FController; + + property DefaultBCP47: string read FDefaultBCP47 write FDefaultBCP47; + property DefaultLangID: Integer read FDefaultLangID write FDefaultLangID; end; implementation diff --git a/windows/src/engine/kmcomapi/kmcomapi.dproj b/windows/src/engine/kmcomapi/kmcomapi.dproj index cf51be0329..fd9b6c51d7 100644 --- a/windows/src/engine/kmcomapi/kmcomapi.dproj +++ b/windows/src/engine/kmcomapi/kmcomapi.dproj @@ -124,7 +124,7 @@ false false - ..\..\desktop\kmshell\kmshell.exe + ..\..\desktop\kmshell\bin\win32\debug\kmshell.exe CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) -c false diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl index bdee680f06..ac20310a87 100644 --- a/windows/src/engine/kmcomapi/kmcomapi.ridl +++ b/windows/src/engine/kmcomapi/kmcomapi.ridl @@ -6,7 +6,7 @@ // However, when applying changes via the Editor this file will be regenerated // and comments or formatting changes will be lost. // ************************************************************************ // -// File generated on 1/02/2021 8:52:42 AM (- $Rev: 12980 $, 9519671). +// File generated on 16/09/2021 6:54:45 PM (- $Rev: 12980 $, 32940875). [ uuid(F16E2A9A-DA46-4EA3-BFF3-BA46B480C961), @@ -66,6 +66,7 @@ library keymanapi interface IKeymanKeyboardLanguageInstalled2; interface IKeymanKeyboardLanguagesInstalled2; interface IKeymanBCP47Canonicalization; + interface IKeymanDefaultLanguage; [ @@ -1019,6 +1020,17 @@ library keymanapi HRESULT _stdcall GetFullTagList([in] BSTR Tag, [out, retval] VARIANT* Result); }; + [ + uuid(77BAB934-B7DF-4304-AFA6-B8F6BEC16516), + dual, + oleautomation + ] + interface IKeymanDefaultLanguage: IDispatch + { + [id(0x000000C9)] + HRESULT _stdcall SetDefaultLanguage([in] BSTR DefaultBCP47, [in] long DefaultLangID); + }; + [ uuid(CF46549D-4D2D-4679-A2E1-23A815F172F8), version(10.0) diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas index e91cab0eeb..147bb9aef6 100644 --- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas +++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas @@ -326,6 +326,7 @@ var kpil: TKPInstallKeyboardLanguage; ml: TMitigateWin10_1803.TMitigatedLanguage; KeyboardName: string; + FDefaultBCP47: string; type TWSLCallback = reference to procedure(r: TRegistryErrorControlled); @@ -366,7 +367,11 @@ begin ErrorFmt(KMN_E_Install_InvalidFile, VarArrayOf([ExtractFileName(FileName), E.Message])); end; - FDefaultHKL := GetDefaultHKL; + FDefaultHKL := Context.DefaultLangID; + if FDefaultHKL = 0 then + FDefaultHKL := GetDefaultHKL; + + FDefaultBCP47 := Context.DefaultBCP47; KeyboardID := GetShortKeyboardName(FileName); @@ -456,12 +461,6 @@ begin begin FLanguages := GetLanguageCodesFromKeyboard(ki); - // - // Final fallback is to install against default language for system // I4607 - // - if Length(FLanguages) = 0 then - AddLanguage(HKLToLanguageID(FDefaultHKL)); - for i := 0 to High(Flanguages) do if TMitigateWin10_1803.IsMitigationRequired(FLanguages[i], ml) then begin @@ -483,6 +482,19 @@ begin i: Integer; BCP47Tag: string; begin + if Length(FLanguages) = 0 then + begin + // + // Final fallback is to install against default language for system + // + kpil := TKPInstallKeyboardLanguage.Create(Context); + try + kpil.RegisterTip(KeyboardID, FDefaultBCP47, KeyboardName, FDefaultHKL, FIconFileName, ''); //TODO: language name + finally + kpil.Free; + end; + r.WriteString(FDefaultBCP47, ''); // TODO: language name + end; for i := 0 to High(FLanguages) do begin BCP47Tag := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(FLanguages[i]); diff --git a/windows/src/engine/kmtip/kmtip.vcxproj b/windows/src/engine/kmtip/kmtip.vcxproj index e8af27c61a..c19f306a62 100644 --- a/windows/src/engine/kmtip/kmtip.vcxproj +++ b/windows/src/engine/kmtip/kmtip.vcxproj @@ -104,6 +104,7 @@ kmtip64 kmtip64 + NDEBUG;%(PreprocessorDefinitions) diff --git a/windows/src/global/delphi/cust/StockFileNames.pas b/windows/src/global/delphi/cust/StockFileNames.pas index d9b573107e..8a3719e6a7 100644 --- a/windows/src/global/delphi/cust/StockFileNames.pas +++ b/windows/src/global/delphi/cust/StockFileNames.pas @@ -28,11 +28,14 @@ const StockFileName_Menu = 'menu.txt'; // do not localise const StockFileName_AppIcon = 'appicon.ico'; // do not localise const StockFileName_ConfigIcon = 'cfgicon.ico'; // do not localise -const SStockFileNames: array[0..3] of string = ( +const StockFileName_CoreIcon = 'core.ico'; // do not localise + +const SStockFileNames: array[0..4] of string = ( StockFileName_TrayIcon, StockFileName_Menu, StockFileName_AppIcon, - StockFileName_ConfigIcon); + StockFileName_ConfigIcon, + StockFileName_CoreIcon); // Following file names are standard names within a .kmp file diff --git a/windows/src/global/delphi/general/Keyman.System.CanonicalLanguageCodeUtils.pas b/windows/src/global/delphi/general/Keyman.System.CanonicalLanguageCodeUtils.pas index 3d13b07a8a..4da9557054 100644 --- a/windows/src/global/delphi/general/Keyman.System.CanonicalLanguageCodeUtils.pas +++ b/windows/src/global/delphi/general/Keyman.System.CanonicalLanguageCodeUtils.pas @@ -14,6 +14,7 @@ implementation uses BCP47Tag, + GetOsVersion, Keyman.System.LanguageCodeUtils, Keyman.System.Standards.LangTagsRegistry, System.SysUtils; @@ -34,6 +35,12 @@ var t: TBCP47Tag; LangTag: TLangTag; begin + // We do not try and canonicalize language tags on Windows 7, because it does + // not follow the same patterns as for later versions of Windows. For example, + // zh-CN is not canonicalized to zh-Hans-CN + if GetOs = osWin7 then + Exit(Tag); + if Tag = '' then Exit(''); diff --git a/windows/src/global/delphi/general/Keyman.System.Settings.pas b/windows/src/global/delphi/general/Keyman.System.Settings.pas index 2e30cfb540..b22e47056f 100644 --- a/windows/src/global/delphi/general/Keyman.System.Settings.pas +++ b/windows/src/global/delphi/general/Keyman.System.Settings.pas @@ -95,7 +95,7 @@ const ValueType: kstInteger ); - BaseKeymanSettings: array[0..33] of TKeymanSettingBase = ( + BaseKeymanSettings: array[0..34] of TKeymanSettingBase = ( // TIKE:UTikeDebugMode.TikeDebugMode ( @@ -388,6 +388,18 @@ const ValueType: kstInteger ), + ( + ID: 'engine.compatibility.use_keyman_core'; + Name: SRegValue_Flag_UseKeymanCore; + RootKey: HKCU; + Key: SRegKey_KeymanEngineDebug_CU; + Description: 'When set to 1, uses the new Keyman Core common library; '+ + 'if compatibility issues are encountered, set to 0 '+ + 'to use the legacy Keyman for Windows core.'; + DefaultInt: 1; + ValueType: kstInteger + ), + // keyman32:k32_lowlevelkeyboardhook ( ID: 'engine.compatibility.old_cached_hotkey_modifier_state'; diff --git a/windows/src/global/delphi/general/RegistryKeys.pas b/windows/src/global/delphi/general/RegistryKeys.pas index 9bb9731e6c..5500f63611 100644 --- a/windows/src/global/delphi/general/RegistryKeys.pas +++ b/windows/src/global/delphi/general/RegistryKeys.pas @@ -452,6 +452,8 @@ const SRegValue_Flag_UseAutoStartTask = 'Flag_UseAutoStartTask'; SRegValue_Flag_SyncLanguagesToCloud = 'Flag_SyncLanguagesToCloud'; + SRegValue_Flag_UseKeymanCore = 'Flag_UseKeymanCore'; + // Fixed path names const // PF = CSIDL_PROGRAM_FILES diff --git a/windows/src/global/delphi/general/klog.pas b/windows/src/global/delphi/general/klog.pas index 0915696743..2787f8b347 100644 --- a/windows/src/global/delphi/general/klog.pas +++ b/windows/src/global/delphi/general/klog.pas @@ -1,18 +1,18 @@ (* Name: klog Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 1 Aug 2006 Modified Date: 8 Jun 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 01 Aug 2006 - mcdurdin - Disable logging with KLOGGING define 14 Sep 2006 - mcdurdin - Add Current Tick Count to logs (for correlation with system.log) 04 May 2012 - mcdurdin - I3309 - V9.0 - Migrate to Delphi XE2, VS2010, svn 1.7 @@ -23,8 +23,6 @@ unit klog; // I3309 interface -{TNT-IGNORE-UNIT} - {DEFINE KLOGGING} {$IFDEF KLOGGING} @@ -41,6 +39,7 @@ type FLogFile: TextFile; FMethodStack: TStringList; FFileOpen: Boolean; + function VarRecToString(const vr: TVarRec): string; {$ENDIF} public constructor Create; @@ -50,7 +49,8 @@ type procedure LogError(const msg: string); overload; procedure LogError(const msg: string; const fmt: array of const); overload; procedure MethodEnter(instance: TObject; const name: string; const parameters: array of const); - procedure MethodExit(instance: TObject; const name: string); + procedure MethodExit(instance: TObject; const name: string); overload; + procedure MethodExit(instance: TObject; const name: string; const result: array of const); overload; end; function KL: TKLog; @@ -60,8 +60,7 @@ implementation {$IFDEF KLOGGING} uses - Variants, Windows, SysUtils, ErrorControlledRegistry, ErrLogPath, VersionInfo, Unicode; - + Variants, Windows, SysUtils, ErrorControlledRegistry, KeymanPaths, VersionInfo, Unicode; {$ENDIF} var @@ -75,6 +74,7 @@ end; { TKLog } +{$WARN SYMBOL_PLATFORM OFF} constructor TKLog.Create; {$IFDEF KLOGGING} @@ -88,21 +88,21 @@ constructor TKLog.Create; var buf: array[0..260] of char; - FRootPath: string; begin inherited Create; - FRootPath := GetErrLogPath; FMethodStack := TStringList.Create; - GetModuleFileName(hInstance, buf, 260); FAppName := buf; - FLogFileName := FRootPath+ChangeFileExt(ExtractFileName(FAppName), '') + IntToStr(GetCurrentProcessId) + '.log'; + GetModuleFileName(hInstance, buf, 260); FAppName := ChangeFileExt(ExtractFileName(buf),''); + FLogFileName := TKeymanPaths.ErrorLogPath(FAppName); Log(StringOfChar('=', 160)); - Log('Starting application '+FAppName+', version '+GetFileVersionString(FAppName)); + Log('Starting application %s, version %s', [FAppName, GetFileVersionString(FAppName)]); + Log('Command line: %s', [String(CmdLine)]); {$ELSE} begin inherited Create; {$ENDIF} end; +{$WARN SYMBOL_PLATFORM DEFAULT} destructor TKLog.Destroy; {$IFDEF KLOGGING} @@ -163,26 +163,7 @@ begin begin vr := TVarRec(parameters[i]); if i > Low(parameters) then s := s + ', '; - case vr.VType of - vtInteger: s := s + IntToStr(vr.VInteger); - vtBoolean: if vr.VBoolean then s := s + 'True' else s := s + 'False'; - vtChar: s := s + ''''+Char(vr.VChar)+''''; // I3310 - vtExtended: s := s + FloatToStr(vr.VExtended^); - vtString: s := s + ''''+String_AtoU(vr.VString^)+''''; // I3310 // I3310 - vtPointer: s := s + '$'+IntToHex(Integer(vr.VPointer), 8); - vtPChar: s := s + ''''+String_AtoU(vr.VPChar)+''''; // I3310 - vtObject: if vr.VObject = nil then s := s + 'object ' else s := s + 'object '+vr.VObject.ClassName+' [$'+IntToHex(Integer(Pointer(vr.VObject)), 8)+']'; - vtClass: if vr.VClass = nil then s := s + 'class ' else s := s + 'class '+vr.VClass.ClassName; - vtWideChar: s := s + ''''+vr.VWideChar+''''; - vtPWideChar: s := s + ''''+vr.VPWideChar+''''; - vtAnsiString: s := s + ''''+string(vr.VAnsiString)+''''; - vtCurrency: s := s + CurrToStr(vr.VCurrency^); - vtVariant: try s := s + VarToStr(vr.VVariant^); except s := s + ''; end; - vtInterface: s := s + 'interface $'+IntToHex(Integer(vr.VInterface), 8); - vtWideString: s := s + ''''+widestring(vr.VWideString)+''''; - vtInt64: s := s + IntToStr(vr.VInt64^); - vtUnicodeString: s := s + ''''+WideString(vr.VUnicodeString)+''''; // I3309 - end; + s := s + VarRecToString(vr); end; if Assigned(instance) then begin @@ -199,6 +180,26 @@ begin {$ENDIF} end; +procedure TKLog.MethodExit(instance: TObject; const name: string; const result: array of const); +{$IFDEF KLOGGING} +var + s: string; +begin + if Assigned(instance) + then s := instance.ClassName+'.'+name + else s := name; + while (FMethodStack.Count > 0) and (FMethodStack[FMethodStack.Count-1] <> s) do + begin + Log(FMethodStack[FMethodStack.Count-1] + ' not exiting properly!'); + FMethodStack.Delete(FMethodStack.Count-1); + end; + if FMethodStack.Count > 0 then FMethodStack.Delete(FMethodStack.Count-1); + Log(s+'() exit('+VarRecToString(TVarRec(result[0]))+')'); +{$ELSE} +begin +{$ENDIF} +end; + procedure TKLog.MethodExit(instance: TObject; const name: string); {$IFDEF KLOGGING} var @@ -226,6 +227,33 @@ begin {$ENDIF} end; +{$IFDEF KLOGGING} +function TKLog.VarRecToString(const vr: TVarRec): string; +begin + case vr.VType of + vtInteger: Result := IntToStr(vr.VInteger); + vtBoolean: if vr.VBoolean then Result := 'True' else Result := 'False'; + vtChar: Result := ''''+Char(vr.VChar)+''''; // I3310 + vtExtended: Result := FloatToStr(vr.VExtended^); + vtString: Result := ''''+String_AtoU(vr.VString^)+''''; // I3310 // I3310 + vtPointer: Result := '$'+IntToHex(Integer(vr.VPointer), 8); + vtPChar: Result := ''''+String_AtoU(vr.VPChar)+''''; // I3310 + vtObject: if vr.VObject = nil then Result := 'object ' else Result := 'object '+vr.VObject.ClassName+' [$'+IntToHex(Integer(Pointer(vr.VObject)), 8)+']'; + vtClass: if vr.VClass = nil then Result := 'class ' else Result := 'class '+vr.VClass.ClassName; + vtWideChar: Result := ''''+vr.VWideChar+''''; + vtPWideChar: Result := ''''+vr.VPWideChar+''''; + vtAnsiString: Result := ''''+string(vr.VAnsiString)+''''; + vtCurrency: Result := CurrToStr(vr.VCurrency^); + vtVariant: try Result := VarToStr(vr.VVariant^); except Result := ''; end; + vtInterface: Result := 'interface $'+IntToHex(Integer(vr.VInterface), 8); + vtWideString: Result := ''''+widestring(vr.VWideString)+''''; + vtInt64: Result := IntToStr(vr.VInt64^); + vtUnicodeString: Result := ''''+UnicodeString(vr.VUnicodeString)+''''; // I3309 + else Result := '???'; + end; +end; +{$ENDIF} + function KLEnabled: Boolean; begin {$IFDEF KLOGGING} diff --git a/windows/src/global/inc/keymanengine.h b/windows/src/global/inc/keymanengine.h index 9b45029e00..574162049f 100644 --- a/windows/src/global/inc/keymanengine.h +++ b/windows/src/global/inc/keymanengine.h @@ -18,6 +18,15 @@ #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 1 #endif +// For keyboardprocessor_bits.h +#ifndef KMN_KBP_STATIC +#define KMN_KBP_STATIC +#endif +// For keyboardprocessor_bits.h +#ifndef _WIN32 +#define _WIN32 1 +#endif + #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0600 #endif @@ -30,6 +39,7 @@ #include #include #include "compiler.h" +#include /***************************************************************************/ @@ -77,6 +87,9 @@ typedef struct tagINTKEYBOARDINFO LPINTKEYBOARDOPTIONS KeyboardOptions; int nProfiles; LPINTKEYBOARDPROFILE Profiles; + km_kbp_keyboard* lpCoreKeyboard; + km_kbp_option_item* lpCoreKeyboardOptions; + km_kbp_state* lpCoreKeyboardState; } INTKEYBOARDINFO, * LPINTKEYBOARDINFO; typedef struct tagINI @@ -89,14 +102,17 @@ typedef struct tagINI typedef struct tagKMSTATE { BOOL NoMatches; + MSG msg; + // TODO: 5442 will remove these once windows core is deprecated BOOL StopOutput; int LoopTimes; - MSG msg; - WORD vkey; // I934 - WCHAR charCode; // I4582 - BOOL windowunicode; // I4287 + // TODO: 5442 + WORD vkey; // I934 + WCHAR charCode; // I4582 + BOOL windowunicode; // I4287 LPKEYBOARD lpkb; - LPGROUP startgroup; + km_kbp_keyboard* lpCoreKb; // future use with IMDLL + LPGROUP startgroup; // TODO: 5442 will remove this once windows core is deprecated } KMSTATE; // I3616 @@ -111,6 +127,8 @@ LRESULT CALLBACK kmnLowLevelKeyboardProc( // I4124 ); BOOL ReleaseKeyboardMemory(LPKEYBOARD kbd); +BOOL ReleaseStateMemoryCore(km_kbp_state** state); +BOOL ReleaseKeyboardMemoryCore(km_kbp_keyboard** kbd); void PostGETNEXT(HWND hwnd); BOOL CompareMsg(LPMSG MsgA, LPMSG MsgB); @@ -151,6 +169,8 @@ void PostDummyKeyEvent(); // I3301 - Handle I3250 regression with inadvertent m /* Debugging functions */ +BOOL IsDebugAssertEnabled(); + #ifndef SendDebugMessage // I4379 typedef enum ATSDMState { sdmInternat, sdmAIDefault, sdmMessage, sdmKeyboard, sdmGlobal, sdmMenu, sdmDebug, sdmLoad, sdmOther } TSDMState; @@ -165,6 +185,9 @@ extern "C" void _declspec(dllexport) WINAPI Keyman_WriteDebugEvent(char* file, i #define ShouldDebug(state) ShouldDebug_1() #define DebugLastError(context) (DebugLastError_1(GetLastError(), (context), __FILE__,__LINE__,__FUNCTION__)) #define DebugLastError0(error, context) (DebugLastError_1((error), (context), __FILE__,__LINE__,__FUNCTION__)) +// On failed condition log "message", return FALSE +// and assert if a debugger is attached for a debug build. +#define DebugAssert(condition, message) (DebugAssert_1((condition),(message), __FILE__, __LINE__)) int SendDebugMessage_1(HWND hwnd, TSDMState state, int kmn_lineno, char* file, int line, char* msg); int SendDebugMessageFormat_1(HWND hwnd, TSDMState state, int kmn_lineno, char* file, int line, char* fmt, ...); void DebugLastError_1(DWORD err, char* context, char* file, int line, char* func); @@ -173,7 +196,7 @@ void DebugShift(char* function, char* point); BOOL DebugSignalPause(BOOL fIsUp); char* Debug_VirtualKey(WORD vk); char* Debug_UnicodeString(PWSTR s, int x = 0); - +BOOL DebugAssert_1(BOOL condition, char* msg, char* file, int line); BOOL ShouldDebug_1(); // TSDMState state); #endif @@ -232,6 +255,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd); #include "addins.h" #include "keymancontrol.h" #include "keyboardoptions.h" +#include "kmprocessactions.h" #include "syskbd.h" #include "vkscancodes.h" diff --git a/windows/src/global/inc/registry.h b/windows/src/global/inc/registry.h index 5eccd5c0a2..4e2fe9ae35 100644 --- a/windows/src/global/inc/registry.h +++ b/windows/src/global/inc/registry.h @@ -1,18 +1,18 @@ /* Name: registry Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 20 Jun 2006 Modified Date: 28 Mar 2016 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 20 Jun 2006 - mcdurdin - Initial version 01 Aug 2006 - mcdurdin - Add KeymanHotkeys reg 05 Nov 2007 - mcdurdin - I1087 - Add hotkeys to switch languages @@ -44,7 +44,7 @@ #else // this below is same in NT 4 and Win95; others will be different -#define REGSZ_Keyman "software\\keyman\\keyman engine" +#define REGSZ_Keyman "software\\keyman\\keyman engine" #define REGSZ_KeymanDeveloper "software\\keyman\\keyman developer" #define REGSZ_KeymanCU REGSZ_Keyman #define REGSZ_KeymanLM REGSZ_Keyman @@ -83,7 +83,7 @@ //#define REGSZ_SystemNLSLocale "system\\currentcontrolset\\control\\Nls\\Locale" // I4220 - + #define REGSZ_KeymanFile "keyman file" #define REGSZ_KeymanFile_MnemonicOverride "keyman file mnemonic override" // I4169 #define REGSZ_KeymanFile_MnemonicOverride_Deadkey "keyman file mnemonic override deadkey" // I4552 @@ -110,7 +110,7 @@ #define REGSZ_KeyboardHotkeysAreToggle "hotkeys are toggles" #define REGSZ_DeadkeyConversionMode "deadkey conversion mode" // CU // I4552 #define REGSZ_ZapVirtualKeyCode "zap virtual key code" // LM, defaults to 0x0E (_VK_PREFIX_DEFAULT) -/* +/* Debug flags These are all stored in HKCU\Software\Keyman\Debug */ @@ -129,6 +129,9 @@ #define REGSZ_Flag_UseCachedHotkeyModifierState "Flag_UseCachedHotkeyModifierState" +/* REGSZ_Flag_UseKeymanCore DWORD: Turns on the common core - instead of windows core */ +#define REGSZ_Flag_UseKeymanCore "Flag_UseKeymanCore" + /* DWORD: Enable/disable deep TSF integration, default enabled; 0 = disabled, 1 = enabled, 2 = default */ #define REGSZ_DeepTSFIntegration "deep tsf integration" @@ -140,7 +143,8 @@ #define REGSZ_AutomaticallyReportErrors "automatically report errors" // CU, SRegKey_IDEOptions and SRegKey_KeymanEngine_CU #define REGSZ_AutomaticallyReportUsage "automatically report usage" // CU, SRegKey_IDEOptions and SRegKey_KeymanEngine_CU -/* Splitting Registry into ReadOnly and FullAccess makes it much easier to ensure that we are using the registry + +/* Splitting Registry into ReadOnly and FullAccess makes it much easier to ensure that we are using the registry correctly -- readonly wherever possible. */ class RegistryReadOnly @@ -173,7 +177,7 @@ public: class RegistryFullAccess: public RegistryReadOnly { -protected: +protected: HKEY GetKey(LPCSTR AKey); BOOL IntRecursiveDeleteKey(HKEY hkey); diff --git a/windows/src/test/unit-tests/standards-data/GetOsVersion.pas b/windows/src/test/unit-tests/standards-data/GetOsVersion.pas new file mode 100644 index 0000000000..a3068b51cd --- /dev/null +++ b/windows/src/test/unit-tests/standards-data/GetOsVersion.pas @@ -0,0 +1,18 @@ +unit GetOsVersion; + +interface + +// This is a stub version of GetOsVersion for unit tests +type + TOS = (osLegacy, osVista, osWin7, osWin8, osWin10, osOther); + +function GetOs: TOS; + +implementation + +function GetOs: TOS; +begin + Result := osWin10; +end; + +end. diff --git a/windows/src/test/unit-tests/standards-data/standardsdata.dpr b/windows/src/test/unit-tests/standards-data/standardsdata.dpr index 8ed4edae64..ed5fc9ff6e 100644 --- a/windows/src/test/unit-tests/standards-data/standardsdata.dpr +++ b/windows/src/test/unit-tests/standards-data/standardsdata.dpr @@ -22,7 +22,8 @@ uses Keyman.System.Standards.LangTagsRegistry in '..\..\..\global\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', Keyman.System.Standards.LCIDToBCP47Registry in '..\..\..\global\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', utilstr in '..\..\..\global\delphi\general\utilstr.pas', - Unicode in '..\..\..\global\delphi\general\Unicode.pas'; + Unicode in '..\..\..\global\delphi\general\Unicode.pas', + GetOsVersion in 'GetOsVersion.pas'; var runner : ITestRunner; diff --git a/windows/src/test/unit-tests/standards-data/standardsdata.dproj b/windows/src/test/unit-tests/standards-data/standardsdata.dproj index 9b34d0b20e..6da4551ac8 100644 --- a/windows/src/test/unit-tests/standards-data/standardsdata.dproj +++ b/windows/src/test/unit-tests/standards-data/standardsdata.dproj @@ -100,6 +100,7 @@ + Cfg_2 Base @@ -141,7 +142,7 @@ true - + standardsdata.exe true