Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_xbox360.c
9912 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
4
5
This software is provided 'as-is', without any express or implied
6
warranty. In no event will the authors be held liable for any damages
7
arising from the use of this software.
8
9
Permission is granted to anyone to use this software for any purpose,
10
including commercial applications, and to alter it and redistribute it
11
freely, subject to the following restrictions:
12
13
1. The origin of this software must not be misrepresented; you must not
14
claim that you wrote the original software. If you use this software
15
in a product, an acknowledgment in the product documentation would be
16
appreciated but is not required.
17
2. Altered source versions must be plainly marked as such, and must not be
18
misrepresented as being the original software.
19
3. This notice may not be removed or altered from any source distribution.
20
*/
21
#include "SDL_internal.h"
22
23
#ifdef SDL_JOYSTICK_HIDAPI
24
25
#include "../../SDL_hints_c.h"
26
#include "../SDL_sysjoystick.h"
27
#include "SDL_hidapijoystick_c.h"
28
#include "SDL_hidapi_rumble.h"
29
30
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
31
32
// Define this if you want to log all packets from the controller
33
// #define DEBUG_XBOX_PROTOCOL
34
35
typedef struct
36
{
37
SDL_HIDAPI_Device *device;
38
SDL_Joystick *joystick;
39
int player_index;
40
bool player_lights;
41
Uint8 last_state[USB_PACKET_LENGTH];
42
} SDL_DriverXbox360_Context;
43
44
static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata)
45
{
46
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
47
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
48
}
49
50
static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata)
51
{
52
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);
53
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);
54
}
55
56
static bool HIDAPI_DriverXbox360_IsEnabled(void)
57
{
58
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
59
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));
60
}
61
62
static bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
63
{
64
const int XB360W_IFACE_PROTOCOL = 129; // Wireless
65
66
if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) {
67
// This is the ASTRO C40 in Xbox 360 mode
68
return true;
69
}
70
if (vendor_id == USB_VENDOR_NVIDIA) {
71
// This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol
72
return false;
73
}
74
if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) ||
75
(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {
76
// This is the wireless dongle, which talks a different protocol
77
return false;
78
}
79
if (interface_number > 0) {
80
// This is the chatpad or other input interface, not the Xbox 360 interface
81
return false;
82
}
83
#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
84
if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
85
// GCController support doesn't work with the Steam Virtual Gamepad
86
return true;
87
} else {
88
// On macOS you can't write output reports to wired XBox controllers,
89
// so we'll just use the GCController support instead.
90
return false;
91
}
92
#else
93
return (type == SDL_GAMEPAD_TYPE_XBOX360);
94
#endif
95
}
96
97
static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)
98
{
99
const bool blink = false;
100
Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
101
Uint8 led_packet[] = { 0x01, 0x03, 0x00 };
102
103
led_packet[2] = mode;
104
if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
105
return false;
106
}
107
return true;
108
}
109
110
static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx)
111
{
112
if (ctx->player_lights && ctx->player_index >= 0) {
113
SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);
114
} else {
115
SetSlotLED(ctx->device->dev, 0, false);
116
}
117
}
118
119
static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
120
{
121
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata;
122
bool player_lights = SDL_GetStringBoolean(hint, true);
123
124
if (player_lights != ctx->player_lights) {
125
ctx->player_lights = player_lights;
126
127
UpdateSlotLED(ctx);
128
HIDAPI_UpdateDeviceProperties(ctx->device);
129
}
130
}
131
132
static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)
133
{
134
SDL_DriverXbox360_Context *ctx;
135
136
ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));
137
if (!ctx) {
138
return false;
139
}
140
ctx->device = device;
141
142
device->context = ctx;
143
144
device->type = SDL_GAMEPAD_TYPE_XBOX360;
145
146
if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) &&
147
device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) {
148
int slot = 0;
149
SDL_sscanf(device->product_string, "GamePad-%d", &slot);
150
device->steam_virtual_gamepad_slot = (slot - 1);
151
}
152
153
return HIDAPI_JoystickConnected(device, NULL);
154
}
155
156
static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
157
{
158
return -1;
159
}
160
161
static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
162
{
163
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
164
165
if (!ctx->joystick) {
166
return;
167
}
168
169
ctx->player_index = player_index;
170
171
UpdateSlotLED(ctx);
172
}
173
174
static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
175
{
176
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
177
178
SDL_AssertJoysticksLocked();
179
180
ctx->joystick = joystick;
181
SDL_zeroa(ctx->last_state);
182
183
// Initialize player index (needed for setting LEDs)
184
ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
185
ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);
186
UpdateSlotLED(ctx);
187
188
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
189
SDL_PlayerLEDHintChanged, ctx);
190
191
// Initialize the joystick capabilities
192
joystick->nbuttons = 11;
193
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
194
joystick->nhats = 1;
195
196
return true;
197
}
198
199
static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
200
{
201
Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
202
203
rumble_packet[3] = (low_frequency_rumble >> 8);
204
rumble_packet[4] = (high_frequency_rumble >> 8);
205
206
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
207
return SDL_SetError("Couldn't send rumble packet");
208
}
209
return true;
210
}
211
212
static bool HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
213
{
214
return SDL_Unsupported();
215
}
216
217
static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
218
{
219
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
220
Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;
221
222
if (ctx->player_lights) {
223
result |= SDL_JOYSTICK_CAP_PLAYER_LED;
224
}
225
return result;
226
}
227
228
static bool HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
229
{
230
return SDL_Unsupported();
231
}
232
233
static bool HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
234
{
235
return SDL_Unsupported();
236
}
237
238
static bool HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
239
{
240
return SDL_Unsupported();
241
}
242
243
static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)
244
{
245
Sint16 axis;
246
#ifdef SDL_PLATFORM_MACOS
247
const bool invert_y_axes = false;
248
#else
249
const bool invert_y_axes = true;
250
#endif
251
Uint64 timestamp = SDL_GetTicksNS();
252
253
if (ctx->last_state[2] != data[2]) {
254
Uint8 hat = 0;
255
256
if (data[2] & 0x01) {
257
hat |= SDL_HAT_UP;
258
}
259
if (data[2] & 0x02) {
260
hat |= SDL_HAT_DOWN;
261
}
262
if (data[2] & 0x04) {
263
hat |= SDL_HAT_LEFT;
264
}
265
if (data[2] & 0x08) {
266
hat |= SDL_HAT_RIGHT;
267
}
268
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
269
270
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));
271
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));
272
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));
273
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
274
}
275
276
if (ctx->last_state[3] != data[3]) {
277
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));
278
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
279
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));
280
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));
281
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
282
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));
283
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));
284
}
285
286
axis = ((int)data[4] * 257) - 32768;
287
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
288
axis = ((int)data[5] * 257) - 32768;
289
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
290
axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));
291
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
292
axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));
293
if (invert_y_axes) {
294
axis = ~axis;
295
}
296
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
297
axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));
298
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
299
axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));
300
if (invert_y_axes) {
301
axis = ~axis;
302
}
303
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
304
305
SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state)));
306
}
307
308
static bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device)
309
{
310
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
311
SDL_Joystick *joystick = NULL;
312
Uint8 data[USB_PACKET_LENGTH];
313
int size = 0;
314
315
if (device->num_joysticks > 0) {
316
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
317
} else {
318
return false;
319
}
320
321
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
322
#ifdef DEBUG_XBOX_PROTOCOL
323
HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size);
324
#endif
325
if (!joystick) {
326
continue;
327
}
328
329
if (data[0] == 0x00) {
330
HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size);
331
}
332
}
333
334
if (size < 0) {
335
// Read error, device is disconnected
336
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
337
}
338
return (size >= 0);
339
}
340
341
static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
342
{
343
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
344
345
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
346
SDL_PlayerLEDHintChanged, ctx);
347
348
ctx->joystick = NULL;
349
}
350
351
static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device)
352
{
353
}
354
355
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = {
356
SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,
357
true,
358
HIDAPI_DriverXbox360_RegisterHints,
359
HIDAPI_DriverXbox360_UnregisterHints,
360
HIDAPI_DriverXbox360_IsEnabled,
361
HIDAPI_DriverXbox360_IsSupportedDevice,
362
HIDAPI_DriverXbox360_InitDevice,
363
HIDAPI_DriverXbox360_GetDevicePlayerIndex,
364
HIDAPI_DriverXbox360_SetDevicePlayerIndex,
365
HIDAPI_DriverXbox360_UpdateDevice,
366
HIDAPI_DriverXbox360_OpenJoystick,
367
HIDAPI_DriverXbox360_RumbleJoystick,
368
HIDAPI_DriverXbox360_RumbleJoystickTriggers,
369
HIDAPI_DriverXbox360_GetJoystickCapabilities,
370
HIDAPI_DriverXbox360_SetJoystickLED,
371
HIDAPI_DriverXbox360_SendJoystickEffect,
372
HIDAPI_DriverXbox360_SetJoystickSensorsEnabled,
373
HIDAPI_DriverXbox360_CloseJoystick,
374
HIDAPI_DriverXbox360_FreeDevice,
375
};
376
377
#endif // SDL_JOYSTICK_HIDAPI_XBOX360
378
379
#endif // SDL_JOYSTICK_HIDAPI
380
381