From 7becde9d8e33e1b4c85017d671bf53295c9d9f22 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sat, 21 Mar 2026 22:45:19 +0100 Subject: [PATCH] Fix settings keyboard input focus loss --- src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 34 +++++++++++--- .../UI/ViewModels/Input/InputViewModel.cs | 47 +++++++++++++++++-- src/Ryujinx/UI/Views/Input/InputView.axaml.cs | 9 ++++ 3 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index 7010e14b1..0bdfd8755 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Ava.Input private readonly Control _control; private readonly Dictionary _semanticPressedKeys; private readonly Dictionary _physicalPressedKeys; + private readonly Dictionary _observedPhysicalKeysBySemanticKey; private readonly KeyboardInputMode _defaultMode; public event EventHandler KeyPressed; @@ -29,6 +30,7 @@ namespace Ryujinx.Ava.Input _control = control; _semanticPressedKeys = []; _physicalPressedKeys = []; + _observedPhysicalKeysBySemanticKey = []; _defaultMode = defaultMode; _control.KeyDown += OnKeyPress; @@ -192,17 +194,37 @@ namespace Ryujinx.Ava.Input private void UpdateKeyStates(KeyEventArgs args, bool isPressed) { - UpdateKeyState(_semanticPressedKeys, AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key), isPressed); - UpdateKeyState(_physicalPressedKeys, GetPhysicalInputKey(args), isPressed); + Key semanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.Key); + Key resolvedSemanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key); + ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey); + + UpdateKeyState(_semanticPressedKeys, resolvedSemanticKey, isPressed); + UpdateKeyState(_physicalPressedKeys, physicalKey, isPressed); + + if (isPressed && + semanticKey is not Key.Unknown and not Key.Unbound && + physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound) + { + _observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey; + } } - private static ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args) + private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey) { Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey); - return key is >= Key.Unknown and < Key.Count - ? (ConfigPhysicalKey)(int)key - : ConfigPhysicalKey.Unknown; + if (key is >= Key.Unknown and < Key.Count) + { + return (ConfigPhysicalKey)(int)key; + } + + if (semanticKey is not Key.Unknown and not Key.Unbound && + _observedPhysicalKeysBySemanticKey.TryGetValue(semanticKey, out ConfigPhysicalKey observedPhysicalKey)) + { + return observedPhysicalKey; + } + + return ConfigPhysicalKey.Unknown; } public void Dispose() diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index 30a1a94d7..1f3512315 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -43,6 +43,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private const string KeyboardString = "keyboard"; private const string ControllerString = "controller"; private readonly MainWindow _mainWindow; + private Control _keyboardDriverControl; private PlayerIndex _playerId; private PlayerIndex _playerIdChoose; @@ -66,7 +67,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public IGamepadDriver AvaloniaKeyboardDriver { get; } + public IGamepadDriver AvaloniaKeyboardDriver { get; private set; } public IGamepad SelectedGamepad { @@ -291,9 +292,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _mainWindow = RyujinxApp.MainWindow; - AvaloniaKeyboardDriver keyboardDriver = new(owner, KeyboardInputMode.Physical); - keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; - AvaloniaKeyboardDriver = keyboardDriver; + ReplaceKeyboardDriver(owner); PhysicalKeyLabelHelper.LabelsChanged += OnPhysicalKeyLabelsChanged; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; @@ -313,6 +312,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _isChangeTrackingActive = true; } + public void RetargetKeyboardDriver(Control owner) + { + if (!Program.PreviewerDetached) + { + return; + } + + ReplaceKeyboardDriver(owner); + } + public InputViewModel() { PlayerIndexes = []; @@ -1096,6 +1105,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } + private void ReplaceKeyboardDriver(Control owner) + { + Control target = TopLevel.GetTopLevel(owner) as Control ?? owner; + + if (ReferenceEquals(_keyboardDriverControl, target)) + { + return; + } + + if (AvaloniaKeyboardDriver is AvaloniaKeyboardDriver oldKeyboardDriver) + { + oldKeyboardDriver.KeyPressed -= PhysicalKeyLabelHelper.ObserveKeyPress; + oldKeyboardDriver.Dispose(); + } + + _keyboardDriverControl = target; + + AvaloniaKeyboardDriver keyboardDriver = new(target, KeyboardInputMode.Physical); + keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress; + AvaloniaKeyboardDriver = keyboardDriver; + + if (_isLoaded && Device > 0 && Device < Devices.Count && Devices[Device].Type == DeviceType.Keyboard) + { + SelectedGamepad?.Dispose(); + LoadInputDriver(); + } + } + public void Dispose() { GC.SuppressFinalize(this); @@ -1110,7 +1147,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input SelectedGamepad?.Dispose(); - AvaloniaKeyboardDriver.Dispose(); + AvaloniaKeyboardDriver?.Dispose(); } } } diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs index f8ba04f5d..391feffaf 100644 --- a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Systems.Configuration; @@ -20,6 +21,13 @@ namespace Ryujinx.Ava.UI.Views.Input InitializeComponent(); } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + ViewModel?.RetargetKeyboardDriver(this); + } + public void SaveCurrentProfile() { ViewModel.Save(); @@ -30,6 +38,7 @@ namespace Ryujinx.Ava.UI.Views.Input Dispose(); ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs InitializeComponent(); + ViewModel.RetargetKeyboardDriver(this); } private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)