2c3dab6986
* input: Implement a SDL2 keyboard backend Add a new keyboard backend to the Ryujinx.Input.SDL2 project. This is currently unused. * Address Ac_k's comments * Address gdkchan's comments
420 lines
No EOL
15 KiB
C#
420 lines
No EOL
15 KiB
C#
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,
|
|
|
|
// F25-F35 not exposed on SDL2
|
|
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;
|
|
}
|
|
}
|
|
} |