NpadDevices.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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. bool isEquals;
  278. unsafe
  279. {
  280. var aPointer = Unsafe.AsPointer(ref currentlyUsed);
  281. var bPointer = Unsafe.AsPointer(ref possiblyUnused);
  282. isEquals = aPointer == bPointer;
  283. }
  284. if (!isEquals)
  285. {
  286. NpadCommonState newState = new NpadCommonState();
  287. WriteNewInputEntry(ref possiblyUnused, ref newState);
  288. }
  289. }
  290. private void WriteNewInputEntry(ref RingLifo<NpadCommonState> lifo, ref NpadCommonState state)
  291. {
  292. ref NpadCommonState previousEntry = ref lifo.GetCurrentEntryRef();
  293. state.SamplingNumber = previousEntry.SamplingNumber + 1;
  294. lifo.Write(ref state);
  295. }
  296. private void UpdateUnusedSixInputIfNotEqual(ref RingLifo<SixAxisSensorState> currentlyUsed, ref RingLifo<SixAxisSensorState> possiblyUnused)
  297. {
  298. bool isEquals;
  299. unsafe
  300. {
  301. var aPointer = Unsafe.AsPointer(ref currentlyUsed);
  302. var bPointer = Unsafe.AsPointer(ref possiblyUnused);
  303. isEquals = aPointer == bPointer;
  304. }
  305. if (!isEquals)
  306. {
  307. SixAxisSensorState newState = new SixAxisSensorState();
  308. WriteNewSixInputEntry(ref possiblyUnused, ref newState);
  309. }
  310. }
  311. private void WriteNewSixInputEntry(ref RingLifo<SixAxisSensorState> lifo, ref SixAxisSensorState state)
  312. {
  313. ref SixAxisSensorState previousEntry = ref lifo.GetCurrentEntryRef();
  314. state.SamplingNumber = previousEntry.SamplingNumber + 1;
  315. lifo.Write(ref state);
  316. }
  317. private void UpdateInput(GamepadInput state)
  318. {
  319. if (state.PlayerId == PlayerIndex.Unknown)
  320. {
  321. return;
  322. }
  323. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
  324. if (currentNpad.StyleSet == NpadStyleTag.None)
  325. {
  326. return;
  327. }
  328. ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
  329. NpadCommonState newState = new NpadCommonState
  330. {
  331. Buttons = (NpadButton)state.Buttons,
  332. AnalogStickL = new AnalogStickState
  333. {
  334. X = state.LStick.Dx,
  335. Y = state.LStick.Dy,
  336. },
  337. AnalogStickR = new AnalogStickState
  338. {
  339. X = state.RStick.Dx,
  340. Y = state.RStick.Dy,
  341. }
  342. };
  343. newState.Attributes = NpadAttribute.IsConnected;
  344. switch (currentNpad.StyleSet)
  345. {
  346. case NpadStyleTag.Handheld:
  347. case NpadStyleTag.FullKey:
  348. newState.Attributes |= NpadAttribute.IsWired;
  349. break;
  350. case NpadStyleTag.JoyDual:
  351. newState.Attributes |= NpadAttribute.IsLeftConnected |
  352. NpadAttribute.IsRightConnected;
  353. break;
  354. case NpadStyleTag.JoyLeft:
  355. newState.Attributes |= NpadAttribute.IsLeftConnected;
  356. break;
  357. case NpadStyleTag.JoyRight:
  358. newState.Attributes |= NpadAttribute.IsRightConnected;
  359. break;
  360. }
  361. WriteNewInputEntry(ref lifo, ref newState);
  362. // Mirror data to Default layout just in case
  363. if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt))
  364. {
  365. WriteNewInputEntry(ref currentNpad.SystemExt, ref newState);
  366. }
  367. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey);
  368. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld);
  369. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual);
  370. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyLeft);
  371. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyRight);
  372. UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Palma);
  373. }
  374. private void UpdateDisconnectedInput(PlayerIndex index)
  375. {
  376. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
  377. NpadCommonState newState = new NpadCommonState();
  378. WriteNewInputEntry(ref currentNpad.FullKey, ref newState);
  379. WriteNewInputEntry(ref currentNpad.Handheld, ref newState);
  380. WriteNewInputEntry(ref currentNpad.JoyDual, ref newState);
  381. WriteNewInputEntry(ref currentNpad.JoyLeft, ref newState);
  382. WriteNewInputEntry(ref currentNpad.JoyRight, ref newState);
  383. WriteNewInputEntry(ref currentNpad.Palma, ref newState);
  384. }
  385. public void UpdateSixAxis(IList<SixAxisInput> states)
  386. {
  387. Span<bool> updated = stackalloc bool[10];
  388. for (int i = 0; i < states.Count; ++i)
  389. {
  390. updated[(int)states[i].PlayerId] = true;
  391. if (SetSixAxisState(states[i]))
  392. {
  393. i++;
  394. if (i >= states.Count)
  395. {
  396. return;
  397. }
  398. SetSixAxisState(states[i], true);
  399. }
  400. }
  401. for (int i = 0; i < updated.Length; i++)
  402. {
  403. if (!updated[i])
  404. {
  405. UpdateDisconnectedInputSixAxis((PlayerIndex)i);
  406. }
  407. }
  408. }
  409. private ref RingLifo<SixAxisSensorState> GetSixAxisSensorLifo(ref NpadInternalState npad, bool isRightPair)
  410. {
  411. switch (npad.StyleSet)
  412. {
  413. case NpadStyleTag.FullKey:
  414. return ref npad.FullKeySixAxisSensor;
  415. case NpadStyleTag.Handheld:
  416. return ref npad.HandheldSixAxisSensor;
  417. case NpadStyleTag.JoyDual:
  418. if (isRightPair)
  419. {
  420. return ref npad.JoyDualRightSixAxisSensor;
  421. }
  422. else
  423. {
  424. return ref npad.JoyDualSixAxisSensor;
  425. }
  426. case NpadStyleTag.JoyLeft:
  427. return ref npad.JoyLeftSixAxisSensor;
  428. case NpadStyleTag.JoyRight:
  429. return ref npad.JoyRightSixAxisSensor;
  430. default:
  431. throw new NotImplementedException($"{npad.StyleSet}");
  432. }
  433. }
  434. private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
  435. {
  436. if (state.PlayerId == PlayerIndex.Unknown)
  437. {
  438. return false;
  439. }
  440. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
  441. if (currentNpad.StyleSet == NpadStyleTag.None)
  442. {
  443. return false;
  444. }
  445. HidVector accel = new HidVector()
  446. {
  447. X = state.Accelerometer.X,
  448. Y = state.Accelerometer.Y,
  449. Z = state.Accelerometer.Z
  450. };
  451. HidVector gyro = new HidVector()
  452. {
  453. X = state.Gyroscope.X,
  454. Y = state.Gyroscope.Y,
  455. Z = state.Gyroscope.Z
  456. };
  457. HidVector rotation = new HidVector()
  458. {
  459. X = state.Rotation.X,
  460. Y = state.Rotation.Y,
  461. Z = state.Rotation.Z
  462. };
  463. SixAxisSensorState newState = new SixAxisSensorState
  464. {
  465. Acceleration = accel,
  466. AngularVelocity = gyro,
  467. Angle = rotation,
  468. Attributes = SixAxisSensorAttribute.IsConnected
  469. };
  470. state.Orientation.AsSpan().CopyTo(newState.Direction.ToSpan());
  471. ref RingLifo<SixAxisSensorState> lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair);
  472. WriteNewSixInputEntry(ref lifo, ref newState);
  473. bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair;
  474. if (!isRightPair)
  475. {
  476. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.FullKeySixAxisSensor);
  477. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.HandheldSixAxisSensor);
  478. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyDualSixAxisSensor);
  479. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyLeftSixAxisSensor);
  480. UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyRightSixAxisSensor);
  481. }
  482. if (!needUpdateRight && !isRightPair)
  483. {
  484. SixAxisSensorState emptyState = new SixAxisSensorState();
  485. emptyState.Attributes = SixAxisSensorAttribute.IsConnected;
  486. WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState);
  487. }
  488. return needUpdateRight;
  489. }
  490. private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
  491. {
  492. ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
  493. SixAxisSensorState newState = new SixAxisSensorState();
  494. newState.Attributes = SixAxisSensorAttribute.IsConnected;
  495. WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState);
  496. WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState);
  497. WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState);
  498. WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState);
  499. WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState);
  500. WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
  501. }
  502. public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, HidVibrationValue> dualVibrationValues)
  503. {
  504. if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue))
  505. {
  506. if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue))
  507. {
  508. leftVibrationValue = _neutralVibrationValue;
  509. }
  510. if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue))
  511. {
  512. rightVibrationValue = _neutralVibrationValue;
  513. }
  514. if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
  515. {
  516. currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
  517. LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue);
  518. }
  519. }
  520. }
  521. public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
  522. {
  523. if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
  524. {
  525. return _neutralVibrationValue;
  526. }
  527. return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
  528. }
  529. public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index)
  530. {
  531. if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue))
  532. {
  533. rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>();
  534. _device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
  535. }
  536. return rumbleQueue;
  537. }
  538. }
  539. }