SDL2Gamepad.cs
1 using Ryujinx.Common.Configuration.Hid; 2 using Ryujinx.Common.Configuration.Hid.Controller; 3 using Ryujinx.Common.Logging; 4 using System; 5 using System.Collections.Generic; 6 using System.Numerics; 7 using static SDL2.SDL; 8 9 namespace Ryujinx.Input.SDL2 10 { 11 class SDL2Gamepad : IGamepad 12 { 13 private bool HasConfiguration => _configuration != null; 14 15 private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From); 16 17 private StandardControllerInputConfig _configuration; 18 19 private static readonly SDL_GameControllerButton[] _buttonsDriverMapping = new SDL_GameControllerButton[(int)GamepadButtonInputId.Count] 20 { 21 // Unbound, ignored. 22 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 23 24 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A, 25 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B, 26 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X, 27 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y, 28 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK, 29 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK, 30 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 31 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 32 33 // NOTE: The left and right trigger are axis, we handle those differently 34 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 35 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 36 37 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP, 38 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN, 39 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT, 40 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 41 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK, 42 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START, 43 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE, 44 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1, 45 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1, 46 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2, 47 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3, 48 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4, 49 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD, 50 51 // Virtual buttons are invalid, ignored. 52 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 53 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 54 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 55 SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, 56 }; 57 58 private readonly object _userMappingLock = new(); 59 60 private readonly List<ButtonMappingEntry> _buttonsUserMapping; 61 62 private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] 63 { 64 StickInputId.Unbound, 65 StickInputId.Left, 66 StickInputId.Right, 67 }; 68 69 public GamepadFeaturesFlag Features { get; } 70 71 private IntPtr _gamepadHandle; 72 73 private float _triggerThreshold; 74 75 public SDL2Gamepad(IntPtr gamepadHandle, string driverId) 76 { 77 _gamepadHandle = gamepadHandle; 78 _buttonsUserMapping = new List<ButtonMappingEntry>(20); 79 80 Name = SDL_GameControllerName(_gamepadHandle); 81 Id = driverId; 82 Features = GetFeaturesFlag(); 83 _triggerThreshold = 0.0f; 84 85 // Enable motion tracking 86 if (Features.HasFlag(GamepadFeaturesFlag.Motion)) 87 { 88 if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0) 89 { 90 Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}."); 91 } 92 93 if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE) != 0) 94 { 95 Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}."); 96 } 97 } 98 } 99 100 private GamepadFeaturesFlag GetFeaturesFlag() 101 { 102 GamepadFeaturesFlag result = GamepadFeaturesFlag.None; 103 104 if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE && 105 SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE) 106 { 107 result |= GamepadFeaturesFlag.Motion; 108 } 109 110 int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); 111 112 if (error == 0) 113 { 114 result |= GamepadFeaturesFlag.Rumble; 115 } 116 117 return result; 118 } 119 120 public string Id { get; } 121 public string Name { get; } 122 123 public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE; 124 125 protected virtual void Dispose(bool disposing) 126 { 127 if (disposing && _gamepadHandle != IntPtr.Zero) 128 { 129 SDL_GameControllerClose(_gamepadHandle); 130 131 _gamepadHandle = IntPtr.Zero; 132 } 133 } 134 135 public void Dispose() 136 { 137 Dispose(true); 138 } 139 140 public void SetTriggerThreshold(float triggerThreshold) 141 { 142 _triggerThreshold = triggerThreshold; 143 } 144 145 public void Rumble(float lowFrequency, float highFrequency, uint durationMs) 146 { 147 if (Features.HasFlag(GamepadFeaturesFlag.Rumble)) 148 { 149 ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); 150 ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); 151 152 if (durationMs == uint.MaxValue) 153 { 154 if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0) 155 { 156 Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); 157 } 158 } 159 else if (durationMs > SDL_HAPTIC_INFINITY) 160 { 161 Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}"); 162 } 163 else 164 { 165 if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0) 166 { 167 Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); 168 } 169 } 170 } 171 } 172 173 public Vector3 GetMotionData(MotionInputId inputId) 174 { 175 SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID; 176 177 if (inputId == MotionInputId.Accelerometer) 178 { 179 sensorType = SDL_SensorType.SDL_SENSOR_ACCEL; 180 } 181 else if (inputId == MotionInputId.Gyroscope) 182 { 183 sensorType = SDL_SensorType.SDL_SENSOR_GYRO; 184 } 185 186 if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID) 187 { 188 const int ElementCount = 3; 189 190 unsafe 191 { 192 float* values = stackalloc float[ElementCount]; 193 194 int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount); 195 196 if (result == 0) 197 { 198 Vector3 value = new(values[0], values[1], values[2]); 199 200 if (inputId == MotionInputId.Gyroscope) 201 { 202 return RadToDegree(value); 203 } 204 205 if (inputId == MotionInputId.Accelerometer) 206 { 207 return GsToMs2(value); 208 } 209 210 return value; 211 } 212 } 213 } 214 215 return Vector3.Zero; 216 } 217 218 private static Vector3 RadToDegree(Vector3 rad) 219 { 220 return rad * (180 / MathF.PI); 221 } 222 223 private static Vector3 GsToMs2(Vector3 gs) 224 { 225 return gs / SDL_STANDARD_GRAVITY; 226 } 227 228 public void SetConfiguration(InputConfig configuration) 229 { 230 lock (_userMappingLock) 231 { 232 _configuration = (StandardControllerInputConfig)configuration; 233 234 _buttonsUserMapping.Clear(); 235 236 // First update sticks 237 _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick; 238 _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; 239 240 // Then left joycon 241 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); 242 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); 243 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); 244 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); 245 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); 246 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); 247 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); 248 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); 249 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); 250 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); 251 252 // Finally right joycon 253 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); 254 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); 255 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); 256 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); 257 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); 258 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); 259 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); 260 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); 261 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); 262 _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); 263 264 SetTriggerThreshold(_configuration.TriggerThreshold); 265 } 266 } 267 268 public GamepadStateSnapshot GetStateSnapshot() 269 { 270 return IGamepad.GetStateSnapshot(this); 271 } 272 273 public GamepadStateSnapshot GetMappedStateSnapshot() 274 { 275 GamepadStateSnapshot rawState = GetStateSnapshot(); 276 GamepadStateSnapshot result = default; 277 278 lock (_userMappingLock) 279 { 280 if (_buttonsUserMapping.Count == 0) 281 { 282 return rawState; 283 } 284 285 foreach (ButtonMappingEntry entry in _buttonsUserMapping) 286 { 287 if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound) 288 { 289 continue; 290 } 291 292 // Do not touch state of button already pressed 293 if (!result.IsPressed(entry.To)) 294 { 295 result.SetPressed(entry.To, rawState.IsPressed(entry.From)); 296 } 297 } 298 299 (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]); 300 (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]); 301 302 result.SetStick(StickInputId.Left, leftStickX, leftStickY); 303 result.SetStick(StickInputId.Right, rightStickX, rightStickY); 304 } 305 306 return result; 307 } 308 309 private static float ConvertRawStickValue(short value) 310 { 311 const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); 312 313 return value * ConvertRate; 314 } 315 316 public (float, float) GetStick(StickInputId inputId) 317 { 318 if (inputId == StickInputId.Unbound) 319 { 320 return (0.0f, 0.0f); 321 } 322 323 short stickX; 324 short stickY; 325 326 if (inputId == StickInputId.Left) 327 { 328 stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX); 329 stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY); 330 } 331 else if (inputId == StickInputId.Right) 332 { 333 stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX); 334 stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY); 335 } 336 else 337 { 338 throw new NotSupportedException($"Unsupported stick {inputId}"); 339 } 340 341 float resultX = ConvertRawStickValue(stickX); 342 float resultY = -ConvertRawStickValue(stickY); 343 344 if (HasConfiguration) 345 { 346 if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickX) || 347 (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickX)) 348 { 349 resultX = -resultX; 350 } 351 352 if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickY) || 353 (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickY)) 354 { 355 resultY = -resultY; 356 } 357 358 if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.Rotate90CW) || 359 (inputId == StickInputId.Right && _configuration.RightJoyconStick.Rotate90CW)) 360 { 361 float temp = resultX; 362 resultX = resultY; 363 resultY = -temp; 364 } 365 } 366 367 return (resultX, resultY); 368 } 369 370 public bool IsPressed(GamepadButtonInputId inputId) 371 { 372 if (inputId == GamepadButtonInputId.LeftTrigger) 373 { 374 return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold; 375 } 376 377 if (inputId == GamepadButtonInputId.RightTrigger) 378 { 379 return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold; 380 } 381 382 if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID) 383 { 384 return false; 385 } 386 387 return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1; 388 } 389 } 390 }