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