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,
"");