From 12badfffb94cc2dda128df4895668d1e2716de24 Mon Sep 17 00:00:00 2001
From: Thomas Guillemard <me@thog.eu>
Date: Fri, 3 May 2019 01:29:01 +0200
Subject: [PATCH] hid: Initial Keyboard Support (#684)

* hid: Initial Keyboard Support

This adds basic hid keyboard support.

Because of OpenTK.Input limitations, some specials keys aren't mapped.

* Fix code style

* Fix for loops code style

* Make hid keyboard feature toggleable

* Address comments

* Fix 2 other nits

* Apply jd's suggestion
---
 Ryujinx.HLE/Input/Hid.cs         |  32 ++++++
 Ryujinx.HLE/Input/HidKeyboard.cs |   8 ++
 Ryujinx.HLE/Input/HidValues.cs   |   3 +
 Ryujinx/Config.jsonc             |   5 +-
 Ryujinx/Configuration.cs         |   6 ++
 Ryujinx/Ui/GLScreen.cs           |  20 ++++
 Ryujinx/Ui/NpadKeyboard.cs       | 176 +++++++++++++++++++++++++++++++
 Ryujinx/_schema.json             |  12 +++
 8 files changed, 261 insertions(+), 1 deletion(-)
 create mode 100644 Ryujinx.HLE/Input/HidKeyboard.cs

diff --git a/Ryujinx.HLE/Input/Hid.cs b/Ryujinx.HLE/Input/Hid.cs
index 0154fc63cd..83c4ddea01 100644
--- a/Ryujinx.HLE/Input/Hid.cs
+++ b/Ryujinx.HLE/Input/Hid.cs
@@ -39,6 +39,11 @@ namespace Ryujinx.HLE.Input
             PrimaryController.Connect(controllerId);
         }
 
+        public void InitilizeKeyboard()
+        {
+            _device.Memory.FillWithZeros(HidPosition + HidKeyboardOffset, HidKeyboardSize);
+        }
+
         public HidControllerButtons UpdateStickButtons(
             HidJoystickPosition leftStick,
             HidJoystickPosition rightStick)
@@ -132,6 +137,33 @@ namespace Ryujinx.HLE.Input
             }
         }
 
+        public void WriteKeyboard(HidKeyboard keyboard)
+        {
+            long keyboardOffset = HidPosition + HidKeyboardOffset;
+            long lastEntry      = _device.Memory.ReadInt64(keyboardOffset + 0x10);
+            long currEntry      = (lastEntry + 1) % HidEntryCount;
+            long timestamp      = GetTimestamp();
+
+            _device.Memory.WriteInt64(keyboardOffset + 0x00, timestamp);
+            _device.Memory.WriteInt64(keyboardOffset + 0x08, HidEntryCount);
+            _device.Memory.WriteInt64(keyboardOffset + 0x10, currEntry);
+            _device.Memory.WriteInt64(keyboardOffset + 0x18, HidEntryCount - 1);
+
+            long keyboardEntryOffset = keyboardOffset + HidKeyboardHeaderSize;
+            long lastEntryOffset     = keyboardEntryOffset + lastEntry * HidKeyboardEntrySize;
+            long sampleCounter       = _device.Memory.ReadInt64(lastEntryOffset);
+
+            keyboardEntryOffset += currEntry * HidKeyboardEntrySize;
+            _device.Memory.WriteInt64(keyboardEntryOffset + 0x00, sampleCounter + 1);
+            _device.Memory.WriteInt64(keyboardEntryOffset + 0x08, sampleCounter);
+            _device.Memory.WriteInt64(keyboardEntryOffset + 0x10, keyboard.Modifier);
+
+            for (int i = 0; i < keyboard.Keys.Length; i++)
+            {
+                _device.Memory.WriteInt32(keyboardEntryOffset + 0x18 + (i * 4), keyboard.Keys[i]);
+            }
+        }
+
         internal static long GetTimestamp()
         {
             return PerformanceCounter.ElapsedMilliseconds * 19200;
diff --git a/Ryujinx.HLE/Input/HidKeyboard.cs b/Ryujinx.HLE/Input/HidKeyboard.cs
new file mode 100644
index 0000000000..a5b042a522
--- /dev/null
+++ b/Ryujinx.HLE/Input/HidKeyboard.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.Input
+{
+    public struct HidKeyboard
+    {
+        public int   Modifier;
+        public int[] Keys;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/Input/HidValues.cs b/Ryujinx.HLE/Input/HidValues.cs
index ae02bb42e9..06fe8fc0d4 100644
--- a/Ryujinx.HLE/Input/HidValues.cs
+++ b/Ryujinx.HLE/Input/HidValues.cs
@@ -26,6 +26,9 @@
         internal const int HidControllersSize       = 0x32000;
         internal const int HidUnkSection9Size       = 0x800;
 
+        internal const int HidKeyboardHeaderSize = 0x20;
+        internal const int HidKeyboardEntrySize  = 0x38;
+
         internal const int HidTouchHeaderSize = 0x28;
         internal const int HidTouchEntrySize  = 0x298;
 
diff --git a/Ryujinx/Config.jsonc b/Ryujinx/Config.jsonc
index 376cf502af..9dc46912b4 100644
--- a/Ryujinx/Config.jsonc
+++ b/Ryujinx/Config.jsonc
@@ -45,12 +45,15 @@
     "enable_aggressive_cpu_opts": true,
 
     // Enable or disable ignoring missing services, this may cause instability
-    "ignore_missing_services": false, 
+    "ignore_missing_services": false,
 
     // The primary controller's type
     // Supported Values: Handheld, ProController, NpadPair, NpadLeft, NpadRight
     "controller_type": "Handheld",
 
+    // Enable or disable "direct keyboard access (HID) support" (Provides games access to your keyboard as a text entry device).
+    "enable_keyboard": true,
+
     // Keyboard Controls
     // https://github.com/opentk/opentk/blob/master/src/OpenTK/Input/Key.cs
     "keyboard_controls": {
diff --git a/Ryujinx/Configuration.cs b/Ryujinx/Configuration.cs
index 560a6dab59..1c49de0803 100644
--- a/Ryujinx/Configuration.cs
+++ b/Ryujinx/Configuration.cs
@@ -102,6 +102,11 @@ namespace Ryujinx
         /// </summary>
         public HidControllerType ControllerType { get; private set; }
 
+        /// <summary>
+        /// Enable or disable keyboard support (Independent from controllers binding)
+        /// </summary>
+        public bool EnableKeyboard { get; private set; }
+
         /// <summary>
         /// Keyboard control bindings
         /// </summary>
@@ -224,6 +229,7 @@ namespace Ryujinx
             }
 
             device.Hid.InitilizePrimaryController(Instance.ControllerType);
+            device.Hid.InitilizeKeyboard();
         }
 
         private class ConfigurationEnumFormatter<T> : IJsonFormatter<T>
diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs
index c4fe65ab61..2d43ac07f4 100644
--- a/Ryujinx/Ui/GLScreen.cs
+++ b/Ryujinx/Ui/GLScreen.cs
@@ -145,6 +145,7 @@ namespace Ryujinx
             HidControllerButtons currentButton = 0;
             HidJoystickPosition  leftJoystick;
             HidJoystickPosition  rightJoystick;
+            HidKeyboard?         hidKeyboard = null;
 
             int leftJoystickDx  = 0;
             int leftJoystickDy  = 0;
@@ -165,9 +166,23 @@ namespace Ryujinx
                 currentHotkeyButtons = Configuration.Instance.KeyboardControls.GetHotkeyButtons(keyboard);
                 currentButton        = Configuration.Instance.KeyboardControls.GetButtons(keyboard);
 
+                if (Configuration.Instance.EnableKeyboard)
+                {
+                    hidKeyboard = Configuration.Instance.KeyboardControls.GetKeysDown(keyboard);
+                }
+
                 (leftJoystickDx, leftJoystickDy)   = Configuration.Instance.KeyboardControls.GetLeftStick(keyboard);
                 (rightJoystickDx, rightJoystickDy) = Configuration.Instance.KeyboardControls.GetRightStick(keyboard);
             }
+
+            if (!hidKeyboard.HasValue)
+            {
+                hidKeyboard = new HidKeyboard
+                {
+                    Modifier = 0,
+                    Keys     = new int[0x8]
+                };
+            }
             
             currentButton |= Configuration.Instance.GamepadControls.GetButtons();
 
@@ -255,6 +270,11 @@ namespace Ryujinx
                 _device.Hid.SetTouchPoints();
             }
 
+            if (Configuration.Instance.EnableKeyboard && hidKeyboard.HasValue)
+            {
+                _device.Hid.WriteKeyboard(hidKeyboard.Value);
+            }
+
             HidControllerBase controller = _device.Hid.PrimaryController;
 
             controller.SendInput(currentButton, leftJoystick, rightJoystick);
diff --git a/Ryujinx/Ui/NpadKeyboard.cs b/Ryujinx/Ui/NpadKeyboard.cs
index 8d003c2c6d..f25e604b19 100644
--- a/Ryujinx/Ui/NpadKeyboard.cs
+++ b/Ryujinx/Ui/NpadKeyboard.cs
@@ -1,5 +1,6 @@
 using OpenTK.Input;
 using Ryujinx.HLE.Input;
+using Ryujinx.Common.Logging;
 
 namespace Ryujinx.UI.Input
 {
@@ -116,5 +117,180 @@ namespace Ryujinx.UI.Input
 
             return buttons;
         }
+
+        class KeyMappingEntry
+        {
+            public Key TargetKey;
+            public byte Target;
+        }
+
+        private static readonly KeyMappingEntry[] KeyMapping = new KeyMappingEntry[]
+        {
+            new KeyMappingEntry { TargetKey = Key.A, Target = 0x4  },
+            new KeyMappingEntry { TargetKey = Key.B, Target = 0x5  },
+            new KeyMappingEntry { TargetKey = Key.C, Target = 0x6  },
+            new KeyMappingEntry { TargetKey = Key.D, Target = 0x7  },
+            new KeyMappingEntry { TargetKey = Key.E, Target = 0x8  },
+            new KeyMappingEntry { TargetKey = Key.F, Target = 0x9  },
+            new KeyMappingEntry { TargetKey = Key.G, Target = 0xA  },
+            new KeyMappingEntry { TargetKey = Key.H, Target = 0xB  },
+            new KeyMappingEntry { TargetKey = Key.I, Target = 0xC  },
+            new KeyMappingEntry { TargetKey = Key.J, Target = 0xD  },
+            new KeyMappingEntry { TargetKey = Key.K, Target = 0xE  },
+            new KeyMappingEntry { TargetKey = Key.L, Target = 0xF  },
+            new KeyMappingEntry { TargetKey = Key.M, Target = 0x10 },
+            new KeyMappingEntry { TargetKey = Key.N, Target = 0x11 },
+            new KeyMappingEntry { TargetKey = Key.O, Target = 0x12 },
+            new KeyMappingEntry { TargetKey = Key.P, Target = 0x13 },
+            new KeyMappingEntry { TargetKey = Key.Q, Target = 0x14 },
+            new KeyMappingEntry { TargetKey = Key.R, Target = 0x15 },
+            new KeyMappingEntry { TargetKey = Key.S, Target = 0x16 },
+            new KeyMappingEntry { TargetKey = Key.T, Target = 0x17 },
+            new KeyMappingEntry { TargetKey = Key.U, Target = 0x18 },
+            new KeyMappingEntry { TargetKey = Key.V, Target = 0x19 },
+            new KeyMappingEntry { TargetKey = Key.W, Target = 0x1A },
+            new KeyMappingEntry { TargetKey = Key.X, Target = 0x1B },
+            new KeyMappingEntry { TargetKey = Key.Y, Target = 0x1C },
+            new KeyMappingEntry { TargetKey = Key.Z, Target = 0x1D },
+
+            new KeyMappingEntry { TargetKey = Key.Number1, Target = 0x1E },
+            new KeyMappingEntry { TargetKey = Key.Number2, Target = 0x1F },
+            new KeyMappingEntry { TargetKey = Key.Number3, Target = 0x20 },
+            new KeyMappingEntry { TargetKey = Key.Number4, Target = 0x21 },
+            new KeyMappingEntry { TargetKey = Key.Number5, Target = 0x22 },
+            new KeyMappingEntry { TargetKey = Key.Number6, Target = 0x23 },
+            new KeyMappingEntry { TargetKey = Key.Number7, Target = 0x24 },
+            new KeyMappingEntry { TargetKey = Key.Number8, Target = 0x25 },
+            new KeyMappingEntry { TargetKey = Key.Number9, Target = 0x26 },
+            new KeyMappingEntry { TargetKey = Key.Number0, Target = 0x27 },
+
+            new KeyMappingEntry { TargetKey = Key.Enter,        Target = 0x28 },
+            new KeyMappingEntry { TargetKey = Key.Escape,       Target = 0x29 },
+            new KeyMappingEntry { TargetKey = Key.BackSpace,    Target = 0x2A },
+            new KeyMappingEntry { TargetKey = Key.Tab,          Target = 0x2B },
+            new KeyMappingEntry { TargetKey = Key.Space,        Target = 0x2C },
+            new KeyMappingEntry { TargetKey = Key.Minus,        Target = 0x2D },
+            new KeyMappingEntry { TargetKey = Key.Plus,         Target = 0x2E },
+            new KeyMappingEntry { TargetKey = Key.BracketLeft,  Target = 0x2F },
+            new KeyMappingEntry { TargetKey = Key.BracketRight, Target = 0x30 },
+            new KeyMappingEntry { TargetKey = Key.BackSlash,    Target = 0x31 },
+            new KeyMappingEntry { TargetKey = Key.Tilde,        Target = 0x32 },
+            new KeyMappingEntry { TargetKey = Key.Semicolon,    Target = 0x33 },
+            new KeyMappingEntry { TargetKey = Key.Quote,        Target = 0x34 },
+            new KeyMappingEntry { TargetKey = Key.Grave,        Target = 0x35 },
+            new KeyMappingEntry { TargetKey = Key.Comma,        Target = 0x36 },
+            new KeyMappingEntry { TargetKey = Key.Period,       Target = 0x37 },
+            new KeyMappingEntry { TargetKey = Key.Slash,        Target = 0x38 },
+            new KeyMappingEntry { TargetKey = Key.CapsLock,     Target = 0x39 },
+
+            new KeyMappingEntry { TargetKey = Key.F1,  Target = 0x3a },
+            new KeyMappingEntry { TargetKey = Key.F2,  Target = 0x3b },
+            new KeyMappingEntry { TargetKey = Key.F3,  Target = 0x3c },
+            new KeyMappingEntry { TargetKey = Key.F4,  Target = 0x3d },
+            new KeyMappingEntry { TargetKey = Key.F5,  Target = 0x3e },
+            new KeyMappingEntry { TargetKey = Key.F6,  Target = 0x3f },
+            new KeyMappingEntry { TargetKey = Key.F7,  Target = 0x40 },
+            new KeyMappingEntry { TargetKey = Key.F8,  Target = 0x41 },
+            new KeyMappingEntry { TargetKey = Key.F9,  Target = 0x42 },
+            new KeyMappingEntry { TargetKey = Key.F10, Target = 0x43 },
+            new KeyMappingEntry { TargetKey = Key.F11, Target = 0x44 },
+            new KeyMappingEntry { TargetKey = Key.F12, Target = 0x45 },
+
+            new KeyMappingEntry { TargetKey = Key.PrintScreen, Target = 0x46 },
+            new KeyMappingEntry { TargetKey = Key.ScrollLock,  Target = 0x47 },
+            new KeyMappingEntry { TargetKey = Key.Pause,       Target = 0x48 },
+            new KeyMappingEntry { TargetKey = Key.Insert,      Target = 0x49 },
+            new KeyMappingEntry { TargetKey = Key.Home,        Target = 0x4A },
+            new KeyMappingEntry { TargetKey = Key.PageUp,      Target = 0x4B },
+            new KeyMappingEntry { TargetKey = Key.Delete,      Target = 0x4C },
+            new KeyMappingEntry { TargetKey = Key.End,         Target = 0x4D },
+            new KeyMappingEntry { TargetKey = Key.PageDown,    Target = 0x4E },
+            new KeyMappingEntry { TargetKey = Key.Right,       Target = 0x4F },
+            new KeyMappingEntry { TargetKey = Key.Left,        Target = 0x50 },
+            new KeyMappingEntry { TargetKey = Key.Down,        Target = 0x51 },
+            new KeyMappingEntry { TargetKey = Key.Up,          Target = 0x52 },
+
+            new KeyMappingEntry { TargetKey = Key.NumLock,        Target = 0x53 },
+            new KeyMappingEntry { TargetKey = Key.KeypadDivide,   Target = 0x54 },
+            new KeyMappingEntry { TargetKey = Key.KeypadMultiply, Target = 0x55 },
+            new KeyMappingEntry { TargetKey = Key.KeypadMinus,    Target = 0x56 },
+            new KeyMappingEntry { TargetKey = Key.KeypadPlus,     Target = 0x57 },
+            new KeyMappingEntry { TargetKey = Key.KeypadEnter,    Target = 0x58 },
+            new KeyMappingEntry { TargetKey = Key.Keypad1,        Target = 0x59 },
+            new KeyMappingEntry { TargetKey = Key.Keypad2,        Target = 0x5A },
+            new KeyMappingEntry { TargetKey = Key.Keypad3,        Target = 0x5B },
+            new KeyMappingEntry { TargetKey = Key.Keypad4,        Target = 0x5C },
+            new KeyMappingEntry { TargetKey = Key.Keypad5,        Target = 0x5D },
+            new KeyMappingEntry { TargetKey = Key.Keypad6,        Target = 0x5E },
+            new KeyMappingEntry { TargetKey = Key.Keypad7,        Target = 0x5F },
+            new KeyMappingEntry { TargetKey = Key.Keypad8,        Target = 0x60 },
+            new KeyMappingEntry { TargetKey = Key.Keypad9,        Target = 0x61 },
+            new KeyMappingEntry { TargetKey = Key.Keypad0,        Target = 0x62 },
+            new KeyMappingEntry { TargetKey = Key.KeypadPeriod,   Target = 0x63 },
+
+            new KeyMappingEntry { TargetKey = Key.NonUSBackSlash, Target = 0x64 },
+
+            new KeyMappingEntry { TargetKey = Key.F13, Target = 0x68 },
+            new KeyMappingEntry { TargetKey = Key.F14, Target = 0x69 },
+            new KeyMappingEntry { TargetKey = Key.F15, Target = 0x6A },
+            new KeyMappingEntry { TargetKey = Key.F16, Target = 0x6B },
+            new KeyMappingEntry { TargetKey = Key.F17, Target = 0x6C },
+            new KeyMappingEntry { TargetKey = Key.F18, Target = 0x6D },
+            new KeyMappingEntry { TargetKey = Key.F19, Target = 0x6E },
+            new KeyMappingEntry { TargetKey = Key.F20, Target = 0x6F },
+            new KeyMappingEntry { TargetKey = Key.F21, Target = 0x70 },
+            new KeyMappingEntry { TargetKey = Key.F22, Target = 0x71 },
+            new KeyMappingEntry { TargetKey = Key.F23, Target = 0x72 },
+            new KeyMappingEntry { TargetKey = Key.F24, Target = 0x73 },
+
+            new KeyMappingEntry { TargetKey = Key.ControlLeft,  Target = 0xE0 },
+            new KeyMappingEntry { TargetKey = Key.ShiftLeft,    Target = 0xE1 },
+            new KeyMappingEntry { TargetKey = Key.AltLeft,      Target = 0xE2 },
+            new KeyMappingEntry { TargetKey = Key.WinLeft,      Target = 0xE3 },
+            new KeyMappingEntry { TargetKey = Key.ControlRight, Target = 0xE4 },
+            new KeyMappingEntry { TargetKey = Key.ShiftRight,   Target = 0xE5 },
+            new KeyMappingEntry { TargetKey = Key.AltRight,     Target = 0xE6 },
+            new KeyMappingEntry { TargetKey = Key.WinRight,     Target = 0xE7 },
+        };
+
+        private static readonly KeyMappingEntry[] KeyModifierMapping = new KeyMappingEntry[]
+        {
+            new KeyMappingEntry { TargetKey = Key.ControlLeft,  Target = 0 },
+            new KeyMappingEntry { TargetKey = Key.ShiftLeft,    Target = 1 },
+            new KeyMappingEntry { TargetKey = Key.AltLeft,      Target = 2 },
+            new KeyMappingEntry { TargetKey = Key.WinLeft,      Target = 3 },
+            new KeyMappingEntry { TargetKey = Key.ControlRight, Target = 4 },
+            new KeyMappingEntry { TargetKey = Key.ShiftRight,   Target = 5 },
+            new KeyMappingEntry { TargetKey = Key.AltRight,     Target = 6 },
+            new KeyMappingEntry { TargetKey = Key.WinRight,     Target = 7 },
+            new KeyMappingEntry { TargetKey = Key.CapsLock,     Target = 8 },
+            new KeyMappingEntry { TargetKey = Key.ScrollLock,   Target = 9 },
+            new KeyMappingEntry { TargetKey = Key.NumLock,      Target = 10 },
+        };
+
+        public HidKeyboard GetKeysDown(KeyboardState keyboard)
+        {
+            HidKeyboard hidKeyboard = new HidKeyboard
+            {
+                    Modifier = 0,
+                    Keys     = new int[0x8]
+            };
+
+            foreach (KeyMappingEntry entry in KeyMapping)
+            {
+                int value = keyboard[entry.TargetKey] ? 1 : 0;
+
+                hidKeyboard.Keys[entry.Target / 0x20] |= (value << (entry.Target % 0x20));
+            }
+
+            foreach (KeyMappingEntry entry in KeyModifierMapping)
+            {
+                int value = keyboard[entry.TargetKey] ? 1 : 0;
+
+                hidKeyboard.Modifier |= value << entry.Target;
+            }
+
+            return hidKeyboard;
+        }
     }
 }
diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json
index ccb42dadf6..f07f930b6f 100644
--- a/Ryujinx/_schema.json
+++ b/Ryujinx/_schema.json
@@ -19,6 +19,7 @@
     "enable_fs_integrity_checks",
     "enable_aggressive_cpu_opts",
     "controller_type",
+    "enable_keyboard",
     "keyboard_controls",
     "gamepad_controls"
   ],
@@ -442,6 +443,17 @@
         "NpadRight"
       ]
     },
+    "enable_keyboard": {
+      "$id": "#/properties/enable_keyboard",
+      "type": "boolean",
+      "title": "(HID) Keyboard Enable",
+      "description": "Enable or disable direct keyboard access (HID) support (Provides games access to your keyboard as a text entry device).",
+      "default": true,
+      "examples": [
+        true,
+        false
+      ]
+    },
     "keyboard_controls": {
       "$id": "#/properties/keyboard_controls",
       "type": "object",