Skip to content

Commit a6dc61a

Browse files
committed
joystick: Emscripten can often fake a hat from the d-pad buttons.
Fixes #13817.
1 parent 937bf4d commit a6dc61a

File tree

2 files changed

+71
-12
lines changed

2 files changed

+71
-12
lines changed

src/joystick/emscripten/SDL_sysjoystick.c

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,17 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
6666
goto done;
6767
}
6868

69+
int first_hat_button = -1;
70+
int num_buttons = gamepadEvent->numButtons;
71+
if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat.
72+
num_buttons -= 4;
73+
first_hat_button = 12;
74+
}
75+
76+
item->first_hat_button = first_hat_button;
77+
item->nhats = (first_hat_button >= 0) ? 1 : 0;
6978
item->naxes = gamepadEvent->numAxes;
70-
item->nbuttons = gamepadEvent->numButtons;
79+
item->nbuttons = num_buttons;
7180
item->device_instance = SDL_GetNextObjectID();
7281

7382
item->timestamp = gamepadEvent->timestamp;
@@ -76,9 +85,30 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
7685
item->axis[i] = gamepadEvent->axis[i];
7786
}
7887

79-
for (i = 0; i < item->nbuttons; i++) {
80-
item->analogButton[i] = gamepadEvent->analogButton[i];
81-
item->digitalButton[i] = gamepadEvent->digitalButton[i];
88+
int buttonidx = 0;
89+
for (i = 0; i < item->nbuttons; i++, buttonidx++) {
90+
if (buttonidx == first_hat_button) {
91+
buttonidx += 3; // skip these buttons, we're treating them as hat input.
92+
}
93+
item->analogButton[i] = gamepadEvent->analogButton[buttonidx];
94+
item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx];
95+
}
96+
97+
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
98+
if (item->nhats) {
99+
Uint8 value = SDL_HAT_CENTERED;
100+
// this currently expects the first button to be up, then down, then left, then right.
101+
if (gamepadEvent->digitalButton[first_hat_button + 0]) {
102+
value |= SDL_HAT_UP;
103+
} else if (gamepadEvent->digitalButton[first_hat_button + 1]) {
104+
value |= SDL_HAT_DOWN;
105+
}
106+
if (gamepadEvent->digitalButton[first_hat_button + 2]) {
107+
value |= SDL_HAT_LEFT;
108+
} else if (gamepadEvent->digitalButton[first_hat_button + 3]) {
109+
value |= SDL_HAT_RIGHT;
110+
}
111+
item->hat = value;
82112
}
83113

84114
if (!SDL_joylist_tail) {
@@ -318,9 +348,8 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
318348
joystick->hwdata = (struct joystick_hwdata *)item;
319349
item->joystick = joystick;
320350

321-
// HTML5 Gamepad API doesn't say anything about these
322-
joystick->nhats = 0;
323-
351+
// HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping.
352+
joystick->nhats = item->nhats;
324353
joystick->nbuttons = item->nbuttons;
325354
joystick->naxes = item->naxes;
326355

@@ -361,15 +390,21 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
361390
result = emscripten_get_gamepad_status(item->index, &gamepadState);
362391
if (result == EMSCRIPTEN_RESULT_SUCCESS) {
363392
if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) {
364-
for (i = 0; i < item->nbuttons; i++) {
365-
if (item->digitalButton[i] != gamepadState.digitalButton[i]) {
366-
bool down = (gamepadState.digitalButton[i] != 0);
393+
const int first_hat_button = item->first_hat_button;
394+
395+
int buttonidx = 0;
396+
for (i = 0; i < item->nbuttons; i++, buttonidx++) {
397+
if (buttonidx == first_hat_button) {
398+
buttonidx += 4; // skip these buttons, we're treating them as hat input.
399+
}
400+
if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) {
401+
bool down = (gamepadState.digitalButton[buttonidx] != 0);
367402
SDL_SendJoystickButton(timestamp, item->joystick, i, down);
368403
}
369404

370405
// store values to compare them in the next update
371-
item->analogButton[i] = gamepadState.analogButton[i];
372-
item->digitalButton[i] = gamepadState.digitalButton[i];
406+
item->analogButton[i] = gamepadState.analogButton[buttonidx];
407+
item->digitalButton[i] = gamepadState.digitalButton[buttonidx];
373408
}
374409

375410
for (i = 0; i < item->naxes; i++) {
@@ -383,6 +418,27 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
383418
item->axis[i] = gamepadState.axis[i];
384419
}
385420

421+
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
422+
if (item->nhats) {
423+
Uint8 value = SDL_HAT_CENTERED;
424+
// this currently expects the first button to be up, then down, then left, then right.
425+
if (gamepadState.digitalButton[first_hat_button + 0]) {
426+
value |= SDL_HAT_UP;
427+
} else if (gamepadState.digitalButton[first_hat_button + 1]) {
428+
value |= SDL_HAT_DOWN;
429+
}
430+
if (gamepadState.digitalButton[first_hat_button + 2]) {
431+
value |= SDL_HAT_LEFT;
432+
} else if (gamepadState.digitalButton[first_hat_button + 3]) {
433+
value |= SDL_HAT_RIGHT;
434+
}
435+
if (item->hat != value) {
436+
item->hat = value;
437+
SDL_SendJoystickHat(timestamp, item->joystick, 0, value);
438+
}
439+
}
440+
441+
386442
item->timestamp = gamepadState.timestamp;
387443
}
388444
}

src/joystick/emscripten/SDL_sysjoystick_c.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ typedef struct SDL_joylist_item
3434
char *mapping;
3535
SDL_JoystickID device_instance;
3636
SDL_Joystick *joystick;
37+
int first_hat_button;
38+
int nhats;
3739
int nbuttons;
3840
int naxes;
3941
double timestamp;
4042
double axis[64];
4143
double analogButton[64];
4244
EM_BOOL digitalButton[64];
45+
Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons.
4346

4447
struct SDL_joylist_item *next;
4548
} SDL_joylist_item;

0 commit comments

Comments
 (0)