mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-06 14:07:32 +00:00
chore(developer): Merge branch 'master' into change/developer/touch-layout-reorganize-files
This commit is contained in:
commit
7bdf62f0bf
58 changed files with 889 additions and 439 deletions
31
HISTORY.md
31
HISTORY.md
|
|
@ -1,5 +1,15 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.290 alpha 2026-09-23
|
||||
|
||||
* fix(windows): set basekeyboard as current user not the admin user on an elevated process (#16162)
|
||||
* fix(windows): add non-admin user install with different base keyboard (#16528)
|
||||
|
||||
## 19.0.289 alpha 2026-09-21
|
||||
|
||||
* fix(android): suppress autocorrection of hardware keystroke input (#16606)
|
||||
* change(web): add lexical weighting to prediction search (#16600)
|
||||
|
||||
## 19.0.288 alpha 2026-09-18
|
||||
|
||||
* change(web): remove TokenizationCorrector class only used in epic/boundary-correction (#16581)
|
||||
|
|
@ -1739,6 +1749,27 @@
|
|||
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
|
||||
* chore: increment to alpha 19.0 (#13187)
|
||||
|
||||
## 18.0.252 stable 2026-09-22
|
||||
|
||||
* change(android): use webview asset loader (#16628)
|
||||
* chore: revert "fix(web): retry importScripts NetworkErrors in attempt to mitigate errors loading models" (#16630)
|
||||
|
||||
## 18.0.251 stable 2026-09-21
|
||||
|
||||
* maint(android): update Android Target API to 36 (#16620)
|
||||
*
|
||||
## 18.0.250 stable 2026-09-21
|
||||
|
||||
* fix(linux): fix memory problem (#15828)
|
||||
* chore: update fast-xml-parser to 5.8.0 (#15953)
|
||||
* fix(linux): add dependency on `procps` (#15980)
|
||||
* chore(ios): update first voices distribution certificate (#16007)
|
||||
* chore(linux): release v18 on Ubuntu 26.04 Resolute (#16078)
|
||||
* chore: update multi-labeler to 5.0.0 (#16101)
|
||||
* chore(android): log legacy cloud keyboards (#16243)
|
||||
* fix(web): handle 'unload' message while attempting model 'load' (#16609)
|
||||
* fix(web): retry importScripts NetworkErrors in attempt to mitigate errors loading models (#16604)
|
||||
|
||||
## 18.0.249 stable 2026-03-27
|
||||
|
||||
* chore(linux): Update debian changelog (#15717)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.289
|
||||
19.0.291
|
||||
2
android/.gitignore
vendored
2
android/.gitignore
vendored
|
|
@ -37,10 +37,8 @@ KMEA/**/assets/keymanandroid.js
|
|||
KMEA/**/assets/keyman.js.map
|
||||
KMEA/**/assets/keymanweb-webview.js
|
||||
KMEA/**/assets/keymanweb-webview.js.map
|
||||
KMEA/**/assets/map-polyfill.js
|
||||
KMEA/**/assets/sentry.min.js
|
||||
KMEA/**/assets/keyman-sentry.js
|
||||
KMEA/**/assets/es6-shim.min.js
|
||||
KMEA/**/assets/kmwosk.css
|
||||
KMEA/**/assets/globe-hint.css
|
||||
KMEA/**/assets/resources
|
||||
|
|
|
|||
|
|
@ -380,9 +380,7 @@ public final class KMManager {
|
|||
protected static final String KMFilename_KmwCss = "kmwosk.css";
|
||||
protected static final String KMFilename_KmwGlobeHintCss = "globe-hint.css";
|
||||
protected static final String KMFilename_Osk_Ttf_Font = "keymanweb-osk.ttf";
|
||||
protected static final String KMFilename_JSPolyfill = "es6-shim.min.js";
|
||||
protected static final String KMFilename_JSPolyfill2 = "other-polyfills.js";
|
||||
protected static final String KMFilename_JSPolyfill3 = "map-polyfill.js";
|
||||
|
||||
// Deprecated by KeyboardController.KMFilename_Installed_KeyboardsList
|
||||
public static final String KMFilename_KeyboardsList = "keyboards_list.dat";
|
||||
|
|
|
|||
|
|
@ -55,16 +55,12 @@ do_build() {
|
|||
builder_echo "Copying Keyman Web artifacts"
|
||||
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/keymanweb-webview.js" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/keymanweb-webview.js.map" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/map-polyfill.js" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/web/build/app/resources/osk/ajax-loader.gif" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/web/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/web/build/app/resources/osk/globe-hint.css" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/web/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/"
|
||||
cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js"
|
||||
|
||||
builder_echo "Copying es6-shim polyfill"
|
||||
cp "$KEYMAN_ROOT/node_modules/es6-shim/es6-shim.min.js" "$ENGINE_ASSETS/"
|
||||
|
||||
builder_echo "BUILD_FLAGS $BUILD_FLAGS"
|
||||
|
||||
# Build without test
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ export default function teamcityReporter({ name="Web Test Runner JavaScript test
|
|||
} else {
|
||||
summary.failed++;
|
||||
const message = test.error ? `message='${e(test.error.message)}'` : '';
|
||||
const details = test.error ? `\ndetails='${e(test.error.stack)}\n`: '';
|
||||
const details = test.error ? `details='${e(test.error.stack)}'`: '';
|
||||
|
||||
if(test.error?.actual !== undefined && test.error?.expected !== undefined) {
|
||||
logger.log(`##teamcity[testFailed type='comparisonFailure' name='${e(test.name)}' ${e(message)}] ${e(details)} expected='${e(test.error?.expected)}' actual='${e(test.error?.actual)}']`);
|
||||
logger.log(`##teamcity[testFailed type='comparisonFailure' name='${e(test.name)}' ${message} ${details} expected='${e(test.error?.expected)}' actual='${e(test.error?.actual)}']`);
|
||||
}
|
||||
logger.log(`##teamcity[testFailed name='${e(test.name)}' ${e(message)}] ${e(details)}']`);
|
||||
logger.log(`##teamcity[testFailed name='${e(test.name)}' ${message} ${details}]`);
|
||||
}
|
||||
|
||||
logger.log(`##teamcity[testFinished name='${e(test.name)}' duration='${e(test.duration ?? 0)}']`);
|
||||
|
|
|
|||
|
|
@ -91,6 +91,27 @@ function IsProjectFile(const FileName: string): Boolean;
|
|||
function IsKeyboardFile(const FileName: string): Boolean;
|
||||
function RemoveFileExtension(Filename, Extension: string): string;
|
||||
|
||||
(**
|
||||
* Builds the compiled keyboard filename by inserting the base keyboard ID
|
||||
* before the .kmx extension.
|
||||
*
|
||||
* @param KeyboardFileName Keyboard filename, in the form '[path\]keyboardid[.kmx]'
|
||||
* @param BaseKeyboardIDHex Base keyboard KLID in eight digit hexadecimal form
|
||||
* @return Compiled keyboard filename, in the form '[path\]keyboardid-<KLID>.kmx'
|
||||
*)
|
||||
function BuildKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
|
||||
|
||||
(**
|
||||
* Builds the dead-key compiled keyboard filename by inserting the base
|
||||
* keyboard ID and -d suffix before the .kmx extension.
|
||||
*
|
||||
* @param KeyboardFileName Keyboard filename, in the form '[path]\keyboardid[.kmx]'
|
||||
* @param BaseKeyboardIDHex Base keyboard KLID in eight digit hexadecimal form
|
||||
* @return Dead-key compiled keyboard filename, in the form '[path\]keyboardid-<KLID>-d.kmx'
|
||||
*)
|
||||
function BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
|
||||
|
||||
|
||||
type
|
||||
TKeymanFileTypeInfo = class
|
||||
public
|
||||
|
|
@ -208,4 +229,14 @@ begin
|
|||
SameText(ExtractFileExt(Filename), ExtractFileExt(PackageFile_Welcome)));
|
||||
end;
|
||||
|
||||
function BuildKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
|
||||
begin
|
||||
Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '.kmx';
|
||||
end;
|
||||
|
||||
function BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
|
||||
begin
|
||||
Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx';
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: utilstr
|
||||
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 - Refactor util functions into multiple units
|
||||
23 Aug 2006 - mcdurdin - Add StringToExtString and WideQuotedStr functions
|
||||
14 Sep 2006 - mcdurdin - Add RectToString, StringToRect, use widestrings for some functions
|
||||
|
|
@ -58,8 +58,6 @@ function GetTokenFromCaret(line: string; var selx, sellen: Integer): string;
|
|||
|
||||
function WideQuotedStr(const str: WideString): WideString; deprecated; // I3310
|
||||
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
|
|
@ -78,7 +76,7 @@ begin
|
|||
Result := '';
|
||||
Exit;
|
||||
end;
|
||||
|
||||
|
||||
if s[1] = '"' then
|
||||
begin
|
||||
Delete(s,1,1);
|
||||
|
|
|
|||
|
|
@ -508,8 +508,16 @@ $(function() {
|
|||
|
||||
this.selectLayer = function (val) {
|
||||
let selection = builder.saveSelection();
|
||||
if(val) $('#selLayer').val(val);
|
||||
if(val !== null && val !== undefined) {
|
||||
$('#selLayer').val(val);
|
||||
}
|
||||
builder.lastLayerIndex = $('#selLayer').val();
|
||||
if(builder.lastLayerIndex === null) {
|
||||
// If the layer has just been deleted, go to the first layer
|
||||
builder.lastLayerIndex = 0;
|
||||
builder.lastLayers[builder.lastPlatform] = 0;
|
||||
$('#selLayer').val(builder.lastLayerIndex);
|
||||
}
|
||||
builder.prepareLayer();
|
||||
builder.restoreSelection(selection);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ $(function() {
|
|||
key.sk.forEach(k => fixup(k));
|
||||
}
|
||||
if (key.flick) {
|
||||
key.flick.forEach(k => fixup(k));
|
||||
Object.keys(key.flick).forEach(k => fixup(key.flick[k]));
|
||||
}
|
||||
if (key.multitap) {
|
||||
key.multitap.forEach(k => fixup(k));
|
||||
|
|
|
|||
23
package-lock.json
generated
23
package-lock.json
generated
|
|
@ -2844,23 +2844,6 @@
|
|||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/agent/node_modules/debug": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/agent/node_modules/https-proxy-agent": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
|
||||
|
|
@ -7607,10 +7590,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-shim": {
|
||||
"version": "0.35.6",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
|
||||
|
|
@ -15286,7 +15265,6 @@
|
|||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/models-templates": "*",
|
||||
"@keymanapp/models-wordbreakers": "*",
|
||||
"es6-shim": "^0.35.5",
|
||||
"string.prototype.codepointat": "^0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -15302,7 +15280,6 @@
|
|||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/models-templates": "*",
|
||||
"@keymanapp/models-wordbreakers": "*",
|
||||
"es6-shim": "^0.35.5",
|
||||
"string.prototype.codepointat": "^0.2.1",
|
||||
"string.prototype.startswith": "^0.2.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -83,8 +83,6 @@ compile_and_copy() {
|
|||
"$script" "$sourcemap" --clean
|
||||
done
|
||||
|
||||
node map-polyfill-bundler.js
|
||||
|
||||
# For dependent test pages.
|
||||
builder_launch /web/src/test/manual/embed/android-harness/build.sh configure,build
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Note: while this file is not meant to exist long-term, it provides a nice
|
||||
* low-level proof-of-concept for esbuild bundling of the various Web submodules.
|
||||
*
|
||||
* Add some extra code at the end of src/index.ts and run it to verify successful bundling!
|
||||
*/
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import { iifeConfiguration, prepareTslibTreeshaking } from '../../../../common/tools/es-bundling/build/index.mjs';
|
||||
|
||||
const commonConfig = {
|
||||
...iifeConfiguration,
|
||||
entryPoints: {
|
||||
'index': '../../../build/app/webview/obj/polyfill/map.js',
|
||||
},
|
||||
outfile: '../../../build/app/webview/debug/map-polyfill.js',
|
||||
// `esbuild`'s sourcemap output puts relative paths to the original sources from the
|
||||
// directory of the build output. The following keeps repo structure intact and
|
||||
// puts our code under a common 'namespace' of sorts.
|
||||
sourceRoot: '@keymanapp/keyman/web/build/app/webview/debug/'
|
||||
};
|
||||
|
||||
await prepareTslibTreeshaking(commonConfig, /worker-main\.wrapped(?:\.min)?\.js/);
|
||||
|
||||
await esbuild.build(commonConfig);
|
||||
|
||||
await esbuild.build({
|
||||
...commonConfig,
|
||||
minify: true,
|
||||
outfile: '../../../build/app/webview/release/map-polyfill.js',
|
||||
// `esbuild`'s sourcemap output puts relative paths to the original sources from the
|
||||
// directory of the build output. The following keeps repo structure intact and
|
||||
// puts our code under a common 'namespace' of sorts.
|
||||
sourceRoot: '@keymanapp/keyman/web/build/app/webview/release/'
|
||||
});
|
||||
|
|
@ -39,6 +39,8 @@ export class PassthroughKeyboard extends HardKeyboardBase {
|
|||
isSynthetic: false // is not an OSK keystroke.
|
||||
});
|
||||
|
||||
Lkc.suppressAutocorrection = true;
|
||||
|
||||
// 'us' is our default base layout; our distributed engine doesn't support
|
||||
// changing the assumed base keyboard for mnemonics.
|
||||
Lkc = processForMnemonicsAndLegacy(Lkc, this.activeKeyboard, 'us');
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// A separate build-product needed to support KMW when running in legacy ES5 mode for
|
||||
// non-updated Chrome WebViews (like with Android API 21 / 5.0)
|
||||
// @ts-ignore
|
||||
import { default as Map } from "core-js/stable/map";
|
||||
// @ts-ignore
|
||||
import { default as Symbol } from "core-js/stable/symbol";
|
||||
|
||||
// @ts-ignore
|
||||
Window['Symbol'] = Symbol;
|
||||
// @ts-ignore
|
||||
Window['Map'] = Map;
|
||||
|
|
@ -47,10 +47,9 @@
|
|||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/models-templates": "*",
|
||||
"@keymanapp/models-wordbreakers": "*",
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"es6-shim": "^0.35.5",
|
||||
"string.prototype.codepointat": "^0.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@
|
|||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/models-templates": "*",
|
||||
"@keymanapp/models-wordbreakers": "*",
|
||||
"es6-shim": "^0.35.5",
|
||||
"string.prototype.codepointat": "^0.2.1",
|
||||
"string.prototype.startswith": "^0.2.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,16 @@ export interface CorrectionResultMapping<ResultType> {
|
|||
readonly matchedResult: Readonly<ResultType>;
|
||||
|
||||
/**
|
||||
* Gets the "total cost" of the edge, which should be considered as the
|
||||
* Gets the "correction cost" of the edge, which should be considered as the
|
||||
* negative log-likelihood of the input path taken to reach the node
|
||||
* multiplied by the 'probability' induced by needed Damerau-Levenshtein edits
|
||||
* to the resulting output.
|
||||
*/
|
||||
readonly totalCost: number;
|
||||
readonly correctionCost: number;
|
||||
|
||||
/**
|
||||
* The "total cost" of the edge - comprised of both the correction cost and the
|
||||
* prediction cost based on the model's frequency data for the word.
|
||||
*/
|
||||
readonly currentCost: number;
|
||||
}
|
||||
|
|
@ -29,10 +29,14 @@ type CompleteSearchPath<MappingType> = {
|
|||
|
||||
export type PathResult<MappingType> = NullPath | IntermediateSearchPath | CompleteSearchPath<MappingType>;
|
||||
|
||||
export function CORRECTION_QUEUE_COMPARATOR<T extends {currentCost: number}>(a: T, b: T) {
|
||||
export function PREDICTION_QUEUE_COMPARATOR<T extends {currentCost: number}>(a: T, b: T) {
|
||||
return a.currentCost - b.currentCost;
|
||||
}
|
||||
|
||||
export function CORRECTION_QUEUE_COMPARATOR<T extends {correctionCost: number}>(a: T, b: T) {
|
||||
return a.correctionCost - b.correctionCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents objects that support correction search via the `getBestMatches`
|
||||
* method, providing metadata relative to optimizing the search process for
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { PriorityQueue } from 'keyman/common/web-utils';
|
|||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
import { ClassicalDistanceCalculation } from './classical-calculation.js';
|
||||
import { CORRECTION_QUEUE_COMPARATOR, CorrectionSearchable } from './correction-searchable.js';
|
||||
import { PREDICTION_QUEUE_COMPARATOR, CorrectionSearchable } from './correction-searchable.js';
|
||||
import { CorrectionResultMapping } from './correction-result-mapping.js';
|
||||
import { ExecutionTimer, STANDARD_TIME_BETWEEN_DEFERS } from './execution-timer.js';
|
||||
import { SearchQuotientNode } from './search-quotient-node.js';
|
||||
|
|
@ -262,7 +262,7 @@ export class SearchNode {
|
|||
* The correction search evaluates Nodes in cost-ascending order based on this property's
|
||||
* return value.
|
||||
*/
|
||||
get currentCost(): number {
|
||||
get correctionCost(): number {
|
||||
// - We reintrepret 'known cost' as a psuedo-probability.
|
||||
// - Noting that 1/e = 0.367879441, an edit-distance cost of 1 may be intepreted as -ln(1/e) - a log-space 'likelihood'.
|
||||
// - Not exactly normalized, though.
|
||||
|
|
@ -278,6 +278,14 @@ export class SearchNode {
|
|||
return EDIT_DISTANCE_COST_SCALE * this.editCount + this.inputSamplingCost;
|
||||
}
|
||||
|
||||
get predictionCost(): number {
|
||||
return -Math.log(this.currentTraversal.p);
|
||||
}
|
||||
|
||||
get currentCost(): number {
|
||||
return this.correctionCost + this.predictionCost;
|
||||
}
|
||||
|
||||
addEdit() {
|
||||
this.addedEditCost++;
|
||||
}
|
||||
|
|
@ -625,7 +633,7 @@ export async function *getBestMatches<
|
|||
// If no filter function is provided, default to one that always returns true.
|
||||
filter ??= () => true;
|
||||
|
||||
let spaceQueue = new PriorityQueue<Correctable>(CORRECTION_QUEUE_COMPARATOR);
|
||||
let spaceQueue = new PriorityQueue<Correctable>(PREDICTION_QUEUE_COMPARATOR);
|
||||
|
||||
// Stage 1 - if we already have extracted results, build a queue just for them
|
||||
// and iterate over it first.
|
||||
|
|
@ -633,7 +641,7 @@ export async function *getBestMatches<
|
|||
// Does not get any results that another iterator pulls up after this is
|
||||
// created - and those results won't come up later in stage 2, either. Only
|
||||
// intended for restarting a search, not searching twice in parallel.
|
||||
const priorResultsQueue = new PriorityQueue<ResultMapping>((a, b) => a.totalCost - b.totalCost);
|
||||
const priorResultsQueue = new PriorityQueue<ResultMapping>(PREDICTION_QUEUE_COMPARATOR);
|
||||
priorResultsQueue.enqueueAll(searchModules.map((space) => space.previousResults).flat());
|
||||
|
||||
// With potential prior results re-queued, NOW enqueue. (Not before - the heap may reheapify!)
|
||||
|
|
@ -642,7 +650,7 @@ export async function *getBestMatches<
|
|||
// Stage 2: the fun part; actually searching!
|
||||
do {
|
||||
const entry: ResultMapping = timer.time(() => {
|
||||
if((priorResultsQueue.peek()?.totalCost ?? Number.POSITIVE_INFINITY) <= spaceQueue.peek().currentCost) {
|
||||
if((priorResultsQueue.peek()?.currentCost ?? Number.POSITIVE_INFINITY) <= spaceQueue.peek().currentCost) {
|
||||
const result = priorResultsQueue.dequeue();
|
||||
|
||||
// There's no guarantee that the filter closure is the same instance as
|
||||
|
|
@ -669,7 +677,7 @@ export async function *getBestMatches<
|
|||
let lowestCostSource = spaceQueue.dequeue();
|
||||
const newResult = lowestCostSource.handleNextNode();
|
||||
spaceQueue.enqueue(lowestCostSource);
|
||||
spaceQueue = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR, spaceQueue.toArray());
|
||||
spaceQueue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR, spaceQueue.toArray());
|
||||
|
||||
if(newResult.type == 'none') {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { PriorityQueue } from 'keyman/common/web-utils';
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
import { CORRECTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { PREDICTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { SearchQuotientNode } from './search-quotient-node.js';
|
||||
import { SearchQuotientRoot } from './search-quotient-root.js';
|
||||
import { SearchNode } from './distance-modeler.js';
|
||||
|
|
@ -10,7 +10,7 @@ import LexicalModel = LexicalModelTypes.LexicalModel;
|
|||
import { TokenResultMapping } from './token-result-mapping.js';
|
||||
|
||||
export class LegacyQuotientRoot extends SearchQuotientRoot {
|
||||
private selectionQueue: PriorityQueue<SearchNode> = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
|
||||
private selectionQueue: PriorityQueue<SearchNode> = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR);
|
||||
private processed: SearchNode[] = [];
|
||||
|
||||
constructor(model: LexicalModel) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
import { KMWString, PriorityQueue } from 'keyman/common/web-utils';
|
||||
|
||||
import { CORRECTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { PREDICTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { SearchNode } from './distance-modeler.js';
|
||||
import { SearchQuotientNode, PathInputProperties } from './search-quotient-node.js';
|
||||
import { SearchQuotientSpur } from './search-quotient-spur.js';
|
||||
|
|
@ -24,7 +24,7 @@ import Transform = LexicalModelTypes.Transform;
|
|||
// The set of search spaces corresponding to the same 'context' for search.
|
||||
// Whenever a wordbreak boundary is crossed, a new instance should be made.
|
||||
export class LegacyQuotientSpur extends SearchQuotientSpur {
|
||||
private transposeQueue: PriorityQueue<SearchNode> = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
|
||||
private transposeQueue: PriorityQueue<SearchNode> = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR);
|
||||
private incomingTransposeRootNodes: TokenResultMapping[] = [];
|
||||
|
||||
public readonly insertLength: number;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
import { PriorityQueue } from 'keyman/common/web-utils';
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
import { CORRECTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { PREDICTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { LegacyQuotientRoot } from './legacy-quotient-root.js';
|
||||
import { generateSpaceSeed, InputSegment, SearchQuotientNode } from './search-quotient-node.js';
|
||||
import { SearchQuotientSpur } from './search-quotient-spur.js';
|
||||
|
|
@ -20,7 +20,7 @@ import { TokenResultMapping } from './token-result-mapping.js';
|
|||
// The set of search spaces corresponding to the same 'context' for search.
|
||||
// Whenever a wordbreak boundary is crossed, a new instance should be made.
|
||||
export class SearchQuotientCluster extends SearchQuotientNode {
|
||||
private selectionQueue: PriorityQueue<SearchQuotientNode> = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
|
||||
private selectionQueue: PriorityQueue<SearchQuotientNode> = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR);
|
||||
readonly spaceId: number;
|
||||
|
||||
// We use an array and not a PriorityQueue b/c batch-heapifying at a single
|
||||
|
|
@ -102,7 +102,7 @@ export class SearchQuotientCluster extends SearchQuotientNode {
|
|||
entries.forEach((path) => path.increaseMaxEditDistance());
|
||||
|
||||
// Since we just modified the stored instances, and the costs may have shifted, we need to re-heapify.
|
||||
this.selectionQueue = new PriorityQueue<SearchQuotientNode>(CORRECTION_QUEUE_COMPARATOR, entries.slice());
|
||||
this.selectionQueue = new PriorityQueue<SearchQuotientNode>(PREDICTION_QUEUE_COMPARATOR, entries.slice());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -131,7 +131,7 @@ export class SearchQuotientCluster extends SearchQuotientNode {
|
|||
const bestPath = this.selectionQueue.dequeue();
|
||||
const baseResult = bestPath.handleNextNode();
|
||||
this.selectionQueue.enqueue(bestPath);
|
||||
this.selectionQueue = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR, this.selectionQueue.toArray());
|
||||
this.selectionQueue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR, this.selectionQueue.toArray());
|
||||
|
||||
let finalResult = baseResult;
|
||||
if(baseResult.type == 'complete') {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { KMWString, PriorityQueue } from 'keyman/common/web-utils';
|
|||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
import { buildMergedTransform } from '@keymanapp/models-templates';
|
||||
|
||||
import { CORRECTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { PREDICTION_QUEUE_COMPARATOR, PathResult } from './correction-searchable.js';
|
||||
import { EDIT_DISTANCE_COST_SCALE, SearchNode } from './distance-modeler.js';
|
||||
import { generateSpaceSeed, InputSegment, PathInputProperties, SearchQuotientNode } from './search-quotient-node.js';
|
||||
import { generateSubsetId } from './tokenization-subsets.js';
|
||||
|
|
@ -34,7 +34,7 @@ export const MAX_EDIT_THRESHOLD_FACTOR = 2.5;
|
|||
// The set of search spaces corresponding to the same 'context' for search.
|
||||
// Whenever a wordbreak boundary is crossed, a new instance should be made.
|
||||
export abstract class SearchQuotientSpur extends SearchQuotientNode {
|
||||
private selectionQueue: PriorityQueue<SearchNode> = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
|
||||
private selectionQueue: PriorityQueue<SearchNode> = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR);
|
||||
|
||||
/**
|
||||
* Holds all incoming Nodes generated from a parent `SearchSpace` that have not yet been
|
||||
|
|
@ -152,7 +152,7 @@ export abstract class SearchQuotientSpur extends SearchQuotientNode {
|
|||
entries.forEach(function(edge) { edge.calculation = edge.calculation.increaseMaxDistance(); });
|
||||
|
||||
// Since we just modified the stored instances, and the costs may have shifted, we need to re-heapify.
|
||||
this.selectionQueue = new PriorityQueue<SearchNode>(CORRECTION_QUEUE_COMPARATOR, entries);
|
||||
this.selectionQueue = new PriorityQueue<SearchNode>(PREDICTION_QUEUE_COMPARATOR, entries);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -419,7 +419,7 @@ export abstract class SearchQuotientSpur extends SearchQuotientNode {
|
|||
// Allows a little 'wiggle room' + 2 "hard" edits.
|
||||
// Can be important if needed characters don't actually exist on the keyboard
|
||||
// ... or even just not the then-current layer of the keyboard.
|
||||
if(currentNode.currentCost > this.lowestPossibleSingleCost + MAX_EDIT_THRESHOLD_FACTOR * EDIT_DISTANCE_COST_SCALE) {
|
||||
if(currentNode.correctionCost > this.lowestPossibleSingleCost + MAX_EDIT_THRESHOLD_FACTOR * EDIT_DISTANCE_COST_SCALE) {
|
||||
return unmatchedResult;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ export function initTokenResultFilterer() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if((priorReturnCosts.get(searchResult.matchString) ?? Number.MAX_VALUE) > searchResult.totalCost) {
|
||||
priorReturnCosts.set(searchResult.matchString, searchResult.totalCost);
|
||||
if((priorReturnCosts.get(searchResult.matchString) ?? Number.MAX_VALUE) > searchResult.correctionCost) {
|
||||
priorReturnCosts.set(searchResult.matchString, searchResult.correctionCost);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
|
@ -118,6 +118,14 @@ export class TokenResultMapping implements CorrectionResultMapping<SearchNode> {
|
|||
* multiplied by the 'probability' induced by needed Damerau-Levenshtein edits
|
||||
* to the resulting output.
|
||||
*/
|
||||
get correctionCost(): number {
|
||||
return this.node.correctionCost;
|
||||
}
|
||||
|
||||
get currentCost(): number {
|
||||
return this.node.currentCost;
|
||||
}
|
||||
|
||||
get totalCost(): number {
|
||||
return this.node.currentCost;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -532,7 +532,7 @@ export function buildAndMapPredictions(
|
|||
transition: ContextTransition,
|
||||
tokenization: ContextTokenization,
|
||||
// Originally, Readonly<TokenResultMapping> - but we only need these three components here.
|
||||
match: Readonly<{matchString: string, totalCost: number, editCount: number}>,
|
||||
match: Readonly<{matchString: string, correctionCost: number, editCount: number}>,
|
||||
costFactor: number
|
||||
): CorrectionPredictionTuple[] {
|
||||
const model = transition.final.model;
|
||||
|
|
@ -545,7 +545,7 @@ export function buildAndMapPredictions(
|
|||
|
||||
// --- to move into predictFromCorrections ---
|
||||
let correction = match.matchString;
|
||||
let rootCost = match.totalCost;
|
||||
let rootCost = match.correctionCost;
|
||||
|
||||
// Replace the existing context with the correction.
|
||||
const correctionTransform: Transform = {
|
||||
|
|
@ -656,7 +656,7 @@ export async function correctAndEnumerate(
|
|||
|
||||
// Only run the correction search when corrections are enabled.
|
||||
let rawPredictions: CorrectionPredictionTuple[] = [];
|
||||
let bestCorrectionCost: number;
|
||||
let bestTotalCost: number;
|
||||
const correctionPredictionMap: Record<string, Distribution<Suggestion>> = {};
|
||||
for await(const match of getBestTokenMatches(searchModules, timer)) {
|
||||
// Corrections obtained: now to predict from them!
|
||||
|
|
@ -700,8 +700,8 @@ export async function correctAndEnumerate(
|
|||
const predictions = buildAndMapPredictions(transition, tokenization, match, costFactor);
|
||||
|
||||
// Only set 'best correction' cost when a correction ACTUALLY YIELDS predictions.
|
||||
if(predictions.length > 0 && bestCorrectionCost === undefined) {
|
||||
bestCorrectionCost = match.totalCost * costFactor;
|
||||
if(predictions.length > 0 && bestTotalCost === undefined) {
|
||||
bestTotalCost = match.totalCost * costFactor;
|
||||
}
|
||||
|
||||
// If we're getting the same prediction again, it's lower-cost. Update!
|
||||
|
|
@ -714,7 +714,7 @@ export async function correctAndEnumerate(
|
|||
|
||||
rawPredictions = rawPredictions.concat(predictions);
|
||||
|
||||
if(shouldStopSearchingEarly(bestCorrectionCost, match.totalCost, rawPredictions)) {
|
||||
if(shouldStopSearchingEarly(bestTotalCost, match.totalCost, rawPredictions)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -738,20 +738,15 @@ export function shouldStopSearchingEarly(
|
|||
return true;
|
||||
// If enough have been found, we're safe to terminate earlier.
|
||||
} else if(rawPredictions.length >= ModelCompositor.MAX_SUGGESTIONS) {
|
||||
if(currentCorrectionCost >= bestCorrectionCost + CORRECTION_SEARCH_THRESHOLDS.REPLACEMENT_SEARCH_THRESHOLD) {
|
||||
// Very useful for stopping 'sooner' when words reach a sufficient length.
|
||||
return true;
|
||||
} else {
|
||||
// Sort the prediction list; we need them in descending probability order
|
||||
// for the next check.
|
||||
rawPredictions.sort((a, b) => b.totalProb - a.totalProb);
|
||||
// Sort the prediction list; we need them in descending probability order
|
||||
// for the next check.
|
||||
rawPredictions.sort((a, b) => b.totalProb - a.totalProb);
|
||||
|
||||
// If the best result at the current state of the search fails to beat the worst
|
||||
// pending suggestion from previous tiers, assume all further corrections will
|
||||
// similarly fail to win; terminate the search-loop.
|
||||
if(rawPredictions[ModelCompositor.MAX_SUGGESTIONS-1].totalProb > Math.exp(-currentCorrectionCost)) {
|
||||
return true;
|
||||
}
|
||||
// If the best result at the current state of the search fails to beat the worst
|
||||
// pending suggestion from previous tiers, assume all further corrections will
|
||||
// similarly fail to win; terminate the search-loop.
|
||||
if(rawPredictions[ModelCompositor.MAX_SUGGESTIONS-1].totalProb > Math.exp(-currentCorrectionCost)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,11 @@ export class KeyEvent implements KeyEventSpec {
|
|||
*/
|
||||
isSynthetic: boolean = true;
|
||||
|
||||
/**
|
||||
* Set to `true` to prevent autocorrect from activating from this keystroke.
|
||||
*/
|
||||
suppressAutocorrection: boolean = false;
|
||||
|
||||
public constructor(keyEventSpec: KeyEventSpec) {
|
||||
for(const key in keyEventSpec) {
|
||||
// @ts-ignore
|
||||
|
|
|
|||
|
|
@ -311,9 +311,8 @@ export class ActiveKeyBase {
|
|||
|
||||
// And now for generalized type validation. -----------------------------------------
|
||||
|
||||
// WARNING: Object.values and Object.entries is NOT polyfilled by es6-shim and thus
|
||||
// is NOT available within the Android app in extremely early APIs.
|
||||
// Object.entries requires Android 54.
|
||||
// WARNING: Object.values and Object.entries is NOT available within the
|
||||
// Android app in extremely early APIs. Object.entries requires Android 54.
|
||||
|
||||
for(const key of Object.keys(KeyTypesOfKeyMap)) {
|
||||
const value = KeyTypesOfKeyMap[key as keyof typeof KeyTypesOfKeyMap];
|
||||
|
|
|
|||
|
|
@ -334,6 +334,9 @@ export class InputProcessor {
|
|||
}
|
||||
|
||||
const keyEvent = processorAction.transcription.keystroke;
|
||||
if(keyEvent.suppressAutocorrection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ...and is this immediately after a Suggestion with an appended Transform was applied?
|
||||
// (If not, don't consider reverting an appended transform.)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { PriorityQueue } from 'keyman/common/web-utils';
|
|||
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
|
||||
import { CORRECTION_QUEUE_COMPARATOR, models, SearchNode } from '@keymanapp/lm-worker/test-index';
|
||||
import { CORRECTION_QUEUE_COMPARATOR, models, PREDICTION_QUEUE_COMPARATOR, SearchNode } from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import SENTINEL_CODE_UNIT = models.SENTINEL_CODE_UNIT;
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
|
|
@ -50,15 +50,6 @@ function edgeHasChars(edge: SearchNode, input: string, match: string) {
|
|||
return lastEntry(edge.calculation.matchSequence) == match;
|
||||
}
|
||||
|
||||
function findEdgesWithChars(edgeArray: SearchNode[], match: string) {
|
||||
let results = edgeArray.filter(function(value) {
|
||||
return lastEntry(value.calculation.matchSequence) == match;
|
||||
});
|
||||
|
||||
assert.isAtLeast(results.length, 1);
|
||||
return results;
|
||||
}
|
||||
|
||||
function fetchCommonTENode() {
|
||||
const rootSeed = SEARCH_EDGE_SEED++;
|
||||
const rootNode = new SearchNode(testModel.traverseFromRoot(), rootSeed, toKey);
|
||||
|
|
@ -130,7 +121,9 @@ describe('Correction Distance Modeler', () => {
|
|||
|
||||
assert.equal(rootNode.editCount, 0);
|
||||
assert.equal(rootNode.inputSamplingCost, 0);
|
||||
assert.equal(rootNode.currentCost, 0);
|
||||
assert.equal(rootNode.correctionCost, 0);
|
||||
assert.isAbove(rootNode.predictionCost, 0);
|
||||
assert.isAbove(rootNode.currentCost, 0);
|
||||
|
||||
assert.equal((rootNode.currentTraversal as TrieTraversal).prefix, '');
|
||||
assert.isFalse(rootNode.hasPartialInput);
|
||||
|
|
@ -151,7 +144,7 @@ describe('Correction Distance Modeler', () => {
|
|||
|
||||
assert.equal(clonedNode.editCount, 0);
|
||||
assert.equal(clonedNode.inputSamplingCost, 0);
|
||||
assert.equal(clonedNode.currentCost, 0);
|
||||
assert.equal(clonedNode.correctionCost, 0);
|
||||
|
||||
assert.equal((clonedNode.currentTraversal as TrieTraversal).prefix, '');
|
||||
assert.isFalse(clonedNode.hasPartialInput);
|
||||
|
|
@ -167,6 +160,9 @@ describe('Correction Distance Modeler', () => {
|
|||
// Verify aliasing for properties holding immutable objects
|
||||
assert.equal(clonedNode.calculation, originalNode.calculation);
|
||||
assert.equal(clonedNode.currentTraversal, originalNode.currentTraversal);
|
||||
|
||||
// Verify local values are properly copied.
|
||||
assert.equal(clonedNode.currentCost, originalNode.currentCost);
|
||||
});
|
||||
|
||||
it('properly deep-copies fully-processed nodes later in the search path', () => {
|
||||
|
|
@ -222,12 +218,12 @@ describe('Correction Distance Modeler', () => {
|
|||
|
||||
// *****
|
||||
|
||||
function assertSourceNodeProps(node: SearchNode) {
|
||||
function assertExpectedNodeProps(node: SearchNode) {
|
||||
assert.equal(node.resultKey, 'te');
|
||||
|
||||
assert.equal(node.editCount, 0);
|
||||
assert.equal(node.inputSamplingCost, -Math.log(firstLayerTransforms[0].p) - Math.log(secondLayerTransforms[0].p));
|
||||
assert.equal(node.currentCost, node.inputSamplingCost);
|
||||
assert.equal(node.correctionCost, node.inputSamplingCost);
|
||||
|
||||
assert.isFalse(node.hasPartialInput);
|
||||
assert.isFalse(node.isFullReplacement)
|
||||
|
|
@ -238,12 +234,12 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(node.spaceId, secondSpaceId);
|
||||
}
|
||||
|
||||
assertSourceNodeProps(teNode);
|
||||
assertExpectedNodeProps(teNode);
|
||||
|
||||
const clonedNode = new SearchNode(teNode);
|
||||
|
||||
// Root node properties; may as well re-assert 'em.
|
||||
assertSourceNodeProps(clonedNode);
|
||||
assertExpectedNodeProps(clonedNode);
|
||||
|
||||
// Avoid aliasing for properties holding mutable objects
|
||||
assert.notEqual(clonedNode.priorInput, teNode.priorInput);
|
||||
|
|
@ -308,12 +304,12 @@ describe('Correction Distance Modeler', () => {
|
|||
|
||||
// *****
|
||||
|
||||
function assertSourceNodeProps(node: SearchNode) {
|
||||
function assertExpectedNodeProbs(node: SearchNode) {
|
||||
assert.equal(node.resultKey, 'te');
|
||||
|
||||
assert.equal(node.editCount, 0);
|
||||
assert.equal(node.inputSamplingCost, -Math.log(firstLayerTransforms[0].p) - Math.log(secondLayerTransforms[0].p));
|
||||
assert.equal(node.currentCost, node.inputSamplingCost);
|
||||
assert.equal(node.correctionCost, node.inputSamplingCost);
|
||||
|
||||
assert.isTrue(node.hasPartialInput);
|
||||
assert.isFalse(node.isFullReplacement)
|
||||
|
|
@ -324,12 +320,12 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(node.spaceId, secondLayerId);
|
||||
}
|
||||
|
||||
assertSourceNodeProps(teNode);
|
||||
assertExpectedNodeProbs(teNode);
|
||||
|
||||
const clonedNode = new SearchNode(teNode);
|
||||
|
||||
// Root node properties; may as well re-assert 'em.
|
||||
assertSourceNodeProps(clonedNode);
|
||||
assertExpectedNodeProbs(clonedNode);
|
||||
|
||||
// Avoid aliasing for properties holding mutable objects
|
||||
assert.notEqual(clonedNode.priorInput, teNode.priorInput);
|
||||
|
|
@ -577,7 +573,7 @@ describe('Correction Distance Modeler', () => {
|
|||
// Allow a little value wiggle due to double-precision limitations.
|
||||
assert.approximately(subsetNodes[i].inputSamplingCost, expectedCosts[i], 1e-8);
|
||||
// No actual edit-tracking is done yet, so these should also match.
|
||||
assert.approximately(subsetNodes[i].currentCost, expectedCosts[i], 1e-8);
|
||||
assert.approximately(subsetNodes[i].correctionCost, expectedCosts[i], 1e-8);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -605,7 +601,7 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins1_dl0[1].calculation.matchSequence), 'h');
|
||||
assert.equal(ins1_dl0[1].editCount, 0);
|
||||
assert.isBelow(ins1_dl0[0].inputSamplingCost, ins1_dl0[1].inputSamplingCost);
|
||||
assert.isBelow(ins1_dl0[0].currentCost, ins1_dl0[1].currentCost);
|
||||
assert.isBelow(ins1_dl0[0].correctionCost, ins1_dl0[1].correctionCost);
|
||||
|
||||
// Correction of _other_ input characters to the 't' and the 'h' come
|
||||
// after ALL other corrections - these don't get both 't' and 'h' input
|
||||
|
|
@ -616,13 +612,13 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins1_dl0[FIRST_CHAR_VARIANTS].calculation.matchSequence), 'h');
|
||||
assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS].editCount, 1);
|
||||
assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS-1].inputSamplingCost, ins1_dl0[FIRST_CHAR_VARIANTS].inputSamplingCost);
|
||||
assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS-1].currentCost, ins1_dl0[FIRST_CHAR_VARIANTS].currentCost);
|
||||
assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS-1].correctionCost, ins1_dl0[FIRST_CHAR_VARIANTS].correctionCost);
|
||||
|
||||
assert.equal(lastEntry(ins1_dl0[FIRST_CHAR_VARIANTS+1].calculation.inputSequence), SENTINEL_CODE_UNIT);
|
||||
assert.equal(lastEntry(ins1_dl0[FIRST_CHAR_VARIANTS+1].calculation.matchSequence), 't');
|
||||
assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS+1].editCount, 1);
|
||||
assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS].inputSamplingCost, ins1_dl0[FIRST_CHAR_VARIANTS+1].inputSamplingCost);
|
||||
assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS].currentCost, ins1_dl0[FIRST_CHAR_VARIANTS+1].currentCost);
|
||||
assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS].correctionCost, ins1_dl0[FIRST_CHAR_VARIANTS+1].correctionCost);
|
||||
|
||||
// For everything in between... well, the input-sampling weight is uniform, and
|
||||
// all require a full edit.
|
||||
|
|
@ -649,7 +645,7 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(ins0_dl1[0].editCount, 0);
|
||||
assert.isUndefined(lastEntry(ins0_dl1[0].calculation.inputSequence));
|
||||
assert.equal(ins0_dl1[0].inputSamplingCost, subsetNodes[3].inputSamplingCost);
|
||||
assert.equal(ins0_dl1[0].currentCost, subsetNodes[3].currentCost);
|
||||
assert.equal(ins0_dl1[0].correctionCost, subsetNodes[3].correctionCost);
|
||||
|
||||
// ************
|
||||
// Set 1: set for ins 2, dl 1 - 'tr' + 'th'.
|
||||
|
|
@ -667,8 +663,8 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins2_dl1[0].calculation.matchSequence), 't');
|
||||
assert.equal(ins2_dl1[0].editCount, 0);
|
||||
// The subset hasn't yet split!
|
||||
assert.equal(ins2_dl1[0].currentCost, subsetNodes[1].currentCost);
|
||||
assert.isBelow(ins2_dl1[0].currentCost, ins2_dl1[1].currentCost);
|
||||
assert.equal(ins2_dl1[0].correctionCost, subsetNodes[1].correctionCost);
|
||||
assert.isBelow(ins2_dl1[0].correctionCost, ins2_dl1[1].correctionCost);
|
||||
|
||||
// All other (non-'t') entries get full subset probability with edit count 1;
|
||||
// they're all substitutions, as they fail to match against a non-'t' path.
|
||||
|
|
@ -699,8 +695,8 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins2_dl0[0].calculation.matchSequence), 'c');
|
||||
assert.equal(ins2_dl0[0].editCount, 0);
|
||||
// The subset won't split.
|
||||
assert.equal(ins2_dl0[0].currentCost, subsetNodes[2].currentCost);
|
||||
assert.isBelow(ins2_dl0[0].currentCost, ins2_dl0[1].currentCost);
|
||||
assert.equal(ins2_dl0[0].correctionCost, subsetNodes[2].correctionCost);
|
||||
assert.isBelow(ins2_dl0[0].correctionCost, ins2_dl0[1].correctionCost);
|
||||
|
||||
// All other (non-'c') entries get full subset probability with edit count 1;
|
||||
// they're all substitutions, as they fail to match against a non-'t' path.
|
||||
|
|
@ -816,7 +812,7 @@ describe('Correction Distance Modeler', () => {
|
|||
const subsetNodes = teNode.buildSubstitutionEdges(synthDistribution, SEARCH_EDGE_SEED++);
|
||||
assert.equal(subsetNodes.length, 4);
|
||||
subsetNodes.sort(CORRECTION_QUEUE_COMPARATOR);
|
||||
const expectedCosts = [0.5, .25, 0.15, 0.1].map(x => -Math.log(x) + teNode.currentCost);
|
||||
const expectedCosts = [0.5, .25, 0.15, 0.1].map(x => -Math.log(x) + teNode.correctionCost);
|
||||
// The known subs for the subsets defined above.
|
||||
for(let i=0; i < expectedCosts.length; i++) {
|
||||
assert.isTrue(subsetNodes[i].hasPartialInput);
|
||||
|
|
@ -826,7 +822,7 @@ describe('Correction Distance Modeler', () => {
|
|||
// Allow a little value wiggle due to double-precision limitations.
|
||||
assert.approximately(subsetNodes[i].inputSamplingCost, expectedCosts[i], 1e-8);
|
||||
// No actual edit-tracking is done yet, so these should also match.
|
||||
assert.approximately(subsetNodes[i].currentCost, expectedCosts[i], 1e-8);
|
||||
assert.approximately(subsetNodes[i].correctionCost, expectedCosts[i], 1e-8);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -862,13 +858,13 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins1_dl0[TE_CHILD_PATH_COUNT].calculation.matchSequence), 'l');
|
||||
assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT].editCount, 1);
|
||||
assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT-1].inputSamplingCost, ins1_dl0[TE_CHILD_PATH_COUNT].inputSamplingCost);
|
||||
assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT-1].currentCost, ins1_dl0[TE_CHILD_PATH_COUNT].currentCost);
|
||||
assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT-1].correctionCost, ins1_dl0[TE_CHILD_PATH_COUNT].correctionCost);
|
||||
|
||||
assert.equal(lastEntry(ins1_dl0[TE_CHILD_PATH_COUNT+1].calculation.inputSequence), SENTINEL_CODE_UNIT);
|
||||
assert.equal(lastEntry(ins1_dl0[TE_CHILD_PATH_COUNT+1].calculation.matchSequence), 'r');
|
||||
assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT+1].editCount, 1);
|
||||
assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT].inputSamplingCost, ins1_dl0[TE_CHILD_PATH_COUNT+1].inputSamplingCost);
|
||||
assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT].currentCost, ins1_dl0[TE_CHILD_PATH_COUNT+1].currentCost);
|
||||
assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT].correctionCost, ins1_dl0[TE_CHILD_PATH_COUNT+1].correctionCost);
|
||||
|
||||
// For everything in between... well, the input-sampling weight is uniform, and
|
||||
// all require a full edit.
|
||||
|
|
@ -912,8 +908,8 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins2_dl1[0].calculation.matchSequence), 'a');
|
||||
assert.equal(ins2_dl1[0].editCount, 0);
|
||||
// The subset hasn't yet split!
|
||||
assert.equal(ins2_dl1[0].currentCost, subsetNodes[1].currentCost);
|
||||
assert.isBelow(ins2_dl1[0].currentCost, ins2_dl1[1].currentCost);
|
||||
assert.equal(ins2_dl1[0].correctionCost, subsetNodes[1].correctionCost);
|
||||
assert.isBelow(ins2_dl1[0].correctionCost, ins2_dl1[1].correctionCost);
|
||||
|
||||
// All other (non-'t') entries get full subset probability with edit count 1;
|
||||
// they're all substitutions, as they fail to match against a non-'t' path.
|
||||
|
|
@ -943,8 +939,8 @@ describe('Correction Distance Modeler', () => {
|
|||
assert.equal(lastEntry(ins2_dl0[0].calculation.matchSequence), 'c');
|
||||
assert.equal(ins2_dl0[0].editCount, 0);
|
||||
// The subset won't split.
|
||||
assert.equal(ins2_dl0[0].currentCost, subsetNodes[2].currentCost);
|
||||
assert.isBelow(ins2_dl0[0].currentCost, ins2_dl0[1].currentCost);
|
||||
assert.equal(ins2_dl0[0].correctionCost, subsetNodes[2].correctionCost);
|
||||
assert.isBelow(ins2_dl0[0].correctionCost, ins2_dl0[1].correctionCost);
|
||||
|
||||
// All other (non-'c') entries get full subset probability with edit count 1;
|
||||
// they're all substitutions, as they fail to match against a non-'t' path.
|
||||
|
|
@ -1058,7 +1054,7 @@ describe('Correction Distance Modeler', () => {
|
|||
const layer1Edges = rootNode.buildSubstitutionEdges(synthDistribution1, layer1Id)
|
||||
// No 2+ inserts here; we're fine with just one call.
|
||||
.flatMap(e => e.processSubsetEdge());
|
||||
const layer1Queue = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR, layer1Edges);
|
||||
const layer1Queue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR, layer1Edges);
|
||||
|
||||
const tEdge = layer1Queue.dequeue();
|
||||
assertEdgeChars(tEdge, 't', 't');
|
||||
|
|
@ -1068,7 +1064,7 @@ describe('Correction Distance Modeler', () => {
|
|||
const layer2Edges = tEdge.buildSubstitutionEdges(synthDistribution2, layer2Id)
|
||||
// No 2+ inserts here; we're fine with just one call.
|
||||
.flatMap(e => e.processSubsetEdge());
|
||||
const layer2Queue = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR, layer2Edges);
|
||||
const layer2Queue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR, layer2Edges);
|
||||
|
||||
const eEdge = layer2Queue.dequeue();
|
||||
assertEdgeChars(eEdge, 'e', 'e');
|
||||
|
|
@ -1078,21 +1074,16 @@ describe('Correction Distance Modeler', () => {
|
|||
assertEdgeChars(hEdge, 'h', 'h');
|
||||
assert.equal(hEdge.spaceId, layer2Id);
|
||||
|
||||
// Needed for a proper e <-> h transposition.
|
||||
const ehEdge = findEdgesWithChars(layer2Edges, 'h')[0];
|
||||
|
||||
assert.isOk(ehEdge);
|
||||
|
||||
// Final round: we'll use three nodes and throw all of their results into the same priority queue.
|
||||
// Note: as we're constructing these directly, we're not modeling transpositions.
|
||||
const layer3Id = SEARCH_EDGE_SEED++;
|
||||
const layer3eEdges = eEdge.buildSubstitutionEdges(synthDistribution3, layer3Id)
|
||||
// No 2+ inserts here; we're fine with just one call.
|
||||
.flatMap(e => e.processSubsetEdge());
|
||||
const layer3hEdges = hEdge.buildSubstitutionEdges(synthDistribution3, layer3Id)
|
||||
.flatMap(e => e.processSubsetEdge());
|
||||
const layer3ehEdges = ehEdge.buildSubstitutionEdges(synthDistribution3, layer3Id)
|
||||
.flatMap(e => e.processSubsetEdge());
|
||||
const layer3Queue = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR, layer3eEdges.concat(layer3hEdges).concat(layer3ehEdges));
|
||||
const layer3Queue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR, layer3eEdges.concat(layer3hEdges));
|
||||
|
||||
// Find the first result with an actual word directly represented.
|
||||
let bestEdge;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ import {
|
|||
models,
|
||||
LegacyQuotientRoot,
|
||||
SearchQuotientCluster,
|
||||
TokenResultMapping
|
||||
TokenResultMapping,
|
||||
CORRECTION_QUEUE_COMPARATOR
|
||||
} from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import TrieModel = models.TrieModel;
|
||||
|
|
@ -32,74 +33,31 @@ function buildTestTimer() {
|
|||
describe('Correction Searching', () => {
|
||||
describe('without multi-tokenization; using a single SearchPath sequence', () => {
|
||||
const checkRepeatableResults_teh = async (iter: AsyncGenerator<Readonly<TokenResultMapping>, any, any>) => {
|
||||
const firstIterResult = await iter.next(); // {value: <actual value>, done: <iteration complete?>}
|
||||
assert.isFalse(firstIterResult.done);
|
||||
|
||||
const firstResult: TokenResultMapping = firstIterResult.value; // Retrieves <actual value>
|
||||
// No checks on the first set's cost.
|
||||
assert.equal(firstResult.matchString, "ten");
|
||||
|
||||
// All start with 'te' but one, and invoke one edit of the same cost.
|
||||
// 'th' has an 'h' at the same cost (input 3) of the 'e' (input 2).
|
||||
const secondBatch = [
|
||||
'tec', 'tel', 'tem',
|
||||
'ter', 'tes', 'th',
|
||||
'te'
|
||||
const expectedFirstTwenty = [
|
||||
'ten', // no edits required whatsoever
|
||||
'th', // one edit (deletion), but gets to ignore the cost of a keystroke and still prefixes 'the'
|
||||
'the', // one edit (transposition), incurs the cost of all three keystrokes
|
||||
'te', // one edit (deletion), but gets to ignore the cost of a keystroke
|
||||
'tel', 'beh', // both cost one edit (hard character replacement: n/h -> l vs t -> b)
|
||||
// Other edits, generally of one edit cost, predicting words of varying frequency.
|
||||
'ter', 'tha', 'thi', 'thr', 'tho', 'tem', 'thu', 'then', 'men', 'wen', 'gen', 'en', 'sen', 'tec'
|
||||
];
|
||||
|
||||
async function checkBatch(batch: string[], prevCost: number) {
|
||||
let cost;
|
||||
while(batch.length > 0) {
|
||||
const iter_result = await iter.next();
|
||||
assert.isFalse(iter_result.done);
|
||||
|
||||
const result = iter_result.value;
|
||||
assert.isAbove(result.totalCost, prevCost);
|
||||
if(cost !== undefined) {
|
||||
assert.equal(result.totalCost, cost);
|
||||
} else {
|
||||
cost = result.totalCost;
|
||||
}
|
||||
|
||||
const matchIndex = batch.findIndex((entry) => entry == result.matchString);
|
||||
assert.notEqual(matchIndex, -1, `'${result.matchString}' received as prediction too early`);
|
||||
batch.splice(matchIndex, 1);
|
||||
}
|
||||
|
||||
return cost;
|
||||
let results: TokenResultMapping[] = [];
|
||||
for(let i = 0; i < expectedFirstTwenty.length; i++) {
|
||||
const iterResult = await iter.next();
|
||||
results.push(iterResult.value as TokenResultMapping);
|
||||
}
|
||||
|
||||
const secondCost = await checkBatch(secondBatch, firstResult.totalCost);
|
||||
assert.sameOrderedMembers(results.map((r) => r.matchString), expectedFirstTwenty);
|
||||
for(let i=0; i < expectedFirstTwenty.length - 1; i++) {
|
||||
assert.isAtLeast(results[i+1].totalCost, results[i].totalCost);
|
||||
}
|
||||
|
||||
// Single hard edit, all other input probability aspects are equal
|
||||
const thirdBatch = [
|
||||
// 't' -> 'b' (sub)
|
||||
'beh',
|
||||
// '' -> 'c' (insertion)
|
||||
'tech',
|
||||
// 'eh' -> 'he' (transposition)
|
||||
'the'
|
||||
];
|
||||
|
||||
await checkBatch(thirdBatch, secondCost);
|
||||
|
||||
// All replace the low-likelihood case for the third input.
|
||||
const fourthBatch = [
|
||||
'thi', 'tho', 'thr',
|
||||
'thu', 'tha'
|
||||
];
|
||||
|
||||
await checkBatch(fourthBatch, secondCost);
|
||||
|
||||
// Replace the _first_ input's char OR insert an extra char,
|
||||
// also matching the low-likelihood third-char option.
|
||||
const fifthBatch = [
|
||||
'cen', 'en', 'gen',
|
||||
'ken', 'len', 'men',
|
||||
'sen', 'then', 'wen'
|
||||
];
|
||||
|
||||
await checkBatch(fifthBatch, secondCost);
|
||||
// The results will not be in the order as raw correction likelihood because some words
|
||||
// are more frequent than others.
|
||||
results.sort(CORRECTION_QUEUE_COMPARATOR);
|
||||
assert.notSameOrderedMembers(results.map((r) => r.matchString), expectedFirstTwenty);
|
||||
}
|
||||
|
||||
it('Empty search root, loaded model', async () => {
|
||||
|
|
@ -113,12 +71,13 @@ describe('Correction Searching', () => {
|
|||
|
||||
// While there's no input, insertion operations can produce suggestions.
|
||||
const resultState = await iter.next();
|
||||
const result = resultState.value;
|
||||
const result = resultState.value as TokenResultMapping;
|
||||
|
||||
// Just one suggestion root should be returned as the first result.
|
||||
assert.equal(result.totalCost, 0); // Gives a perfect match
|
||||
assert.equal(result.correctionCost, 0); // Gives a perfect match
|
||||
assert.equal(result.matchString, ''); // an empty match string.
|
||||
assert.isFalse(resultState.done);
|
||||
assert.isAbove(result.totalCost, 0);
|
||||
});
|
||||
|
||||
// Hmm... how best to update this...
|
||||
|
|
@ -423,9 +382,9 @@ describe('Correction Searching', () => {
|
|||
// paths of lower total cost.
|
||||
pathsResults.push(nextFromPaths);
|
||||
|
||||
assert.isAtLeast(nextFromCluster.totalCost, baseCost);
|
||||
assert.isAtLeast(nextFromPaths.totalCost, baseCost);
|
||||
baseCost = Math.max(baseCost, nextFromCluster.totalCost, nextFromPaths.totalCost);
|
||||
assert.isAtLeast(nextFromCluster.correctionCost, baseCost);
|
||||
assert.isAtLeast(nextFromPaths.correctionCost, baseCost);
|
||||
baseCost = Math.max(baseCost, nextFromCluster.correctionCost, nextFromPaths.correctionCost);
|
||||
}
|
||||
|
||||
assert.deepEqual(genResults.map(r => r.matchString), pathsResults.map(r => r.matchString));
|
||||
|
|
@ -434,22 +393,13 @@ describe('Correction Searching', () => {
|
|||
assert.sameDeepMembers(pathsResults.slice(0, 3).map(r => r.matchString), ['th', 'to', 'tr']);
|
||||
// These involve likely-enough corrections that should show, given the model fixture.
|
||||
assert.includeDeepMembers(pathsResults.map(r => r.matchString), [
|
||||
'ty', // 'type' is quite frequent according to the text fixture.
|
||||
't', // Deleting the second keystroke outright lands here.
|
||||
'oth', // What if we insert an 'o' early on? 'other' is a very common English word
|
||||
'ti' // 'time' is pretty common too.
|
||||
'ti', // 'time' is pretty common too.
|
||||
'thi', // 'this' is common enough to show up early despite inserting the 'i'.
|
||||
'wh', // "which" is a super-frequent English word, worthy of being a forced correction
|
||||
'sh' // "she" is also strong enough to force an early appearance.
|
||||
]);
|
||||
|
||||
// NOTE: this level of corrections does not yet consider the word likelihood - only
|
||||
// the raw correction cost. No ordering of "likely word" to "unlikely word" should
|
||||
// occur yet.
|
||||
|
||||
// 'time': weight 934
|
||||
// 'type': weight 540
|
||||
const timeResult = pathsResults.find(r => r.matchString == 'ti');
|
||||
const typeResult = pathsResults.find(r => r.matchString == 'ty');
|
||||
// Correction to either should be equally likely.
|
||||
assert.equal(timeResult.totalCost, typeResult.totalCost);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ describe('LegacyQuotientSpur', () => {
|
|||
const entry_the = thirdResults.find((entry) => entry.matchString == 'the' && entry.editCount == 1);
|
||||
assert.isOk(entry_the);
|
||||
|
||||
thirdResults.sort((a, b) => a.totalCost - b.totalCost);
|
||||
thirdResults.sort((a, b) => a.correctionCost - b.correctionCost);
|
||||
const the_index = thirdResults.findIndex((entry) => entry.matchString == 'the' && entry.editCount == 1);
|
||||
// `teh` should appear fairly early as a viable correction.
|
||||
assert.isBelow(the_index, 10);
|
||||
|
|
@ -438,7 +438,7 @@ describe('LegacyQuotientSpur', () => {
|
|||
// We want to make sure we don't auto-ignore transposition cases by
|
||||
// accident by failing that conditional.
|
||||
const the_entry = thirdResults[the_index];
|
||||
assert.isBelow(the_entry.totalCost - thirdResults[0].totalCost, CORRECTION_SEARCH_THRESHOLDS.REPLACEMENT_SEARCH_THRESHOLD);
|
||||
assert.isBelow(the_entry.correctionCost - thirdResults[0].correctionCost, CORRECTION_SEARCH_THRESHOLDS.REPLACEMENT_SEARCH_THRESHOLD);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -99,9 +99,9 @@ describe('QuotientNodeFinalizer', () => {
|
|||
|
||||
assert.equal(searchResult.type, 'complete');
|
||||
if(searchResult.type == 'complete') {
|
||||
assert.equal(searchResult.mapping.totalCost, -Math.log(therefo.bestExample.p));
|
||||
assert.equal(searchResult.mapping.correctionCost, -Math.log(therefo.bestExample.p));
|
||||
assert.isNotNaN(searchResult.cost);
|
||||
assert.equal(searchResult.cost, searchResult.mapping.totalCost);
|
||||
assert.isAtLeast(searchResult.cost, searchResult.mapping.totalCost);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
|
@ -129,9 +129,9 @@ describe('QuotientNodeFinalizer', () => {
|
|||
|
||||
assert.equal(searchResult.type, 'complete');
|
||||
if(searchResult.type == 'complete') {
|
||||
assert.isAbove(searchResult.mapping.totalCost, -Math.log(therefo.bestExample.p));
|
||||
assert.isAbove(searchResult.mapping.correctionCost, -Math.log(therefo.bestExample.p));
|
||||
assert.isNotNaN(searchResult.cost);
|
||||
assert.equal(searchResult.cost, searchResult.mapping.totalCost);
|
||||
assert.isAtLeast(searchResult.cost, searchResult.mapping.totalCost);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ describe('buildAndMapPredictions', () => {
|
|||
const mappedPredictions = buildAndMapPredictions(
|
||||
transition,
|
||||
transition.base.displayTokenization,
|
||||
{matchString: 'the', totalCost: 0, editCount: 0},
|
||||
{matchString: 'the', correctionCost: 0, editCount: 0},
|
||||
1
|
||||
);
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ describe('buildAndMapPredictions', () => {
|
|||
const mappedPredictions = buildAndMapPredictions(
|
||||
transition,
|
||||
transition.base.displayTokenization,
|
||||
{matchString: '', totalCost: 0, editCount: 0},
|
||||
{matchString: '', correctionCost: 0, editCount: 0},
|
||||
1
|
||||
);
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ describe('buildAndMapPredictions', () => {
|
|||
const mappedPredictions = buildAndMapPredictions(
|
||||
transition,
|
||||
transition.base.displayTokenization,
|
||||
{matchString: '', totalCost: 0, editCount: 0},
|
||||
{matchString: '', correctionCost: 0, editCount: 0},
|
||||
1
|
||||
);
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ describe('buildAndMapPredictions', () => {
|
|||
const mappedPredictions = buildAndMapPredictions(
|
||||
transition,
|
||||
transition.final.displayTokenization,
|
||||
{matchString: '', totalCost: 0, editCount: 0},
|
||||
{matchString: '', correctionCost: 0, editCount: 0},
|
||||
1
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ if builder_start_action build; then
|
|||
|
||||
# We shouldn't rely on a prior Android build for these files! We can retrieve them separately.
|
||||
cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "host/keyman-sentry.js"
|
||||
cp "$KEYMAN_ROOT/node_modules/es6-shim/es6-shim.min.js" "host/es6-shim.min.js"
|
||||
|
||||
# Test keyboards
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ type
|
|||
FPackage: IKeymanPackageInstalled; const BCP47: string); static;
|
||||
public
|
||||
class function BrowseAndInstallKeyboardFromFile(Owner: TComponent): Boolean; static;
|
||||
class function Execute(KeyboardFileNames: TStrings; const FirstKeyboardFileName: string; FSilent, FNoWelcome: Boolean; const LogFile: string): Boolean; overload; static;
|
||||
class function Execute(Owner: TComponent; const FileName: string; ASilent, ANoWelcome: Boolean; const LogFile, BCP47: string): Boolean; overload; static;
|
||||
class function Execute(Owner: TComponent; const FileNames: TStrings; ASilent: Boolean): Boolean; overload; static;
|
||||
class function Execute(KeyboardFileNames: TStrings; const FirstKeyboardFileName: string; FSilent, FNoWelcome: Boolean; const LogFile: string; BaseKeyboardID: Integer): Boolean; overload; static;
|
||||
class function Execute(Owner: TComponent; const FileName: string; ASilent, ANoWelcome: Boolean; const LogFile, BCP47: string; BaseKeyboardID: Integer): Boolean; overload; static;
|
||||
class function Execute(Owner: TComponent; const FileNames: TStrings; ASilent: Boolean; BaseKeyboardID: Integer): Boolean; overload; static;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -35,30 +35,31 @@ uses
|
|||
Keyman.Configuration.UI.KeymanProtocolHandler,
|
||||
Keyman.Configuration.UI.MitigationForWin10_1803,
|
||||
kmint,
|
||||
KeymanOptionNames,
|
||||
Keyman.System.KeymanSentryClient,
|
||||
UfrmHTML,
|
||||
UfrmInstallKeyboard;
|
||||
|
||||
class function TInstallFile.Execute(KeyboardFileNames: TStrings; const FirstKeyboardFileName: string; FSilent, FNoWelcome: Boolean;
|
||||
const LogFile: string): Boolean;
|
||||
const LogFile: string; BaseKeyboardID: Integer): Boolean;
|
||||
begin
|
||||
if TKeymanProtocolHandler.CanHandle(FirstKeyboardFileName) then
|
||||
begin
|
||||
Result := TKeymanProtocolHandler.Handle(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile);
|
||||
Result := TKeymanProtocolHandler.Handle(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile, BaseKeyboardID);
|
||||
end
|
||||
else if (KeyboardFileNames.Count > 1) or (Pos('=', FirstKeyboardFileName) > 0) then
|
||||
begin
|
||||
Result := TInstallFile.Execute(nil, KeyboardFileNames, FSilent)
|
||||
Result := TInstallFile.Execute(nil, KeyboardFileNames, FSilent, BaseKeyboardID)
|
||||
end
|
||||
// TODO: support bare package ids from command line (if it does not include a file extension, assume it is a .kmp and try and download it)
|
||||
// else if IsNotPackageOrKeyboardFile then
|
||||
else
|
||||
begin
|
||||
Result := TInstallFile.Execute(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile, '');
|
||||
Result := TInstallFile.Execute(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile, '', BaseKeyboardID);
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TInstallFile.Execute(Owner: TComponent; const FileName: string; ASilent, ANoWelcome: Boolean; const LogFile, BCP47: string): Boolean;
|
||||
class function TInstallFile.Execute(Owner: TComponent; const FileName: string; ASilent, ANoWelcome: Boolean; const LogFile, BCP47: string; BaseKeyboardID: Integer): Boolean;
|
||||
var
|
||||
n: Integer;
|
||||
InstalledKeyboards: array of IKeymanKeyboardInstalled;
|
||||
|
|
@ -78,7 +79,7 @@ begin
|
|||
begin
|
||||
if ASilent then
|
||||
begin
|
||||
InstallKeyboard(LogFile, BCP47);
|
||||
InstallKeyboard(LogFile, BCP47, BaseKeyboardID);
|
||||
Result := True;
|
||||
end
|
||||
else
|
||||
|
|
@ -140,7 +141,7 @@ end;
|
|||
/// This is the handler for the `-i` parameter, e.g.
|
||||
/// kmshell -i khmer_angkor.kmp c:\temp\sil_euro_latin.kmp=fr
|
||||
/// </summary>
|
||||
class function TInstallFile.Execute(Owner: TComponent; const FileNames: TStrings; ASilent: Boolean): Boolean;
|
||||
class function TInstallFile.Execute(Owner: TComponent; const FileNames: TStrings; ASilent: Boolean; BaseKeyboardID: Integer): Boolean;
|
||||
var
|
||||
i, j: Integer;
|
||||
FPackage: IKeymanPackageInstalled;
|
||||
|
|
@ -172,7 +173,7 @@ begin
|
|||
end;
|
||||
if IsPackage then
|
||||
begin
|
||||
FPackage := (kmcom.Packages as IKeymanPackagesInstalled2).Install2(FileName, True);
|
||||
FPackage := (kmcom.Packages as IKeymanPackagesInstalled3).Install3(FileName, True, BaseKeyboardID);
|
||||
if Length(FilenameBCP47) > 1
|
||||
then RegisterKeyboardPackageLanguage(FPackage, FilenameBCP47[1])
|
||||
else RegisterKeyboardPackageLanguage(FPackage, '');
|
||||
|
|
@ -180,7 +181,7 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
FKeyboard := (kmcom.Keyboards as IKeymanKeyboardsInstalled2).Install2(FileName, True);
|
||||
FKeyboard := (kmcom.Keyboards as IKeymanKeyboardsInstalled3).Install3(FileName, True, BaseKeyboardID);
|
||||
if (Length(FilenameBCP47) > 1) and (Trim(FilenameBCP47[1]) <> '')
|
||||
then BCP47Tag := FilenameBCP47[1]
|
||||
else BCP47Tag := TTIPMaintenance.GetFirstLanguage(FKeyboard);
|
||||
|
|
@ -231,6 +232,7 @@ end;
|
|||
class function TInstallFile.BrowseAndInstallKeyboardFromFile(Owner: TComponent): Boolean;
|
||||
var
|
||||
dlgOpen: TOpenDialog;
|
||||
BaseKeyboardID : Integer;
|
||||
begin
|
||||
dlgOpen := TOpenDialog.Create(nil);
|
||||
try
|
||||
|
|
@ -238,9 +240,9 @@ begin
|
|||
'Keyman files (*.kmx, *.kmp)|*.kmx;*.kmp|Keyman keyboards (*.kmx)' +
|
||||
'|*.kmx|Keyman packages (*.kmp)|*.kmp|All files (*.*)|*.*';
|
||||
dlgOpen.Title := 'Install Keyman Keyboard';
|
||||
|
||||
BaseKeyboardID := kmcom.Options[KeymanOptionName(koBaseLayout)].Value;
|
||||
if dlgOpen.Execute then
|
||||
Result := Execute(Owner, dlgOpen.FileName, False, False, '', '')
|
||||
Result := Execute(Owner, dlgOpen.FileName, False, False, '', '', BaseKeyboardID)
|
||||
else
|
||||
Result := False;
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ type
|
|||
FDownloadURL: string;
|
||||
frmDownloadProgress: TfrmDownloadProgress;
|
||||
function DoHandle(Owner: TComponent; const url: string; ASilent,
|
||||
ANoWelcome: Boolean; const ALogFile: string): Boolean;
|
||||
ANoWelcome: Boolean; const ALogFile: string; BaseKeyboardID: Integer): Boolean;
|
||||
procedure DoDownload(AOwner: TfrmDownloadProgress; var Result: Boolean);
|
||||
procedure HttpReceiveData(const Sender: TObject; AContentLength,
|
||||
AReadCount: Int64; var Abort: Boolean);
|
||||
public
|
||||
class function CanHandle(const url: string): Boolean; static;
|
||||
class function Handle(Owner: TComponent; const url: string; ASilent, ANoWelcome: Boolean; const ALogFile: string): Boolean; static;
|
||||
class function Handle(Owner: TComponent; const url: string; ASilent, ANoWelcome: Boolean; const ALogFile: string; BaseKeyboardID: Integer): Boolean; static;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -51,13 +51,13 @@ end;
|
|||
|
||||
class function TKeymanProtocolHandler.Handle(Owner: TComponent;
|
||||
const url: string; ASilent, ANoWelcome: Boolean;
|
||||
const ALogFile: string): Boolean;
|
||||
const ALogFile: string; BaseKeyboardID: Integer): Boolean;
|
||||
var
|
||||
h: TKeymanProtocolHandler;
|
||||
begin
|
||||
h := TKeymanProtocolHandler.Create;
|
||||
try
|
||||
Result := h.DoHandle(Owner, url, ASilent, ANoWelcome, ALogFile);
|
||||
Result := h.DoHandle(Owner, url, ASilent, ANoWelcome, ALogFile, BaseKeyboardID);
|
||||
finally
|
||||
h.Free;
|
||||
end;
|
||||
|
|
@ -65,7 +65,7 @@ end;
|
|||
|
||||
function TKeymanProtocolHandler.DoHandle(Owner: TComponent;
|
||||
const url: string; ASilent, ANoWelcome: Boolean;
|
||||
const ALogFile: string): Boolean;
|
||||
const ALogFile: string; BaseKeyboardID: Integer): Boolean;
|
||||
var
|
||||
FTempDir: string;
|
||||
PackageID, BCP47: string;
|
||||
|
|
@ -98,7 +98,7 @@ begin
|
|||
end;
|
||||
|
||||
// TODO: this makes a circular dependency, refactor it out!
|
||||
Result := TInstallFile.Execute(nil, FDownloadFilename, False, False, '', BCP47);
|
||||
Result := TInstallFile.Execute(nil, FDownloadFilename, False, False, '', BCP47, BaseKeyboardID);
|
||||
|
||||
finally
|
||||
if FileExists(FDownloadFilename) then
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ uses
|
|||
Vcl.StdCtrls,
|
||||
|
||||
keymanapi_TLB,
|
||||
KeymanOptionNames,
|
||||
UfrmKeymanBase,
|
||||
UfrmWebContainer;
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ type
|
|||
protected
|
||||
procedure FireCommand(const command: WideString; params: TStringList); override;
|
||||
public
|
||||
procedure InstallKeyboard(const ALogFile, BCP47Tag: string);
|
||||
procedure InstallKeyboard(const ALogFile, BCP47Tag: string; BaseKeyboardID: Integer);
|
||||
property DefaultBCP47Tag: string read FDefaultBCP47Tag write SetDefaultBCP47Tag;
|
||||
property InstallFile: string read FInstallFile write SetInstallFile;
|
||||
property Silent: Boolean read FSilent write FSilent;
|
||||
|
|
@ -271,6 +272,7 @@ end;
|
|||
procedure TfrmInstallKeyboard.FireCommand(const command: WideString; params: TStringList);
|
||||
var
|
||||
BCP47Tag: string;
|
||||
BaseKeyboardID :Integer;
|
||||
begin
|
||||
BCP47Tag := '';
|
||||
if (command = 'keyboard_install') and kmcom.SystemInfo.IsAdministrator then // I4172
|
||||
|
|
@ -283,7 +285,8 @@ begin
|
|||
Manager.Title := 'Installing Keyboard';
|
||||
Manager.CanCancel := False;
|
||||
Manager.UpdateProgress('Installing Keyboard', 0, 0);
|
||||
InstallKeyboard('', BCP47Tag);
|
||||
BaseKeyboardID := kmcom.Options[KeymanOptionName(koBaseLayout)].Value;
|
||||
InstallKeyboard('', BCP47Tag, BaseKeyboardID);
|
||||
Result := True;
|
||||
end
|
||||
);
|
||||
|
|
@ -299,6 +302,7 @@ begin
|
|||
var
|
||||
t: TTempFile;
|
||||
ExecParams: string;
|
||||
BaseKeyboardString :string;
|
||||
begin
|
||||
KL.MethodEnter(Self, '"keyboard_install"', [params.Text]);
|
||||
try
|
||||
|
|
@ -306,8 +310,9 @@ begin
|
|||
Manager.CanCancel := False;
|
||||
Manager.UpdateProgress('Installing Keyboard', 0, 0);
|
||||
t := TTempFileManager.Get('.log');
|
||||
BaseKeyboardString := IntToHex(kmcom.Options[KeymanOptionName(koBaseLayout)].Value, 8);
|
||||
try
|
||||
ExecParams := '-log "'+t.Name+'" -s -i "'+FInstallFile+'='+BCP47Tag+'"'+
|
||||
ExecParams := '-log "'+t.Name+'" -bklid "'+BaseKeyboardString+'" -s -i "'+FInstallFile+'='+BCP47Tag+'"'+
|
||||
' -nowelcome '+TTIPMaintenance.GetUserDefaultLangParameterString;
|
||||
KL.Log('Calling elevated kmshell %s', [ExecParams]);
|
||||
if WaitForElevatedConfiguration(GetForegroundWindow, ExecParams) = 0 then
|
||||
|
|
@ -358,7 +363,7 @@ end;
|
|||
------------------------------------------------------------------------------}
|
||||
|
||||
// TODO: move this to TInstallFile
|
||||
procedure TfrmInstallKeyboard.InstallKeyboard(const ALogFile, BCP47Tag: string);
|
||||
procedure TfrmInstallKeyboard.InstallKeyboard(const ALogFile, BCP47Tag: string; BaseKeyboardID: Integer);
|
||||
var
|
||||
i: Integer;
|
||||
kbd: IKeymanKeyboardInstalled;
|
||||
|
|
@ -400,7 +405,7 @@ begin
|
|||
kbd := nil;
|
||||
kmcom.Keyboards.Apply;
|
||||
kmcom.Keyboards.Refresh;
|
||||
FInstalledKeyboard := (FKeyboard as IKeymanKeyboardFile2).Install2(True);
|
||||
FInstalledKeyboard := (FKeyboard as IKeymanKeyboardFile3).Install3(True, BaseKeyboardID);
|
||||
if not InstallTipForKeyboard(BCP47Tag) then
|
||||
begin
|
||||
// TODO can we return a failure code?
|
||||
|
|
@ -461,7 +466,7 @@ begin
|
|||
kmcom.Keyboards.Apply;
|
||||
kmcom.Keyboards.Refresh; // I2169
|
||||
|
||||
(FPackage as IKeymanPackageFile2).Install2(True);
|
||||
(FPackage as IKeymanPackageFile3).Install3(True, BaseKeyboardID);
|
||||
|
||||
kmcom.Refresh;
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ uses
|
|||
Keyman.Configuration.UI.InstallFile,
|
||||
Keyman.System.LocaleStrings,
|
||||
kmint,
|
||||
KeymanOptionNames,
|
||||
MessageIdentifierConsts,
|
||||
Upload_Settings,
|
||||
utilfiletypes,
|
||||
|
|
@ -213,6 +214,7 @@ end;
|
|||
procedure TfrmInstallKeyboardFromWeb.DownloadAndInstallPackage(const PackageID, BCP47: string);
|
||||
var
|
||||
FTempDir: string;
|
||||
BaseKeyboardID: Integer;
|
||||
begin
|
||||
FTempDir := IncludeTrailingPathDelimiter(CreateTempPath); // I1679
|
||||
try
|
||||
|
|
@ -230,8 +232,8 @@ begin
|
|||
finally
|
||||
frmDownloadProgress.Free;
|
||||
end;
|
||||
|
||||
if TInstallFile.Execute(Self, FDownloadFilename, False, False, '', BCP47) then
|
||||
BaseKeyboardID := kmcom.Options[KeymanOptionName(koBaseLayout)].Value;
|
||||
if TInstallFile.Execute(Self, FDownloadFilename, False, False, '', BCP47, BaseKeyboardID) then
|
||||
ModalResult := mrOk;
|
||||
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ uses
|
|||
kmint,
|
||||
RegistryKeys,
|
||||
utilexecute,
|
||||
utilkmshell;
|
||||
utilkmshell,
|
||||
utilfiletypes;
|
||||
|
||||
const
|
||||
{ CurrentMnemonicLayoutVersion = 476; // First 9.0 build with fixes for mnemonic layouts }
|
||||
|
|
@ -170,7 +171,7 @@ begin
|
|||
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
|
||||
FBaseFileName := Keyboard.Filename;
|
||||
FDestFileName := OutputFileName;
|
||||
FDestDeadkeyFileName := ChangeFileExt(FDestFileName, '') + '-d.kmx'; // I4552
|
||||
FDestDeadkeyFileName := BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(FBaseFileName, FBaseKeyboardIDHex); // I4552
|
||||
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
|
||||
FDestPath := ExtractFileDir(Keyboard.Filename);
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,8 @@ uses
|
|||
Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas',
|
||||
Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas',
|
||||
Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall},
|
||||
Keyman.Configuration.Util.NetworkConnection in 'util\Keyman.Configuration.Util.NetworkConnection.pas';
|
||||
Keyman.Configuration.Util.NetworkConnection in 'util\Keyman.Configuration.Util.NetworkConnection.pas',
|
||||
Keyman.Configuration.System.BaseKeyboard in 'main\Keyman.Configuration.System.BaseKeyboard.pas';
|
||||
|
||||
{$R VERSION.RES}
|
||||
{$R manifest.res}
|
||||
|
|
|
|||
|
|
@ -358,6 +358,7 @@
|
|||
<Form>frmStartInstall</Form>
|
||||
</DCCReference>
|
||||
<DCCReference Include="util\Keyman.Configuration.Util.NetworkConnection.pas"/>
|
||||
<DCCReference Include="main\Keyman.Configuration.System.BaseKeyboard.pas"/>
|
||||
<None Include="Profiling\AQtimeModule1.aqt"/>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_2</Key>
|
||||
|
|
@ -419,6 +420,12 @@
|
|||
<Platform value="Win64">False</Platform>
|
||||
</Platforms>
|
||||
<Deployment Version="3">
|
||||
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
|
||||
<Platform Name="Win32">
|
||||
<RemoteName>kmshell.exe</RemoteName>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployFile LocalName="Profiling\AQtimeModule1.aqt" Configuration="Debug" Class="ProjectFile">
|
||||
<Platform Name="Win32">
|
||||
<RemoteDir>.\</RemoteDir>
|
||||
|
|
@ -431,12 +438,6 @@
|
|||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
|
||||
<Platform Name="Win32">
|
||||
<RemoteName>kmshell.exe</RemoteName>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployClass Name="AdditionalDebugSymbols">
|
||||
<Platform Name="OSX32">
|
||||
<Operation>1</Operation>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
(*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by Ross Cruickshank on 2026-09-12
|
||||
*
|
||||
*
|
||||
* This unit assists in setting the base keyboard configuration,
|
||||
* including compiling the installed keyboard layouts against
|
||||
* the selected base keyboard.
|
||||
*)
|
||||
unit Keyman.Configuration.System.BaseKeyboard;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Winapi.Windows,
|
||||
System.SysUtils,
|
||||
keymanapi_TLB;
|
||||
|
||||
(**
|
||||
* Returns true if the keyboard files need to be compiled for the specified KLID.
|
||||
* @param BaseKeyboardID KLID of the base keyboard to compile.
|
||||
* @returns True If the keyboard files need to be compiled.
|
||||
*)
|
||||
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
|
||||
|
||||
(**
|
||||
* Sets the base keyboard KLID for the current user and compiles the keyboard
|
||||
* layout files if necessary. In the case the compiled keyboard files are
|
||||
*not present, it will require elevation.
|
||||
* @param WindowHandle Window handle to own the elevation prompt.
|
||||
* @param BaseKeyboardID KLID of the base keyboard KLID to set.
|
||||
* @returns True when the base keyboard setting has been applied.
|
||||
*)
|
||||
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
|
||||
|
||||
(**
|
||||
* Compiles the installed keyboard layouts for the specified KLID.
|
||||
* Must run elevated.
|
||||
*
|
||||
* @param BaseKeyboardID KLID of the base keyboard to compile.
|
||||
* @returns True when the compilation is successful.
|
||||
*)
|
||||
function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
kmint,
|
||||
utilkmshell,
|
||||
utilfiletypes;
|
||||
|
||||
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
Keyboard: IKeymanKeyboardInstalled;
|
||||
KeyboardFileName: string;
|
||||
BaseKeyboardIDHex: string;
|
||||
begin
|
||||
BaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
|
||||
for I := 0 to kmcom.Keyboards.Count - 1 do
|
||||
begin
|
||||
Keyboard := kmcom.Keyboards.Items[I];
|
||||
KeyboardFileName := Keyboard.Filename;
|
||||
if FileExists(KeyboardFileName) and
|
||||
(not FileExists(BuildKeyboardFilenameWithBaseKeyboardID(KeyboardFileName, BaseKeyboardIDHex)) or
|
||||
not FileExists(BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(KeyboardFileName, BaseKeyboardIDHex))) then
|
||||
Exit(True);
|
||||
end;
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
|
||||
begin
|
||||
if not kmcom.SystemInfo.IsAdministrator then
|
||||
begin
|
||||
Result := WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8)) = 0;
|
||||
end
|
||||
else
|
||||
Result := MCompileBaseKeyboard(BaseKeyboardID);
|
||||
end;
|
||||
if not Result then
|
||||
Exit;
|
||||
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
|
||||
kmcom.Options.Apply;
|
||||
end;
|
||||
|
||||
function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
kbd: IKeymanKeyboardInstalled;
|
||||
begin
|
||||
Result := False;
|
||||
// can be called from command line so test for admin
|
||||
if not kmcom.SystemInfo.IsAdministrator then
|
||||
Exit;
|
||||
for i := 0 to kmcom.Keyboards.Count - 1 do
|
||||
begin
|
||||
kbd := kmcom.Keyboards[i];
|
||||
(kbd as IKeymanKeyboardInstalled2).MCompileForBaseKeyboard(BaseKeyboardID);
|
||||
end;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
@ -11,13 +11,21 @@ type
|
|||
TfrmBaseKeyboard = class(TfrmWebContainer)
|
||||
procedure TntFormCreate(Sender: TObject);
|
||||
private
|
||||
FBaseKeyboardID: Integer;
|
||||
procedure Footer_Cancel;
|
||||
procedure Footer_OK(params: TStringList);
|
||||
protected
|
||||
procedure FireCommand(const command: WideString; params: TStringList); override;
|
||||
end;
|
||||
|
||||
function ConfigureBaseKeyboard: Boolean;
|
||||
(**
|
||||
* Displays a form for the user to select a base keyboard. If the user selects a base
|
||||
* keyboard, the KLID is used to Set the Base Keyboard.
|
||||
*
|
||||
* @returns True if the user selected base keyboard has been set.
|
||||
*)
|
||||
function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
|
|
@ -25,15 +33,21 @@ implementation
|
|||
|
||||
uses
|
||||
BaseKeyboards,
|
||||
kmint;
|
||||
ErrorControlledRegistry,
|
||||
RegistryKeys,
|
||||
keymanapi_TLB,
|
||||
Keyman.Configuration.System.BaseKeyboard,
|
||||
kmint,
|
||||
utilkmshell;
|
||||
|
||||
function ConfigureBaseKeyboard: Boolean;
|
||||
|
||||
function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;
|
||||
begin
|
||||
with TfrmBaseKeyboard.Create(nil) do
|
||||
try
|
||||
Result := ShowModal = mrOk;
|
||||
if Result then
|
||||
kmcom.Apply;
|
||||
SetBaseKeyboard(WindowHandle, FBaseKeyboardID)
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -65,9 +79,9 @@ var
|
|||
v: Integer;
|
||||
begin
|
||||
if not TryStrToInt('$'+params.Values['id'], v) then Exit;
|
||||
kmcom.Options['koBaseLayout'].Value := v;
|
||||
kmcom.Options.Apply;
|
||||
FBaseKeyboardID := v;
|
||||
ModalResult := mrOk;
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ uses
|
|||
Hints,
|
||||
HotkeyUtils,
|
||||
initprog,
|
||||
Keyman.Configuration.System.BaseKeyboard,
|
||||
Keyman.Configuration.System.TIPMaintenance,
|
||||
Keyman.Configuration.UI.UfrmDiagnosticTests,
|
||||
KeymanOptionNames,
|
||||
|
|
@ -198,6 +199,7 @@ uses
|
|||
Keyman.Configuration.UI.UfrmStartInstall,
|
||||
RegistryKeys,
|
||||
SupportXMLRenderer,
|
||||
UfrmBaseKeyboard,
|
||||
UfrmChangeHotkey,
|
||||
UfrmHTML,
|
||||
UfrmInstallKeyboardFromWeb,
|
||||
|
|
@ -665,8 +667,11 @@ end;
|
|||
|
||||
procedure TfrmMain.Options_BaseKeyboard; // I4169
|
||||
begin
|
||||
WaitForElevatedConfiguration(Handle, '-basekeyboard');
|
||||
// Refresh will be triggered by elevated process
|
||||
if ConfigureAndSetBaseKeyboard(Handle) then
|
||||
begin
|
||||
DoRefresh;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
procedure TfrmMain.Options_SettingsManager;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ type
|
|||
fmKeyboardWelcome, // I2569
|
||||
fmKeyboardPrint, // I2329
|
||||
fmBaseKeyboard, // I4169
|
||||
fmMCompileKbds,
|
||||
fmUpgradeMnemonicLayout, // I4553
|
||||
fmRepair,
|
||||
fmKeepInTouch,
|
||||
|
|
@ -114,6 +115,7 @@ uses
|
|||
GetOsVersion,
|
||||
help,
|
||||
HTMLHelpViewer,
|
||||
Keyman.Configuration.System.BaseKeyboard,
|
||||
Keyman.Configuration.UI.InstallFile,
|
||||
Keyman.Configuration.System.TIPMaintenance,
|
||||
Keyman.Configuration.System.UImportOlderVersionKeyboards11To13,
|
||||
|
|
@ -205,7 +207,7 @@ 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; var FDefaultBCP47: string; var FDefaultLangID: Integer): Boolean;
|
||||
var FParentWindow: THandle; var FDefaultBCP47: string; var FDefaultLangID, FBaseKeyboard: Integer): Boolean;
|
||||
var
|
||||
s: string;
|
||||
i: Integer;
|
||||
|
|
@ -220,6 +222,7 @@ begin
|
|||
FQuery := '';
|
||||
FMode := fmStart;
|
||||
KeyboardFileNames.Clear;
|
||||
FBaseKeyboard := 0;
|
||||
|
||||
i := 1;
|
||||
while i <= ParamCount do
|
||||
|
|
@ -262,6 +265,15 @@ begin
|
|||
else if s = '-bd' then FMode := fmBackgroundDownload
|
||||
else if s = '-an' then FMode := fmApplyInstallNow
|
||||
else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169
|
||||
else if s = '-bklid' then begin Inc(i); FBaseKeyboard := StrToIntDef('$' + ParamStr(i), 0); end
|
||||
else if s = '-mcompilekbds' then
|
||||
begin
|
||||
// Requires elevated context
|
||||
FMode := fmMCompileKbds;
|
||||
Inc(i);
|
||||
if i > ParamCount then Exit;
|
||||
FBaseKeyboard := StrToIntDef('$' + ParamStr(i), 0);
|
||||
end
|
||||
else if s = '-nowelcome' then FNoWelcome := True
|
||||
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
|
||||
else if s = '-kp' then FMode := fmKeyboardPrint // I2329
|
||||
|
|
@ -321,7 +333,7 @@ end;
|
|||
|
||||
procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FForce, FNoWelcome: Boolean;
|
||||
FLogFile, FQuery: string; FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean; FParentWindow: THandle;
|
||||
const FDefaultBCP47: string; FDefaultLangID: Integer); forward;
|
||||
const FDefaultBCP47: string; FDefaultLangID, FBaseKeyboard: Integer); forward;
|
||||
|
||||
procedure Run;
|
||||
var
|
||||
|
|
@ -332,7 +344,7 @@ var
|
|||
FForce: Boolean;
|
||||
FParentWindow: THandle;
|
||||
FLogFile: string;
|
||||
FDefaultLangID: Integer;
|
||||
FDefaultLangID, FBaseKeyboard: Integer;
|
||||
FDefaultBCP47, FDisablePackages, FDefaultUILanguage: string;
|
||||
FStartWithConfiguration: Boolean;
|
||||
begin
|
||||
|
|
@ -341,7 +353,7 @@ begin
|
|||
KeyboardFileNames := TStringList.Create;
|
||||
try
|
||||
FParentWindow := 0;
|
||||
if not Init(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID) then
|
||||
if not Init(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID, FBaseKeyboard) then
|
||||
begin
|
||||
//TODO: TUtilExecute.Shell(PChar('hh.exe mk:@MSITStore:'+ExtractFilePath(KMShellExe)+'keyman.chm::/context/keyman_usage.html'), SW_SHOWNORMAL);
|
||||
Exit;
|
||||
|
|
@ -349,7 +361,7 @@ begin
|
|||
|
||||
if not LoadKMCOM then Exit;
|
||||
try
|
||||
RunKMCOM(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID);
|
||||
RunKMCOM(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID, FBaseKeyboard);
|
||||
finally
|
||||
kmcom := nil;
|
||||
end;
|
||||
|
|
@ -388,7 +400,7 @@ end;
|
|||
|
||||
procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FForce, FNoWelcome: Boolean;
|
||||
FLogFile, FQuery: string; FDisablePackages, FDefaultUILanguage: string; FStartWithConfiguration: Boolean;
|
||||
FParentWindow: THandle; const FDefaultBCP47: string; FDefaultLangID: Integer);
|
||||
FParentWindow: THandle; const FDefaultBCP47: string; FDefaultLangID, FBaseKeyboard: Integer);
|
||||
var
|
||||
kdl: IKeymanDefaultLanguage;
|
||||
FIcon: string;
|
||||
|
|
@ -540,12 +552,17 @@ begin
|
|||
end;
|
||||
|
||||
fmBaseKeyboard: // I4169
|
||||
if ConfigureBaseKeyboard
|
||||
if ConfigureAndSetBaseKeyboard(0)
|
||||
then ExitCode := 0
|
||||
else ExitCode := 1;
|
||||
|
||||
fmMCompileKbds:
|
||||
if MCompileBaseKeyboard(FBaseKeyboard)
|
||||
then ExitCode := 0
|
||||
else ExitCode := 1;
|
||||
|
||||
fmInstall:
|
||||
if TInstallFile.Execute(KeyboardFileNames, FirstKeyboardFileName, FSilent, FNoWelcome, FLogFile)
|
||||
if TInstallFile.Execute(KeyboardFileNames, FirstKeyboardFileName, FSilent, FNoWelcome, FLogFile, FBaseKeyboard)
|
||||
then ExitCode := 0
|
||||
else ExitCode := 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ uses
|
|||
keymankeyboard, keymancontext, Classes, PackageInfo, keymankeyboardlanguagesfile;
|
||||
|
||||
type
|
||||
TKeymanKeyboardFile = class(TKeymanKeyboard, IKeymanKeyboardFile, IKeymanKeyboardFile2)
|
||||
TKeymanKeyboardFile = class(TKeymanKeyboard, IKeymanKeyboardFile, IKeymanKeyboardFile2, IKeymanKeyboardFile3)
|
||||
private
|
||||
FFileName: WideString;
|
||||
FError: Boolean;
|
||||
|
|
@ -52,6 +52,7 @@ type
|
|||
{ IKeymanKeyboardFile }
|
||||
procedure Install(Force: WordBool); safecall;
|
||||
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
|
||||
function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
|
||||
|
||||
{ IKeymanKeyboard }
|
||||
function Get_Copyright: WideString; override; safecall;
|
||||
|
|
@ -252,7 +253,7 @@ procedure TKeymanKeyboardFile.Install(Force: WordBool);
|
|||
begin
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FFileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force);
|
||||
Execute(FFileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -264,7 +265,23 @@ var
|
|||
begin
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FFileName, '', [], nil, Force);
|
||||
Execute(FFileName, '', [], nil, Force, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
||||
kki := Context.Keyboards as IKeymanKeyboardsInstalled;
|
||||
kki.Refresh;
|
||||
Result := kki.Items[FFileName];
|
||||
end;
|
||||
|
||||
function TKeymanKeyboardFile.Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled;
|
||||
var
|
||||
kki: IKeymanKeyboardsInstalled;
|
||||
begin
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FFileName, '', [], nil, Force, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ type
|
|||
TKeymanKeyboardInstalled = class( // I3581
|
||||
TKeymanKeyboard,
|
||||
IIntKeymanKeyboardInstalled,
|
||||
IKeymanKeyboardInstalled)
|
||||
IKeymanKeyboardInstalled,
|
||||
IKeymanKeyboardInstalled2)
|
||||
private
|
||||
FRegKeyboard: TRegKeyboard;
|
||||
FVisualKeyboard: IKeymanVisualKeyboard;
|
||||
|
|
@ -109,9 +110,11 @@ type
|
|||
{ IIntKeymanKeyboardInstalled }
|
||||
function RegKeyboard: TRegKeyboard;
|
||||
procedure ClearVisualKeyboard;
|
||||
procedure UpdateBaseLayout; // I4169
|
||||
procedure RefreshInstallation;
|
||||
|
||||
{ IKeymanKeyboardInstalled2 }
|
||||
procedure MCompileForBaseKeyboard(KLID: Integer); safecall;
|
||||
|
||||
public
|
||||
constructor Create(AContext: TKeymanContext; const Name: string);
|
||||
destructor Destroy; override;
|
||||
|
|
@ -151,17 +154,6 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TKeymanKeyboardInstalled.UpdateBaseLayout; // I4169
|
||||
begin
|
||||
if FRegKeyboard.MnemonicLayout and FileExists(FRegKeyboard.KeymanFile) then // I4615
|
||||
with TKPRecompileMnemonicKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FRegKeyboard.KeymanFile, FRegKeyboard.PackageName);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TKeymanKeyboardInstalled.Get_Copyright: WideString;
|
||||
begin
|
||||
Result := FRegKeyboard.Copyright;
|
||||
|
|
@ -475,5 +467,21 @@ begin
|
|||
Result := FRegKeyboard;
|
||||
end;
|
||||
|
||||
{ IKeymanKeyboardInstalled2 }
|
||||
procedure TKeymanKeyboardInstalled.MCompileForBaseKeyboard(KLID: Integer); safecall;
|
||||
var
|
||||
RecompileMnemonicKeyboard: TKPRecompileMnemonicKeyboard;
|
||||
begin
|
||||
if FRegKeyboard.MnemonicLayout and FileExists(FRegKeyboard.KeymanFile) then
|
||||
begin
|
||||
RecompileMnemonicKeyboard := TKPRecompileMnemonicKeyboard.Create(Context);
|
||||
try
|
||||
RecompileMnemonicKeyboard.Execute(FRegKeyboard.KeymanFile, FRegKeyboard.PackageName, KLID);
|
||||
finally
|
||||
RecompileMnemonicKeyboard.Free;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ uses
|
|||
keymanerrorcodes, keymankeyboardinstalled, keymankeyboard, internalinterfaces;
|
||||
|
||||
type
|
||||
TKeymanKeyboardsInstalled = class(TKeymanAutoCollectionObject, IKeymanKeyboardsInstalled, IKeymanKeyboardsInstalled2, IIntKeymanKeyboardsInstalled) // I4376
|
||||
TKeymanKeyboardsInstalled = class(TKeymanAutoCollectionObject, IKeymanKeyboardsInstalled,
|
||||
IKeymanKeyboardsInstalled2, IIntKeymanKeyboardsInstalled, IKeymanKeyboardsInstalled3) // I4376
|
||||
private
|
||||
FKeyboards: TKeyboardList;
|
||||
procedure TriggerWindowsLanguageSync;
|
||||
|
|
@ -55,6 +56,7 @@ type
|
|||
procedure Install(const Filename: WideString; Force: WordBool); safecall;
|
||||
procedure Apply; safecall;
|
||||
function Install2(const Filename: WideString; Force: WordBool): IKeymanKeyboardInstalled; safecall;
|
||||
function Install3(const Filename: WideString; Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
|
||||
procedure RefreshInstalledKeyboards; safecall;
|
||||
|
||||
{ IIntKeymanKeyboardsInstalled }
|
||||
|
|
@ -101,7 +103,7 @@ procedure TKeymanKeyboardsInstalled.Install(const Filename: WideString; Force: W
|
|||
begin
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force);
|
||||
Execute(FileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -112,7 +114,21 @@ function TKeymanKeyboardsInstalled.Install2(const Filename: WideString;
|
|||
begin
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FileName, '', [], nil, Force);
|
||||
Execute(FileName, '', [], nil, Force, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
||||
DoRefresh;
|
||||
Result := Get_Items(FileName);
|
||||
end;
|
||||
|
||||
function TKeymanKeyboardsInstalled.Install3(const Filename: WideString;
|
||||
Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled;
|
||||
begin
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FileName, '', [], nil, Force, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: keymanoptions
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 20 Jun 2006
|
||||
|
||||
Modified Date: 6 Feb 2015
|
||||
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 AutoRefershKeyman call
|
||||
12 Aug 2008 - mcdurdin - Avoid crash with missing options
|
||||
|
|
@ -67,6 +67,7 @@ uses
|
|||
ErrorControlledRegistry,
|
||||
RegistryKeys,
|
||||
Glossary,
|
||||
isadmin,
|
||||
Keyman.System.BaseKeyboard,
|
||||
KeymanOptionNames,
|
||||
keymanerrorcodes;
|
||||
|
|
@ -111,29 +112,8 @@ begin
|
|||
end;
|
||||
|
||||
procedure TKeymanOptions.Apply;
|
||||
var
|
||||
I, FOldBaseLayout: Integer;
|
||||
begin
|
||||
with TRegistryErrorControlled.Create do // I3717
|
||||
try
|
||||
if OpenKey(SRegKey_KeymanEngine_CU, True) then
|
||||
begin
|
||||
if ValueExists(SRegValue_UnderlyingLayout)
|
||||
then FOldBaseLayout := StrToIntDef('$'+ReadString(SRegValue_UnderlyingLayout),0) // I3759
|
||||
else FOldBaseLayout := TBaseKeyboard.GetDefaultBaseLayoutID;
|
||||
end
|
||||
else
|
||||
FOldBaseLayout := TBaseKeyboard.GetDefaultBaseLayoutID;
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
||||
FInternalOptions.Save(Context);
|
||||
|
||||
if FOldBaseLayout <> Get_Items('koBaseLayout').Value then
|
||||
for I := 0 to Context.Keyboards.Count - 1 do // I4169
|
||||
(Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout;
|
||||
|
||||
Context.Control.AutoApplyKeyman;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: keymanpackagefile
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 20 Jun 2006
|
||||
|
||||
Modified Date: 29 Mar 2010
|
||||
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 - Avoid processmessages in unzip
|
||||
04 Dec 2006 - mcdurdin - Add Serialize function, support ShortcutRootPath in installation
|
||||
|
|
@ -37,7 +37,7 @@ uses
|
|||
keymanpackagecontentfiles, StdVcl, kmpinffile, KeymanContext, Graphics, Classes, internalinterfaces;
|
||||
|
||||
type
|
||||
TKeymanPackageFile = class(TKeymanAutoObject, IKeymanPackage, IKeymanPackageFile, IKeymanPackageFile2)
|
||||
TKeymanPackageFile = class(TKeymanAutoObject, IKeymanPackage, IKeymanPackageFile, IKeymanPackageFile2, IKeymanPackageFile3)
|
||||
private
|
||||
FSourcePath: string;
|
||||
FSubFiles: IKeymanPackageContentFiles;
|
||||
|
|
@ -74,6 +74,7 @@ type
|
|||
{ IKeymanPackageFile }
|
||||
procedure Install(Force: WordBool); safecall;
|
||||
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
|
||||
function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
|
||||
public
|
||||
constructor Create(AContext: TKeymanContext; const Filename: Widestring);
|
||||
destructor Destroy; override;
|
||||
|
|
@ -175,7 +176,7 @@ begin
|
|||
o := [ipLegacyRegisterAndInstallProfiles];
|
||||
if Force then
|
||||
Include(o, ipForce);
|
||||
Execute(FFileName, o);
|
||||
Execute(FFileName, o, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -191,7 +192,27 @@ begin
|
|||
o := [];
|
||||
if Force then
|
||||
Include(o, ipForce);
|
||||
Execute(FFileName, o);
|
||||
Execute(FFileName, o, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
||||
kpi := Context.Packages as IKeymanPackagesInstalled;
|
||||
kpi.Refresh;
|
||||
Result := kpi.Items[FFileName];
|
||||
end;
|
||||
|
||||
function TKeymanPackageFile.Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled;
|
||||
var
|
||||
o: TKPInstallPackageOptions;
|
||||
kpi: IKeymanPackagesInstalled;
|
||||
begin
|
||||
with TKPInstallPackage.Create(Context) do
|
||||
try
|
||||
o := [];
|
||||
if Force then
|
||||
Include(o, ipForce);
|
||||
Execute(FFileName, o, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -211,7 +232,7 @@ var
|
|||
begin
|
||||
if not FileExists(FFileName) then
|
||||
raise Exception.Create('File '+FFileName+' does not exist.');
|
||||
|
||||
|
||||
if GetTempPath(260, buf) = 0 then
|
||||
raise Exception.Create('Unable to get temporary path: ' + IntToHex(GetLastError, 8) + ' ' + SysErrorMessage(GetLastError));
|
||||
FTempOutPath := buf;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ type
|
|||
property Items[Index: Integer]: IIntKeymanPackageInstalled read GetItem write SetItem; default;
|
||||
end;
|
||||
|
||||
TKeymanPackagesInstalled = class(TKeymanAutoCollectionObject, IKeymanPackagesInstalled, IKeymanPackagesInstalled2)
|
||||
TKeymanPackagesInstalled = class(TKeymanAutoCollectionObject,
|
||||
IKeymanPackagesInstalled, IKeymanPackagesInstalled2, IKeymanPackagesInstalled3)
|
||||
private
|
||||
FPackages: TPackageList;
|
||||
protected
|
||||
|
|
@ -51,6 +52,7 @@ type
|
|||
function IndexOf(const ID: WideString): Integer; safecall;
|
||||
procedure Install(const Filename: WideString; Force: WordBool); safecall;
|
||||
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
|
||||
function Install3(const Filename: WideString; Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
|
||||
public
|
||||
constructor Create(AContext: TKeymanContext);
|
||||
destructor Destroy; override;
|
||||
|
|
@ -118,7 +120,7 @@ begin
|
|||
o := [ipLegacyRegisterAndInstallProfiles];
|
||||
if Force then
|
||||
Include(o, ipForce);
|
||||
Execute(Filename, o);
|
||||
Execute(Filename, o, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -136,7 +138,29 @@ begin
|
|||
o := [];
|
||||
if Force then
|
||||
Include(o, ipForce);
|
||||
Execute(Filename, o);
|
||||
Execute(Filename, o, 0);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
||||
DoRefresh;
|
||||
Result := Get_Items(Filename);
|
||||
|
||||
KL.MethodExit(Self, 'Install2');
|
||||
end;
|
||||
|
||||
function TKeymanPackagesInstalled.Install3(const Filename: WideString;
|
||||
Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled;
|
||||
var
|
||||
o: TKPInstallPackageOptions;
|
||||
begin
|
||||
KL.MethodEnter(Self, 'Install2', [Filename, Force]);
|
||||
with TKPInstallPackage.Create(Context) do
|
||||
try
|
||||
o := [];
|
||||
if Force then
|
||||
Include(o, ipForce);
|
||||
Execute(Filename, o, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
|
|||
199
windows/src/engine/kmcomapi/keymanapi_TLB.pas
generated
199
windows/src/engine/kmcomapi/keymanapi_TLB.pas
generated
|
|
@ -12,7 +12,7 @@ unit keymanapi_TLB;
|
|||
// ************************************************************************ //
|
||||
|
||||
// $Rev: 52393 $
|
||||
// File generated on 16/09/2021 6:54:44 PM from Type Library described below.
|
||||
// File generated on 31/08/2026 5:28:13 PM from Type Library described below.
|
||||
|
||||
// ************************************************************************ //
|
||||
// Type Lib: C:\Projects\keyman\app\windows\src\engine\kmcomapi\kmcomapi (1)
|
||||
|
|
@ -87,11 +87,16 @@ const
|
|||
IID_IKeymanKeyboardLanguagesInstalled: TGUID = '{7DC22BC0-85BB-45C0-8EDB-A2F4BD1D500B}';
|
||||
IID_IKeymanKeyboardLanguagesFile: TGUID = '{5F90BCDA-F1C1-433A-8FD0-B498299D3C30}';
|
||||
IID_IKeymanKeyboardsInstalled2: TGUID = '{EA57C94F-C140-485E-941A-3F1D5A229024}';
|
||||
IID_IKeymanKeyboardInstalled2: TGUID = '{3086C85C-932A-4726-BF76-2D74DD133AC9}';
|
||||
IID_IKeymanPackagesInstalled2: TGUID = '{F23B9848-2AEF-4A2B-BC3A-292E3A00D691}';
|
||||
IID_IKeymanKeyboardFile2: TGUID = '{EDE4326B-51F4-42D5-8251-B20B71993EC8}';
|
||||
IID_IKeymanPackageFile2: TGUID = '{9B43B6BC-C622-47EF-915E-6780CF53BAAA}';
|
||||
IID_IKeymanKeyboardLanguageInstalled2: TGUID = '{414C26E6-BFAC-4A70-9EA1-E525BA9BBA7E}';
|
||||
IID_IKeymanKeyboardLanguagesInstalled2: TGUID = '{628FF2E6-B490-462E-8FC7-7AE53B9D392C}';
|
||||
IID_IKeymanKeyboardFile3: TGUID = '{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}';
|
||||
IID_IKeymanPackageFile3: TGUID = '{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}';
|
||||
IID_IKeymanKeyboardsInstalled3: TGUID = '{B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641}';
|
||||
IID_IKeymanPackagesInstalled3: TGUID = '{3F8C2D71-94A6-4B0E-87D5-C1E3A9F62458}';
|
||||
CLASS_Keyman: TGUID = '{CF46549D-4D2D-4679-A2E1-23A815F172F8}';
|
||||
IID_IKeymanDefaultLanguage: TGUID = '{77BAB934-B7DF-4304-AFA6-B8F6BEC16516}';
|
||||
|
||||
|
|
@ -248,6 +253,8 @@ type
|
|||
IKeymanKeyboardLanguagesFileDisp = dispinterface;
|
||||
IKeymanKeyboardsInstalled2 = interface;
|
||||
IKeymanKeyboardsInstalled2Disp = dispinterface;
|
||||
IKeymanKeyboardInstalled2 = interface;
|
||||
IKeymanKeyboardInstalled2Disp = dispinterface;
|
||||
IKeymanPackagesInstalled2 = interface;
|
||||
IKeymanPackagesInstalled2Disp = dispinterface;
|
||||
IKeymanKeyboardFile2 = interface;
|
||||
|
|
@ -258,6 +265,14 @@ type
|
|||
IKeymanKeyboardLanguageInstalled2Disp = dispinterface;
|
||||
IKeymanKeyboardLanguagesInstalled2 = interface;
|
||||
IKeymanKeyboardLanguagesInstalled2Disp = dispinterface;
|
||||
IKeymanKeyboardFile3 = interface;
|
||||
IKeymanKeyboardFile3Disp = dispinterface;
|
||||
IKeymanPackageFile3 = interface;
|
||||
IKeymanPackageFile3Disp = dispinterface;
|
||||
IKeymanKeyboardsInstalled3 = interface;
|
||||
IKeymanKeyboardsInstalled3Disp = dispinterface;
|
||||
IKeymanPackagesInstalled3 = interface;
|
||||
IKeymanPackagesInstalled3Disp = dispinterface;
|
||||
IKeymanDefaultLanguage = interface;
|
||||
IKeymanDefaultLanguageDisp = dispinterface;
|
||||
|
||||
|
|
@ -1590,6 +1605,51 @@ type
|
|||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanKeyboardInstalled2
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {3086C85C-932A-4726-BF76-2D74DD133AC9}
|
||||
// *********************************************************************//
|
||||
IKeymanKeyboardInstalled2 = interface(IKeymanKeyboardInstalled)
|
||||
['{3086C85C-932A-4726-BF76-2D74DD133AC9}']
|
||||
procedure MCompileForBaseKeyboard(KLID: Integer); safecall;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// DispIntf: IKeymanKeyboardInstalled2Disp
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {3086C85C-932A-4726-BF76-2D74DD133AC9}
|
||||
// *********************************************************************//
|
||||
IKeymanKeyboardInstalled2Disp = dispinterface
|
||||
['{3086C85C-932A-4726-BF76-2D74DD133AC9}']
|
||||
procedure MCompileForBaseKeyboard(KLID: Integer); dispid 288;
|
||||
property IconFilename: WideString readonly dispid 257;
|
||||
procedure InstallVisualKeyboard(const Filename: WideString); dispid 258;
|
||||
property KeymanID: Integer readonly dispid 259;
|
||||
property Languages: IKeymanKeyboardLanguagesInstalled readonly dispid 260;
|
||||
property Loaded: WordBool dispid 261;
|
||||
property Options: IKeymanKeyboardOptions readonly dispid 262;
|
||||
property OwnerPackage: IKeymanPackageInstalled readonly dispid 263;
|
||||
property VisualKeyboard: IKeymanVisualKeyboard readonly dispid 264;
|
||||
procedure Uninstall; dispid 265;
|
||||
property Bitmap: IPicture readonly dispid 1;
|
||||
property Copyright: WideString readonly dispid 2;
|
||||
property DefaultBCP47Languages: WideString readonly dispid 3;
|
||||
property DefaultPrimaryLanguage: Integer readonly dispid 4;
|
||||
property DefaultWindowsLanguages: WideString readonly dispid 5;
|
||||
property DefaultHotkey: IKeymanHotkey readonly dispid 6;
|
||||
property Encodings: KeymanKeyboardEncodings readonly dispid 7;
|
||||
property Filename: WideString readonly dispid 8;
|
||||
function GetCharsUsed: WideString; dispid 9;
|
||||
property ID: WideString readonly dispid 10;
|
||||
property LayoutType: KeymanKeyboardLayoutType readonly dispid 11;
|
||||
property Message: WideString readonly dispid 12;
|
||||
property Name: WideString readonly dispid 13;
|
||||
property Version: WideString readonly dispid 14;
|
||||
function SerializeXML(Flags: tagKeymanSerializeFlags; const ImagePath: WideString;
|
||||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanPackagesInstalled2
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
|
|
@ -1768,6 +1828,143 @@ type
|
|||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanKeyboardFile3
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}
|
||||
// *********************************************************************//
|
||||
IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
|
||||
['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
|
||||
function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// DispIntf: IKeymanKeyboardFile3Disp
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}
|
||||
// *********************************************************************//
|
||||
IKeymanKeyboardFile3Disp = dispinterface
|
||||
['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
|
||||
function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; dispid 289;
|
||||
procedure Install(Force: WordBool); dispid 256;
|
||||
property Languages: IKeymanKeyboardLanguagesFile readonly dispid 402;
|
||||
property Bitmap: IPicture readonly dispid 1;
|
||||
property Copyright: WideString readonly dispid 2;
|
||||
property DefaultBCP47Languages: WideString readonly dispid 3;
|
||||
property DefaultPrimaryLanguage: Integer readonly dispid 4;
|
||||
property DefaultWindowsLanguages: WideString readonly dispid 5;
|
||||
property DefaultHotkey: IKeymanHotkey readonly dispid 6;
|
||||
property Encodings: KeymanKeyboardEncodings readonly dispid 7;
|
||||
property Filename: WideString readonly dispid 8;
|
||||
function GetCharsUsed: WideString; dispid 9;
|
||||
property ID: WideString readonly dispid 10;
|
||||
property LayoutType: KeymanKeyboardLayoutType readonly dispid 11;
|
||||
property Message: WideString readonly dispid 12;
|
||||
property Name: WideString readonly dispid 13;
|
||||
property Version: WideString readonly dispid 14;
|
||||
function SerializeXML(Flags: tagKeymanSerializeFlags; const ImagePath: WideString;
|
||||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanPackageFile3
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}
|
||||
// *********************************************************************//
|
||||
IKeymanPackageFile3 = interface(IKeymanPackageFile)
|
||||
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
|
||||
function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// DispIntf: IKeymanPackageFile3Disp
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}
|
||||
// *********************************************************************//
|
||||
IKeymanPackageFile3Disp = dispinterface
|
||||
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
|
||||
function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; dispid 292;
|
||||
procedure Install(Force: WordBool); dispid 256;
|
||||
property Author: WideString readonly dispid 1;
|
||||
property AuthorEmail: WideString readonly dispid 2;
|
||||
property Copyright: WideString readonly dispid 3;
|
||||
property Filename: WideString readonly dispid 4;
|
||||
property Files: IKeymanPackageContentFiles readonly dispid 5;
|
||||
property Fonts: IKeymanPackageContentFonts readonly dispid 6;
|
||||
property Graphic: IPicture readonly dispid 7;
|
||||
property GraphicFile: IKeymanPackageContentFile readonly dispid 8;
|
||||
property ID: WideString readonly dispid 9;
|
||||
property KeyboardOptionsFile: IKeymanPackageContentFile readonly dispid 10;
|
||||
property Keyboards: IKeymanPackageContentKeyboards readonly dispid 11;
|
||||
property Name: WideString readonly dispid 12;
|
||||
property ReadmeFile: IKeymanPackageContentFile readonly dispid 13;
|
||||
property UsageFile: IKeymanPackageContentFile readonly dispid 14;
|
||||
property Version: WideString readonly dispid 15;
|
||||
property WelcomeFile: IKeymanPackageContentFile readonly dispid 16;
|
||||
property Website: WideString readonly dispid 17;
|
||||
function SerializeXML(Flags: tagKeymanSerializeFlags; const ImagePath: WideString;
|
||||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanKeyboardsInstalled3
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641}
|
||||
// *********************************************************************//
|
||||
IKeymanKeyboardsInstalled3 = interface(IKeymanKeyboardsInstalled)
|
||||
['{B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641}']
|
||||
function Install3(const Filename: WideString; Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// DispIntf: IKeymanKeyboardsInstalled3Disp
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641}
|
||||
// *********************************************************************//
|
||||
IKeymanKeyboardsInstalled3Disp = dispinterface
|
||||
['{B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641}']
|
||||
function Install3(const Filename: WideString; Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; dispid 290;
|
||||
property Items[Index: OleVariant]: IKeymanKeyboardInstalled readonly dispid 0; default;
|
||||
function GetKeyboardFromFile(const Filename: WideString): IKeymanKeyboardFile; dispid 16;
|
||||
procedure Install(const Filename: WideString; Force: WordBool); dispid 17;
|
||||
procedure Apply; dispid 18;
|
||||
function IndexOf(const ID: WideString): Integer; dispid 5;
|
||||
property Count: Integer readonly dispid 1;
|
||||
property _NewEnum: IUnknown readonly dispid -4;
|
||||
procedure Refresh; dispid 2;
|
||||
function SerializeXML(Flags: tagKeymanSerializeFlags; const ImagePath: WideString;
|
||||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanPackagesInstalled3
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {3F8C2D71-94A6-4B0E-87D5-C1E3A9F62458}
|
||||
// *********************************************************************//
|
||||
IKeymanPackagesInstalled3 = interface(IKeymanPackagesInstalled)
|
||||
['{3F8C2D71-94A6-4B0E-87D5-C1E3A9F62458}']
|
||||
function Install3(const Filename: WideString; Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// DispIntf: IKeymanPackagesInstalled3Disp
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
// GUID: {3F8C2D71-94A6-4B0E-87D5-C1E3A9F62458}
|
||||
// *********************************************************************//
|
||||
IKeymanPackagesInstalled3Disp = dispinterface
|
||||
['{3F8C2D71-94A6-4B0E-87D5-C1E3A9F62458}']
|
||||
function Install3(const Filename: WideString; Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; dispid 291;
|
||||
property Items[Index: OleVariant]: IKeymanPackageInstalled readonly dispid 0; default;
|
||||
function GetPackageFromFile(const Filename: WideString): IKeymanPackageFile; dispid 16;
|
||||
procedure Install(const Filename: WideString; Force: WordBool); dispid 17;
|
||||
function IndexOf(const ID: WideString): Integer; dispid 18;
|
||||
property Count: Integer readonly dispid 1;
|
||||
property _NewEnum: IUnknown readonly dispid -4;
|
||||
procedure Refresh; dispid 2;
|
||||
function SerializeXML(Flags: tagKeymanSerializeFlags; const ImagePath: WideString;
|
||||
out References: OleVariant): WideString; dispid 401;
|
||||
end;
|
||||
|
||||
// *********************************************************************//
|
||||
// Interface: IKeymanDefaultLanguage
|
||||
// Flags: (4416) Dual OleAutomation Dispatchable
|
||||
|
|
|
|||
|
|
@ -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 16/09/2021 6:54:45 PM (- $Rev: 12980 $, 32940875).
|
||||
// File generated on 31/08/2026 5:28:15 PM (- $Rev: 12980 $, 11059984).
|
||||
|
||||
[
|
||||
uuid(F16E2A9A-DA46-4EA3-BFF3-BA46B480C961),
|
||||
|
|
@ -60,11 +60,16 @@ library keymanapi
|
|||
interface IKeymanKeyboardLanguagesInstalled;
|
||||
interface IKeymanKeyboardLanguagesFile;
|
||||
interface IKeymanKeyboardsInstalled2;
|
||||
interface IKeymanKeyboardInstalled2;
|
||||
interface IKeymanPackagesInstalled2;
|
||||
interface IKeymanKeyboardFile2;
|
||||
interface IKeymanPackageFile2;
|
||||
interface IKeymanKeyboardLanguageInstalled2;
|
||||
interface IKeymanKeyboardLanguagesInstalled2;
|
||||
interface IKeymanKeyboardFile3;
|
||||
interface IKeymanPackageFile3;
|
||||
interface IKeymanKeyboardsInstalled3;
|
||||
interface IKeymanPackagesInstalled3;
|
||||
interface IKeymanBCP47Canonicalization;
|
||||
interface IKeymanDefaultLanguage;
|
||||
|
||||
|
|
@ -936,6 +941,19 @@ library keymanapi
|
|||
HRESULT _stdcall RefreshInstalledKeyboards(void);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(3086C85C-932A-4726-BF76-2D74DD133AC9),
|
||||
version(19.0),
|
||||
helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2"),
|
||||
dual,
|
||||
oleautomation
|
||||
]
|
||||
interface IKeymanKeyboardInstalled2: IKeymanKeyboardInstalled
|
||||
{
|
||||
[id(0x00000120)]
|
||||
HRESULT _stdcall MCompileForBaseKeyboard(long KLID);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(F23B9848-2AEF-4A2B-BC3A-292E3A00D691),
|
||||
version(14.0),
|
||||
|
|
@ -1007,6 +1025,58 @@ library keymanapi
|
|||
HRESULT _stdcall Add([in] BSTR BCP47Tag, [out, retval] IKeymanKeyboardLanguageInstalled** Result);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4),
|
||||
version(19.0),
|
||||
helpstring("http://help.keyman.com/developer/engine/desktop/19.0/api/IKeymanKeyboardFile3"),
|
||||
dual,
|
||||
oleautomation
|
||||
]
|
||||
interface IKeymanKeyboardFile3: IKeymanKeyboardFile
|
||||
{
|
||||
[id(0x00000121)]
|
||||
HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7),
|
||||
version(19.0),
|
||||
helpstring("http://help.keyman.com/developer/engine/desktop/19.0/api/IKeymanPackageFile3"),
|
||||
dual,
|
||||
oleautomation
|
||||
]
|
||||
interface IKeymanPackageFile3: IKeymanPackageFile
|
||||
{
|
||||
[id(0x00000124)]
|
||||
HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageInstalled** PackageResult);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
|
||||
version(19.0),
|
||||
helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\x03"),
|
||||
dual,
|
||||
oleautomation
|
||||
]
|
||||
interface IKeymanKeyboardsInstalled3: IKeymanKeyboardsInstalled
|
||||
{
|
||||
[id(0x00000122)]
|
||||
HRESULT _stdcall Install3([in] BSTR Filename, [in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(3F8C2D71-94A6-4B0E-87D5-C1E3A9F62458),
|
||||
version(19.0),
|
||||
helpstring("http://help.keyman.com/developer/engine/desktop/19.0/api/IKeymanPackagesInstalled3"),
|
||||
dual,
|
||||
oleautomation
|
||||
]
|
||||
interface IKeymanPackagesInstalled3: IKeymanPackagesInstalled
|
||||
{
|
||||
[id(0x00000123)]
|
||||
HRESULT _stdcall Install3([in] BSTR Filename, [in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageInstalled** PackageResult);
|
||||
};
|
||||
|
||||
[
|
||||
uuid(CA3B3B00-EA42-4EED-9043-D1A1F1842D52),
|
||||
dual,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ type
|
|||
ikLegacyRegisterAndInstallProfiles);
|
||||
|
||||
TKPInstallKeyboard = class(TKPBase)
|
||||
procedure Execute(const FileName, PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean);
|
||||
procedure Execute(const FileName: string; const PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean; BaseKeyboardID: Integer);
|
||||
procedure RegisterProfiles(const FileName, PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; PackageLanguageMetadata: TPackageKeyboardLanguageList);
|
||||
private
|
||||
procedure LegacyRegisterAndInstallLanguageProfile(Langs: array of Integer;
|
||||
|
|
@ -112,7 +112,7 @@ uses
|
|||
utiltsf,
|
||||
keymanapi_TLB;
|
||||
|
||||
procedure TKPInstallKeyboard.Execute(const FileName, PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean);
|
||||
procedure TKPInstallKeyboard.Execute(const FileName: string; const PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean; BaseKeyboardID: Integer);
|
||||
var
|
||||
ki: TKeyboardInfo;
|
||||
FDestPath: string;
|
||||
|
|
@ -125,6 +125,9 @@ var
|
|||
FExitCode: Integer;
|
||||
FKVKName: WideString;
|
||||
FCreatedIcon: Boolean;
|
||||
ElevatedBaseKeyboardID: Integer;
|
||||
KeymanContext: TKeymanContext;
|
||||
RecompileMnemonicKeyboard: TKPRecompileMnemonicKeyboard;
|
||||
begin
|
||||
KL.MethodEnter(Self, 'Execute', [FileName,PackageID,ikPartOfPackage in FInstallOptions ,Force]);
|
||||
try
|
||||
|
|
@ -245,14 +248,21 @@ begin
|
|||
KL.Log(FLogText);
|
||||
end;
|
||||
|
||||
// Recompile a mnemonic layout to the user's selected base layout
|
||||
// Recompile a mnemonic layout to the user's selected base layout. If
|
||||
// the baselayout has not been passed through (=0) then use the current
|
||||
// process configured value which is likely the Admin user
|
||||
if ki.MnemonicLayout then // I4169
|
||||
begin
|
||||
with TKPRecompileMnemonicKeyboard.Create(Context) do
|
||||
KeymanContext := Context as TKeymanContext;
|
||||
ElevatedBaseKeyboardID := (KeymanContext.Options as IKeymanOptions).Items['koBaseLayout'].Value;
|
||||
RecompileMnemonicKeyboard := TKPRecompileMnemonicKeyboard.Create(Context);
|
||||
try
|
||||
Execute(FDestFileName, PackageID);
|
||||
if (BaseKeyboardID = 0) then
|
||||
RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, ElevatedBaseKeyboardID)
|
||||
else
|
||||
RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
RecompileMnemonicKeyboard.Free;
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: kprecompilemnemonickeyboard
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 24 Apr 2014
|
||||
|
||||
Modified Date: 13 Mar 2015
|
||||
Authors: mcdurdin
|
||||
Related Files:
|
||||
Dependencies:
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
History: 24 Apr 2014 - mcdurdin - I4174 - V9 - mcompile logs should be stored in diag folder
|
||||
06 Feb 2015 - mcdurdin - I4552 - V9.0 - Add mnemonic recompile option to ignore deadkeys
|
||||
13 Mar 2015 - mcdurdin - I4615 - CrashID:kmshell.exe_9.0.481.0_2C6795CE_EOleException
|
||||
|
|
@ -27,7 +27,7 @@ uses
|
|||
|
||||
type
|
||||
TKPRecompileMnemonicKeyboard = class(TKPBase)
|
||||
procedure Execute(const FileName: string; const PackageName: string);
|
||||
procedure Execute(const FileName: string; const PackageName: string; BaseKeyboardID: Cardinal);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -42,13 +42,12 @@ uses
|
|||
Winapi.Windows,
|
||||
|
||||
errorcontrolledregistry,
|
||||
keymancontext,
|
||||
keymanerrorcodes,
|
||||
KeymanPaths,
|
||||
keymanapi_TLB,
|
||||
RegistryKeys,
|
||||
utilexecute,
|
||||
utilkeyman,
|
||||
utilfiletypes,
|
||||
utilsystem;
|
||||
|
||||
function GetKeyboardLayoutFileName(id: Integer): string;
|
||||
|
|
@ -67,7 +66,7 @@ begin
|
|||
Result := '';
|
||||
end;
|
||||
|
||||
procedure TKPRecompileMnemonicKeyboard.Execute(const FileName,PackageName: string);
|
||||
procedure TKPRecompileMnemonicKeyboard.Execute(const FileName,PackageName: string; BaseKeyboardID: Cardinal);
|
||||
var
|
||||
FDestPath, FDestFileName: string;
|
||||
FBaseKeyboardIDHex: string;
|
||||
|
|
@ -76,7 +75,6 @@ var
|
|||
FExitCode: Integer;
|
||||
FMCompilePath: string;
|
||||
FBaseKeyboardFileName: string;
|
||||
BaseKeyboardID: Cardinal;
|
||||
FDestDeadkeyFileName: string;
|
||||
FCommand: string;
|
||||
begin
|
||||
|
|
@ -84,13 +82,10 @@ begin
|
|||
then FDestPath := GetPackageInstallPath(PackageName) // I3581
|
||||
else FDestPath := GetKeyboardInstallPath(FileName); // I3581
|
||||
|
||||
with Context as TKeymanContext do
|
||||
BaseKeyboardID := (Options as IKeymanOptions).Items['koBaseLayout'].Value;
|
||||
|
||||
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
|
||||
FBaseFileName := FDestPath + '\' + ExtractFileName(FileName); // I3581
|
||||
FDestFileName := ChangeFileExt(FBaseFileName, '') + '-'+FBaseKeyboardIDHex + '.kmx';
|
||||
FDestDeadkeyFileName := ChangeFileExt(FBaseFileName, '') + '-'+FBaseKeyboardIDHex + '-d.kmx'; // I4552
|
||||
FDestFileName := BuildKeyboardFilenameWithBaseKeyboardID(FBaseFileName, FBaseKeyboardIDHex);
|
||||
FDestDeadkeyFileName := BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(FBaseFileName, FBaseKeyboardIDHex); // I4552
|
||||
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
|
||||
|
||||
{ Recompile with the traditional deadkey behaviour }
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ type
|
|||
|
||||
TKPInstallPackage = class(TKPBase)
|
||||
public
|
||||
procedure Execute(const FileName: string; Options: TKPInstallPackageOptions);
|
||||
procedure Execute(const FileName: string; Options: TKPInstallPackageOptions; BaseKeyboardID: Integer);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -85,7 +85,7 @@ uses
|
|||
{ TKPInstallPackage }
|
||||
|
||||
|
||||
procedure TKPInstallPackage.Execute(const FileName: string; Options: TKPInstallPackageOptions);
|
||||
procedure TKPInstallPackage.Execute(const FileName: string; Options: TKPInstallPackageOptions; BaseKeyboardID: Integer);
|
||||
function GetHHIcon: string;
|
||||
var
|
||||
buf: array[0..260] of char;
|
||||
|
|
@ -112,7 +112,7 @@ var
|
|||
FErrorValue: Cardinal;
|
||||
FSrcFileName: string;
|
||||
|
||||
procedure InstallKeyboard(FileName: string);
|
||||
procedure InstallKeyboard(FileName: string; BaseKeyboardID: Integer);
|
||||
var
|
||||
FOptions: TKPInstallKeyboardOptions;
|
||||
kbd: TPackageKeyboard;
|
||||
|
|
@ -132,7 +132,7 @@ var
|
|||
|
||||
with TKPInstallKeyboard.Create(Context) do
|
||||
try
|
||||
Execute(FileName, PackageName, FOptions, FLanguages, ipForce in Options);
|
||||
Execute(FileName, PackageName, FOptions, FLanguages, ipForce in Options, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
@ -168,8 +168,8 @@ begin
|
|||
except
|
||||
on E:EZipException do
|
||||
begin
|
||||
ErrorFmt(KMN_E_Install_InvalidFile, VarArrayOf([ExtractFileName(FileName), E.Message, 0]));
|
||||
raise;
|
||||
ErrorFmt(KMN_E_Install_InvalidFile, VarArrayOf([ExtractFileName(FileName), E.Message, 0]));
|
||||
raise;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -262,12 +262,12 @@ begin
|
|||
begin
|
||||
case inf.Files[i].FileType of
|
||||
ftKeymanFile:
|
||||
InstallKeyboard(dest + inf.Files[i].FileName);
|
||||
InstallKeyboard(dest + inf.Files[i].FileName, BaseKeyboardID);
|
||||
|
||||
ftPackageFile:
|
||||
with TKPInstallPackage.Create(Context) do
|
||||
try
|
||||
Execute(dest + inf.Files[i].FileName, Options);
|
||||
Execute(dest + inf.Files[i].FileName, Options, BaseKeyboardID);
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
(*
|
||||
Name: internalinterfaces
|
||||
Copyright: Copyright (C) 2003-2017 SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 25 Jan 2011
|
||||
|
||||
Modified Date: 17 Aug 2014
|
||||
Authors: mcdurdin
|
||||
Related Files:
|
||||
Dependencies:
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
History: 25 Jan 2011 - mcdurdin - I2569 - Keyboard welcome should always shown from kmshell
|
||||
01 Jan 2013 - mcdurdin - I3717 - V9.0 - Need ability to select base keyboard in Keyman Configuration
|
||||
16 Apr 2014 - mcdurdin - I4169 - V9.0 - Mnemonic layouts should be recompiled to positional based on user-selected base keyboard
|
||||
17 Aug 2014 - mcdurdin - I4376 - V9.0 - Unticked keyboards in configuration should be removed from language profile
|
||||
17 Aug 2014 - mcdurdin - I4381 - V9.0 - Keyman keyboards should be removed from language bar when Keyman exits
|
||||
|
||||
|
||||
*)
|
||||
unit internalinterfaces;
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ type
|
|||
IIntKeymanInterface = interface
|
||||
['{D1EBBED5-B9E3-4807-969D-DCF9E1FFB287}']
|
||||
function XMLClassName: WideString;
|
||||
function Serialize(Flags: TOleEnum; const ImagePath: WideString; References: TStrings): WideString;
|
||||
function Serialize(Flags: TOleEnum; const ImagePath: WideString; References: TStrings): WideString;
|
||||
function DoSerialize(Flags: TOleEnum; const ImagePath: WideString; References: TStrings): WideString; // Wraps serialize with tag
|
||||
end;
|
||||
|
||||
|
|
@ -67,7 +67,6 @@ type
|
|||
['{4876E6DF-C557-46E2-84F4-787BE5F55DDA}']
|
||||
function RegKeyboard: TRegKeyboard;
|
||||
procedure ClearVisualKeyboard;
|
||||
procedure UpdateBaseLayout; // I4169
|
||||
procedure RefreshInstallation;
|
||||
end;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue