613 lines
28 KiB
C#
613 lines
28 KiB
C#
|
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION
|
||
|
using System;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using UnityEngine.InputSystem.Controls;
|
||
|
using UnityEngine.InputSystem.Layouts;
|
||
|
using UnityEngine.InputSystem.LowLevel;
|
||
|
using UnityEngine.InputSystem.Switch.LowLevel;
|
||
|
using UnityEngine.InputSystem.Utilities;
|
||
|
|
||
|
////REVIEW: The Switch controller can be used to point at things; can we somehow help leverage that?
|
||
|
|
||
|
namespace UnityEngine.InputSystem.Switch.LowLevel
|
||
|
{
|
||
|
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA
|
||
|
/// <summary>
|
||
|
/// Structure of HID input reports for Switch Pro controllers.
|
||
|
/// </summary>
|
||
|
[StructLayout(LayoutKind.Explicit, Size = 7)]
|
||
|
internal struct SwitchProControllerHIDInputState : IInputStateTypeInfo
|
||
|
{
|
||
|
public static FourCC Format = new FourCC('S', 'P', 'V', 'S'); // Switch Pro Virtual State
|
||
|
|
||
|
public FourCC format => Format;
|
||
|
|
||
|
[InputControl(name = "leftStick", layout = "Stick", format = "VC2B")]
|
||
|
[InputControl(name = "leftStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
|
||
|
[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")]
|
||
|
[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")]
|
||
|
[InputControl(name = "leftStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
|
||
|
[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")]
|
||
|
[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")]
|
||
|
[FieldOffset(0)] public byte leftStickX;
|
||
|
[FieldOffset(1)] public byte leftStickY;
|
||
|
|
||
|
[InputControl(name = "rightStick", layout = "Stick", format = "VC2B")]
|
||
|
[InputControl(name = "rightStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
|
||
|
[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")]
|
||
|
[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")]
|
||
|
[InputControl(name = "rightStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0.15,normalizeMax=0.85,normalizeZero=0.5")]
|
||
|
[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")]
|
||
|
[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")]
|
||
|
[FieldOffset(2)] public byte rightStickX;
|
||
|
[FieldOffset(3)] public byte rightStickY;
|
||
|
|
||
|
[InputControl(name = "dpad", format = "BIT", bit = 0, sizeInBits = 4)]
|
||
|
[InputControl(name = "dpad/up", bit = (int)Button.Up)]
|
||
|
[InputControl(name = "dpad/right", bit = (int)Button.Right)]
|
||
|
[InputControl(name = "dpad/down", bit = (int)Button.Down)]
|
||
|
[InputControl(name = "dpad/left", bit = (int)Button.Left)]
|
||
|
[InputControl(name = "buttonWest", displayName = "Y", shortDisplayName = "Y", bit = (int)Button.Y, usage = "SecondaryAction")]
|
||
|
[InputControl(name = "buttonNorth", displayName = "X", shortDisplayName = "X", bit = (int)Button.X)]
|
||
|
[InputControl(name = "buttonSouth", displayName = "B", shortDisplayName = "B", bit = (int)Button.B, usage = "Back")]
|
||
|
[InputControl(name = "buttonEast", displayName = "A", shortDisplayName = "A", bit = (int)Button.A, usage = "PrimaryAction")]
|
||
|
[InputControl(name = "leftShoulder", displayName = "L", shortDisplayName = "L", bit = (uint)Button.L)]
|
||
|
[InputControl(name = "rightShoulder", displayName = "R", shortDisplayName = "R", bit = (uint)Button.R)]
|
||
|
[InputControl(name = "leftStickPress", displayName = "Left Stick", bit = (uint)Button.StickL)]
|
||
|
[InputControl(name = "rightStickPress", displayName = "Right Stick", bit = (uint)Button.StickR)]
|
||
|
[InputControl(name = "leftTrigger", displayName = "ZL", shortDisplayName = "ZL", format = "BIT", bit = (uint)Button.ZL)]
|
||
|
[InputControl(name = "rightTrigger", displayName = "ZR", shortDisplayName = "ZR", format = "BIT", bit = (uint)Button.ZR)]
|
||
|
[InputControl(name = "start", displayName = "Plus", bit = (uint)Button.Plus, usage = "Menu")]
|
||
|
[InputControl(name = "select", displayName = "Minus", bit = (uint)Button.Minus)]
|
||
|
[FieldOffset(4)] public ushort buttons1;
|
||
|
|
||
|
[InputControl(name = "capture", layout = "Button", displayName = "Capture", bit = (uint)Button.Capture - 16)]
|
||
|
[InputControl(name = "home", layout = "Button", displayName = "Home", bit = (uint)Button.Home - 16)]
|
||
|
[FieldOffset(6)] public byte buttons2;
|
||
|
|
||
|
public enum Button
|
||
|
{
|
||
|
Up = 0,
|
||
|
Right = 1,
|
||
|
Down = 2,
|
||
|
Left = 3,
|
||
|
|
||
|
West = 4,
|
||
|
North = 5,
|
||
|
South = 6,
|
||
|
East = 7,
|
||
|
|
||
|
L = 8,
|
||
|
R = 9,
|
||
|
StickL = 10,
|
||
|
StickR = 11,
|
||
|
|
||
|
ZL = 12,
|
||
|
ZR = 13,
|
||
|
Plus = 14,
|
||
|
Minus = 15,
|
||
|
Capture = 16,
|
||
|
Home = 17,
|
||
|
|
||
|
X = North,
|
||
|
B = South,
|
||
|
Y = West,
|
||
|
A = East,
|
||
|
}
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public SwitchProControllerHIDInputState WithButton(Button button, bool value = true)
|
||
|
{
|
||
|
Set(button, value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public void Set(Button button, bool state)
|
||
|
{
|
||
|
Debug.Assert((int)button < 18, $"Expected button < 18");
|
||
|
if ((int)button < 16)
|
||
|
{
|
||
|
var bit = (ushort)(1U << (int)button);
|
||
|
if (state)
|
||
|
buttons1 = (ushort)(buttons1 | bit);
|
||
|
else
|
||
|
buttons1 &= (ushort)~bit;
|
||
|
}
|
||
|
else if ((int)button < 18)
|
||
|
{
|
||
|
var bit = (byte)(1U << ((int)button - 16));
|
||
|
if (state)
|
||
|
buttons2 = (byte)(buttons2 | bit);
|
||
|
else
|
||
|
buttons2 &= (byte)~bit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public void Press(Button button)
|
||
|
{
|
||
|
Set(button, true);
|
||
|
}
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public void Release(Button button)
|
||
|
{
|
||
|
Set(button, false);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
namespace UnityEngine.InputSystem.Switch
|
||
|
{
|
||
|
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_WSA || PACKAGE_DOCS_GENERATION
|
||
|
/// <summary>
|
||
|
/// A Nintendo Switch Pro controller connected to a desktop mac/windows PC using the HID interface.
|
||
|
/// </summary>
|
||
|
[InputControlLayout(stateType = typeof(SwitchProControllerHIDInputState), displayName = "Switch Pro Controller")]
|
||
|
public class SwitchProControllerHID : Gamepad, IInputStateCallbackReceiver, IEventPreProcessor
|
||
|
{
|
||
|
[InputControl(name = "capture", displayName = "Capture")]
|
||
|
public ButtonControl captureButton { get; protected set; }
|
||
|
|
||
|
[InputControl(name = "home", displayName = "Home")]
|
||
|
public ButtonControl homeButton { get; protected set; }
|
||
|
|
||
|
protected override void OnAdded()
|
||
|
{
|
||
|
base.OnAdded();
|
||
|
|
||
|
captureButton = GetChildControl<ButtonControl>("capture");
|
||
|
homeButton = GetChildControl<ButtonControl>("home");
|
||
|
|
||
|
HandshakeRestart();
|
||
|
}
|
||
|
|
||
|
private static readonly SwitchMagicOutputReport.CommandIdType[] s_HandshakeSequence = new[]
|
||
|
{
|
||
|
SwitchMagicOutputReport.CommandIdType.Status,
|
||
|
SwitchMagicOutputReport.CommandIdType.Handshake,
|
||
|
SwitchMagicOutputReport.CommandIdType.Highspeed,
|
||
|
SwitchMagicOutputReport.CommandIdType.Handshake,
|
||
|
SwitchMagicOutputReport.CommandIdType.ForceUSB
|
||
|
////TODO: Should we add a step to revert back to simple interface?
|
||
|
//// Because currently full reports don't work in old input system.
|
||
|
};
|
||
|
|
||
|
private int m_HandshakeStepIndex;
|
||
|
private double m_HandshakeTimer;
|
||
|
|
||
|
private void HandshakeRestart()
|
||
|
{
|
||
|
// Delay first command issue until some time into the future
|
||
|
m_HandshakeStepIndex = -1;
|
||
|
m_HandshakeTimer = InputRuntime.s_Instance.currentTime;
|
||
|
}
|
||
|
|
||
|
private void HandshakeTick()
|
||
|
{
|
||
|
const double handshakeRestartTimeout = 2.0;
|
||
|
const double handshakeNextStepTimeout = 0.1;
|
||
|
|
||
|
var currentTime = InputRuntime.s_Instance.currentTime;
|
||
|
|
||
|
// There were no events for last few seconds, restart handshake
|
||
|
if (currentTime >= m_LastUpdateTimeInternal + handshakeRestartTimeout &&
|
||
|
currentTime >= m_HandshakeTimer + handshakeRestartTimeout)
|
||
|
m_HandshakeStepIndex = 0;
|
||
|
// If handshake is complete, ignore the tick.
|
||
|
else if (m_HandshakeStepIndex + 1 >= s_HandshakeSequence.Length)
|
||
|
return;
|
||
|
// If we timeout, proceed to next step after some time is elapsed.
|
||
|
else if (currentTime > m_HandshakeTimer + handshakeNextStepTimeout)
|
||
|
m_HandshakeStepIndex++;
|
||
|
// If we haven't timed out on handshake step, skip the tick.
|
||
|
else
|
||
|
return;
|
||
|
|
||
|
m_HandshakeTimer = currentTime;
|
||
|
|
||
|
var command = s_HandshakeSequence[m_HandshakeStepIndex];
|
||
|
|
||
|
// Native backend rejects one of the commands based on size of descriptor.
|
||
|
// So just report both at a same time.
|
||
|
////TODO: fix this.
|
||
|
var commandBt = SwitchMagicOutputHIDBluetooth.Create(command);
|
||
|
if (ExecuteCommand(ref commandBt) > 0)
|
||
|
return;
|
||
|
|
||
|
var commandUsb = SwitchMagicOutputHIDUSB.Create(command);
|
||
|
ExecuteCommand(ref commandUsb);
|
||
|
}
|
||
|
|
||
|
public void OnNextUpdate()
|
||
|
{
|
||
|
HandshakeTick();
|
||
|
}
|
||
|
|
||
|
public void OnStateEvent(InputEventPtr eventPtr)
|
||
|
{
|
||
|
InputState.Change(this, eventPtr);
|
||
|
}
|
||
|
|
||
|
public bool GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public unsafe bool PreProcessEvent(InputEventPtr eventPtr)
|
||
|
{
|
||
|
if (eventPtr.type == DeltaStateEvent.Type)
|
||
|
// if someone queued delta state SPVS directly, just use as-is
|
||
|
// otherwise skip all delta state events
|
||
|
return DeltaStateEvent.FromUnchecked(eventPtr)->stateFormat == SwitchProControllerHIDInputState.Format;
|
||
|
|
||
|
// use all other non-state/non-delta-state events
|
||
|
if (eventPtr.type != StateEvent.Type)
|
||
|
return true;
|
||
|
|
||
|
var stateEvent = StateEvent.FromUnchecked(eventPtr);
|
||
|
var size = stateEvent->stateSizeInBytes;
|
||
|
|
||
|
if (stateEvent->stateFormat == SwitchProControllerHIDInputState.Format)
|
||
|
return true; // if someone queued SPVS directly, just use as-is
|
||
|
|
||
|
if (stateEvent->stateFormat != SwitchHIDGenericInputReport.Format || size < sizeof(SwitchHIDGenericInputReport))
|
||
|
return false; // skip unrecognized state events otherwise they will corrupt control states
|
||
|
|
||
|
var genericReport = (SwitchHIDGenericInputReport*)stateEvent->state;
|
||
|
if (genericReport->reportId == SwitchSimpleInputReport.ExpectedReportId && size >= SwitchSimpleInputReport.kSize)
|
||
|
{
|
||
|
var data = ((SwitchSimpleInputReport*)stateEvent->state)->ToHIDInputReport();
|
||
|
*((SwitchProControllerHIDInputState*)stateEvent->state) = data;
|
||
|
stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
|
||
|
return true;
|
||
|
}
|
||
|
else if (genericReport->reportId == SwitchFullInputReport.ExpectedReportId && size >= SwitchFullInputReport.kSize)
|
||
|
{
|
||
|
var data = ((SwitchFullInputReport*)stateEvent->state)->ToHIDInputReport();
|
||
|
*((SwitchProControllerHIDInputState*)stateEvent->state) = data;
|
||
|
stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
|
||
|
return true;
|
||
|
}
|
||
|
else if (size == 8 || size == 9) // official accessories send 8 byte reports
|
||
|
{
|
||
|
// On Windows HID stack we somehow get 1 byte extra prepended, so if we get 9 bytes, subtract one, see ISX-993
|
||
|
// 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).
|
||
|
var bugOffset = size == 9 ? 1 : 0;
|
||
|
var data = ((SwitchInputOnlyReport*)((byte*)stateEvent->state + bugOffset))->ToHIDInputReport();
|
||
|
*((SwitchProControllerHIDInputState*)stateEvent->state) = data;
|
||
|
stateEvent->stateFormat = SwitchProControllerHIDInputState.Format;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
return false; // skip unrecognized reportId
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit, Size = kSize)]
|
||
|
private struct SwitchInputOnlyReport
|
||
|
{
|
||
|
public const int kSize = 7;
|
||
|
|
||
|
[FieldOffset(0)] public byte buttons0;
|
||
|
[FieldOffset(1)] public byte buttons1;
|
||
|
[FieldOffset(2)] public byte hat;
|
||
|
[FieldOffset(3)] public byte leftX;
|
||
|
[FieldOffset(4)] public byte leftY;
|
||
|
[FieldOffset(5)] public byte rightX;
|
||
|
[FieldOffset(6)] public byte rightY;
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public SwitchProControllerHIDInputState ToHIDInputReport()
|
||
|
{
|
||
|
var state = new SwitchProControllerHIDInputState
|
||
|
{
|
||
|
leftStickX = leftX,
|
||
|
leftStickY = leftY,
|
||
|
rightStickX = rightX,
|
||
|
rightStickY = rightY
|
||
|
};
|
||
|
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
|
||
|
|
||
|
var left = false;
|
||
|
var up = false;
|
||
|
var right = false;
|
||
|
var down = false;
|
||
|
|
||
|
switch (hat)
|
||
|
{
|
||
|
case 0:
|
||
|
up = true;
|
||
|
break;
|
||
|
case 1:
|
||
|
up = true;
|
||
|
right = true;
|
||
|
break;
|
||
|
case 2:
|
||
|
right = true;
|
||
|
break;
|
||
|
case 3:
|
||
|
down = true;
|
||
|
right = true;
|
||
|
break;
|
||
|
case 4:
|
||
|
down = true;
|
||
|
break;
|
||
|
case 5:
|
||
|
down = true;
|
||
|
left = true;
|
||
|
break;
|
||
|
case 6:
|
||
|
left = true;
|
||
|
break;
|
||
|
case 7:
|
||
|
up = true;
|
||
|
left = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Left, left);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Up, up);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Right, right);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Down, down);
|
||
|
return state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit, Size = kSize)]
|
||
|
private struct SwitchSimpleInputReport
|
||
|
{
|
||
|
public const int kSize = 12;
|
||
|
public const byte ExpectedReportId = 0x3f;
|
||
|
|
||
|
[FieldOffset(0)] public byte reportId;
|
||
|
[FieldOffset(1)] public byte buttons0;
|
||
|
[FieldOffset(2)] public byte buttons1;
|
||
|
[FieldOffset(3)] public byte hat;
|
||
|
[FieldOffset(4)] public ushort leftX;
|
||
|
[FieldOffset(6)] public ushort leftY;
|
||
|
[FieldOffset(8)] public ushort rightX;
|
||
|
[FieldOffset(10)] public ushort rightY;
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public SwitchProControllerHIDInputState ToHIDInputReport()
|
||
|
{
|
||
|
var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftX, 16, 8);
|
||
|
var leftYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftY, 16, 8);
|
||
|
var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightX, 16, 8);
|
||
|
var rightYByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightY, 16, 8);
|
||
|
|
||
|
var state = new SwitchProControllerHIDInputState
|
||
|
{
|
||
|
leftStickX = leftXByte,
|
||
|
leftStickY = leftYByte,
|
||
|
rightStickX = rightXByte,
|
||
|
rightStickY = rightYByte
|
||
|
};
|
||
|
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.L, (buttons0 & 0x10) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x20) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons0 & 0x40) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
|
||
|
|
||
|
var left = false;
|
||
|
var up = false;
|
||
|
var right = false;
|
||
|
var down = false;
|
||
|
|
||
|
switch (hat)
|
||
|
{
|
||
|
case 0:
|
||
|
up = true;
|
||
|
break;
|
||
|
case 1:
|
||
|
up = true;
|
||
|
right = true;
|
||
|
break;
|
||
|
case 2:
|
||
|
right = true;
|
||
|
break;
|
||
|
case 3:
|
||
|
down = true;
|
||
|
right = true;
|
||
|
break;
|
||
|
case 4:
|
||
|
down = true;
|
||
|
break;
|
||
|
case 5:
|
||
|
down = true;
|
||
|
left = true;
|
||
|
break;
|
||
|
case 6:
|
||
|
left = true;
|
||
|
break;
|
||
|
case 7:
|
||
|
up = true;
|
||
|
left = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Left, left);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Up, up);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Right, right);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Down, down);
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit, Size = kSize)]
|
||
|
private struct SwitchFullInputReport
|
||
|
{
|
||
|
public const int kSize = 25;
|
||
|
public const byte ExpectedReportId = 0x30;
|
||
|
|
||
|
[FieldOffset(0)] public byte reportId;
|
||
|
[FieldOffset(3)] public byte buttons0;
|
||
|
[FieldOffset(4)] public byte buttons1;
|
||
|
[FieldOffset(5)] public byte buttons2;
|
||
|
[FieldOffset(6)] public byte left0;
|
||
|
[FieldOffset(7)] public byte left1;
|
||
|
[FieldOffset(8)] public byte left2;
|
||
|
[FieldOffset(9)] public byte right0;
|
||
|
[FieldOffset(10)] public byte right1;
|
||
|
[FieldOffset(11)] public byte right2;
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
public SwitchProControllerHIDInputState ToHIDInputReport()
|
||
|
{
|
||
|
////TODO: calibration curve
|
||
|
|
||
|
var leftXRaw = (uint)(left0 | ((left1 & 0x0F) << 8));
|
||
|
var leftYRaw = (uint)(((left1 & 0xF0) >> 4) | (left2 << 4));
|
||
|
var rightXRaw = (uint)(right0 | ((right1 & 0x0F) << 8));
|
||
|
var rightYRaw = (uint)(((right1 & 0xF0) >> 4) | (right2 << 4));
|
||
|
|
||
|
var leftXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftXRaw, 12, 8);
|
||
|
var leftYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(leftYRaw, 12, 8));
|
||
|
var rightXByte = (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightXRaw, 12, 8);
|
||
|
var rightYByte = (byte)(0xff - (byte)NumberHelpers.RemapUIntBitsToNormalizeFloatToUIntBits(rightYRaw, 12, 8));
|
||
|
|
||
|
var state = new SwitchProControllerHIDInputState
|
||
|
{
|
||
|
leftStickX = leftXByte,
|
||
|
leftStickY = leftYByte,
|
||
|
rightStickX = rightXByte,
|
||
|
rightStickY = rightYByte
|
||
|
};
|
||
|
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Y, (buttons0 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.X, (buttons0 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.B, (buttons0 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.A, (buttons0 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.R, (buttons0 & 0x40) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.ZR, (buttons0 & 0x80) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Minus, (buttons1 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Plus, (buttons1 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.StickR, (buttons1 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.StickL, (buttons1 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Home, (buttons1 & 0x10) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Capture, (buttons1 & 0x20) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Down, (buttons2 & 0x01) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Up, (buttons2 & 0x02) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Right, (buttons2 & 0x04) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.Left, (buttons2 & 0x08) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.L, (buttons2 & 0x40) != 0);
|
||
|
state.Set(SwitchProControllerHIDInputState.Button.ZL, (buttons2 & 0x80) != 0);
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit)]
|
||
|
private struct SwitchHIDGenericInputReport
|
||
|
{
|
||
|
public static FourCC Format => new FourCC('H', 'I', 'D');
|
||
|
|
||
|
[FieldOffset(0)] public byte reportId;
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit, Size = kSize)]
|
||
|
internal struct SwitchMagicOutputReport
|
||
|
{
|
||
|
public const int kSize = 49;
|
||
|
|
||
|
public const byte ExpectedReplyInputReportId = 0x81;
|
||
|
|
||
|
[FieldOffset(0)] public byte reportType;
|
||
|
[FieldOffset(1)] public byte commandId;
|
||
|
|
||
|
internal enum ReportType
|
||
|
{
|
||
|
Magic = 0x80
|
||
|
}
|
||
|
|
||
|
public enum CommandIdType
|
||
|
{
|
||
|
Status = 0x01,
|
||
|
Handshake = 0x02,
|
||
|
Highspeed = 0x03,
|
||
|
ForceUSB = 0x04,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit, Size = kSize)]
|
||
|
internal struct SwitchMagicOutputHIDBluetooth : IInputDeviceCommandInfo
|
||
|
{
|
||
|
public static FourCC Type => new FourCC('H', 'I', 'D', 'O');
|
||
|
public FourCC typeStatic => Type;
|
||
|
|
||
|
public const int kSize = InputDeviceCommand.kBaseCommandSize + 49;
|
||
|
|
||
|
[FieldOffset(0)] public InputDeviceCommand baseCommand;
|
||
|
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report;
|
||
|
|
||
|
public static SwitchMagicOutputHIDBluetooth Create(SwitchMagicOutputReport.CommandIdType type)
|
||
|
{
|
||
|
return new SwitchMagicOutputHIDBluetooth
|
||
|
{
|
||
|
baseCommand = new InputDeviceCommand(Type, kSize),
|
||
|
report = new SwitchMagicOutputReport
|
||
|
{
|
||
|
reportType = (byte)SwitchMagicOutputReport.ReportType.Magic,
|
||
|
commandId = (byte)type
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Explicit, Size = kSize)]
|
||
|
internal struct SwitchMagicOutputHIDUSB : IInputDeviceCommandInfo
|
||
|
{
|
||
|
public static FourCC Type => new FourCC('H', 'I', 'D', 'O');
|
||
|
public FourCC typeStatic => Type;
|
||
|
|
||
|
public const int kSize = InputDeviceCommand.kBaseCommandSize + 64;
|
||
|
|
||
|
[FieldOffset(0)] public InputDeviceCommand baseCommand;
|
||
|
[FieldOffset(InputDeviceCommand.kBaseCommandSize + 0)] public SwitchMagicOutputReport report;
|
||
|
|
||
|
public static SwitchMagicOutputHIDUSB Create(SwitchMagicOutputReport.CommandIdType type)
|
||
|
{
|
||
|
return new SwitchMagicOutputHIDUSB
|
||
|
{
|
||
|
baseCommand = new InputDeviceCommand(Type, kSize),
|
||
|
report = new SwitchMagicOutputReport
|
||
|
{
|
||
|
reportType = (byte)SwitchMagicOutputReport.ReportType.Magic,
|
||
|
commandId = (byte)type
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
#endif // UNITY_EDITOR || UNITY_SWITCH
|