aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMary <me@thog.eu>2021-04-14 12:28:43 +0200
committerGitHub <noreply@github.com>2021-04-14 12:28:43 +0200
commit6cb22c9d38622225f9f787f483bd73369774cf77 (patch)
tree715a40903ceab05546f7392e5b0f429de75bdd02
parent978b69b706fc085d66b01e2dd27ef6d4acebf335 (diff)
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
-rw-r--r--Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs17
-rw-r--r--Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs8
-rw-r--r--Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj2
-rw-r--r--Ryujinx.Common/Configuration/ConfigurationFileFormat.cs19
-rw-r--r--Ryujinx.Common/Configuration/ConfigurationState.cs266
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs54
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs37
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs10
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs30
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs76
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs22
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs9
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs4
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs4
-rw-r--r--Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs11
-rw-r--r--Ryujinx.Common/Configuration/Hid/ControllerConfig.cs30
-rw-r--r--Ryujinx.Common/Configuration/Hid/ControllerInputId.cs46
-rw-r--r--Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs15
-rw-r--r--Ryujinx.Common/Configuration/Hid/InputBackendType.cs9
-rw-r--r--Ryujinx.Common/Configuration/Hid/InputConfig.cs55
-rw-r--r--Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs78
-rw-r--r--Ryujinx.Common/Configuration/Hid/Key.cs283
-rw-r--r--Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs15
-rw-r--r--Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs11
-rw-r--r--Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs4
-rw-r--r--Ryujinx.Common/Configuration/Hid/KeyboardConfig.cs18
-rw-r--r--Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs4
-rw-r--r--Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs15
-rw-r--r--Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs20
-rw-r--r--Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs20
-rw-r--r--Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs22
-rw-r--r--Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs22
-rw-r--r--Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs15
-rw-r--r--Ryujinx.Common/Utilities/JsonHelper.cs6
-rw-r--r--Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs29
-rw-r--r--Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs36
-rw-r--r--Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs15
-rw-r--r--Ryujinx.Graphics.OpenGL/IOpenGLContext.cs27
-rw-r--r--Ryujinx.Graphics.OpenGL/Renderer.cs4
-rw-r--r--Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj2
-rw-r--r--Ryujinx.Graphics.OpenGL/Window.cs4
-rw-r--r--Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj16
-rw-r--r--Ryujinx.Input.SDL2/SDL2Driver.cs170
-rw-r--r--Ryujinx.Input.SDL2/SDL2Gamepad.cs366
-rw-r--r--Ryujinx.Input.SDL2/SDL2GamepadDriver.cs138
-rw-r--r--Ryujinx.Input/Assigner/GamepadButtonAssigner.cs198
-rw-r--r--Ryujinx.Input/Assigner/IButtonAssigner.cs36
-rw-r--r--Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs50
-rw-r--r--Ryujinx.Input/GamepadButtonInputId.cs57
-rw-r--r--Ryujinx.Input/GamepadFeaturesFlag.cs28
-rw-r--r--Ryujinx.Input/GamepadStateSnapshot.cs70
-rw-r--r--Ryujinx.Input/HLE/InputManager.cs35
-rw-r--r--Ryujinx.Input/HLE/NpadController.cs496
-rw-r--r--Ryujinx.Input/HLE/NpadManager.cs222
-rw-r--r--Ryujinx.Input/IGamepad.cs122
-rw-r--r--Ryujinx.Input/IGamepadDriver.cs37
-rw-r--r--Ryujinx.Input/IKeyboard.cs41
-rw-r--r--Ryujinx.Input/Key.cs142
-rw-r--r--Ryujinx.Input/KeyboardStateSnapshot.cs29
-rw-r--r--Ryujinx.Input/Motion/CemuHook/Client.cs (renamed from Ryujinx/Modules/Motion/Client.cs)42
-rw-r--r--Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs (renamed from Ryujinx/Modules/Motion/Protocol/ControllerData.cs)2
-rw-r--r--Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs (renamed from Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs)2
-rw-r--r--Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs (renamed from Ryujinx/Modules/Motion/Protocol/Header.cs)2
-rw-r--r--Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs (renamed from Ryujinx/Modules/Motion/Protocol/MessageType.cs)2
-rw-r--r--Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs (renamed from Ryujinx/Modules/Motion/Protocol/SharedResponse.cs)2
-rw-r--r--Ryujinx.Input/Motion/MotionInput.cs (renamed from Ryujinx/Modules/Motion/MotionInput.cs)5
-rw-r--r--Ryujinx.Input/Motion/MotionSensorFilter.cs (renamed from Ryujinx/Modules/Motion/MotionSensorFilter.cs)4
-rw-r--r--Ryujinx.Input/MotionInputId.cs25
-rw-r--r--Ryujinx.Input/Ryujinx.Input.csproj17
-rw-r--r--Ryujinx.Input/StickInputId.cs14
-rw-r--r--Ryujinx.sln12
-rw-r--r--Ryujinx/Config.json67
-rw-r--r--Ryujinx/Input/GTK3/GTK3Keyboard.cs204
-rw-r--r--Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs93
-rw-r--r--Ryujinx/Input/GTK3/GTK3MappingHelper.cs154
-rw-r--r--Ryujinx/Modules/Motion/MotionDevice.cs81
-rw-r--r--Ryujinx/Program.cs17
-rw-r--r--Ryujinx/Ryujinx.csproj7
-rw-r--r--Ryujinx/Ui/GLRenderer.cs252
-rw-r--r--Ryujinx/Ui/GLWidget.cs118
-rw-r--r--Ryujinx/Ui/Input/ButtonAssigner.cs17
-rw-r--r--Ryujinx/Ui/Input/JoystickButtonAssigner.cs227
-rw-r--r--Ryujinx/Ui/Input/KeyboardKeyAssigner.cs51
-rw-r--r--Ryujinx/Ui/JoystickController.cs149
-rw-r--r--Ryujinx/Ui/KeyboardController.cs291
-rw-r--r--Ryujinx/Ui/MainWindow.cs13
-rw-r--r--Ryujinx/Ui/OpenToolkitBindingsContext.cs20
-rw-r--r--Ryujinx/Ui/SPBOpenGLContext.cs49
-rw-r--r--Ryujinx/Ui/Windows/ControllerWindow.cs827
-rw-r--r--Ryujinx/Ui/Windows/ControllerWindow.glade125
-rw-r--r--Ryujinx/Ui/Windows/SettingsWindow.cs2
91 files changed, 4494 insertions, 2026 deletions
diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
index 43f238ef..387ae772 100644
--- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
@@ -1,9 +1,10 @@
-using OpenTK.Audio;
+using OpenTK.Audio.OpenAL;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
@@ -12,8 +13,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
{
private object _lock = new object();
-
- private AudioContext _context;
+ private ALDevice _device;
+ private ALContext _context;
private ManualResetEvent _updateRequiredEvent;
private List<OpenALHardwareDeviceSession> _sessions;
private bool _stillRunning;
@@ -21,7 +22,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
public OpenALHardwareDeviceDriver()
{
- _context = new AudioContext();
+ _device = ALC.OpenDevice("");
+ _context = ALC.CreateContext(_device, new ALContextAttributes());
_updateRequiredEvent = new ManualResetEvent(false);
_sessions = new List<OpenALHardwareDeviceSession>();
@@ -40,7 +42,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
{
try
{
- return AudioContext.AvailableDevices.Count > 0;
+ return ALC.GetStringList(GetEnumerationStringList.DeviceSpecifier).Any();
}
catch
{
@@ -95,6 +97,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
private void Update()
{
+ ALC.MakeContextCurrent(_context);
+
while (_stillRunning)
{
bool updateRequired = false;
@@ -143,7 +147,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
- _context.Dispose();
+ ALC.DestroyContext(_context);
+ ALC.CloseDevice(_device);
}
}
diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
index 055c608b..f0227bf8 100644
--- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
+++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
@@ -71,11 +71,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
OpenALAudioBuffer driverBuffer = new OpenALAudioBuffer
{
DriverIdentifier = buffer.DataPointer,
- BufferId = AL.GenBuffer(),
- SampleCount = GetSampleCount(buffer)
+ BufferId = AL.GenBuffer(),
+ SampleCount = GetSampleCount(buffer)
};
- AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)buffer.DataSize, (int)RequestedSampleRate);
+ AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)RequestedSampleRate);
_queuedBuffers.Enqueue(driverBuffer);
@@ -125,7 +125,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
- public override void UnregisterBuffer(AudioBuffer buffer) {}
+ public override void UnregisterBuffer(AudioBuffer buffer) { }
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
diff --git a/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
index 187fe0fe..4619d73d 100644
--- a/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
+++ b/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
+ <PackageReference Include="OpenTK.OpenAL" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
index 32e76375..1d47051a 100644
--- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
- public const int CurrentVersion = 23;
+ public const int CurrentVersion = 24;
public int Version { get; set; }
@@ -224,14 +224,23 @@ namespace Ryujinx.Configuration
public KeyboardHotkeys Hotkeys { get; set; }
/// <summary>
- /// Keyboard control bindings
+ /// Legacy keyboard control bindings
/// </summary>
- public List<KeyboardConfig> KeyboardConfig { get; set; }
+ /// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
+ /// TODO: Remove this when those older versions aren't in use anymore.
+ public List<object> KeyboardConfig { get; set; }
/// <summary>
- /// Controller control bindings
+ /// Legacy controller control bindings
/// </summary>
- public List<ControllerConfig> ControllerConfig { get; set; }
+ /// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
+ /// TODO: Remove this when those older versions aren't in use anymore.
+ public List<object> ControllerConfig { get; set; }
+
+ /// <summary>
+ /// Input configurations
+ /// </summary>
+ public List<InputConfig> InputConfig { get; set; }
/// <summary>
/// Loads a configuration file from disk
diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs
index 1e26b4f4..7063110f 100644
--- a/Ryujinx.Common/Configuration/ConfigurationState.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationState.cs
@@ -1,8 +1,8 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
-using Ryujinx.Configuration.Hid;
using Ryujinx.Configuration.System;
using Ryujinx.Configuration.Ui;
using System;
@@ -405,21 +405,6 @@ namespace Ryujinx.Configuration
public ConfigurationFileFormat ToFileFormat()
{
- List<ControllerConfig> controllerConfigList = new List<ControllerConfig>();
- List<KeyboardConfig> keyboardConfigList = new List<KeyboardConfig>();
-
- foreach (InputConfig inputConfig in Hid.InputConfig.Value)
- {
- if (inputConfig is ControllerConfig controllerConfig)
- {
- controllerConfigList.Add(controllerConfig);
- }
- else if (inputConfig is KeyboardConfig keyboardConfig)
- {
- keyboardConfigList.Add(keyboardConfig);
- }
- }
-
ConfigurationFileFormat configurationFile = new ConfigurationFileFormat
{
Version = ConfigurationFileFormat.CurrentVersion,
@@ -479,8 +464,9 @@ namespace Ryujinx.Configuration
StartFullscreen = Ui.StartFullscreen,
EnableKeyboard = Hid.EnableKeyboard,
Hotkeys = Hid.Hotkeys,
- KeyboardConfig = keyboardConfigList,
- ControllerConfig = controllerConfigList
+ KeyboardConfig = new List<object>(),
+ ControllerConfig = new List<object>(),
+ InputConfig = Hid.InputConfig,
};
return configurationFile;
@@ -543,54 +529,57 @@ namespace Ryujinx.Configuration
};
Hid.InputConfig.Value = new List<InputConfig>
{
- new KeyboardConfig
- {
- Index = 0,
- ControllerType = ControllerType.JoyconPair,
- PlayerIndex = PlayerIndex.Player1,
- LeftJoycon = new NpadKeyboardLeft
- {
- StickUp = Key.W,
- StickDown = Key.S,
- StickLeft = Key.A,
- StickRight = Key.D,
- StickButton = Key.F,
- DPadUp = Key.Up,
- DPadDown = Key.Down,
- DPadLeft = Key.Left,
- DPadRight = Key.Right,
- ButtonMinus = Key.Minus,
- ButtonL = Key.E,
- ButtonZl = Key.Q,
- ButtonSl = Key.Home,
- ButtonSr = Key.End
- },
- RightJoycon = new NpadKeyboardRight
- {
- StickUp = Key.I,
- StickDown = Key.K,
- StickLeft = Key.J,
- StickRight = Key.L,
- StickButton = Key.H,
- ButtonA = Key.Z,
- ButtonB = Key.X,
- ButtonX = Key.C,
- ButtonY = Key.V,
- ButtonPlus = Key.Plus,
- ButtonR = Key.U,
- ButtonZr = Key.O,
- ButtonSl = Key.PageUp,
- ButtonSr = Key.PageDown
- },
- EnableMotion = false,
- MirrorInput = false,
- Slot = 0,
- AltSlot = 0,
- Sensitivity = 100,
- GyroDeadzone = 1,
- DsuServerHost = "127.0.0.1",
- DsuServerPort = 26760
- }
+ new StandardKeyboardInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.WindowKeyboard,
+ Id = "0",
+ PlayerIndex = PlayerIndex.Player1,
+ ControllerType = ControllerType.JoyconPair,
+ LeftJoycon = new LeftJoyconCommonConfig<Key>
+ {
+ DpadUp = Key.Up,
+ DpadDown = Key.Down,
+ DpadLeft = Key.Left,
+ DpadRight = Key.Right,
+ ButtonMinus = Key.Minus,
+ ButtonL = Key.E,
+ ButtonZl = Key.Q,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.W,
+ StickDown = Key.S,
+ StickLeft = Key.A,
+ StickRight = Key.D,
+ StickButton = Key.F,
+ },
+
+ RightJoycon = new RightJoyconCommonConfig<Key>
+ {
+ ButtonA = Key.Z,
+ ButtonB = Key.X,
+ ButtonX = Key.C,
+ ButtonY = Key.V,
+ ButtonPlus = Key.Plus,
+ ButtonR = Key.U,
+ ButtonZr = Key.O,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ RightJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.I,
+ StickDown = Key.K,
+ StickLeft = Key.J,
+ StickRight = Key.L,
+ StickButton = Key.H,
+ }
+ }
};
}
@@ -643,80 +632,6 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
- if (configurationFileFormat.Version < 6)
- {
- Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 6.");
-
- configurationFileFormat.ControllerConfig = new List<ControllerConfig>();
- configurationFileFormat.KeyboardConfig = new List<KeyboardConfig>
- {
- new KeyboardConfig
- {
- Index = 0,
- ControllerType = ControllerType.JoyconPair,
- PlayerIndex = PlayerIndex.Player1,
- LeftJoycon = new NpadKeyboardLeft
- {
- StickUp = Key.W,
- StickDown = Key.S,
- StickLeft = Key.A,
- StickRight = Key.D,
- StickButton = Key.F,
- DPadUp = Key.Up,
- DPadDown = Key.Down,
- DPadLeft = Key.Left,
- DPadRight = Key.Right,
- ButtonMinus = Key.Minus,
- ButtonL = Key.E,
- ButtonZl = Key.Q,
- ButtonSl = Key.Unbound,
- ButtonSr = Key.Unbound
- },
- RightJoycon = new NpadKeyboardRight
- {
- StickUp = Key.I,
- StickDown = Key.K,
- StickLeft = Key.J,
- StickRight = Key.L,
- StickButton = Key.H,
- ButtonA = Key.Z,
- ButtonB = Key.X,
- ButtonX = Key.C,
- ButtonY = Key.V,
- ButtonPlus = Key.Plus,
- ButtonR = Key.U,
- ButtonZr = Key.O,
- ButtonSl = Key.Unbound,
- ButtonSr = Key.Unbound
- },
- EnableMotion = false,
- MirrorInput = false,
- Slot = 0,
- AltSlot = 0,
- Sensitivity = 100,
- GyroDeadzone = 1,
- DsuServerHost = "127.0.0.1",
- DsuServerPort = 26760
- }
- };
-
- configurationFileUpdated = true;
- }
-
- // Only needed for version 6 configurations.
- if (configurationFileFormat.Version == 6)
- {
- Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 7.");
-
- for (int i = 0; i < configurationFileFormat.KeyboardConfig.Count; i++)
- {
- if (configurationFileFormat.KeyboardConfig[i].Index != KeyboardConfig.AllKeyboardsIndex)
- {
- configurationFileFormat.KeyboardConfig[i].Index++;
- }
- }
- }
-
if (configurationFileFormat.Version < 8)
{
Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 8.");
@@ -826,9 +741,67 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
- List<InputConfig> inputConfig = new List<InputConfig>();
- inputConfig.AddRange(configurationFileFormat.ControllerConfig);
- inputConfig.AddRange(configurationFileFormat.KeyboardConfig);
+ if (configurationFileFormat.Version < 24)
+ {
+ Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 24.");
+
+ configurationFileFormat.InputConfig = new List<InputConfig>
+ {
+ new StandardKeyboardInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.WindowKeyboard,
+ Id = "0",
+ PlayerIndex = PlayerIndex.Player1,
+ ControllerType = ControllerType.JoyconPair,
+ LeftJoycon = new LeftJoyconCommonConfig<Key>
+ {
+ DpadUp = Key.Up,
+ DpadDown = Key.Down,
+ DpadLeft = Key.Left,
+ DpadRight = Key.Right,
+ ButtonMinus = Key.Minus,
+ ButtonL = Key.E,
+ ButtonZl = Key.Q,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.W,
+ StickDown = Key.S,
+ StickLeft = Key.A,
+ StickRight = Key.D,
+ StickButton = Key.F,
+ },
+
+ RightJoycon = new RightJoyconCommonConfig<Key>
+ {
+ ButtonA = Key.Z,
+ ButtonB = Key.X,
+ ButtonX = Key.C,
+ ButtonY = Key.V,
+ ButtonPlus = Key.Plus,
+ ButtonR = Key.U,
+ ButtonZr = Key.O,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ RightJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.I,
+ StickDown = Key.K,
+ StickLeft = Key.J,
+ StickRight = Key.L,
+ StickButton = Key.H,
+ }
+ }
+ };
+
+ configurationFileUpdated = true;
+ }
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@@ -880,7 +853,12 @@ namespace Ryujinx.Configuration
Ui.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard;
Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;
- Hid.InputConfig.Value = inputConfig;
+ Hid.InputConfig.Value = configurationFileFormat.InputConfig;
+
+ if (Hid.InputConfig.Value == null)
+ {
+ Hid.InputConfig.Value = new List<InputConfig>();
+ }
if (configurationFileUpdated)
{
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs b/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs
new file mode 100644
index 00000000..cae65fc9
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs
@@ -0,0 +1,54 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller
+{
+ public enum GamepadInputId : 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
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs b/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs
new file mode 100644
index 00000000..e3423bb5
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs
@@ -0,0 +1,37 @@
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
+
+namespace Ryujinx.Common.Configuration.Hid.Controller
+{
+ public class GenericControllerInputConfig<Button, Stick> : GenericInputConfigurationCommon<Button> where Button : unmanaged where Stick : unmanaged
+ {
+ /// <summary>
+ /// Left JoyCon Controller Stick Bindings
+ /// </summary>
+ public JoyconConfigControllerStick<Button, Stick> LeftJoyconStick { get; set; }
+
+ /// <summary>
+ /// Right JoyCon Controller Stick Bindings
+ /// </summary>
+ public JoyconConfigControllerStick<Button, Stick> RightJoyconStick { get; set; }
+
+ /// <summary>
+ /// Controller Left Analog Stick Deadzone
+ /// </summary>
+ public float DeadzoneLeft { get; set; }
+
+ /// <summary>
+ /// Controller Right Analog Stick Deadzone
+ /// </summary>
+ public float DeadzoneRight { get; set; }
+
+ /// <summary>
+ /// Controller Trigger Threshold
+ /// </summary>
+ public float TriggerThreshold { get; set; }
+
+ /// <summary>
+ /// Controller Motion Settings
+ /// </summary>
+ public MotionConfigController Motion { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs b/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs
new file mode 100644
index 00000000..0a9ca9dd
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller
+{
+ public class JoyconConfigControllerStick<Button, Stick> where Button: unmanaged where Stick: unmanaged
+ {
+ public Stick Joystick { get; set; }
+ public bool InvertStickX { get; set; }
+ public bool InvertStickY { get; set; }
+ public Button StickButton { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs
new file mode 100644
index 00000000..2a5a73ff
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs
@@ -0,0 +1,30 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
+{
+ public class CemuHookMotionConfigController : MotionConfigController
+ {
+ /// <summary>
+ /// Motion Controller Slot
+ /// </summary>
+ public int Slot { get; set; }
+
+ /// <summary>
+ /// Motion Controller Alternative Slot, for RightJoyCon in Pair mode
+ /// </summary>
+ public int AltSlot { get; set; }
+
+ /// <summary>
+ /// Mirror motion input in Pair mode
+ /// </summary>
+ public bool MirrorInput { get; set; }
+
+ /// <summary>
+ /// Host address of the DSU Server
+ /// </summary>
+ public string DsuServerHost { get; set; }
+
+ /// <summary>
+ /// Port of the DSU Server
+ /// </summary>
+ public int DsuServerPort { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs
new file mode 100644
index 00000000..d1c2e4e8
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
+{
+ class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
+ {
+ private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
+ {
+ // Temporary reader to get the backend type
+ Utf8JsonReader tempReader = reader;
+
+ MotionInputBackendType result = MotionInputBackendType.Invalid;
+
+ while (tempReader.Read())
+ {
+ // NOTE: We scan all properties ignoring the depth entirely on purpose.
+ // The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object.
+ // As such, this code will try to parse very field named "motion_backend" to the correct enum.
+ if (tempReader.TokenType == JsonTokenType.PropertyName)
+ {
+ string propertyName = tempReader.GetString();
+
+ if (propertyName.Equals("motion_backend"))
+ {
+ tempReader.Read();
+
+ if (tempReader.TokenType == JsonTokenType.String)
+ {
+ string backendTypeRaw = tempReader.GetString();
+
+ if (!Enum.TryParse(backendTypeRaw, out result))
+ {
+ result = MotionInputBackendType.Invalid;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public override MotionConfigController Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ MotionInputBackendType motionBackendType = GetMotionInputBackendType(ref reader);
+
+ return motionBackendType switch
+ {
+ MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options),
+ MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options),
+ _ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, MotionConfigController value, JsonSerializerOptions options)
+ {
+ switch (value.MotionBackend)
+ {
+ case MotionInputBackendType.GamepadDriver:
+ JsonSerializer.Serialize(writer, value as StandardMotionConfigController, options);
+ break;
+ case MotionInputBackendType.CemuHook:
+ JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options);
+ break;
+ default:
+ throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs
new file mode 100644
index 00000000..832aae0d
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
+{
+ public class MotionConfigController
+ {
+ public MotionInputBackendType MotionBackend { get; set; }
+
+ /// <summary>
+ /// Gyro Sensitivity
+ /// </summary>
+ public int Sensitivity { get; set; }
+
+ /// <summary>
+ /// Gyro Deadzone
+ /// </summary>
+ public double GyroDeadzone { get; set; }
+
+ /// <summary>
+ /// Enable Motion Controls
+ /// </summary>
+ public bool EnableMotion { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs
new file mode 100644
index 00000000..45d654ed
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
+{
+ public enum MotionInputBackendType : byte
+ {
+ Invalid,
+ GamepadDriver,
+ CemuHook
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs
new file mode 100644
index 00000000..df925444
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
+{
+ public class StandardMotionConfigController : MotionConfigController { }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs b/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs
new file mode 100644
index 00000000..4154a42b
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller
+{
+ public class StandardControllerInputConfig : GenericControllerInputConfig<GamepadInputId, StickInputId> { }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs b/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs
new file mode 100644
index 00000000..87945a75
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller
+{
+ public enum StickInputId : byte
+ {
+ Unbound,
+ Left,
+ Right,
+
+ Count
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/ControllerConfig.cs b/Ryujinx.Common/Configuration/Hid/ControllerConfig.cs
deleted file mode 100644
index 3e414055..00000000
--- a/Ryujinx.Common/Configuration/Hid/ControllerConfig.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-namespace Ryujinx.Common.Configuration.Hid
-{
- public class ControllerConfig : InputConfig
- {
- /// <summary>
- /// Controller Left Analog Stick Deadzone
- /// </summary>
- public float DeadzoneLeft { get; set; }
-
- /// <summary>
- /// Controller Right Analog Stick Deadzone
- /// </summary>
- public float DeadzoneRight { get; set; }
-
- /// <summary>
- /// Controller Trigger Threshold
- /// </summary>
- public float TriggerThreshold { get; set; }
-
- /// <summary>
- /// Left JoyCon Controller Bindings
- /// </summary>
- public NpadControllerLeft LeftJoycon { get; set; }
-
- /// <summary>
- /// Right JoyCon Controller Bindings
- /// </summary>
- public NpadControllerRight RightJoycon { get; set; }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs b/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs
deleted file mode 100644
index 606a1b0c..00000000
--- a/Ryujinx.Common/Configuration/Hid/ControllerInputId.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace Ryujinx.Common.Configuration.Hid
-{
- public enum ControllerInputId
- {
- Button0,
- Button1,
- Button2,
- Button3,
- Button4,
- Button5,
- Button6,
- Button7,
- Button8,
- Button9,
- Button10,
- Button11,
- Button12,
- Button13,
- Button14,
- Button15,
- Button16,
- Button17,
- Button18,
- Button19,
- Button20,
- Axis0,
- Axis1,
- Axis2,
- Axis3,
- Axis4,
- Axis5,
- Hat0Up,
- Hat0Down,
- Hat0Left,
- Hat0Right,
- Hat1Up,
- Hat1Down,
- Hat1Left,
- Hat1Right,
- Hat2Up,
- Hat2Down,
- Hat2Left,
- Hat2Right,
- Unbound
- }
-}
diff --git a/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs b/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs
new file mode 100644
index 00000000..3d43817e
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Common.Configuration.Hid
+{
+ public class GenericInputConfigurationCommon<Button> : InputConfig where Button : unmanaged
+ {
+ /// <summary>
+ /// Left JoyCon Controller Bindings
+ /// </summary>
+ public LeftJoyconCommonConfig<Button> LeftJoycon { get; set; }
+
+ /// <summary>
+ /// Right JoyCon Controller Bindings
+ /// </summary>
+ public RightJoyconCommonConfig<Button> RightJoycon { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/InputBackendType.cs b/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
new file mode 100644
index 00000000..9e944f9e
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Common.Configuration.Hid
+{
+ public enum InputBackendType
+ {
+ Invalid,
+ WindowKeyboard,
+ GamepadSDL2,
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/InputConfig.cs b/Ryujinx.Common/Configuration/Hid/InputConfig.cs
index 7ccb989b..d204eba4 100644
--- a/Ryujinx.Common/Configuration/Hid/InputConfig.cs
+++ b/Ryujinx.Common/Configuration/Hid/InputConfig.cs
@@ -1,60 +1,29 @@
-namespace Ryujinx.Common.Configuration.Hid
+namespace Ryujinx.Common.Configuration.Hid
{
public class InputConfig
{
/// <summary>
- /// Controller Device Index
+ /// The current version of the input file format
/// </summary>
- public int Index { get; set; }
+ public const int CurrentVersion = 1;
- /// <summary>
- /// Controller's Type
- /// </summary>
- public ControllerType ControllerType { get; set; }
+ public int Version { get; set; }
- /// <summary>
- /// Player's Index for the controller
- /// </summary>
- public PlayerIndex PlayerIndex { get; set; }
-
- /// <summary>
- /// Motion Controller Slot
- /// </summary>
- public int Slot { get; set; }
-
- /// <summary>
- /// Motion Controller Alternative Slot, for RightJoyCon in Pair mode
- /// </summary>
- public int AltSlot { get; set; }
+ public InputBackendType Backend { get; set; }
/// <summary>
- /// Mirror motion input in Pair mode
+ /// Controller id
/// </summary>
- public bool MirrorInput { get; set; }
+ public string Id { get; set; }
/// <summary>
- /// Host address of the DSU Server
- /// </summary>
- public string DsuServerHost { get; set; }
-
- /// <summary>
- /// Port of the DSU Server
- /// </summary>
- public int DsuServerPort { get; set; }
-
- /// <summary>
- /// Gyro Sensitivity
+ /// Controller's Type
/// </summary>
- public int Sensitivity { get; set; }
+ public ControllerType ControllerType { get; set; }
/// <summary>
- /// Gyro Deadzone
- /// </summary>
- public double GyroDeadzone { get; set; }
-
- /// <summary>
- /// Enable Motion Controls
+ /// Player's Index for the controller
/// </summary>
- public bool EnableMotion { get; set; }
+ public PlayerIndex PlayerIndex { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs b/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs
new file mode 100644
index 00000000..7223ad45
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs
@@ -0,0 +1,78 @@
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.Common.Configuration.Hid
+{
+ class JsonInputConfigConverter : JsonConverter<InputConfig>
+ {
+ private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
+ {
+ // Temporary reader to get the backend type
+ Utf8JsonReader tempReader = reader;
+
+ InputBackendType result = InputBackendType.Invalid;
+
+ while (tempReader.Read())
+ {
+ // NOTE: We scan all properties ignoring the depth entirely on purpose.
+ // The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object.
+ // As such, this code will try to parse very field named "backend" to the correct enum.
+ if (tempReader.TokenType == JsonTokenType.PropertyName)
+ {
+ string propertyName = tempReader.GetString();
+
+ if (propertyName.Equals("backend"))
+ {
+ tempReader.Read();
+
+ if (tempReader.TokenType == JsonTokenType.String)
+ {
+ string backendTypeRaw = tempReader.GetString();
+
+ if (!Enum.TryParse(backendTypeRaw, out result))
+ {
+ result = InputBackendType.Invalid;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public override InputConfig Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ InputBackendType backendType = GetInputBackendType(ref reader);
+
+ return backendType switch
+ {
+ InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options),
+ InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options),
+ _ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, InputConfig value, JsonSerializerOptions options)
+ {
+ switch (value.Backend)
+ {
+ case InputBackendType.WindowKeyboard:
+ JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options);
+ break;
+ case InputBackendType.GamepadSDL2:
+ JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options);
+ break;
+ default:
+ throw new ArgumentException($"Unknown backend type {value.Backend}");
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Key.cs b/Ryujinx.Common/Configuration/Hid/Key.cs
index 67177eec..194843a3 100644
--- a/Ryujinx.Common/Configuration/Hid/Key.cs
+++ b/Ryujinx.Common/Configuration/Hid/Key.cs
@@ -1,154 +1,139 @@
-namespace Ryujinx.Configuration.Hid
+namespace Ryujinx.Common.Configuration.Hid
{
public enum Key
{
- Unknown = 0,
- ShiftLeft = 1,
- LShift = 1,
- ShiftRight = 2,
- RShift = 2,
- ControlLeft = 3,
- LControl = 3,
- ControlRight = 4,
- RControl = 4,
- AltLeft = 5,
- LAlt = 5,
- AltRight = 6,
- RAlt = 6,
- WinLeft = 7,
- LWin = 7,
- WinRight = 8,
- RWin = 8,
- Menu = 9,
- F1 = 10,
- F2 = 11,
- F3 = 12,
- F4 = 13,
- F5 = 14,
- F6 = 15,
- F7 = 16,
- F8 = 17,
- F9 = 18,
- F10 = 19,
- F11 = 20,
- F12 = 21,
- F13 = 22,
- F14 = 23,
- F15 = 24,
- F16 = 25,
- F17 = 26,
- F18 = 27,
- F19 = 28,
- F20 = 29,
- F21 = 30,
- F22 = 31,
- F23 = 32,
- F24 = 33,
- F25 = 34,
- F26 = 35,
- F27 = 36,
- F28 = 37,
- F29 = 38,
- F30 = 39,
- F31 = 40,
- F32 = 41,
- F33 = 42,
- F34 = 43,
- F35 = 44,
- Up = 45,
- Down = 46,
- Left = 47,
- Right = 48,
- Enter = 49,
- Escape = 50,
- Space = 51,
- Tab = 52,
- BackSpace = 53,
- Back = 53,
- Insert = 54,
- Delete = 55,
- PageUp = 56,
- PageDown = 57,
- Home = 58,
- End = 59,
- CapsLock = 60,
- ScrollLock = 61,
- PrintScreen = 62,
- Pause = 63,
- NumLock = 64,
- Clear = 65,
- Sleep = 66,
- Keypad0 = 67,
- Keypad1 = 68,
- Keypad2 = 69,
- Keypad3 = 70,
- Keypad4 = 71,
- Keypad5 = 72,
- Keypad6 = 73,
- Keypad7 = 74,
- Keypad8 = 75,
- Keypad9 = 76,
- KeypadDivide = 77,
- KeypadMultiply = 78,
- KeypadSubtract = 79,
- KeypadMinus = 79,
- KeypadAdd = 80,
- KeypadPlus = 80,
- KeypadDecimal = 81,
- KeypadPeriod = 81,
- KeypadEnter = 82,
- A = 83,
- B = 84,
- C = 85,
- D = 86,
- E = 87,
- F = 88,
- G = 89,
- H = 90,
- I = 91,
- J = 92,
- K = 93,
- L = 94,
- M = 95,
- N = 96,
- O = 97,
- P = 98,
- Q = 99,
- R = 100,
- S = 101,
- T = 102,
- U = 103,
- V = 104,
- W = 105,
- X = 106,
- Y = 107,
- Z = 108,
- Number0 = 109,
- Number1 = 110,
- Number2 = 111,
- Number3 = 112,
- Number4 = 113,
- Number5 = 114,
- Number6 = 115,
- Number7 = 116,
- Number8 = 117,
- Number9 = 118,
- Tilde = 119,
- Grave = 119,
- Minus = 120,
- Plus = 121,
- BracketLeft = 122,
- LBracket = 122,
- BracketRight = 123,
- RBracket = 123,
- Semicolon = 124,
- Quote = 125,
- Comma = 126,
- Period = 127,
- Slash = 128,
- BackSlash = 129,
- NonUSBackSlash = 130,
- LastKey = 131,
- Unbound
+ Unknown,
+ ShiftLeft,
+ ShiftRight,
+ ControlLeft,
+ ControlRight,
+ AltLeft,
+ AltRight,
+ WinLeft,
+ WinRight,
+ Menu,
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+ F25,
+ F26,
+ F27,
+ F28,
+ F29,
+ F30,
+ F31,
+ F32,
+ F33,
+ F34,
+ F35,
+ Up,
+ Down,
+ Left,
+ Right,
+ Enter,
+ Escape,
+ Space,
+ Tab,
+ BackSpace,
+ Insert,
+ Delete,
+ PageUp,
+ PageDown,
+ Home,
+ End,
+ CapsLock,
+ ScrollLock,
+ PrintScreen,
+ Pause,
+ NumLock,
+ Clear,
+ Keypad0,
+ Keypad1,
+ Keypad2,
+ Keypad3,
+ Keypad4,
+ Keypad5,
+ Keypad6,
+ Keypad7,
+ Keypad8,
+ Keypad9,
+ KeypadDivide,
+ KeypadMultiply,
+ KeypadSubtract,
+ KeypadAdd,
+ KeypadDecimal,
+ KeypadEnter,
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ Number0,
+ Number1,
+ Number2,
+ Number3,
+ Number4,
+ Number5,
+ Number6,
+ Number7,
+ Number8,
+ Number9,
+ Tilde,
+ Grave,
+ Minus,
+ Plus,
+ BracketLeft,
+ BracketRight,
+ Semicolon,
+ Quote,
+ Comma,
+ Period,
+ Slash,
+ BackSlash,
+ Unbound,
+
+ Count
}
}
diff --git a/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs b/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs
new file mode 100644
index 00000000..b6c82c93
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Common.Configuration.Hid.Keyboard
+{
+ public class GenericKeyboardInputConfig<Key> : GenericInputConfigurationCommon<Key> where Key : unmanaged
+ {
+ /// <summary>
+ /// Left JoyCon Controller Stick Bindings
+ /// </summary>
+ public JoyconConfigKeyboardStick<Key> LeftJoyconStick { get; set; }
+
+ /// <summary>
+ /// Right JoyCon Controller Stick Bindings
+ /// </summary>
+ public JoyconConfigKeyboardStick<Key> RightJoyconStick { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs b/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs
new file mode 100644
index 00000000..cadc17e8
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Common.Configuration.Hid.Keyboard
+{
+ public class JoyconConfigKeyboardStick<Key> where Key: unmanaged
+ {
+ public Key StickUp { get; set; }
+ public Key StickDown { get; set; }
+ public Key StickLeft { get; set; }
+ public Key StickRight { get; set; }
+ public Key StickButton { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs b/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs
new file mode 100644
index 00000000..054d777d
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Common.Configuration.Hid.Keyboard
+{
+ public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardConfig.cs b/Ryujinx.Common/Configuration/Hid/KeyboardConfig.cs
deleted file mode 100644
index 4e217ae5..00000000
--- a/Ryujinx.Common/Configuration/Hid/KeyboardConfig.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Ryujinx.Common.Configuration.Hid
-{
- public class KeyboardConfig : InputConfig
- {
- // DO NOT MODIFY
- public const uint AllKeyboardsIndex = 0;
-
- /// <summary>
- /// Left JoyCon Keyboard Bindings
- /// </summary>
- public NpadKeyboardLeft LeftJoycon { get; set; }
-
- /// <summary>
- /// Right JoyCon Keyboard Bindings
- /// </summary>
- public NpadKeyboardRight RightJoycon { get; set; }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
index 19cc0487..8e9f168d 100644
--- a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
+++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
@@ -1,6 +1,4 @@
-using Ryujinx.Configuration.Hid;
-
-namespace Ryujinx.Common.Configuration.Hid
+namespace Ryujinx.Common.Configuration.Hid
{
public struct KeyboardHotkeys
{
diff --git a/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs b/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs
new file mode 100644
index 00000000..a57240c4
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Common.Configuration.Hid
+{
+ public class LeftJoyconCommonConfig<Button>
+ {
+ public Button ButtonMinus { get; set; }
+ public Button ButtonL { get; set; }
+ public Button ButtonZl { get; set; }
+ public Button ButtonSl { get; set; }
+ public Button ButtonSr { get; set; }
+ public Button DpadUp { get; set; }
+ public Button DpadDown { get; set; }
+ public Button DpadLeft { get; set; }
+ public Button DpadRight { get; set; }
+ }
+}
diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs
deleted file mode 100644
index 00816e56..00000000
--- a/Ryujinx.Common/Configuration/Hid/NpadControllerLeft.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Ryujinx.Common.Configuration.Hid
-{
- public struct NpadControllerLeft
- {
- public ControllerInputId StickX { get; set; }
- public bool InvertStickX { get; set; }
- public ControllerInputId StickY { get; set; }
- public bool InvertStickY { get; set; }
- public ControllerInputId StickButton { get; set; }
- public ControllerInputId ButtonMinus { get; set; }
- public ControllerInputId ButtonL { get; set; }
- public ControllerInputId ButtonZl { get; set; }
- public ControllerInputId ButtonSl { get; set; }
- public ControllerInputId ButtonSr { get; set; }
- public ControllerInputId DPadUp { get; set; }
- public ControllerInputId DPadDown { get; set; }
- public ControllerInputId DPadLeft { get; set; }
- public ControllerInputId DPadRight { get; set; }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs b/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs
deleted file mode 100644
index b7b289cc..00000000
--- a/Ryujinx.Common/Configuration/Hid/NpadControllerRight.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Ryujinx.Common.Configuration.Hid
-{
- public struct NpadControllerRight
- {
- public ControllerInputId StickX { get; set; }
- public bool InvertStickX { get; set; }
- public ControllerInputId StickY { get; set; }
- public bool InvertStickY { get; set; }
- public ControllerInputId StickButton { get; set; }
- public ControllerInputId ButtonA { get; set; }
- public ControllerInputId ButtonB { get; set; }
- public ControllerInputId ButtonX { get; set; }
- public ControllerInputId ButtonY { get; set; }
- public ControllerInputId ButtonPlus { get; set; }
- public ControllerInputId ButtonR { get; set; }
- public ControllerInputId ButtonZr { get; set; }
- public ControllerInputId ButtonSl { get; set; }
- public ControllerInputId ButtonSr { get; set; }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs
deleted file mode 100644
index 6b78f5b6..00000000
--- a/Ryujinx.Common/Configuration/Hid/NpadKeyboardLeft.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Ryujinx.Configuration.Hid;
-
-namespace Ryujinx.Common.Configuration.Hid
-{
- public struct NpadKeyboardLeft
- {
- public Key StickUp { get; set; }
- public Key StickDown { get; set; }
- public Key StickLeft { get; set; }
- public Key StickRight { get; set; }
- public Key StickButton { get; set; }
- public Key DPadUp { get; set; }
- public Key DPadDown { get; set; }
- public Key DPadLeft { get; set; }
- public Key DPadRight { get; set; }
- public Key ButtonMinus { get; set; }
- public Key ButtonL { get; set; }
- public Key ButtonZl { get; set; }
- public Key ButtonSl { get; set; }
- public Key ButtonSr { get; set; }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs b/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs
deleted file mode 100644
index e2109902..00000000
--- a/Ryujinx.Common/Configuration/Hid/NpadKeyboardRight.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Ryujinx.Configuration.Hid;
-
-namespace Ryujinx.Common.Configuration.Hid
-{
- public struct NpadKeyboardRight
- {
- public Key StickUp { get; set; }
- public Key StickDown { get; set; }
- public Key StickLeft { get; set; }
- public Key StickRight { get; set; }
- public Key StickButton { get; set; }
- public Key ButtonA { get; set; }
- public Key ButtonB { get; set; }
- public Key ButtonX { get; set; }
- public Key ButtonY { get; set; }
- public Key ButtonPlus { get; set; }
- public Key ButtonR { get; set; }
- public Key ButtonZr { get; set; }
- public Key ButtonSl { get; set; }
- public Key ButtonSr { get; set; }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs b/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs
new file mode 100644
index 00000000..ca2d0176
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Common.Configuration.Hid
+{
+ public class RightJoyconCommonConfig<Button>
+ {
+ public Button ButtonPlus { get; set; }
+ public Button ButtonR { get; set; }
+ public Button ButtonZr { get; set; }
+ public Button ButtonSl { get; set; }
+ public Button ButtonSr { get; set; }
+ public Button ButtonX { get; set; }
+ public Button ButtonB { get; set; }
+ public Button ButtonY { get; set; }
+ public Button ButtonA { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Common/Utilities/JsonHelper.cs b/Ryujinx.Common/Utilities/JsonHelper.cs
index 5aa46183..36f39114 100644
--- a/Ryujinx.Common/Utilities/JsonHelper.cs
+++ b/Ryujinx.Common/Utilities/JsonHelper.cs
@@ -1,4 +1,6 @@
-using System.IO;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
+using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -63,6 +65,8 @@ namespace Ryujinx.Common.Utilities
};
options.Converters.Add(new JsonStringEnumConverter());
+ options.Converters.Add(new JsonInputConfigConverter());
+ options.Converters.Add(new JsonMotionConfigControllerConverter());
return options;
}
diff --git a/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs b/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs
index d56951a7..f942037c 100644
--- a/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs
+++ b/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs
@@ -1,37 +1,24 @@
-using OpenTK;
-using OpenTK.Graphics;
-using Ryujinx.Common;
+using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.OpenGL
{
- class BackgroundContextWorker : IDisposable
+ unsafe class BackgroundContextWorker : IDisposable
{
[ThreadStatic]
public static bool InBackground;
-
- private GameWindow _window;
- private GraphicsContext _context;
private Thread _thread;
private bool _running;
private AutoResetEvent _signal;
private Queue<Action> _work;
private ObjectPool<ManualResetEventSlim> _invokePool;
+ private readonly IOpenGLContext _backgroundContext;
- public BackgroundContextWorker(IGraphicsContext baseContext)
+ public BackgroundContextWorker(IOpenGLContext backgroundContext)
{
- _window = new GameWindow(
- 100, 100, GraphicsMode.Default,
- "Background Window", OpenTK.GameWindowFlags.FixedWindow, OpenTK.DisplayDevice.Default,
- 3, 3, GraphicsContextFlags.ForwardCompatible, baseContext, false);
-
- _window.Visible = false;
- _context = (GraphicsContext)_window.Context;
- _context.MakeCurrent(null);
-
_running = true;
_signal = new AutoResetEvent(false);
@@ -40,12 +27,14 @@ namespace Ryujinx.Graphics.OpenGL
_thread = new Thread(Run);
_thread.Start();
+ _backgroundContext = backgroundContext;
}
private void Run()
{
InBackground = true;
- _context.MakeCurrent(_window.WindowInfo);
+
+ _backgroundContext.MakeCurrent();
while (_running)
{
@@ -66,7 +55,7 @@ namespace Ryujinx.Graphics.OpenGL
}
}
- _window.Dispose();
+ _backgroundContext.Dispose();
}
public void Invoke(Action action)
@@ -99,4 +88,4 @@ namespace Ryujinx.Graphics.OpenGL
_signal.Dispose();
}
}
-}
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs b/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs
new file mode 100644
index 00000000..7cc39708
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.Graphics.OpenGL.Helper
+{
+ [SupportedOSPlatform("linux")]
+ internal static class GLXHelper
+ {
+ private const string LibraryName = "glx.dll";
+
+ static GLXHelper()
+ {
+ NativeLibrary.SetDllImportResolver(typeof(GLXHelper).Assembly, (name, assembly, path) =>
+ {
+ if (name != LibraryName)
+ {
+ return IntPtr.Zero;
+ }
+
+ if (!NativeLibrary.TryLoad("libGL.so.1", assembly, path, out IntPtr result))
+ {
+ if (!NativeLibrary.TryLoad("libGL.so", assembly, path, out result))
+ {
+ return IntPtr.Zero;
+ }
+ }
+
+ return result;
+ });
+ }
+
+ [DllImport(LibraryName, EntryPoint = "glXGetCurrentContext")]
+ public static extern IntPtr GetCurrentContext();
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs b/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs
new file mode 100644
index 00000000..b37134eb
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace Ryujinx.Graphics.OpenGL.Helper
+{
+ [SupportedOSPlatform("windows")]
+ internal static class WGLHelper
+ {
+ private const string LibraryName = "OPENGL32.DLL";
+
+ [DllImport(LibraryName, EntryPoint = "wglGetCurrentContext")]
+ public extern static IntPtr GetCurrentContext();
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs b/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs
new file mode 100644
index 00000000..b1f6d72d
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs
@@ -0,0 +1,27 @@
+using Ryujinx.Graphics.OpenGL.Helper;
+using System;
+
+namespace Ryujinx.Graphics.OpenGL
+{
+ public interface IOpenGLContext : IDisposable
+ {
+ void MakeCurrent();
+
+ // TODO: Support more APIs per platform.
+ static bool HasContext()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ return WGLHelper.GetCurrentContext() != IntPtr.Zero;
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ return GLXHelper.GetCurrentContext() != IntPtr.Zero;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index c3d08309..cc8fa195 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -149,7 +149,7 @@ namespace Ryujinx.Graphics.OpenGL
public void BackgroundContextAction(Action action)
{
- if (GraphicsContext.CurrentContext != null)
+ if (IOpenGLContext.HasContext())
{
action(); // We have a context already - use that (assuming it is the main one).
}
@@ -159,7 +159,7 @@ namespace Ryujinx.Graphics.OpenGL
}
}
- public void InitializeBackgroundContext(IGraphicsContext baseContext)
+ public void InitializeBackgroundContext(IOpenGLContext baseContext)
{
_window.InitializeBackgroundContext(baseContext);
}
diff --git a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
index 5d28b4f3..aff59d97 100644
--- a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
+++ b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
+ <PackageReference Include="OpenTK.Graphics" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
index 80149bf7..5de2a645 100644
--- a/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.OpenGL
// Remove Alpha channel
GL.ColorMask(drawFramebuffer, false, false, false, true);
- GL.ClearBuffer(ClearBuffer.Color, 0, new float[] { 0.0f, 0.0f, 0.0f, 1.0f });
+ GL.ClearBuffer(ClearBuffer.Color, drawFramebuffer, new float[] { 0.0f, 0.0f, 0.0f, 1.0f });
GL.ColorMask(drawFramebuffer,
oldFramebufferColorWritemask[0],
oldFramebufferColorWritemask[1],
@@ -158,7 +158,7 @@ namespace Ryujinx.Graphics.OpenGL
return handle;
}
- public void InitializeBackgroundContext(IGraphicsContext baseContext)
+ public void InitializeBackgroundContext(IOpenGLContext baseContext)
{
BackgroundContext = new BackgroundContextWorker(baseContext);
}
diff --git a/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
new file mode 100644
index 00000000..cee18996
--- /dev/null
+++ b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ppy.SDL2-CS" Version="1.0.225-alpha" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Ryujinx.Input.SDL2/SDL2Driver.cs b/Ryujinx.Input.SDL2/SDL2Driver.cs
new file mode 100644
index 00000000..be709043
--- /dev/null
+++ b/Ryujinx.Input.SDL2/SDL2Driver.cs
@@ -0,0 +1,170 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.IO;
+using System.Threading;
+using static SDL2.SDL;
+
+namespace Ryujinx.Input.SDL2
+{
+ class SDL2Driver : IDisposable
+ {
+ private static SDL2Driver _instance;
+
+ public static bool IsInitialized => _instance != null;
+
+ public static SDL2Driver Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = new SDL2Driver();
+ }
+
+ return _instance;
+ }
+ }
+
+ private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_SENSOR;
+
+ private bool _isRunning;
+ private uint _refereceCount;
+ private Thread _worker;
+
+ public event Action<int, int> OnJoyStickConnected;
+ public event Action<int> OnJoystickDisconnected;
+
+ private object _lock = new object();
+
+ private SDL2Driver() {}
+
+ public void Initialize()
+ {
+ lock (_lock)
+ {
+ _refereceCount++;
+
+ if (_isRunning)
+ {
+ return;
+ }
+
+ SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
+ SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+ // TODO: Add this in nuget package once SDL2 2.0.15 hit stable release.
+ SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0");
+ SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1");
+
+ if (SDL_Init(SdlInitFlags) != 0)
+ {
+ string errorMessage = $"SDL2 initlaization failed with error \"{SDL_GetError()}\"";
+
+ Logger.Error?.Print(LogClass.Application, errorMessage);
+
+ throw new Exception(errorMessage);
+ }
+
+ // First ensure that we only enable joystick events (for connected/disconnected).
+ SDL_GameControllerEventState(SDL_DISABLE);
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ // Disable all joysticks information, we don't need them no need to flood the event queue for that.
+ SDL_EventState(SDL_EventType.SDL_JOYAXISMOTION, SDL_DISABLE);
+ SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE);
+ SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE);
+ SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE);
+ SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE);
+
+ SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
+
+ string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt");
+
+ if (File.Exists(gamepadDbPath))
+ {
+ SDL_GameControllerAddMappingsFromFile(gamepadDbPath);
+ }
+
+ _worker = new Thread(EventWorker);
+ _isRunning = true;
+ _worker.Start();
+ }
+ }
+
+ private void HandleSDLEvent(ref SDL_Event evnt)
+ {
+ if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED)
+ {
+ int deviceId = evnt.cbutton.which;
+
+ // SDL2 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system.
+ int instanceId = SDL_JoystickGetDeviceInstanceID(deviceId);
+
+ if (instanceId == -1)
+ {
+ return;
+ }
+
+ Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}");
+
+ OnJoyStickConnected?.Invoke(deviceId, instanceId);
+ }
+ else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED)
+ {
+ Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}");
+
+ OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
+ }
+ }
+
+ private void EventWorker()
+ {
+ const int WaitTimeMs = 10;
+
+ using ManualResetEventSlim waitHandle = new ManualResetEventSlim(false);
+
+ while (_isRunning)
+ {
+ while (SDL_PollEvent(out SDL_Event evnt) != 0)
+ {
+ HandleSDLEvent(ref evnt);
+ }
+
+ waitHandle.Wait(WaitTimeMs);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposing)
+ {
+ return;
+ }
+
+ lock (_lock)
+ {
+ if (_isRunning)
+ {
+ _refereceCount--;
+
+ if (_refereceCount == 0)
+ {
+ _isRunning = false;
+
+ _worker?.Join();
+
+ SDL_Quit();
+
+ OnJoyStickConnected = null;
+ OnJoystickDisconnected = null;
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/Ryujinx.Input.SDL2/SDL2Gamepad.cs
new file mode 100644
index 00000000..26a808e4
--- /dev/null
+++ b/Ryujinx.Input.SDL2/SDL2Gamepad.cs
@@ -0,0 +1,366 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using static SDL2.SDL;
+
+namespace Ryujinx.Input.SDL2
+{
+ class SDL2Gamepad : IGamepad
+ {
+ private bool HasConfiguration => _configuration != null;
+
+ private class ButtonMappingEntry
+ {
+ public readonly GamepadButtonInputId To;
+ public readonly GamepadButtonInputId From;
+
+ public ButtonMappingEntry(GamepadButtonInputId to, GamepadButtonInputId from)
+ {
+ To = to;
+ From = from;
+ }
+ }
+
+ private StandardControllerInputConfig _configuration;
+
+ private static readonly SDL_GameControllerButton[] _buttonsDriverMapping = new SDL_GameControllerButton[(int)GamepadButtonInputId.Count]
+ {
+ // Unbound, ignored.
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
+
+ // NOTE: The left and right trigger are axis, we handle those differently
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD,
+
+ // Virtual buttons are invalid, ignored.
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID,
+ };
+
+ private object _userMappingLock = new object();
+
+ private List<ButtonMappingEntry> _buttonsUserMapping;
+
+ private StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
+ {
+ StickInputId.Unbound,
+ StickInputId.Left,
+ StickInputId.Right
+ };
+
+ public GamepadFeaturesFlag Features { get; }
+
+ private IntPtr _gamepadHandle;
+
+ private float _triggerThreshold;
+
+ public SDL2Gamepad(IntPtr gamepadHandle, string driverId)
+ {
+ _gamepadHandle = gamepadHandle;
+ _buttonsUserMapping = new List<ButtonMappingEntry>();
+
+ Name = SDL_GameControllerName(_gamepadHandle);
+ Id = driverId;
+ Features = GetFeaturesFlag();
+ _triggerThreshold = 0.0f;
+
+ // Enable motion tracking
+ if (Features.HasFlag(GamepadFeaturesFlag.Motion))
+ {
+ SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE);
+ SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE);
+ }
+ }
+
+ private GamepadFeaturesFlag GetFeaturesFlag()
+ {
+ GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
+
+ if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE &&
+ SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE)
+ {
+ result |= GamepadFeaturesFlag.Motion;
+ }
+
+ int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100);
+
+ if (error == 0)
+ {
+ result |= GamepadFeaturesFlag.Rumble;
+ }
+
+ return result;
+ }
+
+ public string Id { get; }
+ public string Name { get; }
+
+ public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && _gamepadHandle != IntPtr.Zero)
+ {
+ SDL_GameControllerClose(_gamepadHandle);
+
+ _gamepadHandle = IntPtr.Zero;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ _triggerThreshold = triggerThreshold;
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ if (Features.HasFlag(GamepadFeaturesFlag.Rumble))
+ {
+ ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
+ ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
+
+ SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs);
+ }
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID;
+
+ if (inputId == MotionInputId.Accelerometer)
+ {
+ sensorType = SDL_SensorType.SDL_SENSOR_ACCEL;
+ }
+ else if (inputId == MotionInputId.Gyroscope)
+ {
+ sensorType = SDL_SensorType.SDL_SENSOR_GYRO;
+ }
+
+ if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID)
+ {
+ const int ElementCount = 3;
+
+ unsafe
+ {
+ float* values = stackalloc float[ElementCount];
+
+ int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount);
+
+ if (result == 0)
+ {
+ Vector3 value = new Vector3(values[0], values[1], values[2]);
+
+ if (inputId == MotionInputId.Gyroscope)
+ {
+ return RadToDegree(value);
+ }
+ else if (inputId == MotionInputId.Accelerometer)
+ {
+ return GsToMs2(value);
+ }
+
+ return value;
+ }
+ }
+ }
+
+ return Vector3.Zero;
+ }
+
+ private static Vector3 RadToDegree(Vector3 rad)
+ {
+ return rad * (180 / MathF.PI);
+ }
+
+ private static Vector3 GsToMs2(Vector3 gs)
+ {
+ return gs / SDL_STANDARD_GRAVITY;
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ lock (_userMappingLock)
+ {
+ _configuration = (StandardControllerInputConfig)configuration;
+
+ _buttonsUserMapping.Clear();
+
+ // First update sticks
+ _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
+ _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
+
+ // Then left joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
+
+ // Finally right joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
+
+ SetTriggerThreshold(_configuration.TriggerThreshold);
+ }
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ return IGamepad.GetStateSnapshot(this);
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ GamepadStateSnapshot rawState = GetStateSnapshot();
+ GamepadStateSnapshot result = default;
+
+ lock (_userMappingLock)
+ {
+ if (_buttonsUserMapping.Count == 0)
+ {
+ return rawState;
+ }
+
+ foreach (ButtonMappingEntry entry in _buttonsUserMapping)
+ {
+ if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound)
+ {
+ continue;
+ }
+
+ // Do not touch state of button already pressed
+ if (!result.IsPressed(entry.To))
+ {
+ result.SetPressed(entry.To, rawState.IsPressed(entry.From));
+ }
+ }
+
+ (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
+ (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
+
+ result.SetStick(StickInputId.Left, leftStickX, leftStickY);
+ result.SetStick(StickInputId.Right, rightStickX, rightStickY);
+ }
+
+ return result;
+ }
+
+ private static float ConvertRawStickValue(short value)
+ {
+ const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
+
+ return value * ConvertRate;
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ if (inputId == StickInputId.Unbound)
+ {
+ return (0.0f, 0.0f);
+ }
+
+ short stickX;
+ short stickY;
+
+ if (inputId == StickInputId.Left)
+ {
+ stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX);
+ stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY);
+ }
+ else if (inputId == StickInputId.Right)
+ {
+ stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX);
+ stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY);
+ }
+ else
+ {
+ throw new NotSupportedException($"Unsupported stick {inputId}");
+ }
+
+ float resultX = ConvertRawStickValue(stickX);
+ float resultY = -ConvertRawStickValue(stickY);
+
+ if (HasConfiguration)
+ {
+ if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickX) ||
+ (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickX))
+ {
+ resultX = -resultX;
+ }
+
+ if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickY) ||
+ (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickY))
+ {
+ resultY = -resultY;
+ }
+ }
+
+ return (resultX, resultY);
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ if (inputId == GamepadButtonInputId.LeftTrigger)
+ {
+ return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
+ }
+ else if (inputId == GamepadButtonInputId.RightTrigger)
+ {
+ return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
+ }
+ else if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID)
+ {
+ return false;
+ }
+ else
+ {
+ return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
new file mode 100644
index 00000000..62098383
--- /dev/null
+++ b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using static SDL2.SDL;
+
+namespace Ryujinx.Input.SDL2
+{
+ public class SDL2GamepadDriver : IGamepadDriver
+ {
+ private Dictionary<int, string> _gamepadsInstanceIdsMapping;
+ private List<string> _gamepadsIds;
+
+ public ReadOnlySpan<string> GamepadsIds => _gamepadsIds.ToArray();
+
+ public string DriverName => "SDL2";
+
+ public event Action<string> OnGamepadConnected;
+ public event Action<string> OnGamepadDisconnected;
+
+ public SDL2GamepadDriver()
+ {
+ _gamepadsInstanceIdsMapping = new Dictionary<int, string>();
+ _gamepadsIds = new List<string>();
+
+ SDL2Driver.Instance.Initialize();
+ SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
+ SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
+
+ // Add already connected gamepads
+ for (int joystickIndex = 0; joystickIndex < SDL_NumJoysticks(); joystickIndex++)
+ {
+ HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex));
+ }
+ }
+
+ private string GenerateGamepadId(int joystickIndex)
+ {
+ Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
+
+ if (guid == Guid.Empty)
+ {
+ return null;
+ }
+
+ return joystickIndex + "-" + guid.ToString();
+ }
+
+ private int GetJoystickIndexByGamepadId(string id)
+ {
+ string[] data = id.Split("-");
+
+ if (data.Length != 6 || !int.TryParse(data[0], out int joystickIndex))
+ {
+ return -1;
+ }
+
+ return joystickIndex;
+ }
+
+ private void HandleJoyStickDisconnected(int joystickInstanceId)
+ {
+ if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
+ {
+ _gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
+ _gamepadsIds.Remove(id);
+
+ OnGamepadDisconnected?.Invoke(id);
+ }
+ }
+
+ private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
+ {
+ if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
+ {
+ string id = GenerateGamepadId(joystickDeviceId);
+
+ if (id == null)
+ {
+ return;
+ }
+
+ if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
+ {
+ _gamepadsIds.Add(id);
+
+ OnGamepadConnected?.Invoke(id);
+ }
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
+ SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
+
+ // Simulate a full disconnect when disposing
+ foreach (string id in _gamepadsIds)
+ {
+ OnGamepadDisconnected?.Invoke(id);
+ }
+
+ _gamepadsIds.Clear();
+
+ SDL2Driver.Instance.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public IGamepad GetGamepad(string id)
+ {
+ int joystickIndex = GetJoystickIndexByGamepadId(id);
+
+ if (joystickIndex == -1)
+ {
+ return null;
+ }
+
+ if (id != GenerateGamepadId(joystickIndex))
+ {
+ return null;
+ }
+
+ IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex);
+
+ if (gamepadHandle == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return new SDL2Gamepad(gamepadHandle, id);
+ }
+ }
+}
diff --git a/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs b/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs
new file mode 100644
index 00000000..e3aaf8b1
--- /dev/null
+++ b/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs
@@ -0,0 +1,198 @@
+using System.Collections.Generic;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.Input.Assigner
+{
+ /// <summary>
+ /// <see cref="IButtonAssigner"/> implementation for regular <see cref="IGamepad"/>.
+ /// </summary>
+ public class GamepadButtonAssigner : IButtonAssigner
+ {
+ private IGamepad _gamepad;
+
+ private GamepadStateSnapshot _currState;
+
+ private GamepadStateSnapshot _prevState;
+
+ private JoystickButtonDetector _detector;
+
+ private bool _forStick;
+
+ public GamepadButtonAssigner(IGamepad gamepad, float triggerThreshold, bool forStick)
+ {
+ _gamepad = gamepad;
+ _detector = new JoystickButtonDetector();
+ _forStick = forStick;
+
+ _gamepad?.SetTriggerThreshold(triggerThreshold);
+ }
+
+ public void Initialize()
+ {
+ if (_gamepad != null)
+ {
+ _currState = _gamepad.GetStateSnapshot();
+ _prevState = _currState;
+ }
+ }
+
+ public void ReadInput()
+ {
+ if (_gamepad != null)
+ {
+ _prevState = _currState;
+ _currState = _gamepad.GetStateSnapshot();
+ }
+
+ CollectButtonStats();
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ return _detector.HasAnyButtonPressed();
+ }
+
+ public bool ShouldCancel()
+ {
+ return _gamepad == null || !_gamepad.IsConnected;
+ }
+
+ public string GetPressedButton()
+ {
+ IEnumerable<GamepadButtonInputId> pressedButtons = _detector.GetPressedButtons();
+
+ if (pressedButtons.Any())
+ {
+ return !_forStick ? pressedButtons.First().ToString() : ((StickInputId)pressedButtons.First()).ToString();
+ }
+
+ return "";
+ }
+
+ private void CollectButtonStats()
+ {
+ if (_forStick)
+ {
+ for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++)
+ {
+ (float x, float y) = _currState.GetStick(inputId);
+
+ float value;
+
+ if (x != 0.0f)
+ {
+ value = x;
+ }
+ else if (y != 0.0f)
+ {
+ value = y;
+ }
+ else
+ {
+ continue;
+ }
+
+ _detector.AddInput((GamepadButtonInputId)inputId, value);
+ }
+ }
+ else
+ {
+ for (GamepadButtonInputId inputId = GamepadButtonInputId.A; inputId < GamepadButtonInputId.Count; inputId++)
+ {
+ if (_currState.IsPressed(inputId) && !_prevState.IsPressed(inputId))
+ {
+ _detector.AddInput(inputId, 1);
+ }
+
+ if (!_currState.IsPressed(inputId) && _prevState.IsPressed(inputId))
+ {
+ _detector.AddInput(inputId, -1);
+ }
+ }
+ }
+ }
+
+ private class JoystickButtonDetector
+ {
+ private Dictionary<GamepadButtonInputId, InputSummary> _stats;
+
+ public JoystickButtonDetector()
+ {
+ _stats = new Dictionary<GamepadButtonInputId, InputSummary>();
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ return _stats.Values.Any(CheckButtonPressed);
+ }
+
+ public IEnumerable<GamepadButtonInputId> GetPressedButtons()
+ {
+ return _stats.Where(kvp => CheckButtonPressed(kvp.Value)).Select(kvp => kvp.Key);
+ }
+
+ public void AddInput(GamepadButtonInputId button, float value)
+ {
+ InputSummary inputSummary;
+
+ if (!_stats.TryGetValue(button, out inputSummary))
+ {
+ inputSummary = new InputSummary();
+ _stats.Add(button, inputSummary);
+ }
+
+ inputSummary.AddInput(value);
+ }
+
+ public override string ToString()
+ {
+ StringWriter writer = new StringWriter();
+
+ foreach (var kvp in _stats)
+ {
+ writer.WriteLine($"Button {kvp.Key} -> {kvp.Value}");
+ }
+
+ return writer.ToString();
+ }
+
+ private bool CheckButtonPressed(InputSummary sequence)
+ {
+ float distance = Math.Abs(sequence.Min - sequence.Avg) + Math.Abs(sequence.Max - sequence.Avg);
+ return distance > 1.5; // distance range [0, 2]
+ }
+ }
+
+ private class InputSummary
+ {
+ public float Min, Max, Sum, Avg;
+
+ public int NumSamples;
+
+ public InputSummary()
+ {
+ Min = float.MaxValue;
+ Max = float.MinValue;
+ Sum = 0;
+ NumSamples = 0;
+ Avg = 0;
+ }
+
+ public void AddInput(float value)
+ {
+ Min = Math.Min(Min, value);
+ Max = Math.Max(Max, value);
+ Sum += value;
+ NumSamples += 1;
+ Avg = Sum / NumSamples;
+ }
+
+ public override string ToString()
+ {
+ return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}";
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Input/Assigner/IButtonAssigner.cs b/Ryujinx.Input/Assigner/IButtonAssigner.cs
new file mode 100644
index 00000000..021736df
--- /dev/null
+++ b/Ryujinx.Input/Assigner/IButtonAssigner.cs
@@ -0,0 +1,36 @@
+namespace Ryujinx.Input.Assigner
+{
+ /// <summary>
+ /// An interface that allows to gather the driver input info to assign to a button on the UI.
+ /// </summary>
+ public interface IButtonAssigner
+ {
+ /// <summary>
+ /// Initialize the button assigner.
+ /// </summary>
+ void Initialize();
+
+ /// <summary>
+ /// Read input.
+ /// </summary>
+ void ReadInput();
+
+ /// <summary>
+ /// Check if a button was pressed.
+ /// </summary>
+ /// <returns>True if a button was pressed</returns>
+ bool HasAnyButtonPressed();
+
+ /// <summary>
+ /// Indicate if the user of this API should cancel operations. This is triggered for example when a gamepad get disconnected or when a user cancel assignation operations.
+ /// </summary>
+ /// <returns>True if the user of this API should cancel operations</returns>
+ bool ShouldCancel();
+
+ /// <summary>
+ /// Get the pressed button that was read in <see cref="ReadInput"/> by the button assigner.
+ /// </summary>
+ /// <returns>The pressed button that was read</returns>
+ string GetPressedButton();
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs b/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs
new file mode 100644
index 00000000..23ae3655
--- /dev/null
+++ b/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs
@@ -0,0 +1,50 @@
+namespace Ryujinx.Input.Assigner
+{
+ /// <summary>
+ /// <see cref="IButtonAssigner"/> implementation for <see cref="IKeyboard"/>.
+ /// </summary>
+ public class KeyboardKeyAssigner : IButtonAssigner
+ {
+ private IKeyboard _keyboard;
+
+ private KeyboardStateSnapshot _keyboardState;
+
+ public KeyboardKeyAssigner(IKeyboard keyboard)
+ {
+ _keyboard = keyboard;
+ }
+
+ public void Initialize() { }
+
+ public void ReadInput()
+ {
+ _keyboardState = _keyboard.GetKeyboardStateSnapshot();
+ }
+
+ public bool HasAnyButtonPressed()
+ {
+ return GetPressedButton().Length != 0;
+ }
+
+ public bool ShouldCancel()
+ {
+ return _keyboardState.IsPressed(Key.Escape);
+ }
+
+ public string GetPressedButton()
+ {
+ string keyPressed = "";
+
+ for (Key key = Key.Unknown; key < Key.Count; key++)
+ {
+ if (_keyboardState.IsPressed(key))
+ {
+ keyPressed = key.ToString();
+ break;
+ }
+ }
+
+ return !ShouldCancel() ? keyPressed : "";
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Input/GamepadButtonInputId.cs b/Ryujinx.Input/GamepadButtonInputId.cs
new file mode 100644
index 00000000..d1e4b9ac
--- /dev/null
+++ b/Ryujinx.Input/GamepadButtonInputId.cs
@@ -0,0 +1,57 @@
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent a button from a gamepad.
+ /// </summary>
+ 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
+ }
+}
diff --git a/Ryujinx.Input/GamepadFeaturesFlag.cs b/Ryujinx.Input/GamepadFeaturesFlag.cs
new file mode 100644
index 00000000..87310a32
--- /dev/null
+++ b/Ryujinx.Input/GamepadFeaturesFlag.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent features supported by a <see cref="IGamepad"/>.
+ /// </summary>
+ [Flags]
+ public enum GamepadFeaturesFlag
+ {
+ /// <summary>
+ /// No features are supported
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Rumble
+ /// </summary>
+ /// <remarks>Also named haptic</remarks>
+ Rumble,
+
+ /// <summary>
+ /// Motion
+ /// <remarks>Also named sixaxis</remarks>
+ /// </summary>
+ Motion
+ }
+}
diff --git a/Ryujinx.Input/GamepadStateSnapshot.cs b/Ryujinx.Input/GamepadStateSnapshot.cs
new file mode 100644
index 00000000..cf3e3e28
--- /dev/null
+++ b/Ryujinx.Input/GamepadStateSnapshot.cs
@@ -0,0 +1,70 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// A snapshot of a <see cref="IGamepad"/>.
+ /// </summary>
+ public struct GamepadStateSnapshot
+ {
+ // NOTE: Update Array size if JoystickInputId is changed.
+ private Array3<Array2<float>> _joysticksState;
+ // NOTE: Update Array size if GamepadInputId is changed.
+ private Array28<bool> _buttonsState;
+
+ /// <summary>
+ /// Create a new instance of <see cref="GamepadStateSnapshot"/>.
+ /// </summary>
+ /// <param name="joysticksState">The joysticks state</param>
+ /// <param name="buttonsState">The buttons state</param>
+ public GamepadStateSnapshot(Array3<Array2<float>> joysticksState, Array28<bool> buttonsState)
+ {
+ _joysticksState = joysticksState;
+ _buttonsState = buttonsState;
+ }
+
+ /// <summary>
+ /// Check if a given input button is pressed.
+ /// </summary>
+ /// <param name="inputId">The button id</param>
+ /// <returns>True if the given button is pressed</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsPressed(GamepadButtonInputId inputId) => _buttonsState[(int)inputId];
+
+
+ /// <summary>
+ /// Set the state of a given button.
+ /// </summary>
+ /// <param name="inputId">The button id</param>
+ /// <param name="value">The state to assign for the given button.</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetPressed(GamepadButtonInputId inputId, bool value) => _buttonsState[(int)inputId] = value;
+
+ /// <summary>
+ /// Get the values of a given input joystick.
+ /// </summary>
+ /// <param name="inputId">The stick id</param>
+ /// <returns>The values of the given input joystick</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ var result = _joysticksState[(int)inputId];
+
+ return (result[0], result[1]);
+ }
+
+ /// <summary>
+ /// Set the values of a given input joystick.
+ /// </summary>
+ /// <param name="inputId">The stick id</param>
+ /// <param name="x">The x axis value</param>
+ /// <param name="y">The y axis value</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetStick(StickInputId inputId, float x, float y)
+ {
+ _joysticksState[(int)inputId][0] = x;
+ _joysticksState[(int)inputId][1] = y;
+ }
+ }
+}
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> _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> 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<GamepadInput> hleInputStates = new List<GamepadInput>();
+ List<SixAxisInput> hleMotionStates = new List<SixAxisInput>(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);
+ }
+ }
+}
diff --git a/Ryujinx.Input/IGamepad.cs b/Ryujinx.Input/IGamepad.cs
new file mode 100644
index 00000000..cc788333
--- /dev/null
+++ b/Ryujinx.Input/IGamepad.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Memory;
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent an emulated gamepad.
+ /// </summary>
+ public interface IGamepad : IDisposable
+ {
+ /// <summary>
+ /// Features supported by the gamepad.
+ /// </summary>
+ GamepadFeaturesFlag Features { get; }
+
+ /// <summary>
+ /// Unique Id of the gamepad.
+ /// </summary>
+ string Id { get; }
+
+ /// <summary>
+ /// The name of the gamepad.
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// True if the gamepad is connected.
+ /// </summary>
+ bool IsConnected { get; }
+
+ /// <summary>
+ /// Check if a given input button is pressed on the gamepad.
+ /// </summary>
+ /// <param name="inputId">The button id</param>
+ /// <returns>True if the given button is pressed on the gamepad</returns>
+ bool IsPressed(GamepadButtonInputId inputId);
+
+ /// <summary>
+ /// Get the values of a given input joystick on the gamepad.
+ /// </summary>
+ /// <param name="inputId">The stick id</param>
+ /// <returns>The values of the given input joystick on the gamepad</returns>
+ (float, float) GetStick(StickInputId inputId);
+
+ /// <summary>
+ /// Get the values of a given motion sensors on the gamepad.
+ /// </summary>
+ /// <param name="inputId">The motion id</param>
+ /// <returns> The values of the given motion sensors on the gamepad.</returns>
+ Vector3 GetMotionData(MotionInputId inputId);
+
+ /// <summary>
+ /// Configure the threshold of the triggers on the gamepad.
+ /// </summary>
+ /// <param name="triggerThreshold">The threshold value for the triggers on the gamepad</param>
+ void SetTriggerThreshold(float triggerThreshold);
+
+ /// <summary>
+ /// Set the configuration of the gamepad.
+ /// </summary>
+ /// <remarks>This expect config to be in the format expected by the driver</remarks>
+ /// <param name="configuration">The configuration of the gamepad</param>
+ void SetConfiguration(InputConfig configuration);
+
+ /// <summary>
+ /// Starts a rumble effect on the gampead.
+ /// </summary>
+ /// <param name="lowFrequency">The intensity of the low frequency from 0.0f to 1.0f</param>
+ /// <param name="highFrequency">The intensity of the high frequency from 0.0f to 1.0f</param>
+ /// <param name="durationMs">The duration of the rumble effect in milliseconds.</param>
+ void Rumble(float lowFrequency, float highFrequency, uint durationMs);
+
+ /// <summary>
+ /// Get a snaphost of the state of the gamepad that is remapped with the informations from the <see cref="InputConfig"/> set via <see cref="SetConfiguration(InputConfig)"/>.
+ /// </summary>
+ /// <returns>A remapped snaphost of the state of the gamepad.</returns>
+ GamepadStateSnapshot GetMappedStateSnapshot();
+
+ /// <summary>
+ /// Get a snaphost of the state of the gamepad.
+ /// </summary>
+ /// <returns>A snaphost of the state of the gamepad.</returns>
+ GamepadStateSnapshot GetStateSnapshot();
+
+ /// <summary>
+ /// Get a snaphost of the state of a gamepad.
+ /// </summary>
+ /// <param name="gamepad">The gamepad to do a snapshot of</param>
+ /// <returns>A snaphost of the state of the gamepad.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static GamepadStateSnapshot GetStateSnapshot(IGamepad gamepad)
+ {
+ // NOTE: Update Array size if JoystickInputId is changed.
+ Array3<Array2<float>> joysticksState = default;
+
+ for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++)
+ {
+ (float state0, float state1) = gamepad.GetStick(inputId);
+
+ Array2<float> state = default;
+
+ state[0] = state0;
+ state[1] = state1;
+
+ joysticksState[(int)inputId] = state;
+ }
+
+ // NOTE: Update Array size if GamepadInputId is changed.
+ Array28<bool> buttonsState = default;
+
+ for (GamepadButtonInputId inputId = GamepadButtonInputId.A; inputId < GamepadButtonInputId.Count; inputId++)
+ {
+ buttonsState[(int)inputId] = gamepad.IsPressed(inputId);
+ }
+
+ return new GamepadStateSnapshot(joysticksState, buttonsState);
+ }
+ }
+}
diff --git a/Ryujinx.Input/IGamepadDriver.cs b/Ryujinx.Input/IGamepadDriver.cs
new file mode 100644
index 00000000..792aef00
--- /dev/null
+++ b/Ryujinx.Input/IGamepadDriver.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent an emulated gamepad driver used to provide input in the emulator.
+ /// </summary>
+ public interface IGamepadDriver : IDisposable
+ {
+ /// <summary>
+ /// The name of the driver
+ /// </summary>
+ string DriverName { get; }
+
+ /// <summary>
+ /// The unique ids of the gamepads connected.
+ /// </summary>
+ ReadOnlySpan<string> GamepadsIds { get; }
+
+ /// <summary>
+ /// Event triggered when a gamepad is connected.
+ /// </summary>
+ event Action<string> OnGamepadConnected;
+
+ /// <summary>
+ /// Event triggered when a gamepad is disconnected.
+ /// </summary>
+ event Action<string> OnGamepadDisconnected;
+
+ /// <summary>
+ /// Open a gampad by its unique id.
+ /// </summary>
+ /// <param name="id">The unique id of the gamepad</param>
+ /// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns>
+ IGamepad GetGamepad(string id);
+ }
+}
diff --git a/Ryujinx.Input/IKeyboard.cs b/Ryujinx.Input/IKeyboard.cs
new file mode 100644
index 00000000..506ec099
--- /dev/null
+++ b/Ryujinx.Input/IKeyboard.cs
@@ -0,0 +1,41 @@
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent an emulated keyboard.
+ /// </summary>
+ public interface IKeyboard : IGamepad
+ {
+ /// <summary>
+ /// Check if a given key is pressed on the keyboard.
+ /// </summary>
+ /// <param name="key">The key</param>
+ /// <returns>True if the given key is pressed on the keyboard</returns>
+ bool IsPressed(Key key);
+
+ /// <summary>
+ /// Get a snaphost of the state of the keyboard.
+ /// </summary>
+ /// <returns>A snaphost of the state of the keyboard.</returns>
+ KeyboardStateSnapshot GetKeyboardStateSnapshot();
+
+ /// <summary>
+ /// Get a snaphost of the state of a keyboard.
+ /// </summary>
+ /// <param name="keyboard">The keyboard to do a snapshot of</param>
+ /// <returns>A snaphost of the state of the keyboard.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard)
+ {
+ bool[] keysState = new bool[(int)Key.Count];
+
+ for (Key key = 0; key < Key.Count; key++)
+ {
+ keysState[(int)key] = keyboard.IsPressed(key);
+ }
+
+ return new KeyboardStateSnapshot(keysState);
+ }
+ }
+}
diff --git a/Ryujinx.Input/Key.cs b/Ryujinx.Input/Key.cs
new file mode 100644
index 00000000..0cadf4ec
--- /dev/null
+++ b/Ryujinx.Input/Key.cs
@@ -0,0 +1,142 @@
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent a key from a keyboard.
+ /// </summary>
+ public enum Key
+ {
+ Unknown,
+ ShiftLeft,
+ ShiftRight,
+ ControlLeft,
+ ControlRight,
+ AltLeft,
+ AltRight,
+ WinLeft,
+ WinRight,
+ Menu,
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+ F25,
+ F26,
+ F27,
+ F28,
+ F29,
+ F30,
+ F31,
+ F32,
+ F33,
+ F34,
+ F35,
+ Up,
+ Down,
+ Left,
+ Right,
+ Enter,
+ Escape,
+ Space,
+ Tab,
+ BackSpace,
+ Insert,
+ Delete,
+ PageUp,
+ PageDown,
+ Home,
+ End,
+ CapsLock,
+ ScrollLock,
+ PrintScreen,
+ Pause,
+ NumLock,
+ Clear,
+ Keypad0,
+ Keypad1,
+ Keypad2,
+ Keypad3,
+ Keypad4,
+ Keypad5,
+ Keypad6,
+ Keypad7,
+ Keypad8,
+ Keypad9,
+ KeypadDivide,
+ KeypadMultiply,
+ KeypadSubtract,
+ KeypadAdd,
+ KeypadDecimal,
+ KeypadEnter,
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ Number0,
+ Number1,
+ Number2,
+ Number3,
+ Number4,
+ Number5,
+ Number6,
+ Number7,
+ Number8,
+ Number9,
+ Tilde,
+ Grave,
+ Minus,
+ Plus,
+ BracketLeft,
+ BracketRight,
+ Semicolon,
+ Quote,
+ Comma,
+ Period,
+ Slash,
+ BackSlash,
+ Unbound,
+
+ Count
+ }
+}
diff --git a/Ryujinx.Input/KeyboardStateSnapshot.cs b/Ryujinx.Input/KeyboardStateSnapshot.cs
new file mode 100644
index 00000000..da77a461
--- /dev/null
+++ b/Ryujinx.Input/KeyboardStateSnapshot.cs
@@ -0,0 +1,29 @@
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// A snapshot of a <see cref="IKeyboard"/>.
+ /// </summary>
+ public class KeyboardStateSnapshot
+ {
+ private bool[] _keysState;
+
+ /// <summary>
+ /// Create a new <see cref="KeyboardStateSnapshot"/>.
+ /// </summary>
+ /// <param name="keysState">The keys state</param>
+ public KeyboardStateSnapshot(bool[] keysState)
+ {
+ _keysState = keysState;
+ }
+
+ /// <summary>
+ /// Check if a given key is pressed.
+ /// </summary>
+ /// <param name="key">The key</param>
+ /// <returns>True if the given key is pressed</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsPressed(Key key) => _keysState[(int)key];
+ }
+}
diff --git a/Ryujinx/Modules/Motion/Client.cs b/Ryujinx.Input/Motion/CemuHook/Client.cs
index 3f18fb7f..395bd0b3 100644
--- a/Ryujinx/Modules/Motion/Client.cs
+++ b/Ryujinx.Input/Motion/CemuHook/Client.cs
@@ -1,8 +1,11 @@
using Force.Crc32;
using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Logging;
using Ryujinx.Configuration;
+using Ryujinx.Input.Motion.CemuHook.Protocol;
using System;
using System.Collections.Generic;
using System.IO;
@@ -11,7 +14,7 @@ using System.Net.Sockets;
using System.Numerics;
using System.Threading.Tasks;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion.CemuHook
{
public class Client : IDisposable
{
@@ -324,32 +327,43 @@ namespace Ryujinx.Modules.Motion
lock (_motionData)
{
- int slot = inputData.Shared.Slot;
-
- if (_motionData.ContainsKey(clientId))
+ // Sanity check the configuration state and remove client if needed if needed.
+ if (config is StandardControllerInputConfig controllerConfig &&
+ controllerConfig.Motion.EnableMotion &&
+ controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook &&
+ controllerConfig.Motion is CemuHookMotionConfigController cemuHookConfig)
{
- if (_motionData[clientId].ContainsKey(slot))
+ int slot = inputData.Shared.Slot;
+
+ if (_motionData.ContainsKey(clientId))
{
- MotionInput previousData = _motionData[clientId][slot];
+ if (_motionData[clientId].ContainsKey(slot))
+ {
+ MotionInput previousData = _motionData[clientId][slot];
+
+ previousData.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone);
+ }
+ else
+ {
+ MotionInput input = new MotionInput();
- previousData.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
+ input.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone);
+
+ _motionData[clientId].Add(slot, input);
+ }
}
else
{
MotionInput input = new MotionInput();
- input.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
+ input.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone);
- _motionData[clientId].Add(slot, input);
+ _motionData.Add(clientId, new Dictionary<int, MotionInput>() { { slot, input } });
}
}
else
{
- MotionInput input = new MotionInput();
-
- input.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
-
- _motionData.Add(clientId, new Dictionary<int, MotionInput>() { { slot, input } });
+ RemoveClient(clientId);
}
}
break;
diff --git a/Ryujinx/Modules/Motion/Protocol/ControllerData.cs b/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs
index 30972b86..9ff8dc91 100644
--- a/Ryujinx/Modules/Motion/Protocol/ControllerData.cs
+++ b/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ControllerDataRequest
diff --git a/Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs b/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs
index 63e2db5b..d483633e 100644
--- a/Ryujinx/Modules/Motion/Protocol/ControllerInfo.cs
+++ b/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoResponse
diff --git a/Ryujinx/Modules/Motion/Protocol/Header.cs b/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs
index 39d8b531..94cf4bb6 100644
--- a/Ryujinx/Modules/Motion/Protocol/Header.cs
+++ b/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Header
diff --git a/Ryujinx/Modules/Motion/Protocol/MessageType.cs b/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs
index 3cbb6579..de1e5e90 100644
--- a/Ryujinx/Modules/Motion/Protocol/MessageType.cs
+++ b/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
public enum MessageType : uint
{
diff --git a/Ryujinx/Modules/Motion/Protocol/SharedResponse.cs b/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs
index e5b03722..0593286d 100644
--- a/Ryujinx/Modules/Motion/Protocol/SharedResponse.cs
+++ b/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SharedResponse
diff --git a/Ryujinx/Modules/Motion/MotionInput.cs b/Ryujinx.Input/Motion/MotionInput.cs
index 3e4d9768..d92c3d7f 100644
--- a/Ryujinx/Modules/Motion/MotionInput.cs
+++ b/Ryujinx.Input/Motion/MotionInput.cs
@@ -1,7 +1,8 @@
-using System;
+using Ryujinx.Input.Motion;
+using System;
using System.Numerics;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input
{
public class MotionInput
{
diff --git a/Ryujinx/Modules/Motion/MotionSensorFilter.cs b/Ryujinx.Input/Motion/MotionSensorFilter.cs
index b83e61a8..440fa7ac 100644
--- a/Ryujinx/Modules/Motion/MotionSensorFilter.cs
+++ b/Ryujinx.Input/Motion/MotionSensorFilter.cs
@@ -1,11 +1,11 @@
using System.Numerics;
-namespace Ryujinx.Modules.Motion
+namespace Ryujinx.Input.Motion
{
// MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm.
// See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
// Based on: https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
- public class MotionSensorFilter
+ class MotionSensorFilter
{
/// <summary>
/// Sample rate coefficient.
diff --git a/Ryujinx.Input/MotionInputId.cs b/Ryujinx.Input/MotionInputId.cs
new file mode 100644
index 00000000..3176a987
--- /dev/null
+++ b/Ryujinx.Input/MotionInputId.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent a motion sensor on a gamepad.
+ /// </summary>
+ public enum MotionInputId : byte
+ {
+ /// <summary>
+ /// Invalid.
+ /// </summary>
+ Invalid,
+
+ /// <summary>
+ /// Accelerometer.
+ /// </summary>
+ /// <remarks>Values are in m/s^2</remarks>
+ Accelerometer,
+
+ /// <summary>
+ /// Gyroscope.
+ /// </summary>
+ /// <remarks>Values are in degrees</remarks>
+ Gyroscope
+ }
+}
diff --git a/Ryujinx.Input/Ryujinx.Input.csproj b/Ryujinx.Input/Ryujinx.Input.csproj
new file mode 100644
index 00000000..b2de3ac2
--- /dev/null
+++ b/Ryujinx.Input/Ryujinx.Input.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Crc32.NET" Version="1.2.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
+ <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Ryujinx.Input/StickInputId.cs b/Ryujinx.Input/StickInputId.cs
new file mode 100644
index 00000000..fc9d8043
--- /dev/null
+++ b/Ryujinx.Input/StickInputId.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Input
+{
+ /// <summary>
+ /// Represent a joystick from a gamepad.
+ /// </summary>
+ public enum StickInputId : byte
+ {
+ Unbound,
+ Left,
+ Right,
+
+ Count
+ }
+}
diff --git a/Ryujinx.sln b/Ryujinx.sln
index ee1d57bd..bd00f000 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Open
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input", "Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input.SDL2", "Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -165,6 +169,14 @@ Global
{716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json
index 804b020c..29460614 100644
--- a/Ryujinx/Config.json
+++ b/Ryujinx/Config.json
@@ -1,5 +1,5 @@
{
- "version": 22,
+ "version": 24,
"res_scale": 1,
"res_scale_custom": 1,
"max_anisotropy": -1,
@@ -13,6 +13,7 @@
"logging_enable_guest": true,
"logging_enable_fs_access_log": false,
"logging_filtered_classes": [],
+ "logging_graphics_debug_level": "None",
"enable_file_log": true,
"system_language": "AmericanEnglish",
"system_region": "USA",
@@ -25,10 +26,12 @@
"hide_cursor_on_idle": false,
"enable_vsync": true,
"enable_shader_cache": true,
+ "enable_multicore_scheduling": false,
"enable_ptc": true,
"enable_fs_integrity_checks": true,
"fs_global_access_log_mode": 0,
"audio_backend": "OpenAl",
+ "expand_ram": false,
"ignore_missing_services": false,
"gui_columns": {
"fav_column": true,
@@ -54,51 +57,51 @@
"hotkeys": {
"toggle_vsync": "Tab"
},
- "keyboard_config": [
+ "keyboard_config": [],
+ "controller_config": [],
+ "input_config": [
{
- "index": 0,
- "controller_type": "JoyconPair",
- "player_index": "Player1",
- "left_joycon": {
+ "left_joycon_stick": {
"stick_up": "W",
"stick_down": "S",
"stick_left": "A",
"stick_right": "D",
- "stick_button": "F",
- "dpad_up": "Up",
- "dpad_down": "Down",
- "dpad_left": "Left",
- "dpad_right": "Right",
+ "stick_button": "F"
+ },
+ "right_joycon_stick": {
+ "stick_up": "I",
+ "stick_down": "K",
+ "stick_left": "J",
+ "stick_right": "L",
+ "stick_button": "H"
+ },
+ "left_joycon": {
"button_minus": "Minus",
"button_l": "E",
"button_zl": "Q",
"button_sl": "Unbound",
- "button_sr": "Unbound"
+ "button_sr": "Unbound",
+ "dpad_up": "Up",
+ "dpad_down": "Down",
+ "dpad_left": "Left",
+ "dpad_right": "Right"
},
"right_joycon": {
- "stick_up": "I",
- "stick_down": "K",
- "stick_left": "J",
- "stick_right": "L",
- "stick_button": "H",
- "button_a": "Z",
- "button_b": "X",
- "button_x": "C",
- "button_y": "V",
"button_plus": "Plus",
"button_r": "U",
"button_zr": "O",
"button_sl": "Unbound",
- "button_sr": "Unbound"
+ "button_sr": "Unbound",
+ "button_x": "C",
+ "button_b": "X",
+ "button_y": "V",
+ "button_a": "Z"
},
- "slot": 0,
- "alt_slot": 0,
- "mirror_input": false,
- "dsu_server_host": "127.0.0.1",
- "dsu_server_port": 26760,
- "sensitivity": 100,
- "enable_motion": false
+ "version": 1,
+ "backend": "WindowKeyboard",
+ "id": "0",
+ "controller_type": "JoyconPair",
+ "player_index": "Player1"
}
- ],
- "controller_config": []
-}
+ ]
+} \ No newline at end of file
diff --git a/Ryujinx/Input/GTK3/GTK3Keyboard.cs b/Ryujinx/Input/GTK3/GTK3Keyboard.cs
new file mode 100644
index 00000000..bc0ad87e
--- /dev/null
+++ b/Ryujinx/Input/GTK3/GTK3Keyboard.cs
@@ -0,0 +1,204 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
+
+namespace Ryujinx.Input.GTK3
+{
+ public class GTK3Keyboard : IKeyboard
+ {
+ private class ButtonMappingEntry
+ {
+ public readonly GamepadButtonInputId To;
+ public readonly Key From;
+
+ public ButtonMappingEntry(GamepadButtonInputId to, Key from)
+ {
+ To = to;
+ From = from;
+ }
+ }
+
+ private object _userMappingLock = new object();
+
+ private readonly GTK3KeyboardDriver _driver;
+ private StandardKeyboardInputConfig _configuration;
+ private List<ButtonMappingEntry> _buttonsUserMapping;
+
+ public GTK3Keyboard(GTK3KeyboardDriver driver, string id, string name)
+ {
+ _driver = driver;
+ Id = id;
+ Name = name;
+ _buttonsUserMapping = new List<ButtonMappingEntry>();
+ }
+
+ private bool HasConfiguration => _configuration != null;
+
+ public string Id { get; }
+
+ public string Name { get; }
+
+ public bool IsConnected => true;
+
+ public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
+
+ public void Dispose()
+ {
+ // No operations
+ }
+
+ public KeyboardStateSnapshot GetKeyboardStateSnapshot()
+ {
+ return IKeyboard.GetStateSnapshot(this);
+ }
+
+ private static float ConvertRawStickValue(short value)
+ {
+ const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
+
+ return value * ConvertRate;
+ }
+
+ private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
+ {
+ short stickX = 0;
+ short stickY = 0;
+
+ if (snapshot.IsPressed((Key)stickConfig.StickUp))
+ {
+ stickY += 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickDown))
+ {
+ stickY -= 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickRight))
+ {
+ stickX += 1;
+ }
+
+ if (snapshot.IsPressed((Key)stickConfig.StickLeft))
+ {
+ stickX -= 1;
+ }
+
+ OpenTK.Mathematics.Vector2 stick = new OpenTK.Mathematics.Vector2(stickX, stickY);
+
+ stick.NormalizeFast();
+
+ return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
+ }
+
+ public GamepadStateSnapshot GetMappedStateSnapshot()
+ {
+ KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
+ GamepadStateSnapshot result = default;
+
+ lock (_userMappingLock)
+ {
+ if (!HasConfiguration)
+ {
+ return result;
+ }
+
+ foreach (ButtonMappingEntry entry in _buttonsUserMapping)
+ {
+ if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
+ {
+ continue;
+ }
+
+ // Do not touch state of button already pressed
+ if (!result.IsPressed(entry.To))
+ {
+ result.SetPressed(entry.To, rawState.IsPressed(entry.From));
+ }
+ }
+
+ (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
+ (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
+
+ result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
+ result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
+ }
+
+ return result;
+ }
+
+ public GamepadStateSnapshot GetStateSnapshot()
+ {
+ throw new NotSupportedException();
+ }
+
+ public (float, float) GetStick(StickInputId inputId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool IsPressed(GamepadButtonInputId inputId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool IsPressed(Key key)
+ {
+ return _driver.IsPressed(key);
+ }
+
+ public void SetConfiguration(InputConfig configuration)
+ {
+ lock (_userMappingLock)
+ {
+ _configuration = (StandardKeyboardInputConfig)configuration;
+
+ _buttonsUserMapping.Clear();
+
+ // Then left joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
+
+ // Finally right joycon
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
+ _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
+ }
+ }
+
+ public void SetTriggerThreshold(float triggerThreshold)
+ {
+ // No operations
+ }
+
+ public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
+ {
+ // No operations
+ }
+
+ public Vector3 GetMotionData(MotionInputId inputId)
+ {
+ // No operations
+
+ return Vector3.Zero;
+ }
+ }
+}
diff --git a/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs b/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs
new file mode 100644
index 00000000..10c092fe
--- /dev/null
+++ b/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs
@@ -0,0 +1,93 @@
+using Gdk;
+using Gtk;
+using System;
+using System.Collections.Generic;
+using GtkKey = Gdk.Key;
+
+namespace Ryujinx.Input.GTK3
+{
+ public class GTK3KeyboardDriver : IGamepadDriver
+ {
+ private readonly Widget _widget;
+ private HashSet<GtkKey> _pressedKeys;
+
+ public GTK3KeyboardDriver(Widget widget)
+ {
+ _widget = widget;
+ _pressedKeys = new HashSet<GtkKey>();
+
+ _widget.KeyPressEvent += OnKeyPress;
+ _widget.KeyReleaseEvent += OnKeyRelease;
+ }
+
+ public string DriverName => "GTK3";
+
+ private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
+
+ public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
+
+ public event Action<string> OnGamepadConnected
+ {
+ add { }
+ remove { }
+ }
+
+ public event Action<string> OnGamepadDisconnected
+ {
+ add { }
+ remove { }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _widget.KeyPressEvent -= OnKeyPress;
+ _widget.KeyReleaseEvent -= OnKeyRelease;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ [GLib.ConnectBefore]
+ protected void OnKeyPress(object sender, KeyPressEventArgs args)
+ {
+ GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key);
+
+ _pressedKeys.Add(key);
+ }
+
+ [GLib.ConnectBefore]
+ protected void OnKeyRelease(object sender, KeyReleaseEventArgs args)
+ {
+ GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key);
+
+ _pressedKeys.Remove(key);
+ }
+
+ internal bool IsPressed(Key key)
+ {
+ if (key == Key.Unbound || key == Key.Unknown)
+ {
+ return false;
+ }
+
+ GtkKey nativeKey = GTK3MappingHelper.ToGtkKey(key);
+
+ return _pressedKeys.Contains(nativeKey);
+ }
+
+ public IGamepad GetGamepad(string id)
+ {
+ if (!_keyboardIdentifers[0].Equals(id))
+ {
+ return null;
+ }
+
+ return new GTK3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
+ }
+ }
+}
diff --git a/Ryujinx/Input/GTK3/GTK3MappingHelper.cs b/Ryujinx/Input/GTK3/GTK3MappingHelper.cs
new file mode 100644
index 00000000..5e68aa40
--- /dev/null
+++ b/Ryujinx/Input/GTK3/GTK3MappingHelper.cs
@@ -0,0 +1,154 @@
+using System.Runtime.CompilerServices;
+using GtkKey = Gdk.Key;
+
+namespace Ryujinx.Input.GTK3
+{
+ public static class GTK3MappingHelper
+ {
+ private static readonly GtkKey[] _keyMapping = new GtkKey[(int)Key.Count]
+ {
+ // NOTE: invalid
+ GtkKey.blank,
+
+ GtkKey.Shift_L,
+ GtkKey.Shift_R,
+ GtkKey.Control_L,
+ GtkKey.Control_R,
+ GtkKey.Alt_L,
+ GtkKey.Alt_R,
+ GtkKey.Super_L,
+ GtkKey.Super_R,
+ GtkKey.Menu,
+ GtkKey.F1,
+ GtkKey.F2,
+ GtkKey.F3,
+ GtkKey.F4,
+ GtkKey.F5,
+ GtkKey.F6,
+ GtkKey.F7,
+ GtkKey.F8,
+ GtkKey.F9,
+ GtkKey.F10,
+ GtkKey.F11,
+ GtkKey.F12,
+ GtkKey.F13,
+ GtkKey.F14,
+ GtkKey.F15,
+ GtkKey.F16,
+ GtkKey.F17,
+ GtkKey.F18,
+ GtkKey.F19,
+ GtkKey.F20,
+ GtkKey.F21,
+ GtkKey.F22,
+ GtkKey.F23,
+ GtkKey.F24,
+ GtkKey.F25,
+ GtkKey.F26,
+ GtkKey.F27,
+ GtkKey.F28,
+ GtkKey.F29,
+ GtkKey.F29,
+ GtkKey.F31,
+ GtkKey.F32,
+ GtkKey.F33,
+ GtkKey.F34,
+ GtkKey.F35,
+ GtkKey.Up,
+ GtkKey.Down,
+ GtkKey.Left,
+ GtkKey.Right,
+ GtkKey.Return,
+ GtkKey.Escape,
+ GtkKey.space,
+ GtkKey.Tab,
+ GtkKey.BackSpace,
+ GtkKey.Insert,
+ GtkKey.Delete,
+ GtkKey.Page_Up,
+ GtkKey.Page_Down,
+ GtkKey.Home,
+ GtkKey.End,
+ GtkKey.Caps_Lock,
+ GtkKey.Scroll_Lock,
+ GtkKey.Print,
+ GtkKey.Pause,
+ GtkKey.Num_Lock,
+ GtkKey.Clear,
+ GtkKey.KP_0,
+ GtkKey.KP_1,
+ GtkKey.KP_2,
+ GtkKey.KP_3,
+ GtkKey.KP_4,
+ GtkKey.KP_5,
+ GtkKey.KP_6,
+ GtkKey.KP_7,
+ GtkKey.KP_8,
+ GtkKey.KP_9,
+ GtkKey.KP_Divide,
+ GtkKey.KP_Multiply,
+ GtkKey.KP_Subtract,
+ GtkKey.KP_Add,
+ GtkKey.KP_Decimal,
+ GtkKey.KP_Enter,
+ GtkKey.a,
+ GtkKey.b,
+ GtkKey.c,
+ GtkKey.d,
+ GtkKey.e,
+ GtkKey.f,
+ GtkKey.g,
+ GtkKey.h,
+ GtkKey.i,
+ GtkKey.j,
+ GtkKey.k,
+ GtkKey.l,
+ GtkKey.m,
+ GtkKey.n,
+ GtkKey.o,
+ GtkKey.p,
+ GtkKey.q,
+ GtkKey.r,
+ GtkKey.s,
+ GtkKey.t,
+ GtkKey.u,
+ GtkKey.v,
+ GtkKey.w,
+ GtkKey.x,
+ GtkKey.y,
+ GtkKey.z,
+ GtkKey.Key_0,
+ GtkKey.Key_1,
+ GtkKey.Key_2,
+ GtkKey.Key_3,
+ GtkKey.Key_4,
+ GtkKey.Key_5,
+ GtkKey.Key_6,
+ GtkKey.Key_7,
+ GtkKey.Key_8,
+ GtkKey.Key_9,
+ GtkKey.grave,
+ GtkKey.minus,
+ GtkKey.plus,
+ GtkKey.bracketleft,
+ GtkKey.bracketright,
+ GtkKey.semicolon,
+ GtkKey.quotedbl,
+ GtkKey.comma,
+ GtkKey.period,
+ GtkKey.slash,
+ GtkKey.backslash,
+ GtkKey.backslash,
+
+ // NOTE: invalid
+ GtkKey.blank,
+ };
+
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GtkKey ToGtkKey(Key key)
+ {
+ return _keyMapping[(int)key];
+ }
+ }
+}
diff --git a/Ryujinx/Modules/Motion/MotionDevice.cs b/Ryujinx/Modules/Motion/MotionDevice.cs
deleted file mode 100644
index 4fb9b422..00000000
--- a/Ryujinx/Modules/Motion/MotionDevice.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using Ryujinx.Common.Configuration.Hid;
-using Ryujinx.Configuration;
-using System;
-using System.Numerics;
-
-namespace Ryujinx.Modules.Motion
-{
- public class MotionDevice
- {
- public Vector3 Gyroscope { get; private set; }
- public Vector3 Accelerometer { get; private set; }
- public Vector3 Rotation { get; private set; }
- public float[] Orientation { get; private set; }
-
- private readonly Client _motionSource;
-
- public MotionDevice(Client motionSource)
- {
- _motionSource = motionSource;
- }
-
- public void RegisterController(PlayerIndex player)
- {
- InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == player);
-
- if (config != null && config.EnableMotion)
- {
- string host = config.DsuServerHost;
- int port = config.DsuServerPort;
-
- _motionSource.RegisterClient((int)player, host, port);
- _motionSource.RequestData((int)player, config.Slot);
-
- if (config.ControllerType == ControllerType.JoyconPair && !config.MirrorInput)
- {
- _motionSource.RequestData((int)player, config.AltSlot);
- }
- }
- }
-
- public void Poll(InputConfig config, int slot)
- {
- Orientation = new float[9];
-
- if (!config.EnableMotion || !_motionSource.TryGetData((int)config.PlayerIndex, slot, out MotionInput input))
- {
- Accelerometer = new Vector3();
- Gyroscope = new Vector3();
-
- return;
- }
-
- Gyroscope = Truncate(input.Gyroscrope * 0.0027f, 3);
- Accelerometer = Truncate(input.Accelerometer, 3);
- Rotation = Truncate(input.Rotation * 0.0027f, 3);
-
- Matrix4x4 orientation = input.GetOrientation();
-
- Orientation[0] = Math.Clamp(orientation.M11, -1f, 1f);
- Orientation[1] = Math.Clamp(orientation.M12, -1f, 1f);
- Orientation[2] = Math.Clamp(orientation.M13, -1f, 1f);
- Orientation[3] = Math.Clamp(orientation.M21, -1f, 1f);
- Orientation[4] = Math.Clamp(orientation.M22, -1f, 1f);
- Orientation[5] = Math.Clamp(orientation.M23, -1f, 1f);
- Orientation[6] = Math.Clamp(orientation.M31, -1f, 1f);
- Orientation[7] = Math.Clamp(orientation.M32, -1f, 1f);
- Orientation[8] = Math.Clamp(orientation.M33, -1f, 1f);
- }
-
- 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;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index 36f379fc..4df82da6 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -1,6 +1,5 @@
using ARMeilleure.Translation.PTC;
using Gtk;
-using OpenTK;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.System;
@@ -12,6 +11,7 @@ using Ryujinx.Ui.Widgets;
using System;
using System.IO;
using System.Reflection;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx
@@ -24,6 +24,9 @@ namespace Ryujinx
public static string ConfigurationPath { get; set; }
+ [DllImport("libX11")]
+ private extern static int XInitThreads();
+
static void Main(string[] args)
{
// Parse Arguments.
@@ -63,15 +66,17 @@ namespace Ryujinx
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
- Toolkit.Init(new ToolkitOptions
- {
- Backend = PlatformBackend.PreferNative
- });
-
Version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
Console.Title = $"Ryujinx Console {Version}";
+ // NOTE: GTK3 doesn't init X11 in a multi threaded way.
+ // This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads).
+ if (OperatingSystem.IsLinux())
+ {
+ XInitThreads();
+ }
+
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 4a5d7508..59edc919 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -12,18 +12,19 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Crc32.NET" Version="1.2.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
- <PackageReference Include="GLWidget" Version="1.0.2" />
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.3.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
- <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.32" />
+ <PackageReference Include="OpenTK.Graphics" Version="4.5.0" />
+ <PackageReference Include="SPB" Version="0.0.2" />
<PackageReference Include="SharpZipLib" Version="1.3.0" />
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
+ <ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs
index 76bf5fa5..99c4698a 100644
--- a/Ryujinx/Ui/GLRenderer.cs
+++ b/Ryujinx/Ui/GLRenderer.cs
@@ -1,39 +1,37 @@
using ARMeilleure.Translation;
using ARMeilleure.Translation.PTC;
using Gdk;
-using OpenTK;
-using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
-using OpenTK.Input;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
-using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Logging;
using Ryujinx.Configuration;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.HOS.Services.Hid;
-using Ryujinx.Modules.Motion;
+using Ryujinx.Input;
+using Ryujinx.Input.HLE;
using Ryujinx.Ui.Widgets;
+using SPB.Graphics;
+using SPB.Graphics.OpenGL;
using System;
-using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Threading;
+using Key = Ryujinx.Input.Key;
+
namespace Ryujinx.Ui
{
using Switch = HLE.Switch;
public class GlRenderer : GLWidget
{
- static GlRenderer()
- {
- OpenTK.Graphics.GraphicsContext.ShareContexts = true;
- }
-
private const int SwitchPanelWidth = 1280;
private const int SwitchPanelHeight = 720;
private const int TargetFps = 60;
public ManualResetEvent WaitEvent { get; set; }
+ public NpadManager NpadManager { get; }
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
@@ -58,9 +56,7 @@ namespace Ryujinx.Ui
private Renderer _renderer;
- private HotkeyButtons _prevHotkeyButtons;
-
- private Client _dsuClient;
+ private KeyboardHotkeyState _prevHotkeyState;
private GraphicsDebugLevel _glLogLevel;
@@ -71,14 +67,22 @@ namespace Ryujinx.Ui
private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor);
private long _lastCursorMoveTime;
private bool _hideCursorOnIdle;
+ private InputManager _inputManager;
+ private IKeyboard _keyboardInterface;
- public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel)
+ public GlRenderer(Switch device, InputManager inputManager, GraphicsDebugLevel glLogLevel)
: base (GetGraphicsMode(),
3, 3,
glLogLevel == GraphicsDebugLevel.None
- ? GraphicsContextFlags.ForwardCompatible
- : GraphicsContextFlags.ForwardCompatible | GraphicsContextFlags.Debug)
+ ? OpenGLContextFlags.Compat
+ : OpenGLContextFlags.Compat | OpenGLContextFlags.Debug)
{
+ _inputManager = inputManager;
+ NpadManager = _inputManager.CreateNpadManager();
+ _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
+
+ NpadManager.ReloadConfiguration(ConfigurationState.Instance.Hid.InputConfig.Value.ToList());
+
WaitEvent = new ManualResetEvent(false);
_device = device;
@@ -101,8 +105,6 @@ namespace Ryujinx.Ui
Shown += Renderer_Shown;
- _dsuClient = new Client();
-
_glLogLevel = glLogLevel;
_exitEvent = new ManualResetEvent(false);
@@ -130,15 +132,15 @@ namespace Ryujinx.Ui
});
}
- private static GraphicsMode GetGraphicsMode()
+ private static FramebufferFormat GetGraphicsMode()
{
- return Environment.OSVersion.Platform == PlatformID.Unix ? new GraphicsMode(new ColorFormat(24)) : new GraphicsMode(new ColorFormat());
+ return Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
}
private void GLRenderer_ShuttingDown(object sender, EventArgs args)
{
_device.DisposeGpu();
- _dsuClient?.Dispose();
+ NpadManager.Dispose();
}
private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
@@ -155,7 +157,7 @@ namespace Ryujinx.Ui
{
ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
- _dsuClient?.Dispose();
+ NpadManager.Dispose();
Dispose();
}
@@ -164,13 +166,13 @@ namespace Ryujinx.Ui
_isFocused = this.ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
}
- public void HandleScreenState(KeyboardState keyboard)
+ public void HandleScreenState(KeyboardStateSnapshot keyboard)
{
- bool toggleFullscreen = keyboard.IsKeyDown(OpenTK.Input.Key.F11)
- || ((keyboard.IsKeyDown(OpenTK.Input.Key.AltLeft)
- || keyboard.IsKeyDown(OpenTK.Input.Key.AltRight))
- && keyboard.IsKeyDown(OpenTK.Input.Key.Enter))
- || keyboard.IsKeyDown(OpenTK.Input.Key.Escape);
+ bool toggleFullscreen = keyboard.IsPressed(Key.F11)
+ || ((keyboard.IsPressed(Key.AltLeft)
+ || keyboard.IsPressed(Key.AltRight))
+ && keyboard.IsPressed(Key.Enter))
+ || keyboard.IsPressed(Key.Escape);
bool fullScreenToggled = ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen);
@@ -185,7 +187,7 @@ namespace Ryujinx.Ui
}
else
{
- if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape))
+ if (keyboard.IsPressed(Key.Escape))
{
if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
{
@@ -203,7 +205,7 @@ namespace Ryujinx.Ui
_toggleFullscreen = toggleFullscreen;
- bool toggleDockedMode = keyboard.IsKeyDown(OpenTK.Input.Key.F9);
+ bool toggleDockedMode = keyboard.IsPressed(Key.F9);
if (toggleDockedMode != _toggleDockedMode)
{
@@ -225,8 +227,8 @@ namespace Ryujinx.Ui
private void GLRenderer_Initialized(object sender, EventArgs e)
{
- // Release the GL exclusivity that OpenTK gave us as we aren't going to use it in GTK Thread.
- GraphicsContext.MakeCurrent(null);
+ // Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread.
+ OpenGLContext.MakeCurrent(null);
WaitEvent.Set();
}
@@ -244,8 +246,6 @@ namespace Ryujinx.Ui
public void Start()
{
- IsRenderHandler = true;
-
_chrono.Restart();
_isActive = true;
@@ -389,7 +389,7 @@ namespace Ryujinx.Ui
public void Exit()
{
- _dsuClient?.Dispose();
+ NpadManager?.Dispose();
if (_isStopped)
{
@@ -416,15 +416,17 @@ namespace Ryujinx.Ui
public void Render()
{
// First take exclusivity on the OpenGL context.
- _renderer.InitializeBackgroundContext(GraphicsContext);
+ _renderer.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(OpenGLContext));
+
Gtk.Window parent = Toplevel as Gtk.Window;
parent.Present();
- GraphicsContext.MakeCurrent(WindowInfo);
+
+ OpenGLContext.MakeCurrent(NativeWindow);
_device.Gpu.Renderer.Initialize(_glLogLevel);
// Make sure the first frame is not transparent.
- GL.ClearColor(OpenTK.Color.Black);
+ GL.ClearColor(0, 0, 0, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit);
SwapBuffers();
@@ -478,7 +480,7 @@ namespace Ryujinx.Ui
public void SwapBuffers()
{
- OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers();
+ NativeWindow.SwapBuffers();
}
public void MainLoop()
@@ -510,13 +512,13 @@ namespace Ryujinx.Ui
{
Gtk.Application.Invoke(delegate
{
- KeyboardState keyboard = OpenTK.Input.Keyboard.GetState();
+ KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot();
HandleScreenState(keyboard);
- if (keyboard.IsKeyDown(OpenTK.Input.Key.Delete))
+ if (keyboard.IsPressed(Key.Delete))
{
- if (!ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen))
+ if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
{
Ptc.Continue();
}
@@ -524,154 +526,19 @@ namespace Ryujinx.Ui
});
}
- List<GamepadInput> gamepadInputs = new List<GamepadInput>(NpadDevices.MaxControllers);
- List<SixAxisInput> motionInputs = new List<SixAxisInput>(NpadDevices.MaxControllers);
-
- MotionDevice motionDevice = new MotionDevice(_dsuClient);
-
- foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
- {
- ControllerKeys currentButton = 0;
- JoystickPosition leftJoystick = new JoystickPosition();
- JoystickPosition rightJoystick = new JoystickPosition();
- KeyboardInput? hidKeyboard = null;
-
- int leftJoystickDx = 0;
- int leftJoystickDy = 0;
- int rightJoystickDx = 0;
- int rightJoystickDy = 0;
-
- if (inputConfig.EnableMotion)
- {
- motionDevice.RegisterController(inputConfig.PlayerIndex);
- }
-
- if (inputConfig is KeyboardConfig keyboardConfig)
- {
- if (_isFocused)
- {
- // Keyboard Input
- KeyboardController keyboardController = new KeyboardController(keyboardConfig);
-
- currentButton = keyboardController.GetButtons();
-
- (leftJoystickDx, leftJoystickDy) = keyboardController.GetLeftStick();
- (rightJoystickDx, rightJoystickDy) = keyboardController.GetRightStick();
-
- leftJoystick = new JoystickPosition
- {
- Dx = leftJoystickDx,
- Dy = leftJoystickDy
- };
-
- rightJoystick = new JoystickPosition
- {
- Dx = rightJoystickDx,
- Dy = rightJoystickDy
- };
-
- if (ConfigurationState.Instance.Hid.EnableKeyboard)
- {
- hidKeyboard = keyboardController.GetKeysDown();
- }
-
- if (!hidKeyboard.HasValue)
- {
- hidKeyboard = new KeyboardInput
- {
- Modifier = 0,
- Keys = new int[0x8]
- };
- }
-
- if (ConfigurationState.Instance.Hid.EnableKeyboard)
- {
- _device.Hid.Keyboard.Update(hidKeyboard.Value);
- }
- }
- }
- else if (inputConfig is Common.Configuration.Hid.ControllerConfig controllerConfig)
- {
- // Controller Input
- JoystickController joystickController = new JoystickController(controllerConfig);
-
- currentButton |= joystickController.GetButtons();
-
- (leftJoystickDx, leftJoystickDy) = joystickController.GetLeftStick();
- (rightJoystickDx, rightJoystickDy) = joystickController.GetRightStick();
-
- leftJoystick = new JoystickPosition
- {
- Dx = controllerConfig.LeftJoycon.InvertStickX ? -leftJoystickDx : leftJoystickDx,
- Dy = controllerConfig.LeftJoycon.InvertStickY ? -leftJoystickDy : leftJoystickDy
- };
-
- rightJoystick = new JoystickPosition
- {
- Dx = controllerConfig.RightJoycon.InvertStickX ? -rightJoystickDx : rightJoystickDx,
- Dy = controllerConfig.RightJoycon.InvertStickY ? -rightJoystickDy : rightJoystickDy
- };
- }
-
- currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
-
- motionDevice.Poll(inputConfig, inputConfig.Slot);
-
- SixAxisInput sixAxisInput = new SixAxisInput()
- {
- PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
- Accelerometer = motionDevice.Accelerometer,
- Gyroscope = motionDevice.Gyroscope,
- Rotation = motionDevice.Rotation,
- Orientation = motionDevice.Orientation
- };
-
- motionInputs.Add(sixAxisInput);
-
- gamepadInputs.Add(new GamepadInput
- {
- PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
- Buttons = currentButton,
- LStick = leftJoystick,
- RStick = rightJoystick
- });
-
- if (inputConfig.ControllerType == Common.Configuration.Hid.ControllerType.JoyconPair)
- {
- if (!inputConfig.MirrorInput)
- {
- motionDevice.Poll(inputConfig, inputConfig.AltSlot);
-
- sixAxisInput = new SixAxisInput()
- {
- PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
- Accelerometer = motionDevice.Accelerometer,
- Gyroscope = motionDevice.Gyroscope,
- Rotation = motionDevice.Rotation,
- Orientation = motionDevice.Orientation
- };
- }
-
- motionInputs.Add(sixAxisInput);
- }
- }
-
- _device.Hid.Npads.Update(gamepadInputs);
- _device.Hid.Npads.UpdateSixAxis(motionInputs);
- _device.TamperMachine.UpdateInput(gamepadInputs);
+ NpadManager.Update(_device.Hid, _device.TamperMachine);
if(_isFocused)
{
- // Hotkeys
- HotkeyButtons currentHotkeyButtons = KeyboardController.GetHotkeyButtons(OpenTK.Input.Keyboard.GetState());
+ KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
- if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
- !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
+ if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) &&
+ !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync))
{
_device.EnableDeviceVsync = !_device.EnableDeviceVsync;
}
- _prevHotkeyButtons = currentHotkeyButtons;
+ _prevHotkeyState = currentHotkeyState;
}
//Touchscreen
@@ -739,5 +606,24 @@ namespace Ryujinx.Ui
return true;
}
+
+ [Flags]
+ private enum KeyboardHotkeyState
+ {
+ None,
+ ToggleVSync
+ }
+
+ private KeyboardHotkeyState GetHotkeyState()
+ {
+ KeyboardHotkeyState state = KeyboardHotkeyState.None;
+
+ if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync))
+ {
+ state |= KeyboardHotkeyState.ToggleVSync;
+ }
+
+ return state;
+ }
}
}
diff --git a/Ryujinx/Ui/GLWidget.cs b/Ryujinx/Ui/GLWidget.cs
new file mode 100644
index 00000000..a465aeef
--- /dev/null
+++ b/Ryujinx/Ui/GLWidget.cs
@@ -0,0 +1,118 @@
+using Gtk;
+using SPB.Graphics;
+using SPB.Graphics.OpenGL;
+using SPB.Platform;
+using SPB.Platform.GLX;
+using SPB.Platform.WGL;
+using SPB.Windowing;
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Ui
+{
+ [ToolboxItem(true)]
+ public class GLWidget : DrawingArea
+ {
+ private bool _initialized;
+
+ public event EventHandler Initialized;
+ public event EventHandler ShuttingDown;
+
+ public OpenGLContextBase OpenGLContext { get; private set; }
+ public NativeWindowBase NativeWindow { get; private set; }
+
+ public FramebufferFormat FramebufferFormat { get; }
+ public int GLVersionMajor { get; }
+ public int GLVersionMinor { get; }
+ public OpenGLContextFlags ContextFlags { get; }
+
+ public bool DirectRendering { get; }
+ public OpenGLContextBase SharedContext { get; }
+
+ public GLWidget(FramebufferFormat framebufferFormat, int major, int minor, OpenGLContextFlags flags = OpenGLContextFlags.Default, bool directRendering = true, OpenGLContextBase sharedContext = null)
+ {
+ FramebufferFormat = framebufferFormat;
+ GLVersionMajor = major;
+ GLVersionMinor = minor;
+ ContextFlags = flags;
+ DirectRendering = directRendering;
+ SharedContext = sharedContext;
+ }
+
+ protected override bool OnDrawn(Cairo.Context cr)
+ {
+ if (!_initialized)
+ {
+ Intialize();
+ }
+
+ return true;
+ }
+
+ private NativeWindowBase RetrieveNativeWindow()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle);
+
+ return new WGLWindow(new NativeHandle(windowHandle));
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle);
+ IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle);
+
+ return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ [DllImport("libgdk-3-0.dll")]
+ private static extern IntPtr gdk_win32_window_get_handle(IntPtr d);
+
+ [DllImport("libgdk-3.so.0")]
+ private static extern IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay);
+
+ [DllImport("libgdk-3.so.0")]
+ private static extern IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow);
+
+ private void Intialize()
+ {
+ NativeWindow = RetrieveNativeWindow();
+
+ Window.EnsureNative();
+
+ OpenGLContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat, GLVersionMajor, GLVersionMinor, ContextFlags, DirectRendering, SharedContext);
+
+ OpenGLContext.Initialize(NativeWindow);
+ OpenGLContext.MakeCurrent(NativeWindow);
+
+ _initialized = true;
+
+ Initialized?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ // Try to bind the OpenGL context before calling the shutdown event
+ try
+ {
+ OpenGLContext?.MakeCurrent(NativeWindow);
+ }
+ catch (Exception) { }
+
+ ShuttingDown?.Invoke(this, EventArgs.Empty);
+
+ // Unbind context and destroy everything
+ try
+ {
+ OpenGLContext?.MakeCurrent(null);
+ }
+ catch (Exception) { }
+
+ OpenGLContext.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx/Ui/Input/ButtonAssigner.cs b/Ryujinx/Ui/Input/ButtonAssigner.cs
deleted file mode 100644
index ff32b106..00000000
--- a/Ryujinx/Ui/Input/ButtonAssigner.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Ryujinx.Common.Configuration.Hid;
-
-namespace Ryujinx.Ui.Input
-{
- interface ButtonAssigner
- {
- void Init();
-
- void ReadInput();
-
- bool HasAnyButtonPressed();
-
- bool ShouldCancel();
-
- string GetPressedButton();
- }
-} \ No newline at end of file
diff --git a/Ryujinx/Ui/Input/JoystickButtonAssigner.cs b/Ryujinx/Ui/Input/JoystickButtonAssigner.cs
deleted file mode 100644
index 481221ac..00000000
--- a/Ryujinx/Ui/Input/JoystickButtonAssigner.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-using OpenTK.Input;
-using Ryujinx.Common.Configuration.Hid;
-using System.Collections.Generic;
-using System;
-using System.IO;
-
-namespace Ryujinx.Ui.Input
-{
- class JoystickButtonAssigner : ButtonAssigner
- {
- private int _index;
-
- private double _triggerThreshold;
-
- private JoystickState _currState;
-
- private JoystickState _prevState;
-
- private JoystickButtonDetector _detector;
-
- public JoystickButtonAssigner(int index, double triggerThreshold)
- {
- _index = index;
- _triggerThreshold = triggerThreshold;
- _detector = new JoystickButtonDetector();
- }
-
- public void Init()
- {
- _currState = Joystick.GetState(_index);
- _prevState = _currState;
- }
-
- public void ReadInput()
- {
- _prevState = _currState;
- _currState = Joystick.GetState(_index);
-
- CollectButtonStats();
- }
-
- public bool HasAnyButtonPressed()
- {
- return _detector.HasAnyButtonPressed();
- }
-
- public bool ShouldCancel()
- {
- return Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsAnyKeyDown;
- }
-
- public string GetPressedButton()
- {
- List<ControllerInputId> pressedButtons = _detector.GetPressedButtons();
-
- // Reverse list so axis button take precedence when more than one button is recognized.
- pressedButtons.Reverse();
-
- return pressedButtons.Count > 0 ? pressedButtons[0].ToString() : "";
- }
-
- private void CollectButtonStats()
- {
- JoystickCapabilities capabilities = Joystick.GetCapabilities(_index);
-
- ControllerInputId pressedButton;
-
- // Buttons
- for (int i = 0; i != capabilities.ButtonCount; i++)
- {
- if (_currState.IsButtonDown(i) && _prevState.IsButtonUp(i))
- {
- Enum.TryParse($"Button{i}", out pressedButton);
- _detector.AddInput(pressedButton, 1);
- }
-
- if (_currState.IsButtonUp(i) && _prevState.IsButtonDown(i))
- {
- Enum.TryParse($"Button{i}", out pressedButton);
- _detector.AddInput(pressedButton, -1);
- }
- }
-
- // Axis
- for (int i = 0; i != capabilities.AxisCount; i++)
- {
- float axisValue = _currState.GetAxis(i);
-
- Enum.TryParse($"Axis{i}", out pressedButton);
- _detector.AddInput(pressedButton, axisValue);
- }
-
- // Hats
- for (int i = 0; i != capabilities.HatCount; i++)
- {
- string currPos = GetHatPosition(_currState.GetHat((JoystickHat)i));
- string prevPos = GetHatPosition(_prevState.GetHat((JoystickHat)i));
-
- if (currPos == prevPos)
- {
- continue;
- }
-
- if (currPos != "")
- {
- Enum.TryParse($"Hat{i}{currPos}", out pressedButton);
- _detector.AddInput(pressedButton, 1);
- }
-
- if (prevPos != "")
- {
- Enum.TryParse($"Hat{i}{prevPos}", out pressedButton);
- _detector.AddInput(pressedButton, -1);
- }
- }
- }
-
- private string GetHatPosition(JoystickHatState hatState)
- {
- if (hatState.IsUp) return "Up";
- if (hatState.IsDown) return "Down";
- if (hatState.IsLeft) return "Left";
- if (hatState.IsRight) return "Right";
- return "";
- }
-
- private class JoystickButtonDetector
- {
- private Dictionary<ControllerInputId, InputSummary> _stats;
-
- public JoystickButtonDetector()
- {
- _stats = new Dictionary<ControllerInputId, InputSummary>();
- }
-
- public bool HasAnyButtonPressed()
- {
- foreach (var inputSummary in _stats.Values)
- {
- if (checkButtonPressed(inputSummary))
- {
- return true;
- }
- }
-
- return false;
- }
-
- public List<ControllerInputId> GetPressedButtons()
- {
- List<ControllerInputId> pressedButtons = new List<ControllerInputId>();
-
- foreach (var kvp in _stats)
- {
- if (!checkButtonPressed(kvp.Value))
- {
- continue;
- }
- pressedButtons.Add(kvp.Key);
- }
-
- return pressedButtons;
- }
-
- public void AddInput(ControllerInputId button, float value)
- {
- InputSummary inputSummary;
-
- if (!_stats.TryGetValue(button, out inputSummary))
- {
- inputSummary = new InputSummary();
- _stats.Add(button, inputSummary);
- }
-
- inputSummary.AddInput(value);
- }
-
- public override string ToString()
- {
- TextWriter writer = new StringWriter();
-
- foreach (var kvp in _stats)
- {
- writer.WriteLine($"Button {kvp.Key} -> {kvp.Value}");
- }
-
- return writer.ToString();
- }
-
- private bool checkButtonPressed(InputSummary sequence)
- {
- float distance = Math.Abs(sequence.Min - sequence.Avg) + Math.Abs(sequence.Max - sequence.Avg);
- return distance > 1.5; // distance range [0, 2]
- }
- }
-
- private class InputSummary
- {
- public float Min, Max, Sum, Avg;
-
- public int NumSamples;
-
- public InputSummary()
- {
- Min = float.MaxValue;
- Max = float.MinValue;
- Sum = 0;
- NumSamples = 0;
- Avg = 0;
- }
-
- public void AddInput(float value)
- {
- Min = Math.Min(Min, value);
- Max = Math.Max(Max, value);
- Sum += value;
- NumSamples += 1;
- Avg = Sum / NumSamples;
- }
-
- public override string ToString()
- {
- return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}";
- }
- }
- }
-}
diff --git a/Ryujinx/Ui/Input/KeyboardKeyAssigner.cs b/Ryujinx/Ui/Input/KeyboardKeyAssigner.cs
deleted file mode 100644
index 2a29c366..00000000
--- a/Ryujinx/Ui/Input/KeyboardKeyAssigner.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using OpenTK.Input;
-using System;
-using Key = Ryujinx.Configuration.Hid.Key;
-
-namespace Ryujinx.Ui.Input
-{
- class KeyboardKeyAssigner : ButtonAssigner
- {
- private int _index;
-
- private KeyboardState _keyboardState;
-
- public KeyboardKeyAssigner(int index)
- {
- _index = index;
- }
-
- public void Init() { }
-
- public void ReadInput()
- {
- _keyboardState = KeyboardController.GetKeyboardState(_index);
- }
-
- public bool HasAnyButtonPressed()
- {
- return _keyboardState.IsAnyKeyDown;
- }
-
- public bool ShouldCancel()
- {
- return Mouse.GetState().IsAnyButtonDown || Keyboard.GetState().IsKeyDown(OpenTK.Input.Key.Escape);
- }
-
- public string GetPressedButton()
- {
- string keyPressed = "";
-
- foreach (Key key in Enum.GetValues(typeof(Key)))
- {
- if (_keyboardState.IsKeyDown((OpenTK.Input.Key)key))
- {
- keyPressed = key.ToString();
- break;
- }
- }
-
- return !ShouldCancel() ? keyPressed : "";
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx/Ui/JoystickController.cs b/Ryujinx/Ui/JoystickController.cs
deleted file mode 100644
index 3ced101a..00000000
--- a/Ryujinx/Ui/JoystickController.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-using OpenTK;
-using OpenTK.Input;
-using Ryujinx.Common.Configuration.Hid;
-using Ryujinx.HLE.HOS.Services.Hid;
-using System;
-
-using ControllerConfig = Ryujinx.Common.Configuration.Hid.ControllerConfig;
-
-namespace Ryujinx.Ui
-{
- public class JoystickController
- {
- private readonly ControllerConfig _config;
-
- public JoystickController(ControllerConfig config)
- {
- _config = config;
- }
-
- private bool IsEnabled()
- {
- return Joystick.GetState(_config.Index).IsConnected;
- }
-
- public ControllerKeys GetButtons()
- {
- // NOTE: This should be initialized AFTER GTK for compat reasons with OpenTK SDL2 backend and GTK on Linux.
- // BODY: Usage of Joystick.GetState must be defer to after GTK full initialization. Otherwise, GTK will segfault because SDL2 was already init *sighs*
- if (!IsEnabled())
- {
- return 0;
- }
-
- JoystickState joystickState = Joystick.GetState(_config.Index);
-
- ControllerKeys buttons = 0;
-
- if (IsActivated(joystickState, _config.LeftJoycon.DPadUp)) buttons |= ControllerKeys.DpadUp;
- if (IsActivated(joystickState, _config.LeftJoycon.DPadDown)) buttons |= ControllerKeys.DpadDown;
- if (IsActivated(joystickState, _config.LeftJoycon.DPadLeft)) buttons |= ControllerKeys.DpadLeft;
- if (IsActivated(joystickState, _config.LeftJoycon.DPadRight)) buttons |= ControllerKeys.DpadRight;
- if (IsActivated(joystickState, _config.LeftJoycon.StickButton)) buttons |= ControllerKeys.LStick;
- if (IsActivated(joystickState, _config.LeftJoycon.ButtonMinus)) buttons |= ControllerKeys.Minus;
- if (IsActivated(joystickState, _config.LeftJoycon.ButtonL)) buttons |= ControllerKeys.L;
- if (IsActivated(joystickState, _config.LeftJoycon.ButtonZl)) buttons |= ControllerKeys.Zl;
- if (IsActivated(joystickState, _config.LeftJoycon.ButtonSl)) buttons |= ControllerKeys.SlLeft;
- if (IsActivated(joystickState, _config.LeftJoycon.ButtonSr)) buttons |= ControllerKeys.SrLeft;
-
- if (IsActivated(joystickState, _config.RightJoycon.ButtonA)) buttons |= ControllerKeys.A;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonB)) buttons |= ControllerKeys.B;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonX)) buttons |= ControllerKeys.X;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonY)) buttons |= ControllerKeys.Y;
- if (IsActivated(joystickState, _config.RightJoycon.StickButton)) buttons |= ControllerKeys.RStick;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonPlus)) buttons |= ControllerKeys.Plus;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonR)) buttons |= ControllerKeys.R;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonZr)) buttons |= ControllerKeys.Zr;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonSl)) buttons |= ControllerKeys.SlRight;
- if (IsActivated(joystickState, _config.RightJoycon.ButtonSr)) buttons |= ControllerKeys.SrRight;
-
- return buttons;
- }
-
- private bool IsActivated(JoystickState joystickState, ControllerInputId controllerInputId)
- {
- if (controllerInputId <= ControllerInputId.Button20)
- {
- return joystickState.IsButtonDown((int)controllerInputId);
- }
- else if (controllerInputId <= ControllerInputId.Axis5)
- {
- int axis = controllerInputId - ControllerInputId.Axis0;
-
- return joystickState.GetAxis(axis) > _config.TriggerThreshold;
- }
- else if (controllerInputId <= ControllerInputId.Hat2Right)
- {
- int hat = (controllerInputId - ControllerInputId.Hat0Up) / 4;
-
- int baseHatId = (int)ControllerInputId.Hat0Up + (hat * 4);
-
- JoystickHatState hatState = joystickState.GetHat((JoystickHat)hat);
-
- if (hatState.IsUp && ((int)controllerInputId % baseHatId == 0)) return true;
- if (hatState.IsDown && ((int)controllerInputId % baseHatId == 1)) return true;
- if (hatState.IsLeft && ((int)controllerInputId % baseHatId == 2)) return true;
- if (hatState.IsRight && ((int)controllerInputId % baseHatId == 3)) return true;
- }
-
- return false;
- }
-
- public (short, short) GetLeftStick()
- {
- if (!IsEnabled())
- {
- return (0, 0);
- }
-
- return GetStick(_config.LeftJoycon.StickX, _config.LeftJoycon.StickY, _config.DeadzoneLeft);
- }
-
- public (short, short) GetRightStick()
- {
- if (!IsEnabled())
- {
- return (0, 0);
- }
-
- return GetStick(_config.RightJoycon.StickX, _config.RightJoycon.StickY, _config.DeadzoneRight);
- }
-
- private (short, short) GetStick(ControllerInputId stickXInputId, ControllerInputId stickYInputId, float deadzone)
- {
- if (stickXInputId < ControllerInputId.Axis0 || stickXInputId > ControllerInputId.Axis5 ||
- stickYInputId < ControllerInputId.Axis0 || stickYInputId > ControllerInputId.Axis5)
- {
- return (0, 0);
- }
-
- JoystickState jsState = Joystick.GetState(_config.Index);
-
- int xAxis = stickXInputId - ControllerInputId.Axis0;
- int yAxis = stickYInputId - ControllerInputId.Axis0;
-
- float xValue = jsState.GetAxis(xAxis);
- float yValue = -jsState.GetAxis(yAxis); // Invert Y-axis
-
- return ApplyDeadzone(new Vector2(xValue, yValue), deadzone);
- }
-
- private (short, short) ApplyDeadzone(Vector2 axis, float deadzone)
- {
- return (ClampAxis(MathF.Abs(axis.X) > deadzone ? axis.X : 0f),
- ClampAxis(MathF.Abs(axis.Y) > deadzone ? axis.Y : 0f));
- }
-
- private static short ClampAxis(float value)
- {
- if (value <= -short.MaxValue)
- {
- return -short.MaxValue;
- }
- else
- {
- return (short)(value * short.MaxValue);
- }
- }
- }
-}
diff --git a/Ryujinx/Ui/KeyboardController.cs b/Ryujinx/Ui/KeyboardController.cs
deleted file mode 100644
index 3fb249db..00000000
--- a/Ryujinx/Ui/KeyboardController.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-using System;
-using OpenTK;
-using OpenTK.Input;
-using Ryujinx.Common.Configuration.Hid;
-using Ryujinx.Configuration;
-using Ryujinx.HLE.HOS.Services.Hid;
-
-namespace Ryujinx.Ui
-{
- [Flags]
- public enum HotkeyButtons
- {
- ToggleVSync = 1 << 0,
- }
-
- public class KeyboardController
- {
- private readonly KeyboardConfig _config;
-
- public KeyboardController(KeyboardConfig config)
- {
- _config = config;
- }
-
- public static KeyboardState GetKeyboardState(int index)
- {
- if (index == KeyboardConfig.AllKeyboardsIndex || index < 0)
- {
- return Keyboard.GetState();
- }
-
- return Keyboard.GetState(index - 1);
- }
-
- public ControllerKeys GetButtons()
- {
- KeyboardState keyboard = GetKeyboardState(_config.Index);
-
- ControllerKeys buttons = 0;
-
- if (keyboard[(Key)_config.LeftJoycon.StickButton]) buttons |= ControllerKeys.LStick;
- if (keyboard[(Key)_config.LeftJoycon.DPadUp]) buttons |= ControllerKeys.DpadUp;
- if (keyboard[(Key)_config.LeftJoycon.DPadDown]) buttons |= ControllerKeys.DpadDown;
- if (keyboard[(Key)_config.LeftJoycon.DPadLeft]) buttons |= ControllerKeys.DpadLeft;
- if (keyboard[(Key)_config.LeftJoycon.DPadRight]) buttons |= ControllerKeys.DpadRight;
- if (keyboard[(Key)_config.LeftJoycon.ButtonMinus]) buttons |= ControllerKeys.Minus;
- if (keyboard[(Key)_config.LeftJoycon.ButtonL]) buttons |= ControllerKeys.L;
- if (keyboard[(Key)_config.LeftJoycon.ButtonZl]) buttons |= ControllerKeys.Zl;
- if (keyboard[(Key)_config.LeftJoycon.ButtonSl]) buttons |= ControllerKeys.SlLeft;
- if (keyboard[(Key)_config.LeftJoycon.ButtonSr]) buttons |= ControllerKeys.SrLeft;
-
- if (keyboard[(Key)_config.RightJoycon.StickButton]) buttons |= ControllerKeys.RStick;
- if (keyboard[(Key)_config.RightJoycon.ButtonA]) buttons |= ControllerKeys.A;
- if (keyboard[(Key)_config.RightJoycon.ButtonB]) buttons |= ControllerKeys.B;
- if (keyboard[(Key)_config.RightJoycon.ButtonX]) buttons |= ControllerKeys.X;
- if (keyboard[(Key)_config.RightJoycon.ButtonY]) buttons |= ControllerKeys.Y;
- if (keyboard[(Key)_config.RightJoycon.ButtonPlus]) buttons |= ControllerKeys.Plus;
- if (keyboard[(Key)_config.RightJoycon.ButtonR]) buttons |= ControllerKeys.R;
- if (keyboard[(Key)_config.RightJoycon.ButtonZr]) buttons |= ControllerKeys.Zr;
- if (keyboard[(Key)_config.RightJoycon.ButtonSl]) buttons |= ControllerKeys.SlRight;
- if (keyboard[(Key)_config.RightJoycon.ButtonSr]) buttons |= ControllerKeys.SrRight;
-
- return buttons;
- }
-
- public (short, short) GetLeftStick()
- {
- KeyboardState keyboard = GetKeyboardState(_config.Index);
-
- short dx = 0;
- short dy = 0;
-
- if (keyboard[(Key)_config.LeftJoycon.StickUp]) dy += 1;
- if (keyboard[(Key)_config.LeftJoycon.StickDown]) dy += -1;
- if (keyboard[(Key)_config.LeftJoycon.StickLeft]) dx += -1;
- if (keyboard[(Key)_config.LeftJoycon.StickRight]) dx += 1;
-
- Vector2 stick = new Vector2(dx, dy);
- stick.NormalizeFast();
-
- return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
- }
-
- public (short, short) GetRightStick()
- {
- KeyboardState keyboard = GetKeyboardState(_config.Index);
-
- short dx = 0;
- short dy = 0;
-
- if (keyboard[(Key)_config.RightJoycon.StickUp]) dy += 1;
- if (keyboard[(Key)_config.RightJoycon.StickDown]) dy += -1;
- if (keyboard[(Key)_config.RightJoycon.StickLeft]) dx += -1;
- if (keyboard[(Key)_config.RightJoycon.StickRight]) dx += 1;
-
- Vector2 stick = new Vector2(dx, dy);
- stick.NormalizeFast();
-
- return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
- }
-
- public static HotkeyButtons GetHotkeyButtons(KeyboardState keyboard)
- {
- HotkeyButtons buttons = 0;
-
- if (keyboard[(Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync])
- {
- buttons |= HotkeyButtons.ToggleVSync;
- }
-
- 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 KeyboardInput GetKeysDown()
- {
- KeyboardState keyboard = GetKeyboardState(_config.Index);
-
- KeyboardInput hidKeyboard = new KeyboardInput
- {
- 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/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 60a43e84..433d23dc 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -17,6 +17,9 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.Input.GTK3;
+using Ryujinx.Input.HLE;
+using Ryujinx.Input.SDL2;
using Ryujinx.Modules;
using Ryujinx.Ui.App;
using Ryujinx.Ui.Applet;
@@ -65,6 +68,7 @@ namespace Ryujinx.Ui
private bool _lastScannedAmiiboShowAll = false;
public GlRenderer GlRendererWidget;
+ public InputManager InputManager;
#pragma warning disable CS0169, CS0649, IDE0044
@@ -223,6 +227,8 @@ namespace Ryujinx.Ui
};
Task.Run(RefreshFirmwareLabel);
+
+ InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver());
}
private void WindowStateEvent_Changed(object o, WindowStateEventArgs args)
@@ -295,6 +301,11 @@ namespace Ryujinx.Ui
}
}
+ protected override void OnDestroyed()
+ {
+ InputManager.Dispose();
+ }
+
private void InitializeSwitchInstance()
{
_virtualFileSystem.Reload();
@@ -636,7 +647,7 @@ namespace Ryujinx.Ui
DisplaySleep.Prevent();
- GlRendererWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
+ GlRendererWidget = new GlRenderer(_emulationContext, InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
Application.Invoke(delegate
{
diff --git a/Ryujinx/Ui/OpenToolkitBindingsContext.cs b/Ryujinx/Ui/OpenToolkitBindingsContext.cs
new file mode 100644
index 00000000..ec4111fa
--- /dev/null
+++ b/Ryujinx/Ui/OpenToolkitBindingsContext.cs
@@ -0,0 +1,20 @@
+using SPB.Graphics;
+using System;
+
+namespace Ryujinx.Ui
+{
+ public class OpenToolkitBindingsContext : OpenTK.IBindingsContext
+ {
+ private IBindingsContext _bindingContext;
+
+ public OpenToolkitBindingsContext(IBindingsContext bindingsContext)
+ {
+ _bindingContext = bindingsContext;
+ }
+
+ public IntPtr GetProcAddress(string procName)
+ {
+ return _bindingContext.GetProcAddress(procName);
+ }
+ }
+}
diff --git a/Ryujinx/Ui/SPBOpenGLContext.cs b/Ryujinx/Ui/SPBOpenGLContext.cs
new file mode 100644
index 00000000..c2b5d638
--- /dev/null
+++ b/Ryujinx/Ui/SPBOpenGLContext.cs
@@ -0,0 +1,49 @@
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.OpenGL;
+using SPB.Graphics;
+using SPB.Graphics.OpenGL;
+using SPB.Platform;
+using SPB.Windowing;
+
+namespace Ryujinx.Ui
+{
+ class SPBOpenGLContext : IOpenGLContext
+ {
+ private OpenGLContextBase _context;
+ private NativeWindowBase _window;
+
+ private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
+ {
+ _context = context;
+ _window = window;
+ }
+
+ public void Dispose()
+ {
+ _context.Dispose();
+ _window.Dispose();
+ }
+
+ public void MakeCurrent()
+ {
+ _context.MakeCurrent(_window);
+ }
+
+ public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
+ {
+ OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
+ NativeWindowBase window = PlatformHelper.CreateWindow(FramebufferFormat.Default, 0, 0, 100, 100);
+
+ context.Initialize(window);
+ context.MakeCurrent(window);
+
+ GL.LoadBindings(new OpenToolkitBindingsContext(context));
+
+ context.MakeCurrent(null);
+
+ return new SPBOpenGLContext(context, window);
+ }
+ }
+}
diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs
index 8654fc6f..6e876ad9 100644
--- a/Ryujinx/Ui/Windows/ControllerWindow.cs
+++ b/Ryujinx/Ui/Windows/ControllerWindow.cs
@@ -1,10 +1,12 @@
using Gtk;
-using OpenTK.Input;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Utilities;
using Ryujinx.Configuration;
-using Ryujinx.Ui.Input;
+using Ryujinx.Input;
+using Ryujinx.Input.GTK3;
using Ryujinx.Ui.Widgets;
using System;
using System.Collections.Generic;
@@ -14,7 +16,13 @@ using System.Text.Json;
using System.Threading;
using GUI = Gtk.Builder.ObjectAttribute;
-using Key = Ryujinx.Configuration.Hid.Key;
+using Key = Ryujinx.Common.Configuration.Hid.Key;
+
+using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
+using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
+using Ryujinx.Common.Logging;
+using Ryujinx.Input.Assigner;
namespace Ryujinx.Ui.Windows
{
@@ -34,14 +42,18 @@ namespace Ryujinx.Ui.Windows
[GUI] Adjustment _sensitivity;
[GUI] Adjustment _gyroDeadzone;
[GUI] CheckButton _enableMotion;
+ [GUI] CheckButton _enableCemuHook;
[GUI] CheckButton _mirrorInput;
[GUI] Entry _dsuServerHost;
[GUI] Entry _dsuServerPort;
[GUI] ComboBoxText _inputDevice;
[GUI] ComboBoxText _profile;
- [GUI] ToggleButton _refreshInputDevicesButton;
[GUI] Box _settingsBox;
- [GUI] Box _altBox;
+ [GUI] Box _motionAltBox;
+ [GUI] Box _motionBox;
+ [GUI] Box _dsuServerHostBox;
+ [GUI] Box _dsuServerPortBox;
+ [GUI] Box _motionControllerSlot;
[GUI] Grid _leftStickKeyboard;
[GUI] Grid _leftStickController;
[GUI] Box _deadZoneLeftBox;
@@ -52,9 +64,8 @@ namespace Ryujinx.Ui.Windows
[GUI] Grid _rightSideTriggerBox;
[GUI] Box _triggerThresholdBox;
[GUI] ComboBoxText _controllerType;
- [GUI] ToggleButton _lStickX;
+ [GUI] ToggleButton _lStick;
[GUI] CheckButton _invertLStickX;
- [GUI] ToggleButton _lStickY;
[GUI] CheckButton _invertLStickY;
[GUI] ToggleButton _lStickUp;
[GUI] ToggleButton _lStickDown;
@@ -68,9 +79,8 @@ namespace Ryujinx.Ui.Windows
[GUI] ToggleButton _minus;
[GUI] ToggleButton _l;
[GUI] ToggleButton _zL;
- [GUI] ToggleButton _rStickX;
+ [GUI] ToggleButton _rStick;
[GUI] CheckButton _invertRStickX;
- [GUI] ToggleButton _rStickY;
[GUI] CheckButton _invertRStickY;
[GUI] ToggleButton _rStickUp;
[GUI] ToggleButton _rStickDown;
@@ -91,10 +101,22 @@ namespace Ryujinx.Ui.Windows
[GUI] Image _controllerImage;
#pragma warning restore CS0649, IDE0044
- public ControllerWindow(PlayerIndex controllerId) : this(new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { }
+ private MainWindow _mainWindow;
+ private IGamepadDriver _gtk3KeyboardDriver;
+ private IGamepad _selectedGamepad;
+ private bool _mousePressed;
+
+ public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { }
- private ControllerWindow(Builder builder, PlayerIndex controllerId) : base(builder.GetObject("_controllerWin").Handle)
+ private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetObject("_controllerWin").Handle)
{
+ _mainWindow = mainWindow;
+ _mousePressed = false;
+ _selectedGamepad = null;
+
+ // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused...
+ _gtk3KeyboardDriver = new GTK3KeyboardDriver(this);
+
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png");
builder.Autoconnect(this);
@@ -120,8 +142,7 @@ namespace Ryujinx.Ui.Windows
_controllerType.Active = 0; // Set initial value to first in list.
// Bind Events.
- _lStickX.Clicked += Button_Pressed;
- _lStickY.Clicked += Button_Pressed;
+ _lStick.Clicked += ButtonForStick_Pressed;
_lStickUp.Clicked += Button_Pressed;
_lStickDown.Clicked += Button_Pressed;
_lStickLeft.Clicked += Button_Pressed;
@@ -136,8 +157,7 @@ namespace Ryujinx.Ui.Windows
_zL.Clicked += Button_Pressed;
_lSl.Clicked += Button_Pressed;
_lSr.Clicked += Button_Pressed;
- _rStickX.Clicked += Button_Pressed;
- _rStickY.Clicked += Button_Pressed;
+ _rStick.Clicked += ButtonForStick_Pressed;
_rStickUp.Clicked += Button_Pressed;
_rStickDown.Clicked += Button_Pressed;
_rStickLeft.Clicked += Button_Pressed;
@@ -152,6 +172,7 @@ namespace Ryujinx.Ui.Windows
_zR.Clicked += Button_Pressed;
_rSl.Clicked += Button_Pressed;
_rSr.Clicked += Button_Pressed;
+ _enableCemuHook.Clicked += CemuHookCheckButtonPressed;
// Setup current values.
UpdateInputDeviceList();
@@ -162,6 +183,63 @@ namespace Ryujinx.Ui.Windows
{
SetCurrentValues();
}
+
+ mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
+ mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
+
+ if (_mainWindow.GlRendererWidget != null)
+ {
+ _mainWindow.GlRendererWidget.NpadManager.BlockInputUpdates();
+ }
+ }
+
+ private void CemuHookCheckButtonPressed(object sender, EventArgs e)
+ {
+ UpdateCemuHookSpecificFieldsVisibility();
+ }
+
+ private void HandleOnGamepadDisconnected(string id)
+ {
+ Application.Invoke(delegate
+ {
+ UpdateInputDeviceList();
+ });
+ }
+
+ private void HandleOnGamepadConnected(string id)
+ {
+ Application.Invoke(delegate
+ {
+ UpdateInputDeviceList();
+ });
+ }
+
+ protected override void OnDestroyed()
+ {
+ _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
+ _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
+
+ if (_mainWindow.GlRendererWidget != null)
+ {
+ _mainWindow.GlRendererWidget.NpadManager.UnblockInputUpdates();
+ }
+
+ _selectedGamepad?.Dispose();
+
+ _gtk3KeyboardDriver.Dispose();
+ }
+
+ private static string GetShrinkedGamepadName(string str)
+ {
+ const string ShrinkChars = "..";
+ const int MaxSize = 52;
+
+ if (str.Length > MaxSize - ShrinkChars.Length)
+ {
+ return str.Substring(0, MaxSize) + ShrinkChars;
+ }
+
+ return str;
}
private void UpdateInputDeviceList()
@@ -170,28 +248,61 @@ namespace Ryujinx.Ui.Windows
_inputDevice.Append("disabled", "Disabled");
_inputDevice.SetActiveId("disabled");
- _inputDevice.Append($"keyboard/{KeyboardConfig.AllKeyboardsIndex}", "All keyboards");
+ foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
+ {
+ IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
+
+ if (gamepad != null)
+ {
+ _inputDevice.Append($"keyboard/{id}", GetShrinkedGamepadName($"{gamepad.Name} ({id})"));
+
+ gamepad.Dispose();
+ }
+ }
- for (int i = 0; i < 20; i++)
+ foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds)
{
- if (KeyboardController.GetKeyboardState(i + 1).IsConnected)
- _inputDevice.Append($"keyboard/{i + 1}", $"Keyboard/{i + 1}");
+ IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
- if (GamePad.GetState(i).IsConnected)
- _inputDevice.Append($"controller/{i}", $"Controller/{i} ({GamePad.GetName(i)})");
+ if (gamepad != null)
+ {
+ _inputDevice.Append($"controller/{id}", GetShrinkedGamepadName($"{gamepad.Name} ({id})"));
+
+ gamepad.Dispose();
+ }
}
switch (_inputConfig)
{
- case KeyboardConfig keyboard:
- _inputDevice.SetActiveId($"keyboard/{keyboard.Index}");
+ case StandardKeyboardInputConfig keyboard:
+ _inputDevice.SetActiveId($"keyboard/{keyboard.Id}");
break;
- case ControllerConfig controller:
- _inputDevice.SetActiveId($"controller/{controller.Index}");
+ case StandardControllerInputConfig controller:
+ _inputDevice.SetActiveId($"controller/{controller.Id}");
break;
}
}
+ private void UpdateCemuHookSpecificFieldsVisibility()
+ {
+ if (_enableCemuHook.Active)
+ {
+ _dsuServerHostBox.Show();
+ _dsuServerPortBox.Show();
+ _motionControllerSlot.Show();
+ _motionAltBox.Show();
+ _mirrorInput.Show();
+ }
+ else
+ {
+ _dsuServerHostBox.Hide();
+ _dsuServerPortBox.Hide();
+ _motionControllerSlot.Hide();
+ _motionAltBox.Hide();
+ _mirrorInput.Hide();
+ }
+ }
+
private void SetAvailableOptions()
{
if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard"))
@@ -202,12 +313,15 @@ namespace Ryujinx.Ui.Windows
_deadZoneLeftBox.Hide();
_deadZoneRightBox.Hide();
_triggerThresholdBox.Hide();
+ _motionBox.Hide();
}
else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller"))
{
ShowAll();
_leftStickKeyboard.Hide();
_rightStickKeyboard.Hide();
+
+ UpdateCemuHookSpecificFieldsVisibility();
}
else
{
@@ -223,11 +337,11 @@ namespace Ryujinx.Ui.Windows
SetProfiles();
- if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is KeyboardConfig)
+ if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is StandardKeyboardInputConfig)
{
SetValues(_inputConfig);
}
- else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is ControllerConfig)
+ else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is StandardControllerInputConfig)
{
SetValues(_inputConfig);
}
@@ -237,7 +351,7 @@ namespace Ryujinx.Ui.Windows
{
_leftSideTriggerBox.Hide();
_rightSideTriggerBox.Hide();
- _altBox.Hide();
+ _motionAltBox.Hide();
switch (_controllerType.ActiveId)
{
@@ -248,7 +362,7 @@ namespace Ryujinx.Ui.Windows
_rightSideTriggerBox.Show();
break;
case "JoyconPair":
- _altBox.Show();
+ _motionAltBox.Show();
break;
}
@@ -263,8 +377,7 @@ namespace Ryujinx.Ui.Windows
private void ClearValues()
{
- _lStickX.Label = "Unbound";
- _lStickY.Label = "Unbound";
+ _lStick.Label = "Unbound";
_lStickUp.Label = "Unbound";
_lStickDown.Label = "Unbound";
_lStickLeft.Label = "Unbound";
@@ -279,8 +392,7 @@ namespace Ryujinx.Ui.Windows
_zL.Label = "Unbound";
_lSl.Label = "Unbound";
_lSr.Label = "Unbound";
- _rStickX.Label = "Unbound";
- _rStickY.Label = "Unbound";
+ _rStick.Label = "Unbound";
_rStickUp.Label = "Unbound";
_rStickDown.Label = "Unbound";
_rStickLeft.Label = "Unbound";
@@ -300,6 +412,7 @@ namespace Ryujinx.Ui.Windows
_controllerTriggerThreshold.Value = 0;
_mirrorInput.Active = false;
_enableMotion.Active = false;
+ _enableCemuHook.Active = false;
_slotNumber.Value = 0;
_altSlotNumber.Value = 0;
_sensitivity.Value = 100;
@@ -312,33 +425,33 @@ namespace Ryujinx.Ui.Windows
{
switch (config)
{
- case KeyboardConfig keyboardConfig:
+ case StandardKeyboardInputConfig keyboardConfig:
if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString()))
{
- _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
- ? ControllerType.Handheld.ToString()
+ _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
+ ? ControllerType.Handheld.ToString()
: ControllerType.ProController.ToString());
}
- _lStickUp.Label = keyboardConfig.LeftJoycon.StickUp.ToString();
- _lStickDown.Label = keyboardConfig.LeftJoycon.StickDown.ToString();
- _lStickLeft.Label = keyboardConfig.LeftJoycon.StickLeft.ToString();
- _lStickRight.Label = keyboardConfig.LeftJoycon.StickRight.ToString();
- _lStickButton.Label = keyboardConfig.LeftJoycon.StickButton.ToString();
- _dpadUp.Label = keyboardConfig.LeftJoycon.DPadUp.ToString();
- _dpadDown.Label = keyboardConfig.LeftJoycon.DPadDown.ToString();
- _dpadLeft.Label = keyboardConfig.LeftJoycon.DPadLeft.ToString();
- _dpadRight.Label = keyboardConfig.LeftJoycon.DPadRight.ToString();
+ _lStickUp.Label = keyboardConfig.LeftJoyconStick.StickUp.ToString();
+ _lStickDown.Label = keyboardConfig.LeftJoyconStick.StickDown.ToString();
+ _lStickLeft.Label = keyboardConfig.LeftJoyconStick.StickLeft.ToString();
+ _lStickRight.Label = keyboardConfig.LeftJoyconStick.StickRight.ToString();
+ _lStickButton.Label = keyboardConfig.LeftJoyconStick.StickButton.ToString();
+ _dpadUp.Label = keyboardConfig.LeftJoycon.DpadUp.ToString();
+ _dpadDown.Label = keyboardConfig.LeftJoycon.DpadDown.ToString();
+ _dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString();
+ _dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString();
_minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString();
_l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString();
_zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString();
_lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString();
_lSr.Label = keyboardConfig.LeftJoycon.ButtonSr.ToString();
- _rStickUp.Label = keyboardConfig.RightJoycon.StickUp.ToString();
- _rStickDown.Label = keyboardConfig.RightJoycon.StickDown.ToString();
- _rStickLeft.Label = keyboardConfig.RightJoycon.StickLeft.ToString();
- _rStickRight.Label = keyboardConfig.RightJoycon.StickRight.ToString();
- _rStickButton.Label = keyboardConfig.RightJoycon.StickButton.ToString();
+ _rStickUp.Label = keyboardConfig.RightJoyconStick.StickUp.ToString();
+ _rStickDown.Label = keyboardConfig.RightJoyconStick.StickDown.ToString();
+ _rStickLeft.Label = keyboardConfig.RightJoyconStick.StickLeft.ToString();
+ _rStickRight.Label = keyboardConfig.RightJoyconStick.StickRight.ToString();
+ _rStickButton.Label = keyboardConfig.RightJoyconStick.StickButton.ToString();
_a.Label = keyboardConfig.RightJoycon.ButtonA.ToString();
_b.Label = keyboardConfig.RightJoycon.ButtonB.ToString();
_x.Label = keyboardConfig.RightJoycon.ButtonX.ToString();
@@ -348,42 +461,33 @@ namespace Ryujinx.Ui.Windows
_zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString();
_rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString();
_rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString();
- _slotNumber.Value = keyboardConfig.Slot;
- _altSlotNumber.Value = keyboardConfig.AltSlot;
- _sensitivity.Value = keyboardConfig.Sensitivity;
- _gyroDeadzone.Value = keyboardConfig.GyroDeadzone;
- _enableMotion.Active = keyboardConfig.EnableMotion;
- _mirrorInput.Active = keyboardConfig.MirrorInput;
- _dsuServerHost.Buffer.Text = keyboardConfig.DsuServerHost;
- _dsuServerPort.Buffer.Text = keyboardConfig.DsuServerPort.ToString();
break;
- case ControllerConfig controllerConfig:
+
+ case StandardControllerInputConfig controllerConfig:
if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString()))
{
- _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
- ? ControllerType.Handheld.ToString()
+ _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
+ ? ControllerType.Handheld.ToString()
: ControllerType.ProController.ToString());
}
- _lStickX.Label = controllerConfig.LeftJoycon.StickX.ToString();
- _invertLStickX.Active = controllerConfig.LeftJoycon.InvertStickX;
- _lStickY.Label = controllerConfig.LeftJoycon.StickY.ToString();
- _invertLStickY.Active = controllerConfig.LeftJoycon.InvertStickY;
- _lStickButton.Label = controllerConfig.LeftJoycon.StickButton.ToString();
- _dpadUp.Label = controllerConfig.LeftJoycon.DPadUp.ToString();
- _dpadDown.Label = controllerConfig.LeftJoycon.DPadDown.ToString();
- _dpadLeft.Label = controllerConfig.LeftJoycon.DPadLeft.ToString();
- _dpadRight.Label = controllerConfig.LeftJoycon.DPadRight.ToString();
+ _lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString();
+ _invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX;
+ _invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY;
+ _lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString();
+ _dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString();
+ _dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString();
+ _dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString();
+ _dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString();
_minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString();
_l.Label = controllerConfig.LeftJoycon.ButtonL.ToString();
_zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString();
_lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString();
_lSr.Label = controllerConfig.LeftJoycon.ButtonSr.ToString();
- _rStickX.Label = controllerConfig.RightJoycon.StickX.ToString();
- _invertRStickX.Active = controllerConfig.RightJoycon.InvertStickX;
- _rStickY.Label = controllerConfig.RightJoycon.StickY.ToString();
- _invertRStickY.Active = controllerConfig.RightJoycon.InvertStickY;
- _rStickButton.Label = controllerConfig.RightJoycon.StickButton.ToString();
+ _rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString();
+ _invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX;
+ _invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY;
+ _rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString();
_a.Label = controllerConfig.RightJoycon.ButtonA.ToString();
_b.Label = controllerConfig.RightJoycon.ButtonB.ToString();
_x.Label = controllerConfig.RightJoycon.ButtonX.ToString();
@@ -396,14 +500,20 @@ namespace Ryujinx.Ui.Windows
_controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft;
_controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight;
_controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold;
- _slotNumber.Value = controllerConfig.Slot;
- _altSlotNumber.Value = controllerConfig.AltSlot;
- _sensitivity.Value = controllerConfig.Sensitivity;
- _gyroDeadzone.Value = controllerConfig.GyroDeadzone;
- _enableMotion.Active = controllerConfig.EnableMotion;
- _mirrorInput.Active = controllerConfig.MirrorInput;
- _dsuServerHost.Buffer.Text = controllerConfig.DsuServerHost;
- _dsuServerPort.Buffer.Text = controllerConfig.DsuServerPort.ToString();
+ _sensitivity.Value = controllerConfig.Motion.Sensitivity;
+ _gyroDeadzone.Value = controllerConfig.Motion.GyroDeadzone;
+ _enableMotion.Active = controllerConfig.Motion.EnableMotion;
+ _enableCemuHook.Active = controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook;
+
+ if (controllerConfig.Motion is CemuHookMotionConfigController cemuHookMotionConfig)
+ {
+ _slotNumber.Value = cemuHookMotionConfig.Slot;
+ _altSlotNumber.Value = cemuHookMotionConfig.AltSlot;
+ _mirrorInput.Active = cemuHookMotionConfig.MirrorInput;
+ _dsuServerHost.Buffer.Text = cemuHookMotionConfig.DsuServerHost;
+ _dsuServerPort.Buffer.Text = cemuHookMotionConfig.DsuServerPort.ToString();
+ }
+
break;
}
}
@@ -442,120 +552,143 @@ namespace Ryujinx.Ui.Windows
Enum.TryParse(_rSl.Label, out Key rButtonSl);
Enum.TryParse(_rSr.Label, out Key rButtonSr);
- int.TryParse(_dsuServerPort.Buffer.Text, out int port);
-
- return new KeyboardConfig
+ return new StandardKeyboardInputConfig
{
- Index = int.Parse(_inputDevice.ActiveId.Split("/")[1]),
- ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId),
- PlayerIndex = _playerIndex,
- LeftJoycon = new NpadKeyboardLeft
+ Backend = InputBackendType.WindowKeyboard,
+ Version = InputConfig.CurrentVersion,
+ Id = _inputDevice.ActiveId.Split("/")[1],
+ ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId),
+ PlayerIndex = _playerIndex,
+ LeftJoycon = new LeftJoyconCommonConfig<Key>
+ {
+ ButtonMinus = lButtonMinus,
+ ButtonL = lButtonL,
+ ButtonZl = lButtonZl,
+ ButtonSl = lButtonSl,
+ ButtonSr = lButtonSr,
+ DpadUp = lDPadUp,
+ DpadDown = lDPadDown,
+ DpadLeft = lDPadLeft,
+ DpadRight = lDPadRight
+ },
+ LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
- StickUp = lStickUp,
- StickDown = lStickDown,
- StickLeft = lStickLeft,
- StickRight = lStickRight,
- StickButton = lStickButton,
- DPadUp = lDPadUp,
- DPadDown = lDPadDown,
- DPadLeft = lDPadLeft,
- DPadRight = lDPadRight,
- ButtonMinus = lButtonMinus,
- ButtonL = lButtonL,
- ButtonZl = lButtonZl,
- ButtonSl = lButtonSl,
- ButtonSr = lButtonSr
+ StickUp = lStickUp,
+ StickDown = lStickDown,
+ StickLeft = lStickLeft,
+ StickRight = lStickRight,
+ StickButton = lStickButton,
+ },
+ RightJoycon = new RightJoyconCommonConfig<Key>
+ {
+ ButtonA = rButtonA,
+ ButtonB = rButtonB,
+ ButtonX = rButtonX,
+ ButtonY = rButtonY,
+ ButtonPlus = rButtonPlus,
+ ButtonR = rButtonR,
+ ButtonZr = rButtonZr,
+ ButtonSl = rButtonSl,
+ ButtonSr = rButtonSr
},
- RightJoycon = new NpadKeyboardRight
+ RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
- StickUp = rStickUp,
- StickDown = rStickDown,
- StickLeft = rStickLeft,
- StickRight = rStickRight,
- StickButton = rStickButton,
- ButtonA = rButtonA,
- ButtonB = rButtonB,
- ButtonX = rButtonX,
- ButtonY = rButtonY,
- ButtonPlus = rButtonPlus,
- ButtonR = rButtonR,
- ButtonZr = rButtonZr,
- ButtonSl = rButtonSl,
- ButtonSr = rButtonSr
+ StickUp = rStickUp,
+ StickDown = rStickDown,
+ StickLeft = rStickLeft,
+ StickRight = rStickRight,
+ StickButton = rStickButton,
},
- EnableMotion = _enableMotion.Active,
- MirrorInput = _mirrorInput.Active,
- Slot = (int)_slotNumber.Value,
- AltSlot = (int)_altSlotNumber.Value,
- Sensitivity = (int)_sensitivity.Value,
- GyroDeadzone = _gyroDeadzone.Value,
- DsuServerHost = _dsuServerHost.Buffer.Text,
- DsuServerPort = port
};
}
if (_inputDevice.ActiveId.StartsWith("controller"))
{
- Enum.TryParse(_lStickX.Label, out ControllerInputId lStickX);
- Enum.TryParse(_lStickY.Label, out ControllerInputId lStickY);
- Enum.TryParse(_lStickButton.Label, out ControllerInputId lStickButton);
- Enum.TryParse(_minus.Label, out ControllerInputId lButtonMinus);
- Enum.TryParse(_l.Label, out ControllerInputId lButtonL);
- Enum.TryParse(_zL.Label, out ControllerInputId lButtonZl);
- Enum.TryParse(_lSl.Label, out ControllerInputId lButtonSl);
- Enum.TryParse(_lSr.Label, out ControllerInputId lButtonSr);
- Enum.TryParse(_dpadUp.Label, out ControllerInputId lDPadUp);
- Enum.TryParse(_dpadDown.Label, out ControllerInputId lDPadDown);
- Enum.TryParse(_dpadLeft.Label, out ControllerInputId lDPadLeft);
- Enum.TryParse(_dpadRight.Label, out ControllerInputId lDPadRight);
-
- Enum.TryParse(_rStickX.Label, out ControllerInputId rStickX);
- Enum.TryParse(_rStickY.Label, out ControllerInputId rStickY);
- Enum.TryParse(_rStickButton.Label, out ControllerInputId rStickButton);
- Enum.TryParse(_a.Label, out ControllerInputId rButtonA);
- Enum.TryParse(_b.Label, out ControllerInputId rButtonB);
- Enum.TryParse(_x.Label, out ControllerInputId rButtonX);
- Enum.TryParse(_y.Label, out ControllerInputId rButtonY);
- Enum.TryParse(_plus.Label, out ControllerInputId rButtonPlus);
- Enum.TryParse(_r.Label, out ControllerInputId rButtonR);
- Enum.TryParse(_zR.Label, out ControllerInputId rButtonZr);
- Enum.TryParse(_rSl.Label, out ControllerInputId rButtonSl);
- Enum.TryParse(_rSr.Label, out ControllerInputId rButtonSr);
+ Enum.TryParse(_lStick.Label, out ConfigStickInputId lStick);
+ Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton);
+ Enum.TryParse(_minus.Label, out ConfigGamepadInputId lButtonMinus);
+ Enum.TryParse(_l.Label, out ConfigGamepadInputId lButtonL);
+ Enum.TryParse(_zL.Label, out ConfigGamepadInputId lButtonZl);
+ Enum.TryParse(_lSl.Label, out ConfigGamepadInputId lButtonSl);
+ Enum.TryParse(_lSr.Label, out ConfigGamepadInputId lButtonSr);
+ Enum.TryParse(_dpadUp.Label, out ConfigGamepadInputId lDPadUp);
+ Enum.TryParse(_dpadDown.Label, out ConfigGamepadInputId lDPadDown);
+ Enum.TryParse(_dpadLeft.Label, out ConfigGamepadInputId lDPadLeft);
+ Enum.TryParse(_dpadRight.Label, out ConfigGamepadInputId lDPadRight);
+
+ Enum.TryParse(_rStick.Label, out ConfigStickInputId rStick);
+ Enum.TryParse(_rStickButton.Label, out ConfigGamepadInputId rStickButton);
+ Enum.TryParse(_a.Label, out ConfigGamepadInputId rButtonA);
+ Enum.TryParse(_b.Label, out ConfigGamepadInputId rButtonB);
+ Enum.TryParse(_x.Label, out ConfigGamepadInputId rButtonX);
+ Enum.TryParse(_y.Label, out ConfigGamepadInputId rButtonY);
+ Enum.TryParse(_plus.Label, out ConfigGamepadInputId rButtonPlus);
+ Enum.TryParse(_r.Label, out ConfigGamepadInputId rButtonR);
+ Enum.TryParse(_zR.Label, out ConfigGamepadInputId rButtonZr);
+ Enum.TryParse(_rSl.Label, out ConfigGamepadInputId rButtonSl);
+ Enum.TryParse(_rSr.Label, out ConfigGamepadInputId rButtonSr);
int.TryParse(_dsuServerPort.Buffer.Text, out int port);
- return new ControllerConfig
+ MotionConfigController motionConfig;
+
+ if (_enableCemuHook.Active)
+ {
+ motionConfig = new CemuHookMotionConfigController
+ {
+ MotionBackend = MotionInputBackendType.CemuHook,
+ EnableMotion = _enableMotion.Active,
+ Sensitivity = (int)_sensitivity.Value,
+ GyroDeadzone = _gyroDeadzone.Value,
+ MirrorInput = _mirrorInput.Active,
+ Slot = (int)_slotNumber.Value,
+ AltSlot = (int)_altSlotNumber.Value,
+ DsuServerHost = _dsuServerHost.Buffer.Text,
+ DsuServerPort = port
+ };
+ }
+ else
+ {
+ motionConfig = new StandardMotionConfigController
+ {
+ MotionBackend = MotionInputBackendType.GamepadDriver,
+ EnableMotion = _enableMotion.Active,
+ Sensitivity = (int)_sensitivity.Value,
+ GyroDeadzone = _gyroDeadzone.Value,
+ };
+ }
+
+ return new StandardControllerInputConfig
{
- Index = int.Parse(_inputDevice.ActiveId.Split("/")[1]),
+ Backend = InputBackendType.GamepadSDL2,
+ Version = InputConfig.CurrentVersion,
+ Id = _inputDevice.ActiveId.Split("/")[1].Split(" ")[0],
ControllerType = Enum.Parse<ControllerType>(_controllerType.ActiveId),
PlayerIndex = _playerIndex,
DeadzoneLeft = (float)_controllerDeadzoneLeft.Value,
DeadzoneRight = (float)_controllerDeadzoneRight.Value,
TriggerThreshold = (float)_controllerTriggerThreshold.Value,
- LeftJoycon = new NpadControllerLeft
+ LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
- InvertStickX = _invertLStickX.Active,
- StickX = lStickX,
- InvertStickY = _invertLStickY.Active,
- StickY = lStickY,
- StickButton = lStickButton,
ButtonMinus = lButtonMinus,
ButtonL = lButtonL,
ButtonZl = lButtonZl,
ButtonSl = lButtonSl,
ButtonSr = lButtonSr,
- DPadUp = lDPadUp,
- DPadDown = lDPadDown,
- DPadLeft = lDPadLeft,
- DPadRight = lDPadRight
+ DpadUp = lDPadUp,
+ DpadDown = lDPadDown,
+ DpadLeft = lDPadLeft,
+ DpadRight = lDPadRight
},
- RightJoycon = new NpadControllerRight
+ LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
+ {
+ InvertStickX = _invertLStickX.Active,
+ Joystick = lStick,
+ InvertStickY = _invertLStickY.Active,
+ StickButton = lStickButton,
+ },
+ RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
- InvertStickX = _invertRStickX.Active,
- StickX = rStickX,
- InvertStickY = _invertRStickY.Active,
- StickY = rStickY,
- StickButton = rStickButton,
ButtonA = rButtonA,
ButtonB = rButtonB,
ButtonX = rButtonX,
@@ -566,14 +699,14 @@ namespace Ryujinx.Ui.Windows
ButtonSl = rButtonSl,
ButtonSr = rButtonSr
},
- EnableMotion = _enableMotion.Active,
- MirrorInput = _mirrorInput.Active,
- Slot = (int)_slotNumber.Value,
- AltSlot = (int)_altSlotNumber.Value,
- Sensitivity = (int)_sensitivity.Value,
- GyroDeadzone = _gyroDeadzone.Value,
- DsuServerHost = _dsuServerHost.Buffer.Text,
- DsuServerPort = port
+ RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
+ {
+ InvertStickX = _invertRStickX.Active,
+ Joystick = rStick,
+ InvertStickY = _invertRStickY.Active,
+ StickButton = rStickButton,
+ },
+ Motion = motionConfig
};
}
@@ -587,18 +720,16 @@ namespace Ryujinx.Ui.Windows
private string GetProfileBasePath()
{
- string path = AppDataManager.ProfilesDirPath;
-
if (_inputDevice.ActiveId.StartsWith("keyboard"))
{
- path = System.IO.Path.Combine(path, "keyboard");
+ return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
}
else if (_inputDevice.ActiveId.StartsWith("controller"))
{
- path = System.IO.Path.Combine(path, "controller");
+ return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "controller");
}
- return path;
+ return AppDataManager.ProfilesDirPath;
}
//
@@ -609,37 +740,70 @@ namespace Ryujinx.Ui.Windows
SetAvailableOptions();
SetControllerSpecificFields();
- if (_inputDevice.ActiveId != null) SetProfiles();
- }
+ _selectedGamepad?.Dispose();
+ _selectedGamepad = null;
- private void Controller_Changed(object sender, EventArgs args)
- {
- SetControllerSpecificFields();
+ if (_inputDevice.ActiveId != null)
+ {
+ SetProfiles();
+
+ string id = GetCurrentGamepadId();
+
+ if (_inputDevice.ActiveId.StartsWith("keyboard"))
+ {
+ if (_inputConfig is StandardKeyboardInputConfig)
+ {
+ SetValues(_inputConfig);
+ }
+
+ if (_mainWindow.InputManager.KeyboardDriver is GTK3KeyboardDriver)
+ {
+ // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused...
+ _selectedGamepad = _gtk3KeyboardDriver.GetGamepad(id);
+ }
+ else
+ {
+ _selectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
+ }
+ }
+ else if (_inputDevice.ActiveId.StartsWith("controller"))
+ {
+ if (_inputConfig is StandardControllerInputConfig)
+ {
+ SetValues(_inputConfig);
+ }
+
+ _selectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
+ }
+ }
}
- private void RefreshInputDevicesButton_Pressed(object sender, EventArgs args)
+ private string GetCurrentGamepadId()
{
- UpdateInputDeviceList();
+ if (_inputDevice.ActiveId == null || _inputDevice.ActiveId == "disabled")
+ {
+ return null;
+ }
- _refreshInputDevicesButton.SetStateFlags(StateFlags.Normal, true);
+ return _inputDevice.ActiveId.Split("/")[1].Split(" ")[0];
}
- private ButtonAssigner CreateButtonAssigner()
+ private void Controller_Changed(object sender, EventArgs args)
{
- int index = int.Parse(_inputDevice.ActiveId.Split("/")[1]);
+ SetControllerSpecificFields();
+ }
- ButtonAssigner assigner;
+ private IButtonAssigner CreateButtonAssigner(bool forStick)
+ {
+ IButtonAssigner assigner;
if (_inputDevice.ActiveId.StartsWith("keyboard"))
{
- assigner = new KeyboardKeyAssigner(index);
+ assigner = new KeyboardKeyAssigner((IKeyboard)_selectedGamepad);
}
else if (_inputDevice.ActiveId.StartsWith("controller"))
{
- // TODO: triggerThresold is passed but not used by JoystickButtonAssigner. Should it be used for key binding?.
- // Note that, like left and right sticks, ZL and ZR triggers are treated as axis.
- // The problem is then how to decide which axis should use triggerThresold.
- assigner = new JoystickButtonAssigner(index, _controllerTriggerThreshold.Value);
+ assigner = new GamepadButtonAssigner(_selectedGamepad, (float)_controllerTriggerThreshold.Value, forStick);
}
else
{
@@ -649,27 +813,36 @@ namespace Ryujinx.Ui.Windows
return assigner;
}
- private void Button_Pressed(object sender, EventArgs args)
+ private void HandleButtonPressed(ToggleButton button, bool forStick)
{
if (_isWaitingForInput)
{
+ button.Active = false;
+
return;
}
- ButtonAssigner assigner = CreateButtonAssigner();
+ _mousePressed = false;
+
+ ButtonPressEvent += MouseClick;
+
+ IButtonAssigner assigner = CreateButtonAssigner(forStick);
_isWaitingForInput = true;
+ // Open GTK3 keyboard for cancel operations
+ IKeyboard keyboard = (IKeyboard)_gtk3KeyboardDriver.GetGamepad("0");
+
Thread inputThread = new Thread(() =>
{
- assigner.Init();
+ assigner.Initialize();
while (true)
{
Thread.Sleep(10);
assigner.ReadInput();
- if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel())
+ if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.HasAnyButtonPressed() || assigner.ShouldCancel())
{
break;
}
@@ -677,17 +850,18 @@ namespace Ryujinx.Ui.Windows
string pressedButton = assigner.GetPressedButton();
- ToggleButton button = (ToggleButton) sender;
-
Application.Invoke(delegate
{
if (pressedButton != "")
{
button.Label = pressedButton;
}
-
+
+ ButtonPressEvent -= MouseClick;
+ keyboard.Dispose();
+
button.Active = false;
- _isWaitingForInput = false;
+ _isWaitingForInput = false;
});
});
@@ -696,22 +870,47 @@ namespace Ryujinx.Ui.Windows
inputThread.Start();
}
+ private void Button_Pressed(object sender, EventArgs args)
+ {
+ HandleButtonPressed((ToggleButton)sender, false);
+ }
+
+ private void ButtonForStick_Pressed(object sender, EventArgs args)
+ {
+ HandleButtonPressed((ToggleButton)sender, true);
+ }
+
+ private void MouseClick(object sender, ButtonPressEventArgs args)
+ {
+ _mousePressed = true;
+ }
+
private void SetProfiles()
{
+ _profile.RemoveAll();
+
string basePath = GetProfileBasePath();
-
+
if (!Directory.Exists(basePath))
{
Directory.CreateDirectory(basePath);
}
- _profile.RemoveAll();
- _profile.Append("default", "Default");
-
- foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories))
+ if (_inputDevice.ActiveId == null|| _inputDevice.ActiveId.Equals("disabled"))
+ {
+ _profile.Append("default", "None");
+ }
+ else
{
- _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile));
+ _profile.Append("default", "Default");
+
+ foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories))
+ {
+ _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile));
+ }
}
+
+ _profile.SetActiveId("default");
}
private void ProfileLoad_Activated(object sender, EventArgs args)
@@ -727,105 +926,119 @@ namespace Ryujinx.Ui.Windows
{
if (_inputDevice.ActiveId.StartsWith("keyboard"))
{
- config = new KeyboardConfig
+ config = new StandardKeyboardInputConfig
{
- Index = 0,
- ControllerType = ControllerType.JoyconPair,
- LeftJoycon = new NpadKeyboardLeft
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.WindowKeyboard,
+ Id = null,
+ ControllerType = ControllerType.JoyconPair,
+ LeftJoycon = new LeftJoyconCommonConfig<Key>
{
- StickUp = Key.W,
- StickDown = Key.S,
- StickLeft = Key.A,
- StickRight = Key.D,
- StickButton = Key.F,
- DPadUp = Key.Up,
- DPadDown = Key.Down,
- DPadLeft = Key.Left,
- DPadRight = Key.Right,
- ButtonMinus = Key.Minus,
- ButtonL = Key.E,
- ButtonZl = Key.Q,
- ButtonSl = Key.Unbound,
- ButtonSr = Key.Unbound
+ DpadUp = Key.Up,
+ DpadDown = Key.Down,
+ DpadLeft = Key.Left,
+ DpadRight = Key.Right,
+ ButtonMinus = Key.Minus,
+ ButtonL = Key.E,
+ ButtonZl = Key.Q,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
},
- RightJoycon = new NpadKeyboardRight
+
+ LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
- StickUp = Key.I,
- StickDown = Key.K,
- StickLeft = Key.J,
- StickRight = Key.L,
- StickButton = Key.H,
- ButtonA = Key.Z,
- ButtonB = Key.X,
- ButtonX = Key.C,
- ButtonY = Key.V,
- ButtonPlus = Key.Plus,
- ButtonR = Key.U,
- ButtonZr = Key.O,
- ButtonSl = Key.Unbound,
- ButtonSr = Key.Unbound
+ StickUp = Key.W,
+ StickDown = Key.S,
+ StickLeft = Key.A,
+ StickRight = Key.D,
+ StickButton = Key.F,
},
- EnableMotion = false,
- MirrorInput = false,
- Slot = 0,
- AltSlot = 0,
- Sensitivity = 100,
- GyroDeadzone = 1,
- DsuServerHost = "127.0.0.1",
- DsuServerPort = 26760
+
+ RightJoycon = new RightJoyconCommonConfig<Key>
+ {
+ ButtonA = Key.Z,
+ ButtonB = Key.X,
+ ButtonX = Key.C,
+ ButtonY = Key.V,
+ ButtonPlus = Key.Plus,
+ ButtonR = Key.U,
+ ButtonZr = Key.O,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ RightJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.I,
+ StickDown = Key.K,
+ StickLeft = Key.J,
+ StickRight = Key.L,
+ StickButton = Key.H,
+ }
};
}
else if (_inputDevice.ActiveId.StartsWith("controller"))
{
- config = new ControllerConfig
+ bool isNintendoStyle = _inputDevice.ActiveText.Contains("Nintendo");
+
+ config = new StandardControllerInputConfig
{
- Index = 0,
- ControllerType = ControllerType.ProController,
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.GamepadSDL2,
+ Id = null,
+ ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
TriggerThreshold = 0.5f,
- LeftJoycon = new NpadControllerLeft
+ LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
+ {
+ 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<ConfigGamepadInputId, ConfigStickInputId>
{
- StickX = ControllerInputId.Axis0,
- StickY = ControllerInputId.Axis1,
- StickButton = ControllerInputId.Button8,
- DPadUp = ControllerInputId.Hat0Up,
- DPadDown = ControllerInputId.Hat0Down,
- DPadLeft = ControllerInputId.Hat0Left,
- DPadRight = ControllerInputId.Hat0Right,
- ButtonMinus = ControllerInputId.Button6,
- ButtonL = ControllerInputId.Button4,
- ButtonZl = ControllerInputId.Axis2,
- ButtonSl = ControllerInputId.Unbound,
- ButtonSr = ControllerInputId.Unbound,
+ Joystick = ConfigStickInputId.Left,
+ StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
- InvertStickY = false
+ InvertStickY = false,
},
- RightJoycon = new NpadControllerRight
+
+ RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
- StickX = ControllerInputId.Axis3,
- StickY = ControllerInputId.Axis4,
- StickButton = ControllerInputId.Button9,
- ButtonA = ControllerInputId.Button1,
- ButtonB = ControllerInputId.Button0,
- ButtonX = ControllerInputId.Button3,
- ButtonY = ControllerInputId.Button2,
- ButtonPlus = ControllerInputId.Button7,
- ButtonR = ControllerInputId.Button5,
- ButtonZr = ControllerInputId.Axis5,
- ButtonSl = ControllerInputId.Unbound,
- ButtonSr = ControllerInputId.Unbound,
+ ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
+ ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
+ ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
+ ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
+ ButtonPlus = ConfigGamepadInputId.Plus,
+ ButtonR = ConfigGamepadInputId.RightShoulder,
+ ButtonZr = ConfigGamepadInputId.RightTrigger,
+ ButtonSl = ConfigGamepadInputId.Unbound,
+ ButtonSr = ConfigGamepadInputId.Unbound,
+ },
+
+ RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
+ {
+ Joystick = ConfigStickInputId.Right,
+ StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
- InvertStickY = false
+ InvertStickY = false,
},
- EnableMotion = false,
- MirrorInput = false,
- Slot = 0,
- AltSlot = 0,
- Sensitivity = 100,
- GyroDeadzone = 1,
- DsuServerHost = "127.0.0.1",
- DsuServerPort = 26760
+
+ Motion = new StandardMotionConfigController
+ {
+ MotionBackend = MotionInputBackendType.GamepadDriver,
+ EnableMotion = true,
+ Sensitivity = 100,
+ GyroDeadzone = 1,
+ }
};
}
}
@@ -847,20 +1060,10 @@ namespace Ryujinx.Ui.Windows
{
using (Stream stream = File.OpenRead(path))
{
- config = JsonHelper.Deserialize<ControllerConfig>(stream);
+ config = JsonHelper.Deserialize<InputConfig>(stream);
}
}
- catch (JsonException)
- {
- try
- {
- using (Stream stream = File.OpenRead(path))
- {
- config = JsonHelper.Deserialize<KeyboardConfig>(stream);
- }
- }
- catch { }
- }
+ catch (JsonException) { }
}
SetValues(config);
@@ -882,14 +1085,7 @@ namespace Ryujinx.Ui.Windows
string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName);
string jsonString;
- if (inputConfig is KeyboardConfig keyboardConfig)
- {
- jsonString = JsonHelper.Serialize(keyboardConfig, true);
- }
- else
- {
- jsonString = JsonHelper.Serialize(inputConfig as ControllerConfig, true);
- }
+ jsonString = JsonHelper.Serialize(inputConfig, true);
File.WriteAllText(path, jsonString);
}
@@ -945,6 +1141,11 @@ namespace Ryujinx.Ui.Windows
}
}
+ if (_mainWindow.GlRendererWidget != null)
+ {
+ _mainWindow.GlRendererWidget.NpadManager.ReloadConfiguration(newConfig);
+ }
+
// Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
diff --git a/Ryujinx/Ui/Windows/ControllerWindow.glade b/Ryujinx/Ui/Windows/ControllerWindow.glade
index d1ba42f4..8e897795 100644
--- a/Ryujinx/Ui/Windows/ControllerWindow.glade
+++ b/Ryujinx/Ui/Windows/ControllerWindow.glade
@@ -48,8 +48,8 @@
<property name="title" translatable="yes">Ryujinx - Controller Settings</property>
<property name="modal">True</property>
<property name="window_position">center</property>
- <property name="default_width">1150</property>
- <property name="default_height">690</property>
+ <property name="default_width">1200</property>
+ <property name="default_height">720</property>
<child type="titlebar">
<placeholder/>
</child>
@@ -113,21 +113,6 @@
<property name="position">1</property>
</packing>
</child>
- <child>
- <object class="GtkToggleButton" id="_refreshInputDevicesButton">
- <property name="label" translatable="yes">Refresh</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="margin_left">5</property>
- <signal name="toggled" handler="RefreshInputDevicesButton_Pressed" swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">2</property>
- </packing>
- </child>
</object>
<packing>
<property name="expand">False</property>
@@ -682,7 +667,7 @@
<property name="width_request">80</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">LStick Lt/Rt</property>
+ <property name="label" translatable="yes">LStick</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -691,20 +676,7 @@
</packing>
</child>
<child>
- <object class="GtkLabel">
- <property name="width_request">80</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">LStick Up/Dn</property>
- <property name="xalign">0</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkToggleButton" id="_lStickX">
+ <object class="GtkToggleButton" id="_lStick">
<property name="label" translatable="yes"> </property>
<property name="width_request">65</property>
<property name="visible">True</property>
@@ -717,21 +689,8 @@
</packing>
</child>
<child>
- <object class="GtkToggleButton" id="_lStickY">
- <property name="label" translatable="yes"> </property>
- <property name="width_request">65</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
<object class="GtkCheckButton" id="_invertLStickX">
- <property name="label" translatable="yes">Invert</property>
+ <property name="label" translatable="yes">Invert Stick X</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -744,7 +703,7 @@
</child>
<child>
<object class="GtkCheckButton" id="_invertLStickY">
- <property name="label" translatable="yes">Invert</property>
+ <property name="label" translatable="yes">Invert Stick Y</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1501,7 +1460,7 @@
<property name="width_request">80</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">RStick Lt/Rt</property>
+ <property name="label" translatable="yes">RStick</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -1510,20 +1469,7 @@
</packing>
</child>
<child>
- <object class="GtkLabel">
- <property name="width_request">80</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">RStick Up/Dn</property>
- <property name="xalign">0</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkToggleButton" id="_rStickX">
+ <object class="GtkToggleButton" id="_rStick">
<property name="label" translatable="yes"> </property>
<property name="width_request">65</property>
<property name="visible">True</property>
@@ -1536,21 +1482,8 @@
</packing>
</child>
<child>
- <object class="GtkToggleButton" id="_rStickY">
- <property name="label" translatable="yes"> </property>
- <property name="width_request">65</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
<object class="GtkCheckButton" id="_invertRStickX">
- <property name="label" translatable="yes">Invert</property>
+ <property name="label" translatable="yes">Invert Stick X</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1563,7 +1496,7 @@
</child>
<child>
<object class="GtkCheckButton" id="_invertRStickY">
- <property name="label" translatable="yes">Invert</property>
+ <property name="label" translatable="yes">Invert Stick Y</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1640,7 +1573,7 @@
</packing>
</child>
<child>
- <object class="GtkBox" id="MotionBox">
+ <object class="GtkBox" id="_motionBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
@@ -1679,7 +1612,21 @@
</packing>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkCheckButton" id="_enableCemuHook">
+ <property name="label" translatable="yes">Use CemuHook compatible motion</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="_motionControllerSlot">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
@@ -1718,7 +1665,7 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
- <property name="position">2</property>
+ <property name="position">3</property>
</packing>
</child>
<child>
@@ -1761,11 +1708,11 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
- <property name="position">3</property>
+ <property name="position">4</property>
</packing>
</child>
<child>
- <object class="GtkBox" id="_altBox">
+ <object class="GtkBox" id="_motionAltBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
@@ -1829,11 +1776,11 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">4</property>
+ <property name="position">5</property>
</packing>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkBox" id="_dsuServerHostBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">30</property>
@@ -1866,11 +1813,11 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
- <property name="position">5</property>
+ <property name="position">6</property>
</packing>
</child>
<child>
- <object class="GtkBox">
+ <object class="GtkBox" id="_dsuServerPortBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">30</property>
@@ -1903,7 +1850,7 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
- <property name="position">6</property>
+ <property name="position">7</property>
</packing>
</child>
<child>
@@ -1916,7 +1863,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">7</property>
+ <property name="position">8</property>
</packing>
</child>
<child>
@@ -1930,7 +1877,7 @@
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">8</property>
+ <property name="position">9</property>
</packing>
</child>
</object>
diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs
index 4cd5eb72..be9dc271 100644
--- a/Ryujinx/Ui/Windows/SettingsWindow.cs
+++ b/Ryujinx/Ui/Windows/SettingsWindow.cs
@@ -578,7 +578,7 @@ namespace Ryujinx.Ui.Windows
{
((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
- ControllerWindow controllerWindow = new ControllerWindow(playerIndex);
+ ControllerWindow controllerWindow = new ControllerWindow(_parent, playerIndex);
controllerWindow.SetSizeRequest((int)(controllerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(controllerWindow.DefaultHeight * Program.WindowScaleFactor));
controllerWindow.Show();