NpadDevices.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. using System;
  2. using Ryujinx.Common.Logging;
  3. using Ryujinx.HLE.HOS.Kernel.Threading;
  4. namespace Ryujinx.HLE.HOS.Services.Hid
  5. {
  6. public class NpadDevices : BaseDevice
  7. {
  8. internal NpadJoyHoldType JoyHold = NpadJoyHoldType.Vertical;
  9. internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
  10. private enum FilterState
  11. {
  12. Unconfigured = 0,
  13. Configured = 1,
  14. Accepted = 2
  15. }
  16. private struct NpadConfig
  17. {
  18. public ControllerType ConfiguredType;
  19. public FilterState State;
  20. }
  21. private const int _maxControllers = 9; // Players1-8 and Handheld
  22. private NpadConfig[] _configuredNpads;
  23. private ControllerType _supportedStyleSets = ControllerType.ProController |
  24. ControllerType.JoyconPair |
  25. ControllerType.JoyconLeft |
  26. ControllerType.JoyconRight |
  27. ControllerType.Handheld;
  28. public ControllerType SupportedStyleSets
  29. {
  30. get => _supportedStyleSets;
  31. set
  32. {
  33. if (_supportedStyleSets != value) // Deal with spamming
  34. {
  35. _supportedStyleSets = value;
  36. MatchControllers();
  37. }
  38. }
  39. }
  40. public PlayerIndex PrimaryController { get; set; } = PlayerIndex.Unknown;
  41. private KEvent[] _styleSetUpdateEvents;
  42. private static readonly Array3<BatteryCharge> _fullBattery;
  43. public NpadDevices(Switch device, bool active = true) : base(device, active)
  44. {
  45. _configuredNpads = new NpadConfig[_maxControllers];
  46. _styleSetUpdateEvents = new KEvent[_maxControllers];
  47. for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
  48. {
  49. _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
  50. }
  51. _fullBattery[0] = _fullBattery[1] = _fullBattery[2] = BatteryCharge.Percent100;
  52. }
  53. public void AddControllers(params ControllerConfig[] configs)
  54. {
  55. for (int i = 0; i < configs.Length; ++i)
  56. {
  57. PlayerIndex player = configs[i].Player;
  58. ControllerType controllerType = configs[i].Type;
  59. if (player > PlayerIndex.Handheld)
  60. {
  61. throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld");
  62. }
  63. if (controllerType == ControllerType.Handheld)
  64. {
  65. player = PlayerIndex.Handheld;
  66. }
  67. _configuredNpads[(int)player] = new NpadConfig { ConfiguredType = controllerType, State = FilterState.Configured };
  68. }
  69. MatchControllers();
  70. }
  71. private void MatchControllers()
  72. {
  73. PrimaryController = PlayerIndex.Unknown;
  74. for (int i = 0; i < _configuredNpads.Length; ++i)
  75. {
  76. ref NpadConfig config = ref _configuredNpads[i];
  77. if (config.State == FilterState.Unconfigured)
  78. {
  79. continue; // Ignore unconfigured
  80. }
  81. if ((config.ConfiguredType & _supportedStyleSets) == 0)
  82. {
  83. Logger.PrintWarning(LogClass.Hid, $"ControllerType {config.ConfiguredType} (connected to {(PlayerIndex)i}) not supported by game. Removing...");
  84. config.State = FilterState.Configured;
  85. _device.Hid.SharedMemory.Npads[i] = new ShMemNpad(); // Zero it
  86. continue;
  87. }
  88. InitController((PlayerIndex)i, config.ConfiguredType);
  89. }
  90. // Couldn't find any matching configuration. Reassign to something that works.
  91. if (PrimaryController == PlayerIndex.Unknown)
  92. {
  93. ControllerType[] npadsTypeList = (ControllerType[])Enum.GetValues(typeof(ControllerType));
  94. // Skip None Type
  95. for (int i = 1; i < npadsTypeList.Length; ++i)
  96. {
  97. ControllerType controllerType = npadsTypeList[i];
  98. if ((controllerType & _supportedStyleSets) != 0)
  99. {
  100. Logger.PrintWarning(LogClass.Hid, $"No matching controllers found. Reassigning input as ControllerType {controllerType}...");
  101. InitController(controllerType == ControllerType.Handheld ? PlayerIndex.Handheld : PlayerIndex.Player1, controllerType);
  102. return;
  103. }
  104. }
  105. Logger.PrintError(LogClass.Hid, "Couldn't find any appropriate controller.");
  106. }
  107. }
  108. internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
  109. {
  110. return ref _styleSetUpdateEvents[(int)player];
  111. }
  112. private void InitController(PlayerIndex player, ControllerType type)
  113. {
  114. if (type == ControllerType.Handheld)
  115. {
  116. player = PlayerIndex.Handheld;
  117. }
  118. ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player];
  119. controller = new ShMemNpad(); // Zero it
  120. // TODO: Allow customizing colors at config
  121. NpadStateHeader defaultHeader = new NpadStateHeader
  122. {
  123. IsHalf = false,
  124. SingleColorBody = NpadColor.BodyGray,
  125. SingleColorButtons = NpadColor.ButtonGray,
  126. LeftColorBody = NpadColor.BodyNeonBlue,
  127. LeftColorButtons = NpadColor.ButtonGray,
  128. RightColorBody = NpadColor.BodyNeonRed,
  129. RightColorButtons = NpadColor.ButtonGray
  130. };
  131. controller.SystemProperties = NpadSystemProperties.PowerInfo0Connected |
  132. NpadSystemProperties.PowerInfo1Connected |
  133. NpadSystemProperties.PowerInfo2Connected;
  134. controller.BatteryState = _fullBattery;
  135. switch (type)
  136. {
  137. case ControllerType.ProController:
  138. defaultHeader.Type = ControllerType.ProController;
  139. controller.DeviceType = DeviceType.FullKey;
  140. controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
  141. NpadSystemProperties.PlusButtonCapability |
  142. NpadSystemProperties.MinusButtonCapability;
  143. break;
  144. case ControllerType.Handheld:
  145. defaultHeader.Type = ControllerType.Handheld;
  146. controller.DeviceType = DeviceType.HandheldLeft |
  147. DeviceType.HandheldRight;
  148. controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
  149. NpadSystemProperties.PlusButtonCapability |
  150. NpadSystemProperties.MinusButtonCapability;
  151. break;
  152. case ControllerType.JoyconPair:
  153. defaultHeader.Type = ControllerType.JoyconPair;
  154. controller.DeviceType = DeviceType.JoyLeft |
  155. DeviceType.JoyRight;
  156. controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
  157. NpadSystemProperties.PlusButtonCapability |
  158. NpadSystemProperties.MinusButtonCapability;
  159. break;
  160. case ControllerType.JoyconLeft:
  161. defaultHeader.Type = ControllerType.JoyconLeft;
  162. defaultHeader.IsHalf = true;
  163. controller.DeviceType = DeviceType.JoyLeft;
  164. controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented |
  165. NpadSystemProperties.MinusButtonCapability;
  166. break;
  167. case ControllerType.JoyconRight:
  168. defaultHeader.Type = ControllerType.JoyconRight;
  169. defaultHeader.IsHalf = true;
  170. controller.DeviceType = DeviceType.JoyRight;
  171. controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented |
  172. NpadSystemProperties.PlusButtonCapability;
  173. break;
  174. case ControllerType.Pokeball:
  175. defaultHeader.Type = ControllerType.Pokeball;
  176. controller.DeviceType = DeviceType.Palma;
  177. break;
  178. }
  179. controller.Header = defaultHeader;
  180. if (PrimaryController == PlayerIndex.Unknown)
  181. {
  182. PrimaryController = player;
  183. }
  184. _configuredNpads[(int)player].State = FilterState.Accepted;
  185. _styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
  186. Logger.PrintInfo(LogClass.Hid, $"Connected ControllerType {type} to PlayerIndex {player}");
  187. }
  188. private static NpadLayoutsIndex ControllerTypeToLayout(ControllerType controllerType)
  189. => controllerType switch
  190. {
  191. ControllerType.ProController => NpadLayoutsIndex.ProController,
  192. ControllerType.Handheld => NpadLayoutsIndex.Handheld,
  193. ControllerType.JoyconPair => NpadLayoutsIndex.JoyDual,
  194. ControllerType.JoyconLeft => NpadLayoutsIndex.JoyLeft,
  195. ControllerType.JoyconRight => NpadLayoutsIndex.JoyRight,
  196. ControllerType.Pokeball => NpadLayoutsIndex.Pokeball,
  197. _ => NpadLayoutsIndex.SystemExternal
  198. };
  199. public void SetGamepadsInput(params GamepadInput[] states)
  200. {
  201. UpdateAllEntries();
  202. for (int i = 0; i < states.Length; ++i)
  203. {
  204. SetGamepadState(states[i].PlayerId, states[i].Buttons, states[i].LStick, states[i].RStick);
  205. }
  206. }
  207. private void SetGamepadState(PlayerIndex player, ControllerKeys buttons,
  208. JoystickPosition leftJoystick, JoystickPosition rightJoystick)
  209. {
  210. if (player == PlayerIndex.Auto)
  211. {
  212. player = PrimaryController;
  213. }
  214. if (player == PlayerIndex.Unknown)
  215. {
  216. return;
  217. }
  218. if (_configuredNpads[(int)player].State != FilterState.Accepted)
  219. {
  220. return;
  221. }
  222. ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)player];
  223. ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToLayout(currentNpad.Header.Type)];
  224. ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
  225. currentEntry.Buttons = buttons;
  226. currentEntry.LStickX = leftJoystick.Dx;
  227. currentEntry.LStickY = leftJoystick.Dy;
  228. currentEntry.RStickX = rightJoystick.Dx;
  229. currentEntry.RStickY = rightJoystick.Dy;
  230. // Mirror data to Default layout just in case
  231. ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal];
  232. mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry;
  233. }
  234. private void UpdateAllEntries()
  235. {
  236. ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
  237. for (int i = 0; i < controllers.Length; ++i)
  238. {
  239. ref Array7<NpadLayout> layouts = ref controllers[i].Layouts;
  240. for (int l = 0; l < layouts.Length; ++l)
  241. {
  242. ref NpadLayout currentLayout = ref layouts[l];
  243. int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
  244. ref NpadState currentEntry = ref currentLayout.Entries[currentIndex];
  245. NpadState previousEntry = currentLayout.Entries[previousIndex];
  246. currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
  247. currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
  248. if (controllers[i].Header.Type == ControllerType.None)
  249. {
  250. continue;
  251. }
  252. currentEntry.ConnectionState = NpadConnectionState.ControllerStateConnected;
  253. switch (controllers[i].Header.Type)
  254. {
  255. case ControllerType.Handheld:
  256. case ControllerType.ProController:
  257. currentEntry.ConnectionState |= NpadConnectionState.ControllerStateWired;
  258. break;
  259. case ControllerType.JoyconPair:
  260. currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected |
  261. NpadConnectionState.JoyRightConnected;
  262. break;
  263. case ControllerType.JoyconLeft:
  264. currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected;
  265. break;
  266. case ControllerType.JoyconRight:
  267. currentEntry.ConnectionState |= NpadConnectionState.JoyRightConnected;
  268. break;
  269. }
  270. }
  271. }
  272. }
  273. }
  274. }