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 1/5] 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 6a631fb5461..a469db480c3 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -661,6 +661,8 @@ procedure TfrmMain.cefBeforeBrowseSync(Sender: TObject; const Url: string; 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 2/5] 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 40a65e31ce1..b3f60a5067b 100644 --- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas +++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas @@ -11,13 +11,16 @@ interface 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 @@ procedure TfrmBaseKeyboard.Footer_OK(params: TStringList); 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 a469db480c3..a556019abd2 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -197,6 +197,7 @@ implementation Keyman.Configuration.UI.UfrmStartInstall, RegistryKeys, SupportXMLRenderer, + UfrmBaseKeyboard, UfrmChangeHotkey, UfrmHTML, UfrmInstallKeyboardFromWeb, @@ -660,11 +661,15 @@ procedure TfrmMain.cefBeforeBrowseSync(Sender: TObject; const Url: string; ------------------------------------------------------------------------------} 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 359130f27e7..8ea4331995f 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -90,6 +90,7 @@ function Main(Owner: TComponent = nil): TModalResult; fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 fmBaseKeyboard, // I4169 + fmMCompile, fmUpgradeMnemonicLayout, // I4553 fmRepair, fmKeepInTouch, @@ -262,6 +263,13 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, 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 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF kdl: IKeymanDefaultLanguage; FIcon: string; FMutex: TKeymanMutex; // I2720 + BaseKeyboardID: Integer; function FirstKeyboardFileName: WideString; begin if KeyboardFileNames.Count = 0 @@ -540,7 +549,12 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF 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 051d399b6ec..91ecaa9b5f8 100644 --- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas +++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas @@ -109,7 +109,7 @@ TKeymanKeyboardInstalled = class( // I3581 { IIntKeymanKeyboardInstalled } function RegKeyboard: TRegKeyboard; procedure ClearVisualKeyboard; - procedure UpdateBaseLayout; // I4169 + procedure UpdateBaseLayout(BaseKeyboardID: Integer); // I4169 procedure RefreshInstallation; public @@ -151,12 +151,12 @@ procedure TKeymanKeyboardInstalled.Uninstall; 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 7bea91450bc..8635a748083 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 @@ implementation ErrorControlledRegistry, RegistryKeys, Glossary, + isadmin, Keyman.System.BaseKeyboard, KeymanOptionNames, keymanerrorcodes; @@ -112,7 +113,9 @@ function TKeymanOptions.IndexOf(const ID: WideString): Integer; procedure TKeymanOptions.Apply; var - I, FOldBaseLayout: Integer; + I: Integer; + FOldBaseLayout: Integer; + FNewBaseLayout: Integer; begin with TRegistryErrorControlled.Create do // I3717 try @@ -130,9 +133,10 @@ procedure TKeymanOptions.Apply; 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 26c92faa90a..1df237b0c60 100644 --- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas +++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas @@ -125,6 +125,7 @@ procedure TKPInstallKeyboard.Execute(const FileName, PackageID: string; FInstall FExitCode: Integer; FKVKName: WideString; FCreatedIcon: Boolean; + BaseKeyboardID: Integer; begin KL.MethodEnter(Self, 'Execute', [FileName,PackageID,ikPartOfPackage in FInstallOptions ,Force]); try @@ -248,9 +249,11 @@ procedure TKPInstallKeyboard.Execute(const FileName, PackageID: string; FInstall // 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 30d0c275cc2..7a4d5a2b395 100644 --- a/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas +++ b/windows/src/engine/kmcomapi/processes/keyboard/kprecompilemnemonickeyboard.pas @@ -27,7 +27,7 @@ interface 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 @@ implementation Winapi.Windows, errorcontrolledregistry, - keymancontext, keymanerrorcodes, KeymanPaths, - keymanapi_TLB, RegistryKeys, utilexecute, utilkeyman, @@ -67,7 +65,7 @@ function GetKeyboardLayoutFileName(id: Integer): string; 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 @@ procedure TKPRecompileMnemonicKeyboard.Execute(const FileName,PackageName: strin FExitCode: Integer; FMCompilePath: string; FBaseKeyboardFileName: string; - BaseKeyboardID: Cardinal; FDestDeadkeyFileName: string; FCommand: string; begin @@ -84,9 +81,6 @@ procedure TKPRecompileMnemonicKeyboard.Execute(const FileName,PackageName: strin 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 1759468e006..11093ac89a1 100644 --- a/windows/src/engine/kmcomapi/util/internalinterfaces.pas +++ b/windows/src/engine/kmcomapi/util/internalinterfaces.pas @@ -67,7 +67,7 @@ interface ['{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 3/5] 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 91ecaa9b5f8..52fd6984354 100644 --- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas +++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas @@ -109,7 +109,7 @@ TKeymanKeyboardInstalled = class( // I3581 { IIntKeymanKeyboardInstalled } function RegKeyboard: TRegKeyboard; procedure ClearVisualKeyboard; - procedure UpdateBaseLayout(BaseKeyboardID: Integer); // I4169 + procedure UpdateBaseLayout; // I4169 procedure RefreshInstallation; public @@ -151,15 +151,20 @@ procedure TKeymanKeyboardInstalled.Uninstall; 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 8635a748083..a60c5da0416 100644 --- a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas +++ b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas @@ -136,7 +136,7 @@ procedure TKeymanOptions.Apply; 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 7a4d5a2b395..3d806afda2e 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 11093ac89a1..1759468e006 100644 --- a/windows/src/engine/kmcomapi/util/internalinterfaces.pas +++ b/windows/src/engine/kmcomapi/util/internalinterfaces.pas @@ -67,7 +67,7 @@ interface ['{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 4/5] 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 a556019abd2..4ee970cca57 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -666,8 +666,8 @@ procedure TfrmMain.Options_BaseKeyboard; // I4169 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 5/5] 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 b3f60a5067b..e9ce67d5e34 100644 --- a/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas +++ b/windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas @@ -21,6 +21,7 @@ TfrmBaseKeyboard = class(TfrmWebContainer) 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 @@ procedure TfrmBaseKeyboard.Footer_OK(params: TStringList); 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 @@ function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolea 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 3444e7ce538..f9b5f9ac9b8 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -90,7 +90,7 @@ function Main(Owner: TComponent = nil): TModalResult; fmKeyboardWelcome, // I2569 fmKeyboardPrint, // I2329 fmBaseKeyboard, // I4169 - fmMCompile, + fmMCompileKbds, fmUpgradeMnemonicLayout, // I4553 fmRepair, fmKeepInTouch, @@ -263,9 +263,9 @@ function Init(var FMode: TKMShellMode; KeyboardFileNames: TStrings; var FSilent, 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 @@ procedure RunKMCOM(FMode: TKMShellMode; KeyboardFileNames: TStrings; FSilent, FF 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 52fd6984354..01b03ae3fc8 100644 --- a/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas +++ b/windows/src/engine/kmcomapi/com/keyboards/keymankeyboardinstalled.pas @@ -63,7 +63,8 @@ TKeymanKeyboardInstalled = class; TKeymanKeyboardInstalled = class( // I3581 TKeymanKeyboard, IIntKeymanKeyboardInstalled, - IKeymanKeyboardInstalled) + IKeymanKeyboardInstalled, + IKeymanKeyboardInstalled2) private FRegKeyboard: TRegKeyboard; FVisualKeyboard: IKeymanVisualKeyboard; @@ -112,6 +113,9 @@ TKeymanKeyboardInstalled = class( // I3581 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 @@ function TKeymanKeyboardInstalled.RegKeyboard: TRegKeyboard; 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 a60c5da0416..1848428033c 100644 --- a/windows/src/engine/kmcomapi/com/options/keymanoptions.pas +++ b/windows/src/engine/kmcomapi/com/options/keymanoptions.pas @@ -112,32 +112,8 @@ function TKeymanOptions.IndexOf(const ID: WideString): Integer; 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 3ceb5b15cd7..786ce6c9b2d 100644 --- a/windows/src/engine/kmcomapi/keymanapi_TLB.pas +++ b/windows/src/engine/kmcomapi/keymanapi_TLB.pas @@ -1569,6 +1569,16 @@ interface 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 ac20310a871..9eb63fbd68d 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),