/ src / Ryujinx.Input / HLE / NpadManager.cs
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  }