Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_steam_hori.c
9912 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
#include "../SDL_joystick_c.h"
29
30
#ifdef SDL_JOYSTICK_HIDAPI_STEAM_HORI
31
32
/* Define this if you want to log all packets from the controller */
33
/*#define DEBUG_HORI_PROTOCOL*/
34
35
#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
36
37
enum
38
{
39
SDL_GAMEPAD_BUTTON_HORI_QAM = 11,
40
SDL_GAMEPAD_BUTTON_HORI_FR,
41
SDL_GAMEPAD_BUTTON_HORI_FL,
42
SDL_GAMEPAD_BUTTON_HORI_M1,
43
SDL_GAMEPAD_BUTTON_HORI_M2,
44
SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L,
45
SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R,
46
SDL_GAMEPAD_NUM_HORI_BUTTONS
47
};
48
49
typedef struct
50
{
51
Uint8 last_state[USB_PACKET_LENGTH];
52
Uint64 sensor_ticks;
53
Uint32 last_tick;
54
bool wireless;
55
bool serial_needs_init;
56
} SDL_DriverSteamHori_Context;
57
58
static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device);
59
60
static void HIDAPI_DriverSteamHori_RegisterHints(SDL_HintCallback callback, void *userdata)
61
{
62
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);
63
}
64
65
static void HIDAPI_DriverSteamHori_UnregisterHints(SDL_HintCallback callback, void *userdata)
66
{
67
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, callback, userdata);
68
}
69
70
static bool HIDAPI_DriverSteamHori_IsEnabled(void)
71
{
72
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
73
}
74
75
static bool HIDAPI_DriverSteamHori_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)
76
{
77
return SDL_IsJoystickHoriSteamController(vendor_id, product_id);
78
}
79
80
static bool HIDAPI_DriverSteamHori_InitDevice(SDL_HIDAPI_Device *device)
81
{
82
SDL_DriverSteamHori_Context *ctx;
83
84
ctx = (SDL_DriverSteamHori_Context *)SDL_calloc(1, sizeof(*ctx));
85
if (!ctx) {
86
return false;
87
}
88
89
device->context = ctx;
90
ctx->serial_needs_init = true;
91
92
HIDAPI_SetDeviceName(device, "Wireless HORIPAD For Steam");
93
94
return HIDAPI_JoystickConnected(device, NULL);
95
}
96
97
static int HIDAPI_DriverSteamHori_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
98
{
99
return -1;
100
}
101
102
static void HIDAPI_DriverSteamHori_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
103
{
104
}
105
106
static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
107
{
108
SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;
109
110
SDL_AssertJoysticksLocked();
111
112
SDL_zeroa(ctx->last_state);
113
114
/* Initialize the joystick capabilities */
115
joystick->nbuttons = SDL_GAMEPAD_NUM_HORI_BUTTONS;
116
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
117
joystick->nhats = 1;
118
119
ctx->wireless = device->product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT;
120
121
if (ctx->wireless && device->serial) {
122
joystick->serial = SDL_strdup(device->serial);
123
ctx->serial_needs_init = false;
124
} else if (!ctx->wireless) {
125
// Need to actual read from the device to init the serial
126
HIDAPI_DriverSteamHori_UpdateDevice(device);
127
}
128
129
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
130
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
131
132
return true;
133
}
134
135
static bool HIDAPI_DriverSteamHori_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
136
{
137
// Device doesn't support rumble
138
return SDL_Unsupported();
139
}
140
141
static bool HIDAPI_DriverSteamHori_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
142
{
143
return SDL_Unsupported();
144
}
145
146
static Uint32 HIDAPI_DriverSteamHori_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
147
{
148
return 0;
149
}
150
151
static bool HIDAPI_DriverSteamHori_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
152
{
153
return SDL_Unsupported();
154
}
155
156
static bool HIDAPI_DriverSteamHori_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
157
{
158
return SDL_Unsupported();
159
}
160
161
static bool HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
162
{
163
return true;
164
}
165
166
#undef clamp
167
#define clamp(val, min, max) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
168
169
#ifndef DEG2RAD
170
#define DEG2RAD(x) ((float)(x) * (float)(SDL_PI_F / 180.f))
171
#endif
172
173
//---------------------------------------------------------------------------
174
// Scale and clamp values to a range
175
//---------------------------------------------------------------------------
176
static float RemapValClamped(float val, float A, float B, float C, float D)
177
{
178
if (A == B) {
179
return (val - B) >= 0.0f ? D : C;
180
} else {
181
float cVal = (val - A) / (B - A);
182
cVal = clamp(cVal, 0.0f, 1.0f);
183
184
return C + (D - C) * cVal;
185
}
186
}
187
188
#define REPORT_HEADER_USB 0x07
189
#define REPORT_HEADER_BT 0x00
190
191
static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverSteamHori_Context *ctx, Uint8 *data, int size)
192
{
193
Sint16 axis;
194
Uint64 timestamp = SDL_GetTicksNS();
195
196
// Make sure it's gamepad state and not OTA FW update info
197
if (data[0] != REPORT_HEADER_USB && data[0] != REPORT_HEADER_BT) {
198
/* We don't know how to handle this report */
199
return;
200
}
201
202
#define READ_STICK_AXIS(offset) \
203
(data[offset] == 0x80 ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x80), -0x80, 0xff - 0x80, SDL_MIN_SINT16, SDL_MAX_SINT16))
204
{
205
axis = READ_STICK_AXIS(1);
206
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
207
axis = READ_STICK_AXIS(2);
208
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
209
axis = READ_STICK_AXIS(3);
210
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
211
axis = READ_STICK_AXIS(4);
212
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
213
}
214
#undef READ_STICK_AXIS
215
216
if (ctx->last_state[5] != data[5]) {
217
Uint8 hat;
218
219
switch (data[5] & 0xF) {
220
case 0:
221
hat = SDL_HAT_UP;
222
break;
223
case 1:
224
hat = SDL_HAT_RIGHTUP;
225
break;
226
case 2:
227
hat = SDL_HAT_RIGHT;
228
break;
229
case 3:
230
hat = SDL_HAT_RIGHTDOWN;
231
break;
232
case 4:
233
hat = SDL_HAT_DOWN;
234
break;
235
case 5:
236
hat = SDL_HAT_LEFTDOWN;
237
break;
238
case 6:
239
hat = SDL_HAT_LEFT;
240
break;
241
case 7:
242
hat = SDL_HAT_LEFTUP;
243
break;
244
default:
245
hat = SDL_HAT_CENTERED;
246
break;
247
}
248
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
249
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[5] & 0x10) != 0));
250
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[5] & 0x20) != 0));
251
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_QAM, ((data[5] & 0x40) != 0));
252
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[5] & 0x80) != 0));
253
254
}
255
256
if (ctx->last_state[6] != data[6]) {
257
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[6] & 0x01) != 0));
258
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M1 /* M1 */, ((data[6] & 0x02) != 0));
259
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[6] & 0x04) != 0));
260
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[6] & 0x08) != 0));
261
262
// TODO: can we handle the digital trigger mode? The data seems to come through analog regardless of the trigger state
263
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[6] & 0x40) != 0));
264
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[6] & 0x80) != 0));
265
}
266
267
if (ctx->last_state[7] != data[7]) {
268
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[7] & 0x01) != 0));
269
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0));
270
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0));
271
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0));
272
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0));
273
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0));
274
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0));
275
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));
276
}
277
278
if (!ctx->wireless && ctx->serial_needs_init) {
279
char serial[18];
280
(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
281
data[38], data[39], data[40], data[41], data[42], data[43]);
282
283
joystick->serial = SDL_strdup(serial);
284
ctx->serial_needs_init = false;
285
}
286
287
#define READ_TRIGGER_AXIS(offset) \
288
(Sint16)(((int)data[offset] * 257) - 32768)
289
{
290
axis = READ_TRIGGER_AXIS(8);
291
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
292
axis = READ_TRIGGER_AXIS(9);
293
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
294
}
295
#undef READ_TRIGGER_AXIS
296
297
if (1) {
298
Uint64 sensor_timestamp;
299
float imu_data[3];
300
301
/* 16-bit timestamp */
302
Uint32 delta;
303
Uint16 tick = LOAD16(data[10],
304
data[11]);
305
if (ctx->last_tick < tick) {
306
delta = (tick - ctx->last_tick);
307
} else {
308
delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
309
}
310
311
ctx->last_tick = tick;
312
ctx->sensor_ticks += delta;
313
314
/* Sensor timestamp is in 1us units, but there seems to be some issues with the values reported from the device */
315
sensor_timestamp = timestamp; // if the values were good we woudl call SDL_US_TO_NS(ctx->sensor_ticks);
316
317
const float accelScale = SDL_STANDARD_GRAVITY * 8 / 32768.0f;
318
const float gyroScale = DEG2RAD(2048);
319
320
imu_data[1] = RemapValClamped(-1.0f * LOAD16(data[12], data[13]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
321
imu_data[2] = RemapValClamped(-1.0f * LOAD16(data[14], data[15]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
322
imu_data[0] = RemapValClamped(-1.0f * LOAD16(data[16], data[17]), INT16_MIN, INT16_MAX, -gyroScale, gyroScale);
323
324
325
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, imu_data, 3);
326
327
// SDL_Log("%u %f, %f, %f ", data[0], imu_data[0], imu_data[1], imu_data[2] );
328
imu_data[2] = LOAD16(data[18], data[19]) * accelScale;
329
imu_data[1] = -1 * LOAD16(data[20], data[21]) * accelScale;
330
imu_data[0] = LOAD16(data[22], data[23]) * accelScale;
331
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, imu_data, 3);
332
}
333
334
if (ctx->last_state[24] != data[24]) {
335
bool bCharging = (data[24] & 0x10) != 0;
336
int percent = (data[24] & 0xF) * 10;
337
SDL_PowerState state;
338
if (bCharging) {
339
state = SDL_POWERSTATE_CHARGING;
340
} else if (ctx->wireless) {
341
state = SDL_POWERSTATE_ON_BATTERY;
342
} else {
343
state = SDL_POWERSTATE_CHARGED;
344
}
345
346
SDL_SendJoystickPowerInfo(joystick, state, percent);
347
}
348
349
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
350
}
351
352
static bool HIDAPI_DriverSteamHori_UpdateDevice(SDL_HIDAPI_Device *device)
353
{
354
SDL_DriverSteamHori_Context *ctx = (SDL_DriverSteamHori_Context *)device->context;
355
SDL_Joystick *joystick = NULL;
356
Uint8 data[USB_PACKET_LENGTH];
357
int size = 0;
358
359
if (device->num_joysticks > 0) {
360
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
361
} else {
362
return false;
363
}
364
365
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
366
#ifdef DEBUG_HORI_PROTOCOL
367
HIDAPI_DumpPacket("Google Hori packet: size = %d", data, size);
368
#endif
369
if (!joystick) {
370
continue;
371
}
372
373
HIDAPI_DriverSteamHori_HandleStatePacket(joystick, ctx, data, size);
374
}
375
376
if (size < 0) {
377
/* Read error, device is disconnected */
378
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
379
}
380
return (size >= 0);
381
}
382
383
static void HIDAPI_DriverSteamHori_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
384
{
385
}
386
387
static void HIDAPI_DriverSteamHori_FreeDevice(SDL_HIDAPI_Device *device)
388
{
389
}
390
391
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori = {
392
SDL_HINT_JOYSTICK_HIDAPI_STEAM_HORI,
393
true,
394
HIDAPI_DriverSteamHori_RegisterHints,
395
HIDAPI_DriverSteamHori_UnregisterHints,
396
HIDAPI_DriverSteamHori_IsEnabled,
397
HIDAPI_DriverSteamHori_IsSupportedDevice,
398
HIDAPI_DriverSteamHori_InitDevice,
399
HIDAPI_DriverSteamHori_GetDevicePlayerIndex,
400
HIDAPI_DriverSteamHori_SetDevicePlayerIndex,
401
HIDAPI_DriverSteamHori_UpdateDevice,
402
HIDAPI_DriverSteamHori_OpenJoystick,
403
HIDAPI_DriverSteamHori_RumbleJoystick,
404
HIDAPI_DriverSteamHori_RumbleJoystickTriggers,
405
HIDAPI_DriverSteamHori_GetJoystickCapabilities,
406
HIDAPI_DriverSteamHori_SetJoystickLED,
407
HIDAPI_DriverSteamHori_SendJoystickEffect,
408
HIDAPI_DriverSteamHori_SetJoystickSensorsEnabled,
409
HIDAPI_DriverSteamHori_CloseJoystick,
410
HIDAPI_DriverSteamHori_FreeDevice,
411
};
412
413
#endif /* SDL_JOYSTICK_HIDAPI_STEAM_HORI */
414
415
#endif /* SDL_JOYSTICK_HIDAPI */
416
417