From 2c3dab69860e8a7b23cfaba603b3bb31b75a168b Mon Sep 17 00:00:00 2001
From: Mary <me@thog.eu>
Date: Fri, 21 May 2021 01:16:34 +0200
Subject: [PATCH] input: Implement a SDL2 keyboard backend (#2277)

* 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
---
 Ryujinx.Input.SDL2/SDL2Keyboard.cs      | 420 ++++++++++++++++++++++++
 Ryujinx.Input.SDL2/SDLKeyboardDriver.cs |  54 +++
 2 files changed, 474 insertions(+)
 create mode 100644 Ryujinx.Input.SDL2/SDL2Keyboard.cs
 create mode 100644 Ryujinx.Input.SDL2/SDLKeyboardDriver.cs

diff --git a/Ryujinx.Input.SDL2/SDL2Keyboard.cs b/Ryujinx.Input.SDL2/SDL2Keyboard.cs
new file mode 100644
index 0000000000..b8ad82811d
--- /dev/null
+++ b/Ryujinx.Input.SDL2/SDL2Keyboard.cs
@@ -0,0 +1,420 @@
+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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs b/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs
new file mode 100644
index 0000000000..e9361c248a
--- /dev/null
+++ b/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