NpadDevices.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. using System;
  2. using System.Collections.Generic;
  3. using Ryujinx.Common;
  4. using Ryujinx.Common.Logging;
  5. using Ryujinx.Common.Memory;
  6. using Ryujinx.HLE.HOS.Kernel.Threading;
  7. namespace Ryujinx.HLE.HOS.Services.Hid
  8. {
  9. public class NpadDevices : BaseDevice
  10. {
  11. private const BatteryCharge DefaultBatteryCharge = BatteryCharge.Percent100;
  12. private const int NoMatchNotifyFrequencyMs = 2000;
  13. private int _activeCount;
  14. private long _lastNotifyTimestamp;
  15. public const int MaxControllers = 9; // Players 1-8 and Handheld
  16. private ControllerType[] _configuredTypes;
  17. private KEvent[] _styleSetUpdateEvents;
  18. private bool[] _supportedPlayers;
  19. internal NpadJoyHoldType JoyHold { get; set; }
  20. internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
  21. internal ControllerType SupportedStyleSets { get; set; }
  22. public NpadDevices(Switch device, bool active = true) : base(device, active)
  23. {
  24. _configuredTypes = new ControllerType[MaxControllers];
  25. SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair |
  26. ControllerType.JoyconLeft | ControllerType.JoyconRight |
  27. ControllerType.ProController;
  28. _supportedPlayers = new bool[MaxControllers];
  29. _supportedPlayers.AsSpan().Fill(true);
  30. _styleSetUpdateEvents = new KEvent[MaxControllers];
  31. for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
  32. {
  33. _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
  34. }
  35. _activeCount = 0;
  36. JoyHold = NpadJoyHoldType.Vertical;
  37. }
  38. internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
  39. {
  40. return ref _styleSetUpdateEvents[(int)player];
  41. }
  42. internal void ClearSupportedPlayers()
  43. {
  44. _supportedPlayers.AsSpan().Clear();
  45. }
  46. internal void SetSupportedPlayer(PlayerIndex player, bool supported = true)
  47. {
  48. _supportedPlayers[(int)player] = supported;
  49. }
  50. internal IEnumerable<PlayerIndex> GetSupportedPlayers()
  51. {
  52. for (int i = 0; i < _supportedPlayers.Length; ++i)
  53. {
  54. if (_supportedPlayers[i])
  55. {
  56. yield return (PlayerIndex)i;
  57. }
  58. }
  59. }
  60. public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex)
  61. {
  62. primaryIndex = PlayerIndex.Unknown;
  63. configuredCount = 0;
  64. for (int i = 0; i < MaxControllers; ++i)
  65. {
  66. ControllerType npad = _configuredTypes[i];
  67. if (npad == ControllerType.Handheld && _device.System.State.DockedMode)
  68. {
  69. continue;
  70. }
  71. ControllerType currentType = _device.Hid.SharedMemory.Npads[i].Header.Type;
  72. if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i])
  73. {
  74. configuredCount++;
  75. if (primaryIndex == PlayerIndex.Unknown)
  76. {
  77. primaryIndex = (PlayerIndex)i;
  78. }
  79. }
  80. }
  81. if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown)
  82. {
  83. return false;
  84. }
  85. return true;
  86. }
  87. public void Configure(params ControllerConfig[] configs)
  88. {
  89. _configuredTypes = new ControllerType[MaxControllers];
  90. for (int i = 0; i < configs.Length; ++i)
  91. {
  92. PlayerIndex player = configs[i].Player;
  93. ControllerType controllerType = configs[i].Type;
  94. if (player > PlayerIndex.Handheld)
  95. {
  96. throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld");
  97. }
  98. if (controllerType == ControllerType.Handheld)
  99. {
  100. player = PlayerIndex.Handheld;
  101. }
  102. _configuredTypes[(int)player] = controllerType;
  103. Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}");
  104. }
  105. }
  106. public void Update(IList<GamepadInput> states)
  107. {
  108. Remap();
  109. UpdateAllEntries();
  110. // Update configured inputs
  111. for (int i = 0; i < states.Count; ++i)
  112. {
  113. UpdateInput(states[i]);
  114. }
  115. }
  116. private void Remap()
  117. {
  118. // Remap/Init if necessary
  119. for (int i = 0; i < MaxControllers; ++i)
  120. {
  121. ControllerType config = _configuredTypes[i];
  122. // Remove Handheld config when Docked
  123. if (config == ControllerType.Handheld && _device.System.State.DockedMode)
  124. {
  125. config = ControllerType.None;
  126. }
  127. // Auto-remap ProController and JoyconPair
  128. if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0)
  129. {
  130. config = ControllerType.ProController;
  131. }
  132. else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0)
  133. {
  134. config = ControllerType.JoyconPair;
  135. }
  136. // Check StyleSet and PlayerSet
  137. if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i])
  138. {
  139. config = ControllerType.None;
  140. }
  141. SetupNpad((PlayerIndex)i, config);
  142. }
  143. if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs)
  144. {
  145. Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'");
  146. _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds;
  147. }
  148. }
  149. private void SetupNpad(PlayerIndex player, ControllerType type)
  150. {
  151. ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player];
  152. ControllerType oldType = controller.Header.Type;
  153. if (oldType == type)
  154. {
  155. return; // Already configured
  156. }
  157. controller = new ShMemNpad(); // Zero it
  158. if (type == ControllerType.None)
  159. {
  160. _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect
  161. _activeCount--;
  162. Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}");
  163. return;
  164. }
  165. // TODO: Allow customizing colors at config
  166. NpadStateHeader defaultHeader = new NpadStateHeader
  167. {
  168. IsHalf = false,
  169. SingleColorBody = NpadColor.BodyGray,
  170. SingleColorButtons = NpadColor.ButtonGray,
  171. LeftColorBody = NpadColor.BodyNeonBlue,
  172. LeftColorButtons = NpadColor.ButtonGray,
  173. RightColorBody = NpadColor.BodyNeonRed,
  174. RightColorButtons = NpadColor.ButtonGray
  175. };
  176. controller.SystemProperties = NpadSystemProperties.PowerInfo0Connected |
  177. NpadSystemProperties.PowerInfo1Connected |
  178. NpadSystemProperties.PowerInfo2Connected;
  179. controller.BatteryState.ToSpan().Fill(DefaultBatteryCharge);
  180. switch (type)
  181. {
  182. case ControllerType.ProController:
  183. defaultHeader.Type = ControllerType.ProController;
  184. controller.DeviceType = DeviceType.FullKey;
  185. controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
  186. NpadSystemProperties.PlusButtonCapability |
  187. NpadSystemProperties.MinusButtonCapability;
  188. break;
  189. case ControllerType.Handheld:
  190. defaultHeader.Type = ControllerType.Handheld;
  191. controller.DeviceType = DeviceType.HandheldLeft |
  192. DeviceType.HandheldRight;
  193. controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
  194. NpadSystemProperties.PlusButtonCapability |
  195. NpadSystemProperties.MinusButtonCapability;
  196. break;
  197. case ControllerType.JoyconPair:
  198. defaultHeader.Type = ControllerType.JoyconPair;
  199. controller.DeviceType = DeviceType.JoyLeft |
  200. DeviceType.JoyRight;
  201. controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
  202. NpadSystemProperties.PlusButtonCapability |
  203. NpadSystemProperties.MinusButtonCapability;
  204. break;
  205. case ControllerType.JoyconLeft:
  206. defaultHeader.Type = ControllerType.JoyconLeft;
  207. defaultHeader.IsHalf = true;
  208. controller.DeviceType = DeviceType.JoyLeft;
  209. controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented |
  210. NpadSystemProperties.MinusButtonCapability;
  211. break;
  212. case ControllerType.JoyconRight:
  213. defaultHeader.Type = ControllerType.JoyconRight;
  214. defaultHeader.IsHalf = true;
  215. controller.DeviceType = DeviceType.JoyRight;
  216. controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented |
  217. NpadSystemProperties.PlusButtonCapability;
  218. break;
  219. case ControllerType.Pokeball:
  220. defaultHeader.Type = ControllerType.Pokeball;
  221. controller.DeviceType = DeviceType.Palma;
  222. break;
  223. }
  224. controller.Header = defaultHeader;
  225. _styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
  226. _activeCount++;
  227. Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}");
  228. }
  229. private static NpadLayoutsIndex ControllerTypeToNpadLayout(ControllerType controllerType)
  230. => controllerType switch
  231. {
  232. ControllerType.ProController => NpadLayoutsIndex.ProController,
  233. ControllerType.Handheld => NpadLayoutsIndex.Handheld,
  234. ControllerType.JoyconPair => NpadLayoutsIndex.JoyDual,
  235. ControllerType.JoyconLeft => NpadLayoutsIndex.JoyLeft,
  236. ControllerType.JoyconRight => NpadLayoutsIndex.JoyRight,
  237. ControllerType.Pokeball => NpadLayoutsIndex.Pokeball,
  238. _ => NpadLayoutsIndex.SystemExternal
  239. };
  240. private void UpdateInput(GamepadInput state)
  241. {
  242. if (state.PlayerId == PlayerIndex.Unknown)
  243. {
  244. return;
  245. }
  246. ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId];
  247. if (currentNpad.Header.Type == ControllerType.None)
  248. {
  249. return;
  250. }
  251. ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToNpadLayout(currentNpad.Header.Type)];
  252. ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
  253. currentEntry.Buttons = state.Buttons;
  254. currentEntry.LStickX = state.LStick.Dx;
  255. currentEntry.LStickY = state.LStick.Dy;
  256. currentEntry.RStickX = state.RStick.Dx;
  257. currentEntry.RStickY = state.RStick.Dy;
  258. // Mirror data to Default layout just in case
  259. ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal];
  260. mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry;
  261. }
  262. private static SixAxixLayoutsIndex ControllerTypeToSixAxisLayout(ControllerType controllerType)
  263. => controllerType switch
  264. {
  265. ControllerType.ProController => SixAxixLayoutsIndex.ProController,
  266. ControllerType.Handheld => SixAxixLayoutsIndex.Handheld,
  267. ControllerType.JoyconPair => SixAxixLayoutsIndex.JoyDualLeft,
  268. ControllerType.JoyconLeft => SixAxixLayoutsIndex.JoyLeft,
  269. ControllerType.JoyconRight => SixAxixLayoutsIndex.JoyRight,
  270. ControllerType.Pokeball => SixAxixLayoutsIndex.Pokeball,
  271. _ => SixAxixLayoutsIndex.SystemExternal
  272. };
  273. public void UpdateSixAxis(IList<SixAxisInput> states)
  274. {
  275. for (int i = 0; i < states.Count; ++i)
  276. {
  277. if (SetSixAxisState(states[i]))
  278. {
  279. i++;
  280. if (i >= states.Count)
  281. {
  282. return;
  283. }
  284. SetSixAxisState(states[i], true);
  285. }
  286. }
  287. }
  288. private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
  289. {
  290. if (state.PlayerId == PlayerIndex.Unknown)
  291. {
  292. return false;
  293. }
  294. ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId];
  295. if (currentNpad.Header.Type == ControllerType.None)
  296. {
  297. return false;
  298. }
  299. HidVector accel = new HidVector()
  300. {
  301. X = state.Accelerometer.X,
  302. Y = state.Accelerometer.Y,
  303. Z = state.Accelerometer.Z
  304. };
  305. HidVector gyro = new HidVector()
  306. {
  307. X = state.Gyroscope.X,
  308. Y = state.Gyroscope.Y,
  309. Z = state.Gyroscope.Z
  310. };
  311. HidVector rotation = new HidVector()
  312. {
  313. X = state.Rotation.X,
  314. Y = state.Rotation.Y,
  315. Z = state.Rotation.Z
  316. };
  317. ref NpadSixAxis currentLayout = ref currentNpad.Sixaxis[(int)ControllerTypeToSixAxisLayout(currentNpad.Header.Type) + (isRightPair ? 1 : 0)];
  318. ref SixAxisState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
  319. int previousEntryIndex = (int)(currentLayout.Header.LatestEntry == 0 ?
  320. currentLayout.Header.MaxEntryIndex : currentLayout.Header.LatestEntry - 1);
  321. ref SixAxisState previousEntry = ref currentLayout.Entries[previousEntryIndex];
  322. currentEntry.Accelerometer = accel;
  323. currentEntry.Gyroscope = gyro;
  324. currentEntry.Rotations = rotation;
  325. unsafe
  326. {
  327. for (int i = 0; i < 9; i++)
  328. {
  329. currentEntry.Orientation[i] = state.Orientation[i];
  330. }
  331. }
  332. return currentNpad.Header.Type == ControllerType.JoyconPair && !isRightPair;
  333. }
  334. private void UpdateAllEntries()
  335. {
  336. ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
  337. for (int i = 0; i < controllers.Length; ++i)
  338. {
  339. ref Array7<NpadLayout> layouts = ref controllers[i].Layouts;
  340. for (int l = 0; l < layouts.Length; ++l)
  341. {
  342. ref NpadLayout currentLayout = ref layouts[l];
  343. int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
  344. ref NpadState currentEntry = ref currentLayout.Entries[currentIndex];
  345. NpadState previousEntry = currentLayout.Entries[previousIndex];
  346. currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
  347. currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
  348. if (controllers[i].Header.Type == ControllerType.None)
  349. {
  350. continue;
  351. }
  352. currentEntry.ConnectionState = NpadConnectionState.ControllerStateConnected;
  353. switch (controllers[i].Header.Type)
  354. {
  355. case ControllerType.Handheld:
  356. case ControllerType.ProController:
  357. currentEntry.ConnectionState |= NpadConnectionState.ControllerStateWired;
  358. break;
  359. case ControllerType.JoyconPair:
  360. currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected |
  361. NpadConnectionState.JoyRightConnected;
  362. break;
  363. case ControllerType.JoyconLeft:
  364. currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected;
  365. break;
  366. case ControllerType.JoyconRight:
  367. currentEntry.ConnectionState |= NpadConnectionState.JoyRightConnected;
  368. break;
  369. }
  370. }
  371. ref Array6<NpadSixAxis> sixaxis = ref controllers[i].Sixaxis;
  372. for (int l = 0; l < sixaxis.Length; ++l)
  373. {
  374. ref NpadSixAxis currentLayout = ref sixaxis[l];
  375. int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
  376. ref SixAxisState currentEntry = ref currentLayout.Entries[currentIndex];
  377. SixAxisState previousEntry = currentLayout.Entries[previousIndex];
  378. currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
  379. currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
  380. currentEntry._unknown2 = 1;
  381. }
  382. }
  383. }
  384. }
  385. }