summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c')
-rw-r--r--contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c828
1 files changed, 828 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c b/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c
new file mode 100644
index 0000000..6cf0a90
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/gdk/SDL_gameinputjoystick.c
@@ -0,0 +1,828 @@
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_GAMEINPUT
24
25#include "../SDL_sysjoystick.h"
26#include "../usb_ids.h"
27#include "../../core/windows/SDL_gameinput.h"
28
29// Default value for SDL_HINT_JOYSTICK_GAMEINPUT
30#if defined(SDL_PLATFORM_GDK)
31#define SDL_GAMEINPUT_DEFAULT true
32#else
33#define SDL_GAMEINPUT_DEFAULT false
34#endif
35
36enum
37{
38 SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE = 11
39};
40
41typedef struct GAMEINPUT_InternalDevice
42{
43 IGameInputDevice *device;
44 char path[(APP_LOCAL_DEVICE_ID_SIZE * 2) + 1];
45 char *name;
46 SDL_GUID guid; // generated by SDL
47 SDL_JoystickID device_instance; // generated by SDL
48 const GameInputDeviceInfo *info;
49 bool isAdded;
50 bool isDeleteRequested;
51} GAMEINPUT_InternalDevice;
52
53typedef struct GAMEINPUT_InternalList
54{
55 GAMEINPUT_InternalDevice **devices;
56 int count;
57} GAMEINPUT_InternalList;
58
59typedef struct joystick_hwdata
60{
61 GAMEINPUT_InternalDevice *devref;
62 bool report_sensors;
63 GameInputRumbleParams rumbleParams;
64 GameInputCallbackToken system_button_callback_token;
65} GAMEINPUT_InternalJoystickHwdata;
66
67static GAMEINPUT_InternalList g_GameInputList = { NULL };
68static IGameInput *g_pGameInput = NULL;
69static GameInputCallbackToken g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
70static Uint64 g_GameInputTimestampOffset;
71
72static bool GAMEINPUT_InternalIsGamepad(const GameInputDeviceInfo *info)
73{
74 if (info->supportedInput & GameInputKindGamepad) {
75 return true;
76 }
77 return false;
78}
79
80static bool GAMEINPUT_InternalAddOrFind(IGameInputDevice *pDevice)
81{
82 GAMEINPUT_InternalDevice **devicelist = NULL;
83 GAMEINPUT_InternalDevice *elem = NULL;
84 const GameInputDeviceInfo *info = NULL;
85 Uint16 bus = SDL_HARDWARE_BUS_USB;
86 Uint16 vendor = 0;
87 Uint16 product = 0;
88 Uint16 version = 0;
89 const char *manufacturer_string = NULL;
90 const char *product_string = NULL;
91 char tmp[4];
92 int idx = 0;
93
94 SDL_AssertJoysticksLocked();
95
96 info = IGameInputDevice_GetDeviceInfo(pDevice);
97 if (info->capabilities & GameInputDeviceCapabilityWireless) {
98 bus = SDL_HARDWARE_BUS_BLUETOOTH;
99 } else {
100 bus = SDL_HARDWARE_BUS_USB;
101 }
102 vendor = info->vendorId;
103 product = info->productId;
104 version = (info->firmwareVersion.major << 8) | info->firmwareVersion.minor;
105
106 if (SDL_JoystickHandledByAnotherDriver(&SDL_GAMEINPUT_JoystickDriver, vendor, product, version, "")) {
107 return true;
108 }
109
110 for (idx = 0; idx < g_GameInputList.count; ++idx) {
111 elem = g_GameInputList.devices[idx];
112 if (elem && elem->device == pDevice) {
113 // we're already added
114 elem->isDeleteRequested = false;
115 return true;
116 }
117 }
118
119 elem = (GAMEINPUT_InternalDevice *)SDL_calloc(1, sizeof(*elem));
120 if (!elem) {
121 return false;
122 }
123
124 devicelist = (GAMEINPUT_InternalDevice **)SDL_realloc(g_GameInputList.devices, sizeof(elem) * (g_GameInputList.count + 1LL));
125 if (!devicelist) {
126 SDL_free(elem);
127 return false;
128 }
129
130 // Generate a device path
131 for (idx = 0; idx < APP_LOCAL_DEVICE_ID_SIZE; ++idx) {
132 SDL_snprintf(tmp, SDL_arraysize(tmp), "%02hhX", info->deviceId.value[idx]);
133 SDL_strlcat(elem->path, tmp, SDL_arraysize(tmp));
134 }
135
136 if (info->deviceStrings) {
137 // In theory we could get the manufacturer and product strings here, but they're NULL for all the controllers I've tested
138 }
139
140 if (info->displayName) {
141 // This could give us a product string, but it's NULL for all the controllers I've tested
142 }
143
144 IGameInputDevice_AddRef(pDevice);
145 elem->device = pDevice;
146 elem->name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
147 elem->guid = SDL_CreateJoystickGUID(bus, vendor, product, version, manufacturer_string, product_string, 'g', 0);
148 elem->device_instance = SDL_GetNextObjectID();
149 elem->info = info;
150
151 g_GameInputList.devices = devicelist;
152 g_GameInputList.devices[g_GameInputList.count++] = elem;
153
154 return true;
155}
156
157static bool GAMEINPUT_InternalRemoveByIndex(int idx)
158{
159 GAMEINPUT_InternalDevice **devicelist = NULL;
160 GAMEINPUT_InternalDevice *elem;
161 int bytes = 0;
162
163 SDL_AssertJoysticksLocked();
164
165 if (idx < 0 || idx >= g_GameInputList.count) {
166 return SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
167 }
168
169 elem = g_GameInputList.devices[idx];
170 if (elem) {
171 IGameInputDevice_Release(elem->device);
172 SDL_free(elem->name);
173 SDL_free(elem);
174 }
175 g_GameInputList.devices[idx] = NULL;
176
177 if (g_GameInputList.count == 1) {
178 // last element in the list, free the entire list then
179 SDL_free(g_GameInputList.devices);
180 g_GameInputList.devices = NULL;
181 } else {
182 if (idx != g_GameInputList.count - 1) {
183 bytes = sizeof(*devicelist) * (g_GameInputList.count - idx);
184 SDL_memmove(&g_GameInputList.devices[idx], &g_GameInputList.devices[idx + 1], bytes);
185 }
186 }
187
188 // decrement the count and return
189 --g_GameInputList.count;
190 return true;
191}
192
193static GAMEINPUT_InternalDevice *GAMEINPUT_InternalFindByIndex(int idx)
194{
195 // We're guaranteed that the index is in range when this is called
196 SDL_AssertJoysticksLocked();
197 return g_GameInputList.devices[idx];
198}
199
200static void CALLBACK GAMEINPUT_InternalJoystickDeviceCallback(
201 _In_ GameInputCallbackToken callbackToken,
202 _In_ void* context,
203 _In_ IGameInputDevice* device,
204 _In_ uint64_t timestamp,
205 _In_ GameInputDeviceStatus currentStatus,
206 _In_ GameInputDeviceStatus previousStatus)
207{
208 int idx = 0;
209 GAMEINPUT_InternalDevice *elem = NULL;
210
211 if (!device) {
212 // This should never happen, but ignore it if it does
213 return;
214 }
215
216 SDL_LockJoysticks();
217
218 if (currentStatus & GameInputDeviceConnected) {
219 GAMEINPUT_InternalAddOrFind(device);
220 } else {
221 for (idx = 0; idx < g_GameInputList.count; ++idx) {
222 elem = g_GameInputList.devices[idx];
223 if (elem && elem->device == device) {
224 // will be deleted on the next Detect call
225 elem->isDeleteRequested = true;
226 break;
227 }
228 }
229 }
230
231 SDL_UnlockJoysticks();
232}
233
234static void GAMEINPUT_JoystickDetect(void);
235
236static bool GAMEINPUT_JoystickInit(void)
237{
238 HRESULT hR;
239
240 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_GAMEINPUT, SDL_GAMEINPUT_DEFAULT)) {
241 return true;
242 }
243
244 if (!SDL_InitGameInput(&g_pGameInput)) {
245 return false;
246 }
247
248 hR = IGameInput_RegisterDeviceCallback(g_pGameInput,
249 NULL,
250 GameInputKindController,
251 GameInputDeviceConnected,
252 GameInputBlockingEnumeration,
253 NULL,
254 GAMEINPUT_InternalJoystickDeviceCallback,
255 &g_GameInputCallbackToken);
256 if (FAILED(hR)) {
257 return SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08lX", hR);
258 }
259
260 // Calculate the relative offset between SDL timestamps and GameInput timestamps
261 Uint64 now = SDL_GetTicksNS();
262 uint64_t timestampUS = IGameInput_GetCurrentTimestamp(g_pGameInput);
263 g_GameInputTimestampOffset = (SDL_NS_TO_US(now) - timestampUS);
264
265 GAMEINPUT_JoystickDetect();
266
267 return true;
268}
269
270static int GAMEINPUT_JoystickGetCount(void)
271{
272 SDL_AssertJoysticksLocked();
273
274 return g_GameInputList.count;
275}
276
277static void GAMEINPUT_JoystickDetect(void)
278{
279 int idx;
280 GAMEINPUT_InternalDevice *elem = NULL;
281
282 SDL_AssertJoysticksLocked();
283
284 for (idx = 0; idx < g_GameInputList.count; ++idx) {
285 elem = g_GameInputList.devices[idx];
286 if (!elem) {
287 continue;
288 }
289
290 if (!elem->isAdded) {
291 SDL_PrivateJoystickAdded(elem->device_instance);
292 elem->isAdded = true;
293 }
294
295 if (elem->isDeleteRequested || !(IGameInputDevice_GetDeviceStatus(elem->device) & GameInputDeviceConnected)) {
296 SDL_PrivateJoystickRemoved(elem->device_instance);
297 GAMEINPUT_InternalRemoveByIndex(idx--);
298 }
299 }
300}
301
302static bool GAMEINPUT_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
303{
304 SDL_AssertJoysticksLocked();
305
306 if (g_pGameInput) {
307 if (vendor_id == USB_VENDOR_MICROSOFT &&
308 product_id == USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER) {
309 // The Xbox One controller shows up as a hardcoded raw input VID/PID, which we definitely handle
310 return true;
311 }
312
313 for (int i = 0; i < g_GameInputList.count; ++i) {
314 GAMEINPUT_InternalDevice *elem = g_GameInputList.devices[i];
315 if (elem && vendor_id == elem->info->vendorId && product_id == elem->info->productId) {
316 return true;
317 }
318 }
319 }
320 return false;
321}
322
323static const char *GAMEINPUT_JoystickGetDeviceName(int device_index)
324{
325 return GAMEINPUT_InternalFindByIndex(device_index)->name;
326}
327
328static const char *GAMEINPUT_JoystickGetDevicePath(int device_index)
329{
330 // APP_LOCAL_DEVICE_ID as a hex string, since it's required for some association callbacks
331 return GAMEINPUT_InternalFindByIndex(device_index)->path;
332}
333
334static int GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
335{
336 return -1;
337}
338
339static int GAMEINPUT_JoystickGetDevicePlayerIndex(int device_index)
340{
341 return -1;
342}
343
344static void GAMEINPUT_JoystickSetDevicePlayerIndex(int device_index, int player_index)
345{
346}
347
348static SDL_GUID GAMEINPUT_JoystickGetDeviceGUID(int device_index)
349{
350 return GAMEINPUT_InternalFindByIndex(device_index)->guid;
351}
352
353static SDL_JoystickID GAMEINPUT_JoystickGetDeviceInstanceID(int device_index)
354{
355 return GAMEINPUT_InternalFindByIndex(device_index)->device_instance;
356}
357
358static void GAMEINPUT_UpdatePowerInfo(SDL_Joystick *joystick, IGameInputDevice *device)
359{
360 GameInputBatteryState battery_state;
361 SDL_PowerState state;
362 int percent = 0;
363
364 SDL_zero(battery_state);
365 IGameInputDevice_GetBatteryState(device, &battery_state);
366
367 switch (battery_state.status) {
368 case GameInputBatteryNotPresent:
369 state = SDL_POWERSTATE_NO_BATTERY;
370 break;
371 case GameInputBatteryDischarging:
372 state = SDL_POWERSTATE_ON_BATTERY;
373 break;
374 case GameInputBatteryIdle:
375 state = SDL_POWERSTATE_CHARGED;
376 break;
377 case GameInputBatteryCharging:
378 state = SDL_POWERSTATE_CHARGING;
379 break;
380 default:
381 state = SDL_POWERSTATE_UNKNOWN;
382 break;
383 }
384 if (battery_state.fullChargeCapacity > 0.0f) {
385 percent = (int)SDL_roundf((battery_state.remainingCapacity / battery_state.fullChargeCapacity) * 100.0f);
386 }
387 SDL_SendJoystickPowerInfo(joystick, state, percent);
388}
389
390#ifdef IGameInput_RegisterSystemButtonCallback
391
392static void CALLBACK GAMEINPUT_InternalSystemButtonCallback(
393 _In_ GameInputCallbackToken callbackToken,
394 _In_ void * context,
395 _In_ IGameInputDevice * device,
396 _In_ uint64_t timestampUS,
397 _In_ GameInputSystemButtons currentButtons,
398 _In_ GameInputSystemButtons previousButtons)
399{
400 SDL_Joystick *joystick = (SDL_Joystick *)context;
401
402 GameInputSystemButtons changedButtons = (previousButtons ^ currentButtons);
403 if (changedButtons) {
404 Uint64 timestamp = SDL_US_TO_NS(timestampUS + g_GameInputTimestampOffset);
405
406 SDL_LockJoysticks();
407 if (changedButtons & GameInputSystemButtonGuide) {
408 bool down = ((currentButtons & GameInputSystemButtonGuide) != 0);
409 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, down);
410 }
411 if (changedButtons & GameInputSystemButtonShare) {
412 bool down = ((currentButtons & GameInputSystemButtonShare) != 0);
413 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE, down);
414 }
415 SDL_UnlockJoysticks();
416 }
417}
418
419#endif // IGameInput_RegisterSystemButtonCallback
420
421static bool GAMEINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
422{
423 GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
424 const GameInputDeviceInfo *info = elem->info;
425 GAMEINPUT_InternalJoystickHwdata *hwdata = NULL;
426
427 if (!elem) {
428 return false;
429 }
430
431 hwdata = (GAMEINPUT_InternalJoystickHwdata *)SDL_calloc(1, sizeof(*hwdata));
432 if (!hwdata) {
433 return false;
434 }
435
436 hwdata->devref = elem;
437
438 joystick->hwdata = hwdata;
439 if (GAMEINPUT_InternalIsGamepad(info)) {
440 joystick->naxes = 6;
441 joystick->nbuttons = 11;
442 joystick->nhats = 1;
443
444#ifdef IGameInput_RegisterSystemButtonCallback
445 if (info->supportedSystemButtons != GameInputSystemButtonNone) {
446 if (info->supportedSystemButtons & GameInputSystemButtonShare) {
447 ++joystick->nbuttons;
448 }
449
450#if 1 // The C macro in GameInput.h version 10.0.26100 refers to a focus policy which I guess has been removed from the final API?
451#undef IGameInput_RegisterSystemButtonCallback
452#define IGameInput_RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken) ((This)->lpVtbl->RegisterSystemButtonCallback(This, device, buttonFilter, context, callbackFunc, callbackToken))
453#endif
454 IGameInput_RegisterSystemButtonCallback(g_pGameInput, elem->device, (GameInputSystemButtonGuide | GameInputSystemButtonShare), joystick, GAMEINPUT_InternalSystemButtonCallback, &hwdata->system_button_callback_token);
455 }
456#endif // IGameInput_RegisterSystemButtonCallback
457 } else {
458 joystick->naxes = info->controllerAxisCount;
459 joystick->nbuttons = info->controllerButtonCount;
460 joystick->nhats = info->controllerSwitchCount;
461 }
462
463 if (info->supportedRumbleMotors & (GameInputRumbleLowFrequency | GameInputRumbleHighFrequency)) {
464 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
465 }
466 if (info->supportedRumbleMotors & (GameInputRumbleLeftTrigger | GameInputRumbleRightTrigger)) {
467 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
468 }
469
470 if (info->supportedInput & GameInputKindTouch) {
471 SDL_PrivateJoystickAddTouchpad(joystick, info->touchPointCount);
472 }
473
474 if (info->supportedInput & GameInputKindMotion) {
475 // FIXME: What's the sensor update rate?
476 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
477 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
478 }
479
480 if (info->capabilities & GameInputDeviceCapabilityWireless) {
481 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
482 } else {
483 joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRED;
484 }
485 return true;
486}
487
488static bool GAMEINPUT_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
489{
490 // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it
491 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
492 GameInputRumbleParams *params = &hwdata->rumbleParams;
493 params->lowFrequency = (float)low_frequency_rumble / (float)SDL_MAX_UINT16;
494 params->highFrequency = (float)high_frequency_rumble / (float)SDL_MAX_UINT16;
495 IGameInputDevice_SetRumbleState(hwdata->devref->device, params);
496 return true;
497}
498
499static bool GAMEINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
500{
501 // don't check for caps here, since SetRumbleState doesn't return any result - we don't need to check it
502 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
503 GameInputRumbleParams *params = &hwdata->rumbleParams;
504 params->leftTrigger = (float)left_rumble / (float)SDL_MAX_UINT16;
505 params->rightTrigger = (float)right_rumble / (float)SDL_MAX_UINT16;
506 IGameInputDevice_SetRumbleState(hwdata->devref->device, params);
507 return true;
508}
509
510static bool GAMEINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
511{
512 return SDL_Unsupported();
513}
514
515static bool GAMEINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
516{
517 return SDL_Unsupported();
518}
519
520static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
521{
522 joystick->hwdata->report_sensors = enabled;
523 return true;
524}
525
526static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
527{
528 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
529 IGameInputDevice *device = hwdata->devref->device;
530 const GameInputDeviceInfo *info = hwdata->devref->info;
531 IGameInputReading *reading = NULL;
532 Uint64 timestamp;
533 GameInputGamepadState state;
534 HRESULT hR;
535
536 hR = IGameInput_GetCurrentReading(g_pGameInput, info->supportedInput, device, &reading);
537 if (FAILED(hR)) {
538 // don't SetError here since there can be a legitimate case when there's no reading avail
539 return;
540 }
541
542 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + g_GameInputTimestampOffset);
543
544 if (GAMEINPUT_InternalIsGamepad(info)) {
545 static WORD s_XInputButtons[] = {
546 GameInputGamepadA, // SDL_GAMEPAD_BUTTON_SOUTH
547 GameInputGamepadB, // SDL_GAMEPAD_BUTTON_EAST
548 GameInputGamepadX, // SDL_GAMEPAD_BUTTON_WEST
549 GameInputGamepadY, // SDL_GAMEPAD_BUTTON_NORTH
550 GameInputGamepadView, // SDL_GAMEPAD_BUTTON_BACK
551 0, // The guide button is not available
552 GameInputGamepadMenu, // SDL_GAMEPAD_BUTTON_START
553 GameInputGamepadLeftThumbstick, // SDL_GAMEPAD_BUTTON_LEFT_STICK
554 GameInputGamepadRightThumbstick, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
555 GameInputGamepadLeftShoulder, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
556 GameInputGamepadRightShoulder, // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
557 };
558 Uint8 btnidx = 0, hat = 0;
559
560 if (IGameInputReading_GetGamepadState(reading, &state)) {
561 for (btnidx = 0; btnidx < SDL_arraysize(s_XInputButtons); ++btnidx) {
562 WORD button_mask = s_XInputButtons[btnidx];
563 if (!button_mask) {
564 continue;
565 }
566 bool down = ((state.buttons & button_mask) != 0);
567 SDL_SendJoystickButton(timestamp, joystick, btnidx, down);
568 }
569
570 if (state.buttons & GameInputGamepadDPadUp) {
571 hat |= SDL_HAT_UP;
572 }
573 if (state.buttons & GameInputGamepadDPadDown) {
574 hat |= SDL_HAT_DOWN;
575 }
576 if (state.buttons & GameInputGamepadDPadLeft) {
577 hat |= SDL_HAT_LEFT;
578 }
579 if (state.buttons & GameInputGamepadDPadRight) {
580 hat |= SDL_HAT_RIGHT;
581 }
582 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
583
584#define CONVERT_AXIS(v) (Sint16)(((v) < 0.0f) ? ((v)*32768.0f) : ((v)*32767.0f))
585 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, CONVERT_AXIS(state.leftThumbstickX));
586 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, CONVERT_AXIS(-state.leftThumbstickY));
587 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, CONVERT_AXIS(state.rightThumbstickX));
588 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, CONVERT_AXIS(-state.rightThumbstickY));
589#undef CONVERT_AXIS
590#define CONVERT_TRIGGER(v) (Sint16)((v)*65535.0f - 32768.0f)
591 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, CONVERT_TRIGGER(state.leftTrigger));
592 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, CONVERT_TRIGGER(state.rightTrigger));
593#undef CONVERT_TRIGGER
594 }
595 } else {
596 bool *button_state = SDL_stack_alloc(bool, info->controllerButtonCount);
597 float *axis_state = SDL_stack_alloc(float, info->controllerAxisCount);
598 GameInputSwitchPosition *switch_state = SDL_stack_alloc(GameInputSwitchPosition, info->controllerSwitchCount);
599
600 if (button_state) {
601 uint32_t i;
602 uint32_t button_count = IGameInputReading_GetControllerButtonState(reading, info->controllerButtonCount, button_state);
603 for (i = 0; i < button_count; ++i) {
604 SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, button_state[i]);
605 }
606 SDL_stack_free(button_state);
607 }
608
609#define CONVERT_AXIS(v) (Sint16)((v)*65535.0f - 32768.0f)
610 if (axis_state) {
611 uint32_t i;
612 uint32_t axis_count = IGameInputReading_GetControllerAxisState(reading, info->controllerAxisCount, axis_state);
613 for (i = 0; i < axis_count; ++i) {
614 SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, CONVERT_AXIS(axis_state[i]));
615 }
616 SDL_stack_free(axis_state);
617 }
618#undef CONVERT_AXIS
619
620 if (switch_state) {
621 uint32_t i;
622 uint32_t switch_count = IGameInputReading_GetControllerSwitchState(reading, info->controllerSwitchCount, switch_state);
623 for (i = 0; i < switch_count; ++i) {
624 Uint8 hat;
625 switch (switch_state[i]) {
626 case GameInputSwitchUp:
627 hat = SDL_HAT_UP;
628 break;
629 case GameInputSwitchUpRight:
630 hat = SDL_HAT_UP | SDL_HAT_RIGHT;
631 break;
632 case GameInputSwitchRight:
633 hat = SDL_HAT_RIGHT;
634 break;
635 case GameInputSwitchDownRight:
636 hat = SDL_HAT_DOWN | SDL_HAT_RIGHT;
637 break;
638 case GameInputSwitchDown:
639 hat = SDL_HAT_DOWN;
640 break;
641 case GameInputSwitchDownLeft:
642 hat = SDL_HAT_DOWN | SDL_HAT_LEFT;
643 break;
644 case GameInputSwitchLeft:
645 hat = SDL_HAT_LEFT;
646 break;
647 case GameInputSwitchUpLeft:
648 hat = SDL_HAT_UP | SDL_HAT_LEFT;
649 break;
650 case GameInputSwitchCenter:
651 default:
652 hat = SDL_HAT_CENTERED;
653 break;
654 }
655 SDL_SendJoystickHat(timestamp, joystick, (Uint8)i, hat);
656 }
657 SDL_stack_free(switch_state);
658 }
659 }
660
661 if (info->supportedInput & GameInputKindTouch) {
662 GameInputTouchState *touch_state = SDL_stack_alloc(GameInputTouchState, info->touchPointCount);
663 if (touch_state) {
664 uint32_t i;
665 uint32_t touch_count = IGameInputReading_GetTouchState(reading, info->touchPointCount, touch_state);
666 for (i = 0; i < touch_count; ++i) {
667 GameInputTouchState *touch = &touch_state[i];
668 // FIXME: We should use touch->touchId to track fingers instead of using i below
669 SDL_SendJoystickTouchpad(timestamp, joystick, 0, i, true, touch->positionX * info->touchSensorInfo[i].resolutionX, touch->positionY * info->touchSensorInfo[0].resolutionY, touch->pressure);
670 }
671 SDL_stack_free(touch_state);
672 }
673 }
674
675 if (hwdata->report_sensors) {
676 GameInputMotionState motion_state;
677
678 if (IGameInputReading_GetMotionState(reading, &motion_state)) {
679 // FIXME: How do we interpret the motion data?
680 }
681 }
682
683 IGameInputReading_Release(reading);
684
685 // FIXME: We can poll this at a much lower rate
686 GAMEINPUT_UpdatePowerInfo(joystick, device);
687}
688
689static void GAMEINPUT_JoystickClose(SDL_Joystick* joystick)
690{
691 GAMEINPUT_InternalJoystickHwdata *hwdata = joystick->hwdata;
692
693 if (hwdata->system_button_callback_token) {
694 IGameInput_UnregisterCallback(g_pGameInput, hwdata->system_button_callback_token, 5000);
695 }
696 SDL_free(hwdata);
697
698 joystick->hwdata = NULL;
699}
700
701static void GAMEINPUT_JoystickQuit(void)
702{
703 if (g_pGameInput) {
704 // free the callback
705 IGameInput_UnregisterCallback(g_pGameInput, g_GameInputCallbackToken, /*timeoutInUs:*/ 10000);
706 g_GameInputCallbackToken = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
707
708 // free the list
709 while (g_GameInputList.count > 0) {
710 GAMEINPUT_InternalRemoveByIndex(0);
711 }
712
713 SDL_QuitGameInput();
714 g_pGameInput = NULL;
715 }
716}
717
718static bool GAMEINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
719{
720 GAMEINPUT_InternalDevice *elem = GAMEINPUT_InternalFindByIndex(device_index);
721
722 if (!GAMEINPUT_InternalIsGamepad(elem->info)) {
723 return false;
724 }
725
726 out->a.kind = EMappingKind_Button;
727 out->a.target = SDL_GAMEPAD_BUTTON_SOUTH;
728
729 out->b.kind = EMappingKind_Button;
730 out->b.target = SDL_GAMEPAD_BUTTON_EAST;
731
732 out->x.kind = EMappingKind_Button;
733 out->x.target = SDL_GAMEPAD_BUTTON_WEST;
734
735 out->y.kind = EMappingKind_Button;
736 out->y.target = SDL_GAMEPAD_BUTTON_NORTH;
737
738 out->back.kind = EMappingKind_Button;
739 out->back.target = SDL_GAMEPAD_BUTTON_BACK;
740
741#ifdef IGameInput_RegisterSystemButtonCallback
742 if (elem->info->supportedSystemButtons & GameInputSystemButtonGuide) {
743 out->guide.kind = EMappingKind_Button;
744 out->guide.target = SDL_GAMEPAD_BUTTON_GUIDE;
745 }
746
747 if (elem->info->supportedSystemButtons & GameInputSystemButtonShare) {
748 out->misc1.kind = EMappingKind_Button;
749 out->misc1.target = SDL_GAMEPAD_BUTTON_GAMEINPUT_SHARE;
750 }
751#endif
752
753 out->start.kind = EMappingKind_Button;
754 out->start.target = SDL_GAMEPAD_BUTTON_START;
755
756 out->leftstick.kind = EMappingKind_Button;
757 out->leftstick.target = SDL_GAMEPAD_BUTTON_LEFT_STICK;
758
759 out->rightstick.kind = EMappingKind_Button;
760 out->rightstick.target = SDL_GAMEPAD_BUTTON_RIGHT_STICK;
761
762 out->leftshoulder.kind = EMappingKind_Button;
763 out->leftshoulder.target = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER;
764
765 out->rightshoulder.kind = EMappingKind_Button;
766 out->rightshoulder.target = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER;
767
768 out->dpup.kind = EMappingKind_Hat;
769 out->dpup.target = SDL_HAT_UP;
770
771 out->dpdown.kind = EMappingKind_Hat;
772 out->dpdown.target = SDL_HAT_DOWN;
773
774 out->dpleft.kind = EMappingKind_Hat;
775 out->dpleft.target = SDL_HAT_LEFT;
776
777 out->dpright.kind = EMappingKind_Hat;
778 out->dpright.target = SDL_HAT_RIGHT;
779
780 out->leftx.kind = EMappingKind_Axis;
781 out->leftx.target = SDL_GAMEPAD_AXIS_LEFTX;
782
783 out->lefty.kind = EMappingKind_Axis;
784 out->lefty.target = SDL_GAMEPAD_AXIS_LEFTY;
785
786 out->rightx.kind = EMappingKind_Axis;
787 out->rightx.target = SDL_GAMEPAD_AXIS_RIGHTX;
788
789 out->righty.kind = EMappingKind_Axis;
790 out->righty.target = SDL_GAMEPAD_AXIS_RIGHTY;
791
792 out->lefttrigger.kind = EMappingKind_Axis;
793 out->lefttrigger.target = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
794
795 out->righttrigger.kind = EMappingKind_Axis;
796 out->righttrigger.target = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
797
798 return true;
799}
800
801
802SDL_JoystickDriver SDL_GAMEINPUT_JoystickDriver =
803{
804 GAMEINPUT_JoystickInit,
805 GAMEINPUT_JoystickGetCount,
806 GAMEINPUT_JoystickDetect,
807 GAMEINPUT_JoystickIsDevicePresent,
808 GAMEINPUT_JoystickGetDeviceName,
809 GAMEINPUT_JoystickGetDevicePath,
810 GAMEINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
811 GAMEINPUT_JoystickGetDevicePlayerIndex,
812 GAMEINPUT_JoystickSetDevicePlayerIndex,
813 GAMEINPUT_JoystickGetDeviceGUID,
814 GAMEINPUT_JoystickGetDeviceInstanceID,
815 GAMEINPUT_JoystickOpen,
816 GAMEINPUT_JoystickRumble,
817 GAMEINPUT_JoystickRumbleTriggers,
818 GAMEINPUT_JoystickSetLED,
819 GAMEINPUT_JoystickSendEffect,
820 GAMEINPUT_JoystickSetSensorsEnabled,
821 GAMEINPUT_JoystickUpdate,
822 GAMEINPUT_JoystickClose,
823 GAMEINPUT_JoystickQuit,
824 GAMEINPUT_JoystickGetGamepadMapping
825};
826
827
828#endif // SDL_JOYSTICK_GAMEINPUT