diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs index 834bee6f0..0e622f3ad 100644 --- a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -175,6 +175,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid private void Remap() { + Logger.Warning?.Print(LogClass.Hid, $"Connected controllers"); // Remap/Init if necessary for (int i = 0; i < MaxControllers; ++i) { diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index 251f53cba..95d46e4ce 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using Ryujinx.SDL2.Common; using System; using System.Collections.Generic; @@ -96,6 +97,7 @@ namespace Ryujinx.Input.SDL2 private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId) { + Logger.Warning?.Print(LogClass.Application, "(SDL2) Joystick connected: " + joystickDeviceId + " " + joystickInstanceId); if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE) { if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId)) diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 08f222a91..35dbc1605 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -1,5 +1,8 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.HLE.HOS.Services.Hid; using System; @@ -13,6 +16,7 @@ using ControllerType = Ryujinx.Common.Configuration.Hid.ControllerType; using PlayerIndex = Ryujinx.HLE.HOS.Services.Hid.PlayerIndex; using Switch = Ryujinx.HLE.Switch; + namespace Ryujinx.Input.HLE { public class NpadManager : IDisposable @@ -35,6 +39,7 @@ namespace Ryujinx.Input.HLE private List _inputConfig; private bool _enableKeyboard; private bool _enableMouse; + private bool _enableAutoAssign; private Switch _device; public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) @@ -83,14 +88,14 @@ namespace Ryujinx.Input.HLE } } - ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse, _enableAutoAssign); } } private void HandleOnGamepadConnected(string id) { // Force input reload - ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse, _enableAutoAssign); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -117,7 +122,7 @@ namespace Ryujinx.Input.HLE return controller.GamepadDriver != null; } - public void ReloadConfiguration(List inputConfig, bool enableKeyboard, bool enableMouse) + public void ReloadConfiguration(List inputConfig, bool enableKeyboard, bool enableMouse, bool enableAutoAssign) { lock (_lock) { @@ -125,52 +130,178 @@ namespace Ryujinx.Input.HLE List validInputs = new(); - foreach (InputConfig inputConfigEntry in inputConfig) + // if auto assign is disabled, we want to keep the old logic with profiles. + if (!enableAutoAssign) { - NpadController controller; - int index = (int)inputConfigEntry.PlayerIndex; + foreach (InputConfig inputConfigEntry in inputConfig) + { + NpadController controller; + int index = (int)inputConfigEntry.PlayerIndex; - if (oldControllers[index] != null) - { - // Try reuse the existing controller. - controller = oldControllers[index]; - oldControllers[index] = null; - } - else - { - controller = new(_cemuHookClient); - } + if (oldControllers[index] != null) + { + // Try reuse the existing controller. + controller = oldControllers[index]; + oldControllers[index] = null; + } + else + { + controller = new(_cemuHookClient); + } - bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); + bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); - if (!isValid) - { - _controllers[index] = null; - controller.Dispose(); - } - else - { - _controllers[index] = controller; - validInputs.Add(inputConfigEntry); + if (!isValid) + { + _controllers[index] = null; + controller.Dispose(); + } + else + { + _controllers[index] = controller; + validInputs.Add(inputConfigEntry); + } } } - + else + { + List controllers = _gamepadDriver.GetGamepads().ToList(); + + foreach (IGamepad activeController in controllers) + { + NpadController controller; + int index = controllers.FindIndex(x => x == activeController); + + // TODO: Implement a function to determine if pro controller or single joycon (L/R) and to create the appropriate config. + // Also if old controller exists, try to reuse it (and create their config too). + bool isNintendoStyle = controllers.FirstOrDefault(x => x.Id == activeController.Id).Name.Contains("Nintendo"); + string id = activeController.Id.Split(" ")[0]; + InputConfig config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = id, + ControllerType = ControllerType.ProController, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + }, + 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 = false, + TurnOffLed = false, + UseRainbow = false, + LedColor = 0, + }, + }; + + config.PlayerIndex = (Common.Configuration.Hid.PlayerIndex)index; + + if (oldControllers[index] != null) + { + // Try reuse the existing controller. + controller = oldControllers[index]; + oldControllers[index] = null; + } + else + { + controller = new(_cemuHookClient); + } + + // TODO: call function to get config from controller here + + bool isValid = DriverConfigurationUpdate(ref controller, config); + + if (!isValid) + { + _controllers[index] = null; + controller.Dispose(); + } + else + { + _controllers[index] = controller; + validInputs.Add(config); + } + } + } + for (int i = 0; i < oldControllers.Length; i++) { // Disconnect any controllers that weren't reused by the new configuration. - + oldControllers[i]?.Dispose(); oldControllers[i] = null; } - - _inputConfig = inputConfig; + + _inputConfig = (enableAutoAssign) ? validInputs : inputConfig; _enableKeyboard = enableKeyboard; _enableMouse = enableMouse; + _enableAutoAssign = enableAutoAssign; _device.Hid.RefreshInputConfig(validInputs); + } } + private InputConfig CreateConfigFromController(IGamepad controller) + { + InputConfig config; + + return null; + } + public void UnblockInputUpdates() { lock (_lock) @@ -192,12 +323,12 @@ namespace Ryujinx.Input.HLE } } - public void Initialize(Switch device, List inputConfig, bool enableKeyboard, bool enableMouse) + public void Initialize(Switch device, List inputConfig, bool enableKeyboard, bool enableMouse, bool enableAutoAssign) { _device = device; _device.Configuration.RefreshInputConfig = RefreshInputConfigForHLE; - ReloadConfiguration(inputConfig, enableKeyboard, enableMouse); + ReloadConfiguration(inputConfig, enableKeyboard, enableMouse, enableAutoAssign); } public void Update(float aspectRatio = 1) diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 65c279fc4..5d6d0fc18 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -482,7 +482,7 @@ namespace Ryujinx.Ava DisplaySleep.Prevent(); - NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse, ConfigurationState.Instance.Hid.EnableAutoAssign); TouchScreenManager.Initialize(Device); _viewModel.IsGameRunning = true; diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 698af9d17..317dfa804 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -147,6 +147,31 @@ "zh_TW": "滑鼠直接存取" } }, + { + "ID": "SettingsTabInputAutoAssign", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Auto-assign controllers", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "Assegnamento controller automatico", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "SettingsTabSystemMemoryManagerMode", "Translations": { @@ -15322,6 +15347,31 @@ "zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。" } }, + { + "ID": "AutoAssignTooltip", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Automatic controllers assignment support. Automatically assigns connected controllers to each player.\n\nThis feature may override custom player-to-controller assignments.\n\nLeave OFF if you prefer to manually assign controllers.", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "Supporto per l'assegnazione automatica dei controller. Assegna automaticamente i controller connessi a ciascun giocatore.\\n\\nQuesta funzionalità potrebbe sovrascrivere le configurazioni personalizzate controller-giocatore.\\n\\nLascia disattivato se preferisci assegnare i controller manualmente.", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "。" + } + }, { "ID": "RegionTooltip", "Translations": { diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index 787aaca62..09b3a8841 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -44,6 +44,7 @@ namespace Ryujinx.Headless private static List _inputConfiguration = []; private static bool _enableKeyboard; private static bool _enableMouse; + private static bool _enableAutoAssign; private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); @@ -228,6 +229,7 @@ namespace Ryujinx.Headless _inputConfiguration ??= []; _enableKeyboard = option.EnableKeyboard; _enableMouse = option.EnableMouse; + _enableAutoAssign = option.EnableAutoAssign; LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1); LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); @@ -368,7 +370,7 @@ namespace Ryujinx.Headless DisplaySleep.Prevent(); - _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse); + _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse, _enableAutoAssign); _window.Execute(); diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index f527e9811..2846be20e 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -27,6 +27,9 @@ namespace Ryujinx.Headless if (NeedsOverride(nameof(EnableMouse))) EnableMouse = configurationState.Hid.EnableMouse; + + if (NeedsOverride(nameof(EnableAutoAssign))) + EnableAutoAssign = configurationState.Hid.EnableAutoAssign; if (NeedsOverride(nameof(HideCursorMode))) HideCursorMode = configurationState.HideCursor; @@ -273,6 +276,9 @@ namespace Ryujinx.Headless [Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")] public bool EnableMouse { get; set; } + + [Option("enable-auto-assign", Required = false, Default = false, HelpText = "Enable or disable auto-assigning controllers to players.")] + public bool EnableAutoAssign { get; set; } [Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")] public HideCursorMode HideCursorMode { get; set; } diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs index 068c32062..6bbf4dca6 100644 --- a/src/Ryujinx/Headless/Windows/WindowBase.cs +++ b/src/Ryujinx/Headless/Windows/WindowBase.cs @@ -118,7 +118,7 @@ namespace Ryujinx.Headless SDL2Driver.Instance.Initialize(); } - public void Initialize(Switch device, List inputConfigs, bool enableKeyboard, bool enableMouse) + public void Initialize(Switch device, List inputConfigs, bool enableKeyboard, bool enableMouse, bool enableAutoAssign) { Device = device; @@ -131,7 +131,7 @@ namespace Ryujinx.Headless Renderer = renderer; - NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse); + NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse, enableAutoAssign); TouchScreenManager.Initialize(device); } diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs index c59ec540c..9ba80d488 100644 --- a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -851,8 +851,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input newConfig[i] = config; } } - - _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + + _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse, ConfigurationState.Instance.Hid.EnableAutoAssign); // Atomically replace and signal input change. // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index b2311cfc7..69a866bd1 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -128,6 +128,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } public bool EnableMouse { get; set; } + public bool EnableAutoAssign { get; set; } public VSyncMode VSyncMode { get => _vSyncMode; @@ -510,6 +511,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableDockedMode = config.System.EnableDockedMode; EnableKeyboard = config.Hid.EnableKeyboard; EnableMouse = config.Hid.EnableMouse; + EnableAutoAssign = config.Hid.EnableAutoAssign; // Keyboard Hotkeys KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value); @@ -617,6 +619,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.EnableDockedMode.Value = EnableDockedMode; config.Hid.EnableKeyboard.Value = EnableKeyboard; config.Hid.EnableMouse.Value = EnableMouse; + config.Hid.EnableAutoAssign.Value = EnableAutoAssign; // Keyboard Hotkeys config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig(); diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml index b0edc51a5..920c5994e 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml @@ -58,6 +58,12 @@ + + + diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs index 4bbc5e622..e984ee4b2 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs @@ -370,6 +370,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Enable or disable mouse support (Independent from controllers binding) /// public bool EnableMouse { get; set; } + + /// + /// Enable or disable automatic controller assignment for players + /// + public bool EnableAutoAssign { get; set; } /// /// Hotkey Keyboard Bindings diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 0d08536d7..72dadc69b 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -432,6 +432,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Enable or disable mouse support (Independent from controllers binding) /// public ReactiveObject EnableMouse { get; private set; } + + /// + /// Enable or disable auto-assigning controllers to players + /// + public ReactiveObject EnableAutoAssign { get; private set; } /// /// Hotkey Keyboard Bindings @@ -449,6 +454,7 @@ namespace Ryujinx.Ava.Utilities.Configuration { EnableKeyboard = new ReactiveObject(); EnableMouse = new ReactiveObject(); + EnableAutoAssign = new ReactiveObject(); Hotkeys = new ReactiveObject(); InputConfig = new ReactiveObject>(); } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index 80b3e5c03..49f1b125d 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -129,6 +129,7 @@ namespace Ryujinx.Ava.Utilities.Configuration ShowConsole = UI.ShowConsole, EnableKeyboard = Hid.EnableKeyboard, EnableMouse = Hid.EnableMouse, + EnableAutoAssign = Hid.EnableAutoAssign, Hotkeys = Hid.Hotkeys, KeyboardConfig = [], ControllerConfig = [], @@ -243,6 +244,7 @@ namespace Ryujinx.Ava.Utilities.Configuration UI.WindowStartup.WindowMaximized.Value = false; Hid.EnableKeyboard.Value = false; Hid.EnableMouse.Value = false; + Hid.EnableAutoAssign.Value = false; Hid.Hotkeys.Value = new KeyboardHotkeys { ToggleVSyncMode = Key.F1,