NpadDevices.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. using Ryujinx.Common;
  2. using Ryujinx.Common.Logging;
  3. using Ryujinx.Common.Memory;
  4. using Ryujinx.HLE.HOS.Kernel.Threading;
  5. using System;
  6. using System.Collections.Generic;
  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 void UpdateAllEntries()
  263. {
  264. ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
  265. for (int i = 0; i < controllers.Length; ++i)
  266. {
  267. ref Array7<NpadLayout> layouts = ref controllers[i].Layouts;
  268. for (int l = 0; l < layouts.Length; ++l)
  269. {
  270. ref NpadLayout currentLayout = ref layouts[l];
  271. int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
  272. ref NpadState currentEntry = ref currentLayout.Entries[currentIndex];
  273. NpadState previousEntry = currentLayout.Entries[previousIndex];
  274. currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
  275. currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
  276. if (controllers[i].Header.Type == ControllerType.None)
  277. {
  278. continue;
  279. }
  280. currentEntry.ConnectionState = NpadConnectionState.ControllerStateConnected;
  281. switch (controllers[i].Header.Type)
  282. {
  283. case ControllerType.Handheld:
  284. case ControllerType.ProController:
  285. currentEntry.ConnectionState |= NpadConnectionState.ControllerStateWired;
  286. break;
  287. case ControllerType.JoyconPair:
  288. currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected |
  289. NpadConnectionState.JoyRightConnected;
  290. break;
  291. case ControllerType.JoyconLeft:
  292. currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected;
  293. break;
  294. case ControllerType.JoyconRight:
  295. currentEntry.ConnectionState |= NpadConnectionState.JoyRightConnected;
  296. break;
  297. }
  298. }
  299. }
  300. }
  301. }
  302. }