diff --git a/src/Ryujinx/Input/AutoAssignController.cs b/src/Ryujinx/Input/AutoAssignController.cs index 58978531d..5ebba82f6 100644 --- a/src/Ryujinx/Input/AutoAssignController.cs +++ b/src/Ryujinx/Input/AutoAssignController.cs @@ -1,4 +1,5 @@ -using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Models.Input; +using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; @@ -61,79 +62,159 @@ namespace Ryujinx.Ava.Input public void RefreshControllers() { 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); - + + (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 (hasNewControllersConnected) { - _configurationState.Hid.InputConfig.Value = newConfig; ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); } + else + { + // we must switch the order of the controllers in oldConfig to match the new order + newConfig = ReorderControllers(newConfig, oldConfig); + } + + _configurationState.Hid.InputConfig.Value = newConfig; ConfigurationUpdated?.Invoke(); } + /// + /// Ensures that the order of controllers remains the same unless a new controller is connected. + /// + private List ReorderControllers(List newConfig, List oldConfig) + { + List reorderedConfig = oldConfig.Select(config => new GamepadInputConfig(config).GetConfig()).ToList(); + + foreach (var config in newConfig) + { + InputConfig substitute = reorderedConfig.FirstOrDefault(x => x.Id == config.Id); + InputConfig toBeReplaced = reorderedConfig.FirstOrDefault(x => x.PlayerIndex == config.PlayerIndex); + + if (substitute == null || toBeReplaced == null || substitute.PlayerIndex == toBeReplaced.PlayerIndex) continue; + + (substitute.PlayerIndex, toBeReplaced.PlayerIndex) = (toBeReplaced.PlayerIndex, substitute.PlayerIndex); + } + + return reorderedConfig; + } + + /// + /// Orders controllers, preserving existing mappings while assigning new controllers to available slots. + /// private (List, bool) GetOrderedConfig(List controllers, List oldConfig) - { - Dictionary oldConfigMap = oldConfig.Where(c => c?.Id != null).ToDictionary(x => x.Id); + { + 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()) + + // Assign controllers that have an existing configuration + AssignExistingControllers(remainingControllers, oldConfigMap, playerIndexMap, usedIndices, ref recognizedControllersCount); + + // Assign new controllers + AssignNewControllers(remainingControllers, playerIndexMap, usedIndices); + + List orderedConfigs = playerIndexMap.OrderBy(x => x.Key).Select(x => x.Value).ToList(); + + // Sequentially update PlayerIndex and LED colors + UpdatePlayerIndicesAndLEDs(orderedConfigs); + + bool hasNewControllersConnected = controllers.Count > recognizedControllersCount; + return (orderedConfigs, hasNewControllersConnected); + } + + /// + /// Assigns controllers with existing configurations while keeping their PlayerIndex if available. + /// + private void AssignExistingControllers( + List controllers, + Dictionary oldConfigMap, + Dictionary playerIndexMap, + HashSet usedIndices, + ref int recognizedControllersCount) + { + foreach (var controller in controllers.ToList()) { if (oldConfigMap.TryGetValue(controller.Id, out InputConfig existingConfig)) { int desiredIndex = (int)existingConfig.PlayerIndex; - // Check if the desired index is valid and available + + // Ensure the index is valid and available if (desiredIndex < 0 || desiredIndex >= MaxControllers || usedIndices.Contains(desiredIndex)) { - // Find the first available index - desiredIndex = Enumerable.Range(0, MaxControllers).First(i => !usedIndices.Contains(i)); + desiredIndex = GetFirstAvailableIndex(usedIndices); } - existingConfig.PlayerIndex = (PlayerIndex)desiredIndex; + + InputConfig config = new GamepadInputConfig(existingConfig).GetConfig(); + config.PlayerIndex = (PlayerIndex)desiredIndex; usedIndices.Add(desiredIndex); - playerIndexMap[desiredIndex] = existingConfig; + playerIndexMap[desiredIndex] = config; recognizedControllersCount++; - - remainingControllers.Remove(controller); + + controllers.Remove(controller); } } + } - // Assign remaining (new) controllers - foreach (var controller in remainingControllers.OrderBy(c => c.Id)) + /// + /// Assigns new controllers to the first available PlayerIndex. + /// + private void AssignNewControllers( + List controllers, + Dictionary playerIndexMap, + HashSet usedIndices) + { + foreach (var controller in controllers) { InputConfig config = CreateConfigFromController(controller); - int freeIndex = Enumerable.Range(0, MaxControllers).First(i => !usedIndices.Contains(i)); + int freeIndex = GetFirstAvailableIndex(usedIndices); 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) + /// + /// Finds the first available PlayerIndex that isn't in use. + /// + private int GetFirstAvailableIndex(HashSet usedIndices) + { + for (int i = 0; i < MaxControllers; i++) { - config.PlayerIndex = (PlayerIndex)index; - if (config is StandardControllerInputConfig standardConfig) - { - Logger.Warning?.Print(LogClass.Application, $"Setting color for Player{index+1}"); - standardConfig.Led = new LedConfigController { EnableLed = true, LedColor = _playerColors[index] }; - } - index++; + if (!usedIndices.Contains(i)) return i; } + return -1; // Should not happen unless MaxControllers is exceeded + } - bool hasNewControllersConnected = (controllers.Count > recognizedControllersCount); - return (orderedConfigs, hasNewControllersConnected); + /// + /// Updates PlayerIndex and LED configurations sequentially. + /// + private void UpdatePlayerIndicesAndLEDs(List orderedConfigs) + { + for (int index = 0; index < orderedConfigs.Count; index++) + { + orderedConfigs[index].PlayerIndex = (PlayerIndex)index; + + if (orderedConfigs[index] is StandardControllerInputConfig standardConfig) + { + Logger.Warning?.Print(LogClass.Application, $"Setting color for Player{index + 1}"); + standardConfig.Led = new LedConfigController + { + EnableLed = true, + LedColor = _playerColors[index] + }; + } + } } private static InputConfig CreateConfigFromController(IGamepad controller)