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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with
50 additions and
2 deletions
src/Windows/Avalonia.Win32/Input/KeyInterop.cs
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.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>
@ -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 ;
@ -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 ( )
{