No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SwitchProControllerHID.cs 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. #if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using System.Runtime.InteropServices;
  5. using UnityEngine.InputSystem.Controls;
  6. using UnityEngine.InputSystem.Layouts;
  7. using UnityEngine.InputSystem.LowLevel;
  8. using UnityEngine.InputSystem.Switch.LowLevel;
  9. using UnityEngine.InputSystem.Utilities;
  10. ////REVIEW: The Switch controller can be used to point at things; can we somehow help leverage that?
  11. namespace UnityEngine.InputSystem.Switch.LowLevel
  12. {
  13. #if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA
  14. /// <summary>
  15. /// Structure of HID input reports for Switch Pro controllers.
  16. /// </summary>
  17. [StructLayout(LayoutKind.Explicit, Size = 7)]
  18. internal struct SwitchProControllerHIDInputState : IInputStateTypeInfo
  19. {
  20. public static FourCC Format = new FourCC('S', 'P', 'V', 'S'); // Switch Pro Virtual State
  21. public FourCC format => Format;
  22. [InputControl(name = "leftStick", layout = "Stick", format = "VC2B")]
  23. [InputControl(name = "leftStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
  24. [InputControl(name = "leftStick/left", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")]
  25. [InputControl(name = "leftStick/right", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85")]
  26. [InputControl(name = "leftStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
  27. [InputControl(name = "leftStick/up", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")]
  28. [InputControl(name = "leftStick/down", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85,invert=false")]
  29. [FieldOffset(0)] public byte leftStickX;
  30. [FieldOffset(1)] public byte leftStickY;
  31. [InputControl(name = "rightStick", layout = "Stick", format = "VC2B")]
  32. [InputControl(name = "rightStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
  33. [InputControl(name = "rightStick/left", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")]
  34. [InputControl(name = "rightStick/right", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")]
  35. [InputControl(name = "rightStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
  36. [InputControl(name = "rightStick/up", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.15,clampMax=0.5,invert")]
  37. [InputControl(name = "rightStick/down", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=0.85,invert=false")]
  38. [FieldOffset(2)] public byte rightStickX;
  39. [FieldOffset(3)] public byte rightStickY;
  40. [InputControl(name = "dpad", format = "BIT", bit = 0, sizeInBits = 4)]
  41. [InputControl(name = "dpad/up", bit = (int)Button.Up)]
  42. [InputControl(name = "dpad/right", bit = (int)Button.Right)]
  43. [InputControl(name = "dpad/down", bit = (int)Button.Down)]
  44. [InputControl(name = "dpad/left", bit = (int)Button.Left)]
  45. [InputControl(name = "buttonWest", displayName = "Y", shortDisplayName = "Y", bit = (int)Button.Y, usage = "SecondaryAction")]
  46. [InputControl(name = "buttonNorth", displayName = "X", shortDisplayName = "X", bit = (int)Button.X)]
  47. [InputControl(name = "buttonSouth", displayName = "B", shortDisplayName = "B", bit = (int)Button.B, usages = new[] { "Back", "Cancel" })]
  48. [InputControl(name = "buttonEast", displayName = "A", shortDisplayName = "A", bit = (int)Button.A, usages = new[] { "PrimaryAction", "Submit" })]
  49. [InputControl(name = "leftShoulder", displayName = "L", shortDisplayName = "L", bit = (uint)Button.L)]
  50. [InputControl(name = "rightShoulder", displayName = "R", shortDisplayName = "R", bit = (uint)Button.R)]
  51. [InputControl(name = "leftStickPress", displayName = "Left Stick", bit = (uint)Button.StickL)]
  52. [InputControl(name = "rightStickPress", displayName = "Right Stick", bit = (uint)Button.StickR)]
  53. [InputControl(name = "leftTrigger", displayName = "ZL", shortDisplayName = "ZL", format = "BIT", bit = (uint)Button.ZL)]
  54. [InputControl(name = "rightTrigger", displayName = "ZR", shortDisplayName = "ZR", format = "BIT", bit = (uint)Button.ZR)]
  55. [InputControl(name = "start", displayName = "Plus", bit = (uint)Button.Plus, usage = "Menu")]
  56. [InputControl(name = "select", displayName = "Minus", bit = (uint)Button.Minus)]
  57. [FieldOffset(4)] public ushort buttons1;
  58. [InputControl(name = "capture", layout = "Button", displayName = "Capture", bit = (uint)Button.Capture - 16)]
  59. [InputControl(name = "home", layout = "Button", displayName = "Home", bit = (uint)Button.Home - 16)]
  60. [FieldOffset(6)] public byte buttons2;
  61. public enum Button
  62. {
  63. Up = 0,
  64. Right = 1,
  65. Down = 2,
  66. Left = 3,
  67. West = 4,
  68. North = 5,
  69. South = 6,
  70. East = 7,
  71. L = 8,
  72. R = 9,
  73. StickL = 10,
  74. StickR = 11,
  75. ZL = 12,
  76. ZR = 13,
  77. Plus = 14,
  78. Minus = 15,
  79. Capture = 16,
  80. Home = 17,
  81. X = North,
  82. B = South,
  83. Y = West,
  84. A = East,
  85. }
  86. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  87. public SwitchProControllerHIDInputState WithButton(Button button, bool value = true)
  88. {
  89. Set(button, value);
  90. return this;
  91. }
  92. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  93. public void Set(Button button, bool state)
  94. {
  95. Debug.Assert((int)button < 18, $"Expected button < 18");
  96. if ((int)button < 16)
  97. {
  98. var bit = (ushort)(1U << (int)button);
  99. if (state)
  100. buttons1 = (ushort)(buttons1 | bit);
  101. else
  102. buttons1 &= (ushort)~bit;
  103. }
  104. else if ((int)button < 18)
  105. {
  106. var bit = (byte)(1U << ((int)button - 16));
  107. if (state)
  108. buttons2 = (byte)(buttons2 | bit);
  109. else
  110. buttons2 &= (byte)~bit;
  111. }
  112. }
  113. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  114. public void Press(Button button)
  115. {
  116. Set(button, true);
  117. }
  118. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  119. public void Release(Button button)
  120. {
  121. Set(button, false);
  122. }
  123. }
  124. #endif
  125. }
  126. namespace UnityEngine.InputSystem.Switch
  127. {
  128. #if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION
  129. /// <summary>
  130. /// A Nintendo Switch Pro controller connected to a desktop mac/windows PC using the HID interface.
  131. /// </summary>
  132. [InputControlLayout(stateType = typeof(SwitchProControllerHIDInputState), displayName = "Switch Pro Controller")]
  133. public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEventPreProcessor
  134. {
  135. [InputControl(name = "capture", displayName = "Capture")]
  136. public ButtonControl captureButton { get; protected set; }
  137. [InputControl(name = "home", displayName = "Home")]
  138. public ButtonControl homeButton { get; protected set; }
  139. protected override void OnAdded()
  140. {
  141. base.OnAdded();
  142. captureButton = GetChildControl<ButtonControl>("capture");
  143. homeButton = GetChildControl<ButtonControl>("home");
  144. HandshakeRestart();
  145. }
  146. private static readonly SwitchMagicOutputReport.CommandIdType[] s_HandshakeSequence = new[]
  147. {
  148. SwitchMagicOutputReport.CommandIdType.Status,
  149. SwitchMagicOutputReport.CommandIdType.Handshake,
  150. SwitchMagicOutputReport.CommandIdType.Highspeed,
  151. SwitchMagicOutputReport.CommandIdType.Handshake,
  152. SwitchMagicOutputReport.CommandIdType.ForceUSB
  153. ////TODO: Should we add a step to revert back to simple interface?
  154. //// Because currently full reports don't work in old input system.
  155. };
  156. private int m_HandshakeStepIndex;
  157. private double m_HandshakeTimer;
  158. private void HandshakeRestart()
  159. {
  160. // Delay first command issue until some time into the future
  161. m_HandshakeStepIndex = -1;
  162. m_HandshakeTimer = InputRuntime.s_Instance.currentTime;
  163. }
  164. private void HandshakeTick()
  165. {
  166. const double handshakeRestartTimeout = 2.0;
  167. const double handshakeNextStepTimeout = 0.1;
  168. var currentTime = InputRuntime.s_Instance.currentTime;
  169. // There were no events for last few seconds, restart handshake
  170. if (currentTime >= m_LastUpdateTimeInternal + handshakeRestartTimeout &&
  171. currentTime >= m_HandshakeTimer + handshakeRestartTimeout)
  172. m_HandshakeStepIndex = 0;
  173. // If handshake is complete, ignore the tick.
  174. else if (m_HandshakeStepIndex + 1 >= s_HandshakeSequence.Length)
  175. return;
  176. // If we timeout, proceed to next step after some time is elapsed.
  177. else if (currentTime > m_HandshakeTimer + handshakeNextStepTimeout)
  178. m_HandshakeStepIndex++;
  179. // If we haven't timed out on handshake step, skip the tick.
  180. else
  181. return;
  182. m_HandshakeTimer = currentTime;
  183. var command = s_HandshakeSequence[m_HandshakeStepIndex];
  184. // Native backend rejects one of the commands based on size of descriptor.
  185. // So just report both at a same time.
  186. ////TODO: fix this.
  187. var commandBt = SwitchMagicOutputHIDBluetooth.Create(command);
  188. if (ExecuteCommand(ref commandBt) > 0)
  189. return;
  190. var commandUsb = SwitchMagicOutputHIDUSB.Create(command);
  191. ExecuteCommand(ref commandUsb);
  192. }
  193. public void OnNextUpdate()
  194. {
  195. HandshakeTick();
  196. }
  197. // filter out three lower bits as jitter noise
  198. internal const byte JitterMaskLow = 0b01111000;
  199. internal const byte JitterMaskHigh = 0b10000111;
  200. public unsafe void OnStateEvent(InputEventPtr eventPtr)
  201. {
  202. if (eventPtr.type == StateEvent.Type && eventPtr.stateFormat == SwitchProControllerHIDInputState.Format)
  203. {
  204. var currentState = (SwitchProControllerHIDInputState*)((byte*)currentStatePtr + m_StateBlock.byteOffset);
  205. var newState = (SwitchProControllerHIDInputState*)StateEvent.FromUnchecked(eventPtr)->state;
  206. var actuated =
  207. // we need to make device current if axes are outside of deadzone specifying hardware jitter of sticks around zero point
  208. newState->leftStickX<JitterMaskLow
  209. || newState->leftStickX> JitterMaskHigh
  210. || newState->leftStickY<JitterMaskLow
  211. || newState->leftStickY> JitterMaskHigh
  212. || newState->rightStickX<JitterMaskLow
  213. || newState->rightStickX> JitterMaskHigh
  214. || newState->rightStickY<JitterMaskLow
  215. || newState->rightStickY> JitterMaskHigh
  216. // we need to make device current if buttons state change
  217. || newState->buttons1 != currentState->buttons1
  218. || newState->buttons2 != currentState->buttons2;
  219. if (!actuated)
  220. InputSystem.s_Manager.DontMakeCurrentlyUpdatingDeviceCurrent();
  221. }
  222. InputState.Change(this, eventPtr);
  223. }
  224. public bool GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset)
  225. {
  226. return false;
  227. }
  228. public unsafe bool PreProcessEvent(InputEventPtr eventPtr)
  229. {
  230. if (eventPtr.type == DeltaStateEvent.Type)
  231. // if someone queued delta state SPVS directly, just use as-is
  232. // otherwise skip all delta state events
  233. return DeltaStateEvent.FromUnchecked(eventPtr)->stateFormat == SwitchProControllerHIDInputState.Format;
  234. // use all other non-state/non-delta-state events
  235. if (eventPtr.type != StateEvent.Type)
  236. return true;
  237. var stateEvent = StateEvent.FromUnchecked(eventPtr);
  238. var size = stateEvent->stateSizeInBytes;
  239. if (stateEvent->stateFormat == SwitchProControllerHIDInputState.Format)
  240. return true; // if someone queued SPVS directly, just use as-is
  241. if (stateEvent->stateFormat != SwitchHIDGenericInputReport.Format || size < sizeof(SwitchHIDGenericInputReport))
  242. return false; // skip unrecognized state events otherwise they will corrupt control states
  243. var genericReport = (SwitchHIDGenericInputReport*)stateEvent->state;
  244. if (genericReport->reportId == SwitchSimpleInputReport.ExpectedReportId && size >= SwitchSimpleInputReport.kSize)
  245. {
  246. var data = ((SwitchSimpleInputReport*)stateEvent->state)->ToHIDInputReport();
  247. *((SwitchProControllerHIDInputState*)stateEvent->state) = data;
  248. stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
  249. return true;
  250. }
  251. else if (genericReport->reportId == SwitchFullInputReport.ExpectedReportId && size >= SwitchFullInputReport.kSize)
  252. {
  253. var data = ((SwitchFullInputReport*)stateEvent->state)->ToHIDInputReport();
  254. *((SwitchProControllerHIDInputState*)stateEvent->state) = data;
  255. stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
  256. return true;
  257. }
  258. else if (size == 8 || size == 9) // official accessories send 8 byte reports
  259. {
  260. // On Windows HID stack we somehow get 1 byte extra prepended, so if we get 9 bytes, subtract one, see ISX-993
  261. // This is written in such way that if we fix it in backend, we wont break the package (Unity will report 8 bytes instead of 9 bytes).
  262. var bugOffset = size == 9 ? 1 : 0;
  263. var data = ((SwitchInputOnlyReport*)((byte*)stateEvent->state + bugOffset))->ToHIDInputReport();
  264. *((SwitchProControllerHIDInputState*)stateEvent->state) = data;
  265. stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
  266. return true;
  267. }
  268. else
  269. return false; // skip unrecognized reportId
  270. }
  271. [StructLayout(LayoutKind.Explicit, Size = kSize)]
  272. private struct SwitchInputOnlyReport
  273. {
  274. public const int kSize = 7;
  275. [FieldOffset(0)] public byte buttons0;
  276. [FieldOffset(1)] public byte buttons1;
  277. [FieldOffset(2)] public byte hat;
  278. [FieldOffset(3)] public byte leftX;
  279. [FieldOffset(4)] public byte leftY;
  280. [FieldOffset(5)] public byte rightX;
  281. [FieldOffset(6)] public byte rightY;
  282. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  283. public SwitchProControllerHIDInputState ToHIDInputReport()
  284. {
  285. var state = new SwitchProControllerHIDInputState
  286. {
  287. leftStickX = leftX,
  288. leftStickY = leftY,
  289. rightStickX = rightX,
  290. rightStickY = rightY
  291. };
  292. state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0);
  293. state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x02) != 0);
  294. state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x04) != 0);
  295. state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0);
  296. state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0);
  297. state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0);
  298. state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0);
  299. state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
  300. state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
  301. state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
  302. state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0);
  303. state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0);
  304. state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
  305. state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
  306. var left = false;
  307. var up = false;
  308. var right = false;
  309. var down = false;
  310. switch (hat)
  311. {
  312. case 0:
  313. up = true;
  314. break;
  315. case 1:
  316. up = true;
  317. right = true;
  318. break;
  319. case 2:
  320. right = true;
  321. break;
  322. case 3:
  323. down = true;
  324. right = true;
  325. break;
  326. case 4:
  327. down = true;
  328. break;
  329. case 5:
  330. down = true;
  331. left = true;
  332. break;
  333. case 6:
  334. left = true;
  335. break;
  336. case 7:
  337. up = true;
  338. left = true;
  339. break;
  340. }
  341. state.Set(SwitchProControllerHIDInputState.Button.Left, left);
  342. state.Set(SwitchProControllerHIDInputState.Button.Up, up);
  343. state.Set(SwitchProControllerHIDInputState.Button.Right, right);
  344. state.Set(SwitchProControllerHIDInputState.Button.Down, down);
  345. return state;
  346. }
  347. }
  348. [StructLayout(LayoutKind.Explicit, Size = kSize)]
  349. private struct SwitchSimpleInputReport
  350. {
  351. public const int kSize = 12;
  352. public const byte ExpectedReportId = 0x3f;
  353. [FieldOffset(0)] public byte reportId;
  354. [FieldOffset(1)] public byte buttons0;
  355. [FieldOffset(2)] public byte buttons1;
  356. [FieldOffset(3)] public byte hat;
  357. [FieldOffset(4)] public ushort leftX;
  358. [FieldOffset(6)] public ushort leftY;
  359. [FieldOffset(8)] public ushort rightX;
  360. [FieldOffset(10)] public ushort rightY;
  361. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  362. public SwitchProControllerHIDInputState ToHIDInputReport()
  363. {
  364. var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftX, 16, 8);
  365. var leftYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftY, 16, 8);
  366. var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightX, 16, 8);
  367. var rightYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightY, 16, 8);
  368. var state = new SwitchProControllerHIDInputState
  369. {
  370. leftStickX = leftXByte,
  371. leftStickY = leftYByte,
  372. rightStickX = rightXByte,
  373. rightStickY = rightYByte
  374. };
  375. state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x01) != 0);
  376. state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x02) != 0);
  377. state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x04) != 0);
  378. state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0);
  379. state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0);
  380. state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0);
  381. state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0);
  382. state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
  383. state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
  384. state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
  385. state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0);
  386. state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0);
  387. state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
  388. state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
  389. var left = false;
  390. var up = false;
  391. var right = false;
  392. var down = false;
  393. switch (hat)
  394. {
  395. case 0:
  396. up = true;
  397. break;
  398. case 1:
  399. up = true;
  400. right = true;
  401. break;
  402. case 2:
  403. right = true;
  404. break;
  405. case 3:
  406. down = true;
  407. right = true;
  408. break;
  409. case 4:
  410. down = true;
  411. break;
  412. case 5:
  413. down = true;
  414. left = true;
  415. break;
  416. case 6:
  417. left = true;
  418. break;
  419. case 7:
  420. up = true;
  421. left = true;
  422. break;
  423. }
  424. state.Set(SwitchProControllerHIDInputState.Button.Left, left);
  425. state.Set(SwitchProControllerHIDInputState.Button.Up, up);
  426. state.Set(SwitchProControllerHIDInputState.Button.Right, right);
  427. state.Set(SwitchProControllerHIDInputState.Button.Down, down);
  428. return state;
  429. }
  430. }
  431. [StructLayout(LayoutKind.Explicit, Size = kSize)]
  432. private struct SwitchFullInputReport
  433. {
  434. public const int kSize = 25;
  435. public const byte ExpectedReportId = 0x30;
  436. [FieldOffset(0)] public byte reportId;
  437. [FieldOffset(3)] public byte buttons0;
  438. [FieldOffset(4)] public byte buttons1;
  439. [FieldOffset(5)] public byte buttons2;
  440. [FieldOffset(6)] public byte left0;
  441. [FieldOffset(7)] public byte left1;
  442. [FieldOffset(8)] public byte left2;
  443. [FieldOffset(9)] public byte right0;
  444. [FieldOffset(10)] public byte right1;
  445. [FieldOffset(11)] public byte right2;
  446. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  447. public SwitchProControllerHIDInputState ToHIDInputReport()
  448. {
  449. ////TODO: calibration curve
  450. var leftXRaw = (uint)(left0 | ((left1 & 0x0F) << 8));
  451. var leftYRaw = (uint)(((left1 & 0xF0) >> 4) | (left2 << 4));
  452. var rightXRaw = (uint)(right0 | ((right1 & 0x0F) << 8));
  453. var rightYRaw = (uint)(((right1 & 0xF0) >> 4) | (right2 << 4));
  454. var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftXRaw, 12, 8);
  455. var leftYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftYRaw, 12, 8));
  456. var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightXRaw, 12, 8);
  457. var rightYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightYRaw, 12, 8));
  458. var state = new SwitchProControllerHIDInputState
  459. {
  460. leftStickX = leftXByte,
  461. leftStickY = leftYByte,
  462. rightStickX = rightXByte,
  463. rightStickY = rightYByte
  464. };
  465. state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0);
  466. state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x02) != 0);
  467. state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x04) != 0);
  468. state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x08) != 0);
  469. state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x40) != 0);
  470. state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
  471. state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
  472. state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
  473. state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x04) != 0);
  474. state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x08) != 0);
  475. state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
  476. state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
  477. state.Set(SwitchProControllerHIDInputState.Button.Down, (buttons2 & 0x01) != 0);
  478. state.Set(SwitchProControllerHIDInputState.Button.Up, (buttons2 & 0x02) != 0);
  479. state.Set(SwitchProControllerHIDInputState.Button.Right, (buttons2 & 0x04) != 0);
  480. state.Set(SwitchProControllerHIDInputState.Button.Left, (buttons2 & 0x08) != 0);
  481. state.Set(SwitchProControllerHIDInputState.Button.L, (buttons2 & 0x40) != 0);
  482. state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons2 & 0x80) != 0);
  483. return state;
  484. }
  485. }
  486. [StructLayout(LayoutKind.Explicit)]
  487. private struct SwitchHIDGenericInputReport
  488. {
  489. public static FourCC Format => new FourCC('H', 'I', 'D');
  490. [FieldOffset(0)] public byte reportId;
  491. }
  492. [StructLayout(LayoutKind.Explicit, Size = kSize)]
  493. internal struct SwitchMagicOutputReport
  494. {
  495. public const int kSize = 49;
  496. public const byte ExpectedReplyInputReportId = 0x81;
  497. [FieldOffset(0)] public byte reportType;
  498. [FieldOffset(1)] public byte commandId;
  499. internal enum ReportType
  500. {
  501. Magic = 0x80
  502. }
  503. public enum CommandIdType
  504. {
  505. Status = 0x01,
  506. Handshake = 0x02,
  507. Highspeed = 0x03,
  508. ForceUSB = 0x04,
  509. }
  510. }
  511. [StructLayout(LayoutKind.Explicit, Size = kSize)]
  512. internal struct SwitchMagicOutputHIDBluetooth : IInputDeviceCommandInfo
  513. {
  514. public static FourCC Type => new FourCC('H', 'I', 'D', 'O');
  515. public FourCC typeStatic => Type;
  516. public const int kSize = InputDeviceCommand.kBaseCommandSize + 49;
  517. [FieldOffset(0)] public InputDeviceCommand baseCommand;
  518. [FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report;
  519. public static SwitchMagicOutputHIDBluetooth Create(SwitchMagicOutputReport.CommandIdType type)
  520. {
  521. return new SwitchMagicOutputHIDBluetooth
  522. {
  523. baseCommand = new InputDeviceCommand(Type, kSize),
  524. report = new SwitchMagicOutputReport
  525. {
  526. reportType = (byte)SwitchMagicOutputReport.ReportType.Magic,
  527. commandId = (byte)type
  528. }
  529. };
  530. }
  531. }
  532. [StructLayout(LayoutKind.Explicit, Size = kSize)]
  533. internal struct SwitchMagicOutputHIDUSB : IInputDeviceCommandInfo
  534. {
  535. public static FourCC Type => new FourCC('H', 'I', 'D', 'O');
  536. public FourCC typeStatic => Type;
  537. public const int kSize = InputDeviceCommand.kBaseCommandSize + 64;
  538. [FieldOffset(0)] public InputDeviceCommand baseCommand;
  539. [FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report;
  540. public static SwitchMagicOutputHIDUSB Create(SwitchMagicOutputReport.CommandIdType type)
  541. {
  542. return new SwitchMagicOutputHIDUSB
  543. {
  544. baseCommand = new InputDeviceCommand(Type, kSize),
  545. report = new SwitchMagicOutputReport
  546. {
  547. reportType = (byte)SwitchMagicOutputReport.ReportType.Magic,
  548. commandId = (byte)type
  549. }
  550. };
  551. }
  552. }
  553. }
  554. #endif
  555. }
  556. #endif // UNITY_EDITOR || UNITY_SWITCH