From 5b88a2dd89f707e97fd2e9e3e7414bf1cd43cef5 Mon Sep 17 00:00:00 2001 From: uncavo-hdmi Date: Sun, 9 Feb 2025 16:07:12 +0100 Subject: [PATCH] enhance AutoAssignController to trigger configuration updates on gamepad connection changes; improve controller assignment logic and ensure proper LED color settings --- src/Ryujinx/Input/AutoAssignController.cs | 139 +++++++++--------- .../UI/ViewModels/Input/InputViewModel.cs | 18 ++- 2 files changed, 87 insertions(+), 70 deletions(-) diff --git a/src/Ryujinx/Input/AutoAssignController.cs b/src/Ryujinx/Input/AutoAssignController.cs index 71b443fa6..3af467d37 100644 --- a/src/Ryujinx/Input/AutoAssignController.cs +++ b/src/Ryujinx/Input/AutoAssignController.cs @@ -8,6 +8,7 @@ using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInpu using Ryujinx.Common.Logging; using Ryujinx.Input; using Ryujinx.Input.HLE; +using System; using System.Collections.Generic; using System.Linq; @@ -31,6 +32,8 @@ namespace Ryujinx.Ava.Input private readonly InputManager _inputManager; private readonly MainWindowViewModel _viewModel; private readonly ConfigurationState _configurationState; + + public event Action ConfigurationUpdated; public AutoAssignController(InputManager inputManager, MainWindowViewModel mainWindowViewModel) { @@ -43,39 +46,6 @@ namespace Ryujinx.Ava.Input RefreshControllers(); } - public void RefreshControllers() - { - if (!_configurationState.Hid.EnableAutoAssign) return; - //if (controllers.Count == 0) return; - - // Get every controller config and update the configuration state - - List controllers = _inputManager.GamepadDriver.GetGamepads().ToList(); - List oldConfig = _configurationState.Hid.InputConfig.Value.Where(x => x != null).ToList(); - (List newConfig, bool hasNewControllersConnected) = GetOrderedConfig(controllers, oldConfig); - - int index = 0; - foreach (var config in newConfig) - { - config.PlayerIndex = (PlayerIndex)index; - if (config is StandardControllerInputConfig standardConfig) - { - Logger.Warning?.Print(LogClass.Application, $"Setting color for player {index+1}"); - standardConfig.Led.LedColor = _playerColors[index]; - } - index++; - } - - _viewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, _configurationState.Hid.EnableKeyboard, _configurationState.Hid.EnableMouse); - - // update the configuration state only if there are more controllers than before - if(hasNewControllersConnected) - { - _configurationState.Hid.InputConfig.Value = newConfig; - ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); - } - } - private void HandleOnGamepadConnected(string id) { Logger.Warning?.Print(LogClass.Application, $"Gamepad connected: {id}"); @@ -88,50 +58,85 @@ namespace Ryujinx.Ava.Input RefreshControllers(); } - private (List, bool) GetOrderedConfig(List controllers, List oldConfig) + public void RefreshControllers() { - Dictionary playerIndexMap = new(); - int existingControllers = 0; - - // Convert oldConfig into a dictionary for quick lookup by controller Id - Dictionary oldConfigMap = oldConfig.ToDictionary(x => x.Id, x => x); - - foreach (var controller in controllers) + if (!_configurationState.Hid.EnableAutoAssign) return; + + // Get every controller config and update the configuration state + List controllers = _inputManager.GamepadDriver.GetGamepads().ToList(); + List oldConfig = _configurationState.Hid.InputConfig.Value.Where(x => x != null).ToList(); + (List newConfig, bool hasNewControllersConnected) = GetOrderedConfig(controllers, oldConfig); + + _viewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, _configurationState.Hid.EnableKeyboard, _configurationState.Hid.EnableMouse); + + // update the configuration state only if there are new controllers connected + if(hasNewControllersConnected) { - if (controller == null) continue; + _configurationState.Hid.InputConfig.Value = newConfig; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + ConfigurationUpdated?.Invoke(); + } - // If the controller already has a config in oldConfig, use it + private (List, bool) GetOrderedConfig(List controllers, List oldConfig) + { + Dictionary oldConfigMap = oldConfig.Where(c => c?.Id != null).ToDictionary(x => x.Id); + Dictionary playerIndexMap = new(); + HashSet usedIndices = new(); + int recognizedControllersCount = 0; + + // Assign controllers that have an old config + List remainingControllers = controllers.Where(c => c?.Id != null).ToList(); + foreach (var controller in remainingControllers.ToList()) + { if (oldConfigMap.TryGetValue(controller.Id, out InputConfig existingConfig)) { - // Use the existing PlayerIndex from oldConfig and add it to the map - playerIndexMap[(int)existingConfig.PlayerIndex] = existingConfig; - - existingControllers++; - } - else - { - // Find the first available PlayerIndex (0 to MaxControllers) - for (int i = 0; i < MaxControllers-1; i++) + int desiredIndex = (int)existingConfig.PlayerIndex; + // Check if the desired index is valid and available + if (desiredIndex < 0 || desiredIndex >= MaxControllers || usedIndices.Contains(desiredIndex)) { - if (!playerIndexMap.ContainsKey(i)) // Check if the PlayerIndex is available - { - // Create a new InputConfig and assign PlayerIndex - InputConfig newConfig = CreateConfigFromController(controller); - newConfig.PlayerIndex = (PlayerIndex)i; - - // Add the new config to the map with the available PlayerIndex - playerIndexMap[i] = newConfig; - break; - } + // Find the first available index + desiredIndex = Enumerable.Range(0, MaxControllers).First(i => !usedIndices.Contains(i)); } + existingConfig.PlayerIndex = (PlayerIndex)desiredIndex; + usedIndices.Add(desiredIndex); + playerIndexMap[desiredIndex] = existingConfig; + recognizedControllersCount++; + + remainingControllers.Remove(controller); } } - // Return the sorted list of InputConfigs, ordered by PlayerIndex, and a bool indicating if new controllers were connected - return (playerIndexMap.OrderBy(x => x.Key).Select(x => x.Value).ToList(), controllers.Count > existingControllers); + // Assign remaining (new) controllers + foreach (var controller in remainingControllers.OrderBy(c => c.Id)) + { + InputConfig config = CreateConfigFromController(controller); + int freeIndex = Enumerable.Range(0, MaxControllers).First(i => !usedIndices.Contains(i)); + config.PlayerIndex = (PlayerIndex)freeIndex; + usedIndices.Add(freeIndex); + playerIndexMap[freeIndex] = config; + } + + List orderedConfigs = playerIndexMap.OrderBy(x => x.Key).Select(x => x.Value).ToList(); + + // Sequential reassignment of PlayerIndex and LED colors + int index = 0; + foreach (var config in orderedConfigs) + { + config.PlayerIndex = (PlayerIndex)index; + if (config is StandardControllerInputConfig standardConfig) + { + Logger.Warning?.Print(LogClass.Application, $"Setting color for Player{index+1}"); + standardConfig.Led.LedColor = _playerColors[index]; + } + index++; + } + + bool hasNewControllersConnected = (controllers.Count > recognizedControllersCount); + return (orderedConfigs, hasNewControllersConnected); } - - private InputConfig CreateConfigFromController(IGamepad controller) + + private static InputConfig CreateConfigFromController(IGamepad controller) { if (controller == null) return null; @@ -158,7 +163,7 @@ namespace Ryujinx.Ava.Input } else { - // if it's not a nintendo controller, we assume it's a pro controller or a joycon pair + // if it's not a nintendo controller, we assume it's a pro controller or a joy-con pair controllerType = ControllerType.ProController; } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index a88c7d64b..75ba27960 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -245,6 +245,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + _mainWindow.AutoAssignController.ConfigurationUpdated += OnConfigurationUpdated; + _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); _isLoaded = false; @@ -358,16 +360,25 @@ namespace Ryujinx.Ava.UI.ViewModels.Input private void HandleOnGamepadDisconnected(string id) { + if(ConfigurationState.Instance.Hid.EnableAutoAssign) return; Dispatcher.UIThread.Post(LoadDevices); } private void HandleOnGamepadConnected(string id) { - Dispatcher.UIThread.Post(() => - { + if(ConfigurationState.Instance.Hid.EnableAutoAssign) return; + Dispatcher.UIThread.Post(LoadDevices); + } + + private void OnConfigurationUpdated() + { + Dispatcher.UIThread.Post(() => { LoadDevices(); + _isLoaded = false; + LoadConfiguration(); LoadDevice(); - LoadConfiguration(); + _isLoaded = true; + NotifyChanges(); }); } @@ -888,6 +899,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + _mainWindow.AutoAssignController.ConfigurationUpdated -= OnConfigurationUpdated; _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();