Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: WIP Set macOS Xbox gamepad based on PID/VID #2097

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Assets/Tests/InputSystem/APIVerificationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ internal static bool IgnoreTypeForDocsByName(string fullName)
#if UNITY_EDITOR_OSX
fullName == typeof(UnityEngine.InputSystem.XInput.XboxGamepadMacOS).FullName ||
fullName == typeof(UnityEngine.InputSystem.XInput.XboxOneGampadMacOSWireless).FullName ||
fullName == typeof(UnityEngine.InputSystem.XInput.XboxGamepadMacOSWireless).FullName ||
#endif
#if UNITY_EDITOR_WIN
fullName == typeof(UnityEngine.InputSystem.XInput.XInputControllerWindows).FullName ||
Expand Down
58 changes: 58 additions & 0 deletions Assets/Tests/InputSystem/Plugins/XInputTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.Utilities;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.HID;
using UnityEngine.InputSystem.Processors;

#if UNITY_EDITOR_WIN || UNITY_EDITOR_OSX || UNITY_XBOXONE || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN
Expand Down Expand Up @@ -148,6 +149,63 @@ public void Devices_SupportXboxControllerOnOSX()
AssertButtonPress(gamepad, new XInputControllerOSXState().WithButton(XInputControllerOSXState.Button.Select), gamepad.selectButton);
}

[TestCase(0x045e, 0x02e0, 16, 11)] // Xbox One Wireless Controller
[TestCase(0x045e, 0x02fd, 10, 11)] // Xbox Series X|S Wireless Controller
// This test is used to establish the correct button map layout based on the PID and VIDs
// If the layout is changed this test will fail and will need to be adapted either with a new device/layout or
// a new button map.
public void Devices_SupportWirelessXboxOneAndSeriesControllerOnOSX(int vendorId, int productId, int selectBit, int startBit)
{
// Fake a real Xbox Wireless Controller
var xboxGamepad = InputSystem.AddDevice(new InputDeviceDescription
{
interfaceName = "HID",
product = "Xbox Wireless Controller",
manufacturer = "Microsoft",
capabilities = new HID.HIDDeviceDescriptor
{
vendorId = vendorId,
productId = productId,
}.ToJson()
});


Assert.That(xboxGamepad, Is.AssignableTo<XInputController>());

var gamepad = (XInputController)xboxGamepad;
Assert.That(gamepad.selectButton.isPressed, Is.False);

// Check if the controller is an Xbox One from a particular type where we know the select and start buttons are
// different
if (productId == 0x02e0)
{
Assert.That(xboxGamepad, Is.AssignableTo<XboxOneGampadMacOSWireless>());

InputSystem.QueueStateEvent(gamepad,
new XInputControllerWirelessOSXState
{
buttons = (uint)(1 << selectBit |
1 << startBit)
});
InputSystem.Update();
}
else
{
Assert.That(xboxGamepad, Is.AssignableTo<XboxGamepadMacOSWireless>());

InputSystem.QueueStateEvent(gamepad,
new XInputControllerWirelessOSXState
{
buttons = (uint)(1 << selectBit |
1 << startBit)
});
InputSystem.Update();
}

Assert.That(gamepad.selectButton.isPressed);
Assert.That(gamepad.startButton.isPressed);
}

// Disable tests in standalone builds from 2022.1+ see UUM-19622
#if !UNITY_STANDALONE_OSX || !TEMP_DISABLE_STANDALONE_OSX_XINPUT_TEST
[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,18 @@ public static void Initialize()
InputSystem.RegisterLayout<XboxGamepadMacOS>(
matches: new InputDeviceMatcher().WithInterface("HID")
.WithProduct("Xbox.*Wired Controller"));

// Matching older controllers that have different View and Share buttons than the newer Xbox Series
// controllers.
// Reported inhttps://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1264
InputSystem.RegisterLayout<XboxOneGampadMacOSWireless>(
matches: new InputDeviceMatcher().WithInterface("HID")
.WithCapability("vendorId", 0x045E)
.WithCapability("productId", 0x02E0));

// This layout is for all the other Xbox One or Series controllers that have the same View and Share buttons.
// Reported in https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-385
InputSystem.RegisterLayout<XboxGamepadMacOSWireless>(
matches: new InputDeviceMatcher().WithInterface("HID")
.WithProduct("Xbox.*Wireless Controller"));
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ internal struct XInputControllerWirelessOSXState : IInputStateTypeInfo
public enum Button
{
Start = 11,
Select = 10,
Select = 16,
LeftThumbstickPress = 13,
RightThumbstickPress = 14,
LeftShoulder = 6,
Expand Down Expand Up @@ -194,6 +194,98 @@ public XInputControllerWirelessOSXState WithDpad(byte value)
leftStickY = 32767
};
}

[StructLayout(LayoutKind.Explicit)]
internal struct XInputControllerWirelessOSXStateV2 : IInputStateTypeInfo
{
public static FourCC kFormat => new FourCC('H', 'I', 'D');

public enum Button
{
Start = 11,
Select = 10,
LeftThumbstickPress = 13,
RightThumbstickPress = 14,
LeftShoulder = 6,
RightShoulder = 7,
A = 0,
B = 1,
X = 3,
Y = 4,
}
[FieldOffset(0)]
private byte padding;

[InputControl(name = "leftStick", layout = "Stick", format = "VC2S")]
[InputControl(name = "leftStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "leftStick/left", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "leftStick/right", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")]
[InputControl(name = "leftStick/y", offset = 2, format = "USHT", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "leftStick/up", offset = 2, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "leftStick/down", offset = 2, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1,invert=false")]
[FieldOffset(1)] public ushort leftStickX;
[FieldOffset(3)] public ushort leftStickY;

[InputControl(name = "rightStick", layout = "Stick", format = "VC2S")]
[InputControl(name = "rightStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "rightStick/left", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "rightStick/right", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")]
[InputControl(name = "rightStick/y", offset = 2, format = "USHT", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "rightStick/up", offset = 2, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "rightStick/down", offset = 2, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1,invert=false")]
[FieldOffset(5)] public ushort rightStickX;
[FieldOffset(7)] public ushort rightStickY;

[InputControl(name = "leftTrigger", format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=0.01560998")]
[FieldOffset(9)] public ushort leftTrigger;
[InputControl(name = "rightTrigger", format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=0.01560998")]
[FieldOffset(11)] public ushort rightTrigger;

[InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)]
[InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=8,maxValue=2,nullValue=0,wrapAtValue=9", bit = 0, sizeInBits = 4)]
[InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=2,maxValue=4", bit = 0, sizeInBits = 4)]
[InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=4,maxValue=6", bit = 0, sizeInBits = 4)]
[InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=6, maxValue=8", bit = 0, sizeInBits = 4)]
[FieldOffset(13)]
public byte dpad;

[InputControl(name = "start", bit = (uint)Button.Start, displayName = "Start")]
[InputControl(name = "select", bit = (uint)Button.Select, displayName = "Select")]
[InputControl(name = "leftStickPress", bit = (uint)Button.LeftThumbstickPress)]
[InputControl(name = "rightStickPress", bit = (uint)Button.RightThumbstickPress)]
[InputControl(name = "leftShoulder", bit = (uint)Button.LeftShoulder)]
[InputControl(name = "rightShoulder", bit = (uint)Button.RightShoulder)]
[InputControl(name = "buttonSouth", bit = (uint)Button.A, displayName = "A")]
[InputControl(name = "buttonEast", bit = (uint)Button.B, displayName = "B")]
[InputControl(name = "buttonWest", bit = (uint)Button.X, displayName = "X")]
[InputControl(name = "buttonNorth", bit = (uint)Button.Y, displayName = "Y")]

[FieldOffset(14)]
public uint buttons;

public FourCC format => kFormat;

public XInputControllerWirelessOSXStateV2 WithButton(Button button)
{
Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask");
buttons |= 1U << (int)button;
return this;
}

public XInputControllerWirelessOSXStateV2 WithDpad(byte value)
{
dpad = value;
return this;
}

public static XInputControllerWirelessOSXStateV2 defaultState => new XInputControllerWirelessOSXStateV2
{
rightStickX = 32767,
rightStickY = 32767,
leftStickX = 32767,
leftStickY = 32767
};
}
}
namespace UnityEngine.InputSystem.XInput
{
Expand Down Expand Up @@ -223,5 +315,22 @@ public class XboxGamepadMacOS : XInputController
public class XboxOneGampadMacOSWireless : XInputController
{
}

/// <summary>
/// A wireless Xbox One or Xbox Series Gamepad connected to a macOS computer.
/// </summary>
/// <remarks>
/// An Xbox One/Series wireless gamepad connected to a mac using Bluetooth.
/// The reason this is different from <see cref="XboxOneGampadMacOSWireless"/> is that some Xbox Controllers have
/// different View and Share button bit mapping. So we need to use a different layout for those controllers. It seems
/// that some Xbox One and Xbox Series controller share the same mappings so this combines them all.
/// Note: only the latest version of Xbox One wireless gamepads support Bluetooth. Older models only work
/// with a proprietary Xbox wireless protocol, and cannot be used on a Mac.
/// Unlike wired controllers, bluetooth-cabable Xbox One controllers do not need a custom driver to work on macOS.
/// </remarks>
[InputControlLayout(displayName = "Wireless Xbox Controller", stateType = typeof(XInputControllerWirelessOSXStateV2), hideInUI = true)]
public class XboxGamepadMacOSWireless : XInputController
{
}
}
#endif // UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
Loading