fix(macOS): ignore Option/Alt and Command/Win keys as standalone input in TextBox#22726
fix(macOS): ignore Option/Alt and Command/Win keys as standalone input in TextBox#22726
Conversation
Co-authored-by: spouliot <260465+spouliot@users.noreply.github.com>
Co-authored-by: spouliot <260465+spouliot@users.noreply.github.com>
…one test Co-authored-by: MartinZikmund <1075116+MartinZikmund@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…lot/sub-pr-22014
|
|
6ca8b24 to
712668a
Compare
There was a problem hiding this comment.
Pull request overview
Fixes Skia TextBox text insertion when modifier keys (Option/Alt, Command/Windows) are pressed standalone, aligning behavior with other pure modifiers like Control while keeping KeyDown routing intact.
Changes:
- Treat
VirtualKey.Menu/LeftMenu/RightMenuandVirtualKey.LeftWindows/RightWindowsas standalone no-ops inTextBox.skia.cs(suppress text insertion). - Add a Skia runtime test to assert standalone Alt/Windows keys don’t change
Textand thatKeyDownstill fires.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs | Adds Alt/Windows virtual keys to the “standalone modifier no-op” switch to suppress character insertion. |
| src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.skia.cs | Adds a runtime test covering standalone Alt/Windows key behavior and KeyDown propagation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Test Option/Alt keys | ||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.Menu, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(1, keyDownCount); | ||
|
|
||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(2, keyDownCount); | ||
|
|
||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(3, keyDownCount); | ||
|
|
||
| // Test Command/Windows keys | ||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(4, keyDownCount); | ||
|
|
||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(5, keyDownCount); |
There was a problem hiding this comment.
When_Alt_Or_Win_Key_Alone repeats the same assert pattern for each key. Consider iterating over a small list of keys (and expected modifiers) to reduce duplication and make it easier to add/remove keys later.
| // Test Option/Alt keys | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.Menu, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(1, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(2, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(3, keyDownCount); | |
| // Test Command/Windows keys | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(4, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(5, keyDownCount); | |
| // Test Option/Alt and Command/Windows keys | |
| var keysToTest = new[] | |
| { | |
| VirtualKey.Menu, | |
| VirtualKey.LeftMenu, | |
| VirtualKey.RightMenu, | |
| VirtualKey.LeftWindows, | |
| VirtualKey.RightWindows | |
| }; | |
| for (var i = 0; i < keysToTest.Length; i++) | |
| { | |
| SUT.SafeRaiseEvent( | |
| UIElement.KeyDownEvent, | |
| new KeyRoutedEventArgs(SUT, keysToTest[i], VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(i + 1, keyDownCount); | |
| } |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.Menu, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(1, keyDownCount); | ||
|
|
||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(2, keyDownCount); | ||
|
|
||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(3, keyDownCount); | ||
|
|
||
| // Test Command/Windows keys | ||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | ||
| await WindowHelper.WaitForIdle(); | ||
| Assert.AreEqual("hello world", SUT.Text); | ||
| Assert.AreEqual(4, keyDownCount); | ||
|
|
||
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); |
There was a problem hiding this comment.
The test uses unicodeKey: '\0', but macOS Skia creates KeyEventArgs with unicode == 0 ? null : (char)unicode (see MacOSWindowHost.CreateArgs). That means a real standalone Option/Command press that produces unwanted insertion would have a non-zero unicode value, not \0. Consider using an arbitrary non-zero char (any visible symbol) to better match the regression being fixed, and set VirtualKeyModifiers.Menu/Windows to reflect the actual modifier flags for those keys on macOS.
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.Menu, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(1, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(2, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightMenu, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(3, keyDownCount); | |
| // Test Command/Windows keys | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(4, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightWindows, VirtualKeyModifiers.None, unicodeKey: '\0')); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.Menu, VirtualKeyModifiers.Menu, unicodeKey: 'x')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(1, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftMenu, VirtualKeyModifiers.Menu, unicodeKey: 'x')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(2, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightMenu, VirtualKeyModifiers.Menu, unicodeKey: 'x')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(3, keyDownCount); | |
| // Test Command/Windows keys | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.LeftWindows, VirtualKeyModifiers.Windows, unicodeKey: 'x')); | |
| await WindowHelper.WaitForIdle(); | |
| Assert.AreEqual("hello world", SUT.Text); | |
| Assert.AreEqual(4, keyDownCount); | |
| SUT.SafeRaiseEvent(UIElement.KeyDownEvent, new KeyRoutedEventArgs(SUT, VirtualKey.RightWindows, VirtualKeyModifiers.Windows, unicodeKey: 'x')); |
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22726/wasm-skia-net9/index.html |
|
|
On macOS (Skia), pressing Option or Command alone would insert symbols into a focused
TextBoxinstead of being treated as a pure modifier.What is the current behavior? 🤔
VirtualKey.Menu(Alt/Option) andVirtualKey.LeftWindows/VirtualKey.RightWindows(Command) pressed standalone produce unwanted character insertion inTextBox.What is the new behavior? 🚀
These keys are now no-ops when pressed alone (same treatment as
Control), while still functioning as modifiers in key combinations.KeyDownevents continue to fire normally — only text insertion is suppressed.Keys added to the no-op list in
TextBox.skia.cs:VirtualKey.Menu,VirtualKey.LeftMenu,VirtualKey.RightMenuVirtualKey.LeftWindows,VirtualKey.RightWindowsRuntime test
When_Alt_Or_Win_Key_Alonecovers all five keys, asserting both thatTextis unchanged and thatKeyDownevents still propagate.PR Checklist ✅
Please check if your PR fulfills the following requirements:
Screenshots Compare Test Runresults.Other information ℹ️
Follows up on the original fix in #22014. Test naming uses Windows API terminology (
Menu/Windows) rather than macOS-specific terms since Skia runs cross-platform.✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.