NpadManager.cs
1 using Ryujinx.Common.Configuration.Hid; 2 using Ryujinx.Common.Configuration.Hid.Controller; 3 using Ryujinx.Common.Configuration.Hid.Keyboard; 4 using Ryujinx.HLE.HOS.Services.Hid; 5 using System; 6 using System.Collections.Generic; 7 using System.Diagnostics; 8 using System.Linq; 9 using System.Runtime.CompilerServices; 10 using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client; 11 using ControllerType = Ryujinx.Common.Configuration.Hid.ControllerType; 12 using PlayerIndex = Ryujinx.HLE.HOS.Services.Hid.PlayerIndex; 13 using Switch = Ryujinx.HLE.Switch; 14 15 namespace Ryujinx.Input.HLE 16 { 17 public class NpadManager : IDisposable 18 { 19 private readonly CemuHookClient _cemuHookClient; 20 21 private readonly object _lock = new(); 22 23 private bool _blockInputUpdates; 24 25 private const int MaxControllers = 9; 26 27 private readonly NpadController[] _controllers; 28 29 private readonly IGamepadDriver _keyboardDriver; 30 private readonly IGamepadDriver _gamepadDriver; 31 private readonly IGamepadDriver _mouseDriver; 32 private bool _isDisposed; 33 34 private List<InputConfig> _inputConfig; 35 private bool _enableKeyboard; 36 private bool _enableMouse; 37 private Switch _device; 38 39 public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) 40 { 41 _controllers = new NpadController[MaxControllers]; 42 _cemuHookClient = new CemuHookClient(this); 43 44 _keyboardDriver = keyboardDriver; 45 _gamepadDriver = gamepadDriver; 46 _mouseDriver = mouseDriver; 47 _inputConfig = new List<InputConfig>(); 48 49 _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; 50 _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; 51 } 52 53 private void RefreshInputConfigForHLE() 54 { 55 lock (_lock) 56 { 57 List<InputConfig> validInputs = new(); 58 foreach (var inputConfigEntry in _inputConfig) 59 { 60 if (_controllers[(int)inputConfigEntry.PlayerIndex] != null) 61 { 62 validInputs.Add(inputConfigEntry); 63 } 64 } 65 66 _device.Hid.RefreshInputConfig(validInputs); 67 } 68 } 69 70 private void HandleOnGamepadDisconnected(string obj) 71 { 72 // Force input reload 73 lock (_lock) 74 { 75 // Forcibly disconnect any controllers with this ID. 76 for (int i = 0; i < _controllers.Length; i++) 77 { 78 if (_controllers[i]?.Id == obj) 79 { 80 _controllers[i]?.Dispose(); 81 _controllers[i] = null; 82 } 83 } 84 85 ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); 86 } 87 } 88 89 private void HandleOnGamepadConnected(string id) 90 { 91 // Force input reload 92 ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); 93 } 94 95 [MethodImpl(MethodImplOptions.AggressiveInlining)] 96 private bool DriverConfigurationUpdate(ref NpadController controller, InputConfig config) 97 { 98 IGamepadDriver targetDriver = _gamepadDriver; 99 100 if (config is StandardControllerInputConfig) 101 { 102 targetDriver = _gamepadDriver; 103 } 104 else if (config is StandardKeyboardInputConfig) 105 { 106 targetDriver = _keyboardDriver; 107 } 108 109 Debug.Assert(targetDriver != null, "Unknown input configuration!"); 110 111 if (controller.GamepadDriver != targetDriver || controller.Id != config.Id) 112 { 113 return controller.UpdateDriverConfiguration(targetDriver, config); 114 } 115 116 return controller.GamepadDriver != null; 117 } 118 119 public void ReloadConfiguration(List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse) 120 { 121 lock (_lock) 122 { 123 NpadController[] oldControllers = _controllers.ToArray(); 124 125 List<InputConfig> validInputs = new(); 126 127 foreach (InputConfig inputConfigEntry in inputConfig) 128 { 129 NpadController controller; 130 int index = (int)inputConfigEntry.PlayerIndex; 131 132 if (oldControllers[index] != null) 133 { 134 // Try reuse the existing controller. 135 controller = oldControllers[index]; 136 oldControllers[index] = null; 137 } 138 else 139 { 140 controller = new(_cemuHookClient); 141 } 142 143 bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); 144 145 if (!isValid) 146 { 147 _controllers[index] = null; 148 controller.Dispose(); 149 } 150 else 151 { 152 _controllers[index] = controller; 153 validInputs.Add(inputConfigEntry); 154 } 155 } 156 157 for (int i = 0; i < oldControllers.Length; i++) 158 { 159 // Disconnect any controllers that weren't reused by the new configuration. 160 161 oldControllers[i]?.Dispose(); 162 oldControllers[i] = null; 163 } 164 165 _inputConfig = inputConfig; 166 _enableKeyboard = enableKeyboard; 167 _enableMouse = enableMouse; 168 169 _device.Hid.RefreshInputConfig(validInputs); 170 } 171 } 172 173 public void UnblockInputUpdates() 174 { 175 lock (_lock) 176 { 177 foreach (InputConfig inputConfig in _inputConfig) 178 { 179 _controllers[(int)inputConfig.PlayerIndex]?.GamepadDriver?.Clear(); 180 } 181 182 _blockInputUpdates = false; 183 } 184 } 185 186 public void BlockInputUpdates() 187 { 188 lock (_lock) 189 { 190 _blockInputUpdates = true; 191 } 192 } 193 194 public void Initialize(Switch device, List<InputConfig> inputConfig, bool enableKeyboard, bool enableMouse) 195 { 196 _device = device; 197 _device.Configuration.RefreshInputConfig = RefreshInputConfigForHLE; 198 199 ReloadConfiguration(inputConfig, enableKeyboard, enableMouse); 200 } 201 202 public void Update(float aspectRatio = 1) 203 { 204 lock (_lock) 205 { 206 List<GamepadInput> hleInputStates = new(); 207 List<SixAxisInput> hleMotionStates = new(NpadDevices.MaxControllers); 208 209 KeyboardInput? hleKeyboardInput = null; 210 211 foreach (InputConfig inputConfig in _inputConfig) 212 { 213 GamepadInput inputState = default; 214 (SixAxisInput, SixAxisInput) motionState = default; 215 216 NpadController controller = _controllers[(int)inputConfig.PlayerIndex]; 217 PlayerIndex playerIndex = (PlayerIndex)inputConfig.PlayerIndex; 218 219 bool isJoyconPair = false; 220 221 // Do we allow input updates and is a controller connected? 222 if (!_blockInputUpdates && controller != null) 223 { 224 DriverConfigurationUpdate(ref controller, inputConfig); 225 226 controller.UpdateUserConfiguration(inputConfig); 227 controller.Update(); 228 controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex)); 229 230 inputState = controller.GetHLEInputState(); 231 232 inputState.Buttons |= _device.Hid.UpdateStickButtons(inputState.LStick, inputState.RStick); 233 234 isJoyconPair = inputConfig.ControllerType == ControllerType.JoyconPair; 235 236 var altMotionState = isJoyconPair ? controller.GetHLEMotionState(true) : default; 237 238 motionState = (controller.GetHLEMotionState(), altMotionState); 239 } 240 else 241 { 242 // Ensure that orientation isn't null 243 motionState.Item1.Orientation = new float[9]; 244 } 245 246 inputState.PlayerId = playerIndex; 247 motionState.Item1.PlayerId = playerIndex; 248 249 hleInputStates.Add(inputState); 250 hleMotionStates.Add(motionState.Item1); 251 252 if (isJoyconPair && !motionState.Item2.Equals(default)) 253 { 254 motionState.Item2.PlayerId = playerIndex; 255 256 hleMotionStates.Add(motionState.Item2); 257 } 258 } 259 260 if (!_blockInputUpdates && _enableKeyboard) 261 { 262 hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); 263 } 264 265 _device.Hid.Npads.Update(hleInputStates); 266 _device.Hid.Npads.UpdateSixAxis(hleMotionStates); 267 268 if (hleKeyboardInput.HasValue) 269 { 270 _device.Hid.Keyboard.Update(hleKeyboardInput.Value); 271 } 272 273 if (_enableMouse) 274 { 275 var mouse = _mouseDriver.GetGamepad("0") as IMouse; 276 277 var mouseInput = IMouse.GetMouseStateSnapshot(mouse); 278 279 uint buttons = 0; 280 281 if (mouseInput.IsPressed(MouseButton.Button1)) 282 { 283 buttons |= 1 << 0; 284 } 285 286 if (mouseInput.IsPressed(MouseButton.Button2)) 287 { 288 buttons |= 1 << 1; 289 } 290 291 if (mouseInput.IsPressed(MouseButton.Button3)) 292 { 293 buttons |= 1 << 2; 294 } 295 296 if (mouseInput.IsPressed(MouseButton.Button4)) 297 { 298 buttons |= 1 << 3; 299 } 300 301 if (mouseInput.IsPressed(MouseButton.Button5)) 302 { 303 buttons |= 1 << 4; 304 } 305 306 var position = IMouse.GetScreenPosition(mouseInput.Position, mouse.ClientSize, aspectRatio); 307 308 _device.Hid.Mouse.Update((int)position.X, (int)position.Y, buttons, (int)mouseInput.Scroll.X, (int)mouseInput.Scroll.Y, true); 309 } 310 else 311 { 312 _device.Hid.Mouse.Update(0, 0); 313 } 314 315 _device.TamperMachine.UpdateInput(hleInputStates); 316 } 317 } 318 319 internal InputConfig GetPlayerInputConfigByIndex(int index) 320 { 321 lock (_lock) 322 { 323 return _inputConfig.Find(x => x.PlayerIndex == (Common.Configuration.Hid.PlayerIndex)index); 324 } 325 } 326 327 protected virtual void Dispose(bool disposing) 328 { 329 if (disposing) 330 { 331 lock (_lock) 332 { 333 if (!_isDisposed) 334 { 335 _cemuHookClient.Dispose(); 336 337 _gamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; 338 _gamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; 339 340 for (int i = 0; i < _controllers.Length; i++) 341 { 342 _controllers[i]?.Dispose(); 343 } 344 345 _isDisposed = true; 346 } 347 } 348 } 349 } 350 351 public void Dispose() 352 { 353 GC.SuppressFinalize(this); 354 Dispose(true); 355 } 356 } 357 }