From 9aeb0c8c8c0f860114958d7a4459ef643f8bae3e Mon Sep 17 00:00:00 2001 From: Babib3l Date: Sun, 22 Mar 2026 01:05:17 +0100 Subject: [PATCH] Fallback to keyboard on controller disconnect --- src/Ryujinx.Input/HLE/NpadManager.cs | 101 ++++++++++++++++-- .../UI/ViewModels/Input/InputViewModel.cs | 39 ++++++- 2 files changed, 128 insertions(+), 12 deletions(-) diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index f2936aa72..73a64f313 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -36,6 +36,7 @@ namespace Ryujinx.Input.HLE private bool _isDisposed; private List _inputConfig; + private List _requestedInputConfig; private bool _enableKeyboard; private bool _enableMouse; private Switch _device; @@ -52,6 +53,7 @@ namespace Ryujinx.Input.HLE _gamepadDriver = gamepadDriver; _mouseDriver = mouseDriver; _inputConfig = []; + _requestedInputConfig = []; _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; @@ -89,14 +91,14 @@ namespace Ryujinx.Input.HLE } } - ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse); } } private void HandleOnGamepadConnected(string id) { // Force input reload - ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -127,11 +129,13 @@ namespace Ryujinx.Input.HLE { lock (_lock) { + _requestedInputConfig = inputConfig?.ToList() ?? []; + NpadController[] oldControllers = _controllers.ToArray(); List validInputs = []; - foreach (InputConfig inputConfigEntry in inputConfig) + foreach (InputConfig inputConfigEntry in _requestedInputConfig) { NpadController controller; int index = (int)inputConfigEntry.PlayerIndex; @@ -147,7 +151,17 @@ namespace Ryujinx.Input.HLE controller = new(_cemuHookClient); } - bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); + InputConfig activeConfig = inputConfigEntry; + bool isValid = DriverConfigurationUpdate(ref controller, activeConfig); + + if (!isValid && + enableKeyboard && + inputConfigEntry is StandardControllerInputConfig && + TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig)) + { + activeConfig = fallbackConfig; + isValid = DriverConfigurationUpdate(ref controller, activeConfig); + } if (!isValid) { @@ -157,7 +171,7 @@ namespace Ryujinx.Input.HLE else { _controllers[index] = controller; - validInputs.Add(inputConfigEntry); + validInputs.Add(activeConfig); } } @@ -169,7 +183,7 @@ namespace Ryujinx.Input.HLE oldControllers[i] = null; } - _inputConfig = inputConfig; + _inputConfig = validInputs; _enableKeyboard = enableKeyboard; _enableMouse = enableMouse; @@ -177,6 +191,79 @@ namespace Ryujinx.Input.HLE } } + private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig) + { + fallbackConfig = null; + + ReadOnlySpan keyboardIds = _keyboardDriver.GamepadsIds; + + if (keyboardIds.IsEmpty) + { + return false; + } + + string keyboardId = keyboardIds[0]; + + using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId); + + if (keyboard == null) + { + return false; + } + + fallbackConfig = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = keyboardId, + Name = keyboard.Name, + PlayerIndex = inputConfig.PlayerIndex, + ControllerType = inputConfig.ControllerType, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = PhysicalKey.Up, + DpadDown = PhysicalKey.Down, + DpadLeft = PhysicalKey.Left, + DpadRight = PhysicalKey.Right, + ButtonMinus = PhysicalKey.Minus, + ButtonL = PhysicalKey.E, + ButtonZl = PhysicalKey.Q, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = PhysicalKey.W, + StickDown = PhysicalKey.S, + StickLeft = PhysicalKey.A, + StickRight = PhysicalKey.D, + StickButton = PhysicalKey.F, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = PhysicalKey.Z, + ButtonB = PhysicalKey.X, + ButtonX = PhysicalKey.C, + ButtonY = PhysicalKey.V, + ButtonPlus = PhysicalKey.Plus, + ButtonR = PhysicalKey.U, + ButtonZr = PhysicalKey.O, + ButtonSl = PhysicalKey.Unbound, + ButtonSr = PhysicalKey.Unbound, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = PhysicalKey.I, + StickDown = PhysicalKey.K, + StickLeft = PhysicalKey.J, + StickRight = PhysicalKey.L, + StickButton = PhysicalKey.H, + }, + }; + + return true; + } + public void UnblockInputUpdates() { lock (_lock) @@ -334,7 +421,7 @@ namespace Ryujinx.Input.HLE } } - internal InputConfig GetPlayerInputConfigByIndex(int index) + public InputConfig GetPlayerInputConfigByIndex(int index) { lock (_lock) { diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index fcfc94aa6..7892cac08 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -348,15 +348,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private void LoadConfiguration(InputConfig inputConfig = null) { + InputConfig persistedConfig; + if (UseGlobalConfig && Program.UseExtraConfig) { - Config = inputConfig ?? ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); + persistedConfig = ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); } else { - Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); + persistedConfig = ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); } + Config = inputConfig ?? GetDisplayedInputConfig(persistedConfig); + if (Config is StandardKeyboardInputConfig keyboardInputConfig) { ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick); @@ -368,6 +372,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input } } + private InputConfig GetDisplayedInputConfig(InputConfig persistedConfig) + { + if (persistedConfig is not StandardControllerInputConfig) + { + return persistedConfig; + } + + InputConfig activeConfig = _mainWindow?.ViewModel.AppHost?.NpadManager?.GetPlayerInputConfigByIndex((int)_playerId); + + return activeConfig is StandardKeyboardInputConfig ? activeConfig : persistedConfig; + } + private void FindPairedDeviceInConfigFile() { // This function allows you to output a message about the device configuration found in the file @@ -475,11 +491,24 @@ namespace Ryujinx.Ava.UI.ViewModels.Input { _isChangeTrackingActive = false; // Disable configuration change tracking + bool shouldApplyKeyboardFallback = Config is StandardControllerInputConfig controllerConfig && controllerConfig.Id == id; + LoadDevices(); - IsModified = true; - RevertChanges(); - FindPairedDeviceInConfigFile(); + if (shouldApplyKeyboardFallback) + { + LoadConfiguration(); + LoadDevice(); + NotificationIsVisible = false; + IsModified = false; + NotifyChanges(); + } + else + { + IsModified = true; + RevertChanges(); + FindPairedDeviceInConfigFile(); + } _isChangeTrackingActive = true; // Enable configuration change tracking