From 12badfffb94cc2dda128df4895668d1e2716de24 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard 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 /// public HidControllerType ControllerType { get; private set; } + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public bool EnableKeyboard { get; private set; } + /// /// Keyboard control bindings /// @@ -224,6 +229,7 @@ namespace Ryujinx } device.Hid.InitilizePrimaryController(Instance.ControllerType); + device.Hid.InitilizeKeyboard(); } private class ConfigurationEnumFormatter : IJsonFormatter 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",