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 3 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
61 changes: 60 additions & 1 deletion 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 All @@ -23,7 +24,7 @@ internal class XInputTests : CoreTestsFixture
[Category("Devices")]
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
[TestCase("Xbox One Wired Controller", "Microsoft", "HID", "XboxGamepadMacOS")]
[TestCase("Xbox One Wireless Controller", "Microsoft", "HID", "XboxOneGampadMacOSWireless")]
[TestCase("Xbox Series Wireless Controller", "Microsoft", "HID", "XboxGamepadMacOSWireless")]
#endif
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_WSA
[TestCase(null, null, "XInput", "XInputControllerWindows")]
Expand Down Expand Up @@ -148,6 +149,64 @@ 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, 0x0B20, 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. The usual difference
// is around the select and start button bits.
// 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
1 change: 1 addition & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ however, it has to be formatted properly to pass verification tests.
- Fixed multiple `OnScreenStick` Components that does not work together when using them simultaneously in isolation mode. [ISXB-813](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-813)
- Fixed an issue in input actions editor window that caused certain fields in custom input composite bindings to require multiple clicks to action / focus. [ISXB-1171](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1171)
- Fixed an editor/player hang in `InputSystemUIInputModule` due to an infinite loop. This was caused by the assumption that `RemovePointerAtIndex` would _always_ successfully remove the pointer, which is not the case with touch based pointers. [ISXB-1258](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1258)
- Fixed wrong Xbox Series S|X and Xbox One wireless controllers "View" button mapping on macOS by expanding device PID and VID matching. [ISXB-1264](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1264)

### Changed
- Changed location of the link xml file (code stripping rules), from a temporary directory to the project Library folder (ISX-2140).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ internal void OnGUI()
EditorGUILayout.LabelField("Product", m_Device.description.product);
if (!string.IsNullOrEmpty(m_Device.description.manufacturer))
EditorGUILayout.LabelField("Manufacturer", m_Device.description.manufacturer);
if (!string.IsNullOrEmpty(m_Device.description.version))
EditorGUILayout.LabelField("Version", m_Device.description.version);
if (!string.IsNullOrEmpty(m_Device.description.serial))
EditorGUILayout.LabelField("Serial Number", m_Device.description.serial);
EditorGUILayout.LabelField("Device ID", m_DeviceIdString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ static class XInputSupport
{
public static void Initialize()
{
void RegisterXboxOneWirelessFromProductAndVendorID(int vendorId, int productId)
{
InputSystem.RegisterLayout<XboxOneGampadMacOSWireless>(
matches: new InputDeviceMatcher().WithInterface("HID")
.WithProduct("Xbox.*Wireless Controller")
.WithCapability("vendorId", vendorId)
.WithCapability("productId", productId));
}

// Base layout for Xbox-style gamepad.
InputSystem.RegisterLayout<XInputController>();

Expand All @@ -28,7 +37,24 @@ public static void Initialize()
InputSystem.RegisterLayout<XboxGamepadMacOS>(
matches: new InputDeviceMatcher().WithInterface("HID")
.WithProduct("Xbox.*Wired Controller"));
InputSystem.RegisterLayout<XboxOneGampadMacOSWireless>(

// Matching older Xbox One 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
// Based on devices from this list
// https://github.com/mdqinc/SDL_GameControllerDB/blob/a453871de2e0e2484544514c6c080e1e916d620c/gamecontrollerdb.txt#L798C1-L806C1
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02B0);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02D1);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02DD);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02E0);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02E3);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02EA);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02FD);
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02FF);

// 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