diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Input.SDL2 | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Input.SDL2')
| -rw-r--r-- | src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj | 13 | ||||
| -rw-r--r-- | src/Ryujinx.Input.SDL2/SDL2Gamepad.cs | 376 | ||||
| -rw-r--r-- | src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 148 | ||||
| -rw-r--r-- | src/Ryujinx.Input.SDL2/SDL2Keyboard.cs | 419 | ||||
| -rw-r--r-- | src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs | 54 |
5 files changed, 1010 insertions, 0 deletions
diff --git a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj new file mode 100644 index 00000000..817a96e2 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj @@ -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> diff --git a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs new file mode 100644 index 00000000..92552673 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -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; + } + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs new file mode 100644 index 00000000..d4086a10 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -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); + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs b/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs new file mode 100644 index 00000000..edadae15 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs b/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs new file mode 100644 index 00000000..e9361c24 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs @@ -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"); + } + } +}
\ No newline at end of file |
