NpadDevices.cs 25 KB

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