Simple Directmedia Layer
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2024 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
22#include "SDL_internal.h"
23
24#ifdef SDL_JOYSTICK_EMSCRIPTEN
25
26#include <stdio.h> // For the definition of NULL
27
28#include "SDL_sysjoystick_c.h"
29#include "../SDL_joystick_c.h"
30
31static SDL_joylist_item *JoystickByIndex(int index);
32
33static SDL_joylist_item *SDL_joylist = NULL;
34static SDL_joylist_item *SDL_joylist_tail = NULL;
35static int numjoysticks = 0;
36
37static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
38{
39 int i;
40
41 SDL_joylist_item *item;
42
43 if (JoystickByIndex(gamepadEvent->index) != NULL) {
44 return 1;
45 }
46
47 item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item));
48 if (!item) {
49 return 1;
50 }
51
52 SDL_zerop(item);
53 item->index = gamepadEvent->index;
54
55 item->name = SDL_CreateJoystickName(0, 0, NULL, gamepadEvent->id);
56 if (!item->name) {
57 SDL_free(item);
58 return 1;
59 }
60
61 item->mapping = SDL_strdup(gamepadEvent->mapping);
62 if (!item->mapping) {
63 SDL_free(item->name);
64 SDL_free(item);
65 return 1;
66 }
67
68 item->naxes = gamepadEvent->numAxes;
69 item->nbuttons = gamepadEvent->numButtons;
70 item->device_instance = SDL_GetNextObjectID();
71
72 item->timestamp = gamepadEvent->timestamp;
73
74 for (i = 0; i < item->naxes; i++) {
75 item->axis[i] = gamepadEvent->axis[i];
76 }
77
78 for (i = 0; i < item->nbuttons; i++) {
79 item->analogButton[i] = gamepadEvent->analogButton[i];
80 item->digitalButton[i] = gamepadEvent->digitalButton[i];
81 }
82
83 if (!SDL_joylist_tail) {
84 SDL_joylist = SDL_joylist_tail = item;
85 } else {
86 SDL_joylist_tail->next = item;
87 SDL_joylist_tail = item;
88 }
89
90 ++numjoysticks;
91
92 SDL_PrivateJoystickAdded(item->device_instance);
93
94#ifdef DEBUG_JOYSTICK
95 SDL_Log("Number of joysticks is %d", numjoysticks);
96#endif
97#ifdef DEBUG_JOYSTICK
98 SDL_Log("Added joystick with index %d", item->index);
99#endif
100
101 return 1;
102}
103
104static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
105{
106 SDL_joylist_item *item = SDL_joylist;
107 SDL_joylist_item *prev = NULL;
108
109 while (item) {
110 if (item->index == gamepadEvent->index) {
111 break;
112 }
113 prev = item;
114 item = item->next;
115 }
116
117 if (!item) {
118 return 1;
119 }
120
121 if (item->joystick) {
122 item->joystick->hwdata = NULL;
123 }
124
125 if (prev) {
126 prev->next = item->next;
127 } else {
128 SDL_assert(SDL_joylist == item);
129 SDL_joylist = item->next;
130 }
131 if (item == SDL_joylist_tail) {
132 SDL_joylist_tail = prev;
133 }
134
135 // Need to decrement the joystick count before we post the event
136 --numjoysticks;
137
138 SDL_PrivateJoystickRemoved(item->device_instance);
139
140#ifdef DEBUG_JOYSTICK
141 SDL_Log("Removed joystick with id %d", item->device_instance);
142#endif
143 SDL_free(item->name);
144 SDL_free(item->mapping);
145 SDL_free(item);
146 return 1;
147}
148
149// Function to perform any system-specific joystick related cleanup
150static void EMSCRIPTEN_JoystickQuit(void)
151{
152 SDL_joylist_item *item = NULL;
153 SDL_joylist_item *next = NULL;
154
155 for (item = SDL_joylist; item; item = next) {
156 next = item->next;
157 SDL_free(item->mapping);
158 SDL_free(item->name);
159 SDL_free(item);
160 }
161
162 SDL_joylist = SDL_joylist_tail = NULL;
163
164 numjoysticks = 0;
165
166 emscripten_set_gamepadconnected_callback(NULL, 0, NULL);
167 emscripten_set_gamepaddisconnected_callback(NULL, 0, NULL);
168}
169
170// Function to scan the system for joysticks.
171static bool EMSCRIPTEN_JoystickInit(void)
172{
173 int rc, i, numjs;
174 EmscriptenGamepadEvent gamepadState;
175
176 numjoysticks = 0;
177
178 rc = emscripten_sample_gamepad_data();
179
180 // Check if gamepad is supported by browser
181 if (rc == EMSCRIPTEN_RESULT_NOT_SUPPORTED) {
182 return SDL_SetError("Gamepads not supported");
183 }
184
185 numjs = emscripten_get_num_gamepads();
186
187 // handle already connected gamepads
188 if (numjs > 0) {
189 for (i = 0; i < numjs; i++) {
190 rc = emscripten_get_gamepad_status(i, &gamepadState);
191 if (rc == EMSCRIPTEN_RESULT_SUCCESS) {
192 Emscripten_JoyStickConnected(EMSCRIPTEN_EVENT_GAMEPADCONNECTED,
193 &gamepadState,
194 NULL);
195 }
196 }
197 }
198
199 rc = emscripten_set_gamepadconnected_callback(NULL,
200 0,
201 Emscripten_JoyStickConnected);
202
203 if (rc != EMSCRIPTEN_RESULT_SUCCESS) {
204 EMSCRIPTEN_JoystickQuit();
205 return SDL_SetError("Could not set gamepad connect callback");
206 }
207
208 rc = emscripten_set_gamepaddisconnected_callback(NULL,
209 0,
210 Emscripten_JoyStickDisconnected);
211 if (rc != EMSCRIPTEN_RESULT_SUCCESS) {
212 EMSCRIPTEN_JoystickQuit();
213 return SDL_SetError("Could not set gamepad disconnect callback");
214 }
215
216 return true;
217}
218
219// Returns item matching given SDL device index.
220static SDL_joylist_item *JoystickByDeviceIndex(int device_index)
221{
222 SDL_joylist_item *item = SDL_joylist;
223
224 while (0 < device_index) {
225 --device_index;
226 item = item->next;
227 }
228
229 return item;
230}
231
232// Returns item matching given HTML gamepad index.
233static SDL_joylist_item *JoystickByIndex(int index)
234{
235 SDL_joylist_item *item = SDL_joylist;
236
237 if (index < 0) {
238 return NULL;
239 }
240
241 while (item) {
242 if (item->index == index) {
243 break;
244 }
245 item = item->next;
246 }
247
248 return item;
249}
250
251static int EMSCRIPTEN_JoystickGetCount(void)
252{
253 return numjoysticks;
254}
255
256static void EMSCRIPTEN_JoystickDetect(void)
257{
258}
259
260static bool EMSCRIPTEN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
261{
262 // We don't override any other drivers
263 return false;
264}
265
266static const char *EMSCRIPTEN_JoystickGetDeviceName(int device_index)
267{
268 return JoystickByDeviceIndex(device_index)->name;
269}
270
271static const char *EMSCRIPTEN_JoystickGetDevicePath(int device_index)
272{
273 return NULL;
274}
275
276static int EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
277{
278 return -1;
279}
280
281static int EMSCRIPTEN_JoystickGetDevicePlayerIndex(int device_index)
282{
283 return -1;
284}
285
286static void EMSCRIPTEN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
287{
288}
289
290static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index)
291{
292 return JoystickByDeviceIndex(device_index)->device_instance;
293}
294
295static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
296{
297 SDL_joylist_item *item = JoystickByDeviceIndex(device_index);
298
299 if (!item) {
300 return SDL_SetError("No such device");
301 }
302
303 if (item->joystick) {
304 return SDL_SetError("Joystick already opened");
305 }
306
307 joystick->hwdata = (struct joystick_hwdata *)item;
308 item->joystick = joystick;
309
310 // HTML5 Gamepad API doesn't say anything about these
311 joystick->nhats = 0;
312
313 joystick->nbuttons = item->nbuttons;
314 joystick->naxes = item->naxes;
315
316 return true;
317}
318
319/* Function to update the state of a joystick - called as a device poll.
320 * This function shouldn't update the joystick structure directly,
321 * but instead should call SDL_PrivateJoystick*() to deliver events
322 * and update joystick device state.
323 */
324static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
325{
326 EmscriptenGamepadEvent gamepadState;
327 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
328 int i, result;
329 Uint64 timestamp = SDL_GetTicksNS();
330
331 emscripten_sample_gamepad_data();
332
333 if (item) {
334 result = emscripten_get_gamepad_status(item->index, &gamepadState);
335 if (result == EMSCRIPTEN_RESULT_SUCCESS) {
336 if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) {
337 for (i = 0; i < item->nbuttons; i++) {
338 if (item->digitalButton[i] != gamepadState.digitalButton[i]) {
339 bool down = (gamepadState.digitalButton[i] != 0);
340 SDL_SendJoystickButton(timestamp, item->joystick, i, down);
341 }
342
343 // store values to compare them in the next update
344 item->analogButton[i] = gamepadState.analogButton[i];
345 item->digitalButton[i] = gamepadState.digitalButton[i];
346 }
347
348 for (i = 0; i < item->naxes; i++) {
349 if (item->axis[i] != gamepadState.axis[i]) {
350 // do we need to do conversion?
351 SDL_SendJoystickAxis(timestamp, item->joystick, i,
352 (Sint16)(32767. * gamepadState.axis[i]));
353 }
354
355 // store to compare in next update
356 item->axis[i] = gamepadState.axis[i];
357 }
358
359 item->timestamp = gamepadState.timestamp;
360 }
361 }
362 }
363}
364
365// Function to close a joystick after use
366static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick)
367{
368 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata;
369 if (item) {
370 item->joystick = NULL;
371 }
372}
373
374static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index)
375{
376 // the GUID is just the name for now
377 const char *name = EMSCRIPTEN_JoystickGetDeviceName(device_index);
378 return SDL_CreateJoystickGUIDForName(name);
379}
380
381static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
382{
383 return SDL_Unsupported();
384}
385
386static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
387{
388 return SDL_Unsupported();
389}
390
391static bool EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
392{
393 return false;
394}
395
396static bool EMSCRIPTEN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
397{
398 return SDL_Unsupported();
399}
400
401static bool EMSCRIPTEN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
402{
403 return SDL_Unsupported();
404}
405
406static bool EMSCRIPTEN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
407{
408 return SDL_Unsupported();
409}
410
411SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = {
412 EMSCRIPTEN_JoystickInit,
413 EMSCRIPTEN_JoystickGetCount,
414 EMSCRIPTEN_JoystickDetect,
415 EMSCRIPTEN_JoystickIsDevicePresent,
416 EMSCRIPTEN_JoystickGetDeviceName,
417 EMSCRIPTEN_JoystickGetDevicePath,
418 EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot,
419 EMSCRIPTEN_JoystickGetDevicePlayerIndex,
420 EMSCRIPTEN_JoystickSetDevicePlayerIndex,
421 EMSCRIPTEN_JoystickGetDeviceGUID,
422 EMSCRIPTEN_JoystickGetDeviceInstanceID,
423 EMSCRIPTEN_JoystickOpen,
424 EMSCRIPTEN_JoystickRumble,
425 EMSCRIPTEN_JoystickRumbleTriggers,
426 EMSCRIPTEN_JoystickSetLED,
427 EMSCRIPTEN_JoystickSendEffect,
428 EMSCRIPTEN_JoystickSetSensorsEnabled,
429 EMSCRIPTEN_JoystickUpdate,
430 EMSCRIPTEN_JoystickClose,
431 EMSCRIPTEN_JoystickQuit,
432 EMSCRIPTEN_JoystickGetGamepadMapping
433};
434
435#endif // SDL_JOYSTICK_EMSCRIPTEN