diff --git a/assets/Locales/KeyboardLayout.json b/assets/Locales/KeyboardLayout.json index 3020c6656..86d9772c1 100644 --- a/assets/Locales/KeyboardLayout.json +++ b/assets/Locales/KeyboardLayout.json @@ -1608,7 +1608,7 @@ "el_GR": "", "en_US": "`", "es_ES": "ยบ", - "fr_FR": null, + "fr_FR": "<", "he_IL": "", "it_IT": "", "ja_JP": "", @@ -1858,7 +1858,7 @@ "el_GR": "", "en_US": "\\", "es_ES": "<", - "fr_FR": "<", + "fr_FR": "*", "he_IL": "", "it_IT": "<", "ja_JP": "", diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 95287954a..c7a5bd395 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -4,7 +4,6 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Input; using System; using System.Collections.Generic; -using AvaKey = Avalonia.Input.Key; using Key = Ryujinx.Input.Key; namespace Ryujinx.Ava.Input @@ -13,7 +12,7 @@ namespace Ryujinx.Ava.Input { private static readonly string[] _keyboardIdentifers = ["0"]; private readonly Control _control; - private readonly HashSet _pressedKeys; + private readonly Dictionary _pressedKeys; public event EventHandler KeyPressed; public event EventHandler KeyRelease; @@ -65,21 +64,48 @@ namespace Ryujinx.Ava.Input { if (disposing) { - _control.KeyUp -= OnKeyPress; - _control.KeyDown -= OnKeyRelease; + _control.KeyDown -= OnKeyPress; + _control.KeyUp -= OnKeyRelease; } } protected void OnKeyPress(object sender, KeyEventArgs args) { - _pressedKeys.Add(args.Key); + Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + + if (key != Key.Unknown) + { + if (_pressedKeys.TryGetValue(key, out int count)) + { + _pressedKeys[key] = count + 1; + } + else + { + _pressedKeys[key] = 1; + } + } KeyPressed?.Invoke(this, args); } protected void OnKeyRelease(object sender, KeyEventArgs args) { - _pressedKeys.Remove(args.Key); + Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + + if (key != Key.Unknown) + { + if (_pressedKeys.TryGetValue(key, out int count)) + { + if (count <= 1) + { + _pressedKeys.Remove(key); + } + else + { + _pressedKeys[key] = count - 1; + } + } + } KeyRelease?.Invoke(this, args); } @@ -91,9 +117,7 @@ namespace Ryujinx.Ava.Input return false; } - AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey); - - return _pressedKeys.Contains(nativeKey); + return _pressedKeys.ContainsKey(key); } public void Clear() diff --git a/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs index 4aa8692dd..b95b8c56f 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs @@ -2,6 +2,7 @@ using Ryujinx.Input; using System; using System.Collections.Generic; using AvaKey = Avalonia.Input.Key; +using AvaPhysicalKey = Avalonia.Input.PhysicalKey; namespace Ryujinx.Ava.Input { @@ -132,7 +133,8 @@ namespace Ryujinx.Ava.Input AvaKey.D8, AvaKey.D9, AvaKey.OemTilde, - AvaKey.OemTilde,AvaKey.OemMinus, + AvaKey.Oem102, + AvaKey.OemMinus, AvaKey.OemPlus, AvaKey.OemOpenBrackets, AvaKey.OemCloseBrackets, @@ -147,7 +149,149 @@ namespace Ryujinx.Ava.Input AvaKey.None ]; + private static readonly AvaPhysicalKey[] _physicalKeyMapping = + [ + // NOTE: Invalid + AvaPhysicalKey.None, + + AvaPhysicalKey.ShiftLeft, + AvaPhysicalKey.ShiftRight, + AvaPhysicalKey.ControlLeft, + AvaPhysicalKey.ControlRight, + AvaPhysicalKey.AltLeft, + AvaPhysicalKey.AltRight, + AvaPhysicalKey.MetaLeft, + AvaPhysicalKey.MetaRight, + AvaPhysicalKey.ContextMenu, + AvaPhysicalKey.F1, + AvaPhysicalKey.F2, + AvaPhysicalKey.F3, + AvaPhysicalKey.F4, + AvaPhysicalKey.F5, + AvaPhysicalKey.F6, + AvaPhysicalKey.F7, + AvaPhysicalKey.F8, + AvaPhysicalKey.F9, + AvaPhysicalKey.F10, + AvaPhysicalKey.F11, + AvaPhysicalKey.F12, + AvaPhysicalKey.F13, + AvaPhysicalKey.F14, + AvaPhysicalKey.F15, + AvaPhysicalKey.F16, + AvaPhysicalKey.F17, + AvaPhysicalKey.F18, + AvaPhysicalKey.F19, + AvaPhysicalKey.F20, + AvaPhysicalKey.F21, + AvaPhysicalKey.F22, + AvaPhysicalKey.F23, + AvaPhysicalKey.F24, + + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + AvaPhysicalKey.None, + + AvaPhysicalKey.ArrowUp, + AvaPhysicalKey.ArrowDown, + AvaPhysicalKey.ArrowLeft, + AvaPhysicalKey.ArrowRight, + AvaPhysicalKey.Enter, + AvaPhysicalKey.Escape, + AvaPhysicalKey.Space, + AvaPhysicalKey.Tab, + AvaPhysicalKey.Backspace, + AvaPhysicalKey.Insert, + AvaPhysicalKey.Delete, + AvaPhysicalKey.PageUp, + AvaPhysicalKey.PageDown, + AvaPhysicalKey.Home, + AvaPhysicalKey.End, + AvaPhysicalKey.CapsLock, + AvaPhysicalKey.ScrollLock, + AvaPhysicalKey.PrintScreen, + AvaPhysicalKey.Pause, + AvaPhysicalKey.NumLock, + AvaPhysicalKey.NumPadClear, + AvaPhysicalKey.NumPad0, + AvaPhysicalKey.NumPad1, + AvaPhysicalKey.NumPad2, + AvaPhysicalKey.NumPad3, + AvaPhysicalKey.NumPad4, + AvaPhysicalKey.NumPad5, + AvaPhysicalKey.NumPad6, + AvaPhysicalKey.NumPad7, + AvaPhysicalKey.NumPad8, + AvaPhysicalKey.NumPad9, + AvaPhysicalKey.NumPadDivide, + AvaPhysicalKey.NumPadMultiply, + AvaPhysicalKey.NumPadSubtract, + AvaPhysicalKey.NumPadAdd, + AvaPhysicalKey.NumPadDecimal, + AvaPhysicalKey.NumPadEnter, + AvaPhysicalKey.A, + AvaPhysicalKey.B, + AvaPhysicalKey.C, + AvaPhysicalKey.D, + AvaPhysicalKey.E, + AvaPhysicalKey.F, + AvaPhysicalKey.G, + AvaPhysicalKey.H, + AvaPhysicalKey.I, + AvaPhysicalKey.J, + AvaPhysicalKey.K, + AvaPhysicalKey.L, + AvaPhysicalKey.M, + AvaPhysicalKey.N, + AvaPhysicalKey.O, + AvaPhysicalKey.P, + AvaPhysicalKey.Q, + AvaPhysicalKey.R, + AvaPhysicalKey.S, + AvaPhysicalKey.T, + AvaPhysicalKey.U, + AvaPhysicalKey.V, + AvaPhysicalKey.W, + AvaPhysicalKey.X, + AvaPhysicalKey.Y, + AvaPhysicalKey.Z, + AvaPhysicalKey.Digit0, + AvaPhysicalKey.Digit1, + AvaPhysicalKey.Digit2, + AvaPhysicalKey.Digit3, + AvaPhysicalKey.Digit4, + AvaPhysicalKey.Digit5, + AvaPhysicalKey.Digit6, + AvaPhysicalKey.Digit7, + AvaPhysicalKey.Digit8, + AvaPhysicalKey.Digit9, + AvaPhysicalKey.Backquote, + AvaPhysicalKey.IntlBackslash, + AvaPhysicalKey.Minus, + AvaPhysicalKey.Equal, + AvaPhysicalKey.BracketLeft, + AvaPhysicalKey.BracketRight, + AvaPhysicalKey.Semicolon, + AvaPhysicalKey.Quote, + AvaPhysicalKey.Comma, + AvaPhysicalKey.Period, + AvaPhysicalKey.Slash, + AvaPhysicalKey.Backslash, + + // NOTE: invalid + AvaPhysicalKey.None + ]; + private static readonly Dictionary _avaKeyMapping; + private static readonly Dictionary _avaPhysicalKeyMapping; static AvaloniaKeyboardMappingHelper() { @@ -155,21 +299,42 @@ namespace Ryujinx.Ava.Input // NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array. _avaKeyMapping = new Dictionary(); + _avaPhysicalKeyMapping = new Dictionary(); foreach (Key key in inputKeys) { - if (TryGetAvaKey(key, out AvaKey index)) + if (TryGetAvaKey(key, out AvaKey avaKey)) { - _avaKeyMapping[index] = key; + _avaKeyMapping[avaKey] = key; + } + + if (TryGetAvaPhysicalKey(key, out AvaPhysicalKey avaPhysicalKey)) + { + _avaPhysicalKeyMapping[avaPhysicalKey] = key; } } + + // Alias additional Avalonia key values to improve non-US layout support. + _avaKeyMapping[AvaKey.Oem1] = Key.Semicolon; + _avaKeyMapping[AvaKey.Oem2] = Key.Slash; + _avaKeyMapping[AvaKey.Oem3] = Key.Tilde; + _avaKeyMapping[AvaKey.Oem4] = Key.BracketLeft; + _avaKeyMapping[AvaKey.Oem5] = Key.BackSlash; + _avaKeyMapping[AvaKey.Oem6] = Key.BracketRight; + _avaKeyMapping[AvaKey.Oem7] = Key.Quote; + _avaKeyMapping[AvaKey.OemBackslash] = Key.Grave; + _avaKeyMapping[AvaKey.Oem102] = Key.Grave; + + // Common alternates for non-US/JIS physical keys. + _avaPhysicalKeyMapping[AvaPhysicalKey.IntlRo] = Key.BackSlash; + _avaPhysicalKeyMapping[AvaPhysicalKey.IntlYen] = Key.BackSlash; } public static bool TryGetAvaKey(Key key, out AvaKey avaKey) { avaKey = AvaKey.None; - bool keyExist = (int)key < _keyMapping.Length; + bool keyExist = key < Key.Count && (int)key < _keyMapping.Length; if (keyExist) { avaKey = _keyMapping[(int)key]; @@ -178,9 +343,34 @@ namespace Ryujinx.Ava.Input return keyExist; } + public static bool TryGetAvaPhysicalKey(Key key, out AvaPhysicalKey avaPhysicalKey) + { + avaPhysicalKey = AvaPhysicalKey.None; + + bool keyExist = key < Key.Count && (int)key < _physicalKeyMapping.Length; + if (keyExist) + { + avaPhysicalKey = _physicalKeyMapping[(int)key]; + } + + return keyExist; + } + public static Key ToInputKey(AvaKey key) { return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown); } + + public static Key ToInputKey(AvaPhysicalKey key) + { + return _avaPhysicalKeyMapping.GetValueOrDefault(key, Key.Unknown); + } + + public static Key ToInputKey(AvaPhysicalKey physicalKey, AvaKey key) + { + Key inputKey = ToInputKey(key); + + return inputKey != Key.Unknown ? inputKey : ToInputKey(physicalKey); + } } } diff --git a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs index 397eab72c..340af15e4 100644 --- a/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.Applet private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) { - HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key); if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) { @@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Applet private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) { - HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key); if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) {