/ src / Ryujinx.Input.SDL2 / SDL2GamepadDriver.cs
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  }