Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
65335ec
fix(windows): initial commit
rc-swag Jul 24, 2026
92643e3
fix(windows): handle basekeyboard change non-admin user
rc-swag Jul 29, 2026
2f4c428
fix(windows): restore UpdateBaseLayout interface
rc-swag Jul 29, 2026
27970c6
fix(windows): refresh UI after basekbd change
rc-swag Aug 3, 2026
9108af2
chore(windows): Merge branch 'master' into fix/windows/15152/basekeyb…
rc-swag Aug 18, 2026
62dd0de
fix(windows): add compile for base keyoard to api
rc-swag Aug 21, 2026
82fb400
fix(windows): pass basekeyboardid to installation api
rc-swag Aug 31, 2026
155b8e1
fix(windows): strtoint for command line basekeyboardid
rc-swag Sep 1, 2026
0c68b1e
fix(windows): typos and return type in interface declaration
rc-swag Sep 2, 2026
3c1b47e
fix(windows): Free the correct object
rc-swag Sep 2, 2026
8e77a57
fix(windows): CompileForBaseKeyboard doesn't return
rc-swag Sep 2, 2026
314dea4
fix(windows): move non ui function from ufrmbasekeyboard
rc-swag Sep 3, 2026
c316094
chore(windows): Revert "fix(windows): typos and return type in interf…
rc-swag Sep 3, 2026
5e89946
chore(windows): Revert "fix(windows): strtoint for command line basek…
rc-swag Sep 3, 2026
4892366
chore(windows): Revert "fix(windows): pass basekeyboardid to installa…
rc-swag Sep 3, 2026
b9743d2
fix(windows): fix indentation
rc-swag Sep 3, 2026
d67c491
fix(windows): rename Setting System
rc-swag Sep 4, 2026
0409cda
fix(windows): add comments for new functions
rc-swag Sep 7, 2026
163806b
fix(windows): git revert error fix
rc-swag Sep 7, 2026
63d12b6
fix(windows): remove UpdateBaseLayout deadcode
rc-swag Sep 7, 2026
f80986d
fix(windows): apply batched suggestions from code review
rc-swag Sep 14, 2026
babd43a
fix(windows): refactor ConfigureBaseKeyboard
rc-swag Sep 15, 2026
b6f475a
fix(windows): add basekeyboard filename functions
rc-swag Sep 16, 2026
eec0b12
fix(windows): rename argument that was misleading
rc-swag Sep 17, 2026
c940cae
fix(windows): fix date in title block
rc-swag Sep 18, 2026
587d74d
fix(windows): apply batched suggestions from code review
rc-swag Sep 18, 2026
bb20b89
fix(windows): rename filename builder functions
rc-swag Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions common/windows/delphi/general/utilfiletypes.pas
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ function IsProjectFile(const FileName: string): Boolean;
function IsKeyboardFile(const FileName: string): Boolean;
function RemoveFileExtension(Filename, Extension: string): string;

(**
* Builds the compiled keyboard filename by inserting the base keyboard ID
* before the .kmx extension.
*
* @param KeyboardFileName Keyboard filename, in the form '[path\]keyboardid[.kmx]'
* @param BaseKeyboardIDHex Base keyboard KLID in eight digit hexadecimal form
* @return Compiled keyboard filename, in the form '[path\]keyboardid-<KLID>.kmx'
*)
function BuildKeyboardFilenameWithBaseKeyboardID(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;

(**
* Builds the dead-key compiled keyboard filename by inserting the base
* keyboard ID and -d suffix before the .kmx extension.
*
* @param KeyboardFileName Keyboard filename, in the form '[path]\keyboardid[.kmx]'
* @param BaseKeyboardIDHex Base keyboard KLID in eight digit hexadecimal form
* @return Dead-key compiled keyboard filename, in the form '[path\]keyboardid-<KLID>-d.kmx'
*)
function BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(const KeyboardFileName: string; BaseKeyboardIDHex: string): string;


type
TKeymanFileTypeInfo = class
public
Expand Down Expand Up @@ -208,4 +229,14 @@ class function TKeymanFileTypeInfo.IsPackageWelcomeFile(
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.
18 changes: 8 additions & 10 deletions common/windows/delphi/general/utilstr.pas

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made a number of suggestions to these functions. Can you move the whole lot to utilfiletypes.pas, rather than utilstr.pas?

@rc-swag rc-swag Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move the whole lot to utilfiletypes.pas, rather than utilstr.pas?

six for one half-a-dozen for the other. It isn't really filetype identifier but I guess it is making a new filename of the same type. It is also really str manipulation function. I will move it.

Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -58,8 +58,6 @@ function GetTokenFromCaret(line: string; var selx, sellen: Integer): string;

function WideQuotedStr(const str: WideString): WideString; deprecated; // I3310



implementation

uses
Expand All @@ -78,7 +76,7 @@ function CommaToken(var s: WideString): WideString;
Result := '';
Exit;
end;

if s[1] = '"' then
begin
Delete(s,1,1);
Expand Down
19 changes: 10 additions & 9 deletions windows/src/desktop/kmshell/install/UpgradeMnemonicLayout.pas
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -46,7 +46,8 @@ implementation
kmint,
RegistryKeys,
utilexecute,
utilkmshell;
utilkmshell,
utilfiletypes;

const
{ CurrentMnemonicLayoutVersion = 476; // First 9.0 build with fixes for mnemonic layouts }
Expand Down Expand Up @@ -171,7 +172,7 @@ class procedure TUpgradeMnemonicLayout.UpgradeLayoutLanguage(
FBaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
FBaseFileName := Keyboard.Filename;
FDestFileName := OutputFileName;
FDestDeadkeyFileName := ChangeFileExt(FDestFileName, '') + '-d.kmx'; // I4552
FDestDeadkeyFileName := BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(FBaseFileName, FBaseKeyboardIDHex); // I4552
FMCompilePath := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_MCompileExe);
FDestPath := ExtractFileDir(Keyboard.Filename);

Expand Down
3 changes: 2 additions & 1 deletion windows/src/desktop/kmshell/kmshell.dpr
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ uses
Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas',
Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas',
Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall},
Keyman.Configuration.Util.NetworkConnection in 'util\Keyman.Configuration.Util.NetworkConnection.pas';
Keyman.Configuration.Util.NetworkConnection in 'util\Keyman.Configuration.Util.NetworkConnection.pas',
Keyman.Configuration.System.BaseKeyboard in 'main\Keyman.Configuration.System.BaseKeyboard.pas';

{$R VERSION.RES}
{$R manifest.res}
Expand Down
13 changes: 7 additions & 6 deletions windows/src/desktop/kmshell/kmshell.dproj
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@
<Form>frmStartInstall</Form>
</DCCReference>
<DCCReference Include="util\Keyman.Configuration.Util.NetworkConnection.pas"/>
<DCCReference Include="main\Keyman.Configuration.System.BaseKeyboard.pas"/>
<None Include="Profiling\AQtimeModule1.aqt"/>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>
Expand Down Expand Up @@ -419,21 +420,21 @@
<Platform value="Win64">False</Platform>
</Platforms>
<Deployment Version="3">
<DeployFile LocalName="Profiling\AQtimeModule1.aqt" Configuration="Debug" Class="ProjectFile">
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<RemoteName>kmshell.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\Win32\Debug\kmshell.rsm" Configuration="Debug" Class="DebugSymbols">
<DeployFile LocalName="Profiling\AQtimeModule1.aqt" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteName>kmshell.rsm</RemoteName>
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\Win32\Debug\kmshell.exe" Configuration="Debug" Class="ProjectOutput">
<DeployFile LocalName="bin\Win32\Debug\kmshell.rsm" Configuration="Debug" Class="DebugSymbols">
<Platform Name="Win32">
<RemoteName>kmshell.exe</RemoteName>
<RemoteName>kmshell.rsm</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
(*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by Ross Cruickshank on 2026-09-12
*
*
* This unit assists in setting the base keyboard configuration,
* including compiling the installed keyboard layouts against
* the selected base keyboard.
*)
unit Keyman.Configuration.System.BaseKeyboard;
Comment thread
mcdurdin marked this conversation as resolved.

interface

uses
Winapi.Windows,
System.SysUtils,
keymanapi_TLB;

(**
* Returns true if the keyboard files need to be compiled for the specified KLID.
* @param BaseKeyboardID KLID of the base keyboard to compile.
* @returns True If the keyboard files need to be compiled.
*)
function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;

(**
* Sets the base keyboard KLID for the current user and compiles the keyboard
* layout files if necessary. In the case the compiled keyboard files are
*not present, it will require elevation.
* @param WindowHandle Window handle to own the elevation prompt.
* @param BaseKeyboardID KLID of the base keyboard KLID to set.
* @returns True when the base keyboard setting has been applied.
*)
function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;

(**
* Compiles the installed keyboard layouts for the specified KLID.
* Must run elevated.
*
* @param BaseKeyboardID KLID of the base keyboard to compile.
* @returns True when the compilation is successful.
*)
function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;

implementation

uses
kmint,
utilkmshell,
utilfiletypes;

function BaseKeyboardNeedsMCompile(BaseKeyboardID: Integer): Boolean;
var
I: Integer;
Keyboard: IKeymanKeyboardInstalled;
KeyboardFileName: string;
BaseKeyboardIDHex: string;
begin
BaseKeyboardIDHex := IntToHex(BaseKeyboardID, 8);
for I := 0 to kmcom.Keyboards.Count - 1 do
begin
Keyboard := kmcom.Keyboards.Items[I];
KeyboardFileName := Keyboard.Filename;
if FileExists(KeyboardFileName) and
(not FileExists(BuildKeyboardFilenameWithBaseKeyboardID(KeyboardFileName, BaseKeyboardIDHex)) or
not FileExists(BuildKeyboardFilenameWithBaseKeyboardIDAndDeadkey(KeyboardFileName, BaseKeyboardIDHex))) then
Exit(True);
end;
Result := False;
end;

function SetBaseKeyboard(WindowHandle: THandle; BaseKeyboardID: Integer): Boolean;
begin
Result := True;
if BaseKeyboardNeedsMCompile(BaseKeyboardID) then
begin
if not kmcom.SystemInfo.IsAdministrator then
begin
Result := WaitForElevatedConfiguration(WindowHandle, '-mcompilekbds ' + IntToHex(BaseKeyboardID, 8)) = 0;
end
else
Result := MCompileBaseKeyboard(BaseKeyboardID);
end;
if not Result then
Exit;
kmcom.Options['koBaseLayout'].Value := BaseKeyboardID;
kmcom.Options.Apply;
end;

function MCompileBaseKeyboard(BaseKeyboardID: Integer): Boolean;
var
i: Integer;
kbd: IKeymanKeyboardInstalled;
begin
Result := False;
// can be called from command line so test for admin
if not kmcom.SystemInfo.IsAdministrator then
Exit;
for i := 0 to kmcom.Keyboards.Count - 1 do
begin
kbd := kmcom.Keyboards[i];
(kbd as IKeymanKeyboardInstalled2).MCompileForBaseKeyboard(BaseKeyboardID);
end;
Result := True;
end;

end.
27 changes: 21 additions & 6 deletions windows/src/desktop/kmshell/main/UfrmBaseKeyboard.pas
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,44 @@ 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;
(**
* Displays a form for the user to select a base keyboard. If the user selects a base
* keyboard, the KLID is used to Set the Base Keyboard.
*
* @returns True if the user selected base keyboard has been set.
*)
function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;


implementation

{$R *.dfm}

uses
BaseKeyboards,
kmint;
ErrorControlledRegistry,
RegistryKeys,
keymanapi_TLB,
Keyman.Configuration.System.BaseKeyboard,
kmint,
utilkmshell;


function ConfigureBaseKeyboard: Boolean;
function ConfigureAndSetBaseKeyboard(WindowHandle: THandle): Boolean;
var BaseKeyboardID: Integer;
begin
with TfrmBaseKeyboard.Create(nil) do
try
Result := ShowModal = mrOk;
if Result then
kmcom.Apply;
SetBaseKeyboard(WindowHandle, FBaseKeyboardID)
finally
Free;
end;
Expand Down Expand Up @@ -65,9 +80,9 @@ 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;


end.
11 changes: 9 additions & 2 deletions windows/src/desktop/kmshell/main/UfrmMain.pas
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ implementation
Hints,
HotkeyUtils,
initprog,
Keyman.Configuration.System.BaseKeyboard,
Keyman.Configuration.System.TIPMaintenance,
Keyman.Configuration.UI.UfrmDiagnosticTests,
KeymanOptionNames,
Expand All @@ -198,6 +199,7 @@ implementation
Keyman.Configuration.UI.UfrmStartInstall,
RegistryKeys,
SupportXMLRenderer,
UfrmBaseKeyboard,
UfrmChangeHotkey,
UfrmHTML,
UfrmInstallKeyboardFromWeb,
Expand Down Expand Up @@ -661,9 +663,14 @@ procedure TfrmMain.cefBeforeBrowseSync(Sender: TObject; const Url: string;
------------------------------------------------------------------------------}

procedure TfrmMain.Options_BaseKeyboard; // I4169
var
BaseKeyboardID: Integer;
begin
WaitForElevatedConfiguration(Handle, '-basekeyboard');
// Refresh will be triggered by elevated process
if ConfigureAndSetBaseKeyboard(Handle) then
begin
DoRefresh;
end;
Comment on lines +666 to +672

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This then can also use ConfigureAndSetBaseKeyboard (from earlier comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


end;

procedure TfrmMain.Options_SettingsManager;
Expand Down
Loading
Loading