Move solution and projects to src
This commit is contained in:
13
src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
Normal file
13
src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
376
src/Ryujinx.Input.SDL2/SDL2Gamepad.cs
Normal file
376
src/Ryujinx.Input.SDL2/SDL2Gamepad.cs
Normal file
@ -0,0 +1,376 @@
|
||||
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 static SDL2.SDL;
|
||||
|
||||
namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
class SDL2Gamepad : IGamepad
|
||||
{
|
||||
private bool HasConfiguration => _configuration != null;
|
||||
|
||||
private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId 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>(20);
|
||||
|
||||
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);
|
||||
|
||||
if (durationMs == uint.MaxValue)
|
||||
{
|
||||
SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY);
|
||||
}
|
||||
else if (durationMs > SDL_HAPTIC_INFINITY)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.Rotate90CW) ||
|
||||
(inputId == StickInputId.Right && _configuration.RightJoyconStick.Rotate90CW))
|
||||
{
|
||||
float temp = resultX;
|
||||
resultX = resultY;
|
||||
resultY = -temp;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
148
src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
Normal file
148
src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using Ryujinx.SDL2.Common;
|
||||
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
|
||||
int numJoysticks = SDL_NumJoysticks();
|
||||
|
||||
for (int joystickIndex = 0; joystickIndex < 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;
|
||||
}
|
||||
|
||||
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
||||
// so it is rejected to avoid doubling the entries.
|
||||
if (_gamepadsIds.Contains(id))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
419
src/Ryujinx.Input.SDL2/SDL2Keyboard.cs
Normal file
419
src/Ryujinx.Input.SDL2/SDL2Keyboard.cs
Normal file
@ -0,0 +1,419 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static SDL2.SDL;
|
||||
|
||||
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
class SDL2Keyboard : IKeyboard
|
||||
{
|
||||
private class ButtonMappingEntry
|
||||
{
|
||||
public readonly GamepadButtonInputId To;
|
||||
public readonly Key From;
|
||||
|
||||
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
|
||||
{
|
||||
To = to;
|
||||
From = from;
|
||||
}
|
||||
}
|
||||
|
||||
private object _userMappingLock = new object();
|
||||
|
||||
private readonly SDL2KeyboardDriver _driver;
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
private List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
|
||||
private static readonly SDL_Keycode[] _keysDriverMapping = new SDL_Keycode[(int)Key.Count]
|
||||
{
|
||||
// INVALID
|
||||
SDL_Keycode.SDLK_0,
|
||||
// Presented as modifiers, so invalid here.
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
|
||||
SDL_Keycode.SDLK_F1,
|
||||
SDL_Keycode.SDLK_F2,
|
||||
SDL_Keycode.SDLK_F3,
|
||||
SDL_Keycode.SDLK_F4,
|
||||
SDL_Keycode.SDLK_F5,
|
||||
SDL_Keycode.SDLK_F6,
|
||||
SDL_Keycode.SDLK_F7,
|
||||
SDL_Keycode.SDLK_F8,
|
||||
SDL_Keycode.SDLK_F9,
|
||||
SDL_Keycode.SDLK_F10,
|
||||
SDL_Keycode.SDLK_F11,
|
||||
SDL_Keycode.SDLK_F12,
|
||||
SDL_Keycode.SDLK_F13,
|
||||
SDL_Keycode.SDLK_F14,
|
||||
SDL_Keycode.SDLK_F15,
|
||||
SDL_Keycode.SDLK_F16,
|
||||
SDL_Keycode.SDLK_F17,
|
||||
SDL_Keycode.SDLK_F18,
|
||||
SDL_Keycode.SDLK_F19,
|
||||
SDL_Keycode.SDLK_F20,
|
||||
SDL_Keycode.SDLK_F21,
|
||||
SDL_Keycode.SDLK_F22,
|
||||
SDL_Keycode.SDLK_F23,
|
||||
SDL_Keycode.SDLK_F24,
|
||||
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_0,
|
||||
|
||||
SDL_Keycode.SDLK_UP,
|
||||
SDL_Keycode.SDLK_DOWN,
|
||||
SDL_Keycode.SDLK_LEFT,
|
||||
SDL_Keycode.SDLK_RIGHT,
|
||||
SDL_Keycode.SDLK_RETURN,
|
||||
SDL_Keycode.SDLK_ESCAPE,
|
||||
SDL_Keycode.SDLK_SPACE,
|
||||
SDL_Keycode.SDLK_TAB,
|
||||
SDL_Keycode.SDLK_BACKSPACE,
|
||||
SDL_Keycode.SDLK_INSERT,
|
||||
SDL_Keycode.SDLK_DELETE,
|
||||
SDL_Keycode.SDLK_PAGEUP,
|
||||
SDL_Keycode.SDLK_PAGEDOWN,
|
||||
SDL_Keycode.SDLK_HOME,
|
||||
SDL_Keycode.SDLK_END,
|
||||
SDL_Keycode.SDLK_CAPSLOCK,
|
||||
SDL_Keycode.SDLK_SCROLLLOCK,
|
||||
SDL_Keycode.SDLK_PRINTSCREEN,
|
||||
SDL_Keycode.SDLK_PAUSE,
|
||||
SDL_Keycode.SDLK_NUMLOCKCLEAR,
|
||||
SDL_Keycode.SDLK_CLEAR,
|
||||
SDL_Keycode.SDLK_KP_0,
|
||||
SDL_Keycode.SDLK_KP_1,
|
||||
SDL_Keycode.SDLK_KP_2,
|
||||
SDL_Keycode.SDLK_KP_3,
|
||||
SDL_Keycode.SDLK_KP_4,
|
||||
SDL_Keycode.SDLK_KP_5,
|
||||
SDL_Keycode.SDLK_KP_6,
|
||||
SDL_Keycode.SDLK_KP_7,
|
||||
SDL_Keycode.SDLK_KP_8,
|
||||
SDL_Keycode.SDLK_KP_9,
|
||||
SDL_Keycode.SDLK_KP_DIVIDE,
|
||||
SDL_Keycode.SDLK_KP_MULTIPLY,
|
||||
SDL_Keycode.SDLK_KP_MINUS,
|
||||
SDL_Keycode.SDLK_KP_PLUS,
|
||||
SDL_Keycode.SDLK_KP_DECIMAL,
|
||||
SDL_Keycode.SDLK_KP_ENTER,
|
||||
SDL_Keycode.SDLK_a,
|
||||
SDL_Keycode.SDLK_b,
|
||||
SDL_Keycode.SDLK_c,
|
||||
SDL_Keycode.SDLK_d,
|
||||
SDL_Keycode.SDLK_e,
|
||||
SDL_Keycode.SDLK_f,
|
||||
SDL_Keycode.SDLK_g,
|
||||
SDL_Keycode.SDLK_h,
|
||||
SDL_Keycode.SDLK_i,
|
||||
SDL_Keycode.SDLK_j,
|
||||
SDL_Keycode.SDLK_k,
|
||||
SDL_Keycode.SDLK_l,
|
||||
SDL_Keycode.SDLK_m,
|
||||
SDL_Keycode.SDLK_n,
|
||||
SDL_Keycode.SDLK_o,
|
||||
SDL_Keycode.SDLK_p,
|
||||
SDL_Keycode.SDLK_q,
|
||||
SDL_Keycode.SDLK_r,
|
||||
SDL_Keycode.SDLK_s,
|
||||
SDL_Keycode.SDLK_t,
|
||||
SDL_Keycode.SDLK_u,
|
||||
SDL_Keycode.SDLK_v,
|
||||
SDL_Keycode.SDLK_w,
|
||||
SDL_Keycode.SDLK_x,
|
||||
SDL_Keycode.SDLK_y,
|
||||
SDL_Keycode.SDLK_z,
|
||||
SDL_Keycode.SDLK_0,
|
||||
SDL_Keycode.SDLK_1,
|
||||
SDL_Keycode.SDLK_2,
|
||||
SDL_Keycode.SDLK_3,
|
||||
SDL_Keycode.SDLK_4,
|
||||
SDL_Keycode.SDLK_5,
|
||||
SDL_Keycode.SDLK_6,
|
||||
SDL_Keycode.SDLK_7,
|
||||
SDL_Keycode.SDLK_8,
|
||||
SDL_Keycode.SDLK_9,
|
||||
SDL_Keycode.SDLK_BACKQUOTE,
|
||||
SDL_Keycode.SDLK_BACKQUOTE,
|
||||
SDL_Keycode.SDLK_MINUS,
|
||||
SDL_Keycode.SDLK_PLUS,
|
||||
SDL_Keycode.SDLK_LEFTBRACKET,
|
||||
SDL_Keycode.SDLK_RIGHTBRACKET,
|
||||
SDL_Keycode.SDLK_SEMICOLON,
|
||||
SDL_Keycode.SDLK_QUOTE,
|
||||
SDL_Keycode.SDLK_COMMA,
|
||||
SDL_Keycode.SDLK_PERIOD,
|
||||
SDL_Keycode.SDLK_SLASH,
|
||||
SDL_Keycode.SDLK_BACKSLASH,
|
||||
|
||||
// Invalids
|
||||
SDL_Keycode.SDLK_0,
|
||||
};
|
||||
|
||||
public SDL2Keyboard(SDL2KeyboardDriver driver, string id, string name)
|
||||
{
|
||||
_driver = driver;
|
||||
Id = id;
|
||||
Name = name;
|
||||
_buttonsUserMapping = new List<ButtonMappingEntry>();
|
||||
}
|
||||
|
||||
private bool HasConfiguration => _configuration != null;
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public bool IsConnected => true;
|
||||
|
||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// No operations
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int ToSDL2Scancode(Key key)
|
||||
{
|
||||
if (key >= Key.Unknown && key <= Key.Menu)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key]);
|
||||
}
|
||||
|
||||
private static SDL_Keymod GetKeyboardModifierMask(Key key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Key.ShiftLeft:
|
||||
return SDL_Keymod.KMOD_LSHIFT;
|
||||
case Key.ShiftRight:
|
||||
return SDL_Keymod.KMOD_RSHIFT;
|
||||
case Key.ControlLeft:
|
||||
return SDL_Keymod.KMOD_LCTRL;
|
||||
case Key.ControlRight:
|
||||
return SDL_Keymod.KMOD_RCTRL;
|
||||
case Key.AltLeft:
|
||||
return SDL_Keymod.KMOD_LALT;
|
||||
case Key.AltRight:
|
||||
return SDL_Keymod.KMOD_RALT;
|
||||
case Key.WinLeft:
|
||||
return SDL_Keymod.KMOD_LGUI;
|
||||
case Key.WinRight:
|
||||
return SDL_Keymod.KMOD_RGUI;
|
||||
// NOTE: Menu key isn't supported by SDL2.
|
||||
case Key.Menu:
|
||||
default:
|
||||
return SDL_Keymod.KMOD_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
|
||||
{
|
||||
ReadOnlySpan<byte> rawKeyboardState;
|
||||
SDL_Keymod rawKeyboardModifierState = SDL_GetModState();
|
||||
|
||||
unsafe
|
||||
{
|
||||
IntPtr statePtr = SDL_GetKeyboardState(out int numKeys);
|
||||
|
||||
rawKeyboardState = new ReadOnlySpan<byte>((byte*)statePtr, numKeys);
|
||||
}
|
||||
|
||||
bool[] keysState = new bool[(int)Key.Count];
|
||||
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
{
|
||||
int index = ToSDL2Scancode(key);
|
||||
if (index == -1)
|
||||
{
|
||||
SDL_Keymod modifierMask = GetKeyboardModifierMask(key);
|
||||
|
||||
if (modifierMask == SDL_Keymod.KMOD_NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
keysState[(int)key] = (rawKeyboardModifierState & modifierMask) == modifierMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
keysState[(int)key] = rawKeyboardState[index] == 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new KeyboardStateSnapshot(keysState);
|
||||
}
|
||||
|
||||
private static float ConvertRawStickValue(short value)
|
||||
{
|
||||
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
|
||||
|
||||
return value * ConvertRate;
|
||||
}
|
||||
|
||||
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
|
||||
GamepadStateSnapshot result = default;
|
||||
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
if (!HasConfiguration)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
{
|
||||
if (entry.From == Key.Unknown || entry.From == Key.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));
|
||||
}
|
||||
}
|
||||
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetStateSnapshot()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public (float, float) GetStick(StickInputId inputId)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool IsPressed(GamepadButtonInputId inputId)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool IsPressed(Key key)
|
||||
{
|
||||
// We only implement GetKeyboardStateSnapshot.
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
lock (_userMappingLock)
|
||||
{
|
||||
_configuration = (StandardKeyboardInputConfig)configuration;
|
||||
|
||||
// First clear the buttons mapping
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
// Then configure left joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Finally configure right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
// No operations
|
||||
}
|
||||
|
||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
// No operations
|
||||
}
|
||||
|
||||
public Vector3 GetMotionData(MotionInputId inputId)
|
||||
{
|
||||
// No operations
|
||||
|
||||
return Vector3.Zero;
|
||||
}
|
||||
}
|
||||
}
|
54
src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs
Normal file
54
src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using Ryujinx.SDL2.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Input.SDL2
|
||||
{
|
||||
public class SDL2KeyboardDriver : IGamepadDriver
|
||||
{
|
||||
public SDL2KeyboardDriver()
|
||||
{
|
||||
SDL2Driver.Instance.Initialize();
|
||||
}
|
||||
|
||||
public string DriverName => "SDL2";
|
||||
|
||||
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
|
||||
|
||||
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
|
||||
|
||||
public event Action<string> OnGamepadConnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadDisconnected
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
SDL2Driver.Instance.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
if (!_keyboardIdentifers[0].Equals(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards");
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user