Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_luna.c
9906 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_LUNA
30
31
// Define this if you want to log all packets from the controller
32
// #define DEBUG_LUNA_PROTOCOL
33
34
// Sending rumble on macOS blocks for a long time and eventually fails
35
#ifndef SDL_PLATFORM_MACOS
36
#define ENABLE_LUNA_BLUETOOTH_RUMBLE
37
#endif
38
39
enum
40
{
41
SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE = 11,
42
SDL_GAMEPAD_NUM_LUNA_BUTTONS,
43
};
44
45
typedef struct
46
{
47
Uint8 last_state[USB_PACKET_LENGTH];
48
} SDL_DriverLuna_Context;
49
50
static void HIDAPI_DriverLuna_RegisterHints(SDL_HintCallback callback, void *userdata)
51
{
52
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
53
}
54
55
static void HIDAPI_DriverLuna_UnregisterHints(SDL_HintCallback callback, void *userdata)
56
{
57
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_LUNA, callback, userdata);
58
}
59
60
static bool HIDAPI_DriverLuna_IsEnabled(void)
61
{
62
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
63
}
64
65
static bool HIDAPI_DriverLuna_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)
66
{
67
return SDL_IsJoystickAmazonLunaController(vendor_id, product_id);
68
}
69
70
static bool HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device)
71
{
72
SDL_DriverLuna_Context *ctx;
73
74
ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));
75
if (!ctx) {
76
return false;
77
}
78
device->context = ctx;
79
80
HIDAPI_SetDeviceName(device, "Amazon Luna Controller");
81
82
return HIDAPI_JoystickConnected(device, NULL);
83
}
84
85
static int HIDAPI_DriverLuna_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
86
{
87
return -1;
88
}
89
90
static void HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
91
{
92
}
93
94
static bool HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
95
{
96
SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
97
98
SDL_AssertJoysticksLocked();
99
100
SDL_zeroa(ctx->last_state);
101
102
// Initialize the joystick capabilities
103
joystick->nbuttons = SDL_GAMEPAD_NUM_LUNA_BUTTONS;
104
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
105
joystick->nhats = 1;
106
107
return true;
108
}
109
110
static bool HIDAPI_DriverLuna_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
111
{
112
#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
113
if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
114
// Same packet as on Xbox One controllers connected via Bluetooth
115
Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
116
117
// Magnitude is 1..100 so scale the 16-bit input here
118
rumble_packet[4] = (Uint8)(low_frequency_rumble / 655);
119
rumble_packet[5] = (Uint8)(high_frequency_rumble / 655);
120
121
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
122
return SDL_SetError("Couldn't send rumble packet");
123
}
124
125
return true;
126
}
127
#endif // ENABLE_LUNA_BLUETOOTH_RUMBLE
128
129
// There is currently no rumble packet over USB
130
return SDL_Unsupported();
131
}
132
133
static bool HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
134
{
135
return SDL_Unsupported();
136
}
137
138
static Uint32 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
139
{
140
Uint32 result = 0;
141
142
#ifdef ENABLE_LUNA_BLUETOOTH_RUMBLE
143
if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
144
result |= SDL_JOYSTICK_CAP_RUMBLE;
145
}
146
#endif
147
148
return result;
149
}
150
151
static bool HIDAPI_DriverLuna_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_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
157
{
158
return SDL_Unsupported();
159
}
160
161
static bool HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
162
{
163
return SDL_Unsupported();
164
}
165
166
static void HIDAPI_DriverLuna_HandleUSBStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
167
{
168
Uint64 timestamp = SDL_GetTicksNS();
169
170
if (ctx->last_state[1] != data[1]) {
171
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[1] & 0x01) != 0));
172
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[1] & 0x02) != 0));
173
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[1] & 0x04) != 0));
174
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[1] & 0x08) != 0));
175
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[1] & 0x10) != 0));
176
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[1] & 0x20) != 0));
177
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x40) != 0));
178
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x80) != 0));
179
}
180
if (ctx->last_state[2] != data[2]) {
181
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[2] & 0x01) != 0));
182
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[2] & 0x02) != 0));
183
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[2] & 0x04) != 0));
184
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[2] & 0x08) != 0));
185
}
186
187
if (ctx->last_state[3] != data[3]) {
188
Uint8 hat;
189
190
switch (data[3] & 0x0f) {
191
case 0:
192
hat = SDL_HAT_UP;
193
break;
194
case 1:
195
hat = SDL_HAT_RIGHTUP;
196
break;
197
case 2:
198
hat = SDL_HAT_RIGHT;
199
break;
200
case 3:
201
hat = SDL_HAT_RIGHTDOWN;
202
break;
203
case 4:
204
hat = SDL_HAT_DOWN;
205
break;
206
case 5:
207
hat = SDL_HAT_LEFTDOWN;
208
break;
209
case 6:
210
hat = SDL_HAT_LEFT;
211
break;
212
case 7:
213
hat = SDL_HAT_LEFTUP;
214
break;
215
default:
216
hat = SDL_HAT_CENTERED;
217
break;
218
}
219
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
220
}
221
222
#define READ_STICK_AXIS(offset) \
223
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
224
{
225
Sint16 axis = READ_STICK_AXIS(4);
226
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
227
axis = READ_STICK_AXIS(5);
228
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
229
axis = READ_STICK_AXIS(6);
230
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
231
axis = READ_STICK_AXIS(7);
232
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
233
}
234
#undef READ_STICK_AXIS
235
236
#define READ_TRIGGER_AXIS(offset) \
237
(Sint16) HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16)
238
{
239
Sint16 axis = READ_TRIGGER_AXIS(8);
240
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
241
axis = READ_TRIGGER_AXIS(9);
242
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
243
}
244
#undef READ_TRIGGER_AXIS
245
246
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
247
}
248
249
static void HIDAPI_DriverLuna_HandleBluetoothStatePacket(SDL_Joystick *joystick, SDL_DriverLuna_Context *ctx, Uint8 *data, int size)
250
{
251
Uint64 timestamp = SDL_GetTicksNS();
252
253
if (size >= 2 && data[0] == 0x02) {
254
// Home button has dedicated report
255
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x1) != 0));
256
return;
257
}
258
259
if (size >= 2 && data[0] == 0x04) {
260
// Battery level report
261
int percent = (int)SDL_roundf((data[1] / 255.0f) * 100.0f);
262
SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, percent);
263
return;
264
}
265
266
if (size < 17 || data[0] != 0x01) {
267
// We don't know how to handle this report
268
return;
269
}
270
271
if (ctx->last_state[13] != data[13]) {
272
Uint8 hat;
273
274
switch (data[13] & 0x0f) {
275
case 1:
276
hat = SDL_HAT_UP;
277
break;
278
case 2:
279
hat = SDL_HAT_RIGHTUP;
280
break;
281
case 3:
282
hat = SDL_HAT_RIGHT;
283
break;
284
case 4:
285
hat = SDL_HAT_RIGHTDOWN;
286
break;
287
case 5:
288
hat = SDL_HAT_DOWN;
289
break;
290
case 6:
291
hat = SDL_HAT_LEFTDOWN;
292
break;
293
case 7:
294
hat = SDL_HAT_LEFT;
295
break;
296
case 8:
297
hat = SDL_HAT_LEFTUP;
298
break;
299
default:
300
hat = SDL_HAT_CENTERED;
301
break;
302
}
303
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
304
}
305
306
if (ctx->last_state[14] != data[14]) {
307
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[14] & 0x01) != 0));
308
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[14] & 0x02) != 0));
309
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[14] & 0x08) != 0));
310
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[14] & 0x10) != 0));
311
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[14] & 0x40) != 0));
312
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[14] & 0x80) != 0));
313
}
314
if (ctx->last_state[15] != data[15]) {
315
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[15] & 0x08) != 0));
316
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[15] & 0x20) != 0));
317
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[15] & 0x40) != 0));
318
}
319
if (ctx->last_state[16] != data[16]) {
320
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[16] & 0x01) != 0));
321
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LUNA_MICROPHONE, ((data[16] & 0x02) != 0));
322
}
323
324
#define READ_STICK_AXIS(offset) \
325
(data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)data[offset], 0x00, 0xff, SDL_MIN_SINT16, SDL_MAX_SINT16))
326
{
327
Sint16 axis = READ_STICK_AXIS(2);
328
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
329
axis = READ_STICK_AXIS(4);
330
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
331
axis = READ_STICK_AXIS(6);
332
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
333
axis = READ_STICK_AXIS(8);
334
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
335
}
336
#undef READ_STICK_AXIS
337
338
#define READ_TRIGGER_AXIS(offset) \
339
(Sint16) HIDAPI_RemapVal((float)((int)(((data[offset] | (data[offset + 1] << 8)) & 0x3ff) - 0x200)), 0x00 - 0x200, 0x3ff - 0x200, SDL_MIN_SINT16, SDL_MAX_SINT16)
340
{
341
Sint16 axis = READ_TRIGGER_AXIS(9);
342
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
343
axis = READ_TRIGGER_AXIS(11);
344
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
345
}
346
#undef READ_TRIGGER_AXIS
347
348
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
349
}
350
351
static bool HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
352
{
353
SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
354
SDL_Joystick *joystick = NULL;
355
Uint8 data[USB_PACKET_LENGTH];
356
int size = 0;
357
358
if (device->num_joysticks > 0) {
359
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
360
} else {
361
return false;
362
}
363
364
while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
365
#ifdef DEBUG_LUNA_PROTOCOL
366
HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size);
367
#endif
368
if (!joystick) {
369
continue;
370
}
371
372
switch (size) {
373
case 10:
374
HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size);
375
break;
376
default:
377
HIDAPI_DriverLuna_HandleBluetoothStatePacket(joystick, ctx, data, size);
378
break;
379
}
380
}
381
382
if (size < 0) {
383
// Read error, device is disconnected
384
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
385
}
386
return (size >= 0);
387
}
388
389
static void HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
390
{
391
}
392
393
static void HIDAPI_DriverLuna_FreeDevice(SDL_HIDAPI_Device *device)
394
{
395
}
396
397
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = {
398
SDL_HINT_JOYSTICK_HIDAPI_LUNA,
399
true,
400
HIDAPI_DriverLuna_RegisterHints,
401
HIDAPI_DriverLuna_UnregisterHints,
402
HIDAPI_DriverLuna_IsEnabled,
403
HIDAPI_DriverLuna_IsSupportedDevice,
404
HIDAPI_DriverLuna_InitDevice,
405
HIDAPI_DriverLuna_GetDevicePlayerIndex,
406
HIDAPI_DriverLuna_SetDevicePlayerIndex,
407
HIDAPI_DriverLuna_UpdateDevice,
408
HIDAPI_DriverLuna_OpenJoystick,
409
HIDAPI_DriverLuna_RumbleJoystick,
410
HIDAPI_DriverLuna_RumbleJoystickTriggers,
411
HIDAPI_DriverLuna_GetJoystickCapabilities,
412
HIDAPI_DriverLuna_SetJoystickLED,
413
HIDAPI_DriverLuna_SendJoystickEffect,
414
HIDAPI_DriverLuna_SetJoystickSensorsEnabled,
415
HIDAPI_DriverLuna_CloseJoystick,
416
HIDAPI_DriverLuna_FreeDevice,
417
};
418
419
#endif // SDL_JOYSTICK_HIDAPI_LUNA
420
421
#endif // SDL_JOYSTICK_HIDAPI
422
423