src/joystick/emscripten/SDL_sysjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 03 Jan 2017 23:39:28 -0800
changeset 10745 7461fcef6ae2
parent 10737 3406a0f8b041
child 10780 4ea5472ed455
permissions -rw-r--r--
Fixed binding the D-pad on some Super NES style controllers
Fixed a case where partial trigger pull could be bound to another button

There is a fundamental problem not resolved by this commit:

Some controllers have axes (triggers, pedals, etc.) that don't start at zero, but we're guaranteed that if we get a value that it's correct. For these controllers, the current code works, where we take the first value we get and use that as the zero point and generate axis motion starting from that point on.

Other controllers have digital axes (D-pad) that assume a zero starting point, and the first value we get is the min or max axis value when the D-pad is moved. For these controllers, the current code thinks that the zero point is the axis value after the D-pad motion and this doesn't work.

My hypothesis is that the first class of devices is more common and that we should solve for that, and add an exception to SDL_JoystickAxesCenteredAtZero() as needed for the second class of devices.
icculus@9278
     1
/*
icculus@9278
     2
  Simple DirectMedia Layer
slouken@10737
     3
  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
icculus@9278
     4
icculus@9278
     5
  This software is provided 'as-is', without any express or implied
icculus@9278
     6
  warranty.  In no event will the authors be held liable for any damages
icculus@9278
     7
  arising from the use of this software.
icculus@9278
     8
icculus@9278
     9
  Permission is granted to anyone to use this software for any purpose,
icculus@9278
    10
  including commercial applications, and to alter it and redistribute it
icculus@9278
    11
  freely, subject to the following restrictions:
icculus@9278
    12
icculus@9278
    13
  1. The origin of this software must not be misrepresented; you must not
icculus@9278
    14
     claim that you wrote the original software. If you use this software
icculus@9278
    15
     in a product, an acknowledgment in the product documentation would be
icculus@9278
    16
     appreciated but is not required.
icculus@9278
    17
  2. Altered source versions must be plainly marked as such, and must not be
icculus@9278
    18
     misrepresented as being the original software.
icculus@9278
    19
  3. This notice may not be removed or altered from any source distribution.
icculus@9278
    20
*/
icculus@9278
    21
icculus@9278
    22
#include "../../SDL_internal.h"
icculus@9278
    23
icculus@9278
    24
#ifdef SDL_JOYSTICK_EMSCRIPTEN
icculus@9278
    25
icculus@9278
    26
#include <stdio.h>              /* For the definition of NULL */
icculus@9278
    27
#include "SDL_error.h"
icculus@9278
    28
#include "SDL_events.h"
icculus@9278
    29
icculus@9278
    30
#include "SDL_joystick.h"
icculus@9278
    31
#include "SDL_hints.h"
icculus@9278
    32
#include "SDL_assert.h"
icculus@9278
    33
#include "SDL_timer.h"
icculus@9278
    34
#include "SDL_log.h"
icculus@9278
    35
#include "SDL_sysjoystick_c.h"
icculus@9278
    36
#include "../SDL_joystick_c.h"
icculus@9278
    37
icculus@9278
    38
static SDL_joylist_item * JoystickByIndex(int index);
icculus@9278
    39
icculus@9278
    40
static SDL_joylist_item *SDL_joylist = NULL;
icculus@9278
    41
static SDL_joylist_item *SDL_joylist_tail = NULL;
icculus@9278
    42
static int numjoysticks = 0;
icculus@9278
    43
static int instance_counter = 0;
icculus@9278
    44
philipp@9352
    45
EM_BOOL
icculus@9278
    46
Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
icculus@9278
    47
{
icculus@9278
    48
    int i;
icculus@9278
    49
icculus@9278
    50
    SDL_joylist_item *item;
icculus@9278
    51
icculus@9278
    52
    if (JoystickByIndex(gamepadEvent->index) != NULL) {
icculus@9278
    53
      return 1;
icculus@9278
    54
    }
icculus@9278
    55
icculus@9278
    56
    item = (SDL_joylist_item *) SDL_malloc(sizeof (SDL_joylist_item));
icculus@9278
    57
    if (item == NULL) {
icculus@9278
    58
        return 1;
icculus@9278
    59
    }
icculus@9278
    60
icculus@9278
    61
    SDL_zerop(item);
icculus@9278
    62
    item->index = gamepadEvent->index;
icculus@9278
    63
icculus@9278
    64
    item->name = SDL_strdup(gamepadEvent->id);
icculus@9278
    65
    if ( item->name == NULL ) {
icculus@9278
    66
        SDL_free(item);
icculus@9278
    67
        return 1;
icculus@9278
    68
    }
icculus@9278
    69
icculus@9278
    70
    item->mapping = SDL_strdup(gamepadEvent->mapping);
icculus@9278
    71
    if ( item->mapping == NULL ) {
icculus@9278
    72
        SDL_free(item->name);
icculus@9278
    73
        SDL_free(item);
icculus@9278
    74
        return 1;
icculus@9278
    75
    }
icculus@9278
    76
icculus@9278
    77
    item->naxes = gamepadEvent->numAxes;
icculus@9278
    78
    item->nbuttons = gamepadEvent->numButtons;
icculus@9278
    79
    item->device_instance = instance_counter++;
icculus@9278
    80
icculus@9278
    81
    item->timestamp = gamepadEvent->timestamp;
icculus@9278
    82
icculus@9278
    83
    for( i = 0; i < item->naxes; i++) {
icculus@9278
    84
        item->axis[i] = gamepadEvent->axis[i];
icculus@9278
    85
    }
icculus@9278
    86
icculus@9278
    87
    for( i = 0; i < item->nbuttons; i++) {
icculus@9278
    88
        item->analogButton[i] = gamepadEvent->analogButton[i];
icculus@9278
    89
        item->digitalButton[i] = gamepadEvent->digitalButton[i];
icculus@9278
    90
    }
icculus@9278
    91
icculus@9278
    92
    if (SDL_joylist_tail == NULL) {
icculus@9278
    93
        SDL_joylist = SDL_joylist_tail = item;
icculus@9278
    94
    } else {
icculus@9278
    95
        SDL_joylist_tail->next = item;
icculus@9278
    96
        SDL_joylist_tail = item;
icculus@9278
    97
    }
icculus@9278
    98
icculus@9278
    99
    ++numjoysticks;
slouken@10226
   100
slouken@10226
   101
    SDL_PrivateJoystickAdded(numjoysticks - 1);
slouken@10226
   102
philipp@9345
   103
#ifdef DEBUG_JOYSTICK
philipp@9345
   104
    SDL_Log("Number of joysticks is %d", numjoysticks);
philipp@9345
   105
#endif
icculus@9278
   106
philipp@9345
   107
#ifdef DEBUG_JOYSTICK
icculus@9278
   108
    SDL_Log("Added joystick with index %d", item->index);
philipp@9345
   109
#endif
icculus@9278
   110
icculus@9278
   111
    return 1;
icculus@9278
   112
}
icculus@9278
   113
philipp@9352
   114
EM_BOOL
icculus@9278
   115
Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
icculus@9278
   116
{
icculus@9278
   117
    SDL_joylist_item *item = SDL_joylist;
icculus@9278
   118
    SDL_joylist_item *prev = NULL;
icculus@9278
   119
icculus@9278
   120
    while (item != NULL) {
icculus@9278
   121
        if (item->index == gamepadEvent->index) {
icculus@9278
   122
            break;
icculus@9278
   123
        }
icculus@9278
   124
        prev = item;
icculus@9278
   125
        item = item->next;
icculus@9278
   126
    }
icculus@9278
   127
icculus@9278
   128
    if (item == NULL) {
icculus@9278
   129
        return 1;
icculus@9278
   130
    }
icculus@9278
   131
icculus@9278
   132
    if (item->joystick) {
icculus@9278
   133
        item->joystick->hwdata = NULL;
icculus@9278
   134
    }
icculus@9278
   135
icculus@9278
   136
    if (prev != NULL) {
icculus@9278
   137
        prev->next = item->next;
icculus@9278
   138
    } else {
icculus@9278
   139
        SDL_assert(SDL_joylist == item);
icculus@9278
   140
        SDL_joylist = item->next;
icculus@9278
   141
    }
icculus@9278
   142
    if (item == SDL_joylist_tail) {
icculus@9278
   143
        SDL_joylist_tail = prev;
icculus@9278
   144
    }
icculus@9278
   145
icculus@9278
   146
    /* Need to decrement the joystick count before we post the event */
icculus@9278
   147
    --numjoysticks;
icculus@9278
   148
slouken@10745
   149
    SDL_PrivateJoystickRemoved(item->device_instance);
icculus@9278
   150
philipp@9345
   151
#ifdef DEBUG_JOYSTICK
philipp@9345
   152
    SDL_Log("Removed joystick with id %d", item->device_instance);
philipp@9345
   153
#endif
icculus@9278
   154
    SDL_free(item->name);
icculus@9278
   155
    SDL_free(item->mapping);
icculus@9278
   156
    SDL_free(item);
icculus@9278
   157
    return 1;
icculus@9278
   158
}
icculus@9278
   159
icculus@9278
   160
/* Function to scan the system for joysticks.
icculus@9278
   161
 * It should return 0, or -1 on an unrecoverable fatal error.
icculus@9278
   162
 */
icculus@9278
   163
int
icculus@9278
   164
SDL_SYS_JoystickInit(void)
icculus@9278
   165
{
icculus@9278
   166
    int retval, i, numjs;
icculus@9278
   167
    EmscriptenGamepadEvent gamepadState;
icculus@9278
   168
icculus@9278
   169
    numjoysticks = 0;
icculus@9278
   170
    numjs = emscripten_get_num_gamepads();
icculus@9278
   171
icculus@9278
   172
    /* Check if gamepad is supported by browser */
icculus@9278
   173
    if (numjs == EMSCRIPTEN_RESULT_NOT_SUPPORTED) {
philipp@9835
   174
        return SDL_SetError("Gamepads not supported");
icculus@9278
   175
    }
icculus@9278
   176
icculus@9278
   177
    /* handle already connected gamepads */
icculus@9278
   178
    if (numjs > 0) {
icculus@9278
   179
        for(i = 0; i < numjs; i++) {
icculus@9278
   180
            retval = emscripten_get_gamepad_status(i, &gamepadState);
icculus@9278
   181
            if (retval == EMSCRIPTEN_RESULT_SUCCESS) {
icculus@9278
   182
                Emscripten_JoyStickConnected(EMSCRIPTEN_EVENT_GAMEPADCONNECTED,
icculus@9278
   183
                                             &gamepadState,
icculus@9278
   184
                                             NULL);
icculus@9278
   185
            }
icculus@9278
   186
        }
icculus@9278
   187
    }
icculus@9278
   188
icculus@9278
   189
    retval = emscripten_set_gamepadconnected_callback(NULL,
icculus@9278
   190
                                                      0,
icculus@9278
   191
                                                      Emscripten_JoyStickConnected);
icculus@9278
   192
icculus@9278
   193
    if(retval != EMSCRIPTEN_RESULT_SUCCESS) {
philipp@9357
   194
        SDL_SYS_JoystickQuit();
philipp@9835
   195
        return SDL_SetError("Could not set gamepad connect callback");
icculus@9278
   196
    }
icculus@9278
   197
icculus@9278
   198
    retval = emscripten_set_gamepaddisconnected_callback(NULL,
icculus@9278
   199
                                                         0,
icculus@9278
   200
                                                         Emscripten_JoyStickDisconnected);
icculus@9278
   201
    if(retval != EMSCRIPTEN_RESULT_SUCCESS) {
philipp@9357
   202
        SDL_SYS_JoystickQuit();
philipp@9835
   203
        return SDL_SetError("Could not set gamepad disconnect callback");
icculus@9278
   204
    }
icculus@9278
   205
icculus@9278
   206
    return 0;
icculus@9278
   207
}
icculus@9278
   208
philipp@9373
   209
/* Returns item matching given SDL device index. */
philipp@9373
   210
static SDL_joylist_item *
philipp@9373
   211
JoystickByDeviceIndex(int device_index)
philipp@9373
   212
{
philipp@9373
   213
    SDL_joylist_item *item = SDL_joylist;
philipp@9373
   214
philipp@9373
   215
    while (0 < device_index) {
philipp@9373
   216
        --device_index;
philipp@9373
   217
        item = item->next;
philipp@9373
   218
    }
philipp@9373
   219
philipp@9373
   220
    return item;
philipp@9373
   221
}
philipp@9373
   222
philipp@9373
   223
/* Returns item matching given HTML gamepad index. */
icculus@9278
   224
static SDL_joylist_item *
icculus@9278
   225
JoystickByIndex(int index)
icculus@9278
   226
{
icculus@9278
   227
    SDL_joylist_item *item = SDL_joylist;
icculus@9278
   228
icculus@9278
   229
    if (index < 0) {
icculus@9278
   230
        return NULL;
icculus@9278
   231
    }
icculus@9278
   232
icculus@9278
   233
    while (item != NULL) {
icculus@9278
   234
        if (item->index == index) {
icculus@9278
   235
            break;
icculus@9278
   236
        }
icculus@9278
   237
        item = item->next;
icculus@9278
   238
    }
icculus@9278
   239
icculus@9278
   240
    return item;
icculus@9278
   241
}
icculus@9278
   242
philipp@10617
   243
int
philipp@10617
   244
SDL_SYS_NumJoysticks(void)
icculus@9278
   245
{
icculus@9278
   246
    return numjoysticks;
icculus@9278
   247
}
icculus@9278
   248
philipp@10617
   249
void
philipp@10617
   250
SDL_SYS_JoystickDetect(void)
icculus@9278
   251
{
icculus@9278
   252
}
icculus@9278
   253
icculus@9278
   254
/* Function to get the device-dependent name of a joystick */
icculus@9278
   255
const char *
philipp@9373
   256
SDL_SYS_JoystickNameForDeviceIndex(int device_index)
icculus@9278
   257
{
philipp@9373
   258
    return JoystickByDeviceIndex(device_index)->name;
icculus@9278
   259
}
icculus@9278
   260
icculus@9278
   261
/* Function to perform the mapping from device index to the instance id for this index */
philipp@9373
   262
SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
icculus@9278
   263
{
philipp@9373
   264
    return JoystickByDeviceIndex(device_index)->device_instance;
icculus@9278
   265
}
icculus@9278
   266
icculus@9278
   267
/* Function to open a joystick for use.
philipp@9380
   268
   The joystick to open is specified by the device index.
icculus@9278
   269
   This should fill the nbuttons and naxes fields of the joystick structure.
icculus@9278
   270
   It returns 0, or -1 if there is an error.
icculus@9278
   271
 */
icculus@9278
   272
int
philipp@9373
   273
SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
icculus@9278
   274
{
philipp@9373
   275
    SDL_joylist_item *item = JoystickByDeviceIndex(device_index);
icculus@9278
   276
icculus@9278
   277
    if (item == NULL ) {
icculus@9278
   278
        return SDL_SetError("No such device");
icculus@9278
   279
    }
icculus@9278
   280
icculus@9278
   281
    if (item->joystick != NULL) {
icculus@9278
   282
        return SDL_SetError("Joystick already opened");
icculus@9278
   283
    }
icculus@9278
   284
icculus@9278
   285
    joystick->instance_id = item->device_instance;
icculus@9278
   286
    joystick->hwdata = (struct joystick_hwdata *) item;
icculus@9278
   287
    item->joystick = joystick;
icculus@9278
   288
icculus@9278
   289
    /* HTML5 Gamepad API doesn't say anything about these */
icculus@9278
   290
    joystick->nhats = 0;
icculus@9278
   291
    joystick->nballs = 0;
icculus@9278
   292
icculus@9278
   293
    joystick->nbuttons = item->nbuttons;
icculus@9278
   294
    joystick->naxes = item->naxes;
icculus@9278
   295
icculus@9278
   296
    return (0);
icculus@9278
   297
}
icculus@9278
   298
philipp@9561
   299
/* Function to determine if this joystick is attached to the system right now */
icculus@9278
   300
SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
icculus@9278
   301
{
icculus@9433
   302
    return joystick->hwdata != NULL;
icculus@9278
   303
}
icculus@9278
   304
icculus@9278
   305
/* Function to update the state of a joystick - called as a device poll.
icculus@9278
   306
 * This function shouldn't update the joystick structure directly,
icculus@9278
   307
 * but instead should call SDL_PrivateJoystick*() to deliver events
icculus@9278
   308
 * and update joystick device state.
icculus@9278
   309
 */
icculus@9278
   310
void
icculus@9278
   311
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
icculus@9278
   312
{
icculus@9278
   313
    EmscriptenGamepadEvent gamepadState;
philipp@9372
   314
    SDL_joylist_item *item = (SDL_joylist_item *) joystick->hwdata;
philipp@9291
   315
    int i, result, buttonState;
icculus@9278
   316
philipp@9372
   317
    if (item) {
icculus@9278
   318
        result = emscripten_get_gamepad_status(item->index, &gamepadState);
icculus@9278
   319
        if( result == EMSCRIPTEN_RESULT_SUCCESS) {
icculus@9278
   320
            if(gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) {
icculus@9278
   321
                for(i = 0; i < item->nbuttons; i++) {
icculus@9278
   322
                    if(item->digitalButton[i] != gamepadState.digitalButton[i]) {
icculus@9278
   323
                        buttonState = gamepadState.digitalButton[i]? SDL_PRESSED: SDL_RELEASED;
icculus@9278
   324
                        SDL_PrivateJoystickButton(item->joystick, i, buttonState);
icculus@9278
   325
                    }
philipp@9560
   326
philipp@9560
   327
                    /* store values to compare them in the next update */
philipp@9560
   328
                    item->analogButton[i] = gamepadState.analogButton[i];
philipp@9560
   329
                    item->digitalButton[i] = gamepadState.digitalButton[i];
icculus@9278
   330
                }
icculus@9278
   331
icculus@9278
   332
                for(i = 0; i < item->naxes; i++) {
icculus@9278
   333
                    if(item->axis[i] != gamepadState.axis[i]) {
icculus@9278
   334
                        // do we need to do conversion?
icculus@9278
   335
                        SDL_PrivateJoystickAxis(item->joystick, i,
icculus@9278
   336
                                                  (Sint16) (32767.*gamepadState.axis[i]));
icculus@9278
   337
                    }
philipp@9560
   338
philipp@9560
   339
                    /* store to compare in next update */
philipp@9560
   340
                    item->axis[i] = gamepadState.axis[i];
icculus@9278
   341
                }
icculus@9278
   342
icculus@9278
   343
                item->timestamp = gamepadState.timestamp;
icculus@9278
   344
            }
icculus@9278
   345
        }
icculus@9278
   346
    }
icculus@9278
   347
}
icculus@9278
   348
icculus@9278
   349
/* Function to close a joystick after use */
icculus@9278
   350
void
icculus@9278
   351
SDL_SYS_JoystickClose(SDL_Joystick * joystick)
icculus@9278
   352
{
philipp@10220
   353
    SDL_joylist_item *item = (SDL_joylist_item *) joystick->hwdata;
philipp@10224
   354
    if (item) {
philipp@10224
   355
        item->joystick = NULL;
philipp@10224
   356
    }
icculus@9278
   357
}
icculus@9278
   358
icculus@9278
   359
/* Function to perform any system-specific joystick related cleanup */
icculus@9278
   360
void
icculus@9278
   361
SDL_SYS_JoystickQuit(void)
icculus@9278
   362
{
icculus@9278
   363
    SDL_joylist_item *item = NULL;
icculus@9278
   364
    SDL_joylist_item *next = NULL;
icculus@9278
   365
icculus@9278
   366
    for (item = SDL_joylist; item; item = next) {
icculus@9278
   367
        next = item->next;
icculus@9278
   368
        SDL_free(item->mapping);
icculus@9278
   369
        SDL_free(item->name);
icculus@9278
   370
        SDL_free(item);
icculus@9278
   371
    }
icculus@9278
   372
icculus@9278
   373
    SDL_joylist = SDL_joylist_tail = NULL;
icculus@9278
   374
icculus@9278
   375
    numjoysticks = 0;
icculus@9278
   376
    instance_counter = 0;
philipp@9357
   377
philipp@9357
   378
    emscripten_set_gamepadconnected_callback(NULL, 0, NULL);
philipp@9357
   379
    emscripten_set_gamepaddisconnected_callback(NULL, 0, NULL);
icculus@9278
   380
}
icculus@9278
   381
philipp@9373
   382
SDL_JoystickGUID
philipp@9373
   383
SDL_SYS_JoystickGetDeviceGUID(int device_index)
icculus@9278
   384
{
icculus@9278
   385
    SDL_JoystickGUID guid;
icculus@9278
   386
    /* the GUID is just the first 16 chars of the name for now */
philipp@9373
   387
    const char *name = SDL_SYS_JoystickNameForDeviceIndex(device_index);
icculus@9278
   388
    SDL_zero(guid);
icculus@9278
   389
    SDL_memcpy(&guid, name, SDL_min(sizeof(guid), SDL_strlen(name)));
icculus@9278
   390
    return guid;
icculus@9278
   391
}
icculus@9278
   392
philipp@9373
   393
SDL_JoystickGUID
philipp@9373
   394
SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
icculus@9278
   395
{
icculus@9278
   396
    SDL_JoystickGUID guid;
icculus@9278
   397
    /* the GUID is just the first 16 chars of the name for now */
icculus@9278
   398
    const char *name = joystick->name;
icculus@9278
   399
    SDL_zero(guid);
icculus@9278
   400
    SDL_memcpy(&guid, name, SDL_min(sizeof(guid), SDL_strlen(name)));
icculus@9278
   401
    return guid;
icculus@9278
   402
}
icculus@9278
   403
icculus@9278
   404
#endif /* SDL_JOYSTICK_EMSCRIPTEN */