From e9ecbd44fcb7763369c2186c4d673f3deb8e3538 Mon Sep 17 00:00:00 2001 From: madwind Date: Mon, 23 Dec 2024 17:57:55 +0800 Subject: [PATCH 01/16] Add a virtual controller to merge Joy-Cons. --- src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 20 +- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 229 ++++++++++++++++++++ src/Ryujinx.Input/HLE/NpadController.cs | 19 +- src/Ryujinx.Input/MotionInputId.cs | 12 + 4 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index c580e4e7d..eefae8fb4 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -1,6 +1,7 @@ using Ryujinx.SDL2.Common; using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using static SDL2.SDL; @@ -11,7 +12,7 @@ namespace Ryujinx.Input.SDL2 private readonly Dictionary _gamepadsInstanceIdsMapping; private readonly List _gamepadsIds; private readonly Lock _lock = new(); - + private readonly SDL2JoyConPair joyConPair; public ReadOnlySpan GamepadsIds { get @@ -36,7 +37,7 @@ namespace Ryujinx.Input.SDL2 SDL2Driver.Instance.Initialize(); SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; - + joyConPair = new SDL2JoyConPair(); // Add already connected gamepads int numJoysticks = SDL_NumJoysticks(); @@ -89,6 +90,10 @@ namespace Ryujinx.Input.SDL2 lock (_lock) { _gamepadsIds.Remove(id); + if (joyConPair.GetJoyConPair(_gamepadsIds) == null) + { + _gamepadsIds.Remove(joyConPair.Id); + } } OnGamepadDisconnected?.Invoke(id); @@ -120,8 +125,12 @@ namespace Ryujinx.Input.SDL2 _gamepadsIds.Insert(joystickDeviceId, id); else _gamepadsIds.Add(id); + if (joyConPair.GetJoyConPair(_gamepadsIds) != null) + { + _gamepadsIds.Remove(joyConPair.Id); + _gamepadsIds.Add(joyConPair.Id); + } } - OnGamepadConnected?.Invoke(id); } } @@ -157,6 +166,11 @@ namespace Ryujinx.Input.SDL2 public IGamepad GetGamepad(string id) { + if (id == joyConPair.Id) + { + return joyConPair.GetJoyConPair(_gamepadsIds); + } + int joystickIndex = GetJoystickIndexByGamepadId(id); if (joystickIndex == -1) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs new file mode 100644 index 000000000..e3ca336df --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -0,0 +1,229 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using static SDL2.SDL; + +namespace Ryujinx.Input.SDL2 +{ + internal class SDL2JoyConPair : IGamepad + { + private IGamepad _left; + private IGamepad _right; + + private StandardControllerInputConfig _configuration; + private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] +{ + StickInputId.Unbound, + StickInputId.Left, + StickInputId.Right, +}; + private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From) + { + public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound; + } + + private readonly List _buttonsUserMapping; + public SDL2JoyConPair() + { + _buttonsUserMapping = new List(20); + } + + private readonly object _userMappingLock = new(); + + public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) | (_right?.Features ?? GamepadFeaturesFlag.None); + + public string Id => "JoyConPair"; + + public string Name => "Nintendo Switch Joy-Con (L/R)"; + private static readonly string leftName = "Nintendo Switch Joy-Con (L)"; + private static readonly string rightName = "Nintendo Switch Joy-Con (R)"; + public bool IsConnected => (_left != null && _left.IsConnected) && (_right != null && _right.IsConnected); + + public void Dispose() + { + _left?.Dispose(); + _right?.Dispose(); + } + public GamepadStateSnapshot GetMappedStateSnapshot() + { + GamepadStateSnapshot rawState = GetStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (_buttonsUserMapping.Count == 0) + return rawState; + + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (!entry.IsValid) + continue; + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]); + (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]); + + result.SetStick(StickInputId.Left, leftStickX, leftStickY); + result.SetStick(StickInputId.Right, rightStickX, rightStickY); + } + + return result; + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + return inputId switch + { + MotionInputId.SecondAccelerometer => _right.GetMotionData(MotionInputId.Accelerometer), + MotionInputId.SecondGyroscope => _right.GetMotionData(MotionInputId.Gyroscope), + _ => _left.GetMotionData(inputId) + }; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + return IGamepad.GetStateSnapshot(this); + } + + public (float, float) GetStick(StickInputId inputId) + { + if (inputId == StickInputId.Left) + { + (float x, float y) = _left.GetStick(StickInputId.Left); + return (y, -x); + } + else if (inputId == StickInputId.Right) + { + (float x, float y) = _right.GetStick(StickInputId.Left); + return (-y, x); + } + return (0, 0); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + return inputId switch + { + GamepadButtonInputId.LeftStick => _left.IsPressed(GamepadButtonInputId.LeftStick), + GamepadButtonInputId.DpadUp => _left.IsPressed(GamepadButtonInputId.Y), + GamepadButtonInputId.DpadDown => _left.IsPressed(GamepadButtonInputId.A), + GamepadButtonInputId.DpadLeft => _left.IsPressed(GamepadButtonInputId.B), + GamepadButtonInputId.DpadRight => _left.IsPressed(GamepadButtonInputId.X), + GamepadButtonInputId.Minus => _left.IsPressed(GamepadButtonInputId.Start), + GamepadButtonInputId.LeftShoulder => _left.IsPressed(GamepadButtonInputId.Paddle2), + GamepadButtonInputId.LeftTrigger => _left.IsPressed(GamepadButtonInputId.Paddle4), + GamepadButtonInputId.SingleRightTrigger0 => _left.IsPressed(GamepadButtonInputId.LeftShoulder), + GamepadButtonInputId.SingleLeftTrigger0 => _left.IsPressed(GamepadButtonInputId.RightShoulder), + + GamepadButtonInputId.RightStick => _right.IsPressed(GamepadButtonInputId.LeftStick), + GamepadButtonInputId.A => _right.IsPressed(GamepadButtonInputId.B), + GamepadButtonInputId.B => _right.IsPressed(GamepadButtonInputId.Y), + GamepadButtonInputId.X => _right.IsPressed(GamepadButtonInputId.A), + GamepadButtonInputId.Y => _right.IsPressed(GamepadButtonInputId.X), + GamepadButtonInputId.Plus => _right.IsPressed(GamepadButtonInputId.Start), + GamepadButtonInputId.RightShoulder => _right.IsPressed(GamepadButtonInputId.Paddle1), + GamepadButtonInputId.RightTrigger => _right.IsPressed(GamepadButtonInputId.Paddle3), + GamepadButtonInputId.SingleRightTrigger1 => _right.IsPressed(GamepadButtonInputId.LeftShoulder), + GamepadButtonInputId.SingleLeftTrigger1 => _right.IsPressed(GamepadButtonInputId.RightShoulder), + + _ => false + }; + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + if (lowFrequency != 0) + { + _right.Rumble(lowFrequency, lowFrequency, durationMs); + } + if (highFrequency != 0) + { + _left.Rumble(highFrequency, highFrequency, durationMs); + } + if (lowFrequency == 0 && highFrequency == 0) + { + _left.Rumble(lowFrequency, highFrequency, durationMs); + _right.Rumble(lowFrequency, highFrequency, durationMs); + } + } + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + + _configuration = (StandardControllerInputConfig)configuration; + _left.SetConfiguration(configuration); + _right.SetConfiguration(configuration); + + _buttonsUserMapping.Clear(); + + // First update sticks + _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick; + _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; + + // Then left joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); + + // Finally right joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); + + SetTriggerThreshold(_configuration.TriggerThreshold); + } + + } + + public void SetTriggerThreshold(float triggerThreshold) + { + _left.SetTriggerThreshold(triggerThreshold); + _right.SetTriggerThreshold(triggerThreshold); + } + + public SDL2JoyConPair GetJoyConPair(List _gamepadsIds) + { + this.Dispose(); + var gamepadNames = _gamepadsIds.Where(gamepadId => gamepadId != Id).Select((gamepadId, index) => SDL_GameControllerNameForIndex(index)).ToList(); + int leftIndex = gamepadNames.IndexOf(leftName); + int rightIndex = gamepadNames.IndexOf(rightName); + + if (leftIndex != -1 && rightIndex != -1) + { + nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex); + nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex); + _left = new SDL2Gamepad(leftGamepadHandle, _gamepadsIds[leftIndex]); + _right = new SDL2Gamepad(rightGamepadHandle, _gamepadsIds[leftIndex]); + return this; + } + return null; + } + } +} diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 380745283..94cf35ad1 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -266,6 +266,7 @@ namespace Ryujinx.Input.HLE if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook) { _leftMotionInput = new MotionInput(); + _rightMotionInput = new MotionInput(); } else { @@ -298,7 +299,20 @@ namespace Ryujinx.Input.HLE if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair) { - _rightMotionInput = _leftMotionInput; + if (gamepad.Id== "JoyConPair") + { + Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.SecondAccelerometer); + Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.SecondGyroscope); + + rightAccelerometer = new Vector3(rightAccelerometer.X, -rightAccelerometer.Z, rightAccelerometer.Y); + rightGyroscope = new Vector3(rightGyroscope.X, -rightGyroscope.Z, rightGyroscope.Y); + + _rightMotionInput.Update(rightAccelerometer, rightGyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone); + } + else + { + _rightMotionInput = _leftMotionInput; + } } } } @@ -333,6 +347,7 @@ namespace Ryujinx.Input.HLE // Reset states State = default; _leftMotionInput = null; + _rightMotionInput = null; } } @@ -545,7 +560,7 @@ namespace Ryujinx.Input.HLE _gamepad.Rumble(low, high, uint.MaxValue); - Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + + Logger.Info?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + diff --git a/src/Ryujinx.Input/MotionInputId.cs b/src/Ryujinx.Input/MotionInputId.cs index 8aeb043a9..c3768da96 100644 --- a/src/Ryujinx.Input/MotionInputId.cs +++ b/src/Ryujinx.Input/MotionInputId.cs @@ -21,5 +21,17 @@ namespace Ryujinx.Input /// /// Values are in degrees Gyroscope, + + /// + /// Second accelerometer. + /// + /// Values are in m/s^2 + SecondAccelerometer, + + /// + /// Second gyroscope. + /// + /// Values are in degrees + SecondGyroscope } } -- 2.47.1 From 86f9544910ca1d7b6824d0691b2035dd9ba985db Mon Sep 17 00:00:00 2001 From: IvonWei Date: Mon, 23 Dec 2024 18:54:11 +0800 Subject: [PATCH 02/16] Update NpadController.cs back to Debug --- src/Ryujinx.Input/HLE/NpadController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 94cf35ad1..5517767be 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -560,7 +560,7 @@ namespace Ryujinx.Input.HLE _gamepad.Rumble(low, high, uint.MaxValue); - Logger.Info?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + + Logger.Debug.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + -- 2.47.1 From ad7d9d1ce02efb9f6956af4266634aaa880f1146 Mon Sep 17 00:00:00 2001 From: IvonWei Date: Mon, 23 Dec 2024 18:55:49 +0800 Subject: [PATCH 03/16] Update NpadController.cs add ? --- src/Ryujinx.Input/HLE/NpadController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 5517767be..53426f71a 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -560,7 +560,7 @@ namespace Ryujinx.Input.HLE _gamepad.Rumble(low, high, uint.MaxValue); - Logger.Debug.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + + Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + -- 2.47.1 From fec197d9ec9431cc790da803e497977fe7202715 Mon Sep 17 00:00:00 2001 From: madwind Date: Wed, 25 Dec 2024 10:39:07 +0800 Subject: [PATCH 04/16] log powerLevel --- src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 27 +++++--- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 76 +++++++++++---------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index eefae8fb4..8c2fd82d7 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -1,7 +1,7 @@ +using Ryujinx.Common.Logging; using Ryujinx.SDL2.Common; using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using static SDL2.SDL; @@ -12,7 +12,8 @@ namespace Ryujinx.Input.SDL2 private readonly Dictionary _gamepadsInstanceIdsMapping; private readonly List _gamepadsIds; private readonly Lock _lock = new(); - private readonly SDL2JoyConPair joyConPair; + private readonly SDL2JoyConPair _joyConPair; + public ReadOnlySpan GamepadsIds { get @@ -37,7 +38,7 @@ namespace Ryujinx.Input.SDL2 SDL2Driver.Instance.Initialize(); SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; - joyConPair = new SDL2JoyConPair(); + _joyConPair = new SDL2JoyConPair(); // Add already connected gamepads int numJoysticks = SDL_NumJoysticks(); @@ -90,9 +91,9 @@ namespace Ryujinx.Input.SDL2 lock (_lock) { _gamepadsIds.Remove(id); - if (joyConPair.GetJoyConPair(_gamepadsIds) == null) + if (_joyConPair.GetGamepad(_gamepadsIds) == null) { - _gamepadsIds.Remove(joyConPair.Id); + _gamepadsIds.Remove(_joyConPair.Id); } } @@ -125,12 +126,15 @@ namespace Ryujinx.Input.SDL2 _gamepadsIds.Insert(joystickDeviceId, id); else _gamepadsIds.Add(id); - if (joyConPair.GetJoyConPair(_gamepadsIds) != null) + var powerLevel = SDL_JoystickCurrentPowerLevel(GetJoystickIndexByGamepadId(id)); + Logger.Info?.Print(LogClass.Hid, $"Gamepad connected: {id}, power level: {powerLevel}"); + if (_joyConPair.GetGamepad(_gamepadsIds) != null) { - _gamepadsIds.Remove(joyConPair.Id); - _gamepadsIds.Add(joyConPair.Id); + _gamepadsIds.Remove(_joyConPair.Id); + _gamepadsIds.Add(_joyConPair.Id); } } + OnGamepadConnected?.Invoke(id); } } @@ -166,9 +170,12 @@ namespace Ryujinx.Input.SDL2 public IGamepad GetGamepad(string id) { - if (id == joyConPair.Id) + if (id == _joyConPair.Id) { - return joyConPair.GetJoyConPair(_gamepadsIds); + lock (_lock) + { + return _joyConPair.GetGamepad(_gamepadsIds); + } } int joystickIndex = GetJoystickIndexByGamepadId(id); diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index e3ca336df..0b8f2f72d 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -1,9 +1,9 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; -using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Threading; using static SDL2.SDL; namespace Ryujinx.Input.SDL2 @@ -14,33 +14,29 @@ namespace Ryujinx.Input.SDL2 private IGamepad _right; private StandardControllerInputConfig _configuration; - private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] -{ + private readonly StickInputId[] _stickUserMapping = + [ StickInputId.Unbound, StickInputId.Left, - StickInputId.Right, -}; + StickInputId.Right + ]; private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From) { public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound; } - private readonly List _buttonsUserMapping; - public SDL2JoyConPair() - { - _buttonsUserMapping = new List(20); - } + private readonly List _buttonsUserMapping = new(20); - private readonly object _userMappingLock = new(); + private readonly Lock _userMappingLock = new(); public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) | (_right?.Features ?? GamepadFeaturesFlag.None); public string Id => "JoyConPair"; public string Name => "Nintendo Switch Joy-Con (L/R)"; - private static readonly string leftName = "Nintendo Switch Joy-Con (L)"; - private static readonly string rightName = "Nintendo Switch Joy-Con (R)"; - public bool IsConnected => (_left != null && _left.IsConnected) && (_right != null && _right.IsConnected); + private const string _leftName = "Nintendo Switch Joy-Con (L)"; + private const string _rightName = "Nintendo Switch Joy-Con (R)"; + public bool IsConnected => _left is { IsConnected: true } && _right is { IsConnected: true }; public void Dispose() { @@ -98,17 +94,23 @@ namespace Ryujinx.Input.SDL2 public (float, float) GetStick(StickInputId inputId) { - if (inputId == StickInputId.Left) + switch (inputId) { - (float x, float y) = _left.GetStick(StickInputId.Left); - return (y, -x); + case StickInputId.Left: + { + (float x, float y) = _left.GetStick(StickInputId.Left); + return (y, -x); + } + case StickInputId.Right: + { + (float x, float y) = _right.GetStick(StickInputId.Left); + return (-y, x); + } + case StickInputId.Unbound: + case StickInputId.Count: + default: + return (0, 0); } - else if (inputId == StickInputId.Right) - { - (float x, float y) = _right.GetStick(StickInputId.Left); - return (-y, x); - } - return (0, 0); } public bool IsPressed(GamepadButtonInputId inputId) @@ -153,8 +155,8 @@ namespace Ryujinx.Input.SDL2 } if (lowFrequency == 0 && highFrequency == 0) { - _left.Rumble(lowFrequency, highFrequency, durationMs); - _right.Rumble(lowFrequency, highFrequency, durationMs); + _left.Rumble(0, 0, durationMs); + _right.Rumble(0, 0, durationMs); } } @@ -208,22 +210,24 @@ namespace Ryujinx.Input.SDL2 _right.SetTriggerThreshold(triggerThreshold); } - public SDL2JoyConPair GetJoyConPair(List _gamepadsIds) + public IGamepad GetGamepad(List gamepadsIds) { this.Dispose(); - var gamepadNames = _gamepadsIds.Where(gamepadId => gamepadId != Id).Select((gamepadId, index) => SDL_GameControllerNameForIndex(index)).ToList(); - int leftIndex = gamepadNames.IndexOf(leftName); - int rightIndex = gamepadNames.IndexOf(rightName); + var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id) + .Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList(); + int leftIndex = gamepadNames.IndexOf(_leftName); + int rightIndex = gamepadNames.IndexOf(_rightName); - if (leftIndex != -1 && rightIndex != -1) + if (leftIndex == -1 || rightIndex == -1) { - nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex); - nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex); - _left = new SDL2Gamepad(leftGamepadHandle, _gamepadsIds[leftIndex]); - _right = new SDL2Gamepad(rightGamepadHandle, _gamepadsIds[leftIndex]); - return this; + return null; } - return null; + + nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex); + nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex); + _left = new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]); + _right = new SDL2Gamepad(rightGamepadHandle, gamepadsIds[leftIndex]); + return this; } } } -- 2.47.1 From e509ffa716d8bb92bc45147142402e4c9b5ec8d1 Mon Sep 17 00:00:00 2001 From: madwind Date: Wed, 25 Dec 2024 16:57:36 +0800 Subject: [PATCH 05/16] delay 2000ms before ShowPowerLevel --- src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 22 ++-- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 106 ++++++++++++++------ 2 files changed, 87 insertions(+), 41 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index 8c2fd82d7..cd35f4a0d 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -1,4 +1,3 @@ -using Ryujinx.Common.Logging; using Ryujinx.SDL2.Common; using System; using System.Collections.Generic; @@ -12,7 +11,7 @@ namespace Ryujinx.Input.SDL2 private readonly Dictionary _gamepadsInstanceIdsMapping; private readonly List _gamepadsIds; private readonly Lock _lock = new(); - private readonly SDL2JoyConPair _joyConPair; + private readonly SDL2JoyConPair joyConPair; public ReadOnlySpan GamepadsIds { @@ -38,7 +37,7 @@ namespace Ryujinx.Input.SDL2 SDL2Driver.Instance.Initialize(); SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; - _joyConPair = new SDL2JoyConPair(); + joyConPair = new SDL2JoyConPair(); // Add already connected gamepads int numJoysticks = SDL_NumJoysticks(); @@ -91,9 +90,9 @@ namespace Ryujinx.Input.SDL2 lock (_lock) { _gamepadsIds.Remove(id); - if (_joyConPair.GetGamepad(_gamepadsIds) == null) + if (joyConPair.GetGamepad(_gamepadsIds) == null) { - _gamepadsIds.Remove(_joyConPair.Id); + _gamepadsIds.Remove(joyConPair.Id); } } @@ -126,12 +125,10 @@ namespace Ryujinx.Input.SDL2 _gamepadsIds.Insert(joystickDeviceId, id); else _gamepadsIds.Add(id); - var powerLevel = SDL_JoystickCurrentPowerLevel(GetJoystickIndexByGamepadId(id)); - Logger.Info?.Print(LogClass.Hid, $"Gamepad connected: {id}, power level: {powerLevel}"); - if (_joyConPair.GetGamepad(_gamepadsIds) != null) + if (joyConPair.GetGamepad(_gamepadsIds) != null) { - _gamepadsIds.Remove(_joyConPair.Id); - _gamepadsIds.Add(_joyConPair.Id); + _gamepadsIds.Remove(joyConPair.Id); + _gamepadsIds.Add(joyConPair.Id); } } @@ -170,11 +167,11 @@ namespace Ryujinx.Input.SDL2 public IGamepad GetGamepad(string id) { - if (id == _joyConPair.Id) + if (id == joyConPair.Id) { lock (_lock) { - return _joyConPair.GetGamepad(_gamepadsIds); + return joyConPair.GetGamepad(_gamepadsIds); } } @@ -192,6 +189,7 @@ namespace Ryujinx.Input.SDL2 return null; } + Console.WriteLine("Game controller opened" + SDL_GameControllerName(gamepadHandle)); return new SDL2Gamepad(gamepadHandle, id); } } diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index 0b8f2f72d..a5d93a794 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -1,10 +1,12 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Logging; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Threading; using static SDL2.SDL; +using Timer = System.Timers.Timer; namespace Ryujinx.Input.SDL2 { @@ -12,14 +14,16 @@ namespace Ryujinx.Input.SDL2 { private IGamepad _left; private IGamepad _right; - + private Timer timer; private StandardControllerInputConfig _configuration; + private readonly StickInputId[] _stickUserMapping = [ StickInputId.Unbound, StickInputId.Left, StickInputId.Right ]; + private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From) { public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound; @@ -29,13 +33,14 @@ namespace Ryujinx.Input.SDL2 private readonly Lock _userMappingLock = new(); - public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) | (_right?.Features ?? GamepadFeaturesFlag.None); + public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) | + (_right?.Features ?? GamepadFeaturesFlag.None); public string Id => "JoyConPair"; public string Name => "Nintendo Switch Joy-Con (L/R)"; - private const string _leftName = "Nintendo Switch Joy-Con (L)"; - private const string _rightName = "Nintendo Switch Joy-Con (R)"; + private const string LeftName = "Nintendo Switch Joy-Con (L)"; + private const string RightName = "Nintendo Switch Joy-Con (R)"; public bool IsConnected => _left is { IsConnected: true } && _right is { IsConnected: true }; public void Dispose() @@ -43,6 +48,7 @@ namespace Ryujinx.Input.SDL2 _left?.Dispose(); _right?.Dispose(); } + public GamepadStateSnapshot GetMappedStateSnapshot() { GamepadStateSnapshot rawState = GetStateSnapshot(); @@ -149,10 +155,12 @@ namespace Ryujinx.Input.SDL2 { _right.Rumble(lowFrequency, lowFrequency, durationMs); } + if (highFrequency != 0) { _left.Rumble(highFrequency, highFrequency, durationMs); } + if (lowFrequency == 0 && highFrequency == 0) { _left.Rumble(0, 0, durationMs); @@ -164,7 +172,6 @@ namespace Ryujinx.Input.SDL2 { lock (_userMappingLock) { - _configuration = (StandardControllerInputConfig)configuration; _left.SetConfiguration(configuration); _right.SetConfiguration(configuration); @@ -176,32 +183,51 @@ namespace Ryujinx.Input.SDL2 _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; // Then left joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, + (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, + (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, + (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); // Finally right joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, + (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, + (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); SetTriggerThreshold(_configuration.TriggerThreshold); } - } public void SetTriggerThreshold(float triggerThreshold) @@ -215,8 +241,8 @@ namespace Ryujinx.Input.SDL2 this.Dispose(); var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id) .Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList(); - int leftIndex = gamepadNames.IndexOf(_leftName); - int rightIndex = gamepadNames.IndexOf(_rightName); + int leftIndex = gamepadNames.IndexOf(LeftName); + int rightIndex = gamepadNames.IndexOf(RightName); if (leftIndex == -1 || rightIndex == -1) { @@ -225,9 +251,31 @@ namespace Ryujinx.Input.SDL2 nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex); nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex); + + if (leftGamepadHandle == nint.Zero || rightGamepadHandle == nint.Zero) + { + return null; + } + _left = new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]); - _right = new SDL2Gamepad(rightGamepadHandle, gamepadsIds[leftIndex]); + _right = new SDL2Gamepad(rightGamepadHandle, gamepadsIds[rightIndex]); + ShowPowerLevel(leftGamepadHandle, rightGamepadHandle); return this; } + + private void ShowPowerLevel(nint leftGamepadHandle, nint rightGamepadHandle) + { + timer?.Stop(); + timer = new Timer(2000); + timer.Elapsed += (_, _) => + { + timer.Stop(); + var leftLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(leftGamepadHandle)); + var rightLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(rightGamepadHandle)); + Logger.Info?.Print(LogClass.Hid, $"Left power level: {leftLevel}, Right power level: {rightLevel}"); + }; + timer.AutoReset = false; + timer.Start(); + } } } -- 2.47.1 From c4dea0ee28e823c2989b8246ff8e95325a6b3260 Mon Sep 17 00:00:00 2001 From: madwind Date: Thu, 26 Dec 2024 11:54:52 +0800 Subject: [PATCH 06/16] add SQL_JOYBATTERYUPDATED , OnJoyBatteryUpdated --- src/Ryujinx.SDL2.Common/SDL2Driver.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index 851c07867..3b184ee91 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -25,14 +25,17 @@ namespace Ryujinx.SDL2.Common public static Action MainThreadDispatcher { get; set; } - private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; + private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | + SDL_INIT_AUDIO | SDL_INIT_VIDEO; private bool _isRunning; private uint _refereceCount; private Thread _worker; + private const uint SQL_JOYBATTERYUPDATED = 1543; public event Action OnJoyStickConnected; public event Action OnJoystickDisconnected; + public event Action OnJoyBatteryUpdated; private ConcurrentDictionary> _registeredWindowHandlers; @@ -78,12 +81,14 @@ namespace Ryujinx.SDL2.Common // First ensure that we only enable joystick events (for connected/disconnected). if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE) { - Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events."); + Logger.Error?.PrintMsg(LogClass.Application, + "Couldn't change the state of game controller events."); } if (SDL_JoystickEventState(SDL_ENABLE) < 0) { - Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}"); + Logger.Error?.PrintMsg(LogClass.Application, + $"Failed to enable joystick event polling: {SDL_GetError()}"); } // Disable all joysticks information, we don't need them no need to flood the event queue for that. @@ -143,7 +148,12 @@ namespace Ryujinx.SDL2.Common OnJoystickDisconnected?.Invoke(evnt.cbutton.which); } - else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP) + else if ((uint)evnt.type == SQL_JOYBATTERYUPDATED) + { + OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code); + } + else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN + or SDL_EventType.SDL_MOUSEBUTTONUP) { if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action handler)) { -- 2.47.1 From 7863e97cb01cee1a67ca598f866d60c2d2001d70 Mon Sep 17 00:00:00 2001 From: madwind Date: Thu, 26 Dec 2024 11:58:00 +0800 Subject: [PATCH 07/16] invoke OnGamepadConnected and OnGamepadDisconnected --- src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 45 +++++--- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 121 +++++++++----------- 2 files changed, 85 insertions(+), 81 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index cd35f4a0d..965a8cfab 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; @@ -11,7 +12,6 @@ namespace Ryujinx.Input.SDL2 private readonly Dictionary _gamepadsInstanceIdsMapping; private readonly List _gamepadsIds; private readonly Lock _lock = new(); - private readonly SDL2JoyConPair joyConPair; public ReadOnlySpan GamepadsIds { @@ -37,7 +37,8 @@ namespace Ryujinx.Input.SDL2 SDL2Driver.Instance.Initialize(); SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; - joyConPair = new SDL2JoyConPair(); + SDL2Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated; + // Add already connected gamepads int numJoysticks = SDL_NumJoysticks(); @@ -84,23 +85,30 @@ namespace Ryujinx.Input.SDL2 private void HandleJoyStickDisconnected(int joystickInstanceId) { + bool joyConPairDisconnected = false; if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id)) return; lock (_lock) { _gamepadsIds.Remove(id); - if (joyConPair.GetGamepad(_gamepadsIds) == null) + if (!SDL2JoyConPair.IsCombinable(_gamepadsIds)) { - _gamepadsIds.Remove(joyConPair.Id); + _gamepadsIds.Remove(SDL2JoyConPair.Id); + joyConPairDisconnected = true; } } OnGamepadDisconnected?.Invoke(id); + if (joyConPairDisconnected) + { + OnGamepadDisconnected?.Invoke(SDL2JoyConPair.Id); + } } private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId) { + bool joyConPairConnected = false; if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE) { if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId)) @@ -125,18 +133,29 @@ namespace Ryujinx.Input.SDL2 _gamepadsIds.Insert(joystickDeviceId, id); else _gamepadsIds.Add(id); - if (joyConPair.GetGamepad(_gamepadsIds) != null) + if (SDL2JoyConPair.IsCombinable(_gamepadsIds)) { - _gamepadsIds.Remove(joyConPair.Id); - _gamepadsIds.Add(joyConPair.Id); + _gamepadsIds.Remove(SDL2JoyConPair.Id); + _gamepadsIds.Add(SDL2JoyConPair.Id); + joyConPairConnected = true; } } OnGamepadConnected?.Invoke(id); + if (joyConPairConnected) + { + OnGamepadConnected?.Invoke(SDL2JoyConPair.Id); + } } } } + private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel) + { + var apiPowerLevel = SDL_JoystickCurrentPowerLevel(SDL_JoystickFromInstanceID(joystickDeviceId)); + Logger.Info?.Print(LogClass.Hid, $"From user code: {powerLevel}, From api: {apiPowerLevel}"); + } + protected virtual void Dispose(bool disposing) { if (disposing) @@ -167,11 +186,11 @@ namespace Ryujinx.Input.SDL2 public IGamepad GetGamepad(string id) { - if (id == joyConPair.Id) + if (id == SDL2JoyConPair.Id) { lock (_lock) { - return joyConPair.GetGamepad(_gamepadsIds); + return SDL2JoyConPair.GetGamepad(_gamepadsIds); } } @@ -184,13 +203,7 @@ namespace Ryujinx.Input.SDL2 nint gamepadHandle = SDL_GameControllerOpen(joystickIndex); - if (gamepadHandle == nint.Zero) - { - return null; - } - - Console.WriteLine("Game controller opened" + SDL_GameControllerName(gamepadHandle)); - return new SDL2Gamepad(gamepadHandle, id); + return gamepadHandle == nint.Zero ? null : new SDL2Gamepad(gamepadHandle, id); } } } diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index a5d93a794..c2fb9557a 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -1,20 +1,15 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Logging; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Threading; using static SDL2.SDL; -using Timer = System.Timers.Timer; namespace Ryujinx.Input.SDL2 { - internal class SDL2JoyConPair : IGamepad + internal class SDL2JoyConPair(IGamepad left, IGamepad right) : IGamepad { - private IGamepad _left; - private IGamepad _right; - private Timer timer; private StandardControllerInputConfig _configuration; private readonly StickInputId[] _stickUserMapping = @@ -33,20 +28,21 @@ namespace Ryujinx.Input.SDL2 private readonly Lock _userMappingLock = new(); - public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) | - (_right?.Features ?? GamepadFeaturesFlag.None); + public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) | + (right?.Features ?? GamepadFeaturesFlag.None); - public string Id => "JoyConPair"; + public const string Id = "JoyConPair"; + string IGamepad.Id => Id; public string Name => "Nintendo Switch Joy-Con (L/R)"; private const string LeftName = "Nintendo Switch Joy-Con (L)"; private const string RightName = "Nintendo Switch Joy-Con (R)"; - public bool IsConnected => _left is { IsConnected: true } && _right is { IsConnected: true }; + public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true }; public void Dispose() { - _left?.Dispose(); - _right?.Dispose(); + left?.Dispose(); + right?.Dispose(); } public GamepadStateSnapshot GetMappedStateSnapshot() @@ -87,9 +83,9 @@ namespace Ryujinx.Input.SDL2 { return inputId switch { - MotionInputId.SecondAccelerometer => _right.GetMotionData(MotionInputId.Accelerometer), - MotionInputId.SecondGyroscope => _right.GetMotionData(MotionInputId.Gyroscope), - _ => _left.GetMotionData(inputId) + MotionInputId.SecondAccelerometer => right.GetMotionData(MotionInputId.Accelerometer), + MotionInputId.SecondGyroscope => right.GetMotionData(MotionInputId.Gyroscope), + _ => left.GetMotionData(inputId) }; } @@ -104,12 +100,12 @@ namespace Ryujinx.Input.SDL2 { case StickInputId.Left: { - (float x, float y) = _left.GetStick(StickInputId.Left); + (float x, float y) = left.GetStick(StickInputId.Left); return (y, -x); } case StickInputId.Right: { - (float x, float y) = _right.GetStick(StickInputId.Left); + (float x, float y) = right.GetStick(StickInputId.Left); return (-y, x); } case StickInputId.Unbound: @@ -123,27 +119,27 @@ namespace Ryujinx.Input.SDL2 { return inputId switch { - GamepadButtonInputId.LeftStick => _left.IsPressed(GamepadButtonInputId.LeftStick), - GamepadButtonInputId.DpadUp => _left.IsPressed(GamepadButtonInputId.Y), - GamepadButtonInputId.DpadDown => _left.IsPressed(GamepadButtonInputId.A), - GamepadButtonInputId.DpadLeft => _left.IsPressed(GamepadButtonInputId.B), - GamepadButtonInputId.DpadRight => _left.IsPressed(GamepadButtonInputId.X), - GamepadButtonInputId.Minus => _left.IsPressed(GamepadButtonInputId.Start), - GamepadButtonInputId.LeftShoulder => _left.IsPressed(GamepadButtonInputId.Paddle2), - GamepadButtonInputId.LeftTrigger => _left.IsPressed(GamepadButtonInputId.Paddle4), - GamepadButtonInputId.SingleRightTrigger0 => _left.IsPressed(GamepadButtonInputId.LeftShoulder), - GamepadButtonInputId.SingleLeftTrigger0 => _left.IsPressed(GamepadButtonInputId.RightShoulder), + GamepadButtonInputId.LeftStick => left.IsPressed(GamepadButtonInputId.LeftStick), + GamepadButtonInputId.DpadUp => left.IsPressed(GamepadButtonInputId.Y), + GamepadButtonInputId.DpadDown => left.IsPressed(GamepadButtonInputId.A), + GamepadButtonInputId.DpadLeft => left.IsPressed(GamepadButtonInputId.B), + GamepadButtonInputId.DpadRight => left.IsPressed(GamepadButtonInputId.X), + GamepadButtonInputId.Minus => left.IsPressed(GamepadButtonInputId.Start), + GamepadButtonInputId.LeftShoulder => left.IsPressed(GamepadButtonInputId.Paddle2), + GamepadButtonInputId.LeftTrigger => left.IsPressed(GamepadButtonInputId.Paddle4), + GamepadButtonInputId.SingleRightTrigger0 => left.IsPressed(GamepadButtonInputId.LeftShoulder), + GamepadButtonInputId.SingleLeftTrigger0 => left.IsPressed(GamepadButtonInputId.RightShoulder), - GamepadButtonInputId.RightStick => _right.IsPressed(GamepadButtonInputId.LeftStick), - GamepadButtonInputId.A => _right.IsPressed(GamepadButtonInputId.B), - GamepadButtonInputId.B => _right.IsPressed(GamepadButtonInputId.Y), - GamepadButtonInputId.X => _right.IsPressed(GamepadButtonInputId.A), - GamepadButtonInputId.Y => _right.IsPressed(GamepadButtonInputId.X), - GamepadButtonInputId.Plus => _right.IsPressed(GamepadButtonInputId.Start), - GamepadButtonInputId.RightShoulder => _right.IsPressed(GamepadButtonInputId.Paddle1), - GamepadButtonInputId.RightTrigger => _right.IsPressed(GamepadButtonInputId.Paddle3), - GamepadButtonInputId.SingleRightTrigger1 => _right.IsPressed(GamepadButtonInputId.LeftShoulder), - GamepadButtonInputId.SingleLeftTrigger1 => _right.IsPressed(GamepadButtonInputId.RightShoulder), + GamepadButtonInputId.RightStick => right.IsPressed(GamepadButtonInputId.LeftStick), + GamepadButtonInputId.A => right.IsPressed(GamepadButtonInputId.B), + GamepadButtonInputId.B => right.IsPressed(GamepadButtonInputId.Y), + GamepadButtonInputId.X => right.IsPressed(GamepadButtonInputId.A), + GamepadButtonInputId.Y => right.IsPressed(GamepadButtonInputId.X), + GamepadButtonInputId.Plus => right.IsPressed(GamepadButtonInputId.Start), + GamepadButtonInputId.RightShoulder => right.IsPressed(GamepadButtonInputId.Paddle1), + GamepadButtonInputId.RightTrigger => right.IsPressed(GamepadButtonInputId.Paddle3), + GamepadButtonInputId.SingleRightTrigger1 => right.IsPressed(GamepadButtonInputId.LeftShoulder), + GamepadButtonInputId.SingleLeftTrigger1 => right.IsPressed(GamepadButtonInputId.RightShoulder), _ => false }; @@ -153,18 +149,18 @@ namespace Ryujinx.Input.SDL2 { if (lowFrequency != 0) { - _right.Rumble(lowFrequency, lowFrequency, durationMs); + right.Rumble(lowFrequency, lowFrequency, durationMs); } if (highFrequency != 0) { - _left.Rumble(highFrequency, highFrequency, durationMs); + left.Rumble(highFrequency, highFrequency, durationMs); } if (lowFrequency == 0 && highFrequency == 0) { - _left.Rumble(0, 0, durationMs); - _right.Rumble(0, 0, durationMs); + left.Rumble(0, 0, durationMs); + right.Rumble(0, 0, durationMs); } } @@ -173,8 +169,8 @@ namespace Ryujinx.Input.SDL2 lock (_userMappingLock) { _configuration = (StandardControllerInputConfig)configuration; - _left.SetConfiguration(configuration); - _right.SetConfiguration(configuration); + left.SetConfiguration(configuration); + right.SetConfiguration(configuration); _buttonsUserMapping.Clear(); @@ -232,18 +228,29 @@ namespace Ryujinx.Input.SDL2 public void SetTriggerThreshold(float triggerThreshold) { - _left.SetTriggerThreshold(triggerThreshold); - _right.SetTriggerThreshold(triggerThreshold); + left.SetTriggerThreshold(triggerThreshold); + right.SetTriggerThreshold(triggerThreshold); } - public IGamepad GetGamepad(List gamepadsIds) + public static bool IsCombinable(List gamepadsIds) + { + (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds); + return leftIndex >= 0 && rightIndex >= 0; + } + + private static (int leftIndex, int rightIndex) DetectJoyConPair(List gamepadsIds) { - this.Dispose(); var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id) .Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList(); int leftIndex = gamepadNames.IndexOf(LeftName); int rightIndex = gamepadNames.IndexOf(RightName); + return (leftIndex, rightIndex); + } + + public static IGamepad GetGamepad(List gamepadsIds) + { + (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds); if (leftIndex == -1 || rightIndex == -1) { return null; @@ -257,25 +264,9 @@ namespace Ryujinx.Input.SDL2 return null; } - _left = new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]); - _right = new SDL2Gamepad(rightGamepadHandle, gamepadsIds[rightIndex]); - ShowPowerLevel(leftGamepadHandle, rightGamepadHandle); - return this; - } - private void ShowPowerLevel(nint leftGamepadHandle, nint rightGamepadHandle) - { - timer?.Stop(); - timer = new Timer(2000); - timer.Elapsed += (_, _) => - { - timer.Stop(); - var leftLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(leftGamepadHandle)); - var rightLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(rightGamepadHandle)); - Logger.Info?.Print(LogClass.Hid, $"Left power level: {leftLevel}, Right power level: {rightLevel}"); - }; - timer.AutoReset = false; - timer.Start(); + return new SDL2JoyConPair(new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]), + new SDL2Gamepad(rightGamepadHandle, gamepadsIds[rightIndex])); } } } -- 2.47.1 From 20fdbff964557e4553ea49db804f0293c729f7be Mon Sep 17 00:00:00 2001 From: madwind Date: Thu, 26 Dec 2024 14:47:40 +0800 Subject: [PATCH 08/16] clean log --- src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index 965a8cfab..3e3681145 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -152,8 +152,7 @@ namespace Ryujinx.Input.SDL2 private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel) { - var apiPowerLevel = SDL_JoystickCurrentPowerLevel(SDL_JoystickFromInstanceID(joystickDeviceId)); - Logger.Info?.Print(LogClass.Hid, $"From user code: {powerLevel}, From api: {apiPowerLevel}"); + Logger.Info?.Print(LogClass.Hid, $"{SDL_GameControllerNameForIndex(joystickDeviceId) } power level: {powerLevel}"); } protected virtual void Dispose(bool disposing) -- 2.47.1 From 6dec7ff8ba5518f0b091163217d14b74a43f7656 Mon Sep 17 00:00:00 2001 From: madwind Date: Sat, 28 Dec 2024 09:07:22 +0800 Subject: [PATCH 09/16] fix motionData --- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index c2fb9557a..4c825dd1a 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -81,12 +82,23 @@ namespace Ryujinx.Input.SDL2 public Vector3 GetMotionData(MotionInputId inputId) { - return inputId switch + Vector3 motionData; + switch (inputId) { - MotionInputId.SecondAccelerometer => right.GetMotionData(MotionInputId.Accelerometer), - MotionInputId.SecondGyroscope => right.GetMotionData(MotionInputId.Gyroscope), - _ => left.GetMotionData(inputId) - }; + case MotionInputId.Accelerometer: + case MotionInputId.Gyroscope: + motionData = left.GetMotionData(inputId); + return new Vector3(-motionData.Z, motionData.Y, motionData.X); + case MotionInputId.SecondAccelerometer: + motionData = right.GetMotionData(MotionInputId.Accelerometer); + return new Vector3(motionData.Z, motionData.Y, -motionData.X); + case MotionInputId.SecondGyroscope: + motionData = right.GetMotionData(MotionInputId.Gyroscope); + return new Vector3(motionData.Z, motionData.Y, -motionData.X); + case MotionInputId.Invalid: + default: + return Vector3.Zero; + } } public GamepadStateSnapshot GetStateSnapshot() -- 2.47.1 From 68c03051ad240525a4d7d99afc6da8e11675a9fe Mon Sep 17 00:00:00 2001 From: madwind Date: Sun, 29 Dec 2024 00:55:26 +0800 Subject: [PATCH 10/16] For the JoyCon controller, wrap SDL2Gamepad as SDL2JoyCon to use a suitable layout and correct the motion sensing and joystick orientation. --- src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 14 +- src/Ryujinx.Input.SDL2/SDL2JoyCon.cs | 134 ++++++++++++++ src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 182 +++----------------- 3 files changed, 166 insertions(+), 164 deletions(-) create mode 100644 src/Ryujinx.Input.SDL2/SDL2JoyCon.cs diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index 3e3681145..2c73d1604 100644 --- a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -152,7 +152,8 @@ namespace Ryujinx.Input.SDL2 private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel) { - Logger.Info?.Print(LogClass.Hid, $"{SDL_GameControllerNameForIndex(joystickDeviceId) } power level: {powerLevel}"); + Logger.Info?.Print(LogClass.Hid, + $"{SDL_GameControllerNameForIndex(joystickDeviceId)} power level: {powerLevel}"); } protected virtual void Dispose(bool disposing) @@ -201,8 +202,17 @@ namespace Ryujinx.Input.SDL2 } nint gamepadHandle = SDL_GameControllerOpen(joystickIndex); + if (gamepadHandle == nint.Zero) + { + return null; + } - return gamepadHandle == nint.Zero ? null : new SDL2Gamepad(gamepadHandle, id); + if (SDL_GameControllerName(gamepadHandle).StartsWith(SDL2JoyCon.Prefix)) + { + return new SDL2JoyCon(gamepadHandle, id); + } + + return new SDL2Gamepad(gamepadHandle, id); } } } diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs new file mode 100644 index 000000000..f7e0ccbf9 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs @@ -0,0 +1,134 @@ +using Ryujinx.Common.Configuration.Hid; +using System.Numerics; + +namespace Ryujinx.Input.SDL2 +{ + internal class SDL2JoyCon : IGamepad + { + readonly IGamepad _gamepad; + private enum JoyConType + { + Left , Right + } + + public const string Prefix = "Nintendo Switch Joy-Con"; + public const string LeftName = "Nintendo Switch Joy-Con (L)"; + public const string RightName = "Nintendo Switch Joy-Con (R)"; + + private readonly JoyConType _joyConType; + + public SDL2JoyCon(nint gamepadHandle, string driverId) + { + _gamepad = new SDL2Gamepad(gamepadHandle, driverId); + _joyConType = Name switch + { + LeftName => JoyConType.Left, + RightName => JoyConType.Right, + _ => JoyConType.Left + }; + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + var motionData = _gamepad.GetMotionData(inputId); + return _joyConType switch + { + JoyConType.Left => new Vector3(-motionData.Z, motionData.Y, motionData.X), + JoyConType.Right => new Vector3(motionData.Z, motionData.Y, -motionData.X), + _ => Vector3.Zero + }; + } + + public void SetTriggerThreshold(float triggerThreshold) + { + _gamepad.SetTriggerThreshold(triggerThreshold); + } + + public void SetConfiguration(InputConfig configuration) + { + _gamepad.SetConfiguration(configuration); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + _gamepad.Rumble(lowFrequency, highFrequency, durationMs); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + return GetStateSnapshot(); + } + + public GamepadStateSnapshot GetStateSnapshot() + { + return IGamepad.GetStateSnapshot(this); + } + + + public (float, float) GetStick(StickInputId inputId) + { + if (inputId == StickInputId.Left) + { + switch (_joyConType) + { + case JoyConType.Left: + { + (float x, float y) = _gamepad.GetStick(inputId); + return (y, -x); + } + case JoyConType.Right: + { + (float x, float y) = _gamepad.GetStick(inputId); + return (-y, x); + } + } + } + return (0, 0); + } + + public GamepadFeaturesFlag Features => _gamepad.Features; + public string Id => _gamepad.Id; + public string Name => _gamepad.Name; + public bool IsConnected => _gamepad.IsConnected; + + public bool IsPressed(GamepadButtonInputId inputId) + { + return _joyConType switch + { + JoyConType.Left => inputId switch + { + GamepadButtonInputId.LeftStick => _gamepad.IsPressed(GamepadButtonInputId.LeftStick), + GamepadButtonInputId.DpadUp => _gamepad.IsPressed(GamepadButtonInputId.Y), + GamepadButtonInputId.DpadDown => _gamepad.IsPressed(GamepadButtonInputId.A), + GamepadButtonInputId.DpadLeft => _gamepad.IsPressed(GamepadButtonInputId.B), + GamepadButtonInputId.DpadRight => _gamepad.IsPressed(GamepadButtonInputId.X), + GamepadButtonInputId.Minus => _gamepad.IsPressed(GamepadButtonInputId.Start), + GamepadButtonInputId.LeftShoulder => _gamepad.IsPressed(GamepadButtonInputId.Paddle2), + GamepadButtonInputId.LeftTrigger => _gamepad.IsPressed(GamepadButtonInputId.Paddle4), + GamepadButtonInputId.SingleRightTrigger0 => _gamepad.IsPressed(GamepadButtonInputId.RightShoulder), + GamepadButtonInputId.SingleLeftTrigger0 => _gamepad.IsPressed(GamepadButtonInputId.LeftShoulder), + _ => false + }, + JoyConType.Right => inputId switch + { + GamepadButtonInputId.RightStick => _gamepad.IsPressed(GamepadButtonInputId.LeftStick), + GamepadButtonInputId.A => _gamepad.IsPressed(GamepadButtonInputId.B), + GamepadButtonInputId.B => _gamepad.IsPressed(GamepadButtonInputId.Y), + GamepadButtonInputId.X => _gamepad.IsPressed(GamepadButtonInputId.A), + GamepadButtonInputId.Y => _gamepad.IsPressed(GamepadButtonInputId.X), + GamepadButtonInputId.Plus => _gamepad.IsPressed(GamepadButtonInputId.Start), + GamepadButtonInputId.RightShoulder => _gamepad.IsPressed(GamepadButtonInputId.Paddle1), + GamepadButtonInputId.RightTrigger => _gamepad.IsPressed(GamepadButtonInputId.Paddle3), + GamepadButtonInputId.SingleRightTrigger1 => _gamepad.IsPressed(GamepadButtonInputId.RightShoulder), + GamepadButtonInputId.SingleLeftTrigger1 => _gamepad.IsPressed(GamepadButtonInputId.LeftShoulder), + _ => false + } + }; + } + + public void Dispose() + { + _gamepad.Dispose(); + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index 4c825dd1a..5ff11f987 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Threading; using static SDL2.SDL; namespace Ryujinx.Input.SDL2 @@ -20,15 +19,6 @@ namespace Ryujinx.Input.SDL2 StickInputId.Right ]; - private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From) - { - public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound; - } - - private readonly List _buttonsUserMapping = new(20); - - private readonly Lock _userMappingLock = new(); - public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) | (right?.Features ?? GamepadFeaturesFlag.None); @@ -36,8 +26,6 @@ namespace Ryujinx.Input.SDL2 string IGamepad.Id => Id; public string Name => "Nintendo Switch Joy-Con (L/R)"; - private const string LeftName = "Nintendo Switch Joy-Con (L)"; - private const string RightName = "Nintendo Switch Joy-Con (R)"; public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true }; public void Dispose() @@ -48,57 +36,19 @@ namespace Ryujinx.Input.SDL2 public GamepadStateSnapshot GetMappedStateSnapshot() { - GamepadStateSnapshot rawState = GetStateSnapshot(); - GamepadStateSnapshot result = default; - - lock (_userMappingLock) - { - if (_buttonsUserMapping.Count == 0) - return rawState; - - - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (ButtonMappingEntry entry in _buttonsUserMapping) - { - if (!entry.IsValid) - continue; - - // Do not touch state of button already pressed - if (!result.IsPressed(entry.To)) - { - result.SetPressed(entry.To, rawState.IsPressed(entry.From)); - } - } - - (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]); - (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]); - - result.SetStick(StickInputId.Left, leftStickX, leftStickY); - result.SetStick(StickInputId.Right, rightStickX, rightStickY); - } - - return result; + return GetStateSnapshot(); } public Vector3 GetMotionData(MotionInputId inputId) { - Vector3 motionData; - switch (inputId) + return inputId switch { - case MotionInputId.Accelerometer: - case MotionInputId.Gyroscope: - motionData = left.GetMotionData(inputId); - return new Vector3(-motionData.Z, motionData.Y, motionData.X); - case MotionInputId.SecondAccelerometer: - motionData = right.GetMotionData(MotionInputId.Accelerometer); - return new Vector3(motionData.Z, motionData.Y, -motionData.X); - case MotionInputId.SecondGyroscope: - motionData = right.GetMotionData(MotionInputId.Gyroscope); - return new Vector3(motionData.Z, motionData.Y, -motionData.X); - case MotionInputId.Invalid: - default: - return Vector3.Zero; - } + MotionInputId.Accelerometer or + MotionInputId.Gyroscope => left.GetMotionData(inputId), + MotionInputId.SecondAccelerometer => right.GetMotionData(MotionInputId.Accelerometer), + MotionInputId.SecondGyroscope => right.GetMotionData(MotionInputId.Gyroscope), + _ => Vector3.Zero + }; } public GamepadStateSnapshot GetStateSnapshot() @@ -108,53 +58,17 @@ namespace Ryujinx.Input.SDL2 public (float, float) GetStick(StickInputId inputId) { - switch (inputId) + return inputId switch { - case StickInputId.Left: - { - (float x, float y) = left.GetStick(StickInputId.Left); - return (y, -x); - } - case StickInputId.Right: - { - (float x, float y) = right.GetStick(StickInputId.Left); - return (-y, x); - } - case StickInputId.Unbound: - case StickInputId.Count: - default: - return (0, 0); - } + StickInputId.Left => left.GetStick(StickInputId.Left), + StickInputId.Right => right.GetStick(StickInputId.Left), + _ => (0, 0) + }; } public bool IsPressed(GamepadButtonInputId inputId) { - return inputId switch - { - GamepadButtonInputId.LeftStick => left.IsPressed(GamepadButtonInputId.LeftStick), - GamepadButtonInputId.DpadUp => left.IsPressed(GamepadButtonInputId.Y), - GamepadButtonInputId.DpadDown => left.IsPressed(GamepadButtonInputId.A), - GamepadButtonInputId.DpadLeft => left.IsPressed(GamepadButtonInputId.B), - GamepadButtonInputId.DpadRight => left.IsPressed(GamepadButtonInputId.X), - GamepadButtonInputId.Minus => left.IsPressed(GamepadButtonInputId.Start), - GamepadButtonInputId.LeftShoulder => left.IsPressed(GamepadButtonInputId.Paddle2), - GamepadButtonInputId.LeftTrigger => left.IsPressed(GamepadButtonInputId.Paddle4), - GamepadButtonInputId.SingleRightTrigger0 => left.IsPressed(GamepadButtonInputId.LeftShoulder), - GamepadButtonInputId.SingleLeftTrigger0 => left.IsPressed(GamepadButtonInputId.RightShoulder), - - GamepadButtonInputId.RightStick => right.IsPressed(GamepadButtonInputId.LeftStick), - GamepadButtonInputId.A => right.IsPressed(GamepadButtonInputId.B), - GamepadButtonInputId.B => right.IsPressed(GamepadButtonInputId.Y), - GamepadButtonInputId.X => right.IsPressed(GamepadButtonInputId.A), - GamepadButtonInputId.Y => right.IsPressed(GamepadButtonInputId.X), - GamepadButtonInputId.Plus => right.IsPressed(GamepadButtonInputId.Start), - GamepadButtonInputId.RightShoulder => right.IsPressed(GamepadButtonInputId.Paddle1), - GamepadButtonInputId.RightTrigger => right.IsPressed(GamepadButtonInputId.Paddle3), - GamepadButtonInputId.SingleRightTrigger1 => right.IsPressed(GamepadButtonInputId.LeftShoulder), - GamepadButtonInputId.SingleLeftTrigger1 => right.IsPressed(GamepadButtonInputId.RightShoulder), - - _ => false - }; + return left.IsPressed(inputId) || right.IsPressed(inputId); } public void Rumble(float lowFrequency, float highFrequency, uint durationMs) @@ -178,64 +92,8 @@ namespace Ryujinx.Input.SDL2 public void SetConfiguration(InputConfig configuration) { - lock (_userMappingLock) - { - _configuration = (StandardControllerInputConfig)configuration; - left.SetConfiguration(configuration); - right.SetConfiguration(configuration); - - _buttonsUserMapping.Clear(); - - // First update sticks - _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick; - _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; - - // Then left joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, - (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, - (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, - (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, - (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, - (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, - (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, - (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, - (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, - (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, - (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); - - // Finally right joycon - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, - (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); - _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, - (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); - - SetTriggerThreshold(_configuration.TriggerThreshold); - } + left.SetConfiguration(configuration); + right.SetConfiguration(configuration); } public void SetTriggerThreshold(float triggerThreshold) @@ -254,8 +112,8 @@ namespace Ryujinx.Input.SDL2 { var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id) .Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList(); - int leftIndex = gamepadNames.IndexOf(LeftName); - int rightIndex = gamepadNames.IndexOf(RightName); + int leftIndex = gamepadNames.IndexOf(SDL2JoyCon.LeftName); + int rightIndex = gamepadNames.IndexOf(SDL2JoyCon.RightName); return (leftIndex, rightIndex); } @@ -277,8 +135,8 @@ namespace Ryujinx.Input.SDL2 } - return new SDL2JoyConPair(new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]), - new SDL2Gamepad(rightGamepadHandle, gamepadsIds[rightIndex])); + return new SDL2JoyConPair(new SDL2JoyCon(leftGamepadHandle, gamepadsIds[leftIndex]), + new SDL2JoyCon(rightGamepadHandle, gamepadsIds[rightIndex])); } } } -- 2.47.1 From 8e50dd9fa6f1069dd7c5efca72afb487eeb33a54 Mon Sep 17 00:00:00 2001 From: madwind Date: Sun, 29 Dec 2024 01:49:25 +0800 Subject: [PATCH 11/16] fix right JoyCon stick --- src/Ryujinx.Input.SDL2/SDL2JoyCon.cs | 30 +++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs index f7e0ccbf9..b25243932 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs @@ -67,23 +67,21 @@ namespace Ryujinx.Input.SDL2 public (float, float) GetStick(StickInputId inputId) { - if (inputId == StickInputId.Left) + switch (inputId) { - switch (_joyConType) - { - case JoyConType.Left: - { - (float x, float y) = _gamepad.GetStick(inputId); - return (y, -x); - } - case JoyConType.Right: - { - (float x, float y) = _gamepad.GetStick(inputId); - return (-y, x); - } - } - } - return (0, 0); + case StickInputId.Left when _joyConType == JoyConType.Left: + { + (float x, float y) = _gamepad.GetStick(inputId); + return (y, -x); + } + case StickInputId.Right when _joyConType == JoyConType.Right: + { + (float x, float y) = _gamepad.GetStick(StickInputId.Left); + return (-y, x); + } + default: + return (0, 0); + } } public GamepadFeaturesFlag Features => _gamepad.Features; -- 2.47.1 From 69dfd8c60e53bec1ad1753566536ea0c845b5489 Mon Sep 17 00:00:00 2001 From: madwind Date: Sun, 29 Dec 2024 02:11:31 +0800 Subject: [PATCH 12/16] fix right Stick --- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index 5ff11f987..e08b298d4 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -61,7 +61,7 @@ namespace Ryujinx.Input.SDL2 return inputId switch { StickInputId.Left => left.GetStick(StickInputId.Left), - StickInputId.Right => right.GetStick(StickInputId.Left), + StickInputId.Right => right.GetStick(StickInputId.Right), _ => (0, 0) }; } -- 2.47.1 From 7a451ab16049b24387b42db8ef703685465968d0 Mon Sep 17 00:00:00 2001 From: madwind Date: Mon, 30 Dec 2024 22:01:21 +0800 Subject: [PATCH 13/16] fix GetMappedStateSnapshot --- src/Ryujinx.Input.SDL2/SDL2JoyCon.cs | 417 ++++++++++++++++++++++----- 1 file changed, 347 insertions(+), 70 deletions(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs index b25243932..027785b96 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs @@ -1,14 +1,69 @@ using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; using System.Numerics; +using System.Threading; +using static SDL2.SDL; namespace Ryujinx.Input.SDL2 { internal class SDL2JoyCon : IGamepad { - readonly IGamepad _gamepad; + private bool HasConfiguration => _configuration != null; + + private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From) + { + public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound; + } + + private StandardControllerInputConfig _configuration; + + private readonly Dictionary _leftButtonsDriverMapping = new() + { + { GamepadButtonInputId.LeftStick , SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK }, + {GamepadButtonInputId.DpadUp ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y}, + {GamepadButtonInputId.DpadDown ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A}, + {GamepadButtonInputId.DpadLeft ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B}, + {GamepadButtonInputId.DpadRight ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X}, + {GamepadButtonInputId.Minus ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START}, + {GamepadButtonInputId.LeftShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2}, + {GamepadButtonInputId.LeftTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4}, + {GamepadButtonInputId.SingleRightTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {GamepadButtonInputId.SingleLeftTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + }; + private readonly Dictionary _rightButtonsDriverMapping = new() + { + {GamepadButtonInputId.RightStick,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {GamepadButtonInputId.A,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B}, + {GamepadButtonInputId.B,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y}, + {GamepadButtonInputId.X,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A}, + {GamepadButtonInputId.Y,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X}, + {GamepadButtonInputId.Plus,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START}, + {GamepadButtonInputId.RightShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1}, + {GamepadButtonInputId.RightTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3}, + {GamepadButtonInputId.SingleRightTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {GamepadButtonInputId.SingleLeftTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER} + }; + + private readonly Dictionary _buttonsDriverMapping; + private readonly Lock _userMappingLock = new(); + + private readonly List _buttonsUserMapping; + + private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] + { + StickInputId.Unbound, StickInputId.Left, StickInputId.Right, + }; + + public GamepadFeaturesFlag Features { get; } + + private nint _gamepadHandle; + private enum JoyConType { - Left , Right + Left, Right } public const string Prefix = "Nintendo Switch Joy-Con"; @@ -19,44 +74,205 @@ namespace Ryujinx.Input.SDL2 public SDL2JoyCon(nint gamepadHandle, string driverId) { - _gamepad = new SDL2Gamepad(gamepadHandle, driverId); - _joyConType = Name switch + _gamepadHandle = gamepadHandle; + _buttonsUserMapping = new List(10); + + Name = SDL_GameControllerName(_gamepadHandle); + Id = driverId; + Features = GetFeaturesFlag(); + + // Enable motion tracking + if (Features.HasFlag(GamepadFeaturesFlag.Motion)) { - LeftName => JoyConType.Left, - RightName => JoyConType.Right, - _ => JoyConType.Left - }; + if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, + SDL_bool.SDL_TRUE) != 0) + { + Logger.Error?.Print(LogClass.Hid, + $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}."); + } + + if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, + SDL_bool.SDL_TRUE) != 0) + { + Logger.Error?.Print(LogClass.Hid, + $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}."); + } + } + + switch (Name) + { + case LeftName: + { + _buttonsDriverMapping = _leftButtonsDriverMapping; + _joyConType = JoyConType.Left; + break; + } + case RightName: + { + _buttonsDriverMapping = _rightButtonsDriverMapping; + _joyConType = JoyConType.Right; + break; + } + } } - public Vector3 GetMotionData(MotionInputId inputId) + private GamepadFeaturesFlag GetFeaturesFlag() { - var motionData = _gamepad.GetMotionData(inputId); - return _joyConType switch + GamepadFeaturesFlag result = GamepadFeaturesFlag.None; + + if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE && + SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE) { - JoyConType.Left => new Vector3(-motionData.Z, motionData.Y, motionData.X), - JoyConType.Right => new Vector3(motionData.Z, motionData.Y, -motionData.X), - _ => Vector3.Zero - }; + result |= GamepadFeaturesFlag.Motion; + } + + int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); + + if (error == 0) + { + result |= GamepadFeaturesFlag.Rumble; + } + + return result; } + public string Id { get; } + public string Name { get; } + public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE; + + protected virtual void Dispose(bool disposing) + { + if (disposing && _gamepadHandle != nint.Zero) + { + SDL_GameControllerClose(_gamepadHandle); + + _gamepadHandle = nint.Zero; + } + } + + public void Dispose() + { + Dispose(true); + } + + public void SetTriggerThreshold(float triggerThreshold) { - _gamepad.SetTriggerThreshold(triggerThreshold); - } - public void SetConfiguration(InputConfig configuration) - { - _gamepad.SetConfiguration(configuration); } public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { - _gamepad.Rumble(lowFrequency, highFrequency, durationMs); + if (!Features.HasFlag(GamepadFeaturesFlag.Rumble)) + return; + + ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); + ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); + + if (durationMs == uint.MaxValue) + { + if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != + 0) + Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); + } + else if (durationMs > SDL_HAPTIC_INFINITY) + { + Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}"); + } + else + { + if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0) + Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); + } } - public GamepadStateSnapshot GetMappedStateSnapshot() + public Vector3 GetMotionData(MotionInputId inputId) { - return GetStateSnapshot(); + SDL_SensorType sensorType = inputId switch + { + MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL, + MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO, + _ => SDL_SensorType.SDL_SENSOR_INVALID + }; + + if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) + return Vector3.Zero; + + const int ElementCount = 3; + + unsafe + { + float* values = stackalloc float[ElementCount]; + + int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount); + + if (result != 0) + return Vector3.Zero; + + Vector3 value = _joyConType switch + { + JoyConType.Left => new Vector3(-values[2], values[1], values[0]), + JoyConType.Right => new Vector3(values[2], values[1], -values[0]) + }; + + return inputId switch + { + MotionInputId.Gyroscope => RadToDegree(value), + MotionInputId.Accelerometer => GsToMs2(value), + _ => value + }; + } + } + + private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI); + + private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY; + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + _configuration = (StandardControllerInputConfig)configuration; + + _buttonsUserMapping.Clear(); + + // First update sticks + _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick; + _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; + + + switch (_joyConType) + { + case JoyConType.Left: + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); + break; + case JoyConType.Right: + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + SetTriggerThreshold(_configuration.TriggerThreshold); + } } public GamepadStateSnapshot GetStateSnapshot() @@ -64,69 +280,130 @@ namespace Ryujinx.Input.SDL2 return IGamepad.GetStateSnapshot(this); } + public GamepadStateSnapshot GetMappedStateSnapshot() + { + GamepadStateSnapshot rawState = GetStateSnapshot(); + GamepadStateSnapshot result = default; - public (float, float) GetStick(StickInputId inputId) + lock (_userMappingLock) + { + if (_buttonsUserMapping.Count == 0) + return rawState; + + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (!entry.IsValid) + continue; + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]); + (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]); + + result.SetStick(StickInputId.Left, leftStickX, leftStickY); + result.SetStick(StickInputId.Right, rightStickX, rightStickY); + } + + return result; + } + + + private static float ConvertRawStickValue(short value) + { + const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); + + return value * ConvertRate; + } + + private JoyconConfigControllerStick + GetLogicalJoyStickConfig(StickInputId inputId) { switch (inputId) { - case StickInputId.Left when _joyConType == JoyConType.Left: - { - (float x, float y) = _gamepad.GetStick(inputId); - return (y, -x); - } - case StickInputId.Right when _joyConType == JoyConType.Right: - { - (float x, float y) = _gamepad.GetStick(StickInputId.Left); - return (-y, x); - } - default: - return (0, 0); + case StickInputId.Left: + if (_configuration.RightJoyconStick.Joystick == + Common.Configuration.Hid.Controller.StickInputId.Left) + return _configuration.RightJoyconStick; + else + return _configuration.LeftJoyconStick; + case StickInputId.Right: + if (_configuration.LeftJoyconStick.Joystick == + Common.Configuration.Hid.Controller.StickInputId.Right) + return _configuration.LeftJoyconStick; + else + return _configuration.RightJoyconStick; } + + return null; } - public GamepadFeaturesFlag Features => _gamepad.Features; - public string Id => _gamepad.Id; - public string Name => _gamepad.Name; - public bool IsConnected => _gamepad.IsConnected; - public bool IsPressed(GamepadButtonInputId inputId) + public (float, float) GetStick(StickInputId inputId) { - return _joyConType switch + if (inputId == StickInputId.Unbound) + return (0.0f, 0.0f); + + if (inputId == StickInputId.Left && _joyConType == JoyConType.Right || inputId == StickInputId.Right && _joyConType == JoyConType.Left) { - JoyConType.Left => inputId switch + return (0.0f, 0.0f); + } + + (short stickX, short stickY) = GetStickXY(); + + float resultX = ConvertRawStickValue(stickX); + float resultY = -ConvertRawStickValue(stickY); + + if (HasConfiguration) + { + var joyconStickConfig = GetLogicalJoyStickConfig(inputId); + + if (joyconStickConfig != null) { - GamepadButtonInputId.LeftStick => _gamepad.IsPressed(GamepadButtonInputId.LeftStick), - GamepadButtonInputId.DpadUp => _gamepad.IsPressed(GamepadButtonInputId.Y), - GamepadButtonInputId.DpadDown => _gamepad.IsPressed(GamepadButtonInputId.A), - GamepadButtonInputId.DpadLeft => _gamepad.IsPressed(GamepadButtonInputId.B), - GamepadButtonInputId.DpadRight => _gamepad.IsPressed(GamepadButtonInputId.X), - GamepadButtonInputId.Minus => _gamepad.IsPressed(GamepadButtonInputId.Start), - GamepadButtonInputId.LeftShoulder => _gamepad.IsPressed(GamepadButtonInputId.Paddle2), - GamepadButtonInputId.LeftTrigger => _gamepad.IsPressed(GamepadButtonInputId.Paddle4), - GamepadButtonInputId.SingleRightTrigger0 => _gamepad.IsPressed(GamepadButtonInputId.RightShoulder), - GamepadButtonInputId.SingleLeftTrigger0 => _gamepad.IsPressed(GamepadButtonInputId.LeftShoulder), - _ => false - }, - JoyConType.Right => inputId switch - { - GamepadButtonInputId.RightStick => _gamepad.IsPressed(GamepadButtonInputId.LeftStick), - GamepadButtonInputId.A => _gamepad.IsPressed(GamepadButtonInputId.B), - GamepadButtonInputId.B => _gamepad.IsPressed(GamepadButtonInputId.Y), - GamepadButtonInputId.X => _gamepad.IsPressed(GamepadButtonInputId.A), - GamepadButtonInputId.Y => _gamepad.IsPressed(GamepadButtonInputId.X), - GamepadButtonInputId.Plus => _gamepad.IsPressed(GamepadButtonInputId.Start), - GamepadButtonInputId.RightShoulder => _gamepad.IsPressed(GamepadButtonInputId.Paddle1), - GamepadButtonInputId.RightTrigger => _gamepad.IsPressed(GamepadButtonInputId.Paddle3), - GamepadButtonInputId.SingleRightTrigger1 => _gamepad.IsPressed(GamepadButtonInputId.RightShoulder), - GamepadButtonInputId.SingleLeftTrigger1 => _gamepad.IsPressed(GamepadButtonInputId.LeftShoulder), - _ => false + if (joyconStickConfig.InvertStickX) + resultX = -resultX; + + if (joyconStickConfig.InvertStickY) + resultY = -resultY; + + if (joyconStickConfig.Rotate90CW) + { + float temp = resultX; + resultX = resultY; + resultY = -temp; + } } + } + + return inputId switch + { + StickInputId.Left when _joyConType == JoyConType.Left => (resultY, -resultX), + StickInputId.Right when _joyConType == JoyConType.Right => (-resultY, resultX), + _ => (0.0f, 0.0f) }; } - public void Dispose() + private (short, short) GetStickXY() { - _gamepad.Dispose(); + return ( + SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX), + SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY)); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + if (!_buttonsDriverMapping.TryGetValue(inputId, out var button)) + { + return false; + } + + return SDL_GameControllerGetButton(_gamepadHandle, button) == 1; } } } -- 2.47.1 From 4399edaa9f1241450b4c8e1aa62cc73a440e5783 Mon Sep 17 00:00:00 2001 From: madwind Date: Thu, 2 Jan 2025 11:50:20 +0800 Subject: [PATCH 14/16] Fix typo: SQL_JOYBATTERYUPDATED => SDL_JOYBATTERYUPDATED --- src/Ryujinx.SDL2.Common/SDL2Driver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index 3b184ee91..d41cbe945 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -32,7 +32,7 @@ namespace Ryujinx.SDL2.Common private uint _refereceCount; private Thread _worker; - private const uint SQL_JOYBATTERYUPDATED = 1543; + private const uint SDL_JOYBATTERYUPDATED = 1543; public event Action OnJoyStickConnected; public event Action OnJoystickDisconnected; public event Action OnJoyBatteryUpdated; @@ -148,7 +148,7 @@ namespace Ryujinx.SDL2.Common OnJoystickDisconnected?.Invoke(evnt.cbutton.which); } - else if ((uint)evnt.type == SQL_JOYBATTERYUPDATED) + else if ((uint)evnt.type == SDL_JOYBATTERYUPDATED) { OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code); } -- 2.47.1 From 9356b68f26238ac304f607067816fe5277518904 Mon Sep 17 00:00:00 2001 From: madwind Date: Mon, 13 Jan 2025 08:41:32 +0800 Subject: [PATCH 15/16] Add a '*' to label the virtual controller. --- src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index e08b298d4..4e0c01ef6 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Input.SDL2 public const string Id = "JoyConPair"; string IGamepad.Id => Id; - public string Name => "Nintendo Switch Joy-Con (L/R)"; + public string Name => "* Nintendo Switch Joy-Con (L/R)"; public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true }; public void Dispose() -- 2.47.1 From b33fe6facce77d19949c31a6fe6fcccf62216f8d Mon Sep 17 00:00:00 2001 From: madwind Date: Thu, 6 Mar 2025 17:02:47 +0800 Subject: [PATCH 16/16] fix: add missing implementation --- src/Ryujinx.Input.SDL2/SDL2JoyCon.cs | 4 ++++ src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs index 027785b96..8ca14f932 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyCon.cs @@ -275,6 +275,10 @@ namespace Ryujinx.Input.SDL2 } } + public void SetLed(uint packedRgb) + { + } + public GamepadStateSnapshot GetStateSnapshot() { return IGamepad.GetStateSnapshot(this); diff --git a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs index 4e0c01ef6..61af69a22 100644 --- a/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs +++ b/src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs @@ -96,6 +96,10 @@ namespace Ryujinx.Input.SDL2 right.SetConfiguration(configuration); } + public void SetLed(uint packedRgb) + { + } + public void SetTriggerThreshold(float triggerThreshold) { left.SetTriggerThreshold(triggerThreshold); -- 2.47.1