aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Input/HLE/NpadManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Input/HLE/NpadManager.cs')
-rw-r--r--src/Ryujinx.Input/HLE/NpadManager.cs320
1 files changed, 320 insertions, 0 deletions
diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs
new file mode 100644
index 00000000..5290ecbb
--- /dev/null
+++ b/src/Ryujinx.Input/HLE/NpadManager.cs
@@ -0,0 +1,320 @@
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+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;
+using Switch = Ryujinx.HLE.Switch;
+
+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 readonly IGamepadDriver _mouseDriver;
+ private bool _isDisposed;
+
+ private List<InputConfig> _inputConfig;
+ private bool _enableKeyboard;
+ private bool _enableMouse;
+ private Switch _device;
+
+ public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
+ {
+ _controllers = new NpadController[MaxControllers];
+ _cemuHookClient = new CemuHookClient(this);
+
+ _keyboardDriver = keyboardDriver;
+ _gamepadDriver = gamepadDriver;
+ _mouseDriver = mouseDriver;
+ _inputConfig = new List<InputConfig>();
+
+ _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
+ _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
+ }
+
+ private void RefreshInputConfigForHLE()
+ {
+ lock (_lock)
+ {
+ List<InputConfig> validInputs = new List<InputConfig>();
+ foreach (var inputConfigEntry in _inputConfig)
+ {
+ if (_controllers[(int)inputConfigEntry.PlayerIndex] != null)
+ {
+ validInputs.Add(inputConfigEntry);
+ }
+ }
+
+ _device.Hid.RefreshInputConfig(validInputs);
+ }
+ }
+
+ private void HandleOnGamepadDisconnected(string obj)
+ {
+ // Force input reload
+ ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
+ }
+
+ private void HandleOnGamepadConnected(string id)
+ {
+ // Force input reload
+ ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
+ }
+
+ [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, bool enableKeyboard, bool enableMouse)
+ {
+ lock (_lock)
+ {
+ for (int i = 0; i < _controllers.Length; i++)
+ {
+ _controllers[i]?.Dispose();
+ _controllers[i] = null;
+ }
+
+ List<InputConfig> validInputs = new List<InputConfig>();
+
+ 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;
+ validInputs.Add(inputConfigEntry);
+ }
+ }
+
+ _inputConfig = inputConfig;
+ _enableKeyboard = enableKeyboard;
+ _enableMouse = enableMouse;
+
+ _device.Hid.RefreshInputConfig(validInputs);
+ }
+ }
+
+ public void UnblockInputUpdates()
+ {
+ lock (_lock)
+ {
+ _blockInputUpdates = false;
+ }
+ }
+
+ public void BlockInputUpdates()
+ {
+ lock (_lock)
+ {
+ _blockInputUpdates = true;
+ }
+ }
+
+ public void Initialize(Switch device, List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse)
+ {
+ _device = device;
+ _device.Configuration.RefreshInputConfig = RefreshInputConfigForHLE;
+
+ ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
+ }
+
+ public void Update(float aspectRatio = 1)
+ {
+ lock (_lock)
+ {
+ List<GamepadInput> hleInputStates = new List<GamepadInput>();
+ List<SixAxisInput> hleMotionStates = new List<SixAxisInput>(NpadDevices.MaxControllers);
+
+ KeyboardInput? hleKeyboardInput = null;
+
+ foreach (InputConfig inputConfig in _inputConfig)
+ {
+ GamepadInput inputState = default;
+ (SixAxisInput, SixAxisInput) motionState = default;
+
+ NpadController controller = _controllers[(int)inputConfig.PlayerIndex];
+ Ryujinx.HLE.HOS.Services.Hid.PlayerIndex playerIndex = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex;
+
+ bool isJoyconPair = false;
+
+ // Do we allow input updates and is a controller connected?
+ if (!_blockInputUpdates && controller != null)
+ {
+ DriverConfigurationUpdate(ref controller, inputConfig);
+
+ controller.UpdateUserConfiguration(inputConfig);
+ controller.Update();
+ controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex));
+
+ inputState = controller.GetHLEInputState();
+
+ inputState.Buttons |= _device.Hid.UpdateStickButtons(inputState.LStick, inputState.RStick);
+
+ isJoyconPair = inputConfig.ControllerType == Common.Configuration.Hid.ControllerType.JoyconPair;
+
+ var altMotionState = isJoyconPair ? controller.GetHLEMotionState(true) : default;
+
+ motionState = (controller.GetHLEMotionState(), altMotionState);
+
+ if (_enableKeyboard)
+ {
+ hleKeyboardInput = controller.GetHLEKeyboardInput();
+ }
+ }
+ else
+ {
+ // Ensure that orientation isn't null
+ motionState.Item1.Orientation = new float[9];
+ }
+
+ inputState.PlayerId = playerIndex;
+ motionState.Item1.PlayerId = playerIndex;
+
+ hleInputStates.Add(inputState);
+ hleMotionStates.Add(motionState.Item1);
+
+ if (isJoyconPair && !motionState.Item2.Equals(default))
+ {
+ motionState.Item2.PlayerId = playerIndex;
+
+ hleMotionStates.Add(motionState.Item2);
+ }
+ }
+
+ _device.Hid.Npads.Update(hleInputStates);
+ _device.Hid.Npads.UpdateSixAxis(hleMotionStates);
+
+ if (hleKeyboardInput.HasValue)
+ {
+ _device.Hid.Keyboard.Update(hleKeyboardInput.Value);
+ }
+
+ if (_enableMouse)
+ {
+ var mouse = _mouseDriver.GetGamepad("0") as IMouse;
+
+ var mouseInput = IMouse.GetMouseStateSnapshot(mouse);
+
+ uint buttons = 0;
+
+ if (mouseInput.IsPressed(MouseButton.Button1))
+ {
+ buttons |= 1 << 0;
+ }
+
+ if (mouseInput.IsPressed(MouseButton.Button2))
+ {
+ buttons |= 1 << 1;
+ }
+
+ if (mouseInput.IsPressed(MouseButton.Button3))
+ {
+ buttons |= 1 << 2;
+ }
+
+ if (mouseInput.IsPressed(MouseButton.Button4))
+ {
+ buttons |= 1 << 3;
+ }
+
+ if (mouseInput.IsPressed(MouseButton.Button5))
+ {
+ buttons |= 1 << 4;
+ }
+
+ var position = IMouse.GetScreenPosition(mouseInput.Position, mouse.ClientSize, aspectRatio);
+
+ _device.Hid.Mouse.Update((int)position.X, (int)position.Y, buttons, (int)mouseInput.Scroll.X, (int)mouseInput.Scroll.Y, true);
+ }
+ else
+ {
+ _device.Hid.Mouse.Update(0, 0);
+ }
+
+ _device.TamperMachine.UpdateInput(hleInputStates);
+ }
+ }
+
+ internal InputConfig GetPlayerInputConfigByIndex(int index)
+ {
+ lock (_lock)
+ {
+ return _inputConfig.Find(x => x.PlayerIndex == (Ryujinx.Common.Configuration.Hid.PlayerIndex)index);
+ }
+ }
+
+ 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);
+ }
+ }
+}