Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_xbox360.c
9912 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324#include "../../SDL_hints_c.h"25#include "../SDL_sysjoystick.h"26#include "SDL_hidapijoystick_c.h"27#include "SDL_hidapi_rumble.h"2829#ifdef SDL_JOYSTICK_HIDAPI_XBOX3603031// Define this if you want to log all packets from the controller32// #define DEBUG_XBOX_PROTOCOL3334typedef struct35{36SDL_HIDAPI_Device *device;37SDL_Joystick *joystick;38int player_index;39bool player_lights;40Uint8 last_state[USB_PACKET_LENGTH];41} SDL_DriverXbox360_Context;4243static void HIDAPI_DriverXbox360_RegisterHints(SDL_HintCallback callback, void *userdata)44{45SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);46SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);47}4849static void HIDAPI_DriverXbox360_UnregisterHints(SDL_HintCallback callback, void *userdata)50{51SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);52SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);53}5455static bool HIDAPI_DriverXbox360_IsEnabled(void)56{57return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,58SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)));59}6061static 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)62{63const int XB360W_IFACE_PROTOCOL = 129; // Wireless6465if (vendor_id == USB_VENDOR_ASTRO && product_id == USB_PRODUCT_ASTRO_C40_XBOX360) {66// This is the ASTRO C40 in Xbox 360 mode67return true;68}69if (vendor_id == USB_VENDOR_NVIDIA) {70// This is the NVIDIA Shield controller which doesn't talk Xbox controller protocol71return false;72}73if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER)) ||74(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {75// This is the wireless dongle, which talks a different protocol76return false;77}78if (interface_number > 0) {79// This is the chatpad or other input interface, not the Xbox 360 interface80return false;81}82#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)83if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {84// GCController support doesn't work with the Steam Virtual Gamepad85return true;86} else {87// On macOS you can't write output reports to wired XBox controllers,88// so we'll just use the GCController support instead.89return false;90}91#else92return (type == SDL_GAMEPAD_TYPE_XBOX360);93#endif94}9596static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)97{98const bool blink = false;99Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;100Uint8 led_packet[] = { 0x01, 0x03, 0x00 };101102led_packet[2] = mode;103if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {104return false;105}106return true;107}108109static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx)110{111if (ctx->player_lights && ctx->player_index >= 0) {112SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);113} else {114SetSlotLED(ctx->device->dev, 0, false);115}116}117118static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)119{120SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata;121bool player_lights = SDL_GetStringBoolean(hint, true);122123if (player_lights != ctx->player_lights) {124ctx->player_lights = player_lights;125126UpdateSlotLED(ctx);127HIDAPI_UpdateDeviceProperties(ctx->device);128}129}130131static bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)132{133SDL_DriverXbox360_Context *ctx;134135ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));136if (!ctx) {137return false;138}139ctx->device = device;140141device->context = ctx;142143device->type = SDL_GAMEPAD_TYPE_XBOX360;144145if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) &&146device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) {147int slot = 0;148SDL_sscanf(device->product_string, "GamePad-%d", &slot);149device->steam_virtual_gamepad_slot = (slot - 1);150}151152return HIDAPI_JoystickConnected(device, NULL);153}154155static int HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)156{157return -1;158}159160static void HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)161{162SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;163164if (!ctx->joystick) {165return;166}167168ctx->player_index = player_index;169170UpdateSlotLED(ctx);171}172173static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)174{175SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;176177SDL_AssertJoysticksLocked();178179ctx->joystick = joystick;180SDL_zeroa(ctx->last_state);181182// Initialize player index (needed for setting LEDs)183ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);184ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);185UpdateSlotLED(ctx);186187SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,188SDL_PlayerLEDHintChanged, ctx);189190// Initialize the joystick capabilities191joystick->nbuttons = 11;192joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;193joystick->nhats = 1;194195return true;196}197198static bool HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)199{200Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };201202rumble_packet[3] = (low_frequency_rumble >> 8);203rumble_packet[4] = (high_frequency_rumble >> 8);204205if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {206return SDL_SetError("Couldn't send rumble packet");207}208return true;209}210211static bool HIDAPI_DriverXbox360_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)212{213return SDL_Unsupported();214}215216static Uint32 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)217{218SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;219Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;220221if (ctx->player_lights) {222result |= SDL_JOYSTICK_CAP_PLAYER_LED;223}224return result;225}226227static bool HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)228{229return SDL_Unsupported();230}231232static bool HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)233{234return SDL_Unsupported();235}236237static bool HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)238{239return SDL_Unsupported();240}241242static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)243{244Sint16 axis;245#ifdef SDL_PLATFORM_MACOS246const bool invert_y_axes = false;247#else248const bool invert_y_axes = true;249#endif250Uint64 timestamp = SDL_GetTicksNS();251252if (ctx->last_state[2] != data[2]) {253Uint8 hat = 0;254255if (data[2] & 0x01) {256hat |= SDL_HAT_UP;257}258if (data[2] & 0x02) {259hat |= SDL_HAT_DOWN;260}261if (data[2] & 0x04) {262hat |= SDL_HAT_LEFT;263}264if (data[2] & 0x08) {265hat |= SDL_HAT_RIGHT;266}267SDL_SendJoystickHat(timestamp, joystick, 0, hat);268269SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));270SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));271SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));272SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));273}274275if (ctx->last_state[3] != data[3]) {276SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));277SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));278SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));279SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));280SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));281SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));282SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));283}284285axis = ((int)data[4] * 257) - 32768;286SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);287axis = ((int)data[5] * 257) - 32768;288SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);289axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));290SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);291axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));292if (invert_y_axes) {293axis = ~axis;294}295SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);296axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));297SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);298axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));299if (invert_y_axes) {300axis = ~axis;301}302SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);303304SDL_memcpy(ctx->last_state, data, SDL_min((size_t)size, sizeof(ctx->last_state)));305}306307static bool HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device)308{309SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;310SDL_Joystick *joystick = NULL;311Uint8 data[USB_PACKET_LENGTH];312int size = 0;313314if (device->num_joysticks > 0) {315joystick = SDL_GetJoystickFromID(device->joysticks[0]);316} else {317return false;318}319320while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {321#ifdef DEBUG_XBOX_PROTOCOL322HIDAPI_DumpPacket("Xbox 360 packet: size = %d", data, size);323#endif324if (!joystick) {325continue;326}327328if (data[0] == 0x00) {329HIDAPI_DriverXbox360_HandleStatePacket(joystick, ctx, data, size);330}331}332333if (size < 0) {334// Read error, device is disconnected335HIDAPI_JoystickDisconnected(device, device->joysticks[0]);336}337return (size >= 0);338}339340static void HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)341{342SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;343344SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,345SDL_PlayerLEDHintChanged, ctx);346347ctx->joystick = NULL;348}349350static void HIDAPI_DriverXbox360_FreeDevice(SDL_HIDAPI_Device *device)351{352}353354SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = {355SDL_HINT_JOYSTICK_HIDAPI_XBOX_360,356true,357HIDAPI_DriverXbox360_RegisterHints,358HIDAPI_DriverXbox360_UnregisterHints,359HIDAPI_DriverXbox360_IsEnabled,360HIDAPI_DriverXbox360_IsSupportedDevice,361HIDAPI_DriverXbox360_InitDevice,362HIDAPI_DriverXbox360_GetDevicePlayerIndex,363HIDAPI_DriverXbox360_SetDevicePlayerIndex,364HIDAPI_DriverXbox360_UpdateDevice,365HIDAPI_DriverXbox360_OpenJoystick,366HIDAPI_DriverXbox360_RumbleJoystick,367HIDAPI_DriverXbox360_RumbleJoystickTriggers,368HIDAPI_DriverXbox360_GetJoystickCapabilities,369HIDAPI_DriverXbox360_SetJoystickLED,370HIDAPI_DriverXbox360_SendJoystickEffect,371HIDAPI_DriverXbox360_SetJoystickSensorsEnabled,372HIDAPI_DriverXbox360_CloseJoystick,373HIDAPI_DriverXbox360_FreeDevice,374};375376#endif // SDL_JOYSTICK_HIDAPI_XBOX360377378#endif // SDL_JOYSTICK_HIDAPI379380381