From dd26b6d232cd553d03535797073eb014032f7067 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 25 Sep 2023 00:46:59 +0200 Subject: [PATCH] Added PhysicalKey handling for Android (#13006) --- .../Specific/Helpers/AndroidKeyInterop.cs | 200 ++++++++++++++++++ .../Helpers/AndroidKeyboardEventsHelper.cs | 38 +++- src/Avalonia.Base/Input/KeySymbolHelper.cs | 2 +- 3 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyInterop.cs diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyInterop.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyInterop.cs new file mode 100644 index 0000000000..fc5ce3a71b --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyInterop.cs @@ -0,0 +1,200 @@ +#nullable enable + +using System.Collections.Generic; +using Avalonia.Input; + +namespace Avalonia.Android.Platform.Specific.Helpers; + +internal static class AndroidKeyInterop +{ + // evdev scan code to physical key map. + // https://github.com/chromium/chromium/blob/main/ui/events/keycodes/dom/dom_code_data.inc + // This list has the same order as the PhysicalKey enum.{ + private static readonly Dictionary s_physicalKeyFromScanCode = new(0xA2) + { + // Writing System Keys + { 0x29, PhysicalKey.Backquote }, + { 0x2B, PhysicalKey.Backslash }, + { 0x1A, PhysicalKey.BracketLeft }, + { 0x1B, PhysicalKey.BracketRight }, + { 0x33, PhysicalKey.Comma }, + { 0x0B, PhysicalKey.Digit0 }, + { 0x02, PhysicalKey.Digit1 }, + { 0x03, PhysicalKey.Digit2 }, + { 0x04, PhysicalKey.Digit3 }, + { 0x05, PhysicalKey.Digit4 }, + { 0x06, PhysicalKey.Digit5 }, + { 0x07, PhysicalKey.Digit6 }, + { 0x08, PhysicalKey.Digit7 }, + { 0x09, PhysicalKey.Digit8 }, + { 0x0A, PhysicalKey.Digit9 }, + { 0x0D, PhysicalKey.Equal }, + { 0x56, PhysicalKey.IntlBackslash }, + { 0x59, PhysicalKey.IntlRo }, + { 0x7C, PhysicalKey.IntlYen }, + { 0x1E, PhysicalKey.A }, + { 0x30, PhysicalKey.B }, + { 0x2E, PhysicalKey.C }, + { 0x20, PhysicalKey.D }, + { 0x12, PhysicalKey.E }, + { 0x21, PhysicalKey.F }, + { 0x22, PhysicalKey.G }, + { 0x23, PhysicalKey.H }, + { 0x17, PhysicalKey.I }, + { 0x24, PhysicalKey.J }, + { 0x25, PhysicalKey.K }, + { 0x26, PhysicalKey.L }, + { 0x32, PhysicalKey.M }, + { 0x31, PhysicalKey.N }, + { 0x18, PhysicalKey.O }, + { 0x19, PhysicalKey.P }, + { 0x10, PhysicalKey.Q }, + { 0x13, PhysicalKey.R }, + { 0x1F, PhysicalKey.S }, + { 0x14, PhysicalKey.T }, + { 0x16, PhysicalKey.U }, + { 0x2F, PhysicalKey.V }, + { 0x11, PhysicalKey.W }, + { 0x2D, PhysicalKey.X }, + { 0x15, PhysicalKey.Y }, + { 0x2C, PhysicalKey.Z }, + { 0x0C, PhysicalKey.Minus }, + { 0x34, PhysicalKey.Period }, + { 0x28, PhysicalKey.Quote }, + { 0x27, PhysicalKey.Semicolon }, + { 0x35, PhysicalKey.Slash }, + + // Functional Keys + { 0x38, PhysicalKey.AltLeft }, + { 0x64, PhysicalKey.AltRight }, + { 0x0E, PhysicalKey.Backspace }, + { 0x3A, PhysicalKey.CapsLock }, + { 0x7F, PhysicalKey.ContextMenu }, + { 0x1D, PhysicalKey.ControlLeft }, + { 0x61, PhysicalKey.ControlRight }, + { 0x1C, PhysicalKey.Enter }, + { 0x7D, PhysicalKey.MetaLeft }, + { 0x7E, PhysicalKey.MetaRight }, + { 0x2A, PhysicalKey.ShiftLeft }, + { 0x36, PhysicalKey.ShiftRight }, + { 0x39, PhysicalKey.Space }, + { 0x0F, PhysicalKey.Tab }, + { 0x5C, PhysicalKey.Convert }, + { 0x5D, PhysicalKey.KanaMode }, + { 0x7A, PhysicalKey.Lang1 }, + { 0x7B, PhysicalKey.Lang2 }, + { 0x5A, PhysicalKey.Lang3 }, + { 0x5B, PhysicalKey.Lang4 }, + { 0x55, PhysicalKey.Lang5 }, + { 0x5E, PhysicalKey.NonConvert }, + + // Control Pad Section + { 0x6F, PhysicalKey.Delete }, + { 0x6B, PhysicalKey.End }, + { 0x8A, PhysicalKey.Help }, + { 0x66, PhysicalKey.Home }, + { 0x6E, PhysicalKey.Insert }, + { 0x6D, PhysicalKey.PageDown }, + { 0x68, PhysicalKey.PageUp }, + + // Arrow Pad Section + { 0x6C, PhysicalKey.ArrowDown }, + { 0x69, PhysicalKey.ArrowLeft }, + { 0x6A, PhysicalKey.ArrowRight }, + { 0x67, PhysicalKey.ArrowUp }, + + // Numpad Section + { 0x45, PhysicalKey.NumLock }, + { 0x52, PhysicalKey.NumPad0 }, + { 0x4F, PhysicalKey.NumPad1 }, + { 0x50, PhysicalKey.NumPad2 }, + { 0x51, PhysicalKey.NumPad3 }, + { 0x4B, PhysicalKey.NumPad4 }, + { 0x4C, PhysicalKey.NumPad5 }, + { 0x4D, PhysicalKey.NumPad6 }, + { 0x47, PhysicalKey.NumPad7 }, + { 0x48, PhysicalKey.NumPad8 }, + { 0x49, PhysicalKey.NumPad9 }, + { 0x4E, PhysicalKey.NumPadAdd }, + //{ , PhysicalKey.NumPadClear }, + { 0x79, PhysicalKey.NumPadComma }, + { 0x53, PhysicalKey.NumPadDecimal }, + { 0x62, PhysicalKey.NumPadDivide }, + { 0x60, PhysicalKey.NumPadEnter }, + { 0x75, PhysicalKey.NumPadEqual }, + { 0x37, PhysicalKey.NumPadMultiply }, + { 0xB3, PhysicalKey.NumPadParenLeft }, + { 0xB4, PhysicalKey.NumPadParenRight }, + { 0x4A, PhysicalKey.NumPadSubtract }, + + // Function Section + { 0x01, PhysicalKey.Escape }, + { 0x3B, PhysicalKey.F1 }, + { 0x3C, PhysicalKey.F2 }, + { 0x3D, PhysicalKey.F3 }, + { 0x3E, PhysicalKey.F4 }, + { 0x3F, PhysicalKey.F5 }, + { 0x40, PhysicalKey.F6 }, + { 0x41, PhysicalKey.F7 }, + { 0x42, PhysicalKey.F8 }, + { 0x43, PhysicalKey.F9 }, + { 0x44, PhysicalKey.F10 }, + { 0x57, PhysicalKey.F11 }, + { 0x58, PhysicalKey.F12 }, + { 0xB7, PhysicalKey.F13 }, + { 0xB8, PhysicalKey.F14 }, + { 0xB9, PhysicalKey.F15 }, + { 0xBA, PhysicalKey.F16 }, + { 0xBB, PhysicalKey.F17 }, + { 0xBC, PhysicalKey.F18 }, + { 0xBD, PhysicalKey.F19 }, + { 0xBE, PhysicalKey.F20 }, + { 0xBF, PhysicalKey.F21 }, + { 0xC0, PhysicalKey.F22 }, + { 0xC1, PhysicalKey.F23 }, + { 0xC2, PhysicalKey.F24 }, + { 0x63, PhysicalKey.PrintScreen }, + { 0x46, PhysicalKey.ScrollLock }, + { 0x77, PhysicalKey.Pause }, + + // Media Keys + { 0x9E, PhysicalKey.BrowserBack }, + { 0x9C, PhysicalKey.BrowserFavorites }, + { 0x9F, PhysicalKey.BrowserForward }, + { 0xAC, PhysicalKey.BrowserHome }, + { 0xAD, PhysicalKey.BrowserRefresh }, + { 0xD9, PhysicalKey.BrowserSearch }, + { 0x80, PhysicalKey.BrowserStop }, + { 0xA1, PhysicalKey.Eject }, + { 0x90, PhysicalKey.LaunchApp1 }, + { 0x8C, PhysicalKey.LaunchApp2 }, + { 0x9B, PhysicalKey.LaunchMail }, + { 0xA4, PhysicalKey.MediaPlayPause }, + { 0xAB, PhysicalKey.MediaSelect }, + { 0xA6, PhysicalKey.MediaStop }, + { 0xA3, PhysicalKey.MediaTrackNext }, + { 0xA5, PhysicalKey.MediaTrackPrevious }, + { 0x74, PhysicalKey.Power }, + { 0x8E, PhysicalKey.Sleep }, + { 0x72, PhysicalKey.AudioVolumeDown }, + { 0x71, PhysicalKey.AudioVolumeMute }, + { 0x73, PhysicalKey.AudioVolumeUp }, + { 0x8F, PhysicalKey.WakeUp }, + + // Legacy Keys + { 0x81, PhysicalKey.Again }, + { 0x85, PhysicalKey.Copy }, + { 0x89, PhysicalKey.Cut }, + { 0x88, PhysicalKey.Find }, + { 0x86, PhysicalKey.Open }, + { 0x87, PhysicalKey.Paste }, + //{ , PhysicalKey.Props }, + { 0x84, PhysicalKey.Select }, + { 0x83, PhysicalKey.Undo } + }; + + public static PhysicalKey PhysicalKeyFromScanCode(int scanCode) + => scanCode is > 0 and <= 255 && s_physicalKeyFromScanCode.TryGetValue((byte)scanCode, out var result) ? + result : + PhysicalKey.None; +} diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 26940f467b..1dcd50d6c2 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using Android.Views; using Avalonia.Android.Platform.Input; @@ -30,11 +32,11 @@ namespace Avalonia.Android.Platform.Specific.Helpers return DispatchKeyEventInternal(e, out callBase); } - static string UnicodeTextInput(KeyEvent keyEvent) + static string? UnicodeTextInput(KeyEvent keyEvent) { return keyEvent.Action == KeyEventActions.Multiple && keyEvent.RepeatCount == 0 - && !string.IsNullOrEmpty(keyEvent?.Characters) + && !string.IsNullOrEmpty(keyEvent.Characters) ? keyEvent.Characters : null; } @@ -49,6 +51,9 @@ namespace Avalonia.Android.Platform.Specific.Helpers return null; } + var physicalKey = AndroidKeyInterop.PhysicalKeyFromScanCode(e.ScanCode); + var keySymbol = GetKeySymbol(e.UnicodeChar, physicalKey); + var rawKeyEvent = new RawKeyEventArgs( AndroidKeyboardDevice.Instance, Convert.ToUInt64(e.EventTime), @@ -56,8 +61,8 @@ namespace Avalonia.Android.Platform.Specific.Helpers e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e), - PhysicalKey.None, - e.DisplayLabel == '\0' ? null : new string(e.DisplayLabel, 1)); + physicalKey, + keySymbol); _view.Input(rawKeyEvent); @@ -94,6 +99,31 @@ namespace Avalonia.Android.Platform.Specific.Helpers return rv; } + private static string? GetKeySymbol(int unicodeChar, PhysicalKey physicalKey) + { + // Handle a very limited set of control characters so that we're consistent with other platforms + // (matches KeySymbolHelper.IsAllowedAsciiKeySymbol) + switch (physicalKey) + { + case PhysicalKey.Backspace: + return "\b"; + case PhysicalKey.Tab: + return "\t"; + case PhysicalKey.Enter: + case PhysicalKey.NumPadEnter: + return "\r"; + case PhysicalKey.Escape: + return "\u001B"; + default: + if (unicodeChar <= 0x7F) + { + var asciiChar = (char)unicodeChar; + return KeySymbolHelper.IsAllowedAsciiKeySymbol(asciiChar) ? asciiChar.ToString() : null; + } + return char.ConvertFromUtf32(unicodeChar); + } + } + public void Dispose() { HandleEvents = false; diff --git a/src/Avalonia.Base/Input/KeySymbolHelper.cs b/src/Avalonia.Base/Input/KeySymbolHelper.cs index 8a4bb227f8..5b87b2c2e9 100644 --- a/src/Avalonia.Base/Input/KeySymbolHelper.cs +++ b/src/Avalonia.Base/Input/KeySymbolHelper.cs @@ -18,7 +18,7 @@ internal static class KeySymbolHelper } } - if (c == 0x07) // delete + if (c == 0x7F) // delete return false; return true;