1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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_sysjoystick.h"
26#include "SDL_hidapijoystick_c.h"
27#include "SDL_hidapi_rumble.h"
28
29#ifdef SDL_JOYSTICK_HIDAPI_STADIA
30
31// Define this if you want to log all packets from the controller
32// #define DEBUG_STADIA_PROTOCOL
33
34enum
35{
36 SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11,
37 SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT,
38 SDL_GAMEPAD_NUM_STADIA_BUTTONS,
39};
40
41typedef struct
42{
43 bool rumble_supported;
44 Uint8 last_state[USB_PACKET_LENGTH];
45} SDL_DriverStadia_Context;
46
47static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata)
48{
49 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
50}
51
52static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata)
53{
54 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
55}
56
57static bool HIDAPI_DriverStadia_IsEnabled(void)
58{
59 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
60}
61
62static 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)
63{
64 return SDL_IsJoystickGoogleStadiaController(vendor_id, product_id);
65}
66
67static bool HIDAPI_DriverStadia_InitDevice(SDL_HIDAPI_Device *device)
68{
69 SDL_DriverStadia_Context *ctx;
70
71 ctx = (SDL_DriverStadia_Context *)SDL_calloc(1, sizeof(*ctx));
72 if (!ctx) {
73 return false;
74 }
75 device->context = ctx;
76
77 // Check whether rumble is supported
78 {
79 Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
80
81 if (SDL_hid_write(device->dev, rumble_packet, sizeof(rumble_packet)) >= 0) {
82 ctx->rumble_supported = true;
83 }
84 }
85
86 HIDAPI_SetDeviceName(device, "Google Stadia Controller");
87
88 return HIDAPI_JoystickConnected(device, NULL);
89}
90
91static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
92{
93 return -1;
94}
95
96static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
97{
98}
99
100static bool HIDAPI_DriverStadia_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
101{
102 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
103
104 SDL_AssertJoysticksLocked();
105
106 SDL_zeroa(ctx->last_state);
107
108 // Initialize the joystick capabilities
109 joystick->nbuttons = SDL_GAMEPAD_NUM_STADIA_BUTTONS;
110 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
111 joystick->nhats = 1;
112
113 return true;
114}
115
116static bool HIDAPI_DriverStadia_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
117{
118 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
119
120 if (ctx->rumble_supported) {
121 Uint8 rumble_packet[] = { 0x05, 0x00, 0x00, 0x00, 0x00 };
122
123
124 rumble_packet[1] = (low_frequency_rumble & 0xFF);
125 rumble_packet[2] = (low_frequency_rumble >> 8);
126 rumble_packet[3] = (high_frequency_rumble & 0xFF);
127 rumble_packet[4] = (high_frequency_rumble >> 8);
128
129 if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
130 return SDL_SetError("Couldn't send rumble packet");
131 }
132 return true;
133 } else {
134 return SDL_Unsupported();
135 }
136}
137
138static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
139{
140 return SDL_Unsupported();
141}
142
143static Uint32 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
144{
145 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
146 Uint32 caps = 0;
147
148 if (ctx->rumble_supported) {
149 caps |= SDL_JOYSTICK_CAP_RUMBLE;
150 }
151 return caps;
152}
153
154static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
155{
156 return SDL_Unsupported();
157}
158
159static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
160{
161 return SDL_Unsupported();
162}
163
164static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
165{
166 return SDL_Unsupported();
167}
168
169static void HIDAPI_DriverStadia_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverStadia_Context *ctx, Uint8 *data, int size)
170{
171 Sint16 axis;
172 Uint64 timestamp = SDL_GetTicksNS();
173
174 // The format is the same but the original FW will send 10 bytes and January '21 FW update will send 11
175 if (size < 10 || data[0] != 0x03) {
176 // We don't know how to handle this report
177 return;
178 }
179
180 if (ctx->last_state[1] != data[1]) {
181 Uint8 hat;
182
183 switch (data[1]) {
184 case 0:
185 hat = SDL_HAT_UP;
186 break;
187 case 1:
188 hat = SDL_HAT_RIGHTUP;
189 break;
190 case 2:
191 hat = SDL_HAT_RIGHT;
192 break;
193 case 3:
194 hat = SDL_HAT_RIGHTDOWN;
195 break;
196 case 4:
197 hat = SDL_HAT_DOWN;
198 break;
199 case 5:
200 hat = SDL_HAT_LEFTDOWN;
201 break;
202 case 6:
203 hat = SDL_HAT_LEFT;
204 break;
205 case 7:
206 hat = SDL_HAT_LEFTUP;
207 break;
208 default:
209 hat = SDL_HAT_CENTERED;
210 break;
211 }
212 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
213 }
214
215 if (ctx->last_state[2] != data[2]) {
216 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[2] & 0x40) != 0));
217 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x10) != 0));
218 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[2] & 0x20) != 0));
219 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x80) != 0));
220 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_CAPTURE, ((data[2] & 0x01) != 0));
221 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT, ((data[2] & 0x02) != 0));
222 }
223
224 if (ctx->last_state[3] != data[3]) {
225 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[3] & 0x40) != 0));
226 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[3] & 0x20) != 0));
227 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[3] & 0x10) != 0));
228 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[3] & 0x08) != 0));
229 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[3] & 0x04) != 0));
230 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[3] & 0x02) != 0));
231 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[3] & 0x01) != 0));
232 }
233
234#define READ_STICK_AXIS(offset) \
235 (data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), 0x01 - 0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
236 {
237 axis = READ_STICK_AXIS(4);
238 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
239 axis = READ_STICK_AXIS(5);
240 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
241 axis = READ_STICK_AXIS(6);
242 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
243 axis = READ_STICK_AXIS(7);
244 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
245 }
246#undef READ_STICK_AXIS
247
248#define READ_TRIGGER_AXIS(offset) \
249 (Sint16)(((int)data[offset] * 257) - 32768)
250 {
251 axis = READ_TRIGGER_AXIS(8);
252 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
253 axis = READ_TRIGGER_AXIS(9);
254 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
255 }
256#undef READ_TRIGGER_AXIS
257
258 SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
259}
260
261static bool HIDAPI_DriverStadia_UpdateDevice(SDL_HIDAPI_Device *device)
262{
263 SDL_DriverStadia_Context *ctx = (SDL_DriverStadia_Context *)device->context;
264 SDL_Joystick *joystick = NULL;
265 Uint8 data[USB_PACKET_LENGTH];
266 int size = 0;
267
268 if (device->num_joysticks > 0) {
269 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
270 } else {
271 return false;
272 }
273
274 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
275#ifdef DEBUG_STADIA_PROTOCOL
276 HIDAPI_DumpPacket("Google Stadia packet: size = %d", data, size);
277#endif
278 if (!joystick) {
279 continue;
280 }
281
282 HIDAPI_DriverStadia_HandleStatePacket(joystick, ctx, data, size);
283 }
284
285 if (size < 0) {
286 // Read error, device is disconnected
287 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
288 }
289 return (size >= 0);
290}
291
292static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
293{
294}
295
296static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device)
297{
298}
299
300SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = {
301 SDL_HINT_JOYSTICK_HIDAPI_STADIA,
302 true,
303 HIDAPI_DriverStadia_RegisterHints,
304 HIDAPI_DriverStadia_UnregisterHints,
305 HIDAPI_DriverStadia_IsEnabled,
306 HIDAPI_DriverStadia_IsSupportedDevice,
307 HIDAPI_DriverStadia_InitDevice,
308 HIDAPI_DriverStadia_GetDevicePlayerIndex,
309 HIDAPI_DriverStadia_SetDevicePlayerIndex,
310 HIDAPI_DriverStadia_UpdateDevice,
311 HIDAPI_DriverStadia_OpenJoystick,
312 HIDAPI_DriverStadia_RumbleJoystick,
313 HIDAPI_DriverStadia_RumbleJoystickTriggers,
314 HIDAPI_DriverStadia_GetJoystickCapabilities,
315 HIDAPI_DriverStadia_SetJoystickLED,
316 HIDAPI_DriverStadia_SendJoystickEffect,
317 HIDAPI_DriverStadia_SetJoystickSensorsEnabled,
318 HIDAPI_DriverStadia_CloseJoystick,
319 HIDAPI_DriverStadia_FreeDevice,
320};
321
322#endif // SDL_JOYSTICK_HIDAPI_STADIA
323
324#endif // SDL_JOYSTICK_HIDAPI
325