From c27a12df2cf770fc34715302b361bf785e04680d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sun, 25 Jun 2023 17:08:50 +0000 Subject: [PATCH] add basic touch and button input interface --- .../LibRyujinxInterop.cs | 103 ++++ src/LibRyujinx.NativeSample/NativeWindow.cs | 140 ++++- src/LibRyujinx.NativeSample/Program.cs | 2 +- src/LibRyujinx/LibRyujinx.Graphics.cs | 1 + src/LibRyujinx/LibRyujinx.Input.cs | 552 +++++++++++++++++- src/LibRyujinx/LibRyujinx.cs | 2 +- 6 files changed, 794 insertions(+), 6 deletions(-) diff --git a/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs index a52264edf..dce64d903 100644 --- a/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs +++ b/src/LibRyujinx.NativeSample/LibRyujinxInterop.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -38,6 +39,33 @@ namespace LibRyujinx.Sample [DllImport(dll, EntryPoint = "graphics_renderer_set_vsync")] internal extern static void SetVsyncState(bool enabled); + + [DllImport(dll, EntryPoint = "input_initialize")] + internal extern static void InitializeInput(int width, int height); + + [DllImport(dll, EntryPoint = "input_set_client_size")] + internal extern static void SetClientSize(int width, int height); + + [DllImport(dll, EntryPoint = "input_set_touch_point")] + internal extern static void SetTouchPoint(int x, int y); + + [DllImport(dll, EntryPoint = "input_release_touch_point")] + internal extern static void ReleaseTouchPoint(); + + [DllImport(dll, EntryPoint = "input_update")] + internal extern static void UpdateInput(); + + [DllImport(dll, EntryPoint = "input_set_button_pressed")] + public extern static void SetButtonPressed(GamepadButtonInputId button, IntPtr idPtr); + + [DllImport(dll, EntryPoint = "input_set_button_released")] + public extern static void SetButtonReleased(GamepadButtonInputId button, IntPtr idPtr); + + [DllImport(dll, EntryPoint = "input_set_stick_axis")] + public extern static void SetStickAxis(StickInputId stick, Vector2 axes, IntPtr idPtr); + + [DllImport(dll, EntryPoint = "input_connect_gamepad")] + public extern static IntPtr ConnectGamepad(int index); } [StructLayout(LayoutKind.Sequential)] @@ -53,11 +81,13 @@ namespace LibRyujinx.Sample public bool EnableSpirvCompilationOnVulkan = true; public bool EnableTextureRecompression = false; public BackendThreading BackendThreading = BackendThreading.Auto; + public AspectRatio AspectRatio = AspectRatio.Fixed16x9; public GraphicsConfiguration() { } } + public enum GraphicsBackend { Vulkan, @@ -78,4 +108,77 @@ namespace LibRyujinx.Sample public IntPtr VkRequiredExtensions; public int VkRequiredExtensionsCount; } + public enum AspectRatio + { + Fixed4x3, + Fixed16x9, + Fixed16x10, + Fixed21x9, + Fixed32x9, + Stretched + } + + /// + /// Represent a button from a gamepad. + /// + public enum GamepadButtonInputId : byte + { + Unbound, + A, + B, + X, + Y, + LeftStick, + RightStick, + LeftShoulder, + RightShoulder, + + // Likely axis + LeftTrigger, + // Likely axis + RightTrigger, + + DpadUp, + DpadDown, + DpadLeft, + DpadRight, + + // Special buttons + + Minus, + Plus, + + Back = Minus, + Start = Plus, + + Guide, + Misc1, + + // Xbox Elite paddle + Paddle1, + Paddle2, + Paddle3, + Paddle4, + + // PS5 touchpad button + Touchpad, + + // Virtual buttons for single joycon + SingleLeftTrigger0, + SingleRightTrigger0, + + SingleLeftTrigger1, + SingleRightTrigger1, + + Count + } + + public enum StickInputId : byte + { + Unbound, + Left, + Right, + + Count + } } diff --git a/src/LibRyujinx.NativeSample/NativeWindow.cs b/src/LibRyujinx.NativeSample/NativeWindow.cs index 63ac5a0ad..b6d5820b6 100644 --- a/src/LibRyujinx.NativeSample/NativeWindow.cs +++ b/src/LibRyujinx.NativeSample/NativeWindow.cs @@ -1,5 +1,7 @@ using LibRyujinx.Sample; using OpenTK.Graphics.OpenGL; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; using System.Runtime.InteropServices; @@ -13,7 +15,12 @@ namespace LibRyujinx.NativeSample public delegate IntPtr GetProcAddress(string name); public delegate IntPtr CreateSurface(IntPtr instance); + private bool _run; private bool _isVulkan; + private Vector2 _lastPosition; + private bool _mousePressed; + private nint _gamepadIdPtr; + private string? _gamepadId; public NativeWindow(NativeWindowSettings nativeWindowSettings) : base(nativeWindowSettings) { @@ -55,6 +62,7 @@ namespace LibRyujinx.NativeSample }; var success = LibRyujinxInterop.InitializeGraphicsRenderer(_isVulkan ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl, nativeGraphicsInterop); success = LibRyujinxInterop.InitializeDevice(); + LibRyujinxInterop.InitializeInput(ClientSize.X, ClientSize.Y); var path = Marshal.StringToHGlobalAnsi(gamePath); var loaded = LibRyujinxInterop.LoadApplication(path); @@ -62,19 +70,28 @@ namespace LibRyujinx.NativeSample Marshal.FreeHGlobal(path); } + _gamepadIdPtr = LibRyujinxInterop.ConnectGamepad(0); + _gamepadId = Marshal.PtrToStringAnsi(_gamepadIdPtr); + if (!_isVulkan) { Context.MakeNoneCurrent(); } + _run = true; var thread = new Thread(new ThreadStart(RunLoop)); thread.Start(); + + UpdateLoop(); + thread.Join(); foreach(var ptr in pointers) { Marshal.FreeHGlobal(ptr); } + + Marshal.FreeHGlobal(_gamepadIdPtr); } public void RunLoop() @@ -89,15 +106,17 @@ namespace LibRyujinx.NativeSample Context.SwapInterval = 0; } - Task.Run(async () => + /* Task.Run(async () => { await Task.Delay(1000); - LibRyujinxInterop.SetVsyncState(false); - }); + LibRyujinxInterop.SetVsyncState(true); + });*/ LibRyujinxInterop.RunLoop(); + _run = false; + if (!_isVulkan) { Context.MakeNoneCurrent(); @@ -111,5 +130,120 @@ namespace LibRyujinx.NativeSample this.Context.SwapBuffers(); } } + + protected override void OnMouseMove(MouseMoveEventArgs e) + { + base.OnMouseMove(e); + _lastPosition = e.Position; + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + if(e.Button == MouseButton.Left) + { + _mousePressed = true; + } + } + + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + + if (_run) + { + LibRyujinxInterop.SetRendererSize(e.Width, e.Height); + LibRyujinxInterop.SetClientSize(e.Width, e.Height); + } + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseUp(e); + if (e.Button == MouseButton.Left) + { + _mousePressed = false; + } + } + + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + base.OnKeyUp(e); + + if (_gamepadIdPtr != IntPtr.Zero) + { + var key = GetKeyMapping(e.Key); + + LibRyujinxInterop.SetButtonReleased(key, _gamepadIdPtr); + } + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + base.OnKeyDown(e); + + if (_gamepadIdPtr != IntPtr.Zero) + { + var key = GetKeyMapping(e.Key); + + LibRyujinxInterop.SetButtonPressed(key, _gamepadIdPtr); + } + } + + public void UpdateLoop() + { + while(_run) + { + ProcessWindowEvents(true); + ProcessInputEvents(); + ProcessWindowEvents(IsEventDriven); + if (_mousePressed) + { + LibRyujinxInterop.SetTouchPoint((int)_lastPosition.X, (int)_lastPosition.Y); + } + else + { + LibRyujinxInterop.ReleaseTouchPoint(); + } + + LibRyujinxInterop.UpdateInput(); + + Thread.Sleep(1); + } + } + + public GamepadButtonInputId GetKeyMapping(Keys key) + { + if(_keyMapping.TryGetValue(key, out var mapping)) + { + return mapping; + } + + return GamepadButtonInputId.Unbound; + } + + private Dictionary _keyMapping = new Dictionary() + { + {Keys.A, GamepadButtonInputId.A }, + {Keys.S, GamepadButtonInputId.B }, + {Keys.Z, GamepadButtonInputId.X }, + {Keys.X, GamepadButtonInputId.Y }, + {Keys.Equal, GamepadButtonInputId.Plus }, + {Keys.Minus, GamepadButtonInputId.Minus }, + {Keys.Q, GamepadButtonInputId.LeftShoulder }, + {Keys.D1, GamepadButtonInputId.LeftTrigger }, + {Keys.W, GamepadButtonInputId.RightShoulder }, + {Keys.D2, GamepadButtonInputId.RightTrigger }, + {Keys.E, GamepadButtonInputId.LeftStick }, + {Keys.R, GamepadButtonInputId.RightStick }, + {Keys.Up, GamepadButtonInputId.DpadUp }, + {Keys.Down, GamepadButtonInputId.DpadDown }, + {Keys.Left, GamepadButtonInputId.DpadLeft }, + {Keys.Right, GamepadButtonInputId.DpadRight }, + {Keys.U, GamepadButtonInputId.SingleLeftTrigger0 }, + {Keys.D7, GamepadButtonInputId.SingleLeftTrigger1 }, + {Keys.O, GamepadButtonInputId.SingleRightTrigger0 }, + {Keys.D9, GamepadButtonInputId.SingleRightTrigger1 } + }; } } diff --git a/src/LibRyujinx.NativeSample/Program.cs b/src/LibRyujinx.NativeSample/Program.cs index c27d77883..d39a64a94 100644 --- a/src/LibRyujinx.NativeSample/Program.cs +++ b/src/LibRyujinx.NativeSample/Program.cs @@ -18,7 +18,7 @@ namespace LibRyujinx.NativeSample Size = new Vector2i(800, 600), Title = "Ryujinx", API = ContextAPI.NoAPI, - IsEventDriven = true, + IsEventDriven = false, // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, }; diff --git a/src/LibRyujinx/LibRyujinx.Graphics.cs b/src/LibRyujinx/LibRyujinx.Graphics.cs index 1ee6abaee..85273d392 100644 --- a/src/LibRyujinx/LibRyujinx.Graphics.cs +++ b/src/LibRyujinx/LibRyujinx.Graphics.cs @@ -207,6 +207,7 @@ namespace LibRyujinx public bool EnableSpirvCompilationOnVulkan = true; public bool EnableTextureRecompression = false; public BackendThreading BackendThreading = BackendThreading.Auto; + public AspectRatio AspectRatio = AspectRatio.Fixed16x9; public GraphicsConfiguration() { diff --git a/src/LibRyujinx/LibRyujinx.Input.cs b/src/LibRyujinx/LibRyujinx.Input.cs index c301f3502..2f304fdf3 100644 --- a/src/LibRyujinx/LibRyujinx.Input.cs +++ b/src/LibRyujinx/LibRyujinx.Input.cs @@ -1,12 +1,562 @@ -using System; +using DiscordRPC; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.Ui.Common.Configuration; +using Silk.NET.Vulkan; +using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using StickInputId = Ryujinx.Input.StickInputId; namespace LibRyujinx { public static partial class LibRyujinx { + private static VirtualGamepadDriver? _gamepadDriver; + private static VirtualTouchScreen? _virtualTouchScreen; + private static VirtualTouchScreenDriver? _touchScreenDriver; + private static TouchScreenManager? _touchScreenManager; + private static InputManager? _inputManager; + private static NpadManager _npadManager; + private static InputConfig[] _configs; + + public static void InitializeInput(int width, int height) + { + if(SwitchDevice!.InputManager != null) + { + throw new InvalidOperationException("Input is already initialized"); + } + + _gamepadDriver = new VirtualGamepadDriver(4); + _configs = new InputConfig[4]; + _virtualTouchScreen = new VirtualTouchScreen(); + _touchScreenDriver = new VirtualTouchScreenDriver(_virtualTouchScreen); + _inputManager = new InputManager(null, _gamepadDriver); + _inputManager.SetMouseDriver(_touchScreenDriver); + _npadManager = _inputManager.CreateNpadManager(); + + SwitchDevice!.InputManager = _inputManager; + + _touchScreenManager = _inputManager.CreateTouchScreenManager(); + _touchScreenManager.Initialize(SwitchDevice!.EmulationContext); + + _npadManager.Initialize(SwitchDevice.EmulationContext, new List(), false, false); + + _virtualTouchScreen.ClientSize = new Size(width, height); + } + + public static void SetClientSize(int width, int height) + { + _virtualTouchScreen!.ClientSize = new Size(width, height); + } + + public static void SetTouchPoint(int x, int y) + { + _virtualTouchScreen?.SetPosition(x, y); + } + + public static void ReleaseTouchPoint() + { + _virtualTouchScreen?.ReleaseTouch(); + } + + public static void SetButtonPressed(GamepadButtonInputId button, string id) + { + _gamepadDriver?.SetButtonPressed(button, id); + } + + public static void SetButtonReleased(GamepadButtonInputId button, string id) + { + _gamepadDriver?.SetButtonReleased(button, id); + } + + public static void SetStickAxis(StickInputId stick, Vector2 axes, string deviceId) + { + _gamepadDriver?.SetStickAxis(stick, axes, deviceId); + } + + public static string ConnectGamepad(int index) + { + var gamepad = _gamepadDriver?.GetGamepad(index); + if (gamepad != null) + { + var config = CreateDefaultInputConfig(); + + config.Id = gamepad.Id; + config.PlayerIndex = (PlayerIndex)index; + + _configs[index] = config; + } + + _npadManager?.ReloadConfiguration(_configs.Where(x => x != null).ToList(), false, false); + + return gamepad?.Id ?? string.Empty; + } + + private static InputConfig CreateDefaultInputConfig() + { + return new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = null, + ControllerType = ControllerType.JoyconPair, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = ConfigGamepadInputId.A, + ButtonB = ConfigGamepadInputId.B, + ButtonX = ConfigGamepadInputId.X, + ButtonY = ConfigGamepadInputId.Y, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false + } + }; + } + + public static void UpdateInput() + { + _npadManager?.Update(GraphicsConfiguration.AspectRatio.ToFloat()); + + if(!_touchScreenManager!.Update(true, _virtualTouchScreen!.IsButtonPressed(MouseButton.Button1), GraphicsConfiguration.AspectRatio.ToFloat())) + { + SwitchDevice!.EmulationContext?.Hid.Touchscreen.Update(); + } + } + + // Native Methods + + [UnmanagedCallersOnly(EntryPoint = "input_initialize")] + public static void InitializeInputNative(int width, int height) + { + InitializeInput(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_client_size")] + public static void SetClientSizeNative(int width, int height) + { + SetClientSize(width, height); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_touch_point")] + public static void SetTouchPointNative(int x, int y) + { + SetTouchPoint(x, y); + } + + + [UnmanagedCallersOnly(EntryPoint = "input_release_touch_point")] + public static void ReleaseTouchPointNative() + { + ReleaseTouchPoint(); + } + + [UnmanagedCallersOnly(EntryPoint = "input_update")] + public static void UpdateInputNative() + { + UpdateInput(); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_button_pressed")] + public static void SetButtonPressedNative(GamepadButtonInputId button, IntPtr idPtr) + { + var id = Marshal.PtrToStringAnsi(idPtr); + SetButtonPressed(button, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_button_released")] + public static void SetButtonReleased(GamepadButtonInputId button, IntPtr idPtr) + { + var id = Marshal.PtrToStringAnsi(idPtr); + SetButtonReleased(button, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_set_stick_axis")] + public static void SetStickAxisNative(StickInputId stick, Vector2 axes, IntPtr idPtr) + { + var id = Marshal.PtrToStringAnsi(idPtr); + SetStickAxis(stick, axes, id); + } + + [UnmanagedCallersOnly(EntryPoint = "input_connect_gamepad")] + public static IntPtr ConnectGamepadNative(int index) + { + var id = ConnectGamepad(index); + + return Marshal.StringToHGlobalAnsi(id); + } + + } + + public class VirtualTouchScreen : IMouse + { + public Size ClientSize { get; set; } + + public bool[] Buttons { get; } + + public VirtualTouchScreen() + { + Buttons = new bool[2]; + } + + public Vector2 CurrentPosition { get; private set; } + public Vector2 Scroll { get; private set; } + public string Id => "0"; + public string Name => "AvaloniaMouse"; + + public bool IsConnected => true; + public GamepadFeaturesFlag Features => throw new NotImplementedException(); + + public void Dispose() + { + + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + throw new NotImplementedException(); + } + + public void SetPosition(int x, int y) + { + CurrentPosition = new Vector2(x, y); + + Buttons[0] = true; + } + + public void ReleaseTouch() + { + Buttons[0] = false; + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + throw new NotImplementedException(); + } + + public Vector2 GetPosition() + { + return CurrentPosition; + } + + public Vector2 GetScroll() + { + return Scroll; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotImplementedException(); + } + + public (float, float) GetStick(Ryujinx.Input.StickInputId inputId) + { + throw new NotImplementedException(); + } + + public bool IsButtonPressed(MouseButton button) + { + return Buttons[0]; + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + throw new NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + throw new NotImplementedException(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + throw new NotImplementedException(); + } + } + + public class VirtualTouchScreenDriver : IGamepadDriver + { + private readonly VirtualTouchScreen _virtualTouchScreen; + + public VirtualTouchScreenDriver(VirtualTouchScreen virtualTouchScreen) + { + _virtualTouchScreen = virtualTouchScreen; + } + + public string DriverName => "VirtualTouchDriver"; + + public ReadOnlySpan GamepadsIds => new[] { "0" }; + + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + public void Dispose() + { + + } + + public IGamepad GetGamepad(string id) + { + return _virtualTouchScreen; + } + } + + public class VirtualGamepadDriver : IGamepadDriver + { + private readonly int _controllerCount; + + public ReadOnlySpan GamepadsIds => _gamePads.Keys.ToArray(); + + public string DriverName => "SDL2"; + + public event Action OnGamepadConnected; + public event Action OnGamepadDisconnected; + + private Dictionary _gamePads; + + public VirtualGamepadDriver(int controllerCount) + { + _gamePads = new Dictionary(); + for (int joystickIndex = 0; joystickIndex < controllerCount; joystickIndex++) + { + HandleJoyStickConnected(joystickIndex); + } + + _controllerCount = controllerCount; + } + + private string GenerateGamepadId(int joystickIndex) + { + return "VirtualGamePad-" + joystickIndex; + } + + private void HandleJoyStickConnected(int joystickDeviceId) + { + string id = GenerateGamepadId(joystickDeviceId); + _gamePads[id] = new VirtualGamepad(this, id); + OnGamepadConnected?.Invoke(id); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Simulate a full disconnect when disposing + var ids = GamepadsIds; + foreach (string id in ids) + { + OnGamepadDisconnected?.Invoke(id); + } + + _gamePads.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + } + + public IGamepad GetGamepad(string id) + { + return _gamePads[id]; + } + + public IGamepad GetGamepad(int index) + { + string id = GenerateGamepadId(index); + return _gamePads[id]; + } + + public void SetStickAxis(StickInputId stick, Vector2 axes, string deviceId) + { + if(_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.StickInputs[(int)stick] = axes; + } + } + + public void SetButtonPressed(GamepadButtonInputId button, string deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.ButtonInputs[(int)button] = true; + } + } + + public void SetButtonReleased(GamepadButtonInputId button, string deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.ButtonInputs[(int)button] = false; + } + } + } + + public class VirtualGamepad : IGamepad + { + private readonly VirtualGamepadDriver _driver; + + private bool[] _buttonInputs; + + private Vector2[] _stickInputs; + + public VirtualGamepad(VirtualGamepadDriver driver, string id) + { + _buttonInputs = new bool[(int)GamepadButtonInputId.Count]; + _stickInputs = new Vector2[(int)StickInputId.Count]; + _driver = driver; + Id = id; + } + + public void Dispose() { } + + public GamepadFeaturesFlag Features { get; } + public string Id { get; } + + public string Name => Id; + public bool IsConnected { get; } + public Vector2[] StickInputs { get => _stickInputs; set => _stickInputs = value; } + public bool[] ButtonInputs { get => _buttonInputs; set => _buttonInputs = value; } + + public bool IsPressed(GamepadButtonInputId inputId) + { + return _buttonInputs[(int)inputId]; + } + + public (float, float) GetStick(StickInputId inputId) + { + var v = _stickInputs[(int)inputId]; + + return (v.X, v.Y); + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + return new Vector3(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + //throw new System.NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + //throw new System.NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + //throw new System.NotImplementedException(); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + GamepadStateSnapshot result = default; + + foreach (var button in Enum.GetValues()) + { + // Do not touch state of button already pressed + if (button != GamepadButtonInputId.Count && !result.IsPressed(button)) + { + result.SetPressed(button, IsPressed(button)); + } + } + + (float leftStickX, float leftStickY) = GetStick(StickInputId.Left); + (float rightStickX, float rightStickY) = GetStick(StickInputId.Right); + + result.SetStick(StickInputId.Left, leftStickX, leftStickY); + result.SetStick(StickInputId.Right, rightStickX, rightStickY); + + return result; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + return new GamepadStateSnapshot(); + } } } diff --git a/src/LibRyujinx/LibRyujinx.cs b/src/LibRyujinx/LibRyujinx.cs index 6d9a14c05..c0c952728 100644 --- a/src/LibRyujinx/LibRyujinx.cs +++ b/src/LibRyujinx/LibRyujinx.cs @@ -132,7 +132,7 @@ namespace LibRyujinx "UTC", MemoryManagerMode.HostMappedUnsafe, false, - AspectRatio.Fixed16x9, + LibRyujinx.GraphicsConfiguration.AspectRatio, 0, true, "");