fix(skia): Support Windows Alt codes in TextBox via CharacterReceived - #23901
fix(skia): Support Windows Alt codes in TextBox via CharacterReceived#23901ramezgerges wants to merge 7 commits into
Conversation
Implements the CharacterReceived routed event and the matching Control.OnCharacterReceived virtual on Skia targets. Characters produced by a key press are raised after KeyDown from InputManager (mirroring the WM_KEYDOWN → WM_CHAR ordering); characters composed on a key release — Windows Alt+numpad codes, which TranslateMessage synthesizes when Alt is released — are delivered by the Win32 host through a new IUnoKeyboardInputSource.CharacterReceived channel with KeyStatus.IsKeyReleased set, mirroring the real WM_CHAR lParam transition bit. Windows.UI.Core.CharacterReceivedEventArgs is implemented as the channel's payload. Also fixes the browser keyboard debug log (missing alt modifier and a key=$ interpolation typo). Part of unoplatform#22254. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An Alt+numpad code's character arrives with the Alt key release, so the KeyDown-based insertion path never sees it and the character was silently dropped on Win32. TextBox now inserts key-release characters from CharacterReceived; the path doesn't depend on an IME session, so this also fixes Alt codes in PasswordBox. Fixes unoplatform#22254 for the Skia Desktop (Windows) target; the WASM half was fixed by aa9f15b. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the typing path (CharacterReceived raised once, after KeyDown), key-release insertion incl. selection replacement, read-only and key-press no-ops, PasswordBox, direct IME commits, and a Win32-only end-to-end test posting the real WM_KEYUP(VK_MENU)+WM_CHAR pair to the app's own HWND, asserting no composition events are raised. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes Skia Desktop (Win32) TextBox input so Windows Alt+numpad composed characters are delivered and inserted correctly by implementing UIElement.CharacterReceived as a real routed event on Skia and wiring a Win32-only keyboard-input channel for characters that arrive on key release (e.g., Alt+0154 → š). This aligns the Skia input pipeline more closely with Win32’s WM_KEYDOWN → WM_CHAR ordering and avoids losing characters that are synthesized during Alt key-up.
Changes:
- Implement
UIElement.CharacterReceivedrouted event on Skia, plusControl.OnCharacterReceivedoverride plumbing and generator support. - Deliver Win32 key-release composed characters via
IUnoKeyboardInputSource.CharacterReceivedand handle them inTextBoxinsertion logic. - Add runtime tests covering key-press ordering, key-release insertion behavior (including selection replacement), read-only behavior, and a Win32 end-to-end
PostMessagepath; includes a small browser keyboard debug-log fix.
Reviewed changes
Copilot reviewed 18 out of 21 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Uno.UWP/UI/Core/Internal/IUnoKeyboardInputSource.cs | Adds CharacterReceived to the internal keyboard input source contract. |
| src/Uno.UWP/UI/Core/CharacterReceivedEventArgs.cs | Introduces a real CharacterReceivedEventArgs payload type (Skia path). |
| src/Uno.UWP/Generated/3.0.0.0/Windows.UI.Core/CharacterReceivedEventArgs.cs | Removes generated [NotImplemented] stubs in favor of the real implementation. |
| src/Uno.UI/UI/Xaml/UIElement.RoutedEvents.cs | Declares CharacterReceivedEvent and exposes UIElement.CharacterReceived event; adds invoke support. |
| src/Uno.UI/UI/Xaml/RoutedEventFlag.cs | Enables RoutedEventFlag.CharacterReceived and keeps it out of _isKey. |
| src/Uno.UI/UI/Xaml/Internal/InputManager.Keyboard.skia.cs | Raises CharacterReceived after KeyDown (key press) and from the new key-release channel. |
| src/Uno.UI/UI/Xaml/Input/CharacterReceivedRoutedEventArgs.cs | Adds an internal ctor that supports setting OriginalSource for raised events/tests. |
| src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs | Inserts key-release composed characters via OnCharacterReceived (Alt codes). |
| src/Uno.UI/UI/Xaml/Controls/Control/Control.cs | Adds OnCharacterReceived virtual and class-handler subscription via implemented routed events. |
| src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/UIElement.cs | Removes generated [NotImplemented] CharacterReceived members due to real declarations. |
| src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml.Controls/Control.cs | Removes generated [NotImplemented] OnCharacterReceived stub due to real override point. |
| src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.skia.cs | Adds runtime test coverage for direct commits and Win32 Alt-code delivery behavior. |
| src/Uno.UI.Runtime.Skia.X11/Devices/Input/X11KeyboardInputSource.cs | Implements the new interface event as a no-op for X11. |
| src/Uno.UI.Runtime.Skia.Win32/Devices/Input/Win32WindowWrapper.Keyboard.cs | Delivers key-release composed characters via CharacterReceived instead of attaching them to KeyUp. |
| src/Uno.UI.Runtime.Skia.WebAssembly.Browser/Devices/Input/BrowserKeyboardInputSource.cs | Implements the new interface event as a no-op for browser Skia and fixes keyboard debug log. |
| src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Window/MacOSWindowHost.cs | Implements the new interface event as a no-op for macOS Skia. |
| src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/Devices/Input/FrameBufferKeyboardInputSource.cs | Implements the new interface event as a no-op for framebuffer. |
| src/Uno.UI.Runtime.Skia.AppleUIKit/Devices/Input/UnoKeyboardInputSource.cs | Implements the new interface event as a no-op for AppleUIKit Skia. |
| src/Uno.UI.Runtime.Skia.Android/Devices/Input/AndroidKeyboardInputSource.cs | Implements the new interface event as a no-op for Android Skia. |
| src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlConstants.cs | Adds CharacterReceivedRoutedEventArgs type constant for generators. |
| src/SourceGenerators/Uno.UI.SourceGenerators/ImplementedRoutedEvents/ImplementedRoutedEventsGenerator.cs | Registers OnCharacterReceived as an implemented routed event for Control override detection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The real WM_CHAR inherits the Alt key-up's lParam, whose previous-key-state bit is set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matches the convention of the sibling Windows.UI.Core partials (PointerEventArgs, KeyEventArgs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23901/wasm-skia-net9/index.html |
|
The build 224634 found UI Test snapshots differences: Details
|
The Skia-only override made the TextBox API surface diverge from the Reference assembly, failing the ReferenceImplComparer package check. The override now lives in the shared TextBox partial and delegates to a Skia-implemented partial method, keeping the surface identical across targets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23901/wasm-skia-net9/index.html |
|
The build 224727 found UI Test snapshots differences: Details
|
GitHub Issue: closes #22254
PR Type:
🐞 Bugfix
What changed? 🚀
On the Skia Desktop (Windows) target, Windows Alt codes (hold Alt, type numpad digits, release Alt — e.g. Alt+
0154→š) never inserted anything intoTextBox. The composedWM_CHARis synthesized byTranslateMessagewhile translating the Alt key-up, so it was attached to the KeyUpKeyEventArgs, which nothing consumes (TextBox inserts on KeyDown only) — the character was silently destroyed.This PR implements
UIElement.CharacterReceivedas a real routed event on Skia (it was a[NotImplemented]stub) and delivers Alt codes through it:UIElement.CharacterReceived+Control.OnCharacterReceivedare implemented following theContextRequestedprecedent (flag, routed event registration,InvokeHandlercase, generated-stub removal,ImplementedRoutedEventsGeneratorentry, Control class-handler wiring).KeyDownfromInputManager, matching theWM_KEYDOWN→WM_CHARordering on Windows.IUnoKeyboardInputSource.CharacterReceivedchannel, withKeyStatus.IsKeyReleasedset — mirroring the realWM_CHAR's lParam transition bit.Windows.UI.Core.CharacterReceivedEventArgsis implemented as the channel payload.TextBoxinserts key-release characters fromOnCharacterReceived. The path doesn't depend on an IME session, so Alt codes now also work inPasswordBox.TextCompositionStarted/Changed/Endeddo not fire for Alt codes (the end-to-end test asserts this).The WASM half of the issue (digits being typed alongside the character) was already fixed on master by aa9f15b and is unreleased; the composed-character delivery on WASM was verified working via a CDP-driven emulation of the exact browser event stream.
Also includes a one-line fix to the browser keyboard debug log (missing
altmodifier and akey=$interpolation typo).Validation (runtime): full
Given_TextBox+Given_ComboBoxsuites pass on Skia Desktop (263/263); new runtime tests cover the typing path (raised once, after KeyDown), key-release insertion incl. selection replacement, read-only/key-press no-ops, PasswordBox, and a Win32-only end-to-end test that posts the realWM_KEYUP(VK_MENU)+WM_CHARpair to the app's own HWND. Manually validated with physical keyboard Alt codes on Windows.Notes / known limitations:
CoreWindow.CharacterReceivedandInputKeyboardSource.CharacterReceivedremain unimplemented; native (non-Skia) targets declare the routed event but don't raise it yet.#if HAS_UNOtypeahead workaround (synthesizedCharacterReceived) is left in place and can migrate to the real event as a follow-up; its popup-child subscription now activates with the real event (no double-fire with the workaround — verified by the ComboBox suite).EnableHexNumpad) arrive as twoWM_CHARs and only the first is delivered — pre-existing limitation shared with the key-press path.PR Checklist ✅
Screenshots Compare Test Runresults.🤖 Generated with Claude Code