From 65335ecaf5057c234816c8fcd1c9aa4f8b2955ed Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 24 Jul 2026 12:07:22 +1000
Subject: [PATCH 01/44] fix(windows): initial commit
---
windows/src/desktop/kmshell/main/UfrmMain.pas | 2 ++
1 file changed, 2 insertions(+)
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index 6a631fb546..a469db480c 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -661,6 +661,8 @@ end;
procedure TfrmMain.Options_BaseKeyboard; // I4169
begin
+ // TODO change this dialog to run as current user and elevate on "ok" button:wq
+
WaitForElevatedConfiguration(Handle, '-basekeyboard');
// Refresh will be triggered by elevated process
end;
From 92643e35692c0678153472cb6a33a11427a1c288 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 29 Jul 2026 14:47:00 +1000
Subject: [PATCH 02/44] fix(windows): handle basekeyboard change non-admin user
---
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 135 ++++++++++++++++--
windows/src/desktop/kmshell/main/UfrmMain.pas | 11 +-
windows/src/desktop/kmshell/main/initprog.pas | 16 ++-
.../com/keyboards/keymankeyboardinstalled.pas | 6 +-
.../kmcomapi/com/options/keymanoptions.pas | 24 ++--
.../processes/keyboard/kpinstallkeyboard.pas | 5 +-
.../keyboard/kprecompilemnemonickeyboard.pas | 10 +-
.../kmcomapi/util/internalinterfaces.pas | 2 +-
8 files changed, 174 insertions(+), 35 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index 40a65e31ce..b3f60a5067 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -11,13 +11,16 @@ 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;
+function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
+function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
+function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
implementation
@@ -25,15 +28,18 @@ implementation
uses
BaseKeyboards,
- kmint;
+ ErrorControlledRegistry,
+ RegistryKeys,
+ keymanapi_TLB,
+ kmint,
+ utilkmshell;
-function ConfigureBaseKeyboard: Boolean;
-begin
- with TfrmBaseKeyboard.Create(nil) do
+function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
+begin with TfrmBaseKeyboard.Create(nil) do
try
Result := ShowModal = mrOk;
if Result then
- kmcom.Apply;
+ BaseKeyboardID := FBaseKeyboardID;
finally
Free;
end;
@@ -65,9 +71,122 @@ 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;
+function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
+var
+ BaseKeyboardID: Integer;
+ PreviousBaseKeyboardID: Integer;
+ PreviousBaseKeyboardValue: string;
+ PreviousBaseKeyboardValueExists: Boolean;
+
+ procedure SavePreviousRegistryBaseKeyboardValue;
+ var
+ Reg: TRegistryErrorControlled;
+ begin
+ PreviousBaseKeyboardValueExists := False;
+ PreviousBaseKeyboardValue := '';
+
+ Reg := TRegistryErrorControlled.Create;
+ try
+ if Reg.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Reg.ValueExists(SRegValue_UnderlyingLayout) then
+ begin
+ PreviousBaseKeyboardValueExists := True;
+ PreviousBaseKeyboardValue := Reg.ReadString(SRegValue_UnderlyingLayout);
+ end;
+ finally
+ Reg.Free;
+ end;
+ end;
+
+ procedure RestorePreviousBaseKeyboardValue;
+ var
+ Reg: TRegistryErrorControlled;
+ begin
+ Reg := TRegistryErrorControlled.Create;
+ try
+ if Reg.OpenKey(SRegKey_KeymanEngine_CU, True) then
+ if PreviousBaseKeyboardValueExists then
+ Reg.WriteString(SRegValue_UnderlyingLayout, PreviousBaseKeyboardValue)
+ else if Reg.ValueExists(SRegValue_UnderlyingLayout) then
+ Reg.DeleteValue(SRegValue_UnderlyingLayout);
+ finally
+ Reg.Free;
+ end;
+ end;
+
+ procedure ForceBaseLayoutChange;
+ var
+ Reg: TRegistryErrorControlled;
+ begin
+ // This is hacky, maybe just remove the registry value, however that
+ // would not force a recompile if the was the default base layout.
+ // Options.Apply re-compiles only when it observes a changed base layout.
+ // The caller may be repairing missing files for the already-selected layout.
+ Reg := TRegistryErrorControlled.Create;
+ try
+ if Reg.OpenKey(SRegKey_KeymanEngine_CU, True) then
+ Reg.WriteString(SRegValue_UnderlyingLayout, '00000000');
+ finally
+ Reg.Free;
+ end;
+ end;
+
+begin
+ Result := False;
+ if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
+ not kmcom.SystemInfo.IsAdministrator then
+ Exit;
+
+ SavePreviousRegistryBaseKeyboardValue;
+ PreviousBaseKeyboardID := kmcom.Options['koBaseLayout'].Value;
+ kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
+ try
+ if PreviousBaseKeyboardID = BaseKeyboardID then
+ ForceBaseLayoutChange;
+ kmcom.Options.Apply;
+ Result := True;
+ finally
+ kmcom.Options['koBaseLayout'].Value := PreviousBaseKeyboardID;
+ RestorePreviousBaseKeyboardValue;
+ end;
+end;
+
+function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
+var
+ I: Integer;
+ Keyboard: IKeymanKeyboardInstalled;
+ BaseFileName: string;
+ BaseKeyboardIDHex: string;
+begin
+ BaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
+ for I := 0 to kmcom.Keyboards.Count - 1 do
+ begin
+ Keyboard := kmcom.Keyboards.Items[I];
+ BaseFileName := Keyboard.Filename;
+ if FileExists(BaseFileName) and
+ (not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx') or
+ not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx')) then
+ Exit(True);
+ end;
+ Result := False;
+end;
+
+function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
+var
+ MCompileResult: Boolean;
+begin
+ MCompileResult := True;
+ Result := False;
+ if BaseKeyboardNeedsMCompile(BaseKeyboardID) and not kmcom.SystemInfo.IsAdministrator then
+ MCompileResult := WaitForElevatedConfiguration(WindowHandle, '-mcompile ' + IntToHex(BaseKeyboardID, 8)) = 0;
+ if not MCompileResult then
+ Exit;
+ kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
+ kmcom.Options.Apply; // This will trigger a recompile if needed
+ Result := True;
+end;
+
end.
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index a469db480c..a556019abd 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -197,6 +197,7 @@ uses
Keyman.Configuration.UI.UfrmStartInstall,
RegistryKeys,
SupportXMLRenderer,
+ UfrmBaseKeyboard,
UfrmChangeHotkey,
UfrmHTML,
UfrmInstallKeyboardFromWeb,
@@ -660,11 +661,15 @@ end;
------------------------------------------------------------------------------}
procedure TfrmMain.Options_BaseKeyboard; // I4169
+var
+ BaseKeyboardID: Integer;
begin
- // TODO change this dialog to run as current user and elevate on "ok" button:wq
+ if ConfigureBaseKeyboard(BaseKeyboardID) then
+ begin
+ SetBaseKeyboard(Handle, BaseKeyboardID)
+ // Refresh will be triggered by elevated process
+ end;
- WaitForElevatedConfiguration(Handle, '-basekeyboard');
- // Refresh will be triggered by elevated process
end;
procedure TfrmMain.Options_SettingsManager;
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 359130f27e..8ea4331995 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -90,6 +90,7 @@ type
fmKeyboardWelcome, // I2569
fmKeyboardPrint, // I2329
fmBaseKeyboard, // I4169
+ fmMCompile,
fmUpgradeMnemonicLayout, // I4553
fmRepair,
fmKeepInTouch,
@@ -262,6 +263,13 @@ 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 = '-mcompile' then
+ begin
+ FMode := fmMCompile;
+ Inc(i);
+ if i > ParamCount then Exit;
+ FQuery := ParamStr(i);
+ end
else if s = '-nowelcome' then FNoWelcome := True
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
else if s = '-kp' then FMode := fmKeyboardPrint // I2329
@@ -393,6 +401,7 @@ var
kdl: IKeymanDefaultLanguage;
FIcon: string;
FMutex: TKeymanMutex; // I2720
+ BaseKeyboardID: Integer;
function FirstKeyboardFileName: WideString;
begin
if KeyboardFileNames.Count = 0
@@ -540,7 +549,12 @@ begin
end;
fmBaseKeyboard: // I4169
- if ConfigureBaseKeyboard
+ if ConfigureBaseKeyboard(BaseKeyboardID) and SetBaseKeyboard(0, BaseKeyboardID)
+ then ExitCode := 0
+ else ExitCode := 1;
+
+ fmMCompile:
+ if MCompileBaseKeyboard(FQuery)
then ExitCode := 0
else ExitCode := 1;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
index 051d399b6e..91ecaa9b5f 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
@@ -109,7 +109,7 @@ type
{ IIntKeymanKeyboardInstalled }
function RegKeyboard: TRegKeyboard;
procedure ClearVisualKeyboard;
- procedure UpdateBaseLayout; // I4169
+ procedure UpdateBaseLayout(BaseKeyboardID: Integer); // I4169
procedure RefreshInstallation;
public
@@ -151,12 +151,12 @@ begin
end;
end;
-procedure TKeymanKeyboardInstalled.UpdateBaseLayout; // I4169
+procedure TKeymanKeyboardInstalled.UpdateBaseLayout(BaseKeyboardID: Integer); // I4169
begin
if FRegKeyboard.MnemonicLayout and FileExists(FRegKeyboard.KeymanFile) then // I4615
with TKPRecompileMnemonicKeyboard.Create(Context) do
try
- Execute(FRegKeyboard.KeymanFile, FRegKeyboard.PackageName);
+ Execute(FRegKeyboard.KeymanFile, FRegKeyboard.PackageName, BaseKeyboardID);
finally
Free;
end;
diff --git a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
index 7bea91450b..8635a74808 100644
--- a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
+++ b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
@@ -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;
@@ -112,7 +113,9 @@ end;
procedure TKeymanOptions.Apply;
var
- I, FOldBaseLayout: Integer;
+ I: Integer;
+ FOldBaseLayout: Integer;
+ FNewBaseLayout: Integer;
begin
with TRegistryErrorControlled.Create do // I3717
try
@@ -130,9 +133,10 @@ begin
FInternalOptions.Save(Context);
- if FOldBaseLayout <> Get_Items('koBaseLayout').Value then
+ FNewBaseLayout := Get_Items('koBaseLayout').Value;
+ if IsAdministrator and (FOldBaseLayout <> FNewBaseLayout) then
for I := 0 to Context.Keyboards.Count - 1 do // I4169
- (Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout;
+ (Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout(FNewBaseLayout);
Context.Control.AutoApplyKeyman;
end;
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
index 26c92faa90..1df237b0c6 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
@@ -125,6 +125,7 @@ var
FExitCode: Integer;
FKVKName: WideString;
FCreatedIcon: Boolean;
+ BaseKeyboardID: Integer;
begin
KL.MethodEnter(Self, 'Execute', [FileName,PackageID,ikPartOfPackage in FInstallOptions ,Force]);
try
@@ -248,9 +249,11 @@ begin
// Recompile a mnemonic layout to the user's selected base layout
if ki.MnemonicLayout then // I4169
begin
+ with Context as TKeymanContext do
+ BaseKeyboardID := (Options as IKeymanOptions).Items['koBaseLayout'].Value;
with TKPRecompileMnemonicKeyboard.Create(Context) do
try
- Execute(FDestFileName, PackageID);
+ Execute(FDestFileName, PackageID, BaseKeyboardID);
finally
Free;
end;
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
index 30d0c275cc..7a4d5a2b39 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
@@ -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,10 +42,8 @@ uses
Winapi.Windows,
errorcontrolledregistry,
- keymancontext,
keymanerrorcodes,
KeymanPaths,
- keymanapi_TLB,
RegistryKeys,
utilexecute,
utilkeyman,
@@ -67,7 +65,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 +74,6 @@ var
FExitCode: Integer;
FMCompilePath: string;
FBaseKeyboardFileName: string;
- BaseKeyboardID: Cardinal;
FDestDeadkeyFileName: string;
FCommand: string;
begin
@@ -84,9 +81,6 @@ 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';
diff --git a/windows/src/engine/kmcomapi/util/internalinterfaces.pas b/windows/src/engine/kmcomapi/util/internalinterfaces.pas
index 1759468e00..11093ac89a 100644
--- a/windows/src/engine/kmcomapi/util/internalinterfaces.pas
+++ b/windows/src/engine/kmcomapi/util/internalinterfaces.pas
@@ -67,7 +67,7 @@ type
['{4876E6DF-C557-46E2-84F4-787BE5F55DDA}']
function RegKeyboard: TRegKeyboard;
procedure ClearVisualKeyboard;
- procedure UpdateBaseLayout; // I4169
+ procedure UpdateBaseLayout(BaseKeyboardID: Cardinal); // I4169
procedure RefreshInstallation;
end;
From 2f4c428b13bbb5215745df9454b6f5c4a3beb851 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 29 Jul 2026 15:03:57 +1000
Subject: [PATCH 03/44] fix(windows): restore UpdateBaseLayout interface
The original change, changed the InKeymanKyboardInstalled
UpdateBaseLayout inteface and it didn't need to. This
change restores it.
---
.../com/keyboards/keymankeyboardinstalled.pas | 9 +++++++--
.../engine/kmcomapi/com/options/keymanoptions.pas | 2 +-
.../keyboard/kprecompilemnemonickeyboard.pas | 14 +++++++-------
.../engine/kmcomapi/util/internalinterfaces.pas | 2 +-
4 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
index 91ecaa9b5f..52fd698435 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
@@ -109,7 +109,7 @@ type
{ IIntKeymanKeyboardInstalled }
function RegKeyboard: TRegKeyboard;
procedure ClearVisualKeyboard;
- procedure UpdateBaseLayout(BaseKeyboardID: Integer); // I4169
+ procedure UpdateBaseLayout; // I4169
procedure RefreshInstallation;
public
@@ -151,15 +151,20 @@ begin
end;
end;
-procedure TKeymanKeyboardInstalled.UpdateBaseLayout(BaseKeyboardID: Integer); // I4169
+procedure TKeymanKeyboardInstalled.UpdateBaseLayout; // I4169
+var
+ BaseKeyboardID: Integer;
begin
if FRegKeyboard.MnemonicLayout and FileExists(FRegKeyboard.KeymanFile) then // I4615
+ begin
+ BaseKeyboardID := (Context.Options as IKeymanOptions).Items['koBaseLayout'].Value;
with TKPRecompileMnemonicKeyboard.Create(Context) do
try
Execute(FRegKeyboard.KeymanFile, FRegKeyboard.PackageName, BaseKeyboardID);
finally
Free;
end;
+ end;
end;
function TKeymanKeyboardInstalled.Get_Copyright: WideString;
diff --git a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
index 8635a74808..a60c5da041 100644
--- a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
+++ b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
@@ -136,7 +136,7 @@ begin
FNewBaseLayout := Get_Items('koBaseLayout').Value;
if IsAdministrator and (FOldBaseLayout <> FNewBaseLayout) then
for I := 0 to Context.Keyboards.Count - 1 do // I4169
- (Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout(FNewBaseLayout);
+ (Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout;
Context.Control.AutoApplyKeyman;
end;
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
index 7a4d5a2b39..3d806afda2 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
@@ -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
diff --git a/windows/src/engine/kmcomapi/util/internalinterfaces.pas b/windows/src/engine/kmcomapi/util/internalinterfaces.pas
index 11093ac89a..1759468e00 100644
--- a/windows/src/engine/kmcomapi/util/internalinterfaces.pas
+++ b/windows/src/engine/kmcomapi/util/internalinterfaces.pas
@@ -67,7 +67,7 @@ type
['{4876E6DF-C557-46E2-84F4-787BE5F55DDA}']
function RegKeyboard: TRegKeyboard;
procedure ClearVisualKeyboard;
- procedure UpdateBaseLayout(BaseKeyboardID: Cardinal); // I4169
+ procedure UpdateBaseLayout; // I4169
procedure RefreshInstallation;
end;
From 27970c671e4bc45c3b24da667d8824b071547fb0 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 3 Aug 2026 15:22:21 +1000
Subject: [PATCH 04/44] fix(windows): refresh UI after basekbd change
---
windows/src/desktop/kmshell/main/UfrmMain.pas | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index a556019abd..4ee970cca5 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -666,8 +666,8 @@ var
begin
if ConfigureBaseKeyboard(BaseKeyboardID) then
begin
- SetBaseKeyboard(Handle, BaseKeyboardID)
- // Refresh will be triggered by elevated process
+ SetBaseKeyboard(Handle, BaseKeyboardID);
+ DoRefresh;
end;
end;
From 62dd0de7868abe931d01428034fc43deabd8ba7f Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 21 Aug 2026 13:53:26 +1000
Subject: [PATCH 05/44] fix(windows): add compile for base keyoard to api
What we was needed was the ability to recompile
installed keyboards against a basekeyboard id.
MCompileForBaseKeyboard which will take the
base keyboard id. This allows the call to be done
elevated and seperates out the actuall selecting
the base keyboard.
---
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 97 +++++--------------
windows/src/desktop/kmshell/main/initprog.pas | 8 +-
.../com/keyboards/keymankeyboardinstalled.pas | 22 ++++-
.../kmcomapi/com/options/keymanoptions.pas | 24 -----
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 10 ++
windows/src/engine/kmcomapi/kmcomapi.ridl | 14 +++
6 files changed, 74 insertions(+), 101 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index b3f60a5067..e9ce67d5e3 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -21,6 +21,7 @@ type
function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
+function CompileForBaseKeyboard(BaseKeyboardID: Integer): Boolean;
implementation
@@ -78,80 +79,12 @@ end;
function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
var
BaseKeyboardID: Integer;
- PreviousBaseKeyboardID: Integer;
- PreviousBaseKeyboardValue: string;
- PreviousBaseKeyboardValueExists: Boolean;
-
- procedure SavePreviousRegistryBaseKeyboardValue;
- var
- Reg: TRegistryErrorControlled;
- begin
- PreviousBaseKeyboardValueExists := False;
- PreviousBaseKeyboardValue := '';
-
- Reg := TRegistryErrorControlled.Create;
- try
- if Reg.OpenKeyReadOnly(SRegKey_KeymanEngine_CU) and Reg.ValueExists(SRegValue_UnderlyingLayout) then
- begin
- PreviousBaseKeyboardValueExists := True;
- PreviousBaseKeyboardValue := Reg.ReadString(SRegValue_UnderlyingLayout);
- end;
- finally
- Reg.Free;
- end;
- end;
-
- procedure RestorePreviousBaseKeyboardValue;
- var
- Reg: TRegistryErrorControlled;
- begin
- Reg := TRegistryErrorControlled.Create;
- try
- if Reg.OpenKey(SRegKey_KeymanEngine_CU, True) then
- if PreviousBaseKeyboardValueExists then
- Reg.WriteString(SRegValue_UnderlyingLayout, PreviousBaseKeyboardValue)
- else if Reg.ValueExists(SRegValue_UnderlyingLayout) then
- Reg.DeleteValue(SRegValue_UnderlyingLayout);
- finally
- Reg.Free;
- end;
- end;
-
- procedure ForceBaseLayoutChange;
- var
- Reg: TRegistryErrorControlled;
- begin
- // This is hacky, maybe just remove the registry value, however that
- // would not force a recompile if the was the default base layout.
- // Options.Apply re-compiles only when it observes a changed base layout.
- // The caller may be repairing missing files for the already-selected layout.
- Reg := TRegistryErrorControlled.Create;
- try
- if Reg.OpenKey(SRegKey_KeymanEngine_CU, True) then
- Reg.WriteString(SRegValue_UnderlyingLayout, '00000000');
- finally
- Reg.Free;
- end;
- end;
-
begin
Result := False;
if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
not kmcom.SystemInfo.IsAdministrator then
Exit;
-
- SavePreviousRegistryBaseKeyboardValue;
- PreviousBaseKeyboardID := kmcom.Options['koBaseLayout'].Value;
- kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
- try
- if PreviousBaseKeyboardID = BaseKeyboardID then
- ForceBaseLayoutChange;
- kmcom.Options.Apply;
- Result := True;
- finally
- kmcom.Options['koBaseLayout'].Value := PreviousBaseKeyboardID;
- RestorePreviousBaseKeyboardValue;
- end;
+ Result := CompileForBaseKeyboard(BaseKeyboardID);
end;
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
@@ -180,13 +113,33 @@ var
begin
MCompileResult := True;
Result := False;
- if BaseKeyboardNeedsMCompile(BaseKeyboardID) and not kmcom.SystemInfo.IsAdministrator then
- MCompileResult := WaitForElevatedConfiguration(WindowHandle, '-mcompile ' + IntToHex(BaseKeyboardID, 8)) = 0;
+ if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
+ begin
+ if not kmcom.SystemInfo.IsAdministrator then
+ begin
+ MCompileResult := WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8)) = 0;
+ end
+ else
+ MCompileResult := CompileForBaseKeyboard(BaseKeyboardID);
+ end;
if not MCompileResult then
Exit;
+
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
- kmcom.Options.Apply; // This will trigger a recompile if needed
+ kmcom.Options.Apply;
Result := True;
end;
+function CompileForBaseKeyboard(BaseKeyboardID: Integer): Boolean;
+var
+ i: Integer;
+ kbd: IKeymanKeyboardInstalled;
+begin
+ for i := 0 to kmcom.Keyboards.Count - 1 do
+ begin
+ kbd := kmcom.Keyboards[i];
+ (kbd as IKeymanKeyboardInstalled2).MCompileForBaseKeyboard(BaseKeyboardID);
+ end;
+end;
+
end.
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 3444e7ce53..f9b5f9ac9b 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -90,7 +90,7 @@ type
fmKeyboardWelcome, // I2569
fmKeyboardPrint, // I2329
fmBaseKeyboard, // I4169
- fmMCompile,
+ fmMCompileKbds,
fmUpgradeMnemonicLayout, // I4553
fmRepair,
fmKeepInTouch,
@@ -263,9 +263,9 @@ 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 = '-mcompile' then
+ else if s = '-mcompilekbds' then
begin
- FMode := fmMCompile;
+ FMode := fmMCompileKbds;
Inc(i);
if i > ParamCount then Exit;
FQuery := ParamStr(i);
@@ -553,7 +553,7 @@ begin
then ExitCode := 0
else ExitCode := 1;
- fmMCompile:
+ fmMCompileKbds:
if MCompileBaseKeyboard(FQuery)
then ExitCode := 0
else ExitCode := 1;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
index 52fd698435..01b03ae3fc 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
@@ -63,7 +63,8 @@ type
TKeymanKeyboardInstalled = class( // I3581
TKeymanKeyboard,
IIntKeymanKeyboardInstalled,
- IKeymanKeyboardInstalled)
+ IKeymanKeyboardInstalled,
+ IKeymanKeyboardInstalled2)
private
FRegKeyboard: TRegKeyboard;
FVisualKeyboard: IKeymanVisualKeyboard;
@@ -112,6 +113,9 @@ type
procedure UpdateBaseLayout; // I4169
procedure RefreshInstallation;
+ { IKeymanKeyboardInstalled2 }
+ procedure MCompileForBaseKeyboard(KLID: Integer); safecall;
+
public
constructor Create(AContext: TKeymanContext; const Name: string);
destructor Destroy; override;
@@ -476,5 +480,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.
diff --git a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
index a60c5da041..1848428033 100644
--- a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
+++ b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas
@@ -112,32 +112,8 @@ begin
end;
procedure TKeymanOptions.Apply;
-var
- I: Integer;
- FOldBaseLayout: Integer;
- FNewBaseLayout: 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);
-
- FNewBaseLayout := Get_Items('koBaseLayout').Value;
- if IsAdministrator and (FOldBaseLayout <> FNewBaseLayout) then
- for I := 0 to Context.Keyboards.Count - 1 do // I4169
- (Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout;
-
Context.Control.AutoApplyKeyman;
end;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 3ceb5b15cd..786ce6c9b2 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1569,6 +1569,16 @@ type
procedure RefreshInstalledKeyboards; safecall;
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: IKeymanKeyboardsInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index ac20310a87..9eb63fbd68 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -60,6 +60,7 @@ library keymanapi
interface IKeymanKeyboardLanguagesInstalled;
interface IKeymanKeyboardLanguagesFile;
interface IKeymanKeyboardsInstalled2;
+ interface IKeymanKeyboardInstalled2;
interface IKeymanPackagesInstalled2;
interface IKeymanKeyboardFile2;
interface IKeymanPackageFile2;
@@ -936,6 +937,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),
From 82fb40002b980a243edd7a9c647f7d3ca585c422 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 31 Aug 2026 15:34:45 +1000
Subject: [PATCH 06/44] fix(windows): pass basekeyboardid to installation api
The updates all the apis so that the basekeyboardid or klid
can be passed in as an argument. This in needed so that
elevated process required to compile the keyboard has
the call users keyboard base id.
---
.../Keyman.Configuration.UI.InstallFile.pas | 30 +++++-----
...Configuration.UI.KeymanProtocolHandler.pas | 12 ++--
.../kmshell/install/UfrmInstallKeyboard.pas | 17 ++++--
.../install/UfrmInstallKeyboardFromWeb.pas | 6 +-
windows/src/desktop/kmshell/main/initprog.pas | 15 ++---
.../com/keyboards/keymankeyboardfile.pas | 23 +++++++-
.../keyboards/keymankeyboardsinstalled.pas | 22 ++++++-
.../com/packages/keymanpackagefile.pas | 43 ++++++++++----
.../com/packages/keymanpackagesinstalled.pas | 30 +++++++++-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 42 ++++++++++++++
windows/src/engine/kmcomapi/kmcomapi.ridl | 58 +++++++++++++++++++
.../processes/keyboard/kpinstallkeyboard.pas | 23 +++++---
.../processes/package/kpinstallpackage.pas | 12 ++--
13 files changed, 264 insertions(+), 69 deletions(-)
diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
index c6ee3564ae..3cc8dcc901 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
@@ -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,29 +35,30 @@ uses
Keyman.Configuration.UI.KeymanProtocolHandler,
Keyman.Configuration.UI.MitigationForWin10_1803,
kmint,
+ KeymanOptionNames,
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;
@@ -77,7 +78,7 @@ begin
begin
if ASilent then
begin
- InstallKeyboard(LogFile, BCP47);
+ InstallKeyboard(LogFile, BCP47, BaseKeyboardID);
Result := True;
end
else
@@ -139,7 +140,7 @@ end;
/// This is the handler for the `-i` parameter, e.g.
/// kmshell -i khmer_angkor.kmp c:\temp\sil_euro_latin.kmp=fr
///
-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;
@@ -170,7 +171,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, '');
@@ -178,7 +179,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);
@@ -209,6 +210,7 @@ end;
class function TInstallFile.BrowseAndInstallKeyboardFromFile(Owner: TComponent): Boolean;
var
dlgOpen: TOpenDialog;
+ BaseKeyboardID : Integer;
begin
dlgOpen := TOpenDialog.Create(nil);
try
@@ -216,9 +218,9 @@ begin
'Keyman files (*.kmx, *.kxx, *.kmp)|*.kmx;*.kxx;*.kmp|Keyman keyboards (*.kmx,*.kxx)' +
'|*.kmx;*.kxx|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
diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
index 6f13f6777b..60ac8ae43c 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
@@ -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
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
index 6d79811a80..775aa1ca2e 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
@@ -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+'" -bkd "'+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;
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
index aff3251037..ed236d489d 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
@@ -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
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index f9b5f9ac9b..0f0cc6f367 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -206,7 +206,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;
@@ -263,6 +263,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := ParamStr(i); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
@@ -329,7 +330,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
@@ -340,7 +341,7 @@ var
FForce: Boolean;
FParentWindow: THandle;
FLogFile: string;
- FDefaultLangID: Integer;
+ FDefaultLangID, FBaseKeyboard: Integer;
FDefaultBCP47, FDisablePackages, FDefaultUILanguage: string;
FStartWithConfiguration: Boolean;
begin
@@ -349,7 +350,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;
@@ -357,7 +358,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;
@@ -396,7 +397,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;
@@ -559,7 +560,7 @@ begin
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;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
index 5ea1e74265..b7255869b7 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
index f5f8b74448..b37e0dfce0 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas b/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
index 14b207e229..28ef2d064a 100644
--- a/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
+++ b/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas b/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
index 9dd9f1a52a..821a38bdc5 100644
--- a/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 786ce6c9b2..7246c9173f 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1569,6 +1569,17 @@ type
procedure RefreshInstalledKeyboards; safecall;
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; BasePackageID: Integer): IKeymanKeyboardInstalled; safecall;
+ procedure RefreshInstalledKeyboards; safecall;
+ end;
+
// *********************************************************************//
// Interface: IKeymanKeyboardInstalled2
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1610,6 +1621,16 @@ type
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
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; BasePackageID: Integer): IKeymanPackageInstalled; safecall;
+ end;
+
// *********************************************************************//
// DispIntf: IKeymanPackagesInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1639,6 +1660,16 @@ type
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
end;
+// *********************************************************************//
+// Interface: IKeymanKeyboardFile3
+// Flags: (4416) Dual OleAutomation Dispatchable
+// GUID: {EDE4326B-51F4-42D5-8251-B20B71993EC8}
+// *********************************************************************//
+ IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
+ ['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
+ end;
+
// *********************************************************************//
// DispIntf: IKeymanKeyboardFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1677,6 +1708,17 @@ type
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
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: IKeymanPackageFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 9eb63fbd68..91f49822b5 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -66,6 +66,10 @@ library keymanapi
interface IKeymanPackageFile2;
interface IKeymanKeyboardLanguageInstalled2;
interface IKeymanKeyboardLanguagesInstalled2;
+ interface IKeymanKeyboardFile3;
+ interface IKeymanPackageFile3;
+ interface IKeymanKeyboardsInstalled3;
+ interface IKeymanPackagesInstalled3;
interface IKeymanBCP47Canonicalization;
interface IKeymanDefaultLanguage;
@@ -976,6 +980,60 @@ library keymanapi
HRESULT _stdcall Install2([in] VARIANT_BOOL Force, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
};
+ [
+ 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] IKeymanPackageFile** Package);
+ };
+
+
+ [
+ uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
+ version(19.0),
+ helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\3"),
+ 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(9B43B6BC-C622-47EF-915E-6780CF53BAAA),
version(14.0),
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
index 1df237b0c6..6d3bd1f3ed 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
@@ -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,7 +125,9 @@ var
FExitCode: Integer;
FKVKName: WideString;
FCreatedIcon: Boolean;
- BaseKeyboardID: Integer;
+ ElevatedBaseKeyboardID: Integer;
+ KeymanContext: TKeymanContext;
+ RecompileMnemonicKeyboard: TKPRecompileMnemonicKeyboard;
begin
KL.MethodEnter(Self, 'Execute', [FileName,PackageID,ikPartOfPackage in FInstallOptions ,Force]);
try
@@ -246,14 +248,19 @@ 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 Context as TKeymanContext do
- BaseKeyboardID := (Options as IKeymanOptions).Items['koBaseLayout'].Value;
- 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, BaseKeyboardID);
+ if (BaseKeyboardID = 0) then
+ RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, ElevatedBaseKeyboardID)
+ else
+ RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, BaseKeyboardID);
finally
Free;
end;
diff --git a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
index 98edfbf3fb..4ab33cdfee 100644
--- a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
+++ b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
@@ -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;
@@ -253,12 +253,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;
From 155b8e15edab819bde11b564423a950366a05812 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Tue, 1 Sep 2026 15:53:48 +1000
Subject: [PATCH 07/44] fix(windows): strtoint for command line basekeyboardid
Also rebuild kmcomapi_TLB.pas
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 251 ++++++++++++++----
windows/src/engine/kmcomapi/kmcomapi.ridl | 108 ++++----
3 files changed, 252 insertions(+), 109 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 0f0cc6f367..b1afe4e10f 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -263,7 +263,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := ParamStr(i); end
+ else if s = 'bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 7246c9173f..3c436dc5d9 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -12,7 +12,7 @@ unit keymanapi_TLB;
// ************************************************************************ //
// $Rev: 52393 $
-// File generated on 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;
@@ -1569,27 +1584,6 @@ type
procedure RefreshInstalledKeyboards; safecall;
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; BasePackageID: Integer): IKeymanKeyboardInstalled; safecall;
- procedure RefreshInstalledKeyboards; safecall;
- 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: IKeymanKeyboardsInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1611,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
@@ -1621,16 +1660,6 @@ type
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
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; BasePackageID: Integer): IKeymanPackageInstalled; safecall;
- end;
-
// *********************************************************************//
// DispIntf: IKeymanPackagesInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1660,16 +1689,6 @@ type
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
end;
-// *********************************************************************//
-// Interface: IKeymanKeyboardFile3
-// Flags: (4416) Dual OleAutomation Dispatchable
-// GUID: {EDE4326B-51F4-42D5-8251-B20B71993EC8}
-// *********************************************************************//
- IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
- ['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
- end;
-
// *********************************************************************//
// DispIntf: IKeymanKeyboardFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1708,17 +1727,6 @@ type
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
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: IKeymanPackageFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1820,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): IKeymanPackageFile; 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): IKeymanPackageFile; 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
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 91f49822b5..ecfff3d707 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -6,7 +6,7 @@
// However, when applying changes via the Editor this file will be regenerated
// and comments or formatting changes will be lost.
// ************************************************************************ //
-// File generated on 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),
@@ -980,60 +980,6 @@ library keymanapi
HRESULT _stdcall Install2([in] VARIANT_BOOL Force, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
};
- [
- 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] IKeymanPackageFile** Package);
- };
-
-
- [
- uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
- version(19.0),
- helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\3"),
- 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(9B43B6BC-C622-47EF-915E-6780CF53BAAA),
version(14.0),
@@ -1079,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] IKeymanPackageFile** Package);
+ };
+
+ [
+ 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,
From 0c68b1e0314086b94deb9ed0a5bf6e2697196ab6 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 2 Sep 2026 10:10:16 +1000
Subject: [PATCH 08/44] fix(windows): typos and return type in interface
declaration
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 4 ++--
windows/src/engine/kmcomapi/kmcomapi.ridl | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index b1afe4e10f..f4d2a4161d 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -263,7 +263,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
+ else if s = '-bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 3c436dc5d9..b0530cb94a 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1873,7 +1873,7 @@ type
// *********************************************************************//
IKeymanPackageFile3 = interface(IKeymanPackageFile)
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageFile; safecall;
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
end;
// *********************************************************************//
@@ -1883,7 +1883,7 @@ type
// *********************************************************************//
IKeymanPackageFile3Disp = dispinterface
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageFile; dispid 292;
+ 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;
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index ecfff3d707..0b2366c144 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -1048,7 +1048,7 @@ library keymanapi
interface IKeymanPackageFile3: IKeymanPackageFile
{
[id(0x00000124)]
- HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageFile** Package);
+ HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageInstalled** PackageResult);
};
[
From 3c1b47eedc2bc9d8422fee659fddbdd2edbf4432 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 2 Sep 2026 12:37:03 +1000
Subject: [PATCH 09/44] fix(windows): Free the correct object
---
.../engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
index 6d3bd1f3ed..1d198264cb 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
@@ -262,7 +262,7 @@ begin
else
RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, BaseKeyboardID);
finally
- Free;
+ RecompileMnemonicKeyboard.Free;
end;
end;
finally
From 8e77a5779a992f970b2a8f59caa0980b2c7f4af2 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 2 Sep 2026 17:54:26 +1000
Subject: [PATCH 10/44] fix(windows): CompileForBaseKeyboard doesn't return
a value
---
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index e9ce67d5e3..1f136871e6 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -21,7 +21,7 @@ type
function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-function CompileForBaseKeyboard(BaseKeyboardID: Integer): Boolean;
+procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
implementation
@@ -84,7 +84,9 @@ begin
if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
not kmcom.SystemInfo.IsAdministrator then
Exit;
- Result := CompileForBaseKeyboard(BaseKeyboardID);
+ CompileForBaseKeyboard(BaseKeyboardID);
+ // TODO: sort out whether we need todo return a result
+ Result := True;
end;
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
@@ -108,29 +110,24 @@ begin
end;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
-var
- MCompileResult: Boolean;
begin
- MCompileResult := True;
Result := False;
if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
begin
if not kmcom.SystemInfo.IsAdministrator then
begin
- MCompileResult := WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8)) = 0;
+ WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8));
end
else
- MCompileResult := CompileForBaseKeyboard(BaseKeyboardID);
+ CompileForBaseKeyboard(BaseKeyboardID);
end;
- if not MCompileResult then
- Exit;
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
kmcom.Options.Apply;
Result := True;
end;
-function CompileForBaseKeyboard(BaseKeyboardID: Integer): Boolean;
+procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
var
i: Integer;
kbd: IKeymanKeyboardInstalled;
From 314dea4bacf1dbbedbeaa2064984e0ea6a583bef Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Thu, 3 Sep 2026 16:17:21 +1000
Subject: [PATCH 11/44] fix(windows): move non ui function from
ufrmbasekeyboard
---
windows/src/desktop/kmshell/kmshell.dpr | 3 +-
windows/src/desktop/kmshell/kmshell.dproj | 13 +--
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 65 ---------------
windows/src/desktop/kmshell/main/UfrmMain.pas | 1 +
windows/src/desktop/kmshell/main/initprog.pas | 1 +
...an.Configuration.Settings.BaseKeyboard.pas | 83 +++++++++++++++++++
6 files changed, 94 insertions(+), 72 deletions(-)
create mode 100644 windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr
index e2d28b88d2..e4ce8316d3 100644
--- a/windows/src/desktop/kmshell/kmshell.dpr
+++ b/windows/src/desktop/kmshell/kmshell.dpr
@@ -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.Settings.BaseKeyboard in 'settings\Keyman.Configuration.Settings.BaseKeyboard.pas';
{$R VERSION.RES}
{$R manifest.res}
diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj
index 371ce4cd49..ca611b40e4 100644
--- a/windows/src/desktop/kmshell/kmshell.dproj
+++ b/windows/src/desktop/kmshell/kmshell.dproj
@@ -358,6 +358,7 @@
+
Cfg_2
@@ -419,6 +420,12 @@
False
+
+
+ kmshell.exe
+ true
+
+
.\
@@ -431,12 +438,6 @@
true
-
-
- kmshell.exe
- true
-
-
1
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index 1f136871e6..56e7c442b4 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -19,9 +19,6 @@ type
end;
function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
-function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
-function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
implementation
@@ -76,67 +73,5 @@ begin
ModalResult := mrOk;
end;
-function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-var
- BaseKeyboardID: Integer;
-begin
- Result := False;
- if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
- not kmcom.SystemInfo.IsAdministrator then
- Exit;
- CompileForBaseKeyboard(BaseKeyboardID);
- // TODO: sort out whether we need todo return a result
- Result := True;
-end;
-
-function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
-var
- I: Integer;
- Keyboard: IKeymanKeyboardInstalled;
- BaseFileName: string;
- BaseKeyboardIDHex: string;
-begin
- BaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
- for I := 0 to kmcom.Keyboards.Count - 1 do
- begin
- Keyboard := kmcom.Keyboards.Items[I];
- BaseFileName := Keyboard.Filename;
- if FileExists(BaseFileName) and
- (not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx') or
- not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx')) then
- Exit(True);
- end;
- Result := False;
-end;
-
-function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
-begin
- Result := False;
- if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
- begin
- if not kmcom.SystemInfo.IsAdministrator then
- begin
- WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8));
- end
- else
- CompileForBaseKeyboard(BaseKeyboardID);
- end;
-
- kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
- kmcom.Options.Apply;
- Result := True;
-end;
-
-procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
-var
- i: Integer;
- kbd: IKeymanKeyboardInstalled;
-begin
- for i := 0 to kmcom.Keyboards.Count - 1 do
- begin
- kbd := kmcom.Keyboards[i];
- (kbd as IKeymanKeyboardInstalled2).MCompileForBaseKeyboard(BaseKeyboardID);
- end;
-end;
end.
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index 100fd24fd2..599566b91c 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -174,6 +174,7 @@ uses
Hints,
HotkeyUtils,
initprog,
+ Keyman.Configuration.Settings.BaseKeyboard,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.UI.UfrmDiagnosticTests,
KeymanOptionNames,
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index f4d2a4161d..2d6cc1e6d2 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -115,6 +115,7 @@ uses
GetOsVersion,
help,
HTMLHelpViewer,
+ Keyman.Configuration.Settings.BaseKeyboard,
Keyman.Configuration.UI.InstallFile,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.System.UImportOlderVersionKeyboards11To13,
diff --git a/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas b/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
new file mode 100644
index 0000000000..44488179c9
--- /dev/null
+++ b/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
@@ -0,0 +1,83 @@
+unit Keyman.Configuration.Settings.BaseKeyboard;
+
+interface
+
+uses
+ Winapi.Windows,
+ System.SysUtils,
+ keymanapi_TLB;
+
+function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
+function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
+procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
+
+implementation
+
+uses
+ kmint,
+ utilkmshell;
+
+function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
+var
+ I: Integer;
+ Keyboard: IKeymanKeyboardInstalled;
+ BaseFileName: string;
+ BaseKeyboardIDHex: string;
+begin
+ BaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
+ for I := 0 to kmcom.Keyboards.Count - 1 do
+ begin
+ Keyboard := kmcom.Keyboards.Items[I];
+ BaseFileName := Keyboard.Filename;
+ if FileExists(BaseFileName) and
+ (not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx') or
+ not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx')) then
+ Exit(True);
+ end;
+ Result := False;
+end;
+
+function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
+begin
+ Result := False;
+ if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
+ begin
+ if not kmcom.SystemInfo.IsAdministrator then
+ begin
+ WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8));
+ end
+ else
+ CompileForBaseKeyboard(BaseKeyboardID);
+ end;
+
+ kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
+ kmcom.Options.Apply;
+ Result := True;
+end;
+
+function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
+var
+ BaseKeyboardID: Integer;
+begin
+ Result := False;
+ if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
+ not kmcom.SystemInfo.IsAdministrator then
+ Exit;
+ CompileForBaseKeyboard(BaseKeyboardID);
+ // TODO: sort out whether we need todo return a result
+ Result := True;
+end;
+
+procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
+var
+ i: Integer;
+ kbd: IKeymanKeyboardInstalled;
+begin
+ for i := 0 to kmcom.Keyboards.Count - 1 do
+ begin
+ kbd := kmcom.Keyboards[i];
+ (kbd as IKeymanKeyboardInstalled2).MCompileForBaseKeyboard(BaseKeyboardID);
+ end;
+end;
+
+end.
From c3160944e5f2cb2277d6580a9c931883277cbf3d Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Thu, 3 Sep 2026 17:29:04 +1000
Subject: [PATCH 12/44] chore(windows): Revert "fix(windows): typos and return
type in interface declaration"
This reverts commit 0c68b1e0314086b94deb9ed0a5bf6e2697196ab6.
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 4 ++--
windows/src/engine/kmcomapi/kmcomapi.ridl | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 2d6cc1e6d2..6457542bca 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -264,7 +264,7 @@ 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 = '-bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
+ else if s = 'bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index b0530cb94a..3c436dc5d9 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1873,7 +1873,7 @@ type
// *********************************************************************//
IKeymanPackageFile3 = interface(IKeymanPackageFile)
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageFile; safecall;
end;
// *********************************************************************//
@@ -1883,7 +1883,7 @@ type
// *********************************************************************//
IKeymanPackageFile3Disp = dispinterface
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; dispid 292;
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageFile; dispid 292;
procedure Install(Force: WordBool); dispid 256;
property Author: WideString readonly dispid 1;
property AuthorEmail: WideString readonly dispid 2;
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 0b2366c144..ecfff3d707 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -1048,7 +1048,7 @@ library keymanapi
interface IKeymanPackageFile3: IKeymanPackageFile
{
[id(0x00000124)]
- HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageInstalled** PackageResult);
+ HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageFile** Package);
};
[
From 5e89946c004c0e1f6b83a2109d26d2e06bddd39e Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Thu, 3 Sep 2026 17:29:51 +1000
Subject: [PATCH 13/44] chore(windows): Revert "fix(windows): strtoint for
command line basekeyboardid"
This reverts commit 155b8e15edab819bde11b564423a950366a05812.
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 251 ++++--------------
windows/src/engine/kmcomapi/kmcomapi.ridl | 108 ++++----
3 files changed, 109 insertions(+), 252 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 6457542bca..3143d72828 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -264,7 +264,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
+ else if s = 'bkd' then begin Inc(i); FBaseKeyboard := ParamStr(i); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 3c436dc5d9..7246c9173f 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -12,7 +12,7 @@ unit keymanapi_TLB;
// ************************************************************************ //
// $Rev: 52393 $
-// File generated on 31/08/2026 5:28:13 PM from Type Library described below.
+// File generated on 16/09/2021 6:54:44 PM from Type Library described below.
// ************************************************************************ //
// Type Lib: C:\Projects\keyman\app\windows\src\engine\kmcomapi\kmcomapi (1)
@@ -87,16 +87,11 @@ 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}';
@@ -253,8 +248,6 @@ type
IKeymanKeyboardLanguagesFileDisp = dispinterface;
IKeymanKeyboardsInstalled2 = interface;
IKeymanKeyboardsInstalled2Disp = dispinterface;
- IKeymanKeyboardInstalled2 = interface;
- IKeymanKeyboardInstalled2Disp = dispinterface;
IKeymanPackagesInstalled2 = interface;
IKeymanPackagesInstalled2Disp = dispinterface;
IKeymanKeyboardFile2 = interface;
@@ -265,14 +258,6 @@ 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;
@@ -1584,6 +1569,27 @@ type
procedure RefreshInstalledKeyboards; safecall;
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; BasePackageID: Integer): IKeymanKeyboardInstalled; safecall;
+ procedure RefreshInstalledKeyboards; safecall;
+ 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: IKeymanKeyboardsInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1605,51 +1611,6 @@ 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
@@ -1660,6 +1621,16 @@ type
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
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; BasePackageID: Integer): IKeymanPackageInstalled; safecall;
+ end;
+
// *********************************************************************//
// DispIntf: IKeymanPackagesInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1689,6 +1660,16 @@ type
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
end;
+// *********************************************************************//
+// Interface: IKeymanKeyboardFile3
+// Flags: (4416) Dual OleAutomation Dispatchable
+// GUID: {EDE4326B-51F4-42D5-8251-B20B71993EC8}
+// *********************************************************************//
+ IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
+ ['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
+ end;
+
// *********************************************************************//
// DispIntf: IKeymanKeyboardFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1727,6 +1708,17 @@ type
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
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: IKeymanPackageFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1828,143 +1820,6 @@ 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): IKeymanPackageFile; 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): IKeymanPackageFile; 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
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index ecfff3d707..91f49822b5 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -6,7 +6,7 @@
// However, when applying changes via the Editor this file will be regenerated
// and comments or formatting changes will be lost.
// ************************************************************************ //
-// File generated on 31/08/2026 5:28:15 PM (- $Rev: 12980 $, 11059984).
+// File generated on 16/09/2021 6:54:45 PM (- $Rev: 12980 $, 32940875).
[
uuid(F16E2A9A-DA46-4EA3-BFF3-BA46B480C961),
@@ -980,6 +980,60 @@ library keymanapi
HRESULT _stdcall Install2([in] VARIANT_BOOL Force, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
};
+ [
+ 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] IKeymanPackageFile** Package);
+ };
+
+
+ [
+ uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
+ version(19.0),
+ helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\3"),
+ 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(9B43B6BC-C622-47EF-915E-6780CF53BAAA),
version(14.0),
@@ -1025,58 +1079,6 @@ 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] IKeymanPackageFile** Package);
- };
-
- [
- 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,
From 48923665d9f6c3270d0ebc4bbe77f70e27b01ba5 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Thu, 3 Sep 2026 17:30:21 +1000
Subject: [PATCH 14/44] chore(windows): Revert "fix(windows): pass
basekeyboardid to installation api"
This reverts commit 82fb40002b980a243edd7a9c647f7d3ca585c422.
---
.../Keyman.Configuration.UI.InstallFile.pas | 30 +++++-----
...Configuration.UI.KeymanProtocolHandler.pas | 12 ++--
.../kmshell/install/UfrmInstallKeyboard.pas | 17 ++----
.../install/UfrmInstallKeyboardFromWeb.pas | 6 +-
windows/src/desktop/kmshell/main/initprog.pas | 15 +++--
.../com/keyboards/keymankeyboardfile.pas | 23 +-------
.../keyboards/keymankeyboardsinstalled.pas | 22 +------
.../com/packages/keymanpackagefile.pas | 43 ++++----------
.../com/packages/keymanpackagesinstalled.pas | 30 +---------
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 42 --------------
windows/src/engine/kmcomapi/kmcomapi.ridl | 58 -------------------
.../processes/keyboard/kpinstallkeyboard.pas | 23 +++-----
.../processes/package/kpinstallpackage.pas | 12 ++--
13 files changed, 69 insertions(+), 264 deletions(-)
diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
index 3cc8dcc901..c6ee3564ae 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
@@ -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; 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;
+ 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;
end;
implementation
@@ -35,30 +35,29 @@ uses
Keyman.Configuration.UI.KeymanProtocolHandler,
Keyman.Configuration.UI.MitigationForWin10_1803,
kmint,
- KeymanOptionNames,
UfrmHTML,
UfrmInstallKeyboard;
class function TInstallFile.Execute(KeyboardFileNames: TStrings; const FirstKeyboardFileName: string; FSilent, FNoWelcome: Boolean;
- const LogFile: string; BaseKeyboardID: Integer): Boolean;
+ const LogFile: string): Boolean;
begin
if TKeymanProtocolHandler.CanHandle(FirstKeyboardFileName) then
begin
- Result := TKeymanProtocolHandler.Handle(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile, BaseKeyboardID);
+ Result := TKeymanProtocolHandler.Handle(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile);
end
else if (KeyboardFileNames.Count > 1) or (Pos('=', FirstKeyboardFileName) > 0) then
begin
- Result := TInstallFile.Execute(nil, KeyboardFileNames, FSilent, BaseKeyboardID)
+ Result := TInstallFile.Execute(nil, KeyboardFileNames, FSilent)
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, '', BaseKeyboardID);
+ Result := TInstallFile.Execute(nil, FirstKeyboardFileName, FSilent, FNoWelcome, LogFile, '');
end;
end;
-class function TInstallFile.Execute(Owner: TComponent; const FileName: string; ASilent, ANoWelcome: Boolean; const LogFile, BCP47: string; BaseKeyboardID: Integer): Boolean;
+class function TInstallFile.Execute(Owner: TComponent; const FileName: string; ASilent, ANoWelcome: Boolean; const LogFile, BCP47: string): Boolean;
var
n: Integer;
InstalledKeyboards: array of IKeymanKeyboardInstalled;
@@ -78,7 +77,7 @@ begin
begin
if ASilent then
begin
- InstallKeyboard(LogFile, BCP47, BaseKeyboardID);
+ InstallKeyboard(LogFile, BCP47);
Result := True;
end
else
@@ -140,7 +139,7 @@ end;
/// This is the handler for the `-i` parameter, e.g.
/// kmshell -i khmer_angkor.kmp c:\temp\sil_euro_latin.kmp=fr
///
-class function TInstallFile.Execute(Owner: TComponent; const FileNames: TStrings; ASilent: Boolean; BaseKeyboardID: Integer): Boolean;
+class function TInstallFile.Execute(Owner: TComponent; const FileNames: TStrings; ASilent: Boolean): Boolean;
var
i, j: Integer;
FPackage: IKeymanPackageInstalled;
@@ -171,7 +170,7 @@ begin
end;
if IsPackage then
begin
- FPackage := (kmcom.Packages as IKeymanPackagesInstalled3).Install3(FileName, True, BaseKeyboardID);
+ FPackage := (kmcom.Packages as IKeymanPackagesInstalled2).Install2(FileName, True);
if Length(FilenameBCP47) > 1
then RegisterKeyboardPackageLanguage(FPackage, FilenameBCP47[1])
else RegisterKeyboardPackageLanguage(FPackage, '');
@@ -179,7 +178,7 @@ begin
end
else
begin
- FKeyboard := (kmcom.Keyboards as IKeymanKeyboardsInstalled3).Install3(FileName, True, BaseKeyboardID);
+ FKeyboard := (kmcom.Keyboards as IKeymanKeyboardsInstalled2).Install2(FileName, True);
if (Length(FilenameBCP47) > 1) and (Trim(FilenameBCP47[1]) <> '')
then BCP47Tag := FilenameBCP47[1]
else BCP47Tag := TTIPMaintenance.GetFirstLanguage(FKeyboard);
@@ -210,7 +209,6 @@ end;
class function TInstallFile.BrowseAndInstallKeyboardFromFile(Owner: TComponent): Boolean;
var
dlgOpen: TOpenDialog;
- BaseKeyboardID : Integer;
begin
dlgOpen := TOpenDialog.Create(nil);
try
@@ -218,9 +216,9 @@ begin
'Keyman files (*.kmx, *.kxx, *.kmp)|*.kmx;*.kxx;*.kmp|Keyman keyboards (*.kmx,*.kxx)' +
'|*.kmx;*.kxx|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, '', '', BaseKeyboardID)
+ Result := Execute(Owner, dlgOpen.FileName, False, False, '', '')
else
Result := False;
finally
diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
index 60ac8ae43c..6f13f6777b 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
@@ -17,13 +17,13 @@ type
FDownloadURL: string;
frmDownloadProgress: TfrmDownloadProgress;
function DoHandle(Owner: TComponent; const url: string; ASilent,
- ANoWelcome: Boolean; const ALogFile: string; BaseKeyboardID: Integer): Boolean;
+ ANoWelcome: Boolean; const ALogFile: string): 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; BaseKeyboardID: Integer): Boolean; static;
+ class function Handle(Owner: TComponent; const url: string; ASilent, ANoWelcome: Boolean; const ALogFile: string): Boolean; static;
end;
implementation
@@ -51,13 +51,13 @@ end;
class function TKeymanProtocolHandler.Handle(Owner: TComponent;
const url: string; ASilent, ANoWelcome: Boolean;
- const ALogFile: string; BaseKeyboardID: Integer): Boolean;
+ const ALogFile: string): Boolean;
var
h: TKeymanProtocolHandler;
begin
h := TKeymanProtocolHandler.Create;
try
- Result := h.DoHandle(Owner, url, ASilent, ANoWelcome, ALogFile, BaseKeyboardID);
+ Result := h.DoHandle(Owner, url, ASilent, ANoWelcome, ALogFile);
finally
h.Free;
end;
@@ -65,7 +65,7 @@ end;
function TKeymanProtocolHandler.DoHandle(Owner: TComponent;
const url: string; ASilent, ANoWelcome: Boolean;
- const ALogFile: string; BaseKeyboardID: Integer): Boolean;
+ const ALogFile: string): 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, BaseKeyboardID);
+ Result := TInstallFile.Execute(nil, FDownloadFilename, False, False, '', BCP47);
finally
if FileExists(FDownloadFilename) then
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
index 775aa1ca2e..6d79811a80 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
@@ -74,7 +74,6 @@ uses
Vcl.StdCtrls,
keymanapi_TLB,
- KeymanOptionNames,
UfrmKeymanBase,
UfrmWebContainer;
@@ -103,7 +102,7 @@ type
protected
procedure FireCommand(const command: WideString; params: TStringList); override;
public
- procedure InstallKeyboard(const ALogFile, BCP47Tag: string; BaseKeyboardID: Integer);
+ procedure InstallKeyboard(const ALogFile, BCP47Tag: string);
property DefaultBCP47Tag: string read FDefaultBCP47Tag write SetDefaultBCP47Tag;
property InstallFile: string read FInstallFile write SetInstallFile;
property Silent: Boolean read FSilent write FSilent;
@@ -272,7 +271,6 @@ 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
@@ -285,8 +283,7 @@ begin
Manager.Title := 'Installing Keyboard';
Manager.CanCancel := False;
Manager.UpdateProgress('Installing Keyboard', 0, 0);
- BaseKeyboardID := kmcom.Options[KeymanOptionName(koBaseLayout)].Value;
- InstallKeyboard('', BCP47Tag, BaseKeyboardID);
+ InstallKeyboard('', BCP47Tag);
Result := True;
end
);
@@ -302,7 +299,6 @@ begin
var
t: TTempFile;
ExecParams: string;
- BaseKeyboardString :string;
begin
KL.MethodEnter(Self, '"keyboard_install"', [params.Text]);
try
@@ -310,9 +306,8 @@ 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+'" -bkd "'+BaseKeyboardString+'" -s -i "'+FInstallFile+'='+BCP47Tag+'"'+
+ ExecParams := '-log "'+t.Name+'" -s -i "'+FInstallFile+'='+BCP47Tag+'"'+
' -nowelcome '+TTIPMaintenance.GetUserDefaultLangParameterString;
KL.Log('Calling elevated kmshell %s', [ExecParams]);
if WaitForElevatedConfiguration(GetForegroundWindow, ExecParams) = 0 then
@@ -363,7 +358,7 @@ end;
------------------------------------------------------------------------------}
// TODO: move this to TInstallFile
-procedure TfrmInstallKeyboard.InstallKeyboard(const ALogFile, BCP47Tag: string; BaseKeyboardID: Integer);
+procedure TfrmInstallKeyboard.InstallKeyboard(const ALogFile, BCP47Tag: string);
var
i: Integer;
kbd: IKeymanKeyboardInstalled;
@@ -405,7 +400,7 @@ begin
kbd := nil;
kmcom.Keyboards.Apply;
kmcom.Keyboards.Refresh;
- FInstalledKeyboard := (FKeyboard as IKeymanKeyboardFile3).Install3(True, BaseKeyboardID);
+ FInstalledKeyboard := (FKeyboard as IKeymanKeyboardFile2).Install2(True);
if not InstallTipForKeyboard(BCP47Tag) then
begin
// TODO can we return a failure code?
@@ -466,7 +461,7 @@ begin
kmcom.Keyboards.Apply;
kmcom.Keyboards.Refresh; // I2169
- (FPackage as IKeymanPackageFile3).Install3(True, BaseKeyboardID);
+ (FPackage as IKeymanPackageFile2).Install2(True);
kmcom.Refresh;
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
index ed236d489d..aff3251037 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
@@ -87,7 +87,6 @@ uses
Keyman.Configuration.UI.InstallFile,
Keyman.System.LocaleStrings,
kmint,
- KeymanOptionNames,
MessageIdentifierConsts,
Upload_Settings,
utilfiletypes,
@@ -214,7 +213,6 @@ end;
procedure TfrmInstallKeyboardFromWeb.DownloadAndInstallPackage(const PackageID, BCP47: string);
var
FTempDir: string;
- BaseKeyboardID: Integer;
begin
FTempDir := IncludeTrailingPathDelimiter(CreateTempPath); // I1679
try
@@ -232,8 +230,8 @@ begin
finally
frmDownloadProgress.Free;
end;
- BaseKeyboardID := kmcom.Options[KeymanOptionName(koBaseLayout)].Value;
- if TInstallFile.Execute(Self, FDownloadFilename, False, False, '', BCP47, BaseKeyboardID) then
+
+ if TInstallFile.Execute(Self, FDownloadFilename, False, False, '', BCP47) then
ModalResult := mrOk;
finally
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 3143d72828..64fc2cd214 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -207,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, FBaseKeyboard: Integer): Boolean;
+ var FParentWindow: THandle; var FDefaultBCP47: string; var FDefaultLangID: Integer): Boolean;
var
s: string;
i: Integer;
@@ -264,7 +264,6 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := ParamStr(i); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
@@ -331,7 +330,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, FBaseKeyboard: Integer); forward;
+ const FDefaultBCP47: string; FDefaultLangID: Integer); forward;
procedure Run;
var
@@ -342,7 +341,7 @@ var
FForce: Boolean;
FParentWindow: THandle;
FLogFile: string;
- FDefaultLangID, FBaseKeyboard: Integer;
+ FDefaultLangID: Integer;
FDefaultBCP47, FDisablePackages, FDefaultUILanguage: string;
FStartWithConfiguration: Boolean;
begin
@@ -351,7 +350,7 @@ begin
KeyboardFileNames := TStringList.Create;
try
FParentWindow := 0;
- if not Init(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID, FBaseKeyboard) then
+ if not Init(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID) then
begin
//TODO: TUtilExecute.Shell(PChar('hh.exe mk:@MSITStore:'+ExtractFilePath(KMShellExe)+'keyman.chm::/context/keyman_usage.html'), SW_SHOWNORMAL);
Exit;
@@ -359,7 +358,7 @@ begin
if not LoadKMCOM then Exit;
try
- RunKMCOM(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID, FBaseKeyboard);
+ RunKMCOM(FMode, KeyboardFileNames, FSilent, FForce, FNoWelcome, FLogFile, FQuery, FDisablePackages, FDefaultUILanguage, FStartWithConfiguration, FParentWindow, FDefaultBCP47, FDefaultLangID);
finally
kmcom := nil;
end;
@@ -398,7 +397,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, FBaseKeyboard: Integer);
+ FParentWindow: THandle; const FDefaultBCP47: string; FDefaultLangID: Integer);
var
kdl: IKeymanDefaultLanguage;
FIcon: string;
@@ -561,7 +560,7 @@ begin
else ExitCode := 1;
fmInstall:
- if TInstallFile.Execute(KeyboardFileNames, FirstKeyboardFileName, FSilent, FNoWelcome, FLogFile, FBaseKeyboard)
+ if TInstallFile.Execute(KeyboardFileNames, FirstKeyboardFileName, FSilent, FNoWelcome, FLogFile)
then ExitCode := 0
else ExitCode := 1;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
index b7255869b7..5ea1e74265 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
@@ -37,7 +37,7 @@ uses
keymankeyboard, keymancontext, Classes, PackageInfo, keymankeyboardlanguagesfile;
type
- TKeymanKeyboardFile = class(TKeymanKeyboard, IKeymanKeyboardFile, IKeymanKeyboardFile2, IKeymanKeyboardFile3)
+ TKeymanKeyboardFile = class(TKeymanKeyboard, IKeymanKeyboardFile, IKeymanKeyboardFile2)
private
FFileName: WideString;
FError: Boolean;
@@ -52,7 +52,6 @@ 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;
@@ -253,7 +252,7 @@ procedure TKeymanKeyboardFile.Install(Force: WordBool);
begin
with TKPInstallKeyboard.Create(Context) do
try
- Execute(FFileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force, 0);
+ Execute(FFileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force);
finally
Free;
end;
@@ -265,23 +264,7 @@ var
begin
with TKPInstallKeyboard.Create(Context) do
try
- 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);
+ Execute(FFileName, '', [], nil, Force);
finally
Free;
end;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
index b37e0dfce0..f5f8b74448 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
@@ -37,8 +37,7 @@ uses
keymanerrorcodes, keymankeyboardinstalled, keymankeyboard, internalinterfaces;
type
- TKeymanKeyboardsInstalled = class(TKeymanAutoCollectionObject, IKeymanKeyboardsInstalled,
- IKeymanKeyboardsInstalled2, IIntKeymanKeyboardsInstalled, IKeymanKeyboardsInstalled3) // I4376
+ TKeymanKeyboardsInstalled = class(TKeymanAutoCollectionObject, IKeymanKeyboardsInstalled, IKeymanKeyboardsInstalled2, IIntKeymanKeyboardsInstalled) // I4376
private
FKeyboards: TKeyboardList;
procedure TriggerWindowsLanguageSync;
@@ -56,7 +55,6 @@ 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 }
@@ -103,7 +101,7 @@ procedure TKeymanKeyboardsInstalled.Install(const Filename: WideString; Force: W
begin
with TKPInstallKeyboard.Create(Context) do
try
- Execute(FileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force, 0);
+ Execute(FileName, '', [ikLegacyRegisterAndInstallProfiles], nil, Force);
finally
Free;
end;
@@ -114,21 +112,7 @@ function TKeymanKeyboardsInstalled.Install2(const Filename: WideString;
begin
with TKPInstallKeyboard.Create(Context) do
try
- 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);
+ Execute(FileName, '', [], nil, Force);
finally
Free;
end;
diff --git a/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas b/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
index 28ef2d064a..14b207e229 100644
--- a/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
+++ b/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
@@ -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, IKeymanPackageFile3)
+ TKeymanPackageFile = class(TKeymanAutoObject, IKeymanPackage, IKeymanPackageFile, IKeymanPackageFile2)
private
FSourcePath: string;
FSubFiles: IKeymanPackageContentFiles;
@@ -74,7 +74,6 @@ 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;
@@ -176,7 +175,7 @@ begin
o := [ipLegacyRegisterAndInstallProfiles];
if Force then
Include(o, ipForce);
- Execute(FFileName, o, 0);
+ Execute(FFileName, o);
finally
Free;
end;
@@ -192,27 +191,7 @@ begin
o := [];
if Force then
Include(o, ipForce);
- 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);
+ Execute(FFileName, o);
finally
Free;
end;
@@ -232,7 +211,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;
diff --git a/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas b/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
index 821a38bdc5..9dd9f1a52a 100644
--- a/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
@@ -38,8 +38,7 @@ type
property Items[Index: Integer]: IIntKeymanPackageInstalled read GetItem write SetItem; default;
end;
- TKeymanPackagesInstalled = class(TKeymanAutoCollectionObject,
- IKeymanPackagesInstalled, IKeymanPackagesInstalled2, IKeymanPackagesInstalled3)
+ TKeymanPackagesInstalled = class(TKeymanAutoCollectionObject, IKeymanPackagesInstalled, IKeymanPackagesInstalled2)
private
FPackages: TPackageList;
protected
@@ -52,7 +51,6 @@ 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;
@@ -120,7 +118,7 @@ begin
o := [ipLegacyRegisterAndInstallProfiles];
if Force then
Include(o, ipForce);
- Execute(Filename, o, 0);
+ Execute(Filename, o);
finally
Free;
end;
@@ -138,29 +136,7 @@ begin
o := [];
if Force then
Include(o, ipForce);
- 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);
+ Execute(Filename, o);
finally
Free;
end;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 7246c9173f..786ce6c9b2 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1569,17 +1569,6 @@ type
procedure RefreshInstalledKeyboards; safecall;
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; BasePackageID: Integer): IKeymanKeyboardInstalled; safecall;
- procedure RefreshInstalledKeyboards; safecall;
- end;
-
// *********************************************************************//
// Interface: IKeymanKeyboardInstalled2
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1621,16 +1610,6 @@ type
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
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; BasePackageID: Integer): IKeymanPackageInstalled; safecall;
- end;
-
// *********************************************************************//
// DispIntf: IKeymanPackagesInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1660,16 +1639,6 @@ type
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
end;
-// *********************************************************************//
-// Interface: IKeymanKeyboardFile3
-// Flags: (4416) Dual OleAutomation Dispatchable
-// GUID: {EDE4326B-51F4-42D5-8251-B20B71993EC8}
-// *********************************************************************//
- IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
- ['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
- end;
-
// *********************************************************************//
// DispIntf: IKeymanKeyboardFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1708,17 +1677,6 @@ type
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
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: IKeymanPackageFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 91f49822b5..9eb63fbd68 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -66,10 +66,6 @@ library keymanapi
interface IKeymanPackageFile2;
interface IKeymanKeyboardLanguageInstalled2;
interface IKeymanKeyboardLanguagesInstalled2;
- interface IKeymanKeyboardFile3;
- interface IKeymanPackageFile3;
- interface IKeymanKeyboardsInstalled3;
- interface IKeymanPackagesInstalled3;
interface IKeymanBCP47Canonicalization;
interface IKeymanDefaultLanguage;
@@ -980,60 +976,6 @@ library keymanapi
HRESULT _stdcall Install2([in] VARIANT_BOOL Force, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
};
- [
- 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] IKeymanPackageFile** Package);
- };
-
-
- [
- uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
- version(19.0),
- helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\3"),
- 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(9B43B6BC-C622-47EF-915E-6780CF53BAAA),
version(14.0),
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
index 1d198264cb..1205d77f72 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
@@ -64,7 +64,7 @@ type
ikLegacyRegisterAndInstallProfiles);
TKPInstallKeyboard = class(TKPBase)
- procedure Execute(const FileName: string; const PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean; BaseKeyboardID: Integer);
+ procedure Execute(const FileName, PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean);
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: string; const PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean; BaseKeyboardID: Integer);
+procedure TKPInstallKeyboard.Execute(const FileName, PackageID: string; FInstallOptions: TKPInstallKeyboardOptions; Languages: TPackageKeyboardLanguageList; Force: Boolean);
var
ki: TKeyboardInfo;
FDestPath: string;
@@ -125,9 +125,7 @@ var
FExitCode: Integer;
FKVKName: WideString;
FCreatedIcon: Boolean;
- ElevatedBaseKeyboardID: Integer;
- KeymanContext: TKeymanContext;
- RecompileMnemonicKeyboard: TKPRecompileMnemonicKeyboard;
+ BaseKeyboardID: Integer;
begin
KL.MethodEnter(Self, 'Execute', [FileName,PackageID,ikPartOfPackage in FInstallOptions ,Force]);
try
@@ -248,19 +246,14 @@ begin
KL.Log(FLogText);
end;
- // 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
+ // Recompile a mnemonic layout to the user's selected base layout
if ki.MnemonicLayout then // I4169
begin
- KeymanContext := Context as TKeymanContext;
- ElevatedBaseKeyboardID := (KeymanContext.Options as IKeymanOptions).Items['koBaseLayout'].Value;
- RecompileMnemonicKeyboard := TKPRecompileMnemonicKeyboard.Create(Context);
+ with Context as TKeymanContext do
+ BaseKeyboardID := (Options as IKeymanOptions).Items['koBaseLayout'].Value;
+ with TKPRecompileMnemonicKeyboard.Create(Context) do
try
- if (BaseKeyboardID = 0) then
- RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, ElevatedBaseKeyboardID)
- else
- RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, BaseKeyboardID);
+ Execute(FDestFileName, PackageID, BaseKeyboardID);
finally
RecompileMnemonicKeyboard.Free;
end;
diff --git a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
index 4ab33cdfee..98edfbf3fb 100644
--- a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
+++ b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
@@ -50,7 +50,7 @@ type
TKPInstallPackage = class(TKPBase)
public
- procedure Execute(const FileName: string; Options: TKPInstallPackageOptions; BaseKeyboardID: Integer);
+ procedure Execute(const FileName: string; Options: TKPInstallPackageOptions);
end;
implementation
@@ -85,7 +85,7 @@ uses
{ TKPInstallPackage }
-procedure TKPInstallPackage.Execute(const FileName: string; Options: TKPInstallPackageOptions; BaseKeyboardID: Integer);
+procedure TKPInstallPackage.Execute(const FileName: string; Options: TKPInstallPackageOptions);
function GetHHIcon: string;
var
buf: array[0..260] of char;
@@ -112,7 +112,7 @@ var
FErrorValue: Cardinal;
FSrcFileName: string;
- procedure InstallKeyboard(FileName: string; BaseKeyboardID: Integer);
+ procedure InstallKeyboard(FileName: string);
var
FOptions: TKPInstallKeyboardOptions;
kbd: TPackageKeyboard;
@@ -132,7 +132,7 @@ var
with TKPInstallKeyboard.Create(Context) do
try
- Execute(FileName, PackageName, FOptions, FLanguages, ipForce in Options, BaseKeyboardID);
+ Execute(FileName, PackageName, FOptions, FLanguages, ipForce in Options);
finally
Free;
end;
@@ -253,12 +253,12 @@ begin
begin
case inf.Files[i].FileType of
ftKeymanFile:
- InstallKeyboard(dest + inf.Files[i].FileName, BaseKeyboardID);
+ InstallKeyboard(dest + inf.Files[i].FileName);
ftPackageFile:
with TKPInstallPackage.Create(Context) do
try
- Execute(dest + inf.Files[i].FileName, Options, BaseKeyboardID);
+ Execute(dest + inf.Files[i].FileName, Options);
finally
Free;
end;
From b9743d277b69077de9b6a934504eef56e8346479 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Thu, 3 Sep 2026 17:37:22 +1000
Subject: [PATCH 15/44] fix(windows): fix indentation
---
windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index 56e7c442b4..91a6710112 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -33,7 +33,8 @@ uses
utilkmshell;
function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
-begin with TfrmBaseKeyboard.Create(nil) do
+begin
+ with TfrmBaseKeyboard.Create(nil) do
try
Result := ShowModal = mrOk;
if Result then
From 91a1a4267e955a7200858627a63ca53fdf23520c Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 31 Aug 2026 15:34:45 +1000
Subject: [PATCH 16/44] fix(windows): pass basekeyboardid to installation api
The updates all the apis so that the basekeyboardid or klid
can be passed in as an argument. This in needed so that
elevated process required to compile the keyboard has
the call users keyboard base id.
---
.../Keyman.Configuration.UI.InstallFile.pas | 30 +++++-----
...Configuration.UI.KeymanProtocolHandler.pas | 12 ++--
.../kmshell/install/UfrmInstallKeyboard.pas | 17 ++++--
.../install/UfrmInstallKeyboardFromWeb.pas | 6 +-
windows/src/desktop/kmshell/main/initprog.pas | 15 ++---
.../com/keyboards/keymankeyboardfile.pas | 23 +++++++-
.../keyboards/keymankeyboardsinstalled.pas | 22 ++++++-
.../com/packages/keymanpackagefile.pas | 43 ++++++++++----
.../com/packages/keymanpackagesinstalled.pas | 30 +++++++++-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 42 ++++++++++++++
windows/src/engine/kmcomapi/kmcomapi.ridl | 58 +++++++++++++++++++
.../processes/keyboard/kpinstallkeyboard.pas | 23 +++++---
.../processes/package/kpinstallpackage.pas | 12 ++--
13 files changed, 264 insertions(+), 69 deletions(-)
diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
index c6ee3564ae..3cc8dcc901 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.InstallFile.pas
@@ -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,29 +35,30 @@ uses
Keyman.Configuration.UI.KeymanProtocolHandler,
Keyman.Configuration.UI.MitigationForWin10_1803,
kmint,
+ KeymanOptionNames,
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;
@@ -77,7 +78,7 @@ begin
begin
if ASilent then
begin
- InstallKeyboard(LogFile, BCP47);
+ InstallKeyboard(LogFile, BCP47, BaseKeyboardID);
Result := True;
end
else
@@ -139,7 +140,7 @@ end;
/// This is the handler for the `-i` parameter, e.g.
/// kmshell -i khmer_angkor.kmp c:\temp\sil_euro_latin.kmp=fr
///
-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;
@@ -170,7 +171,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, '');
@@ -178,7 +179,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);
@@ -209,6 +210,7 @@ end;
class function TInstallFile.BrowseAndInstallKeyboardFromFile(Owner: TComponent): Boolean;
var
dlgOpen: TOpenDialog;
+ BaseKeyboardID : Integer;
begin
dlgOpen := TOpenDialog.Create(nil);
try
@@ -216,9 +218,9 @@ begin
'Keyman files (*.kmx, *.kxx, *.kmp)|*.kmx;*.kxx;*.kmp|Keyman keyboards (*.kmx,*.kxx)' +
'|*.kmx;*.kxx|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
diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
index 6f13f6777b..60ac8ae43c 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.UI.KeymanProtocolHandler.pas
@@ -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
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
index 6d79811a80..775aa1ca2e 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
@@ -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+'" -bkd "'+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;
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
index aff3251037..ed236d489d 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardFromWeb.pas
@@ -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
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 64fc2cd214..3143d72828 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -207,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;
@@ -264,6 +264,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := ParamStr(i); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
@@ -330,7 +331,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
@@ -341,7 +342,7 @@ var
FForce: Boolean;
FParentWindow: THandle;
FLogFile: string;
- FDefaultLangID: Integer;
+ FDefaultLangID, FBaseKeyboard: Integer;
FDefaultBCP47, FDisablePackages, FDefaultUILanguage: string;
FStartWithConfiguration: Boolean;
begin
@@ -350,7 +351,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;
@@ -358,7 +359,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;
@@ -397,7 +398,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;
@@ -560,7 +561,7 @@ begin
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;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
index 5ea1e74265..b7255869b7 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardfile.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
index f5f8b74448..b37e0dfce0 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardsinstalled.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas b/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
index 14b207e229..28ef2d064a 100644
--- a/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
+++ b/windows/src/engine/kmcomapi/com/packages/keymanpackagefile.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas b/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
index 9dd9f1a52a..821a38bdc5 100644
--- a/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/packages/keymanpackagesinstalled.pas
@@ -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;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 786ce6c9b2..7246c9173f 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1569,6 +1569,17 @@ type
procedure RefreshInstalledKeyboards; safecall;
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; BasePackageID: Integer): IKeymanKeyboardInstalled; safecall;
+ procedure RefreshInstalledKeyboards; safecall;
+ end;
+
// *********************************************************************//
// Interface: IKeymanKeyboardInstalled2
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1610,6 +1621,16 @@ type
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
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; BasePackageID: Integer): IKeymanPackageInstalled; safecall;
+ end;
+
// *********************************************************************//
// DispIntf: IKeymanPackagesInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1639,6 +1660,16 @@ type
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
end;
+// *********************************************************************//
+// Interface: IKeymanKeyboardFile3
+// Flags: (4416) Dual OleAutomation Dispatchable
+// GUID: {EDE4326B-51F4-42D5-8251-B20B71993EC8}
+// *********************************************************************//
+ IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
+ ['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
+ end;
+
// *********************************************************************//
// DispIntf: IKeymanKeyboardFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1677,6 +1708,17 @@ type
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
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: IKeymanPackageFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 9eb63fbd68..91f49822b5 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -66,6 +66,10 @@ library keymanapi
interface IKeymanPackageFile2;
interface IKeymanKeyboardLanguageInstalled2;
interface IKeymanKeyboardLanguagesInstalled2;
+ interface IKeymanKeyboardFile3;
+ interface IKeymanPackageFile3;
+ interface IKeymanKeyboardsInstalled3;
+ interface IKeymanPackagesInstalled3;
interface IKeymanBCP47Canonicalization;
interface IKeymanDefaultLanguage;
@@ -976,6 +980,60 @@ library keymanapi
HRESULT _stdcall Install2([in] VARIANT_BOOL Force, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
};
+ [
+ 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] IKeymanPackageFile** Package);
+ };
+
+
+ [
+ uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
+ version(19.0),
+ helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\3"),
+ 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(9B43B6BC-C622-47EF-915E-6780CF53BAAA),
version(14.0),
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
index 1205d77f72..1d198264cb 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
@@ -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,7 +125,9 @@ var
FExitCode: Integer;
FKVKName: WideString;
FCreatedIcon: Boolean;
- BaseKeyboardID: Integer;
+ ElevatedBaseKeyboardID: Integer;
+ KeymanContext: TKeymanContext;
+ RecompileMnemonicKeyboard: TKPRecompileMnemonicKeyboard;
begin
KL.MethodEnter(Self, 'Execute', [FileName,PackageID,ikPartOfPackage in FInstallOptions ,Force]);
try
@@ -246,14 +248,19 @@ 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 Context as TKeymanContext do
- BaseKeyboardID := (Options as IKeymanOptions).Items['koBaseLayout'].Value;
- 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, BaseKeyboardID);
+ if (BaseKeyboardID = 0) then
+ RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, ElevatedBaseKeyboardID)
+ else
+ RecompileMnemonicKeyboard.Execute(FDestFileName, PackageID, BaseKeyboardID);
finally
RecompileMnemonicKeyboard.Free;
end;
diff --git a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
index 98edfbf3fb..4ab33cdfee 100644
--- a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
+++ b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
@@ -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;
@@ -253,12 +253,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;
From f40e0754133e89da36b1314b335918d0d1768e65 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Tue, 1 Sep 2026 15:53:48 +1000
Subject: [PATCH 17/44] fix(windows): strtoint for command line basekeyboardid
Also rebuild kmcomapi_TLB.pas
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 251 ++++++++++++++----
windows/src/engine/kmcomapi/kmcomapi.ridl | 108 ++++----
3 files changed, 252 insertions(+), 109 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 3143d72828..6457542bca 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -264,7 +264,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := ParamStr(i); end
+ else if s = 'bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 7246c9173f..3c436dc5d9 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -12,7 +12,7 @@ unit keymanapi_TLB;
// ************************************************************************ //
// $Rev: 52393 $
-// File generated on 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;
@@ -1569,27 +1584,6 @@ type
procedure RefreshInstalledKeyboards; safecall;
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; BasePackageID: Integer): IKeymanKeyboardInstalled; safecall;
- procedure RefreshInstalledKeyboards; safecall;
- 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: IKeymanKeyboardsInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1611,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
@@ -1621,16 +1660,6 @@ type
function Install2(const Filename: WideString; Force: WordBool): IKeymanPackageInstalled; safecall;
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; BasePackageID: Integer): IKeymanPackageInstalled; safecall;
- end;
-
// *********************************************************************//
// DispIntf: IKeymanPackagesInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1660,16 +1689,6 @@ type
function Install2(Force: WordBool): IKeymanKeyboardInstalled; safecall;
end;
-// *********************************************************************//
-// Interface: IKeymanKeyboardFile3
-// Flags: (4416) Dual OleAutomation Dispatchable
-// GUID: {EDE4326B-51F4-42D5-8251-B20B71993EC8}
-// *********************************************************************//
- IKeymanKeyboardFile3 = interface(IKeymanKeyboardFile)
- ['{8F4B2D91-6C37-4A05-BE82-1D9F7C53A6E4}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanKeyboardInstalled; safecall;
- end;
-
// *********************************************************************//
// DispIntf: IKeymanKeyboardFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1708,17 +1727,6 @@ type
function Install2(Force: WordBool): IKeymanPackageInstalled; safecall;
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: IKeymanPackageFile2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1820,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): IKeymanPackageFile; 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): IKeymanPackageFile; 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
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 91f49822b5..ecfff3d707 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -6,7 +6,7 @@
// However, when applying changes via the Editor this file will be regenerated
// and comments or formatting changes will be lost.
// ************************************************************************ //
-// File generated on 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),
@@ -980,60 +980,6 @@ library keymanapi
HRESULT _stdcall Install2([in] VARIANT_BOOL Force, [out, retval] IKeymanKeyboardInstalled** KeyboardResult);
};
- [
- 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] IKeymanPackageFile** Package);
- };
-
-
- [
- uuid(B7D3A8F1-6C42-4E95-AB17-93F0C2D8E641),
- version(19.0),
- helpstring("https://help.keyman.com/developer/engine/windows/19.0/api/IKeymanKeyboardInstalled2\3"),
- 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(9B43B6BC-C622-47EF-915E-6780CF53BAAA),
version(14.0),
@@ -1079,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] IKeymanPackageFile** Package);
+ };
+
+ [
+ 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,
From 20d5a4c10466274f0f27c78bde6300f842a32a1d Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 2 Sep 2026 10:10:16 +1000
Subject: [PATCH 18/44] fix(windows): typos and return type in interface
declaration
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 4 ++--
windows/src/engine/kmcomapi/kmcomapi.ridl | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 6457542bca..2d6cc1e6d2 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -264,7 +264,7 @@ 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 = 'bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
+ else if s = '-bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 3c436dc5d9..b0530cb94a 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -1873,7 +1873,7 @@ type
// *********************************************************************//
IKeymanPackageFile3 = interface(IKeymanPackageFile)
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageFile; safecall;
+ function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageInstalled; safecall;
end;
// *********************************************************************//
@@ -1883,7 +1883,7 @@ type
// *********************************************************************//
IKeymanPackageFile3Disp = dispinterface
['{C27A6E4B-9D13-47F8-A052-6B8E31D4F9C7}']
- function Install3(Force: WordBool; BaseKeyboardID: Integer): IKeymanPackageFile; dispid 292;
+ 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;
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index ecfff3d707..0b2366c144 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -1048,7 +1048,7 @@ library keymanapi
interface IKeymanPackageFile3: IKeymanPackageFile
{
[id(0x00000124)]
- HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageFile** Package);
+ HRESULT _stdcall Install3([in] VARIANT_BOOL Force, [in] long BaseKeyboardID, [out, retval] IKeymanPackageInstalled** PackageResult);
};
[
From d67c4911af9b5431cda22eb9a13e36e8574386ad Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 4 Sep 2026 13:48:49 +1000
Subject: [PATCH 19/44] fix(windows): rename Setting System
Also update the command line to use FBaseKeyboardID instead of
FQuery.
---
windows/src/desktop/kmshell/kmshell.dpr | 2 +-
windows/src/desktop/kmshell/kmshell.dproj | 2 +-
...man.Configuration.System.BaseKeyboard.pas} | 27 +++++++------------
windows/src/desktop/kmshell/main/UfrmMain.pas | 2 +-
windows/src/desktop/kmshell/main/initprog.pas | 18 ++++++-------
5 files changed, 21 insertions(+), 30 deletions(-)
rename windows/src/desktop/kmshell/{settings/Keyman.Configuration.Settings.BaseKeyboard.pas => main/Keyman.Configuration.System.BaseKeyboard.pas} (72%)
diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr
index e4ce8316d3..124b0357ce 100644
--- a/windows/src/desktop/kmshell/kmshell.dpr
+++ b/windows/src/desktop/kmshell/kmshell.dpr
@@ -184,7 +184,7 @@ uses
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.Settings.BaseKeyboard in 'settings\Keyman.Configuration.Settings.BaseKeyboard.pas';
+ Keyman.Configuration.System.BaseKeyboard in 'main\Keyman.Configuration.System.BaseKeyboard.pas';
{$R VERSION.RES}
{$R manifest.res}
diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj
index ca611b40e4..abcb521597 100644
--- a/windows/src/desktop/kmshell/kmshell.dproj
+++ b/windows/src/desktop/kmshell/kmshell.dproj
@@ -358,7 +358,7 @@
-
+
Cfg_2
diff --git a/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
similarity index 72%
rename from windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
rename to windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index 44488179c9..c47d90a1a5 100644
--- a/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -1,4 +1,4 @@
-unit Keyman.Configuration.Settings.BaseKeyboard;
+unit Keyman.Configuration.System.BaseKeyboard;
interface
@@ -8,8 +8,7 @@ uses
keymanapi_TLB;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
-function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
+function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
implementation
@@ -47,7 +46,7 @@ begin
WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8));
end
else
- CompileForBaseKeyboard(BaseKeyboardID);
+ MCompileBaseKeyboard(BaseKeyboardID);
end;
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
@@ -55,29 +54,21 @@ begin
Result := True;
end;
-function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-var
- BaseKeyboardID: Integer;
-begin
- Result := False;
- if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
- not kmcom.SystemInfo.IsAdministrator then
- Exit;
- CompileForBaseKeyboard(BaseKeyboardID);
- // TODO: sort out whether we need todo return a result
- Result := True;
-end;
-
-procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
+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.
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index 599566b91c..9c4478283e 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -174,7 +174,7 @@ uses
Hints,
HotkeyUtils,
initprog,
- Keyman.Configuration.Settings.BaseKeyboard,
+ Keyman.Configuration.System.BaseKeyboard,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.UI.UfrmDiagnosticTests,
KeymanOptionNames,
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 64fc2cd214..54f4a73f65 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -115,7 +115,7 @@ uses
GetOsVersion,
help,
HTMLHelpViewer,
- Keyman.Configuration.Settings.BaseKeyboard,
+ Keyman.Configuration.System.BaseKeyboard,
Keyman.Configuration.UI.InstallFile,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.System.UImportOlderVersionKeyboards11To13,
@@ -207,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;
@@ -269,7 +269,7 @@ begin
FMode := fmMCompileKbds;
Inc(i);
if i > ParamCount then Exit;
- FQuery := ParamStr(i);
+ FBaseKeyboard := StrToInt('$' + ParamStr(i));
end
else if s = '-nowelcome' then FNoWelcome := True
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
@@ -330,7 +330,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
@@ -341,7 +341,7 @@ var
FForce: Boolean;
FParentWindow: THandle;
FLogFile: string;
- FDefaultLangID: Integer;
+ FDefaultLangID, FBaseKeyboard: Integer;
FDefaultBCP47, FDisablePackages, FDefaultUILanguage: string;
FStartWithConfiguration: Boolean;
begin
@@ -350,7 +350,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;
@@ -358,7 +358,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;
@@ -397,7 +397,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;
@@ -555,7 +555,7 @@ begin
else ExitCode := 1;
fmMCompileKbds:
- if MCompileBaseKeyboard(FQuery)
+ if MCompileBaseKeyboard(FBaseKeyboard)
then ExitCode := 0
else ExitCode := 1;
From 1eda5fe90ee9e17dd121839da0036fa7f7ee8628 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 4 Sep 2026 13:48:49 +1000
Subject: [PATCH 20/44] fix(windows): rename Setting System
Also update the command line to use FBaseKeyboardID instead of
FQuery.
---
windows/src/desktop/kmshell/kmshell.dpr | 2 +-
windows/src/desktop/kmshell/kmshell.dproj | 2 +-
...man.Configuration.System.BaseKeyboard.pas} | 27 +++++++------------
windows/src/desktop/kmshell/main/UfrmMain.pas | 2 +-
windows/src/desktop/kmshell/main/initprog.pas | 6 ++---
5 files changed, 15 insertions(+), 24 deletions(-)
rename windows/src/desktop/kmshell/{settings/Keyman.Configuration.Settings.BaseKeyboard.pas => main/Keyman.Configuration.System.BaseKeyboard.pas} (72%)
diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr
index e4ce8316d3..124b0357ce 100644
--- a/windows/src/desktop/kmshell/kmshell.dpr
+++ b/windows/src/desktop/kmshell/kmshell.dpr
@@ -184,7 +184,7 @@ uses
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.Settings.BaseKeyboard in 'settings\Keyman.Configuration.Settings.BaseKeyboard.pas';
+ Keyman.Configuration.System.BaseKeyboard in 'main\Keyman.Configuration.System.BaseKeyboard.pas';
{$R VERSION.RES}
{$R manifest.res}
diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj
index ca611b40e4..abcb521597 100644
--- a/windows/src/desktop/kmshell/kmshell.dproj
+++ b/windows/src/desktop/kmshell/kmshell.dproj
@@ -358,7 +358,7 @@
-
+
Cfg_2
diff --git a/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
similarity index 72%
rename from windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
rename to windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index 44488179c9..c47d90a1a5 100644
--- a/windows/src/desktop/kmshell/settings/Keyman.Configuration.Settings.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -1,4 +1,4 @@
-unit Keyman.Configuration.Settings.BaseKeyboard;
+unit Keyman.Configuration.System.BaseKeyboard;
interface
@@ -8,8 +8,7 @@ uses
keymanapi_TLB;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
-function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
+function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
implementation
@@ -47,7 +46,7 @@ begin
WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8));
end
else
- CompileForBaseKeyboard(BaseKeyboardID);
+ MCompileBaseKeyboard(BaseKeyboardID);
end;
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
@@ -55,29 +54,21 @@ begin
Result := True;
end;
-function MCompileBaseKeyboard(const BaseKeyboardIDText: string): Boolean;
-var
- BaseKeyboardID: Integer;
-begin
- Result := False;
- if not TryStrToInt('$' + BaseKeyboardIDText, BaseKeyboardID) or
- not kmcom.SystemInfo.IsAdministrator then
- Exit;
- CompileForBaseKeyboard(BaseKeyboardID);
- // TODO: sort out whether we need todo return a result
- Result := True;
-end;
-
-procedure CompileForBaseKeyboard(BaseKeyboardID: Integer);
+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.
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index 599566b91c..9c4478283e 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -174,7 +174,7 @@ uses
Hints,
HotkeyUtils,
initprog,
- Keyman.Configuration.Settings.BaseKeyboard,
+ Keyman.Configuration.System.BaseKeyboard,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.UI.UfrmDiagnosticTests,
KeymanOptionNames,
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 2d6cc1e6d2..89c9733ba1 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -115,7 +115,7 @@ uses
GetOsVersion,
help,
HTMLHelpViewer,
- Keyman.Configuration.Settings.BaseKeyboard,
+ Keyman.Configuration.System.BaseKeyboard,
Keyman.Configuration.UI.InstallFile,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.System.UImportOlderVersionKeyboards11To13,
@@ -270,7 +270,7 @@ begin
FMode := fmMCompileKbds;
Inc(i);
if i > ParamCount then Exit;
- FQuery := ParamStr(i);
+ FBaseKeyboard := StrToInt('$' + ParamStr(i));
end
else if s = '-nowelcome' then FNoWelcome := True
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
@@ -556,7 +556,7 @@ begin
else ExitCode := 1;
fmMCompileKbds:
- if MCompileBaseKeyboard(FQuery)
+ if MCompileBaseKeyboard(FBaseKeyboard)
then ExitCode := 0
else ExitCode := 1;
From 36ae88acea2b32704156b3e8f06c2fff0d57427d Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 4 Sep 2026 14:05:18 +1000
Subject: [PATCH 21/44] fix(windows): cmd line switch rename
---
windows/src/desktop/kmshell/main/initprog.pas | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 89c9733ba1..fc5c9c868e 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -264,7 +264,7 @@ 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 = '-bkd' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
+ else if s = '-bklid' then begin Inc(i); FBaseKeyboard := StrToInt('$' + ParamStr(i)); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
From eb0c2648015e07c7fb2e91ec6d0ac16a945b51f3 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 4 Sep 2026 14:56:06 +1000
Subject: [PATCH 22/44] fix(windows): unsaved file checking
---
windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
index 775aa1ca2e..5833fc5460 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
@@ -312,7 +312,7 @@ begin
t := TTempFileManager.Get('.log');
BaseKeyboardString := IntToHex(kmcom.Options[KeymanOptionName(koBaseLayout)].Value, 8);
try
- ExecParams := '-log "'+t.Name+'" -bkd "'+BaseKeyboardString+'" -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
From 3cc8e42e61fce3ac47f5f0367113b0cdb84314d4 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 7 Sep 2026 10:18:06 +1000
Subject: [PATCH 23/44] fix(windows): add default for basekeyboardID
---
windows/src/desktop/kmshell/main/initprog.pas | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index fc5c9c868e..2febc1b59c 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -222,6 +222,7 @@ begin
FQuery := '';
FMode := fmStart;
KeyboardFileNames.Clear;
+ FBaseKeyboard := 0;
i := 1;
while i <= ParamCount do
@@ -264,13 +265,13 @@ 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 := StrToInt('$' + ParamStr(i)); end
+ else if s = '-bklid' then begin Inc(i); FBaseKeyboard := StrToIntDef('$' + ParamStr(i), 0); end
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;
Inc(i);
if i > ParamCount then Exit;
- FBaseKeyboard := StrToInt('$' + ParamStr(i));
+ FBaseKeyboard := StrToIntDef('$' + ParamStr(i), 0);
end
else if s = '-nowelcome' then FNoWelcome := True
else if s = '-kw' then FMode := fmKeyboardWelcome // I2569
From 0409cda1201fa4c9ff296d148ceeacbebb689b89 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 7 Sep 2026 13:03:29 +1000
Subject: [PATCH 24/44] fix(windows): add comments for new functions
---
...yman.Configuration.System.BaseKeyboard.pas | 31 ++++++++++++++++---
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 8 +++++
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index c47d90a1a5..d91b439f33 100644
--- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -7,7 +7,28 @@ uses
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
+ 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 base keyboard files for the specified KLID.
+ @param BaseKeyboardID KLID of the base keyboard to compile.
+ @returns True when the compilation is successful.
+*)
function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
implementation
@@ -38,20 +59,20 @@ end;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
begin
- Result := False;
+ Result := True;
if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
begin
if not kmcom.SystemInfo.IsAdministrator then
begin
- WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8));
+ Result := WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8)) = 0;
end
else
- MCompileBaseKeyboard(BaseKeyboardID);
+ Result := MCompileBaseKeyboard(BaseKeyboardID);
end;
-
+ if not Result then
+ Exit;
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
kmcom.Options.Apply;
- Result := True;
end;
function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index 91a6710112..dd298a8642 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -18,6 +18,14 @@ type
procedure FireCommand(const command: WideString; params: TStringList); override;
end;
+
+(**
+ Form for the user to select a base keyboard. If the user selects a base
+ keyboard, the KLID of the selected base keyboard is returned in
+ BaseKeyboardID.
+ @param [out] BaseKeyboardID KLID of the base keyboard selected by the user.
+ @returns True if the user selected a base keyboard.
+*)
function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
implementation
From 163806b712d97961b1fa099890a7b828b79149df Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 7 Sep 2026 17:17:39 +1000
Subject: [PATCH 25/44] fix(windows): git revert error fix
---
windows/src/engine/kmcomapi/keymanapi_TLB.pas | 60 +++++++++++++++----
windows/src/engine/kmcomapi/kmcomapi.ridl | 2 +-
.../processes/keyboard/kpinstallkeyboard.pas | 2 +-
3 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/windows/src/engine/kmcomapi/keymanapi_TLB.pas b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
index 786ce6c9b2..550a13064d 100644
--- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas
+++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas
@@ -12,7 +12,7 @@ unit keymanapi_TLB;
// ************************************************************************ //
// $Rev: 52393 $
-// File generated on 16/09/2021 6:54:44 PM from Type Library described below.
+// File generated on 7/09/2026 3:54:23 PM from Type Library described below.
// ************************************************************************ //
// Type Lib: C:\Projects\keyman\app\windows\src\engine\kmcomapi\kmcomapi (1)
@@ -87,6 +87,7 @@ 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}';
@@ -248,6 +249,8 @@ type
IKeymanKeyboardLanguagesFileDisp = dispinterface;
IKeymanKeyboardsInstalled2 = interface;
IKeymanKeyboardsInstalled2Disp = dispinterface;
+ IKeymanKeyboardInstalled2 = interface;
+ IKeymanKeyboardInstalled2Disp = dispinterface;
IKeymanPackagesInstalled2 = interface;
IKeymanPackagesInstalled2Disp = dispinterface;
IKeymanKeyboardFile2 = interface;
@@ -1569,16 +1572,6 @@ type
procedure RefreshInstalledKeyboards; safecall;
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: IKeymanKeyboardsInstalled2Disp
// Flags: (4416) Dual OleAutomation Dispatchable
@@ -1600,6 +1593,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
diff --git a/windows/src/engine/kmcomapi/kmcomapi.ridl b/windows/src/engine/kmcomapi/kmcomapi.ridl
index 9eb63fbd68..4d65b37777 100644
--- a/windows/src/engine/kmcomapi/kmcomapi.ridl
+++ b/windows/src/engine/kmcomapi/kmcomapi.ridl
@@ -6,7 +6,7 @@
// However, when applying changes via the Editor this file will be regenerated
// and comments or formatting changes will be lost.
// ************************************************************************ //
-// File generated on 16/09/2021 6:54:45 PM (- $Rev: 12980 $, 32940875).
+// File generated on 7/09/2026 4:05:40 PM (- $Rev: 12980 $, 1707828).
[
uuid(F16E2A9A-DA46-4EA3-BFF3-BA46B480C961),
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
index 1205d77f72..1df237b0c6 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas
@@ -255,7 +255,7 @@ begin
try
Execute(FDestFileName, PackageID, BaseKeyboardID);
finally
- RecompileMnemonicKeyboard.Free;
+ Free;
end;
end;
finally
From 63d12b671a3a06647d11272bdfa8c4e5b2aa7270 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 7 Sep 2026 20:43:26 +1000
Subject: [PATCH 26/44] fix(windows): remove UpdateBaseLayout deadcode
---
.../com/keyboards/keymankeyboardinstalled.pas | 17 -----------------
.../kmcomapi/util/internalinterfaces.pas | 19 +++++++++----------
2 files changed, 9 insertions(+), 27 deletions(-)
diff --git a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
index 01b03ae3fc..7077bc1291 100644
--- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
+++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas
@@ -110,7 +110,6 @@ type
{ IIntKeymanKeyboardInstalled }
function RegKeyboard: TRegKeyboard;
procedure ClearVisualKeyboard;
- procedure UpdateBaseLayout; // I4169
procedure RefreshInstallation;
{ IKeymanKeyboardInstalled2 }
@@ -155,22 +154,6 @@ begin
end;
end;
-procedure TKeymanKeyboardInstalled.UpdateBaseLayout; // I4169
-var
- BaseKeyboardID: Integer;
-begin
- if FRegKeyboard.MnemonicLayout and FileExists(FRegKeyboard.KeymanFile) then // I4615
- begin
- BaseKeyboardID := (Context.Options as IKeymanOptions).Items['koBaseLayout'].Value;
- with TKPRecompileMnemonicKeyboard.Create(Context) do
- try
- Execute(FRegKeyboard.KeymanFile, FRegKeyboard.PackageName, BaseKeyboardID);
- finally
- Free;
- end;
- end;
-end;
-
function TKeymanKeyboardInstalled.Get_Copyright: WideString;
begin
Result := FRegKeyboard.Copyright;
diff --git a/windows/src/engine/kmcomapi/util/internalinterfaces.pas b/windows/src/engine/kmcomapi/util/internalinterfaces.pas
index 1759468e00..edfc65bf28 100644
--- a/windows/src/engine/kmcomapi/util/internalinterfaces.pas
+++ b/windows/src/engine/kmcomapi/util/internalinterfaces.pas
@@ -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;
From f80986dedd78c10c182a9c6b7b14782e3222f0c0 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Mon, 14 Sep 2026 14:42:32 +1000
Subject: [PATCH 27/44] fix(windows): apply batched suggestions from code
review
Co-authored-by: Marc Durdin
---
...yman.Configuration.System.BaseKeyboard.pas | 32 ++++++++++---------
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 12 +++----
2 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index d91b439f33..926e766daa 100644
--- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -8,27 +8,29 @@ uses
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.
-*)
+ * 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
- 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.
-*)
+ * Sets the base keyboard KLID for the current user and compiles the keyboard
+ * 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 base keyboard files for the specified KLID.
- @param BaseKeyboardID KLID of the base keyboard to compile.
- @returns True when the compilation is successful.
-*)
+ * Compiles the base keyboard files 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
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index dd298a8642..1d06e7ca25 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -20,12 +20,12 @@ type
(**
- Form for the user to select a base keyboard. If the user selects a base
- keyboard, the KLID of the selected base keyboard is returned in
- BaseKeyboardID.
- @param [out] BaseKeyboardID KLID of the base keyboard selected by the user.
- @returns True if the user selected a base keyboard.
-*)
+ * Form for the user to select a base keyboard. If the user selects a base
+ * keyboard, the KLID of the selected base keyboard is returned in
+ * BaseKeyboardID.
+ * @param [out] BaseKeyboardID KLID of the base keyboard selected by the user.
+ * @returns True if the user selected a base keyboard.
+ *)
function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
implementation
From babd43ab29d39ea98d452cbe90303324c48f1702 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Tue, 15 Sep 2026 12:11:02 +1000
Subject: [PATCH 28/44] fix(windows): refactor ConfigureBaseKeyboard
Refactor ConfigureBaseKeyboard to ConfigureAndSetBaseKeyboard
call the SetBaseKeyboard from the form rather then from
the initprog module.
---
...yman.Configuration.System.BaseKeyboard.pas | 16 ++++++++++++---
.../desktop/kmshell/main/UfrmBaseKeyboard.pas | 20 ++++++++++---------
windows/src/desktop/kmshell/main/UfrmMain.pas | 3 +--
windows/src/desktop/kmshell/main/initprog.pas | 3 +--
4 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index 926e766daa..dedea86dae 100644
--- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -1,3 +1,13 @@
+(*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * Created by Ross Cruickshank on 2026-09-dd-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
@@ -16,8 +26,8 @@ function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
(**
* Sets the base keyboard KLID for the current user and compiles the keyboard
- * files if necessary. In the case the compiled keyboard files are not present,
- * it will require elevation.
+ * 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.
@@ -25,7 +35,7 @@ function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
(**
- * Compiles the base keyboard files for the specified KLID.
+ * Compiles the installed keyboard layouts for the specified KLID.
* Must run elevated.
*
* @param BaseKeyboardID KLID of the base keyboard to compile.
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index 1d06e7ca25..825088ad0a 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -18,15 +18,14 @@ type
procedure FireCommand(const command: WideString; params: TStringList); override;
end;
-
(**
- * Form for the user to select a base keyboard. If the user selects a base
- * keyboard, the KLID of the selected base keyboard is returned in
- * BaseKeyboardID.
- * @param [out] BaseKeyboardID KLID of the base keyboard selected by the user.
- * @returns True if the user selected a base keyboard.
+ * 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 ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
+function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;
+
implementation
@@ -37,16 +36,19 @@ uses
ErrorControlledRegistry,
RegistryKeys,
keymanapi_TLB,
+ Keyman.Configuration.System.BaseKeyboard,
kmint,
utilkmshell;
-function ConfigureBaseKeyboard(out BaseKeyboardID: Integer): Boolean;
+
+function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;
+var BaseKeyboardID: Integer;
begin
with TfrmBaseKeyboard.Create(nil) do
try
Result := ShowModal = mrOk;
if Result then
- BaseKeyboardID := FBaseKeyboardID;
+ SetBaseKeyboard(WindowHandle, FBaseKeyboardID)
finally
Free;
end;
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index 9c4478283e..472353102c 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -666,9 +666,8 @@ procedure TfrmMain.Options_BaseKeyboard; // I4169
var
BaseKeyboardID: Integer;
begin
- if ConfigureBaseKeyboard(BaseKeyboardID) then
+ if ConfigureAndSetBaseKeyboard(Handle) then
begin
- SetBaseKeyboard(Handle, BaseKeyboardID);
DoRefresh;
end;
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index 54f4a73f65..f2392d6913 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -402,7 +402,6 @@ var
kdl: IKeymanDefaultLanguage;
FIcon: string;
FMutex: TKeymanMutex; // I2720
- BaseKeyboardID: Integer;
function FirstKeyboardFileName: WideString;
begin
if KeyboardFileNames.Count = 0
@@ -550,7 +549,7 @@ begin
end;
fmBaseKeyboard: // I4169
- if ConfigureBaseKeyboard(BaseKeyboardID) and SetBaseKeyboard(0, BaseKeyboardID)
+ if ConfigureAndSetBaseKeyboard(0)
then ExitCode := 0
else ExitCode := 1;
From a522982dac07d563a0649114fb9c84219e99e65c Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Wed, 16 Sep 2026 05:52:48 +0200
Subject: [PATCH 29/44] fix(developer): address crashes in touch layout editor
Crashes reported in user testing on #16520.
* Fix crash selecting layer after deleting a layer (from #16509)
* Fix crash resetting 'next layer' for flicks after layer deletion
(long-standing issue)
See-also: #16509
See-also: #16520
Cherry-picked-from: 099d039952260e069f61954fc6b29b51b1036960
---
developer/src/tike/xml/layoutbuilder/builder.js | 10 +++++++++-
developer/src/tike/xml/layoutbuilder/layer-controls.js | 2 +-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/developer/src/tike/xml/layoutbuilder/builder.js b/developer/src/tike/xml/layoutbuilder/builder.js
index b81707fa3c..486bce81f9 100644
--- a/developer/src/tike/xml/layoutbuilder/builder.js
+++ b/developer/src/tike/xml/layoutbuilder/builder.js
@@ -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);
}
diff --git a/developer/src/tike/xml/layoutbuilder/layer-controls.js b/developer/src/tike/xml/layoutbuilder/layer-controls.js
index 765aa5c2bd..e1cf11ac34 100644
--- a/developer/src/tike/xml/layoutbuilder/layer-controls.js
+++ b/developer/src/tike/xml/layoutbuilder/layer-controls.js
@@ -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));
From b6f475a382fc763190c489c5f9cf35bf92284d0d Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 16 Sep 2026 17:37:59 +1000
Subject: [PATCH 30/44] fix(windows): add basekeyboard filename functions
---
common/windows/delphi/general/utilstr.pas | 44 +++++++++++++++----
.../kmshell/install/UpgradeMnemonicLayout.pas | 19 ++++----
...yman.Configuration.System.BaseKeyboard.pas | 13 +++---
.../keyboard/kprecompilemnemonickeyboard.pas | 5 ++-
4 files changed, 56 insertions(+), 25 deletions(-)
diff --git a/common/windows/delphi/general/utilstr.pas b/common/windows/delphi/general/utilstr.pas
index 01e085b8a8..b5bcd32e65 100644
--- a/common/windows/delphi/general/utilstr.pas
+++ b/common/windows/delphi/general/utilstr.pas
@@ -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,7 +58,25 @@ function GetTokenFromCaret(line: string; var selx, sellen: Integer): string;
function WideQuotedStr(const str: WideString): WideString; deprecated; // I3310
+(**
+ * Creates the compiled keyboard filename by inserting the base keyboard ID
+ * before the .kmx extension.
+ *
+ * @param BaseFileName Base keyboard filename, in the form keyboardname.kmx.
+ * @param BaseKeyboardIDHex Base keyboard ID in hexadecimal form.
+ * @return Compiled keyboard filename, in the form keyboardname-.kmx.
+ *)
+function InsertBKLIDFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+(**
+ * Creates the dead-key compiled keyboard filename by inserting the base
+ * keyboard ID and -d suffix before the .kmx extension.
+ *
+ * @param BaseFileName Base keyboard filename, in the form keyboardname.kmx.
+ * @param BaseKeyboardIDHex Base keyboard ID in hexadecimal form.
+ * @return Dead-key compiled keyboard filename, in the form keyboardname--d.kmx.
+ *)
+function InsertBKLIDDeadkeyFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
implementation
@@ -78,7 +96,7 @@ begin
Result := '';
Exit;
end;
-
+
if s[1] = '"' then
begin
Delete(s,1,1);
@@ -418,4 +436,14 @@ begin
Result.Bottom := StrToIntDef(s, 0);
end;
+function InsertBKLIDFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+begin
+ Result := ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx';
+end;
+
+function InsertBKLIDDeadkeyFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+begin
+ Result := ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx'
+end;
+
end.
diff --git a/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas b/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
index acad765590..2639ac2072 100644
--- a/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
+++ b/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
@@ -1,18 +1,18 @@
(*
Name: UpgradeMnemonicLayout
Copyright: Copyright (C) SIL International.
- Documentation:
- Description:
+ Documentation:
+ Description:
Create Date: 31 Dec 2014
Modified Date: 2 Jun 2015
Authors: mcdurdin
- Related Files:
- Dependencies:
+ Related Files:
+ Dependencies:
- Bugs:
- Todo:
- Notes:
+ Bugs:
+ Todo:
+ Notes:
History: 31 Dec 2014 - mcdurdin - I4553 - V9.0 - Upgrade to 476 or later requires recompile of all mnemonic layouts
06 Feb 2015 - mcdurdin - I4552 - V9.0 - Add mnemonic recompile option to ignore deadkeys
08 Apr 2015 - mcdurdin - I4651 - V9.0 - Mnemonic layout recompiler maps AltGr+VK_BKSLASH rather than VK_OEM_102
@@ -46,7 +46,8 @@ uses
kmint,
RegistryKeys,
utilexecute,
- utilkmshell;
+ utilkmshell,
+ utilstr;
const
{ CurrentMnemonicLayoutVersion = 476; // First 9.0 build with fixes for mnemonic layouts }
@@ -171,7 +172,7 @@ begin
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
FBaseFileName := Keyboard.Filename;
FDestFileName := OutputFileName;
- FDestDeadkeyFileName := ChangeFileExt(FDestFileName, '') + '-d.kmx'; // I4552
+ FDestDeadkeyFileName := InsertBKLIDDeadkeyFilename(FBaseFileName, FBaseKeyboardIDHex); // I4552
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
FDestPath := ExtractFileDir(Keyboard.Filename);
diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index dedea86dae..4c398f1fe8 100644
--- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -47,23 +47,24 @@ implementation
uses
kmint,
- utilkmshell;
+ utilkmshell,
+ utilstr;
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
var
I: Integer;
Keyboard: IKeymanKeyboardInstalled;
- BaseFileName: string;
+ KeyboardFileName: string;
BaseKeyboardIDHex: string;
begin
BaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
for I := 0 to kmcom.Keyboards.Count - 1 do
begin
Keyboard := kmcom.Keyboards.Items[I];
- BaseFileName := Keyboard.Filename;
- if FileExists(BaseFileName) and
- (not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx') or
- not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx')) then
+ KeyboardFileName := Keyboard.Filename;
+ if FileExists(KeyboardFileName) and
+ (not FileExists(InsertBKLIDFilename(KeyboardFileName, BaseKeyboardIDHex)) or
+ not FileExists(InsertBKLIDDeadkeyFilename(KeyboardFileName, BaseKeyboardIDHex))) then
Exit(True);
end;
Result := False;
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
index 3d806afda2..b05ab325d1 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
@@ -47,6 +47,7 @@ uses
RegistryKeys,
utilexecute,
utilkeyman,
+ utilstr,
utilsystem;
function GetKeyboardLayoutFileName(id: Integer): string;
@@ -83,8 +84,8 @@ begin
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
FBaseFileName := FDestPath + '\' + ExtractFileName(FileName); // I3581
- FDestFileName := ChangeFileExt(FBaseFileName, '') + '-'+FBaseKeyboardIDHex + '.kmx';
- FDestDeadkeyFileName := ChangeFileExt(FBaseFileName, '') + '-'+FBaseKeyboardIDHex + '-d.kmx'; // I4552
+ FDestFileName := InsertBKLIDFilename(FBaseFileName, FBaseKeyboardIDHex);
+ FDestDeadkeyFileName := InsertBKLIDDeadkeyFilename(FBaseFileName, FBaseKeyboardIDHex); // I4552
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
{ Recompile with the traditional deadkey behaviour }
From eec0b12c449b56402991747d82d590fbb049a27f Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Thu, 17 Sep 2026 10:38:33 +1000
Subject: [PATCH 31/44] fix(windows): rename argument that was misleading
---
common/windows/delphi/general/utilstr.pas | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/common/windows/delphi/general/utilstr.pas b/common/windows/delphi/general/utilstr.pas
index b5bcd32e65..59a897ad2e 100644
--- a/common/windows/delphi/general/utilstr.pas
+++ b/common/windows/delphi/general/utilstr.pas
@@ -62,21 +62,21 @@ function WideQuotedStr(const str: WideString): WideString; deprecated; // I3310
* Creates the compiled keyboard filename by inserting the base keyboard ID
* before the .kmx extension.
*
- * @param BaseFileName Base keyboard filename, in the form keyboardname.kmx.
+ * @param KeyboardFileName Keyboard filename, in the form keyboardname.kmx.
* @param BaseKeyboardIDHex Base keyboard ID in hexadecimal form.
* @return Compiled keyboard filename, in the form keyboardname-.kmx.
*)
-function InsertBKLIDFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+function InsertBKLIDFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
(**
* Creates the dead-key compiled keyboard filename by inserting the base
* keyboard ID and -d suffix before the .kmx extension.
*
- * @param BaseFileName Base keyboard filename, in the form keyboardname.kmx.
+ * @param KeyboardFileName Keyboard filename, in the form keyboardname.kmx.
* @param BaseKeyboardIDHex Base keyboard ID in hexadecimal form.
* @return Dead-key compiled keyboard filename, in the form keyboardname--d.kmx.
*)
-function InsertBKLIDDeadkeyFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+function InsertBKLIDDeadkeyFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
implementation
@@ -436,14 +436,14 @@ begin
Result.Bottom := StrToIntDef(s, 0);
end;
-function InsertBKLIDFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+function InsertBKLIDFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
begin
- Result := ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx';
+ Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '.kmx';
end;
-function InsertBKLIDDeadkeyFilename(const BaseFileName: string; BaseKeyboardIDHex: string): string;
+function InsertBKLIDDeadkeyFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
begin
- Result := ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx'
+ Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx'
end;
end.
From 013def7d9c917e767c4304b94f613996280205c6 Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Thu, 17 Sep 2026 16:30:50 -0500
Subject: [PATCH 32/44] change(web): add lexical weighting to prediction search
Fixes: #16577
From 14.0 up until now, our correction-search focused on the minimum "correction distance" to a possible text _correction_, then performed prediction _on top of that_. This PR changes the search to integrate word-frequency weighting from models within the search, allowing us to optimize for the most likely predictions. There is no additional performace cost impact of note here - the probability associated with a correction's best prediction is already accessible O(1) with operations we've already been doing.
It is likely that this will help further mitigate issues with transposition handling noted in the user tests for #16450.
Build-bot: skip release:web,android,ios
---
.../correction/correction-result-mapping.ts | 10 +-
.../main/correction/correction-searchable.ts | 6 +-
.../src/main/correction/distance-modeler.ts | 20 ++-
.../main/correction/legacy-quotient-root.ts | 4 +-
.../main/correction/legacy-quotient-spur.ts | 4 +-
.../correction/search-quotient-cluster.ts | 8 +-
.../main/correction/search-quotient-spur.ts | 8 +-
.../main/correction/token-result-mapping.ts | 12 +-
.../worker-thread/src/main/predict-helpers.ts | 33 +++--
.../distance-modeler.tests.ts | 83 ++++++-------
.../correction-search/getBestMatches.tests.ts | 114 +++++-------------
.../legacy-quotient-spur.tests.ts | 4 +-
.../quotient-node-finalizer.tests.ts | 8 +-
.../build-and-map-predictions.tests.ts | 8 +-
14 files changed, 142 insertions(+), 180 deletions(-)
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-result-mapping.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-result-mapping.ts
index 67e8fa62b6..d1903b6caf 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-result-mapping.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-result-mapping.ts
@@ -27,10 +27,16 @@ export interface CorrectionResultMapping {
readonly matchedResult: Readonly;
/**
- * 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;
}
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-searchable.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-searchable.ts
index 74ca206e1e..648734c3ef 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-searchable.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/correction-searchable.ts
@@ -29,10 +29,14 @@ type CompleteSearchPath = {
export type PathResult = NullPath | IntermediateSearchPath | CompleteSearchPath;
-export function CORRECTION_QUEUE_COMPARATOR(a: T, b: T) {
+export function PREDICTION_QUEUE_COMPARATOR(a: T, b: T) {
return a.currentCost - b.currentCost;
}
+export function CORRECTION_QUEUE_COMPARATOR(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
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts
index b85f6b98c0..d3ffc2e0a4 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts
@@ -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(CORRECTION_QUEUE_COMPARATOR);
+ let spaceQueue = new PriorityQueue(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((a, b) => a.totalCost - b.totalCost);
+ const priorResultsQueue = new PriorityQueue(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;
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts
index 7b381890be..dc73a02be8 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-root.ts
@@ -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 = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
+ private selectionQueue: PriorityQueue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR);
private processed: SearchNode[] = [];
constructor(model: LexicalModel) {
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-spur.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-spur.ts
index a1134ddd3e..99e97b7f84 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-spur.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/legacy-quotient-spur.ts
@@ -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 = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
+ private transposeQueue: PriorityQueue = new PriorityQueue(PREDICTION_QUEUE_COMPARATOR);
private incomingTransposeRootNodes: TokenResultMapping[] = [];
public readonly insertLength: number;
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts
index 4a5ee0723f..ba625b5d81 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-cluster.ts
@@ -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 = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
+ private selectionQueue: PriorityQueue = 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(CORRECTION_QUEUE_COMPARATOR, entries.slice());
+ this.selectionQueue = new PriorityQueue(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') {
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts
index dbc0742287..78fa68eb6e 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts
@@ -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 = new PriorityQueue(CORRECTION_QUEUE_COMPARATOR);
+ private selectionQueue: PriorityQueue = 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(CORRECTION_QUEUE_COMPARATOR, entries);
+ this.selectionQueue = new PriorityQueue(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;
}
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts
index 37e9d1d00b..d37f02ce37 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/token-result-mapping.ts
@@ -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 {
* 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;
}
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts b/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts
index 2ba308fdea..dd1146793c 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/predict-helpers.ts
@@ -532,7 +532,7 @@ export function buildAndMapPredictions(
transition: ContextTransition,
tokenization: ContextTokenization,
// Originally, Readonly - 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> = {};
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;
}
}
diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts
index dbd9f54a1d..97260c24ee 100644
--- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts
+++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts
@@ -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;
diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/getBestMatches.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/getBestMatches.tests.ts
index 4bccef2ce9..dcc3cf3f0c 100644
--- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/getBestMatches.tests.ts
+++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/getBestMatches.tests.ts
@@ -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, any, any>) => {
- const firstIterResult = await iter.next(); // {value: , done: }
- assert.isFalse(firstIterResult.done);
-
- const firstResult: TokenResultMapping = firstIterResult.value; // Retrieves
- // 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);
});
});
});
diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/legacy-quotient-spur.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/legacy-quotient-spur.tests.ts
index ccc38467a5..74b2af3e43 100644
--- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/legacy-quotient-spur.tests.ts
+++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/legacy-quotient-spur.tests.ts
@@ -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);
});
});
});
\ No newline at end of file
diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/quotient-node-finalizer.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/quotient-node-finalizer.tests.ts
index ee6ee088e6..1a89a68321 100644
--- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/quotient-node-finalizer.tests.ts
+++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/quotient-node-finalizer.tests.ts
@@ -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;
}
diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/prediction-helpers/build-and-map-predictions.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/prediction-helpers/build-and-map-predictions.tests.ts
index 5051c2acbf..72876e4ca1 100644
--- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/prediction-helpers/build-and-map-predictions.tests.ts
+++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/prediction-helpers/build-and-map-predictions.tests.ts
@@ -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
);
From c940caeb711d2524a50717d251b54631d7023d8d Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 18 Sep 2026 16:58:58 +1000
Subject: [PATCH 33/44] fix(windows): fix date in title block
---
.../kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index 4c398f1fe8..a1fecdd608 100644
--- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -1,7 +1,7 @@
(*
* Keyman is copyright (C) SIL Global. MIT License.
*
- * Created by Ross Cruickshank on 2026-09-dd-12
+ * Created by Ross Cruickshank on 2026-09-12
*
*
* This unit assists in setting the base keyboard configuration,
From 587d74dee4841ec36ef085e39fb4f33fdbf3f025 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 18 Sep 2026 20:48:56 +1000
Subject: [PATCH 34/44] fix(windows): apply batched suggestions from code
review
Co-authored-by: Marc Durdin
---
common/windows/delphi/general/utilstr.pas | 22 +++++++++----------
windows/src/desktop/kmshell/main/initprog.pas | 1 +
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/common/windows/delphi/general/utilstr.pas b/common/windows/delphi/general/utilstr.pas
index 59a897ad2e..8319c37d04 100644
--- a/common/windows/delphi/general/utilstr.pas
+++ b/common/windows/delphi/general/utilstr.pas
@@ -62,21 +62,21 @@ function WideQuotedStr(const str: WideString): WideString; deprecated; // I3310
* Creates the compiled keyboard filename by inserting the base keyboard ID
* before the .kmx extension.
*
- * @param KeyboardFileName Keyboard filename, in the form keyboardname.kmx.
- * @param BaseKeyboardIDHex Base keyboard ID in hexadecimal form.
- * @return Compiled keyboard filename, in the form keyboardname-.kmx.
+ * @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-.kmx'
*)
-function InsertBKLIDFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
+function GetKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
(**
* Creates 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 keyboardname.kmx.
- * @param BaseKeyboardIDHex Base keyboard ID in hexadecimal form.
- * @return Dead-key compiled keyboard filename, in the form keyboardname--d.kmx.
+ * @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--d.kmx'
*)
-function InsertBKLIDDeadkeyFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
+function GetKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
implementation
@@ -436,14 +436,14 @@ begin
Result.Bottom := StrToIntDef(s, 0);
end;
-function InsertBKLIDFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
+function GetKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
begin
Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '.kmx';
end;
-function InsertBKLIDDeadkeyFilename(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
+function GetKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
begin
- Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx'
+ Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx';
end;
end.
diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas
index f2392d6913..3010dae5cc 100644
--- a/windows/src/desktop/kmshell/main/initprog.pas
+++ b/windows/src/desktop/kmshell/main/initprog.pas
@@ -266,6 +266,7 @@ begin
else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169
else if s = '-mcompilekbds' then
begin
+ // Requires elevated context
FMode := fmMCompileKbds;
Inc(i);
if i > ParamCount then Exit;
From bb20b890347a624e1e80a3afd3afc98858d3f8d2 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:16:54 +1000
Subject: [PATCH 35/44] fix(windows): rename filename builder functions
---
.../windows/delphi/general/utilfiletypes.pas | 31 +++++++++++++++++++
common/windows/delphi/general/utilstr.pas | 30 ------------------
.../kmshell/install/UpgradeMnemonicLayout.pas | 4 +--
...yman.Configuration.System.BaseKeyboard.pas | 6 ++--
.../keyboard/kprecompilemnemonickeyboard.pas | 6 ++--
5 files changed, 39 insertions(+), 38 deletions(-)
diff --git a/common/windows/delphi/general/utilfiletypes.pas b/common/windows/delphi/general/utilfiletypes.pas
index f34be0933d..28faa8804c 100644
--- a/common/windows/delphi/general/utilfiletypes.pas
+++ b/common/windows/delphi/general/utilfiletypes.pas
@@ -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-.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--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.
diff --git a/common/windows/delphi/general/utilstr.pas b/common/windows/delphi/general/utilstr.pas
index 8319c37d04..4cd175e360 100644
--- a/common/windows/delphi/general/utilstr.pas
+++ b/common/windows/delphi/general/utilstr.pas
@@ -58,26 +58,6 @@ function GetTokenFromCaret(line: string; var selx, sellen: Integer): string;
function WideQuotedStr(const str: WideString): WideString; deprecated; // I3310
-(**
- * Creates 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-.kmx'
- *)
-function GetKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
-
-(**
- * Creates 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--d.kmx'
- *)
-function GetKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
-
implementation
uses
@@ -436,14 +416,4 @@ begin
Result.Bottom := StrToIntDef(s, 0);
end;
-function GetKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
-begin
- Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '.kmx';
-end;
-
-function GetKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;
-begin
- Result := ChangeFileExt(KeyboardFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx';
-end;
-
end.
diff --git a/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas b/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
index 2639ac2072..62b75bf566 100644
--- a/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
+++ b/windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
@@ -47,7 +47,7 @@ uses
RegistryKeys,
utilexecute,
utilkmshell,
- utilstr;
+ utilfiletypes;
const
{ CurrentMnemonicLayoutVersion = 476; // First 9.0 build with fixes for mnemonic layouts }
@@ -172,7 +172,7 @@ begin
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
FBaseFileName := Keyboard.Filename;
FDestFileName := OutputFileName;
- FDestDeadkeyFileName := InsertBKLIDDeadkeyFilename(FBaseFileName, FBaseKeyboardIDHex); // I4552
+ FDestDeadkeyFileName := BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(FBaseFileName, FBaseKeyboardIDHex); // I4552
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
FDestPath := ExtractFileDir(Keyboard.Filename);
diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
index a1fecdd608..c4fb428b49 100644
--- a/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.System.BaseKeyboard.pas
@@ -48,7 +48,7 @@ implementation
uses
kmint,
utilkmshell,
- utilstr;
+ utilfiletypes;
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
var
@@ -63,8 +63,8 @@ begin
Keyboard := kmcom.Keyboards.Items[I];
KeyboardFileName := Keyboard.Filename;
if FileExists(KeyboardFileName) and
- (not FileExists(InsertBKLIDFilename(KeyboardFileName, BaseKeyboardIDHex)) or
- not FileExists(InsertBKLIDDeadkeyFilename(KeyboardFileName, BaseKeyboardIDHex))) then
+ (not FileExists(BuildKeyboardFilenameWithBaseKeyboardID(KeyboardFileName, BaseKeyboardIDHex)) or
+ not FileExists(BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(KeyboardFileName, BaseKeyboardIDHex))) then
Exit(True);
end;
Result := False;
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
index b05ab325d1..52139f5839 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas
@@ -47,7 +47,7 @@ uses
RegistryKeys,
utilexecute,
utilkeyman,
- utilstr,
+ utilfiletypes,
utilsystem;
function GetKeyboardLayoutFileName(id: Integer): string;
@@ -84,8 +84,8 @@ begin
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
FBaseFileName := FDestPath + '\' + ExtractFileName(FileName); // I3581
- FDestFileName := InsertBKLIDFilename(FBaseFileName, FBaseKeyboardIDHex);
- FDestDeadkeyFileName := InsertBKLIDDeadkeyFilename(FBaseFileName, FBaseKeyboardIDHex); // I4552
+ FDestFileName := BuildKeyboardFilenameWithBaseKeyboardID(FBaseFileName, FBaseKeyboardIDHex);
+ FDestDeadkeyFileName := BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(FBaseFileName, FBaseKeyboardIDHex); // I4552
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
{ Recompile with the traditional deadkey behaviour }
From 02274223fdd43c4ca62ad28c1424abc4a383f5ff Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Fri, 18 Sep 2026 09:14:59 -0500
Subject: [PATCH 36/44] fix(android): suppress autocorrection of hardware
keystroke input
Fixes: #16602
Build-bot: skip release:web,android
---
web/src/app/webview/src/passthroughKeyboard.ts | 2 ++
web/src/engine/src/keyboard/keyEvent.ts | 5 +++++
web/src/engine/src/main/headless/inputProcessor.ts | 3 +++
3 files changed, 10 insertions(+)
diff --git a/web/src/app/webview/src/passthroughKeyboard.ts b/web/src/app/webview/src/passthroughKeyboard.ts
index 293be9883b..f20d80dbb7 100644
--- a/web/src/app/webview/src/passthroughKeyboard.ts
+++ b/web/src/app/webview/src/passthroughKeyboard.ts
@@ -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');
diff --git a/web/src/engine/src/keyboard/keyEvent.ts b/web/src/engine/src/keyboard/keyEvent.ts
index 149b169bbe..0561d72225 100644
--- a/web/src/engine/src/keyboard/keyEvent.ts
+++ b/web/src/engine/src/keyboard/keyEvent.ts
@@ -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
diff --git a/web/src/engine/src/main/headless/inputProcessor.ts b/web/src/engine/src/main/headless/inputProcessor.ts
index 5018e4de02..b2f4531a5b 100644
--- a/web/src/engine/src/main/headless/inputProcessor.ts
+++ b/web/src/engine/src/main/headless/inputProcessor.ts
@@ -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.)
From d11ad55f10f203899229ca1e17bd2329f909e99d Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Mon, 21 Sep 2026 03:06:55 -0500
Subject: [PATCH 37/44] auto: cherry-pick 18.0.250 history to alpha
Build-bot: skip
Test-bot: skip
---
HISTORY.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/HISTORY.md b/HISTORY.md
index 02229c53a6..cb413d7a18 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1735,6 +1735,18 @@
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
* chore: increment to alpha 19.0 (#13187)
+## 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)
From 7551ed92a4d89508cc53961d2cd269230de091a8 Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Mon, 21 Sep 2026 04:45:36 -0500
Subject: [PATCH 38/44] auto: cherry-pick 18.0.251 history to alpha
Build-bot: skip
Test-bot: skip
---
HISTORY.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/HISTORY.md b/HISTORY.md
index 02229c53a6..7d55598549 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1735,6 +1735,10 @@
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
* chore: increment to alpha 19.0 (#13187)
+## 18.0.251 stable 2026-09-21
+
+* maint(android): update Android Target API to 36 (#16620)
+
## 18.0.249 stable 2026-03-27
* chore(linux): Update debian changelog (#15717)
From b1693b2916ec3055efe279a4c3ada125c9b784cb Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Mon, 21 Sep 2026 13:01:28 -0500
Subject: [PATCH 39/44] auto: increment master version to 19.0.290
Test-bot: skip
Build-bot: skip
---
HISTORY.md | 5 +++++
VERSION.md | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/HISTORY.md b/HISTORY.md
index a4be3e7a9a..c7ae1073c2 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,10 @@
# Keyman Version History
+## 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)
diff --git a/VERSION.md b/VERSION.md
index 4ae56c84c6..64e7fc2538 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-19.0.289
\ No newline at end of file
+19.0.290
\ No newline at end of file
From ed52fa0daa41f41799ded8477c87452142fb2616 Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Tue, 22 Sep 2026 03:07:50 -0500
Subject: [PATCH 40/44] auto: cherry-pick 18.0.252 history to alpha
Build-bot: skip
Test-bot: skip
---
HISTORY.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/HISTORY.md b/HISTORY.md
index a4be3e7a9a..11bbafcead 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1739,6 +1739,11 @@
* 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)
From 71ccb1ab6fc102bc502b2e577cab76e4a6adf208 Mon Sep 17 00:00:00 2001
From: rc-swag <58423624+rc-swag@users.noreply.github.com>
Date: Wed, 23 Sep 2026 11:41:45 +1000
Subject: [PATCH 41/44] fix(windows): merge resloution fix unused var
This fixes a merge resolution from master to the
branch. It also removes unused variables, from
a previous change.
---
.../src/desktop/kmshell/main/UfrmBaseKeyboard.pas | 1 -
windows/src/desktop/kmshell/main/UfrmMain.pas | 2 --
.../kmcomapi/processes/package/kpinstallpackage.pas | 13 +++++++++++--
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
index 825088ad0a..7d8467730c 100644
--- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
+++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
@@ -42,7 +42,6 @@ uses
function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;
-var BaseKeyboardID: Integer;
begin
with TfrmBaseKeyboard.Create(nil) do
try
diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas
index 46387131d2..b6ccccddd6 100644
--- a/windows/src/desktop/kmshell/main/UfrmMain.pas
+++ b/windows/src/desktop/kmshell/main/UfrmMain.pas
@@ -666,8 +666,6 @@ end;
------------------------------------------------------------------------------}
procedure TfrmMain.Options_BaseKeyboard; // I4169
-var
- BaseKeyboardID: Integer;
begin
if ConfigureAndSetBaseKeyboard(Handle) then
begin
diff --git a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
index 023b9ee6ec..51756a2152 100644
--- a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
+++ b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas
@@ -163,9 +163,18 @@ begin
inf := nil;
FZip := TZipFile.Create;
try
- FZip.Open(FileName, TZipMode.zmRead);
+ try
+ FZip.Open(FileName, TZipMode.zmRead);
+ except
+ on E:EZipException do
+ begin
+ ErrorFmt(KMN_E_Install_InvalidFile, VarArrayOf([ExtractFileName(FileName), E.Message, 0]));
+ raise;
+ end;
+ end;
- InfFile := '';
+ try
+ InfFile := '';
for i := 0 to FZip.FileCount - 1 do
begin
From 3c90c732e1b992b7976104f15bb51a2cc3f684af Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Wed, 23 Sep 2026 11:25:13 +0200
Subject: [PATCH 42/44] chore(android): remove unused map-polyfill.js and
es6-shim.min.js polyfills
These files were copied in but never referenced in 19.0 source.
Fixes: #16583
Test-bot: skip
---
android/.gitignore | 2 --
.../java/com/keyman/engine/KMManager.java | 2 --
android/KMEA/build.sh | 4 ---
package-lock.json | 23 ------------
web/src/app/webview/build.sh | 2 --
web/src/app/webview/map-polyfill-bundler.js | 35 -------------------
web/src/app/webview/src/polyfill/map.ts | 11 ------
.../predictive-text/worker-main/package.json | 3 +-
.../worker-thread/package.json | 1 -
.../src/keyboard/keyboards/activeLayout.ts | 5 ++-
.../manual/embed/android-harness/build.sh | 1 -
11 files changed, 3 insertions(+), 86 deletions(-)
delete mode 100644 web/src/app/webview/map-polyfill-bundler.js
delete mode 100644 web/src/app/webview/src/polyfill/map.ts
diff --git a/android/.gitignore b/android/.gitignore
index e822a25c2b..7c298cad5c 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -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
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
index 80701ad683..b0e8661b95 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
@@ -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";
diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh
index 66471c0bbb..14dc821f19 100755
--- a/android/KMEA/build.sh
+++ b/android/KMEA/build.sh
@@ -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
diff --git a/package-lock.json b/package-lock.json
index 88b8c7f08b..e473b783b9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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"
},
diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh
index cce218ba6d..dceaf19715 100755
--- a/web/src/app/webview/build.sh
+++ b/web/src/app/webview/build.sh
@@ -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
}
diff --git a/web/src/app/webview/map-polyfill-bundler.js b/web/src/app/webview/map-polyfill-bundler.js
deleted file mode 100644
index 257f897e70..0000000000
--- a/web/src/app/webview/map-polyfill-bundler.js
+++ /dev/null
@@ -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/'
-});
\ No newline at end of file
diff --git a/web/src/app/webview/src/polyfill/map.ts b/web/src/app/webview/src/polyfill/map.ts
deleted file mode 100644
index a9299df769..0000000000
--- a/web/src/app/webview/src/polyfill/map.ts
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/package.json b/web/src/engine/predictive-text/worker-main/package.json
index dc6301e113..1ae13a4011 100644
--- a/web/src/engine/predictive-text/worker-main/package.json
+++ b/web/src/engine/predictive-text/worker-main/package.json
@@ -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"
}
}
diff --git a/web/src/engine/predictive-text/worker-thread/package.json b/web/src/engine/predictive-text/worker-thread/package.json
index 7263f20b7c..af87bce811 100644
--- a/web/src/engine/predictive-text/worker-thread/package.json
+++ b/web/src/engine/predictive-text/worker-thread/package.json
@@ -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"
}
diff --git a/web/src/engine/src/keyboard/keyboards/activeLayout.ts b/web/src/engine/src/keyboard/keyboards/activeLayout.ts
index 0aad7376fd..8952ba8ebc 100644
--- a/web/src/engine/src/keyboard/keyboards/activeLayout.ts
+++ b/web/src/engine/src/keyboard/keyboards/activeLayout.ts
@@ -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];
diff --git a/web/src/test/manual/embed/android-harness/build.sh b/web/src/test/manual/embed/android-harness/build.sh
index 81d141bc23..e67d359e7a 100755
--- a/web/src/test/manual/embed/android-harness/build.sh
+++ b/web/src/test/manual/embed/android-harness/build.sh
@@ -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
From 3892883b54d6256ebb643435bd185fe40b211be2 Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Wed, 23 Sep 2026 13:01:50 -0500
Subject: [PATCH 43/44] auto: increment master version to 19.0.291
Test-bot: skip
Build-bot: skip
---
HISTORY.md | 5 +++++
VERSION.md | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/HISTORY.md b/HISTORY.md
index 478b350330..3d8156b69b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,10 @@
# 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)
diff --git a/VERSION.md b/VERSION.md
index 64e7fc2538..cdb78fdb4f 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-19.0.290
\ No newline at end of file
+19.0.291
\ No newline at end of file
From 05be47793c75092150710bad35fc53e2854b2450 Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Wed, 23 Sep 2026 21:38:28 +0200
Subject: [PATCH 44/44] chore(common): fix TC reporter reporting failures
The TC reporter failed reporting failures (see e.g.
https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/646398?logView=flowAware&showLog=646398_11692__expandAll)
Turns out we double escaped some strings, and there were a spurious bracket
and single quote in the service message.
Build-bot: skip build:web
Test-bot: skip
---
common/test/resources/test-runner-TC-reporter.mjs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/common/test/resources/test-runner-TC-reporter.mjs b/common/test/resources/test-runner-TC-reporter.mjs
index 33198592af..054f6b2aa4 100644
--- a/common/test/resources/test-runner-TC-reporter.mjs
+++ b/common/test/resources/test-runner-TC-reporter.mjs
@@ -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)}']`);