Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_stadia.c
9913 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_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
34
enum
35
{
36
SDL_GAMEPAD_BUTTON_STADIA_CAPTURE = 11,
37
SDL_GAMEPAD_BUTTON_STADIA_GOOGLE_ASSISTANT,
38
SDL_GAMEPAD_NUM_STADIA_BUTTONS,
39
};
40
41
typedef struct
42
{
43
bool rumble_supported;
44
Uint8 last_state[USB_PACKET_LENGTH];
45
} SDL_DriverStadia_Context;
46
47
static void HIDAPI_DriverStadia_RegisterHints(SDL_HintCallback callback, void *userdata)
48
{
49
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
50
}
51
52
static void HIDAPI_DriverStadia_UnregisterHints(SDL_HintCallback callback, void *userdata)
53
{
54
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STADIA, callback, userdata);
55
}
56
57
static 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
62
static 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
67
static 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
91
static int HIDAPI_DriverStadia_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
92
{
93
return -1;
94
}
95
96
static void HIDAPI_DriverStadia_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
97
{
98
}
99
100
static 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
116
static 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
138
static bool HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
139
{
140
return SDL_Unsupported();
141
}
142
143
static 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
154
static bool HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
155
{
156
return SDL_Unsupported();
157
}
158
159
static bool HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
160
{
161
return SDL_Unsupported();
162
}
163
164
static bool HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
165
{
166
return SDL_Unsupported();
167
}
168
169
static 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
261
static 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
292
static void HIDAPI_DriverStadia_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
293
{
294
}
295
296
static void HIDAPI_DriverStadia_FreeDevice(SDL_HIDAPI_Device *device)
297
{
298
}
299
300
SDL_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
326