diff options
Diffstat (limited to 'Ryujinx.Input.SDL2')
| -rw-r--r-- | Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj | 16 | ||||
| -rw-r--r-- | Ryujinx.Input.SDL2/SDL2Driver.cs | 170 | ||||
| -rw-r--r-- | Ryujinx.Input.SDL2/SDL2Gamepad.cs | 366 | ||||
| -rw-r--r-- | Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 138 |
4 files changed, 690 insertions, 0 deletions
diff --git a/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj new file mode 100644 index 00000000..cee18996 --- /dev/null +++ b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj @@ -0,0 +1,16 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="ppy.SDL2-CS" Version="1.0.225-alpha" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> + </ItemGroup> + +</Project> diff --git a/Ryujinx.Input.SDL2/SDL2Driver.cs b/Ryujinx.Input.SDL2/SDL2Driver.cs new file mode 100644 index 00000000..be709043 --- /dev/null +++ b/Ryujinx.Input.SDL2/SDL2Driver.cs @@ -0,0 +1,170 @@ +using Ryujinx.Common.Logging; +using System; +using System.IO; +using System.Threading; +using static SDL2.SDL; + +namespace Ryujinx.Input.SDL2 +{ + class SDL2Driver : IDisposable + { + private static SDL2Driver _instance; + + public static bool IsInitialized => _instance != null; + + public static SDL2Driver Instance + { + get + { + if (_instance == null) + { + _instance = new SDL2Driver(); + } + + return _instance; + } + } + + private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_SENSOR; + + private bool _isRunning; + private uint _refereceCount; + private Thread _worker; + + public event Action<int, int> OnJoyStickConnected; + public event Action<int> OnJoystickDisconnected; + + private object _lock = new object(); + + private SDL2Driver() {} + + public void Initialize() + { + lock (_lock) + { + _refereceCount++; + + if (_isRunning) + { + return; + } + + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + // TODO: Add this in nuget package once SDL2 2.0.15 hit stable release. + SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0"); + SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1"); + + if (SDL_Init(SdlInitFlags) != 0) + { + string errorMessage = $"SDL2 initlaization failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + // First ensure that we only enable joystick events (for connected/disconnected). + SDL_GameControllerEventState(SDL_DISABLE); + SDL_JoystickEventState(SDL_ENABLE); + + // Disable all joysticks information, we don't need them no need to flood the event queue for that. + SDL_EventState(SDL_EventType.SDL_JOYAXISMOTION, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE); + + SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); + + string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt"); + + if (File.Exists(gamepadDbPath)) + { + SDL_GameControllerAddMappingsFromFile(gamepadDbPath); + } + + _worker = new Thread(EventWorker); + _isRunning = true; + _worker.Start(); + } + } + + private void HandleSDLEvent(ref SDL_Event evnt) + { + if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED) + { + int deviceId = evnt.cbutton.which; + + // SDL2 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system. + int instanceId = SDL_JoystickGetDeviceInstanceID(deviceId); + + if (instanceId == -1) + { + return; + } + + Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}"); + + OnJoyStickConnected?.Invoke(deviceId, instanceId); + } + else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED) + { + Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}"); + + OnJoystickDisconnected?.Invoke(evnt.cbutton.which); + } + } + + private void EventWorker() + { + const int WaitTimeMs = 10; + + using ManualResetEventSlim waitHandle = new ManualResetEventSlim(false); + + while (_isRunning) + { + while (SDL_PollEvent(out SDL_Event evnt) != 0) + { + HandleSDLEvent(ref evnt); + } + + waitHandle.Wait(WaitTimeMs); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + lock (_lock) + { + if (_isRunning) + { + _refereceCount--; + + if (_refereceCount == 0) + { + _isRunning = false; + + _worker?.Join(); + + SDL_Quit(); + + OnJoyStickConnected = null; + OnJoystickDisconnected = null; + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/Ryujinx.Input.SDL2/SDL2Gamepad.cs new file mode 100644 index 00000000..26a808e4 --- /dev/null +++ b/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -0,0 +1,366 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using System; +using System.Collections.Generic; +using System.Numerics; +using static SDL2.SDL; + +namespace Ryujinx.Input.SDL2 +{ + class SDL2Gamepad : IGamepad + { + private bool HasConfiguration => _configuration != null; + + private class ButtonMappingEntry + { + public readonly GamepadButtonInputId To; + public readonly GamepadButtonInputId From; + + public ButtonMappingEntry(GamepadButtonInputId to, GamepadButtonInputId from) + { + To = to; + From = from; + } + } + + private StandardControllerInputConfig _configuration; + + private static readonly SDL_GameControllerButton[] _buttonsDriverMapping = new SDL_GameControllerButton[(int)GamepadButtonInputId.Count] + { + // Unbound, ignored. + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + + // NOTE: The left and right trigger are axis, we handle those differently + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD, + + // Virtual buttons are invalid, ignored. + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + }; + + private object _userMappingLock = new object(); + + private List<ButtonMappingEntry> _buttonsUserMapping; + + private StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] + { + StickInputId.Unbound, + StickInputId.Left, + StickInputId.Right + }; + + public GamepadFeaturesFlag Features { get; } + + private IntPtr _gamepadHandle; + + private float _triggerThreshold; + + public SDL2Gamepad(IntPtr gamepadHandle, string driverId) + { + _gamepadHandle = gamepadHandle; + _buttonsUserMapping = new List<ButtonMappingEntry>(); + + Name = SDL_GameControllerName(_gamepadHandle); + Id = driverId; + Features = GetFeaturesFlag(); + _triggerThreshold = 0.0f; + + // Enable motion tracking + if (Features.HasFlag(GamepadFeaturesFlag.Motion)) + { + SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE); + SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE); + } + } + + private GamepadFeaturesFlag GetFeaturesFlag() + { + 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) + { + 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 != IntPtr.Zero) + { + SDL_GameControllerClose(_gamepadHandle); + + _gamepadHandle = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + _triggerThreshold = triggerThreshold; + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + if (Features.HasFlag(GamepadFeaturesFlag.Rumble)) + { + ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); + ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); + + SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs); + } + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID; + + if (inputId == MotionInputId.Accelerometer) + { + sensorType = SDL_SensorType.SDL_SENSOR_ACCEL; + } + else if (inputId == MotionInputId.Gyroscope) + { + sensorType = SDL_SensorType.SDL_SENSOR_GYRO; + } + + if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID) + { + const int ElementCount = 3; + + unsafe + { + float* values = stackalloc float[ElementCount]; + + int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount); + + if (result == 0) + { + Vector3 value = new Vector3(values[0], values[1], values[2]); + + if (inputId == MotionInputId.Gyroscope) + { + return RadToDegree(value); + } + else if (inputId == MotionInputId.Accelerometer) + { + return GsToMs2(value); + } + + return value; + } + } + } + + return Vector3.Zero; + } + + private static Vector3 RadToDegree(Vector3 rad) + { + return rad * (180 / MathF.PI); + } + + private static Vector3 GsToMs2(Vector3 gs) + { + return 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; + + // 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 GamepadStateSnapshot GetStateSnapshot() + { + return IGamepad.GetStateSnapshot(this); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + GamepadStateSnapshot rawState = GetStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (_buttonsUserMapping.Count == 0) + { + return rawState; + } + + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound) + { + 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; + } + + public (float, float) GetStick(StickInputId inputId) + { + if (inputId == StickInputId.Unbound) + { + return (0.0f, 0.0f); + } + + short stickX; + short stickY; + + if (inputId == StickInputId.Left) + { + stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX); + stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY); + } + else if (inputId == StickInputId.Right) + { + stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX); + stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY); + } + else + { + throw new NotSupportedException($"Unsupported stick {inputId}"); + } + + float resultX = ConvertRawStickValue(stickX); + float resultY = -ConvertRawStickValue(stickY); + + if (HasConfiguration) + { + if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickX) || + (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickX)) + { + resultX = -resultX; + } + + if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickY) || + (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickY)) + { + resultY = -resultY; + } + } + + return (resultX, resultY); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + if (inputId == GamepadButtonInputId.LeftTrigger) + { + return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold; + } + else if (inputId == GamepadButtonInputId.RightTrigger) + { + return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold; + } + else if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID) + { + return false; + } + else + { + return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1; + } + } + } +} diff --git a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs new file mode 100644 index 00000000..62098383 --- /dev/null +++ b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using static SDL2.SDL; + +namespace Ryujinx.Input.SDL2 +{ + public class SDL2GamepadDriver : IGamepadDriver + { + private Dictionary<int, string> _gamepadsInstanceIdsMapping; + private List<string> _gamepadsIds; + + public ReadOnlySpan<string> GamepadsIds => _gamepadsIds.ToArray(); + + public string DriverName => "SDL2"; + + public event Action<string> OnGamepadConnected; + public event Action<string> OnGamepadDisconnected; + + public SDL2GamepadDriver() + { + _gamepadsInstanceIdsMapping = new Dictionary<int, string>(); + _gamepadsIds = new List<string>(); + + SDL2Driver.Instance.Initialize(); + SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; + SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; + + // Add already connected gamepads + for (int joystickIndex = 0; joystickIndex < SDL_NumJoysticks(); joystickIndex++) + { + HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex)); + } + } + + private string GenerateGamepadId(int joystickIndex) + { + Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex); + + if (guid == Guid.Empty) + { + return null; + } + + return joystickIndex + "-" + guid.ToString(); + } + + private int GetJoystickIndexByGamepadId(string id) + { + string[] data = id.Split("-"); + + if (data.Length != 6 || !int.TryParse(data[0], out int joystickIndex)) + { + return -1; + } + + return joystickIndex; + } + + private void HandleJoyStickDisconnected(int joystickInstanceId) + { + if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id)) + { + _gamepadsInstanceIdsMapping.Remove(joystickInstanceId); + _gamepadsIds.Remove(id); + + OnGamepadDisconnected?.Invoke(id); + } + } + + private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId) + { + if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE) + { + string id = GenerateGamepadId(joystickDeviceId); + + if (id == null) + { + return; + } + + if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id)) + { + _gamepadsIds.Add(id); + + OnGamepadConnected?.Invoke(id); + } + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected; + SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected; + + // Simulate a full disconnect when disposing + foreach (string id in _gamepadsIds) + { + OnGamepadDisconnected?.Invoke(id); + } + + _gamepadsIds.Clear(); + + SDL2Driver.Instance.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + + public IGamepad GetGamepad(string id) + { + int joystickIndex = GetJoystickIndexByGamepadId(id); + + if (joystickIndex == -1) + { + return null; + } + + if (id != GenerateGamepadId(joystickIndex)) + { + return null; + } + + IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex); + + if (gamepadHandle == IntPtr.Zero) + { + return null; + } + + return new SDL2Gamepad(gamepadHandle, id); + } + } +} |
