NpadDevices.cs 26 KB

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