SDL2GamepadDriver.cs
1 using Ryujinx.SDL2.Common; 2 using System; 3 using System.Collections.Generic; 4 using static SDL2.SDL; 5 6 namespace Ryujinx.Input.SDL2 7 { 8 public class SDL2GamepadDriver : IGamepadDriver 9 { 10 private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping; 11 private readonly List<string> _gamepadsIds; 12 private readonly object _lock = new object(); 13 14 public ReadOnlySpan<string> GamepadsIds 15 { 16 get 17 { 18 lock (_lock) 19 { 20 return _gamepadsIds.ToArray(); 21 } 22 } 23 } 24 25 public string DriverName => "SDL2"; 26 27 public event Action<string> OnGamepadConnected; 28 public event Action<string> OnGamepadDisconnected; 29 30 public SDL2GamepadDriver() 31 { 32 _gamepadsInstanceIdsMapping = new Dictionary<int, string>(); 33 _gamepadsIds = new List<string>(); 34 35 SDL2Driver.Instance.Initialize(); 36 SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; 37 SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; 38 39 // Add already connected gamepads 40 int numJoysticks = SDL_NumJoysticks(); 41 42 for (int joystickIndex = 0; joystickIndex < numJoysticks; joystickIndex++) 43 { 44 HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex)); 45 } 46 } 47 48 private string GenerateGamepadId(int joystickIndex) 49 { 50 Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex); 51 52 // Add a unique identifier to the start of the GUID in case of duplicates. 53 54 if (guid == Guid.Empty) 55 { 56 return null; 57 } 58 59 string id; 60 61 lock (_lock) 62 { 63 int guidIndex = 0; 64 id = guidIndex + "-" + guid; 65 66 while (_gamepadsIds.Contains(id)) 67 { 68 id = (++guidIndex) + "-" + guid; 69 } 70 } 71 72 return id; 73 } 74 75 private int GetJoystickIndexByGamepadId(string id) 76 { 77 lock (_lock) 78 { 79 return _gamepadsIds.IndexOf(id); 80 } 81 } 82 83 private void HandleJoyStickDisconnected(int joystickInstanceId) 84 { 85 if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id)) 86 { 87 _gamepadsInstanceIdsMapping.Remove(joystickInstanceId); 88 89 lock (_lock) 90 { 91 _gamepadsIds.Remove(id); 92 } 93 94 OnGamepadDisconnected?.Invoke(id); 95 } 96 } 97 98 private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId) 99 { 100 if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE) 101 { 102 if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId)) 103 { 104 // Sometimes a JoyStick connected event fires after the app starts even though it was connected before 105 // so it is rejected to avoid doubling the entries. 106 return; 107 } 108 109 string id = GenerateGamepadId(joystickDeviceId); 110 111 if (id == null) 112 { 113 return; 114 } 115 116 if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id)) 117 { 118 lock (_lock) 119 { 120 _gamepadsIds.Add(id); 121 } 122 123 OnGamepadConnected?.Invoke(id); 124 } 125 } 126 } 127 128 protected virtual void Dispose(bool disposing) 129 { 130 if (disposing) 131 { 132 SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected; 133 SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected; 134 135 // Simulate a full disconnect when disposing 136 foreach (string id in _gamepadsIds) 137 { 138 OnGamepadDisconnected?.Invoke(id); 139 } 140 141 lock (_lock) 142 { 143 _gamepadsIds.Clear(); 144 } 145 146 SDL2Driver.Instance.Dispose(); 147 } 148 } 149 150 public void Dispose() 151 { 152 GC.SuppressFinalize(this); 153 Dispose(true); 154 } 155 156 public IGamepad GetGamepad(string id) 157 { 158 int joystickIndex = GetJoystickIndexByGamepadId(id); 159 160 if (joystickIndex == -1) 161 { 162 return null; 163 } 164 165 IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex); 166 167 if (gamepadHandle == IntPtr.Zero) 168 { 169 return null; 170 } 171 172 return new SDL2Gamepad(gamepadHandle, id); 173 } 174 } 175 }