From 6cb22c9d38622225f9f787f483bd73369774cf77 Mon Sep 17 00:00:00 2001 From: Mary Date: Wed, 14 Apr 2021 12:28:43 +0200 Subject: Miria: The Death of OpenTK 3 (#2194) * openal: Update to OpenTK 4 * Ryujinx.Graphics.OpenGL: Update to OpenTK 4 * Entirely removed OpenTK 3, still wip * Use SPB for context creation and handling Still need to test on GLX and readd input support * Start implementing a new input system So far only gamepad are supported, no configuration possible via UI but detected via hotplug/removal Button mapping backend is implemented TODO: front end, configuration handling and configuration migration TODO: keyboard support * Enforce RGB only framebuffer on the GLWidget Fix possible transparent window * Implement UI gamepad frontend Also fix bad mapping of minus button and ensure gamepad config is updated in real time * Handle controller being disconnected and reconnected again * Revert "Enforce RGB only framebuffer on the GLWidget" This reverts commit 0949715d1a03ec793e35e37f7b610cbff2d63965. * Fix first color clear * Filter SDL2 events a bit * Start working on the keyboard detail - Rework configuration classes a bit to be more clean. - Integrate fully the keyboard configuration to the front end (TODO: assigner) - Start skeleton for the GTK3 keyboard driver * Add KeyboardStateSnapshot and its integration * Implement keyboard assigner and GTK3 key mapping TODO: controller configuration mapping and IGamepad implementation for keyboard * Add missing SR and SL definitions * Fix copy pasta mistake on config for previous commit * Implement IGamepad interface for GTK3 keyboard * Fix some implementation still being commented in the controller ui for keyboard * Port screen handle code * Remove all configuration management code and move HidNew to Hid * Rename InputConfigNew to InputConfig * Add a version field to the input config * Prepare serialization and deserialization of new input config and migrate profile loading and saving * Support input configuration saving to config and bump config version to 23. * Clean up in ConfigurationState * Reference SPB via a nuget package * Move new input system to Ryujinx.Input project and SDL2 detail to Ryujinx.Input.SDL2 * move GTK3 input to the right directory * Fix triggers on SDL2 * Update to SDL2 2.0.14 via our own fork * Update buttons definition for SDL2 2.0.14 and report gamepad features * Implement motion support again with SDL2 TODO: cemu hooks integration * Switch to latest of nightly SDL2 * SDL2: Fix bugs in gamepad id matching allowing different gamepad to match on the same device index * Ensure values are set in UI when the gamepad get hot plugged * Avoid trying to add controllers in the Update method and don't open SDL2 gamepad instance before checking ids This fixes permanent rumble of pro controller in some hotplug scenario * Fix more UI bugs * Move legcay motion code around before reintegration * gamecontroller UI tweaks here and there * Hide Motion on non motion configurations * Update the TODO grave Some TODO were fixed long time ago or are quite oudated... * Integrate cemu hooks motion configuration * Integrate cemu hooks configuration options to the UI again * cemuhooks => cemuhooks * Add cemu hook support again * Fix regression on normal motion and fix some very nasty bugs around * Fix for XCB multithreads issue on Linux * Enable motion by default * Block inputs in the main view when in the controller configuration window * Some fixes for the controller ui again * Add joycon support and fixes other hints * Bug fixes and clean up - Invert default mapping if not a Nintendo controller - Keep alive the controller being selected on the controller window (allow to avoid big delay for controller needing time to init when doing button assignment) - Clean up hints in use - Remove debug logs around - Fixes potential double free with SDL2Gamepad * Move the button assigner and motion logic to the Ryujinx.Input project * Reimplement raw keyboard hle input Also move out the logic of the hotkeys * Move all remaining Input manager stuffs to the Ryujinx.Input project * Increment configuration version yet again because of master changes * Ensure input config isn't null when not present * Fixes for VS not being nice * Fix broken gamepad caching logic causing crashes on ui * Ensure the background context is destroyed * Update dependencies * Readd retrocompat with old format of the config to avoid parsing and crashes on those versions Also updated the debug Config.json * Document new input APIs * Isolate SDL2Driver to the project and remove external export of it * Add support for external gamepad db mappings on SDL2 * Last clean up before PR * Addresses first part of comments * Address gdkchan's comments * Do not use JsonException * Last comment fixes --- Ryujinx.Input/HLE/InputManager.cs | 35 +++ Ryujinx.Input/HLE/NpadController.cs | 496 ++++++++++++++++++++++++++++++++++++ Ryujinx.Input/HLE/NpadManager.cs | 222 ++++++++++++++++ 3 files changed, 753 insertions(+) create mode 100644 Ryujinx.Input/HLE/InputManager.cs create mode 100644 Ryujinx.Input/HLE/NpadController.cs create mode 100644 Ryujinx.Input/HLE/NpadManager.cs (limited to 'Ryujinx.Input/HLE') diff --git a/Ryujinx.Input/HLE/InputManager.cs b/Ryujinx.Input/HLE/InputManager.cs new file mode 100644 index 00000000..277e8ec2 --- /dev/null +++ b/Ryujinx.Input/HLE/InputManager.cs @@ -0,0 +1,35 @@ +using System; + +namespace Ryujinx.Input.HLE +{ + public class InputManager : IDisposable + { + public IGamepadDriver KeyboardDriver { get; private set; } + public IGamepadDriver GamepadDriver { get; private set; } + + public InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver) + { + KeyboardDriver = keyboardDriver; + GamepadDriver = gamepadDriver; + } + + public NpadManager CreateNpadManager() + { + return new NpadManager(KeyboardDriver, GamepadDriver); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + KeyboardDriver?.Dispose(); + GamepadDriver?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Input/HLE/NpadController.cs b/Ryujinx.Input/HLE/NpadController.cs new file mode 100644 index 00000000..d3553d64 --- /dev/null +++ b/Ryujinx.Input/HLE/NpadController.cs @@ -0,0 +1,496 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.HLE.HOS.Services.Hid; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client; +using ConfigControllerType = Ryujinx.Common.Configuration.Hid.ControllerType; + +namespace Ryujinx.Input.HLE +{ + public class NpadController : IDisposable + { + private class HLEButtonMappingEntry + { + public readonly GamepadButtonInputId DriverInputId; + public readonly ControllerKeys HLEInput; + + public HLEButtonMappingEntry(GamepadButtonInputId driverInputId, ControllerKeys hleInput) + { + DriverInputId = driverInputId; + HLEInput = hleInput; + } + } + + private static readonly HLEButtonMappingEntry[] _hleButtonMapping = new HLEButtonMappingEntry[] + { + new HLEButtonMappingEntry(GamepadButtonInputId.A, ControllerKeys.A), + new HLEButtonMappingEntry(GamepadButtonInputId.B, ControllerKeys.B), + new HLEButtonMappingEntry(GamepadButtonInputId.X, ControllerKeys.X), + new HLEButtonMappingEntry(GamepadButtonInputId.Y, ControllerKeys.Y), + new HLEButtonMappingEntry(GamepadButtonInputId.LeftStick, ControllerKeys.LStick), + new HLEButtonMappingEntry(GamepadButtonInputId.RightStick, ControllerKeys.RStick), + new HLEButtonMappingEntry(GamepadButtonInputId.LeftShoulder, ControllerKeys.L), + new HLEButtonMappingEntry(GamepadButtonInputId.RightShoulder, ControllerKeys.R), + new HLEButtonMappingEntry(GamepadButtonInputId.LeftTrigger, ControllerKeys.Zl), + new HLEButtonMappingEntry(GamepadButtonInputId.RightTrigger, ControllerKeys.Zr), + new HLEButtonMappingEntry(GamepadButtonInputId.DpadUp, ControllerKeys.DpadUp), + new HLEButtonMappingEntry(GamepadButtonInputId.DpadDown, ControllerKeys.DpadDown), + new HLEButtonMappingEntry(GamepadButtonInputId.DpadLeft, ControllerKeys.DpadLeft), + new HLEButtonMappingEntry(GamepadButtonInputId.DpadRight, ControllerKeys.DpadRight), + new HLEButtonMappingEntry(GamepadButtonInputId.Minus, ControllerKeys.Minus), + new HLEButtonMappingEntry(GamepadButtonInputId.Plus, ControllerKeys.Plus), + + new HLEButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, ControllerKeys.SlLeft), + new HLEButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, ControllerKeys.SrLeft), + new HLEButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, ControllerKeys.SlRight), + new HLEButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, ControllerKeys.SrRight), + }; + + private class HLEKeyboardMappingEntry + { + public readonly Key TargetKey; + public readonly byte Target; + + public HLEKeyboardMappingEntry(Key targetKey, byte target) + { + TargetKey = targetKey; + Target = target; + } + } + + private static readonly HLEKeyboardMappingEntry[] KeyMapping = new HLEKeyboardMappingEntry[] + { + new HLEKeyboardMappingEntry(Key.A, 0x4), + new HLEKeyboardMappingEntry(Key.B, 0x5), + new HLEKeyboardMappingEntry(Key.C, 0x6), + new HLEKeyboardMappingEntry(Key.D, 0x7), + new HLEKeyboardMappingEntry(Key.E, 0x8), + new HLEKeyboardMappingEntry(Key.F, 0x9), + new HLEKeyboardMappingEntry(Key.G, 0xA), + new HLEKeyboardMappingEntry(Key.H, 0xB), + new HLEKeyboardMappingEntry(Key.I, 0xC), + new HLEKeyboardMappingEntry(Key.J, 0xD), + new HLEKeyboardMappingEntry(Key.K, 0xE), + new HLEKeyboardMappingEntry(Key.L, 0xF), + new HLEKeyboardMappingEntry(Key.M, 0x10), + new HLEKeyboardMappingEntry(Key.N, 0x11), + new HLEKeyboardMappingEntry(Key.O, 0x12), + new HLEKeyboardMappingEntry(Key.P, 0x13), + new HLEKeyboardMappingEntry(Key.Q, 0x14), + new HLEKeyboardMappingEntry(Key.R, 0x15), + new HLEKeyboardMappingEntry(Key.S, 0x16), + new HLEKeyboardMappingEntry(Key.T, 0x17), + new HLEKeyboardMappingEntry(Key.U, 0x18), + new HLEKeyboardMappingEntry(Key.V, 0x19), + new HLEKeyboardMappingEntry(Key.W, 0x1A), + new HLEKeyboardMappingEntry(Key.X, 0x1B), + new HLEKeyboardMappingEntry(Key.Y, 0x1C), + new HLEKeyboardMappingEntry(Key.Z, 0x1D), + + new HLEKeyboardMappingEntry(Key.Number1, 0x1E), + new HLEKeyboardMappingEntry(Key.Number2, 0x1F), + new HLEKeyboardMappingEntry(Key.Number3, 0x20), + new HLEKeyboardMappingEntry(Key.Number4, 0x21), + new HLEKeyboardMappingEntry(Key.Number5, 0x22), + new HLEKeyboardMappingEntry(Key.Number6, 0x23), + new HLEKeyboardMappingEntry(Key.Number7, 0x24), + new HLEKeyboardMappingEntry(Key.Number8, 0x25), + new HLEKeyboardMappingEntry(Key.Number9, 0x26), + new HLEKeyboardMappingEntry(Key.Number0, 0x27), + + new HLEKeyboardMappingEntry(Key.Enter, 0x28), + new HLEKeyboardMappingEntry(Key.Escape, 0x29), + new HLEKeyboardMappingEntry(Key.BackSpace, 0x2A), + new HLEKeyboardMappingEntry(Key.Tab, 0x2B), + new HLEKeyboardMappingEntry(Key.Space, 0x2C), + new HLEKeyboardMappingEntry(Key.Minus, 0x2D), + new HLEKeyboardMappingEntry(Key.Plus, 0x2E), + new HLEKeyboardMappingEntry(Key.BracketLeft, 0x2F), + new HLEKeyboardMappingEntry(Key.BracketRight, 0x30), + new HLEKeyboardMappingEntry(Key.BackSlash, 0x31), + new HLEKeyboardMappingEntry(Key.Tilde, 0x32), + new HLEKeyboardMappingEntry(Key.Semicolon, 0x33), + new HLEKeyboardMappingEntry(Key.Quote, 0x34), + new HLEKeyboardMappingEntry(Key.Grave, 0x35), + new HLEKeyboardMappingEntry(Key.Comma, 0x36), + new HLEKeyboardMappingEntry(Key.Period, 0x37), + new HLEKeyboardMappingEntry(Key.Slash, 0x38), + new HLEKeyboardMappingEntry(Key.CapsLock, 0x39), + + new HLEKeyboardMappingEntry(Key.F1, 0x3a), + new HLEKeyboardMappingEntry(Key.F2, 0x3b), + new HLEKeyboardMappingEntry(Key.F3, 0x3c), + new HLEKeyboardMappingEntry(Key.F4, 0x3d), + new HLEKeyboardMappingEntry(Key.F5, 0x3e), + new HLEKeyboardMappingEntry(Key.F6, 0x3f), + new HLEKeyboardMappingEntry(Key.F7, 0x40), + new HLEKeyboardMappingEntry(Key.F8, 0x41), + new HLEKeyboardMappingEntry(Key.F9, 0x42), + new HLEKeyboardMappingEntry(Key.F10, 0x43), + new HLEKeyboardMappingEntry(Key.F11, 0x44), + new HLEKeyboardMappingEntry(Key.F12, 0x45), + + new HLEKeyboardMappingEntry(Key.PrintScreen, 0x46), + new HLEKeyboardMappingEntry(Key.ScrollLock, 0x47), + new HLEKeyboardMappingEntry(Key.Pause, 0x48), + new HLEKeyboardMappingEntry(Key.Insert, 0x49), + new HLEKeyboardMappingEntry(Key.Home, 0x4A), + new HLEKeyboardMappingEntry(Key.PageUp, 0x4B), + new HLEKeyboardMappingEntry(Key.Delete, 0x4C), + new HLEKeyboardMappingEntry(Key.End, 0x4D), + new HLEKeyboardMappingEntry(Key.PageDown, 0x4E), + new HLEKeyboardMappingEntry(Key.Right, 0x4F), + new HLEKeyboardMappingEntry(Key.Left, 0x50), + new HLEKeyboardMappingEntry(Key.Down, 0x51), + new HLEKeyboardMappingEntry(Key.Up, 0x52), + + new HLEKeyboardMappingEntry(Key.NumLock, 0x53), + new HLEKeyboardMappingEntry(Key.KeypadDivide, 0x54), + new HLEKeyboardMappingEntry(Key.KeypadMultiply, 0x55), + new HLEKeyboardMappingEntry(Key.KeypadSubtract, 0x56), + new HLEKeyboardMappingEntry(Key.KeypadAdd, 0x57), + new HLEKeyboardMappingEntry(Key.KeypadEnter, 0x58), + new HLEKeyboardMappingEntry(Key.Keypad1, 0x59), + new HLEKeyboardMappingEntry(Key.Keypad2, 0x5A), + new HLEKeyboardMappingEntry(Key.Keypad3, 0x5B), + new HLEKeyboardMappingEntry(Key.Keypad4, 0x5C), + new HLEKeyboardMappingEntry(Key.Keypad5, 0x5D), + new HLEKeyboardMappingEntry(Key.Keypad6, 0x5E), + new HLEKeyboardMappingEntry(Key.Keypad7, 0x5F), + new HLEKeyboardMappingEntry(Key.Keypad8, 0x60), + new HLEKeyboardMappingEntry(Key.Keypad9, 0x61), + new HLEKeyboardMappingEntry(Key.Keypad0, 0x62), + new HLEKeyboardMappingEntry(Key.KeypadDecimal, 0x63), + + new HLEKeyboardMappingEntry(Key.F13, 0x68), + new HLEKeyboardMappingEntry(Key.F14, 0x69), + new HLEKeyboardMappingEntry(Key.F15, 0x6A), + new HLEKeyboardMappingEntry(Key.F16, 0x6B), + new HLEKeyboardMappingEntry(Key.F17, 0x6C), + new HLEKeyboardMappingEntry(Key.F18, 0x6D), + new HLEKeyboardMappingEntry(Key.F19, 0x6E), + new HLEKeyboardMappingEntry(Key.F20, 0x6F), + new HLEKeyboardMappingEntry(Key.F21, 0x70), + new HLEKeyboardMappingEntry(Key.F22, 0x71), + new HLEKeyboardMappingEntry(Key.F23, 0x72), + new HLEKeyboardMappingEntry(Key.F24, 0x73), + + new HLEKeyboardMappingEntry(Key.ControlLeft, 0xE0), + new HLEKeyboardMappingEntry(Key.ShiftLeft, 0xE1), + new HLEKeyboardMappingEntry(Key.AltLeft, 0xE2), + new HLEKeyboardMappingEntry(Key.WinLeft, 0xE3), + new HLEKeyboardMappingEntry(Key.ControlRight, 0xE4), + new HLEKeyboardMappingEntry(Key.ShiftRight, 0xE5), + new HLEKeyboardMappingEntry(Key.AltRight, 0xE6), + new HLEKeyboardMappingEntry(Key.WinRight, 0xE7), + }; + + private static readonly HLEKeyboardMappingEntry[] KeyModifierMapping = new HLEKeyboardMappingEntry[] + { + new HLEKeyboardMappingEntry(Key.ControlLeft, 0), + new HLEKeyboardMappingEntry(Key.ShiftLeft, 1), + new HLEKeyboardMappingEntry(Key.AltLeft, 2), + new HLEKeyboardMappingEntry(Key.WinLeft, 3), + new HLEKeyboardMappingEntry(Key.ControlRight, 4), + new HLEKeyboardMappingEntry(Key.ShiftRight, 5), + new HLEKeyboardMappingEntry(Key.AltRight, 6), + new HLEKeyboardMappingEntry(Key.WinRight, 7), + new HLEKeyboardMappingEntry(Key.CapsLock, 8), + new HLEKeyboardMappingEntry(Key.ScrollLock, 9), + new HLEKeyboardMappingEntry(Key.NumLock, 10), + }; + + private bool _isValid; + private string _id; + + private MotionInput _motionInput; + + private IGamepad _gamepad; + private InputConfig _config; + + public IGamepadDriver GamepadDriver { get; private set; } + public GamepadStateSnapshot State { get; private set; } + + public string Id => _id; + + private CemuHookClient _cemuHookClient; + + public NpadController(CemuHookClient cemuHookClient) + { + State = default; + _id = null; + _isValid = false; + _cemuHookClient = cemuHookClient; + } + + public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config) + { + GamepadDriver = gamepadDriver; + + _gamepad?.Dispose(); + + _id = config.Id; + _gamepad = GamepadDriver.GetGamepad(_id); + _isValid = _gamepad != null; + + UpdateUserConfiguration(config); + + return _isValid; + } + + public void UpdateUserConfiguration(InputConfig config) + { + if (config is StandardControllerInputConfig controllerConfig) + { + bool needsMotionInputUpdate = _config == null || (_config is StandardControllerInputConfig oldControllerConfig && + (oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && + (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); + + if (needsMotionInputUpdate) + { + UpdateMotionInput(controllerConfig.Motion); + } + } + else + { + // Non-controller doesn't have motions. + _motionInput = null; + } + + _config = config; + + if (_isValid) + { + _gamepad.SetConfiguration(config); + } + } + + private void UpdateMotionInput(MotionConfigController motionConfig) + { + if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook) + { + _motionInput = new MotionInput(); + } + else + { + _motionInput = null; + } + } + + public void Update() + { + if (_isValid && GamepadDriver != null) + { + State = _gamepad.GetMappedStateSnapshot(); + + if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) + { + if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) + { + if (_gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) + { + Vector3 accelerometer = _gamepad.GetMotionData(MotionInputId.Accelerometer); + Vector3 gyroscope = _gamepad.GetMotionData(MotionInputId.Gyroscope); + + accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y); + gyroscope = new Vector3(gyroscope.X, gyroscope.Z, gyroscope.Y); + + _motionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone); + } + } + else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig) + { + int clientId = (int)controllerConfig.PlayerIndex; + + // First of all ensure we are registered + _cemuHookClient.RegisterClient(clientId, cemuControllerConfig.DsuServerHost, cemuControllerConfig.DsuServerPort); + + // Then request data + _cemuHookClient.RequestData(clientId, cemuControllerConfig.Slot); + + if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair && !cemuControllerConfig.MirrorInput) + { + _cemuHookClient.RequestData(clientId, cemuControllerConfig.AltSlot); + } + + // Finally, get motion input data + _cemuHookClient.TryGetData(clientId, cemuControllerConfig.Slot, out _motionInput); + } + } + } + else + { + // Reset states + State = default; + _motionInput = null; + } + } + + private static short ClampAxis(float value) + { + if (value <= -short.MaxValue) + { + return -short.MaxValue; + } + else + { + return (short)(value * short.MaxValue); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static JoystickPosition ApplyDeadzone(float x, float y, float deadzone) + { + return new JoystickPosition + { + Dx = ClampAxis(MathF.Abs(x) > deadzone ? x : 0.0f), + Dy = ClampAxis(MathF.Abs(y) > deadzone ? y : 0.0f) + }; + } + + public GamepadInput GetHLEInputState() + { + GamepadInput state = new GamepadInput(); + + // First update all buttons + foreach (HLEButtonMappingEntry entry in _hleButtonMapping) + { + if (State.IsPressed(entry.DriverInputId)) + { + state.Buttons |= entry.HLEInput; + } + } + + if (_gamepad is IKeyboard) + { + (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left); + (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right); + + state.LStick = new JoystickPosition + { + Dx = ClampAxis(leftAxisX), + Dy = ClampAxis(leftAxisY) + }; + + state.RStick = new JoystickPosition + { + Dx = ClampAxis(rightAxisX), + Dy = ClampAxis(rightAxisY) + }; + } + else if (_config is StandardControllerInputConfig controllerConfig) + { + (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left); + (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right); + + state.LStick = ApplyDeadzone(leftAxisX, leftAxisY, controllerConfig.DeadzoneLeft); + state.RStick = ApplyDeadzone(rightAxisX, rightAxisY, controllerConfig.DeadzoneRight); + } + + return state; + } + + public SixAxisInput GetHLEMotionState() + { + float[] orientationForHLE = new float[9]; + Vector3 gyroscope; + Vector3 accelerometer; + Vector3 rotation; + + if (_motionInput != null) + { + gyroscope = Truncate(_motionInput.Gyroscrope * 0.0027f, 3); + accelerometer = Truncate(_motionInput.Accelerometer, 3); + rotation = Truncate(_motionInput.Rotation * 0.0027f, 3); + + Matrix4x4 orientation = _motionInput.GetOrientation(); + + orientationForHLE[0] = Math.Clamp(orientation.M11, -1f, 1f); + orientationForHLE[1] = Math.Clamp(orientation.M12, -1f, 1f); + orientationForHLE[2] = Math.Clamp(orientation.M13, -1f, 1f); + orientationForHLE[3] = Math.Clamp(orientation.M21, -1f, 1f); + orientationForHLE[4] = Math.Clamp(orientation.M22, -1f, 1f); + orientationForHLE[5] = Math.Clamp(orientation.M23, -1f, 1f); + orientationForHLE[6] = Math.Clamp(orientation.M31, -1f, 1f); + orientationForHLE[7] = Math.Clamp(orientation.M32, -1f, 1f); + orientationForHLE[8] = Math.Clamp(orientation.M33, -1f, 1f); + } + else + { + gyroscope = new Vector3(); + accelerometer = new Vector3(); + rotation = new Vector3(); + } + + return new SixAxisInput() + { + Accelerometer = accelerometer, + Gyroscope = gyroscope, + Rotation = rotation, + Orientation = orientationForHLE + }; + } + + private static Vector3 Truncate(Vector3 value, int decimals) + { + float power = MathF.Pow(10, decimals); + + value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power; + value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power; + value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power; + + return value; + } + + public KeyboardInput? GetHLEKeyboardInput() + { + if (_gamepad is IKeyboard keyboard) + { + KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot(); + + KeyboardInput hidKeyboard = new KeyboardInput + { + Modifier = 0, + Keys = new int[0x8] + }; + + foreach (HLEKeyboardMappingEntry entry in KeyMapping) + { + int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; + + hidKeyboard.Keys[entry.Target / 0x20] |= (value << (entry.Target % 0x20)); + } + + foreach (HLEKeyboardMappingEntry entry in KeyModifierMapping) + { + int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; + + hidKeyboard.Modifier |= value << entry.Target; + } + + return hidKeyboard; + } + + return null; + } + + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _gamepad?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Input/HLE/NpadManager.cs b/Ryujinx.Input/HLE/NpadManager.cs new file mode 100644 index 00000000..fdb87f9b --- /dev/null +++ b/Ryujinx.Input/HLE/NpadManager.cs @@ -0,0 +1,222 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Configuration; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Hid; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client; + +namespace Ryujinx.Input.HLE +{ + public class NpadManager : IDisposable + { + private CemuHookClient _cemuHookClient; + + private object _lock = new object(); + + private bool _blockInputUpdates; + + private const int MaxControllers = 9; + + private NpadController[] _controllers; + + private readonly IGamepadDriver _keyboardDriver; + private readonly IGamepadDriver _gamepadDriver; + + private bool _isDisposed; + + private List _inputConfig; + + public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver) + { + _controllers = new NpadController[MaxControllers]; + _cemuHookClient = new CemuHookClient(); + + _keyboardDriver = keyboardDriver; + _gamepadDriver = gamepadDriver; + _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value; + + _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + } + + private void HandleOnGamepadDisconnected(string obj) + { + // Force input reload + ReloadConfiguration(ConfigurationState.Instance.Hid.InputConfig.Value); + } + + private void HandleOnGamepadConnected(string id) + { + // Force input reload + ReloadConfiguration(ConfigurationState.Instance.Hid.InputConfig.Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool DriverConfigurationUpdate(ref NpadController controller, InputConfig config) + { + IGamepadDriver targetDriver = _gamepadDriver; + + if (config is StandardControllerInputConfig) + { + targetDriver = _gamepadDriver; + } + else if (config is StandardKeyboardInputConfig) + { + targetDriver = _keyboardDriver; + } + + Debug.Assert(targetDriver != null, "Unknown input configuration!"); + + if (controller.GamepadDriver != targetDriver || controller.Id != config.Id) + { + return controller.UpdateDriverConfiguration(targetDriver, config); + } + else + { + return controller.GamepadDriver != null; + } + } + + public void ReloadConfiguration(List inputConfig) + { + lock (_lock) + { + for (int i = 0; i < _controllers.Length; i++) + { + _controllers[i]?.Dispose(); + _controllers[i] = null; + } + + foreach (InputConfig inputConfigEntry in inputConfig) + { + NpadController controller = new NpadController(_cemuHookClient); + + bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); + + if (!isValid) + { + controller.Dispose(); + } + else + { + _controllers[(int)inputConfigEntry.PlayerIndex] = controller; + } + } + + _inputConfig = inputConfig; + + // Enforce an update of the property that will be updated by HLE. + // TODO: Move that in the input manager maybe? + ConfigurationState.Instance.Hid.InputConfig.Value = inputConfig; + } + } + + public void UnblockInputUpdates() + { + lock (_lock) + { + _blockInputUpdates = false; + } + } + + public void BlockInputUpdates() + { + lock (_lock) + { + _blockInputUpdates = true; + } + } + + public void Update(Hid hleHid, TamperMachine tamperMachine) + { + lock (_lock) + { + List hleInputStates = new List(); + List hleMotionStates = new List(NpadDevices.MaxControllers); + + foreach (InputConfig inputConfig in _inputConfig) + { + GamepadInput inputState = default; + SixAxisInput motionState = default; + + NpadController controller = _controllers[(int)inputConfig.PlayerIndex]; + + // Do we allow input updates and is a controller connected? + if (!_blockInputUpdates && controller != null) + { + DriverConfigurationUpdate(ref controller, inputConfig); + + controller.UpdateUserConfiguration(inputConfig); + controller.Update(); + + inputState = controller.GetHLEInputState(); + + inputState.Buttons |= hleHid.UpdateStickButtons(inputState.LStick, inputState.RStick); + + motionState = controller.GetHLEMotionState(); + } + else + { + // Ensure that orientation isn't null + motionState.Orientation = new float[9]; + } + + inputState.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex; + motionState.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex; + + hleInputStates.Add(inputState); + hleMotionStates.Add(motionState); + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + KeyboardInput? hleKeyboardInput = controller.GetHLEKeyboardInput(); + + if (hleKeyboardInput.HasValue) + { + hleHid.Keyboard.Update(hleKeyboardInput.Value); + } + } + } + + hleHid.Npads.Update(hleInputStates); + hleHid.Npads.UpdateSixAxis(hleMotionStates); + tamperMachine.UpdateInput(hleInputStates); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_lock) + { + if (!_isDisposed) + { + _cemuHookClient.Dispose(); + + _gamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _gamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + for (int i = 0; i < _controllers.Length; i++) + { + _controllers[i]?.Dispose(); + } + + _isDisposed = true; + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} -- cgit v1.2.3