From 542a226cff1c42e24fcd18fc8de9a8840bc58c03 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 5 Nov 2024 19:08:24 +0200 Subject: [PATCH 1/3] fix(wasm): key tracking when no one is subscribing to Key --- src/Uno.UI/ts/WindowManager.ts | 17 ++++++++++++++++ .../UI/Core/Internal/KeyboardStateTracker.cs | 20 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI/ts/WindowManager.ts b/src/Uno.UI/ts/WindowManager.ts index 74d1303ff7d4..ebf2ed9409a1 100644 --- a/src/Uno.UI/ts/WindowManager.ts +++ b/src/Uno.UI/ts/WindowManager.ts @@ -133,6 +133,7 @@ namespace Uno.UI { private static dispatchSuspendingMethod: any; private static getDependencyPropertyValueMethod: any; private static setDependencyPropertyValueMethod: any; + private static keyTrackingMethod: any; private constructor(private containerElementId: string, private loadingElementId: string) { this.initDom(); @@ -1437,6 +1438,7 @@ namespace Uno.UI { WindowManager.dispatchEventMethod = exports.Microsoft.UI.Xaml.UIElement.DispatchEvent; WindowManager.focusInMethod = exports.Microsoft.UI.Xaml.Input.FocusManager.ReceiveFocusNative; WindowManager.dispatchSuspendingMethod = exports.Microsoft.UI.Xaml.Application.DispatchSuspending; + WindowManager.keyTrackingMethod = (globalThis).DotnetExports.Uno.Uno.UI.Core.KeyboardStateTracker.UpdateKeyStateNative; } else { throw `Unable to find dotnet exports`; } @@ -1451,6 +1453,13 @@ namespace Uno.UI { document.body.addEventListener("focusin", this.onfocusin); document.body.appendChild(this.containerElement); + // On WASM, if no one subscribes to key, not only will the event not fire on any UIElement, + // but the browser won't even notify us that a key was pressed/released, and this breaks KeyboardStateTracker + // key tracking, which depends on RaiseEvent being called even if no one is subscribing. Instead, we + // subscribe on the body and make sure to call KeyboardStateTracker ourselves here. + document.body.addEventListener("keydown", this.onBodyKeyDown); + document.body.addEventListener("keyup", this.onBodyKeyUp); + window.addEventListener("resize", x => WindowManager.resize()); window.addEventListener("contextmenu", x => { if (!(x.target instanceof HTMLInputElement) || @@ -1593,6 +1602,14 @@ namespace Uno.UI { public moveWindow(x: number, y: number) { window.moveTo(x, y); } + + private onBodyKeyDown(event: KeyboardEvent) { + WindowManager.keyTrackingMethod(event.key, true); + } + + private onBodyKeyUp(event: KeyboardEvent) { + WindowManager.keyTrackingMethod(event.key, false); + } } if (typeof define === "function") { diff --git a/src/Uno.UWP/UI/Core/Internal/KeyboardStateTracker.cs b/src/Uno.UWP/UI/Core/Internal/KeyboardStateTracker.cs index dcb72a3fc2f1..d6ed5e00d27f 100644 --- a/src/Uno.UWP/UI/Core/Internal/KeyboardStateTracker.cs +++ b/src/Uno.UWP/UI/Core/Internal/KeyboardStateTracker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices.JavaScript; using Windows.System; using Windows.UI.Core; @@ -13,7 +14,7 @@ namespace Uno.UI.Core; /// In UWP/WinUI, every key has a locked state (not only Caps Lock, etc.). The sequence of states is as follows: /// (None) -> (Down) -> (None) -> (Down + Locked) -> (None + Locked) -> (Down) -> (None) -> etc. /// -internal static class KeyboardStateTracker +internal static partial class KeyboardStateTracker { private static readonly Dictionary _keyStates = new Dictionary(); @@ -106,4 +107,21 @@ internal static void Reset() _keyStates.Clear(); } } + +#if __WASM__ +#pragma warning disable IDE0051 // Remove unused private members + [JSExport] + private static void UpdateKeyStateNative(string key, bool down) +#pragma warning restore IDE0051 // Remove unused private members + { + if (down) + { + OnKeyDown(VirtualKeyHelper.FromKey(key)); + } + else + { + OnKeyUp(VirtualKeyHelper.FromKey(key)); + } + } +#endif } From ae1d7c5dc1074c074a721c60708ac505dc422347 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Thu, 7 Nov 2024 17:59:44 +0200 Subject: [PATCH 2/3] test: add a manual test for wasm keyboard tracking --- .../UITests.Shared/UITests.Shared.projitems | 7 +++ .../Keyboard/Keyboard_Modifiers.xaml | 23 ++++++++ .../Keyboard/Keyboard_Modifiers.xaml.cs | 55 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index df5919797d12..df8f9534e1de 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -4254,6 +4254,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -8185,6 +8189,9 @@ Keyboard_Events.xaml + + Keyboard_Modifiers.xaml + Keyboard_iOS_Theme.xaml diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml new file mode 100644 index 000000000000..27e3eb28ed83 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs new file mode 100644 index 000000000000..18f101bc6b04 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using Windows.System; +using Windows.UI.Core; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Uno.UI.Samples.Controls; + +namespace UITests.Windows_UI_Xaml_Input.Keyboard +{ + [Sample("Keyboard", IsManualTest = true)] + public sealed partial class Keyboard_Modifiers : Page + { + private DispatcherTimer _timer; + + public Keyboard_Modifiers() + { + this.InitializeComponent(); +#if HAS_UNO + _timer = new DispatcherTimer(); + _timer.Interval = TimeSpan.FromMilliseconds(100); + _timer.Tick += (_, _) => + { + var mods = PlatformHelpers.GetKeyboardModifiers(); + var modString = ""; + if (mods.HasFlag(VirtualKeyModifiers.Shift)) + { + modString += " Shift"; + } + if (mods.HasFlag(VirtualKeyModifiers.Control)) + { + modString += " Ctrl"; + } + if (mods.HasFlag(VirtualKeyModifiers.Windows)) + { + modString += " Win"; + } + if (mods.HasFlag(VirtualKeyModifiers.Menu)) + { + modString += " Menu"; + } + + if (string.IsNullOrEmpty(modString)) + { + modString = "None"; + } + statusTb.Text = $"Modifiers pressed: {modString}"; + }; + + Loaded += (_, _) => _timer.Start(); + Unloaded += (_, _) => _timer.Stop(); +#endif + } + } +} From acd661b5eb123fa8545cd28d82c187920e09a616 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Tue, 19 Nov 2024 20:24:06 +0200 Subject: [PATCH 3/3] chore: build errors --- .../Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs index 18f101bc6b04..b2cb1e4249e5 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Input/Keyboard/Keyboard_Modifiers.xaml.cs @@ -11,7 +11,9 @@ namespace UITests.Windows_UI_Xaml_Input.Keyboard [Sample("Keyboard", IsManualTest = true)] public sealed partial class Keyboard_Modifiers : Page { +#if HAS_UNO private DispatcherTimer _timer; +#endif public Keyboard_Modifiers() {