Skip to content

Commit 70cf605

Browse files
ekcohjfreire-unity
authored andcommitted
Set different Xbox button mapping based on VID/PID
Only for macOS since Windows uses XInput. Uses some PIDs sourced from SDL2 database. Adds automated test to establish layout mapping. We will need to update this based on reports from users in the future.
1 parent 52185c4 commit 70cf605

File tree

4 files changed

+198
-3
lines changed

4 files changed

+198
-3
lines changed

Assets/Tests/InputSystem/APIVerificationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ internal static bool IgnoreTypeForDocsByName(string fullName)
183183
#if UNITY_EDITOR_OSX
184184
fullName == typeof(UnityEngine.InputSystem.XInput.XboxGamepadMacOS).FullName ||
185185
fullName == typeof(UnityEngine.InputSystem.XInput.XboxOneGampadMacOSWireless).FullName ||
186+
fullName == typeof(UnityEngine.InputSystem.XInput.XboxGamepadMacOSWireless).FullName ||
186187
#endif
187188
#if UNITY_EDITOR_WIN
188189
fullName == typeof(UnityEngine.InputSystem.XInput.XInputControllerWindows).FullName ||

Assets/Tests/InputSystem/Plugins/XInputTests.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using UnityEngine.InputSystem.Layouts;
66
using UnityEngine.InputSystem.Utilities;
77
using System.Runtime.InteropServices;
8+
using UnityEngine.InputSystem.HID;
89
using UnityEngine.InputSystem.Processors;
910

1011
#if UNITY_EDITOR_WIN || UNITY_EDITOR_OSX || UNITY_XBOXONE || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN
@@ -23,7 +24,7 @@ internal class XInputTests : CoreTestsFixture
2324
[Category("Devices")]
2425
#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
2526
[TestCase("Xbox One Wired Controller", "Microsoft", "HID", "XboxGamepadMacOS")]
26-
[TestCase("Xbox One Wireless Controller", "Microsoft", "HID", "XboxOneGampadMacOSWireless")]
27+
[TestCase("Xbox Series Wireless Controller", "Microsoft", "HID", "XboxGamepadMacOSWireless")]
2728
#endif
2829
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN || UNITY_WSA
2930
[TestCase(null, null, "XInput", "XInputControllerWindows")]
@@ -148,6 +149,64 @@ public void Devices_SupportXboxControllerOnOSX()
148149
AssertButtonPress(gamepad, new XInputControllerOSXState().WithButton(XInputControllerOSXState.Button.Select), gamepad.selectButton);
149150
}
150151

152+
[TestCase(0x045E, 0x02E0, 16, 11)] // Xbox One Wireless Controller
153+
[TestCase(0x045E, 0x0B20, 10, 11)] // Xbox Series X|S Wireless Controller
154+
// This test is used to establish the correct button map layout based on the PID and VIDs. The usual difference
155+
// is around the select and start button bits.
156+
// If the layout is changed this test will fail and will need to be adapted either with a new device/layout or
157+
// a new button map.
158+
public void Devices_SupportWirelessXboxOneAndSeriesControllerOnOSX(int vendorId, int productId, int selectBit, int startBit)
159+
{
160+
// Fake a real Xbox Wireless Controller
161+
var xboxGamepad = InputSystem.AddDevice(new InputDeviceDescription
162+
{
163+
interfaceName = "HID",
164+
product = "Xbox Wireless Controller",
165+
manufacturer = "Microsoft",
166+
capabilities = new HID.HIDDeviceDescriptor
167+
{
168+
vendorId = vendorId,
169+
productId = productId,
170+
}.ToJson()
171+
});
172+
173+
174+
Assert.That(xboxGamepad, Is.AssignableTo<XInputController>());
175+
176+
var gamepad = (XInputController)xboxGamepad;
177+
Assert.That(gamepad.selectButton.isPressed, Is.False);
178+
179+
// Check if the controller is an Xbox One from a particular type where we know the select and start buttons are
180+
// different
181+
if (productId == 0x02e0)
182+
{
183+
Assert.That(xboxGamepad, Is.AssignableTo<XboxOneGampadMacOSWireless>());
184+
185+
InputSystem.QueueStateEvent(gamepad,
186+
new XInputControllerWirelessOSXState
187+
{
188+
buttons = (uint)(1 << selectBit |
189+
1 << startBit)
190+
});
191+
InputSystem.Update();
192+
}
193+
else
194+
{
195+
Assert.That(xboxGamepad, Is.AssignableTo<XboxGamepadMacOSWireless>());
196+
197+
InputSystem.QueueStateEvent(gamepad,
198+
new XInputControllerWirelessOSXState
199+
{
200+
buttons = (uint)(1 << selectBit |
201+
1 << startBit)
202+
});
203+
InputSystem.Update();
204+
}
205+
206+
Assert.That(gamepad.selectButton.isPressed);
207+
Assert.That(gamepad.startButton.isPressed);
208+
}
209+
151210
// Disable tests in standalone builds from 2022.1+ see UUM-19622
152211
#if !UNITY_STANDALONE_OSX || !TEMP_DISABLE_STANDALONE_OSX_XINPUT_TEST
153212
[Test]

Packages/com.unity.inputsystem/InputSystem/Plugins/XInput/XInputSupport.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ static class XInputSupport
1616
{
1717
public static void Initialize()
1818
{
19+
void RegisterXboxOneWirelessFromProductAndVendorID(int vendorId, int productId)
20+
{
21+
InputSystem.RegisterLayout<XboxOneGampadMacOSWireless>(
22+
matches: new InputDeviceMatcher().WithInterface("HID")
23+
.WithProduct("Xbox.*Wireless Controller")
24+
.WithCapability("vendorId", vendorId)
25+
.WithCapability("productId", productId));
26+
}
27+
1928
// Base layout for Xbox-style gamepad.
2029
InputSystem.RegisterLayout<XInputController>();
2130

@@ -28,7 +37,24 @@ public static void Initialize()
2837
InputSystem.RegisterLayout<XboxGamepadMacOS>(
2938
matches: new InputDeviceMatcher().WithInterface("HID")
3039
.WithProduct("Xbox.*Wired Controller"));
31-
InputSystem.RegisterLayout<XboxOneGampadMacOSWireless>(
40+
41+
// Matching older Xbox One controllers that have different View and Share buttons than the newer Xbox Series
42+
// controllers.
43+
// Reported inhttps://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-1264
44+
// Based on devices from this list
45+
// https://github.com/mdqinc/SDL_GameControllerDB/blob/a453871de2e0e2484544514c6c080e1e916d620c/gamecontrollerdb.txt#L798C1-L806C1
46+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02B0);
47+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02D1);
48+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02DD);
49+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02E0);
50+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02E3);
51+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02EA);
52+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02FD);
53+
RegisterXboxOneWirelessFromProductAndVendorID(0x045E, 0x02FF);
54+
55+
// This layout is for all the other Xbox One or Series controllers that have the same View and Share buttons.
56+
// Reported in https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-385
57+
InputSystem.RegisterLayout<XboxGamepadMacOSWireless>(
3258
matches: new InputDeviceMatcher().WithInterface("HID")
3359
.WithProduct("Xbox.*Wireless Controller"));
3460
#endif

Packages/com.unity.inputsystem/InputSystem/Plugins/XInput/XboxGamepadMacOS.cs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ internal struct XInputControllerWirelessOSXState : IInputStateTypeInfo
111111
public enum Button
112112
{
113113
Start = 11,
114-
Select = 10,
114+
Select = 16,
115115
LeftThumbstickPress = 13,
116116
RightThumbstickPress = 14,
117117
LeftShoulder = 6,
@@ -194,6 +194,98 @@ public XInputControllerWirelessOSXState WithDpad(byte value)
194194
leftStickY = 32767
195195
};
196196
}
197+
198+
[StructLayout(LayoutKind.Explicit)]
199+
internal struct XInputControllerWirelessOSXStateV2 : IInputStateTypeInfo
200+
{
201+
public static FourCC kFormat => new FourCC('H', 'I', 'D');
202+
203+
public enum Button
204+
{
205+
Start = 11,
206+
Select = 10,
207+
LeftThumbstickPress = 13,
208+
RightThumbstickPress = 14,
209+
LeftShoulder = 6,
210+
RightShoulder = 7,
211+
A = 0,
212+
B = 1,
213+
X = 3,
214+
Y = 4,
215+
}
216+
[FieldOffset(0)]
217+
private byte padding;
218+
219+
[InputControl(name = "leftStick", layout = "Stick", format = "VC2S")]
220+
[InputControl(name = "leftStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
221+
[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")]
222+
[InputControl(name = "leftStick/right", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")]
223+
[InputControl(name = "leftStick/y", offset = 2, format = "USHT", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
224+
[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")]
225+
[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")]
226+
[FieldOffset(1)] public ushort leftStickX;
227+
[FieldOffset(3)] public ushort leftStickY;
228+
229+
[InputControl(name = "rightStick", layout = "Stick", format = "VC2S")]
230+
[InputControl(name = "rightStick/x", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
231+
[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")]
232+
[InputControl(name = "rightStick/right", offset = 0, format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=1,clampMin=0.5,clampMax=1")]
233+
[InputControl(name = "rightStick/y", offset = 2, format = "USHT", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
234+
[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")]
235+
[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")]
236+
[FieldOffset(5)] public ushort rightStickX;
237+
[FieldOffset(7)] public ushort rightStickY;
238+
239+
[InputControl(name = "leftTrigger", format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=0.01560998")]
240+
[FieldOffset(9)] public ushort leftTrigger;
241+
[InputControl(name = "rightTrigger", format = "USHT", parameters = "normalize,normalizeMin=0,normalizeMax=0.01560998")]
242+
[FieldOffset(11)] public ushort rightTrigger;
243+
244+
[InputControl(name = "dpad", format = "BIT", layout = "Dpad", sizeInBits = 4, defaultState = 8)]
245+
[InputControl(name = "dpad/up", format = "BIT", layout = "DiscreteButton", parameters = "minValue=8,maxValue=2,nullValue=0,wrapAtValue=9", bit = 0, sizeInBits = 4)]
246+
[InputControl(name = "dpad/right", format = "BIT", layout = "DiscreteButton", parameters = "minValue=2,maxValue=4", bit = 0, sizeInBits = 4)]
247+
[InputControl(name = "dpad/down", format = "BIT", layout = "DiscreteButton", parameters = "minValue=4,maxValue=6", bit = 0, sizeInBits = 4)]
248+
[InputControl(name = "dpad/left", format = "BIT", layout = "DiscreteButton", parameters = "minValue=6, maxValue=8", bit = 0, sizeInBits = 4)]
249+
[FieldOffset(13)]
250+
public byte dpad;
251+
252+
[InputControl(name = "start", bit = (uint)Button.Start, displayName = "Start")]
253+
[InputControl(name = "select", bit = (uint)Button.Select, displayName = "Select")]
254+
[InputControl(name = "leftStickPress", bit = (uint)Button.LeftThumbstickPress)]
255+
[InputControl(name = "rightStickPress", bit = (uint)Button.RightThumbstickPress)]
256+
[InputControl(name = "leftShoulder", bit = (uint)Button.LeftShoulder)]
257+
[InputControl(name = "rightShoulder", bit = (uint)Button.RightShoulder)]
258+
[InputControl(name = "buttonSouth", bit = (uint)Button.A, displayName = "A")]
259+
[InputControl(name = "buttonEast", bit = (uint)Button.B, displayName = "B")]
260+
[InputControl(name = "buttonWest", bit = (uint)Button.X, displayName = "X")]
261+
[InputControl(name = "buttonNorth", bit = (uint)Button.Y, displayName = "Y")]
262+
263+
[FieldOffset(14)]
264+
public uint buttons;
265+
266+
public FourCC format => kFormat;
267+
268+
public XInputControllerWirelessOSXStateV2 WithButton(Button button)
269+
{
270+
Debug.Assert((int)button < 32, $"Expected button < 32, so we fit into the 32 bit wide bitmask");
271+
buttons |= 1U << (int)button;
272+
return this;
273+
}
274+
275+
public XInputControllerWirelessOSXStateV2 WithDpad(byte value)
276+
{
277+
dpad = value;
278+
return this;
279+
}
280+
281+
public static XInputControllerWirelessOSXStateV2 defaultState => new XInputControllerWirelessOSXStateV2
282+
{
283+
rightStickX = 32767,
284+
rightStickY = 32767,
285+
leftStickX = 32767,
286+
leftStickY = 32767
287+
};
288+
}
197289
}
198290
namespace UnityEngine.InputSystem.XInput
199291
{
@@ -223,5 +315,22 @@ public class XboxGamepadMacOS : XInputController
223315
public class XboxOneGampadMacOSWireless : XInputController
224316
{
225317
}
318+
319+
/// <summary>
320+
/// A wireless Xbox One or Xbox Series Gamepad connected to a macOS computer.
321+
/// </summary>
322+
/// <remarks>
323+
/// An Xbox One/Series wireless gamepad connected to a mac using Bluetooth.
324+
/// The reason this is different from <see cref="XboxOneGampadMacOSWireless"/> is that some Xbox Controllers have
325+
/// different View and Share button bit mapping. So we need to use a different layout for those controllers. It seems
326+
/// that some Xbox One and Xbox Series controller share the same mappings so this combines them all.
327+
/// Note: only the latest version of Xbox One wireless gamepads support Bluetooth. Older models only work
328+
/// with a proprietary Xbox wireless protocol, and cannot be used on a Mac.
329+
/// Unlike wired controllers, bluetooth-cabable Xbox One controllers do not need a custom driver to work on macOS.
330+
/// </remarks>
331+
[InputControlLayout(displayName = "Wireless Xbox Controller", stateType = typeof(XInputControllerWirelessOSXStateV2), hideInUI = true)]
332+
public class XboxGamepadMacOSWireless : XInputController
333+
{
334+
}
226335
}
227336
#endif // UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX

0 commit comments

Comments
 (0)