Browse Source

Fix access keys not working when KeySymbol is null (#21077)

* test: add regression test for access key with system key events

Regression test for #20961: verifies that access keys fire correctly
when triggered via Alt+key (system key events).

* fix: provide KeySymbol for system key events via MapVirtualKey

On Windows, WM_SYSKEYDOWN (Alt+key) intentionally skips ToUnicodeEx
to avoid corrupting keyboard state. This left KeySymbol null, which
broke access keys after #20662 switched from Key to KeySymbol.

Use MapVirtualKey(VK, MAPVK_VK_TO_CHAR) as a layout-aware fallback
for system key events — it resolves the character without touching
keyboard state.

Fixes #20961

* chore: retrigger CI
pull/21093/head
Nathan Nguyen 2 months ago
committed by GitHub
parent
commit
1060839683
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      src/Windows/Avalonia.Win32/Input/KeyInterop.cs
  2. 7
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  3. 27
      tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs

18
src/Windows/Avalonia.Win32/Input/KeyInterop.cs

@ -493,6 +493,24 @@ namespace Avalonia.Win32.Input
PhysicalKey.None;
}
/// <summary>
/// Gets a key symbol from a Windows virtual-key using MapVirtualKey.
/// Unlike <see cref="GetKeySymbol"/>, this does not call ToUnicodeEx and is safe to use
/// during WM_SYSKEYDOWN/UP where ToUnicodeEx would corrupt keyboard state.
/// </summary>
/// <param name="virtualKey">The Windows virtual-key.</param>
/// <returns>A key symbol, or null if none matched.</returns>
public static string? GetKeySymbolFromVirtualKey(int virtualKey)
{
var ch = MapVirtualKey((uint)virtualKey, (uint)MapVirtualKeyMapTypes.MAPVK_VK_TO_CHAR);
if (ch == 0)
return null;
// Bit 31 is set for dead keys — strip it to get the base character.
var c = (char)(ch & 0x7FFFFFFF);
return KeySymbolHelper.IsAllowedAsciiKeySymbol(c) ? c.ToString() : null;
}
/// <summary>
/// Gets a key symbol from a Windows virtual-key and key data.
/// </summary>

7
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -1446,8 +1446,11 @@ namespace Avalonia.Win32
var physicalKey = KeyInterop.PhysicalKeyFromVirtualKey(virtualKey, keyData);
// Avoid calling GetKeySymbol() for WM_SYSKEYDOWN/UP:
// it ultimately calls User32!ToUnicodeEx, which messes up the keyboard state in this case.
var keySymbol = useKeySymbol ? KeyInterop.GetKeySymbol(virtualKey, keyData) : null;
// it ultimately calls ToUnicodeEx, which corrupts keyboard state for system key events.
// Use MapVirtualKey-based fallback instead — it's layout-aware without touching keyboard state.
var keySymbol = useKeySymbol
? KeyInterop.GetKeySymbol(virtualKey, keyData)
: KeyInterop.GetKeySymbolFromVirtualKey(virtualKey);
if (key == Key.None && physicalKey == PhysicalKey.None && string.IsNullOrWhiteSpace(keySymbol))
return null;

27
tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs

@ -233,6 +233,33 @@ namespace Avalonia.Base.UnitTests.Input
}
}
[Fact]
public void Should_Raise_AccessKey_For_System_Key_Event_With_KeySymbol()
{
// Regression test for #20961: on Windows, WM_SYSKEYDOWN (Alt+key) previously
// left KeySymbol null, breaking access keys. MapVirtualKey now provides KeySymbol.
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var button = new Button();
var root = new TestRoot(button);
var target = new AccessKeyHandler();
var raised = 0;
KeyboardDevice.Instance?.SetFocusedElement(button, NavigationMethod.Unspecified, KeyModifiers.None);
target.SetOwner(root);
target.Register("F", button);
button.AddHandler(AccessKeyHandler.AccessKeyEvent, (s, e) => ++raised);
KeyDown(root, Key.LeftAlt);
Assert.Equal(0, raised);
// MapVirtualKey provides lowercase KeySymbol for system key events
KeyDown(root, Key.F, "f", KeyModifiers.Alt);
Assert.Equal(1, raised);
}
}
[Fact]
public void Should_Open_MainMenu_On_Alt_KeyUp()
{

Loading…
Cancel
Save