NpadDevices.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using Ryujinx.Common;
  5. using Ryujinx.Common.Logging;
  6. using Ryujinx.HLE.HOS.Kernel.Threading;
  7. using Ryujinx.HLE.HOS.Services.Hid.Types;
  8. using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
  9. using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
  10. namespace Ryujinx.HLE.HOS.Services.Hid
  11. {
  12. public class NpadDevices : BaseDevice
  13. {
  14. private const int NoMatchNotifyFrequencyMs = 2000;
  15. private int _activeCount;
  16. private long _lastNotifyTimestamp;
  17. public const int MaxControllers = 9; // Players 1-8 and Handheld
  18. private ControllerType[] _configuredTypes;
  19. private KEvent[] _styleSetUpdateEvents;
  20. private bool[] _supportedPlayers;
  21. internal NpadJoyHoldType JoyHold { get; set; }
  22. internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
  23. internal ControllerType SupportedStyleSets { get; set; }
  24. public NpadDevices(Switch device, bool active = true) : base(device, active)
  25. {
  26. _configuredTypes = new ControllerType[MaxControllers];
  27. SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair |
  28. ControllerType.JoyconLeft | ControllerType.JoyconRight |
  29. ControllerType.ProController;
  30. _supportedPlayers = new bool[MaxControllers];
  31. _supportedPlayers.AsSpan().Fill(true);
  32. _styleSetUpdateEvents = new KEvent[MaxControllers];
  33. for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
  34. {
  35. _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
  36. }
  37. _activeCount = 0;
  38. JoyHold = NpadJoyHoldType.Vertical;
  39. }
  40. internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
  41. {
  42. return ref _styleSetUpdateEvents[(int)player];
  43. }
  44. internal void ClearSupportedPlayers()
  45. {
  46. _supportedPlayers.AsSpan().Clear();
  47. }
  48. internal void SetSupportedPlayer(PlayerIndex player, bool supported = true)
  49. {
  50. _supportedPlayers[(int)player] = supported;
  51. }
  52. internal IEnumerable<PlayerIndex> GetSupportedPlayers()
  53. {
  54. for (int i = 0; i < _supportedPlayers.Length; ++i)
  55. {
  56. if (_supportedPlayers[i])
  57. {
  58. yield return (PlayerIndex)i;
  59. }
  60. }
  61. }
  62. public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex)
  63. {
  64. primaryIndex = PlayerIndex.Unknown;
  65. configuredCount = 0;
  66. for (int i = 0; i < MaxControllers; ++i)
  67. {
  68. ControllerType npad = _configuredTypes[i];
  69. if (npad == ControllerType.Handheld && _device.System.State.DockedMode)
  70. {
  71. continue;
  72. }
  73. ControllerType currentType = (ControllerType)_device.Hid.SharedMemory.Npads[i].InternalState.StyleSet;
  74. if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i])
  75. {
  76. configuredCount++;
  77. if (primaryIndex == PlayerIndex.Unknown)
  78. {
  79. primaryIndex = (PlayerIndex)i;
  80. }
  81. }
  82. }
  83. if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown)
  84. {
  85. return false;
  86. }
  87. return true;
  88. }
  89. public void Configure(params ControllerConfig[] configs)
  90. {
  91. _configuredTypes = new ControllerType[MaxControllers];
  92. for (int i = 0; i < configs.Length; ++i)
  93. {
  94. PlayerIndex player = configs[i].Player;
  95. ControllerType controllerType = configs[i].Type;
  96. if (player > PlayerIndex.Handheld)
  97. {
  98. throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld");
  99. }
  100. if (controllerType == ControllerType.Handheld)
  101. {
  102. player = PlayerIndex.Handheld;
  103. }
  104. _configuredTypes[(int)player] = controllerType;
  105. Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}");
  106. }
  107. }
  108. public void Update(IList<GamepadInput> states)
  109. {
  110. Remap();
  111. Span<bool> updated = stackalloc bool[10];
  112. // Update configured inputs
  113. for (int i = 0; i < states.Count; ++i)
  114. {
  115. GamepadInput state = states[i];
  116. updated[(int)state.PlayerId] = true;
  117. UpdateInput(state);
  118. }
  119. for (int i = 0; i < updated.Length; i++)
  120. {
  121. if (!updated[i])
  122. {
  123. UpdateDisconnectedInput((PlayerIndex)i);
  124. }
  125. }
  126. }
  127. private void Remap()
  128. {
  129. // Remap/Init if necessary
  130. for (int i = 0; i < MaxControllers; ++i)
  131. {
  132. ControllerType config = _configuredTypes[i];
  133. // Remove Handheld config when Docked
  134. if (config == ControllerType.Handheld && _device.System.State.DockedMode)
  135. {
  136. config = ControllerType.None;
  137. }
  138. // Auto-remap ProController and JoyconPair
  139. if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0)
  140. {
  141. config = ControllerType.ProController;
  142. }
  143. else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0)
  144. {
  145. config = ControllerType.JoyconPair;
  146. }
  147. // Check StyleSet and PlayerSet
  148. if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i])
  149. {
  150. config = ControllerType.None;
  151. }
  152. SetupNpad((PlayerIndex)i, config);
  153. }
  154. if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs)
  155. {
  156. Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'");
  157. _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds;
  158. }
  159. }
  160. private void SetupNpad(PlayerIndex player, ControllerType type)
  161. {
  162. ref NpadInternalState controller = ref _device.Hid.SharedMemory.Npads[(int)player].InternalState;
  163. ControllerType oldType = (ControllerType)controller.StyleSet;
  164. if (oldType == type)
  165. {
  166. return; // Already configured
  167. }
  168. controller = NpadInternalState.Create(); // Reset it
  169. if (type == ControllerType.None)
  170. {
  171. _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect
  172. _activeCount--;
  173. Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}");
  174. return;
  175. }
  176. // TODO: Allow customizing colors at config
  177. controller.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
  178. controller.FullKeyColor.FullKeyBody = (uint)NpadColor.BodyGray;
  179. controller.FullKeyColor.FullKeyButtons = (uint)NpadColor.ButtonGray;
  180. controller.JoyColor.LeftBody = (uint)NpadColor.BodyNeonBlue;
  181. controller.JoyColor.LeftButtons = (uint)NpadColor.ButtonGray;
  182. controller.JoyColor.RightBody = (uint)NpadColor.BodyNeonRed;
  183. controller.JoyColor.RightButtons = (uint)NpadColor.ButtonGray;
  184. controller.SystemProperties = NpadSystemProperties.IsPoweredJoyDual |
  185. NpadSystemProperties.IsPoweredJoyLeft |
  186. NpadSystemProperties.IsPoweredJoyRight;
  187. controller.BatteryLevelJoyDual = NpadBatteryLevel.Percent100;
  188. controller.BatteryLevelJoyLeft = NpadBatteryLevel.Percent100;
  189. controller.BatteryLevelJoyRight = NpadBatteryLevel.Percent100;
  190. switch (type)
  191. {
  192. case ControllerType.ProController:
  193. controller.StyleSet = NpadStyleTag.FullKey;
  194. controller.DeviceType = DeviceType.FullKey;
  195. controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
  196. NpadSystemProperties.IsPlusAvailable |
  197. NpadSystemProperties.IsMinusAvailable;
  198. break;
  199. case ControllerType.Handheld:
  200. controller.StyleSet = NpadStyleTag.Handheld;
  201. controller.DeviceType = DeviceType.HandheldLeft |
  202. DeviceType.HandheldRight;
  203. controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
  204. NpadSystemProperties.IsPlusAvailable |
  205. NpadSystemProperties.IsMinusAvailable;
  206. break;
  207. case ControllerType.JoyconPair:
  208. controller.StyleSet = NpadStyleTag.JoyDual;
  209. controller.DeviceType = DeviceType.JoyLeft |
  210. DeviceType.JoyRight;
  211. controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
  212. NpadSystemProperties.IsPlusAvailable |
  213. NpadSystemProperties.IsMinusAvailable;
  214. break;
  215. case ControllerType.JoyconLeft:
  216. controller.StyleSet = NpadStyleTag.JoyLeft;
  217. controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
  218. controller.DeviceType = DeviceType.JoyLeft;
  219. controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
  220. NpadSystemProperties.IsMinusAvailable;
  221. break;
  222. case ControllerType.JoyconRight:
  223. controller.StyleSet = NpadStyleTag.JoyRight;
  224. controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
  225. controller.DeviceType = DeviceType.JoyRight;
  226. controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
  227. NpadSystemProperties.IsPlusAvailable;
  228. break;
  229. case ControllerType.Pokeball:
  230. controller.StyleSet = NpadStyleTag.Palma;
  231. controller.DeviceType = DeviceType.Palma;
  232. break;
  233. }
  234. _styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
  235. _activeCount++;
  236. Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}");
  237. }
  238. private ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad)
  239. {
  240. switch (npad.StyleSet)
  241. {
  242. case NpadStyleTag.FullKey:
  243. return ref npad.FullKey;
  244. case NpadStyleTag.Handheld:
  245. return ref npad.Handheld;
  246. case NpadStyleTag.JoyDual:
  247. return ref npad.JoyDual;
  248. case NpadStyleTag.JoyLeft:
  249. return ref npad.JoyLeft;
  250. case NpadStyleTag.JoyRight:
  251. return ref npad.JoyRight;
  252. case NpadStyleTag.Palma:
  253. return ref npad.Palma;
  254. default:
  255. return ref npad.SystemExt;
  256. }
  257. }
  258. private void UpdateUnusedInputIfNotEqual(ref RingLifo<NpadCommonState> currentlyUsed, ref RingLifo<NpadCommonState> possiblyUnused)
  259. {
  260. bool isEquals;
  261. unsafe
  262. {
  263. var aPointer = Unsafe.AsPointer(ref currentlyUsed);
  264. var bPointer = Unsafe.AsPointer(ref possiblyUnused);
  265. isEquals = aPointer == bPointer;
  266. }
  267. if (!isEquals)
  268. {
  269. NpadCommonState newState = new NpadCommonState();
  270. WriteNewInputEntry(ref possiblyUnused, ref newState);
  271. }
  272. }
  273. private void WriteNewInputEntry(ref RingLifo<NpadCommonState> lifo, ref NpadCommonState state)
  274. {
  275. ref NpadCommonState previousEntry = ref lifo.GetCurrentEntryRef();
  276. state.SamplingNumber = previousEntry.SamplingNumber + 1;
  277. lifo.Write(ref state);
  278. }
  279. private void UpdateUnusedSixInputIfNotEqual(ref RingLifo<SixAxisSensorState> currentlyUsed, ref RingLifo<SixAxisSensorState> possiblyUnused)
  280. {
  281. bool isEquals;
  282. unsafe
  283. {
  284. var aPointer = Unsafe.AsPointer(ref currentlyUsed);
  285. var bPointer = Unsafe.AsPointer(ref possiblyUnused);
  286. isEquals = aPointer == bPointer;
  287. }
  288. if (!isEquals)
  289. {
  290. SixAxisSensorState newState = new SixAxisSensorState();
  291. WriteNewSixInputEntry(ref possiblyUnused, ref newState);
  292. }
  293. }
  294. private void WriteNewSixInputEntry(ref RingLifo<SixAxisSensorState> lifo, ref SixAxisSensorState state)
  295. {
  296. ref SixAxisSensorState previousEntry = ref lifo.GetCurrentEntryRef();
  297. state.SamplingNumber = previousEntry.SamplingNumber + 1;
  298. lifo.Write(ref state);
  299. }
  300. private void UpdateInput(GamepadInput state)
  301. {
  302. if (state.PlayerId == PlayerIndex.Unknown)
  303. {
  304. return;
  305. }
  306. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
  307. if (currentNpad.StyleSet == NpadStyleTag.None)
  308. {
  309. return;
  310. }
  311. ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
  312. NpadCommonState newState = new NpadCommonState
  313. {
  314. Buttons = (NpadButton)state.Buttons,
  315. AnalogStickL = new AnalogStickState
  316. {
  317. X = state.LStick.Dx,
  318. Y = state.LStick.Dy,
  319. },
  320. AnalogStickR = new AnalogStickState
  321. {
  322. X = state.RStick.Dx,
  323. Y = state.RStick.Dy,
  324. }
  325. };
  326. newState.Attributes = NpadAttribute.IsConnected;
  327. switch (currentNpad.StyleSet)
  328. {
  329. case NpadStyleTag.Handheld:
  330. case NpadStyleTag.FullKey:
  331. newState.Attributes |= NpadAttribute.IsWired;
  332. break;
  333. case NpadStyleTag.JoyDual:
  334. newState.Attributes |= NpadAttribute.IsLeftConnected |
  335. NpadAttribute.IsRightConnected;
  336. break;
  337. case NpadStyleTag.JoyLeft:
  338. newState.Attributes |= NpadAttribute.IsLeftConnected;
  339. break;
  340. case NpadStyleTag.JoyRight:
  341. newState.Attributes |= NpadAttribute.IsRightConnected;
  342. break;
  343. }
  344. WriteNewInputEntry(ref lifo, ref newState);
  345. // Mirror data to Default layout just in case
  346. if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt))
  347. {
  348. WriteNewInputEntry(ref currentNpad.SystemExt, ref newState);
  349. }
  350. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey);
  351. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld);
  352. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual);
  353. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyLeft);
  354. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyRight);
  355. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Palma);
  356. }
  357. private void UpdateDisconnectedInput(PlayerIndex index)
  358. {
  359. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
  360. NpadCommonState newState = new NpadCommonState();
  361. WriteNewInputEntry(ref currentNpad.FullKey, ref newState);
  362. WriteNewInputEntry(ref currentNpad.Handheld, ref newState);
  363. WriteNewInputEntry(ref currentNpad.JoyDual, ref newState);
  364. WriteNewInputEntry(ref currentNpad.JoyLeft, ref newState);
  365. WriteNewInputEntry(ref currentNpad.JoyRight, ref newState);
  366. WriteNewInputEntry(ref currentNpad.Palma, ref newState);
  367. }
  368. public void UpdateSixAxis(IList<SixAxisInput> states)
  369. {
  370. Span<bool> updated = stackalloc bool[10];
  371. for (int i = 0; i < states.Count; ++i)
  372. {
  373. updated[(int)states[i].PlayerId] = true;
  374. if (SetSixAxisState(states[i]))
  375. {
  376. i++;
  377. if (i >= states.Count)
  378. {
  379. return;
  380. }
  381. SetSixAxisState(states[i], true);
  382. }
  383. }
  384. for (int i = 0; i < updated.Length; i++)
  385. {
  386. if (!updated[i])
  387. {
  388. UpdateDisconnectedInputSixAxis((PlayerIndex)i);
  389. }
  390. }
  391. }
  392. private ref RingLifo<SixAxisSensorState> GetSixAxisSensorLifo(ref NpadInternalState npad, bool isRightPair)
  393. {
  394. switch (npad.StyleSet)
  395. {
  396. case NpadStyleTag.FullKey:
  397. return ref npad.FullKeySixAxisSensor;
  398. case NpadStyleTag.Handheld:
  399. return ref npad.HandheldSixAxisSensor;
  400. case NpadStyleTag.JoyDual:
  401. if (isRightPair)
  402. {
  403. return ref npad.JoyDualRightSixAxisSensor;
  404. }
  405. else
  406. {
  407. return ref npad.JoyDualSixAxisSensor;
  408. }
  409. case NpadStyleTag.JoyLeft:
  410. return ref npad.JoyLeftSixAxisSensor;
  411. case NpadStyleTag.JoyRight:
  412. return ref npad.JoyRightSixAxisSensor;
  413. default:
  414. throw new NotImplementedException($"{npad.StyleSet}");
  415. }
  416. }
  417. private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
  418. {
  419. if (state.PlayerId == PlayerIndex.Unknown)
  420. {
  421. return false;
  422. }
  423. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
  424. if (currentNpad.StyleSet == NpadStyleTag.None)
  425. {
  426. return false;
  427. }
  428. HidVector accel = new HidVector()
  429. {
  430. X = state.Accelerometer.X,
  431. Y = state.Accelerometer.Y,
  432. Z = state.Accelerometer.Z
  433. };
  434. HidVector gyro = new HidVector()
  435. {
  436. X = state.Gyroscope.X,
  437. Y = state.Gyroscope.Y,
  438. Z = state.Gyroscope.Z
  439. };
  440. HidVector rotation = new HidVector()
  441. {
  442. X = state.Rotation.X,
  443. Y = state.Rotation.Y,
  444. Z = state.Rotation.Z
  445. };
  446. SixAxisSensorState newState = new SixAxisSensorState
  447. {
  448. Acceleration = accel,
  449. AngularVelocity = gyro,
  450. Angle = rotation,
  451. Attributes = SixAxisSensorAttribute.IsConnected
  452. };
  453. state.Orientation.AsSpan().CopyTo(newState.Direction.ToSpan());
  454. ref RingLifo<SixAxisSensorState> lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair);
  455. WriteNewSixInputEntry(ref lifo, ref newState);
  456. bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair;
  457. if (!isRightPair)
  458. {
  459. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.FullKeySixAxisSensor);
  460. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.HandheldSixAxisSensor);
  461. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyDualSixAxisSensor);
  462. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyLeftSixAxisSensor);
  463. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyRightSixAxisSensor);
  464. }
  465. if (!needUpdateRight)
  466. {
  467. SixAxisSensorState emptyState = new SixAxisSensorState();
  468. emptyState.Attributes = SixAxisSensorAttribute.IsConnected;
  469. WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState);
  470. }
  471. return needUpdateRight;
  472. }
  473. private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
  474. {
  475. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
  476. SixAxisSensorState newState = new SixAxisSensorState();
  477. newState.Attributes = SixAxisSensorAttribute.IsConnected;
  478. WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState);
  479. WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState);
  480. WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState);
  481. WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState);
  482. WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState);
  483. WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
  484. }
  485. }
  486. }