Feature: Auto-Assign Controllers #706
@ -95,7 +95,7 @@ namespace Ryujinx.Input.SDL2
|
||||
}
|
||||
|
||||
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
|
||||
{
|
||||
{
|
||||
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
||||
{
|
||||
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
||||
|
@ -14,6 +14,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
|
||||
@ -38,6 +39,8 @@ namespace Ryujinx.Input.HLE
|
||||
private bool _enableMouse;
|
||||
private Switch _device;
|
||||
|
||||
public bool AutoAssignEnabled { get; set; } = false;
|
||||
|
||||
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
|
||||
{
|
||||
_controllers = new NpadController[MaxControllers];
|
||||
@ -84,12 +87,14 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
if (AutoAssignEnabled) return;
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
{
|
||||
if (AutoAssignEnabled) return;
|
||||
// Force input reload
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
@ -171,7 +176,7 @@ namespace Ryujinx.Input.HLE
|
||||
_device.Hid.RefreshInputConfig(validInputs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnblockInputUpdates()
|
||||
{
|
||||
lock (_lock)
|
||||
@ -202,11 +207,13 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(Switch device, List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse)
|
||||
public void Initialize(Switch device, List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse, bool enableAutoAssign)
|
||||
{
|
||||
_device = device;
|
||||
_device.Configuration.RefreshInputConfig = RefreshInputConfigForHLE;
|
||||
|
||||
AutoAssignEnabled = enableAutoAssign;
|
||||
|
||||
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
|
||||
}
|
||||
|
||||
|
@ -463,7 +463,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;
|
||||
|
@ -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": {
|
||||
@ -16222,6 +16247,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.\n\nAutomatically assigns connected controllers to each player.\n\nManual configuration remains available, even when this option is activated.\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.\n\nAssegna automaticamente i controller connessi a ciascun giocatore.\n\nÈ possibile configurare manualmente i controller anche quando questa opzione è attivata.\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": {
|
||||
@ -24298,4 +24348,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ namespace Ryujinx.Headless
|
||||
private static List<InputConfig> _inputConfiguration = [];
|
||||
private static bool _enableKeyboard;
|
||||
private static bool _enableMouse;
|
||||
private static bool _enableAutoAssign;
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
@ -230,6 +231,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);
|
||||
@ -371,7 +373,7 @@ namespace Ryujinx.Headless
|
||||
|
||||
DisplaySleep.Prevent();
|
||||
|
||||
_window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse);
|
||||
_window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse, _enableAutoAssign);
|
||||
|
||||
_window.Execute();
|
||||
|
||||
|
@ -26,6 +26,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;
|
||||
@ -272,6 +275,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; }
|
||||
|
@ -119,7 +119,7 @@ namespace Ryujinx.Headless
|
||||
SDL2Driver.Instance.Initialize();
|
||||
}
|
||||
|
||||
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse)
|
||||
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse, bool enableAutoAssign)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
@ -132,7 +132,7 @@ namespace Ryujinx.Headless
|
||||
|
||||
Renderer = renderer;
|
||||
|
||||
NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse);
|
||||
NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse, enableAutoAssign);
|
||||
TouchScreenManager.Initialize(device);
|
||||
}
|
||||
|
||||
|
75
src/Ryujinx/Input/AutoAssignController.cs
Normal file
75
src/Ryujinx/Input/AutoAssignController.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.Utilities.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.HLE;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
public class AutoAssignController
|
||||
{
|
||||
private readonly InputManager _inputManager;
|
||||
private readonly MainWindowViewModel _viewModel;
|
||||
private readonly ConfigurationState _configurationState;
|
||||
|
||||
public event Action ConfigurationUpdated;
|
||||
|
||||
public AutoAssignController(InputManager inputManager, MainWindowViewModel mainWindowViewModel)
|
||||
{
|
||||
_inputManager = inputManager;
|
||||
_viewModel = mainWindowViewModel;
|
||||
_configurationState = ConfigurationState.Instance;
|
||||
|
||||
_inputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||
_inputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||
|
||||
RefreshControllers();
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Gamepad connected: {id}");
|
||||
RefreshControllers();
|
||||
}
|
||||
|
||||
private void HandleOnGamepadDisconnected(string id)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Gamepad disconnected: {id}");
|
||||
RefreshControllers();
|
||||
}
|
||||
|
||||
public void RefreshControllers()
|
||||
{
|
||||
if (!_configurationState.Hid.EnableAutoAssign) return;
|
||||
|
||||
List<IGamepad> controllers = _inputManager.GamepadDriver.GetGamepads().ToList();
|
||||
List<InputConfig> oldConfig = _configurationState.Hid.InputConfig.Value.Where(x => x != null).ToList();
|
||||
|
||||
List<InputConfig> newConfig = ControllerAssignmentManager.GetConfiguredControllers(
|
||||
controllers, oldConfig, out bool hasNewControllersConnected);
|
||||
|
||||
_viewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, _configurationState.Hid.EnableKeyboard, _configurationState.Hid.EnableMouse);
|
||||
|
||||
if (!hasNewControllersConnected)
|
||||
{
|
||||
// there is no *new* controller, we must switch the order of the controllers in
|
||||
// oldConfig to match the new order since probably a controller was disconnected
|
||||
// or an old controller was reconnected
|
||||
newConfig = ControllerAssignmentManager.ReorderControllers(newConfig, oldConfig);
|
||||
}
|
||||
|
||||
_configurationState.Hid.InputConfig.Value = newConfig;
|
||||
|
||||
// we want to save the configuration only if a *new* controller was connected
|
||||
if(hasNewControllersConnected)
|
||||
{
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
ConfigurationUpdated?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
274
src/Ryujinx/Input/ControllerAssignmentManager.cs
Normal file
274
src/Ryujinx/Input/ControllerAssignmentManager.cs
Normal file
@ -0,0 +1,274 @@
|
||||
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 =
|
||||
[
|
||||
0xFF0000FF, // Player 1 - Blue
|
||||
0xFFFF0000, // Player 2 - Red
|
||||
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<InputConfig> ReorderControllers(List<InputConfig> newConfig, List<InputConfig> oldConfig)
|
||||
{
|
||||
if(newConfig == null || oldConfig == null || newConfig.Count == 0 || oldConfig.Count == 0) return [];
|
||||
|
||||
List<InputConfig> 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<InputConfig> GetConfiguredControllers(
|
||||
List<IGamepad> controllers,
|
||||
List<InputConfig> oldConfig,
|
||||
out bool hasNewControllersConnected)
|
||||
{
|
||||
if(controllers == null || controllers.Count == 0)
|
||||
{
|
||||
hasNewControllersConnected = false;
|
||||
return [];
|
||||
}
|
||||
|
||||
Dictionary<string, InputConfig> oldConfigMap = oldConfig
|
||||
.Where(c => c?.Id != null)
|
||||
.ToDictionary(x => x.Id);
|
||||
|
||||
Dictionary<int, InputConfig> playerIndexMap = new();
|
||||
HashSet<int> usedIndices = [];
|
||||
int recognizedControllersCount = 0;
|
||||
|
||||
List<IGamepad> 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<InputConfig> 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<IGamepad> controllers,
|
||||
Dictionary<string, InputConfig> oldConfigMap,
|
||||
Dictionary<int, InputConfig> playerIndexMap,
|
||||
HashSet<int> 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<IGamepad> controllers,
|
||||
Dictionary<int, InputConfig> playerIndexMap,
|
||||
HashSet<int> 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<int> 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<InputConfig> 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<GamepadInputId>
|
||||
{
|
||||
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<GamepadInputId, StickInputId>
|
||||
{
|
||||
Joystick = StickInputId.Left,
|
||||
StickButton = GamepadInputId.LeftStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = (controllerType == ControllerType.JoyconLeft),
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
|
||||
{
|
||||
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<GamepadInputId, StickInputId>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -134,7 +134,7 @@ namespace Ryujinx.Ava
|
||||
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
|
||||
|
||||
ReloadConfig();
|
||||
|
||||
|
||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||
|
||||
// Logging system information.
|
||||
|
@ -252,6 +252,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;
|
||||
@ -365,14 +367,47 @@ 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)
|
||||
{
|
||||
if(ConfigurationState.Instance.Hid.EnableAutoAssign) return;
|
||||
Dispatcher.UIThread.Post(LoadDevices);
|
||||
}
|
||||
|
||||
private void OnConfigurationUpdated()
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
LoadDevices();
|
||||
_isLoaded = false;
|
||||
LoadConfiguration();
|
||||
LoadDevice();
|
||||
_isLoaded = true;
|
||||
|
||||
UpdateGamepadLed();
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateGamepadLed()
|
||||
{
|
||||
if (ConfigViewModel is not ControllerInputViewModel controllerInputViewModel) return;
|
||||
GamepadInputConfig inputConfig = controllerInputViewModel.Config;
|
||||
|
||||
if (inputConfig is not { EnableLedChanging: true }) return;
|
||||
|
||||
if (inputConfig.TurnOffLed)
|
||||
{
|
||||
SelectedGamepad.ClearLed();
|
||||
}
|
||||
|
||||
if (!inputConfig.TurnOffLed && !inputConfig.UseRainbowLed)
|
||||
{
|
||||
SelectedGamepad.SetLed(inputConfig.LedColor.ToUInt32());
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCurrentGamepadId()
|
||||
{
|
||||
if (_device < 0)
|
||||
@ -859,6 +894,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
if (_mainWindow.ViewModel.AppHost != null)
|
||||
{
|
||||
_mainWindow.ViewModel.AppHost.NpadManager.AutoAssignEnabled =
|
||||
ConfigurationState.Instance.Hid.EnableAutoAssign;
|
||||
}
|
||||
|
||||
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||
|
||||
// Atomically replace and signal input change.
|
||||
@ -890,6 +931,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();
|
||||
|
||||
|
@ -140,6 +140,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 bool DisableInputWhenOutOfFocus { get; set; }
|
||||
|
||||
public int FocusLostActionType { get; set; }
|
||||
@ -563,6 +564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
EnableDockedMode = config.System.EnableDockedMode;
|
||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||
EnableMouse = config.Hid.EnableMouse;
|
||||
EnableAutoAssign = config.Hid.EnableAutoAssign;
|
||||
DisableInputWhenOutOfFocus = config.Hid.DisableInputWhenOutOfFocus;
|
||||
|
||||
// Keyboard Hotkeys
|
||||
@ -668,6 +670,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||
config.Hid.EnableMouse.Value = EnableMouse;
|
||||
bool activatingAutoAssign = EnableAutoAssign && !config.Hid.EnableAutoAssign;
|
||||
config.Hid.EnableAutoAssign.Value = EnableAutoAssign;
|
||||
config.Hid.DisableInputWhenOutOfFocus.Value = DisableInputWhenOutOfFocus;
|
||||
|
||||
// Keyboard Hotkeys
|
||||
@ -690,7 +694,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.DramSize.Value = DramSize;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
config.System.IgnoreControllerApplet.Value = IgnoreApplet;
|
||||
config.System.IgnoreControllerApplet.Value = (EnableAutoAssign) || IgnoreApplet;
|
||||
|
||||
// CPU
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
@ -766,7 +770,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
|
||||
|
||||
SaveSettingsEvent?.Invoke();
|
||||
if(activatingAutoAssign)
|
||||
{
|
||||
RyujinxApp.MainWindow.AutoAssignController.RefreshControllers();
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveSettingsEvent?.Invoke();
|
||||
}
|
||||
|
||||
GameListNeedsRefresh = false;
|
||||
}
|
||||
|
@ -58,6 +58,12 @@
|
||||
<TextBlock
|
||||
Text="{ext:Locale SettingsTabInputDirectMouseAccess}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{ext:Locale AutoAssignTooltip}"
|
||||
IsChecked="{Binding EnableAutoAssign}">
|
||||
<TextBlock
|
||||
Text="{ext:Locale SettingsTabInputAutoAssign}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -319,6 +319,7 @@
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreMissingServices}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsEnabled="{Binding !EnableAutoAssign}"
|
||||
IsChecked="{Binding IgnoreApplet}"
|
||||
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />
|
||||
|
@ -64,6 +64,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
public LibHacHorizonManager LibHacHorizonManager { get; private set; }
|
||||
|
||||
public InputManager InputManager { get; private set; }
|
||||
public AutoAssignController AutoAssignController { get; private set; }
|
||||
|
||||
public SettingsWindow SettingsWindow { get; set; }
|
||||
|
||||
@ -110,6 +111,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
|
||||
AutoAssignController = new AutoAssignController(InputManager, ViewModel);
|
||||
|
||||
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
|
||||
this.ScalingChanged += OnScalingChanged;
|
||||
|
@ -389,6 +389,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// </summary>
|
||||
public bool EnableMouse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable or disable automatic controller assignment for players
|
||||
/// </summary>
|
||||
public bool EnableAutoAssign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
|
||||
/// </summary>
|
||||
|
@ -144,6 +144,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
|
||||
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||
Hid.EnableAutoAssign.Value = cff.EnableAutoAssign;
|
||||
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus: Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
|
||||
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
|
||||
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
||||
|
@ -449,6 +449,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableMouse { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable or disable auto-assigning controllers to players
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableAutoAssign { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
|
||||
/// </summary>
|
||||
@ -475,6 +480,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
{
|
||||
EnableKeyboard = new ReactiveObject<bool>();
|
||||
EnableMouse = new ReactiveObject<bool>();
|
||||
EnableAutoAssign = new ReactiveObject<bool>();
|
||||
DisableInputWhenOutOfFocus = new ReactiveObject<bool>();
|
||||
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
|
||||
InputConfig = new ReactiveObject<List<InputConfig>>();
|
||||
|
@ -133,6 +133,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
ShowConsole = UI.ShowConsole,
|
||||
EnableKeyboard = Hid.EnableKeyboard,
|
||||
EnableMouse = Hid.EnableMouse,
|
||||
EnableAutoAssign = Hid.EnableAutoAssign,
|
||||
DisableInputWhenOutOfFocus = Hid.DisableInputWhenOutOfFocus,
|
||||
Hotkeys = Hid.Hotkeys,
|
||||
InputConfig = Hid.InputConfig,
|
||||
@ -249,6 +250,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
UI.WindowStartup.WindowMaximized.Value = false;
|
||||
Hid.EnableKeyboard.Value = false;
|
||||
Hid.EnableMouse.Value = false;
|
||||
Hid.EnableAutoAssign.Value = false;
|
||||
Hid.DisableInputWhenOutOfFocus.Value = false;
|
||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user