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