joystick: Emscripten can often fake a hat from the d-pad buttons.

Fixes #13817.
This commit is contained in:
Ryan C. Gordon
2025-09-05 14:46:59 -04:00
parent 937bf4d789
commit a6dc61ab32
2 changed files with 71 additions and 12 deletions

View File

@@ -66,8 +66,17 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
goto done;
}
int first_hat_button = -1;
int num_buttons = gamepadEvent->numButtons;
if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat.
num_buttons -= 4;
first_hat_button = 12;
}
item->first_hat_button = first_hat_button;
item->nhats = (first_hat_button >= 0) ? 1 : 0;
item->naxes = gamepadEvent->numAxes;
item->nbuttons = gamepadEvent->numButtons;
item->nbuttons = num_buttons;
item->device_instance = SDL_GetNextObjectID();
item->timestamp = gamepadEvent->timestamp;
@@ -76,9 +85,30 @@ static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamep
item->axis[i] = gamepadEvent->axis[i];
}
for (i = 0; i < item->nbuttons; i++) {
item->analogButton[i] = gamepadEvent->analogButton[i];
item->digitalButton[i] = gamepadEvent->digitalButton[i];
int buttonidx = 0;
for (i = 0; i < item->nbuttons; i++, buttonidx++) {
if (buttonidx == first_hat_button) {
buttonidx += 3; // skip these buttons, we're treating them as hat input.
}
item->analogButton[i] = gamepadEvent->analogButton[buttonidx];
item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx];
}
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
if (item->nhats) {
Uint8 value = SDL_HAT_CENTERED;
// this currently expects the first button to be up, then down, then left, then right.
if (gamepadEvent->digitalButton[first_hat_button + 0]) {
value |= SDL_HAT_UP;
} else if (gamepadEvent->digitalButton[first_hat_button + 1]) {
value |= SDL_HAT_DOWN;
}
if (gamepadEvent->digitalButton[first_hat_button + 2]) {
value |= SDL_HAT_LEFT;
} else if (gamepadEvent->digitalButton[first_hat_button + 3]) {
value |= SDL_HAT_RIGHT;
}
item->hat = value;
}
if (!SDL_joylist_tail) {
@@ -318,9 +348,8 @@ static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index)
joystick->hwdata = (struct joystick_hwdata *)item;
item->joystick = joystick;
// HTML5 Gamepad API doesn't say anything about these
joystick->nhats = 0;
// HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping.
joystick->nhats = item->nhats;
joystick->nbuttons = item->nbuttons;
joystick->naxes = item->naxes;
@@ -361,15 +390,21 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
result = emscripten_get_gamepad_status(item->index, &gamepadState);
if (result == EMSCRIPTEN_RESULT_SUCCESS) {
if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) {
for (i = 0; i < item->nbuttons; i++) {
if (item->digitalButton[i] != gamepadState.digitalButton[i]) {
bool down = (gamepadState.digitalButton[i] != 0);
const int first_hat_button = item->first_hat_button;
int buttonidx = 0;
for (i = 0; i < item->nbuttons; i++, buttonidx++) {
if (buttonidx == first_hat_button) {
buttonidx += 4; // skip these buttons, we're treating them as hat input.
}
if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) {
bool down = (gamepadState.digitalButton[buttonidx] != 0);
SDL_SendJoystickButton(timestamp, item->joystick, i, down);
}
// store values to compare them in the next update
item->analogButton[i] = gamepadState.analogButton[i];
item->digitalButton[i] = gamepadState.digitalButton[i];
item->analogButton[i] = gamepadState.analogButton[buttonidx];
item->digitalButton[i] = gamepadState.digitalButton[buttonidx];
}
for (i = 0; i < item->naxes; i++) {
@@ -383,6 +418,27 @@ static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick)
item->axis[i] = gamepadState.axis[i];
}
SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons.
if (item->nhats) {
Uint8 value = SDL_HAT_CENTERED;
// this currently expects the first button to be up, then down, then left, then right.
if (gamepadState.digitalButton[first_hat_button + 0]) {
value |= SDL_HAT_UP;
} else if (gamepadState.digitalButton[first_hat_button + 1]) {
value |= SDL_HAT_DOWN;
}
if (gamepadState.digitalButton[first_hat_button + 2]) {
value |= SDL_HAT_LEFT;
} else if (gamepadState.digitalButton[first_hat_button + 3]) {
value |= SDL_HAT_RIGHT;
}
if (item->hat != value) {
item->hat = value;
SDL_SendJoystickHat(timestamp, item->joystick, 0, value);
}
}
item->timestamp = gamepadState.timestamp;
}
}

View File

@@ -34,12 +34,15 @@ typedef struct SDL_joylist_item
char *mapping;
SDL_JoystickID device_instance;
SDL_Joystick *joystick;
int first_hat_button;
int nhats;
int nbuttons;
int naxes;
double timestamp;
double axis[64];
double analogButton[64];
EM_BOOL digitalButton[64];
Uint8 hat; // there is (currently) only ever one of these, faked from the d-pad buttons.
struct SDL_joylist_item *next;
} SDL_joylist_item;