Skip to content

fix(windows): set basekeyboard as current user not the admin user on a elevated process. - #16162

Open
rc-swag wants to merge 20 commits into
masterfrom
fix/windows/15152/basekeyboard-user-not-admin
Open

fix(windows): set basekeyboard as current user not the admin user on a elevated process.#16162
rc-swag wants to merge 20 commits into
masterfrom
fix/windows/15152/basekeyboard-user-not-admin

Conversation

@rc-swag

@rc-swag rc-swag commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Fixes: #15152

For all the iterartions this change went through you can read through the rest of this PR.

This PR now shows the BaseKeyboard form without elevation for the current user. On successful change it checks to see if there is already compiled keyboards for the selected basekeyboard. If there aren't compiled keyboards in then elevates to compile the keyboards but passes in the current users basekeyboard ID. On successful return from the elevated process it sets the basekeyboard as selected by the user.

For installing keyboards it is also important that if the basekeyboard is different the admin user used for elevation that keyboards are compiled against the correct basekeyboard. This change is made in #16528

Build-bot: release:windows

User Testing

TEST_BASE_KEYBOARD_CURRENT_USER

  1. Login into Windows with and account that is a "standard" user and does not have "Administrator" rights.

  2. Install the Keyman from this PR

  3. Open Keyman Configuration -> Keyboard Layouts

  4. Install a keyboard for example sil_ipa

  5. Open Keyman Configuration -> Options

Check C:\ProgramData\Keyman\Keyman Engine\Keyboard\_Package\sil_ipa don't delete the base sil_ipa.kmx but delete any with KLIDs for example sil_ipa-00000409-d.kmx

  1. Click Base Keyboard, change the Base Keyboard to German. You will need to enter the login details for a Admin user.

  2. Confirm the Keyboard changes for the current user and not the Admin user used for the elevated processs.

  3. Check C:\ProgramData\Keyman\Keyman Engine\Keyboard\_Package\sil_ipa there should now be a sil_ipa-????0407-d.kmx and sil_ipa-????0407.kmx

TEST_BASE_KEYBOARD_CURRENT_USER_KMX_EXISTS

After completing the steps in TEST_BASE_KEYBOARD_CURRENT_USER

  1. Open Keyman Configuration -> Options
  2. Click Base Keyboard, change the Base Keyboard to English. You may need to enter the login details for a Admin user.
  3. Close Configuration
    Check German mcomplied kmx is still there i.e. C:\ProgramData\Keyman\Keyman Engine\Keyboard\_Package\sil_ipa there should now be a sil_ipa-????0407-d.kmx and sil_ipa-????0407.kmx
  4. Open Keyman Configuration -> Options
    6.Click Base Keyboard, change the Base Keyboard to German. You should Not be asked to enter a admin user.

@rc-swag rc-swag self-assigned this Jun 30, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Keyman Jun 30, 2026
@keymanapp-test-bot keymanapp-test-bot Bot added the user-test-missing User tests have not yet been defined for the PR label Jun 30, 2026
@keymanapp-test-bot

keymanapp-test-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

User Test Results

Test specification and instructions

  • TEST_BASE_KEYBOARD_CURRENT_USER (PASSED) (notes)
  • TEST_BASE_KEYBOARD_CURRENT_USER_KMX_EXISTS (PASSED) (notes)

Test Artifacts

@mcdurdin mcdurdin left a comment

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 don't think this is correct. The base keyboard dialog needs to run elevated -- because when you change the base keyboard, Keyman needs to mcompile each of the installed keyboards against the new base keyboard:

if FOldBaseLayout <> Get_Items('koBaseLayout').Value then
for I := 0 to Context.Keyboards.Count - 1 do // I4169
(Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout;

The problem here is that the base layout setting is saved against the Admin user, but needs to be saved against the current user. This probably is best solved by splitting the admin component -- mcompiling -- out of the TKeymanOptions.Apply function, and running it as a separate step from the Base Keyboard dialog. Then the Base Keyboard dialog does not show elevated, but just elevates when OK is clicked, if it detects that new mcompiles need to be run, and does that as a kmshell -mcompile <basekeylayoutid> call? (implementation calls: TKeymanKeyboardInstalled.UpdateBaseLayout for each installed keyboard).

TKPRecompileMnemonicKeyboard then needs a parameter for the base layout, rather than reading it from the context options:

with Context as TKeymanContext do
BaseKeyboardID := (Options as IKeymanOptions).Items['koBaseLayout'].Value;

So some plumbing required, sadly.

@keyman-server keyman-server modified the milestones: A19S32, A19S33 Jul 4, 2026
@rc-swag

rc-swag commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

I don't think this is correct. The base keyboard dialog needs to run elevated -- because when you change the base keyboard, Keyman needs to mcompile each of the installed keyboards against the new base keyboard:

This change may not have been the prefered change but it worked. It does run as an elevated but the process is created as a current user.
I tested it last week and it changed the base keyboard for the non admin user. Not the admin. I will look into it again.

@keymanapp-test-bot keymanapp-test-bot Bot added user-test-required User tests have not been completed and removed user-test-missing User tests have not yet been defined for the PR labels Jul 6, 2026
@mcdurdin

mcdurdin commented Jul 6, 2026

Copy link
Copy Markdown
Member

Did you verify that this worked with a baselayout you had never selected previously? How could the mcompiled .kmx files be written to the C:\ProgramData folder if the base layout steps are run non-elevated?

One reasonably straightforward way to address this, for starting as a non-elevated user:

  1. CU: The Base Keyboard dialog itself should not be elevated - launch from the current process.
  2. CU: After clicking OK, verify if any mcompile is needed by looking to see if the needed .kmx files are already present in ProgramData.
  3. CU: If mcompile is required, then wait for elevated kmshell -mcompile <baseklid> should be called to run the mcompile process.
  4. LM: kmshell -mcompile will need to temporarily set the option[koBaseLayout] for the admin user in order to run the process. This will cause mcompile to be run. Reset option afterwards.
  5. CU: After the wait for elevated process step completes, set option[koBaseLayout] for current user.

Also:

  • If Keyman Configuration is started as an elevated user, skip steps 3 and 4.
  • Need to update kmcomapi to only run mcompile if elevated

@mcdurdin

mcdurdin commented Jul 6, 2026

Copy link
Copy Markdown
Member

5. Confirm the Keyboard changes for the current user and not the Admin user used for the elevated processs

Need to verify that the relevant files are saved in ProgramData too and that the base keyboard is mapped as expected!

@Meng-Heng Meng-Heng assigned Meng-Heng and unassigned Meng-Heng Jul 9, 2026
@Meng-Heng

Meng-Heng commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Test Prerequisites

  1. Setup a non-admin local user by following this https://www.wikihow.com/Create-a-New-Local-User-Account-in-Windows-10

Test Specs

  1. Windows 10 Bootcamp

Test Results

  • TEST_BASE_KEYBOARD_CURRENT_USER (PASSED):
  1. Launch Keyman version 19.0.249
  2. Open Keyman Configuration -> Options
  3. Click Base Keyboard -> Enter Admin's password -> Change the Base Keyboard to English - Irish

Note

The Keyman Configuration window becomes unclickable until I click out of the app and click back. It does not show unresponding.

  1. Confirmed: the Keyboard changes for the current user and not the Admin user.
  2. To clarify, log into Admin account
  3. Verified: The Base keyboard does not change to English - Irish.

Note

The 2nd and later attempts of changing the Base keyboard, this error pop up
image
The Base keyboard does not change until I click X, and then open Keyman again.

@keymanapp-test-bot keymanapp-test-bot Bot removed the user-test-required User tests have not been completed label Jul 9, 2026
@mcdurdin

mcdurdin commented Jul 9, 2026

Copy link
Copy Markdown
Member

The 2nd and later attempts of changing the Base keyboard, this error pop up

Note, if a crash dialog appears, FAIL the test. Also, please Copy to Clipboard and paste it into the test report.

TEST_BASE_KEYBOARD_CURRENT_USER (FAIL): a crash dialog appeared

@keyman-server keyman-server modified the milestones: A19S33, A19S34 Jul 20, 2026
@rc-swag rc-swag closed this Jul 24, 2026
@rc-swag
rc-swag force-pushed the fix/windows/15152/basekeyboard-user-not-admin branch from 2d6a4ee to 2c48025 Compare July 24, 2026 02:03
@github-project-automation github-project-automation Bot moved this from Todo to Done in Keyman Jul 24, 2026
@@ -152,14 +156,19 @@ procedure TKeymanKeyboardInstalled.Uninstall;
end;

procedure TKeymanKeyboardInstalled.UpdateBaseLayout; // I4169

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.

This could now be removed

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.

@mcdurdin how do I go about deprecating this as it is in a interface.

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 is only in an internal interface (IIntKeymanKeyboardsInstalled) so we can just remove it from the interface. It's not a published API. It's not even used outside of kmcomapi, which makes it pretty clean. All the interfaces in internalinterfaces.pas are used only in kmcomapi which makes it safe to modify them as needed -- no need to changing GUIDs or any other files.

(If the interface had been used cross-module, then you'd regenerate the GUID to make it easier to track errors -- resulting in 'interface not found' rather than a weird and hard to diagnose crash.)

4 results - 3 files

windows\src\engine\kmcomapi\com\keyboards\keymankeyboardinstalled.pas:
  111      procedure ClearVisualKeyboard;
  112:     procedure UpdateBaseLayout;   // I4169
  113      procedure RefreshInstallation;

  153  
  154: procedure TKeymanKeyboardInstalled.UpdateBaseLayout;   // I4169
  155  begin

windows\src\engine\kmcomapi\com\options\keymanoptions.pas:
  134      for I := 0 to Context.Keyboards.Count - 1 do   // I4169
  135:       (Context.Keyboards.Items[I] as IIntKeymanKeyboardInstalled).UpdateBaseLayout;
  136  

windows\src\engine\kmcomapi\util\internalinterfaces.pas:
  69      procedure ClearVisualKeyboard;
  70:     procedure UpdateBaseLayout;   // I4169
  71      procedure RefreshInstallation;

@keyman-server keyman-server modified the milestones: A19S36, B19S1 Aug 30, 2026
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.
Comment thread windows/src/engine/kmcomapi/keymanapi_TLB.pas
Comment thread windows/src/engine/kmcomapi/keymanapi_TLB.pas
@rc-swag

rc-swag commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@Meng-Heng
The tests need to be run again due the significant re-work of the changes.
Test-bot: retest TEST_BASE_KEYBOARD_CURRENT_USER TEST_BASE_KEYBOARD_CURRENT_USER_KMX_EXISTS

@keymanapp-test-bot keymanapp-test-bot Bot added the user-test-required User tests have not been completed label Sep 7, 2026
@rc-swag
rc-swag marked this pull request as ready for review September 7, 2026 03:39
@rc-swag
rc-swag requested a review from mcdurdin September 7, 2026 03:39
@mcdurdin

mcdurdin commented Sep 7, 2026

Copy link
Copy Markdown
Member

I'll hold off reviewing until build passes 😀

@Meng-Heng

Copy link
Copy Markdown
Contributor

Test Specs

  1. Windows 10 Bootcamp
  2. Keyman for Windows 19.0.268-alpha-test-16162

Test Results

  • TEST_BASE_KEYBOARD_CURRENT_USER (PASSED):
  1. Log into Standard non-admin user account
  2. Launch Keyman -> Start Keyman -> Open Keyman Configuration
  3. Install the sil_ipa keyboard within Keyman Configuration
  4. Check C:\ProgramData\Keyman\Keyman Engine\Keyboard\_Package\sil_ipa
  5. Keep the sil_ipa.kmx file
  6. Delete all of the KLIDs; sil_ipa-00000409-d.kmx...etc
  7. Click Base Keyboard -> Change the Base Keyboard to German -> Enter Admin's credentials
  8. VERIFIED: the Base Keyboard changes for the current user and not the Admin user.
  9. Check C:\ProgramData\Keyman\Keyman Engine\Keyboard\_Package\sil_ipa
  10. VERIFIED: sil_ipa-00000407-d.kmx and sil_ipa-00000407.kmx appear.
  • TEST_BASE_KEYBOARD_CURRENT_USER_KMX_EXISTS (PASSED):
  1. After completing the steps in TEST_BASE_KEYBOARD_CURRENT_USER
  2. Open Keyman Configuration -> Options
  3. Click Base Keyboard -> Change the Base Keyboard to English - US -> Enter Admin's credentials
  4. Close Configuration
  5. VERIFIED: German mcomplied kmx is still there; C:\ProgramData\Keyman\Keyman Engine\Keyboard\_Package\sil_ipa, sil_ipa-00000407-d.kmx and sil_ipa-00000407.kmx
  6. Open Keyman Configuration -> Options
  7. Click Base Keyboard -> Change the Base Keyboard to German
  8. VERIFIED: The admin credentials window pop-up does not appear.

@keymanapp-test-bot keymanapp-test-bot Bot removed the user-test-required User tests have not been completed label Sep 10, 2026
else if s = '-basekeyboard' then FMode := fmBaseKeyboard // I4169
else if s = '-mcompilekbds' then
begin
FMode := fmMCompileKbds;

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.

Suggested change
FMode := fmMCompileKbds;
// Requires elevated context
FMode := fmMCompileKbds;

It may be good for each command to denote if it requires elevated context in a programmatic way in the future. I think we have scope to separate internal commands and external ones as well -- for example, this is really an internal-use command with little scope generally for external use.

kdl: IKeymanDefaultLanguage;
FIcon: string;
FMutex: TKeymanMutex; // I2720
BaseKeyboardID: Integer;

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 am not comfortable with a BaseKeyboard variable and a FBaseKeyboard parameter -- too easy to confuse them! Can we perhaps refactor the fmBaseKeyboard case into a ConfigureAndSetBaseKeyboard function?

@@ -0,0 +1,95 @@
unit Keyman.Configuration.System.BaseKeyboard;

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.

Please add standard header

@mcdurdin mcdurdin left a comment

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.

Just some minor tweaks and a question about whether we need to publish a new public API for this? (I don't think the work is wasted effort, because you gained a deeper knowledge of how kmcomapi interfaces are implemented, but I am sorry if we decide that it really can be an internal interface)

Comment on lines +10 to +14
(**
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.
*)

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.

Suggested change
(**
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.
*)

Comment on lines +17 to +24
(**
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.
*)

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.

Suggested change
(**
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.
*)

Comment on lines +27 to +31
(**
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.
*)

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.

Suggested change
(**
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.
*)

Comment on lines +53 to +54
(not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '.kmx') or
not FileExists(ChangeFileExt(BaseFileName, '') + '-' + BaseKeyboardIDHex + '-d.kmx')) then

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.

Let's make a function to build these filenames?

Comment on lines +22 to +28
(**
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.
*)

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.

Suggested change
(**
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.
*)

Comment on lines +666 to +673
var
BaseKeyboardID: Integer;
begin
WaitForElevatedConfiguration(Handle, '-basekeyboard');
// Refresh will be triggered by elevated process
if ConfigureBaseKeyboard(BaseKeyboardID) then
begin
SetBaseKeyboard(Handle, BaseKeyboardID);
DoRefresh;
end;

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)

procedure RefreshInstallation;

{ IKeymanKeyboardInstalled2 }
procedure MCompileForBaseKeyboard(KLID: Integer); safecall;

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 wonder if this could be slipped into IIntKeymanKeyboardInstalled and avoid publishing another interface?

@keyman-server keyman-server modified the milestones: B19S1, B19S2 Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

bug(windows): base keyboard user setting not set when using the current user is not an administrator

4 participants