using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Logging; using Ryujinx.Input; using System.Collections.Generic; using System.Linq; using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; namespace Ryujinx.Ava.Input { public static class ControllerAssignmentManager { private static readonly uint[] _playerColors = [ 0xFFFF0000, // Player 1 - Red 0xFF0000FF, // Player 2 - Blue 0xFF00FF00, // Player 3 - Green 0xFFFFFF00, // Player 4 - Yellow 0xFFFF00FF, // Player 5 - Magenta 0xFFFFA500, // Player 6 - Orange 0xFF00FFFF, // Player 7 - Cyan 0xFF800080 // Player 8 - Purple ]; private const int MaxControllers = 9; public static 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; } public static List GetConfiguredControllers( List controllers, List oldConfig, out bool hasNewControllersConnected) { if(controllers == null || controllers.Count == 0) { hasNewControllersConnected = false; return []; } Dictionary oldConfigMap = oldConfig .Where(c => c?.Id != null) .ToDictionary(x => x.Id); Dictionary playerIndexMap = new(); HashSet usedIndices = []; int recognizedControllersCount = 0; List remainingControllers = controllers.Where(c => c?.Id != null).ToList(); // Add controllers with existing configurations AddExistingControllers(remainingControllers, oldConfigMap, playerIndexMap, usedIndices, ref recognizedControllersCount); // Add new controllers AddNewControllers(remainingControllers, playerIndexMap, usedIndices); List orderedConfigs = playerIndexMap .OrderBy(x => x.Key) .Select(x => x.Value) .ToList(); // Update player indices and LED colors UpdatePlayerIndicesAndLEDs(orderedConfigs); hasNewControllersConnected = controllers.Count > recognizedControllersCount; return orderedConfigs; } private static void AddExistingControllers( 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)) { continue; } int desiredIndex = (int)existingConfig.PlayerIndex; // Ensure the index is valid and available if (desiredIndex < 0 || desiredIndex >= MaxControllers || usedIndices.Contains(desiredIndex)) { desiredIndex = GetFirstAvailableIndex(usedIndices); } if(desiredIndex == -1) continue; InputConfig config = new GamepadInputConfig(existingConfig).GetConfig(); config.PlayerIndex = (PlayerIndex)desiredIndex; usedIndices.Add(desiredIndex); playerIndexMap[desiredIndex] = config; recognizedControllersCount++; controllers.Remove(controller); } } private static void AddNewControllers( List controllers, Dictionary playerIndexMap, HashSet usedIndices) { foreach (var controller in controllers) { InputConfig config = CreateConfigFromController(controller); int freeIndex = GetFirstAvailableIndex(usedIndices); config.PlayerIndex = (PlayerIndex)freeIndex; usedIndices.Add(freeIndex); playerIndexMap[freeIndex] = config; } } private static int GetFirstAvailableIndex(HashSet usedIndices) { for (int i = 0; i < MaxControllers; i++) { if (!usedIndices.Contains(i)) return i; } return -1; // Should not happen unless MaxControllers is exceeded } private static void UpdatePlayerIndicesAndLEDs(List orderedConfigs) { for (int index = 0; index < orderedConfigs.Count; index++) { orderedConfigs[index].PlayerIndex = (PlayerIndex)index; if (orderedConfigs[index] is not StandardControllerInputConfig standardConfig || standardConfig.Led.UseRainbow) { continue; } 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) { if (controller == null) return null; Logger.Warning?.Print(LogClass.Application, $"Creating config for controller: {controller.Id}"); string id = controller.Id.Split(" ")[0]; bool isNintendoStyle = controller.Name.Contains("Nintendo"); ControllerType controllerType; if (isNintendoStyle && !controller.Name.Contains("(L/R)")) { if (controller.Name.Contains("(L)")) { controllerType = ControllerType.JoyconLeft; } else if (controller.Name.Contains("(R)")) { controllerType = ControllerType.JoyconRight; } else { controllerType = ControllerType.ProController; } } else { // if it's not a nintendo controller, we assume it's a pro controller or a joy-con pair controllerType = ControllerType.ProController; } InputConfig config = new StandardControllerInputConfig { Version = InputConfig.CurrentVersion, Backend = InputBackendType.GamepadSDL2, Id = id, ControllerType = controllerType, DeadzoneLeft = 0.1f, DeadzoneRight = 0.1f, RangeLeft = 1.0f, RangeRight = 1.0f, TriggerThreshold = 0.5f, LeftJoycon = new LeftJoyconCommonConfig { DpadUp = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.Y : GamepadInputId.DpadUp, DpadDown = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.A : GamepadInputId.DpadDown, DpadLeft = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.B : GamepadInputId.DpadLeft, DpadRight = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.X : GamepadInputId.DpadRight, ButtonMinus = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.Plus : GamepadInputId.Minus, ButtonL = GamepadInputId.LeftShoulder, ButtonZl = GamepadInputId.LeftTrigger, ButtonSl = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.LeftShoulder : GamepadInputId.Unbound, ButtonSr = (controllerType == ControllerType.JoyconLeft) ? GamepadInputId.RightShoulder : GamepadInputId.Unbound, }, LeftJoyconStick = new JoyconConfigControllerStick { Joystick = StickInputId.Left, StickButton = GamepadInputId.LeftStick, InvertStickX = false, InvertStickY = false, Rotate90CW = (controllerType == ControllerType.JoyconLeft), }, RightJoycon = new RightJoyconCommonConfig { ButtonA = GamepadInputId.B, ButtonB = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.Y : GamepadInputId.A, ButtonX = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.A : GamepadInputId.Y, ButtonY = GamepadInputId.X, ButtonPlus = GamepadInputId.Plus, ButtonR = GamepadInputId.RightShoulder, ButtonZr = GamepadInputId.RightTrigger, ButtonSl = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.LeftShoulder : GamepadInputId.Unbound, ButtonSr = (controllerType == ControllerType.JoyconRight) ? GamepadInputId.RightShoulder : GamepadInputId.Unbound, }, RightJoyconStick = new JoyconConfigControllerStick { Joystick = (controllerType == ControllerType.JoyconRight) ? StickInputId.Left : StickInputId.Right, StickButton = GamepadInputId.RightStick, InvertStickX = (controllerType == ControllerType.JoyconRight), InvertStickY = (controllerType == ControllerType.JoyconRight), Rotate90CW = (controllerType == ControllerType.JoyconRight), }, Motion = new StandardMotionConfigController { MotionBackend = MotionInputBackendType.GamepadDriver, EnableMotion = true, Sensitivity = 100, GyroDeadzone = 1, }, Rumble = new RumbleConfigController { StrongRumble = 1f, WeakRumble = 1f, EnableRumble = false, }, Led = new LedConfigController { EnableLed = true, TurnOffLed = false, UseRainbow = false, LedColor = 0, }, }; return config; } } }