Skip to content

fix(windows): caps lock stores to work in compliant applications#15771

Open
rc-swag wants to merge 11 commits intomasterfrom
fix/windows/15594/caps-lock-stores-change-filters
Open

fix(windows): caps lock stores to work in compliant applications#15771
rc-swag wants to merge 11 commits intomasterfrom
fix/windows/15594/caps-lock-stores-change-filters

Conversation

@rc-swag
Copy link
Copy Markdown
Contributor

@rc-swag rc-swag commented Mar 19, 2026

Fixes: #15594
and
Fixes: #5507

To assist reviewer I have inserted this a check list of changes. More details of each change are through out this PR.

  • Add Previous ScanCode and Transition Check to aiTip.cpp. As well as updating LastKey and LastScanCode in aiTip.
  • Add LastTransition thread variable
  • Remove duplication of SCAN_FLAG_KEYMAN_KEY_EVENT checks
  • Add fOnTestEatenBuf

LastScanCode and Key

This change also adds a check in aiTIP.cpp to check the thread data stored last key press and scan code. Before it could process the key event the scan code had been cleared then we would process the synthasized capslock key as a user press.

Code maintenance

The SCAN_FLAG_KEYMAN_KEY_EVENT was checked in OnKey pressess as well as an early return option in _KeymanProcessKeystroke. This change removes the extra and allows _KeymanProcessKeystroke, to be the central place for code readablitiy and maintainablity.

It also adds a processToggleChange call in kmhook_getmessage as it is sometimes called before the TIP hook. the call is also idempotent

fTestEatenBuf

It doesn't seem correct the OnTestKeyUp uses the buffer for pfEaten when it is only set by OnKeyDown and not OnTestKeyDown
*pfEaten = fEatenBuf[wParam];
Because if OnKeyDown never occured in the last processing of a particular key then it will be the wrong value, it will be from the previous press key.

This was observed with shift frees caps. Take the following sequence
Using the CapsOnOnly and ShiftFreesCaps keyboard test keyboard.
In a non-complaint app where there will be two passes the non-updateable also refered to as OnTest, and if and only if the pfEaten bool parameter is retruned as TRUE will a second updateable OnKey updateable pass be called for the key stroke.
Caps LockCaps LockShift
The caps lock does not turn off.

A second press of shift turns capslock off
Caps LockCaps LockShiftShift

Also
Caps LockShift

Here is the problem with the current method of recording pfeaten only in OnKeyDown.
If there is say key like the caps lock key where it is processed pfEaten = true then
it the key stroke will process throught the following 4 function calls.
OnTestKeyDown
OnKeyDown -> here is where the buffer fEatenBuf result is stored.
OnKeyUp
OnTestKeyUP
Then when you get a caps lock key where it is not processed pfEaten = False in the OnTestKeyDown
then OnKeyDown and OnKeyUp will not be called. The OnTestKeyUp then returns buffer value from the pervious call.
OnTestKeyDown
OnTestKeyUp <- uses the buffer which returns previous OnKeyDown which is not the current Key.
A second buffer has been added for fTestEatenBuf this will store the result of the OnTestKeys
OnTestKeyDown > here we store fTestEatenBuf
OnKeyDown -> here is where the buffer fEatenBuf result is stored.
OnKeyUp
OnTestKeyUP

There is a question about whether we need to store and use this result on the key Up directions instead of the value returned from _KeymanProcessKeystroke. It might be the case that now the key processing buisness logic has been moved to Keyman core that it is no longer necessary. I think though that is an investigation for another day.

see more in comments below

Build-bot: release:windows

rc-swag added 5 commits March 12, 2026 20:20
This change also adds a check in aiTIP.cpp to check the
thread data stored last key press and scan code. Before
if the scan code had been cleared then we would process the
synthasized caps lock key presses which was just for the system
and not for the current Keyman app.
also update external processsCaps Lock call to handle the
Updateable change
Return early if caps lock state is unchanged.
The SCAN_FLAG_KEYMAN_KEY_EVENT was checked in OnKey pressess as well
as an early return option in _KeymanProcessKeystroke. This change
removes the extra and allows _KeymanProcessKeystroke, to be the
central place for code readablitiy and maintainablity.

It also adds a processToggleChange call in kmhook_getmessage as
it is sometimes called before the TIP hook. the call is also idempotent
@keymanapp-test-bot
Copy link
Copy Markdown

keymanapp-test-bot Bot commented Mar 19, 2026

User Test Results

Test specification and instructions

🟥 SUITE_CAPSLOCK:

Retesting Template
Test-bot: retest SUITE_CAPSLOCK GROUP_WIN11 TEST_CAPSLOCK-1 TEST_CAPSOFF-3

Test Artifacts

@keymanapp-test-bot keymanapp-test-bot Bot added the user-test-missing User tests have not yet been defined for the PR label Mar 19, 2026
@keymanapp-test-bot keymanapp-test-bot Bot added this to the A19S25 milestone Mar 19, 2026
Added a Key transition member to the thread globals.
This help so better insure we actually matching the key value
that may have been cleared by kmhook_getmessage to the right key.
More importantly makes sure we are not matching it to the wrong
key press. This was happening when a synthazised capslock key press was followed
by and actuall capslock key press.
@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Mar 25, 2026

Who improvements to matching the Keyman generated scancode
Add LastTransition and Update LastScanCode in Keyman TIP
There are 3 hooks registered by keyman for system events 4 if you consider the TIP.
These are:

kmnGetMessageProc
kmnCallWndProc
kmnLowLevelKeyboardProc
CKMTipTextService

The two we are focused on for issues with this PR are kmnGetMessageProc and CKMTipTextService.
The issues that we are dealing with in this PR arise from the fact that the order in which these service get to process the key stroke is not always the same. This issue comes it greatest when the Keyman engine synthasies a keystroke using keybd_event and changes the scancode to FF so that both these to process mentioned can recognise that it was generated by Keyman and not process it in the same ways as a user or system generated key event.
Now after the have handled the event they want to set the scancode back to a normal value. However, they don't know if they are the last to process this.
Currently, the kmnGetMessageProc callback will clear the scancode but set thread variable
LastScanCode and LastKey. The CKMTipTextService if is is called second will check it the Key it is processing matches the LastKey and if LastScanCode was Keyman generated code. If so it will not pass it on to the core to be processed. If CKMTipTextService is called first the values in LastKey and LastScanCode will be from the previous key press, however it has no way of knowing its order in the hooks. There is also no timestamp with the key info. If LastKey is the same as the current wParam it can incorrectly match it as the same keypress.

This problem is again observed with the CapsOnOnly and ShiftFreesCaps keyboard.
When the Capslock key was pressed three in a row. On the second press this will cause a synthasised Capslock to insure the CapsLock Key stays on. When the Capslock Key is pressed for a third time it is processed first by CKMTipTextService before the kmnGetMessageProc matches the current key with LastKey it thinks it is synthasized it doesn't send it for processing in the core and therefore it goes to the application and clears the capslock key.

There is no timestamp in the call back informaion in either wParam or lParam. However we can do a better match I have added LastTransition which combines the key direction with previous key value bits. The chances of incorrect miss match is basically gone. For repeated key press the key up will eventually arrive so it works out in this case also.

Update LastScanCode in Keyman TIP

kmnGetMessageProc is not called at all and td->Last????. not updated
If when OnTestKeyDown or OnKeyDown is called if the pfEaten is set to True.
Then if kmnGetMessageProc is second it will not get called. This means the _td->lastscancode will not be updated.
When the Capslock key was pressed three in a row.
On the second press the OnKeyUp triggers a syntasized key press. TIP recieves this and correctly pfEaten is set to False. As we don't process the Keyman Key in core. kmnGetMessageProc well therefore recieves the keystroke and sets the LastScanCode=FF.
Then we get the 3rd CapsLock press and because TIP does process it kmnGetMessageProc is not called for the KeyDown press so its td->LastScanCode and td->LastTransition is the previous synthasised KeyUp. So now the OnKeyUp will check the _td->LastScanCode and _td->LastTransition.

The solution is to make to set the LastScanCode and LastTransition etc in the TIP aswell. This will make sure it is set even when the kmGetMessageProc is not called. If it is called it is ok because it will be setting it to the same value.
TIP will now have to use 3 new local variable previousScanCode etc. to test for the Keyman synthasied key.

@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Mar 26, 2026

Remove Duplication
In keys.cpp through the On?Key?? calls there was a test for the KeymanScanCode and VK_CAPITAL this check is made early in _KeymanProcessKeystroke. There was even a TODO: pointing out is is done in multiple places and we only need to do it once. _KeymanProcessKeystroke was the best choice and removing the others makes code the code easier to follow and maintain.

@rc-swag rc-swag changed the base branch from fix/windows/15594/caps-lock-stores to master March 26, 2026 06:36
@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Mar 26, 2026

User Testing

On Windows 11
Compliant
Test in both Notepad, Word, Firefox for compliant applications

Non-compliant
Use Texteditor64 and Texteditor32 for non-compliant applications

On Windows 10 Test

Test in Word, Firefox for compliant applications

Non-compliant
Use Notepad, Texteditor64 and Texteditor32 for non-compliant applications

SUITE_CAPSLOCK:

  • GROUP_WIN11
  • GROUP_WIN10

Caps Lock

The test keyboard layouts are found in the keyman repo at app/windows/src/test/manual-tests/caps-lock-stores. There is a project file for the 3 keyboards used in this test. The project file can be used to build the keyboard packages, but you can conveniently use the .kmp file zipped and included respectively below.

The test cases below expect the usage of the capslock.kmp.zip keyboard. That keyboard outputs pass or fail if following the test cases.

Prerequisites before each test

  • System keyboard layout is en-US
  • Install a keyboard that doesn't use any of the caps lock stores, e.g. capslock.kmp.
  • CapsLock is currently on
  • Currently active keyboard is the capslock.kmp keyboard

Test cases

click to expand
  • TEST_CAPSLOCK-1: uppercase with virtual key

    • press and release a

    Expected result:

    • pass. (with other keyboards uppercase A)
  • TEST_CAPSLOCK-2: lowercase with virtual key

    • press and hold 'Shift'
    • press and release b
    • release Shift

    Expected result:

    • pass. (with other keyboards lowercase b)
  • TEST_CAPSLOCK-3: capslock ignored for numbers

    • press and hold 'Shift'
    • press and release 3
    • release Shift

    Expected result:

    • pass. (with other keyboards #)
  • TEST_CAPSLOCK-4: uppercase

    • press and release c

    Expected result:

    • pass. (with other keyboards uppercase C)
  • TEST_CAPSLOCK-5: lowercase

    • press and hold 'Shift'
    • press and release d
    • release Shift

    Expected result:

    • pass. (with other keyboards lowercase d)

CapsAlwaysOff

For these tests, use a keyboard with the caps_always_off.kmp.zip store set. We call this keyboard capsalwaysoff below.

Any keyboard with that store set will work; if you don't have one at hand you can use the caps_always_off.kmp keyboard. The caps_always_off.kmp keyboard will prevent switching caps lock on. As a sanity check to verify that Keyman is actually active, pressing the key a will output ncaps_little_a, and Shift+a will output ncaps_shift_A.

Note: When testing in a virtual machine, use an on-screen keyboard (in VirtualBox: Input/Keyboard/Soft Keyboard) and observe the caps lock indicator of the on-screen keyboard. Using the hardware keyboard might show side effects with caps lock.

Prerequisites before each test

  • Install a keyboard that has CapsAlwaysOff store set, e.g. caps_always_off.kmp.
  • CapsLock is currently off
  • Currently active keyboard is a non-Keyman keyboard

Test cases

click to expand
  • TEST_CAPSOFF-1: sanity check

    • switch to capsalwaysoff keyboard
    • press and release a

    Expected result:

    • output: ncaps_little_a
  • TEST_CAPSOFF-2: caps lock stays off

    • switch to capsalwaysoff keyboard
    • press and release CapsLock key
    • press and release a

    Expected result:

    • caps lock indicator turns off once key pressed
    • output: ncaps_little_a
  • TEST_CAPSOFF-3: no caps lock while holding capslock key

    • switch to capsalwaysoff keyboard
    • press and hold CapsLock key
    • press and release a
    • release CapsLock key

    Expected result:

    • output: ncaps_little_a
  • TEST_CAPSOFF-4: no caps lock while holding capslock key

    • switch to capsalwaysoff keyboard
    • press and hold CapsLock key
    • press and hold Shift key
    • press and release a
    • release CapsLock and Shift keys

    Expected result:

    • output: ncaps_shift_A
  • TEST_CAPSOFF-5: switching turns off caps lock

    • turn on caps lock
    • switch to capsalwaysoff keyboard
    • press and release a

    Expected result:

    • caps lock indicator turned off
    • output: ncaps_little_a

SHIFT: CapsOnOnly/ShiftFreesCaps

For these tests, use a keyboard with the CapsOnOnly and ShiftFreesCaps stores set. We call this keyboard shift_frees_caps below.

Any keyboard with these stores set will work; if you don't have one at hand you can use the shift_frees_caps.kmp.zip keyboard.

The shift_frees_caps.kmp keyboard will enable caps lock by pressing the CapsLock key, and will turn capslock off by pressing the Shift key. The keyboard outputs pass or fail if following the test cases.

Note: When testing in a virtual machine, use an on-screen keyboard (in VirtualBox: Input/Keyboard/Soft Keyboard) and observe the caps lock indicator of the on-screen keyboard. Using the hardware keyboard might show side effects with caps lock. Except for TEST_CAPSONLY-5 which can only be reliably tested on a hardware keyboard on host OS (not a VM). For windows 10 and windows 11 with a virtual box vm-onscreen keyboard, the following happens. The VM soft keyboard does NOT actually send the Shift Shift Key Stroke through but rather will change the keys pressed for example if an a is pressed the soft keyboard itself will change that key to a A. This means we can't Test TEST_CAPONLY-5 on a soft keyboard.

Prerequisites before each test

  • Install a keyboard that has the CapsOnOnly and ShiftFreesCaps stores set, e.g.
    shift_frees_caps.kmp.
  • CapsLock is currently off
  • Currently active keyboard is shift_frees_caps keyboard

Test cases

click to expand
  • TEST_CAPSONLY-1: no caps

    • press and release 1

    Expected result:

    • output: pass.
  • TEST_CAPSONLY-2: caps

    • press and release CapsLock
    • press and release 2

    Expected result:

    • caps lock indicator turned on
    • output: pass.
  • TEST_CAPSONLY-3: caps doesn't toggle

    • press and release CapsLock
    • press and release CapsLock
    • press and release 6

    Expected result:

    • caps lock indicator turned on
    • output: pass.
  • TEST_CAPSONLY-4: shift turns off

    • press and release CapsLock
    • press and hold Shift
    • press and release 3
    • release Shift

    Expected result:

    • caps lock indicator turned off
    • output: pass.
  • TEST_CAPSONLY-5: shift by itself turns off
    Be aware of limitations when testing this on virtual machines as noted above.

    • press and release CapsLock
    • press and release Shift

    Expected result:

    • caps lock indicator turned off
    • (no output)
  • TEST_CAPSONLY-6: press CapsLock multiple times
    Be aware of limitations when testing this on virtual machines as noted above.

    • press and release CapsLock
    • press and release CapsLock
    • press and release CapsLock
    • press and release CapsLock
    • press and release Shift

    Expected result:

    • caps lock indicator stays on after each CapsLock press, momentarily turning off is expected as the key is pressed and released however is should then stay on.

    • caps lock indicator turned off after Shift Pressed

    • (no output)

Regression testcases
Install "Khmer Angkor"
TEST_KM_COMPLIANT:

  • Open Word, On Windows 11 also test Notepad. Switch to "Khmer Angkor" keyboard. Type xEjmr.
  • Verify that the output is "ខ្មែរ".

TEST_KM_NON_COMPLIANT:

  • Open the TextEditors Attached to this PR. Switch to "Khmer Angkor" keyboard. Type xEjmr.
  • Verify that the output is "ខ្មែរ".

@keymanapp-test-bot keymanapp-test-bot Bot added has-user-test 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 Mar 26, 2026
@rc-swag rc-swag marked this pull request as ready for review March 27, 2026 06:23
@keyman-server keyman-server modified the milestones: A19S25, A19S26 Mar 28, 2026
@rc-swag rc-swag self-assigned this Mar 30, 2026
@rc-swag rc-swag marked this pull request as draft March 30, 2026 04:45
Update the lastkey, lastscankey and lasttransition in aaitp.
This ensures that when the pfEaten is FALSE and the get_message_hook
does not recieve the key press the "last" value is correct.
@rc-swag rc-swag marked this pull request as ready for review April 2, 2026 06:26
@Meng-Heng Meng-Heng self-assigned this Apr 7, 2026
@Meng-Heng
Copy link
Copy Markdown
Contributor

Test Specs

  1. Windows 10 Bootcamp
  2. Keyman v19.0.211-alpha-test-15771
  3. Keyboards
    • Capslock
    • Caps Always Off
    • Shift frees caps
  4. Applications
    • Compliant: MS Word
    • Non-compliant: Notepad

Test Results

SUITE_CAPSLOCK:

GROUP_WIN10:

  • TEST_CAPSLOCK-1 (PASSED):
    In both compliant and non-compliant apps
    Press and release a
    Result: pass.

  • TEST_CAPSLOCK-2 (PASSED):
    In both compliant and non-compliant apps
    Press and hold Shift
    Press and release b
    Release shift
    Result: pass.

  • TEST_CAPSLOCK-3 (PASSED):
    In both compliant and non-compliant apps
    Press and hold Shift
    Press and release 3
    Release shift
    Result: pass.

  • TEST_CAPSLOCK-4 (PASSED):
    In both compliant and non-compliant apps
    Press and release c
    Result: pass.

  • TEST_CAPSLOCK-5 (PASSED):
    In both compliant and non-compliant apps
    Press and hold Shift
    Press and release d
    Release shift
    Result: pass.

  • TEST_CAPSOFF-1 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and release a
    Result: ncaps_little_a

  • TEST_CAPSOFF-2 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and release Capslock key
    Press and release a
    Result: ncaps_little_a
    Note:
    In MS Word, the capslock key turns off immediately after being pressed
    In Notepad, the capslock key turns off after a is pressed.

  • TEST_CAPSOFF-3 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and hold Capslock key
    Press and release a
    Release Capslick key
    Result: ncaps_little_a

  • TEST_CAPSOFF-4 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and hold Capslock and Shift key
    Press and release a
    Release Capslick and Shift key
    Result: ncaps_shift_A

  • TEST_CAPSOFF-5 (PASSED):
    In both compliant and non-compliant apps
    Switch to Windows’ English (United States) keyboard
    Press and release Capslock key
    Switch to Caps always off keyboard
    Press and release a
    Result: ncaps_little_a

  • TEST_CAPSONLY-1 (PASSED):
    In both compliant and non-compliant apps
    Press and release a
    Result: pass.

  • TEST_CAPSONLY-2 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and release 2
    Result: pass.
    The Capslock indicator is locked on.

  • TEST_CAPSONLY-3 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and release CapsLock
    Press and release 6
    Result: pass.
    The Capslock indicator is locked on.

  • TEST_CAPSONLY-4 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and hold Shift
    Press and release 3
    Release Shift
    Result: pass
    Capslock indicator is turned off.

  • TEST_CAPSONLY-5 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and release Shift
    Result: Capslock is turned off.

  • TEST_CAPSONLY-6 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock a bunch of time
    The Capslock indicator stays on
    Press and release Shift
    The capslock indicator turns off
    Typing works as expected.

  • TEST_KM_COMPLIANT (PASSED):
    In MS Word → Type xEjmr
    Result is ខ្មែរ

  • TEST_KM_NON_COMPLIANT (PASSED):
    Launch the TextEditor EC Test App 32 → Type xEjmr
    Result is ខ្មែរ

@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Apr 8, 2026

@sze2st Can you please test the Windows 11 cases on one of your machines?

@Meng-Heng Meng-Heng removed their assignment Apr 8, 2026
@sze2st
Copy link
Copy Markdown
Contributor

sze2st commented Apr 8, 2026

Test Specs

  1. Windows 11
  2. Keyman v19.0.211-alpha-test-15771
  3. Keyboards
    • capslock.kmp
    • caps_always_off.kmp
    • shift_frees_caps.kmp
  4. Applications
    • Compliant: LibreOffice Writer
    • Non-compliant: editor64
  5. On test machine system physical keyboard layout is German (tests are started with keyboard ENG US)

Test Results

SUITE_CAPSLOCK:

GROUP_WIN11:

  • TEST_CAPSLOCK-1 (FAILED):
    In both compliant and non-compliant apps
  1. Starting app

  2. Switching CapsLock key on

  3. then just selecting keyboard CapsLock (i.e. without pressing any other key)
    Result: CapsLock key has moved to off unexpectedly. --> failed

  4. Starting app

  5. Switching CapsLock key on

  6. then just selecting keyboard CapsLock (i.e. without pressing any key) --> switches off CapsLock as before

  7. again switching CapsLock key on (i.e. now with current keyboard is CapsLock)

  8. select keyboard ENG US (CapsLock key keeps on)

  9. select keyboard CapsLock --> CapsLock remains on

  10. press a
    Result: pass. --> passed

  • TEST_CAPSLOCK-2 (PASSED):
    In both compliant and non-compliant apps test started when CapsLock key is on
    Press and hold Shift
    Press and release b
    Release shift
    Result: pass.
    Note: With additionally having installed keyboard CapsAlwaysOff similar happens as with keyboard CapsLock. Moreover it turns out that CapsLock key is switched off when selecting CapsAlwaysOff or CapsLock if it has not been set to on with the same keyboard before. Example
  1. start app

  2. press CapsLock (i.e. CapsLock is on)

  3. switch to keyboard CapsAlwaysOff (CapsLock will move to off)

  4. again press CapsLock (i.e. CapsLock is on)

  5. switch to keyboard 'CapsLock' (CapsLock will move to off)

  6. again press CapsLock (i.e. CapsLock is on)

  7. switch to keyboard CapsAlwaysOff --> CapsLock will no longer move to off but hold on

  8. start app

  9. press CapsLock (i.e. CapsLock is on)

  10. switch to keyboard CapsAlwaysOff (CapsLock will move to off)

  11. leave out pressing CapsLock (i.e. CapsLock is still off)

  12. switch to keyboard 'CapsLock' (CapsLock will move to off)

  13. again press CapsLock (i.e. CapsLock is on)

  14. switch to keyboard CapsAlwaysOff --> CapsLock will move to off

  15. again press CapsLock (i.e. CapsLock is on)

  16. switch to keyboard 'CapsLock' --> CapsLock holds to be on

Conclusion: keyboard X seems somehow to remember if during its usage CapsLock has been pressed and is set to on. If so selecting keyboard X will not move CapsLock to off.

  • TEST_CAPSLOCK-3 (PASSED):
    In both compliant and non-compliant apps
    Press and hold Shift
    Press and release 3
    Release shift
    Result: pass.

  • TEST_CAPSLOCK-4 (PASSED):
    In both compliant and non-compliant apps
    Press and release c
    Result: pass.

  • TEST_CAPSLOCK-5 (PASSED):
    In both compliant and non-compliant apps
    Press and hold Shift
    Press and release d
    Release shift
    Result: pass.

  • TEST_CAPSOFF-1 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and release a
    Result: ncaps_little_a

  • TEST_CAPSOFF-2 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and release Capslock key
    Press and release a
    Result: ncaps_little_a and caps lock indicator turns off
    Note:
    In MS Word, the capslock key turns off immediately after being pressed
    In Notepad, the capslock key turns off after a is pressed.

  • TEST_CAPSOFF-3 (FAILED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and hold Capslock key --> pressing Capslock key turns on caps lock indicator however holding it will immediately turn off caps lock indicator unexpectedly--> failed
    Press and release a
    Release Capslock key
    Result: ncaps_little_a (with both pressing Capslock key and pressing a in parallel and subsequently)

  • TEST_CAPSOFF-4 (PASSED):
    In both compliant and non-compliant apps
    Selected keyboard: Caps always off
    Press and hold Capslock and Shift key --> holding Capslock key turns off caps lock indicator immediately as with TEST_CAPSOFF-3
    Press and release a
    Release Capslick and Shift key
    Result: ncaps_shift_A

  • TEST_CAPSOFF-5 (PASSED):
    In both compliant and non-compliant apps
    Switch to Windows’ English (United States) keyboard
    Press and release Capslock key
    Switch to Caps always off keyboard
    Press and release a
    Result: ncaps_little_a

  • TEST_CAPSONLY-1 (PASSED):
    In both compliant and non-compliant apps
    Press and release a
    Result: pass.

  • TEST_CAPSONLY-2 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and release 2
    Result: pass.
    The Capslock indicator is locked on.

  • TEST_CAPSONLY-3 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and release CapsLock
    Press and release 6
    Result: pass.
    The Capslock indicator is locked on.

  • TEST_CAPSONLY-4 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and hold Shift
    Press and release 3
    Release Shift
    Result: pass.
    Capslock indicator is turned off.

  • TEST_CAPSONLY-5 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock
    Press and release Shift
    Result: Capslock keeps on while repeatedly pressing CapsLock and is turned off after Shift (right Shift and left Shift).

  • TEST_CAPSONLY-6 (PASSED):
    In both compliant and non-compliant apps
    Press and release CapsLock a bunch of time
    The Capslock indicator stays on
    Press and release Shift
    The capslock indicator turns off
    Typing works as expected.

  • TEST_KM_COMPLIANT (PASSED):
    In LibreOffice Writer and Notepad → Type xEjmr
    Result is ខ្មែរ (in LibreOffice Writer use 'Busra' font to get the result displayed as expected)

  • TEST_KM_NON_COMPLIANT (PASSED):
    Launch the TextEditor EC Test App 64 → Type xEjmr
    Result is ខ្មែរ

@sze2st sze2st closed this Apr 8, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in Keyman Apr 8, 2026
@mcdurdin mcdurdin reopened this Apr 9, 2026
@github-project-automation github-project-automation Bot moved this from Done to In Progress in Keyman Apr 9, 2026
@mcdurdin
Copy link
Copy Markdown
Member

mcdurdin commented Apr 9, 2026

@sze2st why did you close this PR?

@keymanapp-test-bot keymanapp-test-bot Bot added user-test-failed and removed user-test-required User tests have not been completed labels Apr 9, 2026
@rc-swag rc-swag requested a review from mcdurdin April 9, 2026 11:26
Copy link
Copy Markdown
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

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

Given there are still test failures, I won't approve changes yet, as they'll need a revisit

Suggested title: fix(windows): remove assumptions about order that hooks and TIP events will be called or something like that? If I am understanding the changes correctly, a lot relates to that. But there's a bit more in there that perhaps we should discuss.

* @param _td Theread data to update
* @param wParam WPARAM of the key event
* @param scan Scan code of the key event
* @param keyTransition Transition state of the key event (0 = key down, 1 = repeat, 3 = key up)
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
* @param keyTransition Transition state of the key event (0 = key down, 1 = repeat, 3 = key up)
* @param keyTransition Transition state of the key event (0 = key down, 1 = repeat, 2 = key up)

Can we use an enumeration to clarify this?

BOOL isUp = keyFlags & KF_UP ? TRUE : FALSE;
BOOL extended = keyFlags & KF_EXTENDED ? TRUE : FALSE;
BYTE scan = keyFlags & 0xFF;
BYTE keyTransition = (BYTE)((HIWORD(lParam) & (KF_UP | KF_REPEAT)) >> 14);
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.

KEYMSG_FLAG_TRANSITION macro is defined, can we use that here?

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
BYTE keyTransition = (BYTE)((HIWORD(lParam) & (KF_UP | KF_REPEAT)) >> 14);
BYTE keyTransition = KEYMSG_FLAG_TRANSITION(lParam);

Comment on lines 161 to +163
_td->LastScanCode = scan;
_td->LastKey = mp->wParam;
_td->LastTransition = keyTransitionEvent;
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.

You created a function UpdateLastKeyCache so should use that here too.

Suggested change
_td->LastScanCode = scan;
_td->LastKey = mp->wParam;
_td->LastTransition = keyTransitionEvent;
UpdateLastKeyCache(_td, mp->wParam, scan, keyTransitionEvent);

Comment on lines +90 to +91
if (!Updateable || caps_state_change == KM_CORE_CAPS_UNCHANGED)
{
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
if (!Updateable || caps_state_change == KM_CORE_CAPS_UNCHANGED)
{
if (!Updateable || caps_state_change == KM_CORE_CAPS_UNCHANGED) {

nit only

@keyman-server keyman-server modified the milestones: A19S26, A19S27 Apr 14, 2026
@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Apr 19, 2026

Suggested title: fix(windows): remove assumptions about order that hooks and TIP events will be called or something like that? If I am understanding the changes correctly, a lot relates to that. But there's a bit more in there that perhaps we should discuss.

Good catch. This PR was originally a child of this PR title fix(windows): caps lock stores to work in compliant applications #15730.
Which is what the PR does by addressing assumptions about hook order with some the flow on changes from that listed above in the check list.

@rc-swag rc-swag changed the title fix(windows): reduce the number of places km scan flag fix(windows): caps lock stores to work in compliant applications Apr 19, 2026
@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Apr 21, 2026

I have created a seperate issue for addressing the capslock indicator turning off when selecting a Keyman keyboard. TEST_CAPSLOCK-1 (FAILED): #15857

@rc-swag
Copy link
Copy Markdown
Contributor Author

rc-swag commented Apr 21, 2026

@sze2st I see how TEST_CAPSOFF-3 is failing. The fact that the indicator turns off is ok in this scenario of Caps always off and the output is as expected.

@keyman-server keyman-server modified the milestones: A19S27, A19S28 Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

5 participants