Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_xbox360w.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;37bool connected;38int player_index;39bool player_lights;40Uint8 last_state[USB_PACKET_LENGTH];41} SDL_DriverXbox360W_Context;4243static void HIDAPI_DriverXbox360W_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);47SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);48}4950static void HIDAPI_DriverXbox360W_UnregisterHints(SDL_HintCallback callback, void *userdata)51{52SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX, callback, userdata);53SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, callback, userdata);54SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS, callback, userdata);55}5657static bool HIDAPI_DriverXbox360W_IsEnabled(void)58{59return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,60SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))));61}6263static bool HIDAPI_DriverXbox360W_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)64{65const int XB360W_IFACE_PROTOCOL = 129; // Wireless6667if ((vendor_id == USB_VENDOR_MICROSOFT && (product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY2 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER_THIRDPARTY1 || product_id == USB_PRODUCT_XBOX360_WIRELESS_RECEIVER) && interface_protocol == 0) ||68(type == SDL_GAMEPAD_TYPE_XBOX360 && interface_protocol == XB360W_IFACE_PROTOCOL)) {69return true;70}71return false;72}7374static bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, bool on)75{76const bool blink = false;77Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;78Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };7980led_packet[3] = 0x40 + (mode % 0x0e);81if (SDL_hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {82return false;83}84return true;85}8687static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx)88{89if (ctx->player_lights && ctx->player_index >= 0) {90SetSlotLED(ctx->device->dev, (ctx->player_index % 4), true);91} else {92SetSlotLED(ctx->device->dev, 0, false);93}94}9596static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)97{98SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata;99bool player_lights = SDL_GetStringBoolean(hint, true);100101if (player_lights != ctx->player_lights) {102ctx->player_lights = player_lights;103104UpdateSlotLED(ctx);105HIDAPI_UpdateDeviceProperties(ctx->device);106}107}108109static void UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level)110{111int percent = (int)SDL_roundf((level / 255.0f) * 100.0f);112SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);113}114115static bool HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device)116{117SDL_DriverXbox360W_Context *ctx;118119// Requests controller presence information from the wireless dongle120const Uint8 init_packet[] = { 0x08, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };121122HIDAPI_SetDeviceName(device, "Xbox 360 Wireless Controller");123124ctx = (SDL_DriverXbox360W_Context *)SDL_calloc(1, sizeof(*ctx));125if (!ctx) {126return false;127}128ctx->device = device;129130device->context = ctx;131132if (SDL_hid_write(device->dev, init_packet, sizeof(init_packet)) != sizeof(init_packet)) {133SDL_SetError("Couldn't write init packet");134return false;135}136137device->type = SDL_GAMEPAD_TYPE_XBOX360;138139return true;140}141142static int HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)143{144return -1;145}146147static void HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)148{149SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;150151if (!ctx) {152return;153}154155ctx->player_index = player_index;156157UpdateSlotLED(ctx);158}159160static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)161{162SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;163164SDL_AssertJoysticksLocked();165166SDL_zeroa(ctx->last_state);167168// Initialize player index (needed for setting LEDs)169ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);170ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, true);171UpdateSlotLED(ctx);172173SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,174SDL_PlayerLEDHintChanged, ctx);175176// Initialize the joystick capabilities177joystick->nbuttons = 11;178joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;179joystick->nhats = 1;180joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;181182return true;183}184185static bool HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)186{187Uint8 rumble_packet[] = { 0x00, 0x01, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };188189rumble_packet[5] = (low_frequency_rumble >> 8);190rumble_packet[6] = (high_frequency_rumble >> 8);191192if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {193return SDL_SetError("Couldn't send rumble packet");194}195return true;196}197198static bool HIDAPI_DriverXbox360W_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)199{200return SDL_Unsupported();201}202203static Uint32 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)204{205SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;206Uint32 result = SDL_JOYSTICK_CAP_RUMBLE;207208if (ctx->player_lights) {209result |= SDL_JOYSTICK_CAP_PLAYER_LED;210}211return result;212}213214static bool HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)215{216return SDL_Unsupported();217}218219static bool HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)220{221return SDL_Unsupported();222}223224static bool HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)225{226return SDL_Unsupported();227}228229static void HIDAPI_DriverXbox360W_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverXbox360W_Context *ctx, Uint8 *data, int size)230{231Sint16 axis;232const bool invert_y_axes = true;233Uint64 timestamp = SDL_GetTicksNS();234235if (ctx->last_state[2] != data[2]) {236Uint8 hat = 0;237238if (data[2] & 0x01) {239hat |= SDL_HAT_UP;240}241if (data[2] & 0x02) {242hat |= SDL_HAT_DOWN;243}244if (data[2] & 0x04) {245hat |= SDL_HAT_LEFT;246}247if (data[2] & 0x08) {248hat |= SDL_HAT_RIGHT;249}250SDL_SendJoystickHat(timestamp, joystick, 0, hat);251252SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x10) != 0));253SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x20) != 0));254SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x40) != 0));255SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));256}257258if (ctx->last_state[3] != data[3]) {259SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x01) != 0));260SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));261SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[3] & 0x04) != 0));262SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x10) != 0));263SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));264SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x40) != 0));265SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x80) != 0));266}267268axis = ((int)data[4] * 257) - 32768;269SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);270axis = ((int)data[5] * 257) - 32768;271SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);272axis = SDL_Swap16LE(*(Sint16 *)(&data[6]));273SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);274axis = SDL_Swap16LE(*(Sint16 *)(&data[8]));275if (invert_y_axes) {276axis = ~axis;277}278SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);279axis = SDL_Swap16LE(*(Sint16 *)(&data[10]));280SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);281axis = SDL_Swap16LE(*(Sint16 *)(&data[12]));282if (invert_y_axes) {283axis = ~axis;284}285SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);286287SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));288}289290static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)291{292SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;293SDL_Joystick *joystick = NULL;294Uint8 data[USB_PACKET_LENGTH];295int size;296297if (device->num_joysticks > 0) {298joystick = SDL_GetJoystickFromID(device->joysticks[0]);299}300301while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {302#ifdef DEBUG_XBOX_PROTOCOL303HIDAPI_DumpPacket("Xbox 360 wireless packet: size = %d", data, size);304#endif305if (size == 2 && data[0] == 0x08) {306bool connected = (data[1] & 0x80) ? true : false;307#ifdef DEBUG_JOYSTICK308SDL_Log("Connected = %s", connected ? "TRUE" : "FALSE");309#endif310if (connected != ctx->connected) {311ctx->connected = connected;312313if (connected) {314SDL_JoystickID joystickID;315316HIDAPI_JoystickConnected(device, &joystickID);317318} else if (device->num_joysticks > 0) {319HIDAPI_JoystickDisconnected(device, device->joysticks[0]);320}321}322} else if (size == 29 && data[0] == 0x00 && data[1] == 0x0f && data[2] == 0x00 && data[3] == 0xf0) {323// Serial number is data[7-13]324#ifdef DEBUG_JOYSTICK325SDL_Log("Battery status (initial): %d", data[17]);326#endif327if (joystick) {328UpdatePowerLevel(joystick, data[17]);329}330} else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) {331#ifdef DEBUG_JOYSTICK332SDL_Log("Battery status: %d", data[4]);333#endif334if (joystick) {335UpdatePowerLevel(joystick, data[4]);336}337} else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) {338if (joystick) {339HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4);340}341}342}343344if (size < 0 && device->num_joysticks > 0) {345// Read error, device is disconnected346HIDAPI_JoystickDisconnected(device, device->joysticks[0]);347}348return (size >= 0);349}350351static void HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)352{353SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;354355SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,356SDL_PlayerLEDHintChanged, ctx);357}358359static void HIDAPI_DriverXbox360W_FreeDevice(SDL_HIDAPI_Device *device)360{361}362363SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = {364SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_WIRELESS,365true,366HIDAPI_DriverXbox360W_RegisterHints,367HIDAPI_DriverXbox360W_UnregisterHints,368HIDAPI_DriverXbox360W_IsEnabled,369HIDAPI_DriverXbox360W_IsSupportedDevice,370HIDAPI_DriverXbox360W_InitDevice,371HIDAPI_DriverXbox360W_GetDevicePlayerIndex,372HIDAPI_DriverXbox360W_SetDevicePlayerIndex,373HIDAPI_DriverXbox360W_UpdateDevice,374HIDAPI_DriverXbox360W_OpenJoystick,375HIDAPI_DriverXbox360W_RumbleJoystick,376HIDAPI_DriverXbox360W_RumbleJoystickTriggers,377HIDAPI_DriverXbox360W_GetJoystickCapabilities,378HIDAPI_DriverXbox360W_SetJoystickLED,379HIDAPI_DriverXbox360W_SendJoystickEffect,380HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled,381HIDAPI_DriverXbox360W_CloseJoystick,382HIDAPI_DriverXbox360W_FreeDevice,383};384385#endif // SDL_JOYSTICK_HIDAPI_XBOX360386387#endif // SDL_JOYSTICK_HIDAPI388389390