Skip to content

fix(macOS): ignore Option/Alt and Command/Win keys as standalone input in TextBox#22726

Open
Copilot wants to merge 9 commits intomasterfrom
copilot/sub-pr-22014
Open

fix(macOS): ignore Option/Alt and Command/Win keys as standalone input in TextBox#22726
Copilot wants to merge 9 commits intomasterfrom
copilot/sub-pr-22014

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 24, 2026

On macOS (Skia), pressing Option or Command alone would insert symbols into a focused TextBox instead of being treated as a pure modifier.

What is the current behavior? 🤔

VirtualKey.Menu (Alt/Option) and VirtualKey.LeftWindows/VirtualKey.RightWindows (Command) pressed standalone produce unwanted character insertion in TextBox.

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. KeyDown events 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.RightMenu
  • VirtualKey.LeftWindows, VirtualKey.RightWindows

Runtime test When_Alt_Or_Win_Key_Alone covers all five keys, asserting both that Text is unchanged and that KeyDown events still propagate.

PR Checklist ✅

Please check if your PR fulfills the following requirements:

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.

spouliot and others added 8 commits January 21, 2026 13:49
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>
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 24, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
7 out of 8 committers have signed the CLA.

✅ morning4coffe-dev
✅ MartinZikmund
✅ vatsashah45
✅ ramezgerges
✅ clairernovotny
✅ Xiaoy312
✅ ajpinedam
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI changed the title [WIP] Fix macOS to ignore option and command as standalone keys fix(macOS): ignore Option/Alt and Command/Win keys as standalone input in TextBox Feb 24, 2026
Copilot AI requested a review from spouliot February 24, 2026 22:23
@github-actions github-actions bot added platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform platform/android 🤖 Categorizes an issue or PR as relevant to the Android platform platform/macos 🍏 Categorizes an issue or PR as relevant to the macOS platform platform/ios 🍎 Categorizes an issue or PR as relevant to the iOS platform area/skia ✏️ Categorizes an issue or PR as relevant to Skia area/code-generation Categorizes an issue or PR as relevant to code generation area/build Categorizes an issue or PR as relevant to build infrastructure area/automation Categorizes an issue or PR as relevant to project automation kind/documentation area/sdk Categorizes an issue or PR as relevant to the Uno.Sdk platform/x11 🐧 Categorizes an issue or PR as relevant to X11 labels Feb 24, 2026
@ajpinedam ajpinedam force-pushed the dev/spouliot/gh21925 branch from 6ca8b24 to 712668a Compare March 17, 2026 15:14
Base automatically changed from dev/spouliot/gh21925 to master March 17, 2026 22:52
@MartinZikmund MartinZikmund marked this pull request as ready for review March 28, 2026 07:57
Copilot AI review requested due to automatic review settings March 28, 2026 07:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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/RightMenu and VirtualKey.LeftWindows/RightWindows as standalone no-ops in TextBox.skia.cs (suppress text insertion).
  • Add a Skia runtime test to assert standalone Alt/Windows keys don’t change Text and that KeyDown still 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.

Comment on lines +464 to +489
// 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);
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
// 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);
}

Copilot uses AI. Check for mistakes.
Comment on lines +465 to +486
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'));
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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'));

Copilot uses AI. Check for mistakes.
@unodevops
Copy link
Copy Markdown
Contributor

🤖 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

@unodevops
Copy link
Copy Markdown
Contributor

⚠️⚠️ The build 204260 has failed on Uno.UI - CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/automation Categorizes an issue or PR as relevant to project automation area/build Categorizes an issue or PR as relevant to build infrastructure area/code-generation Categorizes an issue or PR as relevant to code generation area/sdk Categorizes an issue or PR as relevant to the Uno.Sdk area/skia ✏️ Categorizes an issue or PR as relevant to Skia kind/documentation platform/android 🤖 Categorizes an issue or PR as relevant to the Android platform platform/ios 🍎 Categorizes an issue or PR as relevant to the iOS platform platform/macos 🍏 Categorizes an issue or PR as relevant to the macOS platform platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform platform/x11 🐧 Categorizes an issue or PR as relevant to X11

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants