Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_stadia.c
9913 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_sysjoystick.h"25#include "SDL_hidapijoystick_c.h"26#include "SDL_hidapi_rumble.h"2728#ifdef SDL_JOYSTICK_HIDAPI_STADIA2930// Define this if you want to log all packets from the controller31// #define DEBUG_STADIA_PROTOCOL3233enum34{35SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11,36SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT,37SDL_GAMEPAD_NUM_STADIA_BUTTONS,38};3940typedef struct41{42bool rumble_supported;43Uint8 last_state[USB_PACKET_LENGTH];44} SDL_DriverStadia_Context;4546static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata)47{48SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);49}5051static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata)52{53SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);54}5556static bool HIDAPI_DriverStadia_IsEnabled(void)57{58return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));59}6061static bool HIDAPI_DriverStadia_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{63return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id);64}6566static bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device)67{68SDL_DriverStadia_Context *ctx;6970ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx));71if (!ctx) {72return false;73}74device->context = ctx;7576// Check whether rumble is supported77{78Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };7980if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) {81ctx->rumble_supported = true;82}83}8485HIDAPI_SetDeviceName(device, "Google Stadia Controller");8687return HIDAPI_JoystickConnected(device, NULL);88}8990static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)91{92return -1;93}9495static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)96{97}9899static bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)100{101SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;102103SDL_AssertJoysticksLocked();104105SDL_zeroa(ctx->last_state);106107// Initialize the joystick capabilities108joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS;109joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;110joystick->nhats = 1;111112return true;113}114115static bool HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)116{117SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;118119if (ctx->rumble_supported) {120Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };121122123rumble_packet[1] = (low_frequency_rumble & 0xFF);124rumble_packet[2] = (low_frequency_rumble >> 8);125rumble_packet[3] = (high_frequency_rumble & 0xFF);126rumble_packet[4] = (high_frequency_rumble >> 8);127128if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {129return SDL_SetError("Couldn't send rumble packet");130}131return true;132} else {133return SDL_Unsupported();134}135}136137static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)138{139return SDL_Unsupported();140}141142static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)143{144SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;145Uint32 caps = 0;146147if (ctx->rumble_supported) {148caps |= SDL_JOYSTICK_CAP_RUMBLE;149}150return caps;151}152153static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)154{155return SDL_Unsupported();156}157158static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)159{160return SDL_Unsupported();161}162163static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)164{165return SDL_Unsupported();166}167168static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size)169{170Sint16 axis;171Uint64 timestamp = SDL_GetTicksNS();172173// The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11174if (size < 10 || data[0] != 0x03) {175// We don't know how to handle this report176return;177}178179if (ctx->last_state[1] != data[1]) {180Uint8 hat;181182switch (data[1]) {183case 0:184hat = SDL_HAT_UP;185break;186case 1:187hat = SDL_HAT_RIGHTUP;188break;189case 2:190hat = SDL_HAT_RIGHT;191break;192case 3:193hat = SDL_HAT_RIGHTDOWN;194break;195case 4:196hat = SDL_HAT_DOWN;197break;198case 5:199hat = SDL_HAT_LEFTDOWN;200break;201case 6:202hat = SDL_HAT_LEFT;203break;204case 7:205hat = SDL_HAT_LEFTUP;206break;207default:208hat = SDL_HAT_CENTERED;209break;210}211SDL_SendJoystickHat(timestamp, joystick, 0, hat);212}213214if (ctx->last_state[2] != data[2]) {215SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0));216SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x10) != 0));217SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x20) != 0));218SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));219SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, ((data[2] & 0x01) != 0));220SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, ((data[2] & 0x02) != 0));221}222223if (ctx->last_state[3] != data[3]) {224SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0));225SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));226SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x10) != 0));227SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0));228SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0));229SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));230SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x01) != 0));231}232233#define READ_STICK_AXIS(offset) \234(data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))235{236axis = READ_STICK_AXIS(4);237SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);238axis = READ_STICK_AXIS(5);239SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);240axis = READ_STICK_AXIS(6);241SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);242axis = READ_STICK_AXIS(7);243SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);244}245#undef READ_STICK_AXIS246247#define READ_TRIGGER_AXIS(offset) \248(Sint16)(((int)data[offset] * 257) - 32768)249{250axis = READ_TRIGGER_AXIS(8);251SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);252axis = READ_TRIGGER_AXIS(9);253SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);254}255#undef READ_TRIGGER_AXIS256257SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));258}259260static bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device)261{262SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;263SDL_Joystick *joystick = NULL;264Uint8 data[USB_PACKET_LENGTH];265int size = 0;266267if (device->num_joysticks > 0) {268joystick = SDL_GetJoystickFromID(device->joysticks[0]);269} else {270return false;271}272273while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {274#ifdef DEBUG_STADIA_PROTOCOL275HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size);276#endif277if (!joystick) {278continue;279}280281HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size);282}283284if (size < 0) {285// Read error, device is disconnected286HIDAPI_JoystickDisconnected(device, device->joysticks[0]);287}288return (size >= 0);289}290291static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)292{293}294295static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device)296{297}298299SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = {300SDL_HINT_JOYSTICK_HIDAPI_STADIA,301true,302HIDAPI_DriverStadia_RegisterHints,303HIDAPI_DriverStadia_UnregisterHints,304HIDAPI_DriverStadia_IsEnabled,305HIDAPI_DriverStadia_IsSupportedDevice,306HIDAPI_DriverStadia_InitDevice,307HIDAPI_DriverStadia_GetDevicePlayerIndex,308HIDAPI_DriverStadia_SetDevicePlayerIndex,309HIDAPI_DriverStadia_UpdateDevice,310HIDAPI_DriverStadia_OpenJoystick,311HIDAPI_DriverStadia_RumbleJoystick,312HIDAPI_DriverStadia_RumbleJoystickTriggers,313HIDAPI_DriverStadia_GetJoystickCapabilities,314HIDAPI_DriverStadia_SetJoystickLED,315HIDAPI_DriverStadia_SendJoystickEffect,316HIDAPI_DriverStadia_SetJoystickSensorsEnabled,317HIDAPI_DriverStadia_CloseJoystick,318HIDAPI_DriverStadia_FreeDevice,319};320321#endif // SDL_JOYSTICK_HIDAPI_STADIA322323#endif // SDL_JOYSTICK_HIDAPI324325326