From e549f3f59103c9662fd53e30a3a2aa96c40b8a2a Mon Sep 17 00:00:00 2001 From: Pavel Sountsov Date: Tue, 14 Jan 2025 21:37:35 -0800 Subject: [PATCH] Do a pass on joystick hotplugging. - MacOS wasn't reusing joystick slots - Linux was getting duplicate joysticks due to reading from /dev/input/by-path - Windows XInput wasn't re-initializing joystick names - SDL was completely broken Also, I redid ex_joystick_hotplugging to be more useful/exciting. See #1533 --- examples/CMakeLists.txt | 2 +- examples/ex_joystick_events.c | 9 +- examples/ex_joystick_hotplugging.c | 189 ++++++------ src/linux/ljoynu.c | 52 +--- src/macosx/hidjoy.m | 29 +- src/sdl/sdl_joystick.c | 455 ++++++++++++++++------------- src/win/wjoyxi.c | 167 +++++------ 7 files changed, 493 insertions(+), 410 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3cad47c94..cbee07905 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -156,7 +156,7 @@ example(ex_icon2 ${IMAGE} ${DATA_IMAGES}) example(ex_haptic ${PRIM}) example(ex_haptic2 ex_haptic2.cpp ${NIHGUI} ${TTF} DATA ${DATA_TTF}) example(ex_joystick_events ${PRIM} ${FONT}) -example(ex_joystick_hotplugging ${PRIM}) +example(ex_joystick_hotplugging ${PRIM} ${FONT} ${COLOR}) example(ex_keyboard_events) example(ex_keyboard_focus) example(ex_lines ${PRIM}) diff --git a/examples/ex_joystick_events.c b/examples/ex_joystick_events.c index 8994ad3f4..9ec6eb479 100644 --- a/examples/ex_joystick_events.c +++ b/examples/ex_joystick_events.c @@ -184,8 +184,8 @@ static void main_loop(void) while (true) { if (al_is_event_queue_empty(event_queue)) { - setup_joystick_values(al_get_joystick(0), &state2); - draw_all(al_get_joystick(0), &state1, &state2); + setup_joystick_values(joy, &state2); + draw_all(joy, &state1, &state2); } al_wait_for_event(event_queue, &event); @@ -234,8 +234,9 @@ static void main_loop(void) case ALLEGRO_EVENT_JOYSTICK_CONFIGURATION: log_printf("configuration changed\n"); al_reconfigure_joysticks(); - setup_joystick_values(al_get_joystick(0), &state1); - setup_joystick_values(al_get_joystick(0), &state2); + joy = al_get_joystick(0); + setup_joystick_values(joy, &state1); + setup_joystick_values(joy, &state2); break; /* We received an event of some type we don't know about. diff --git a/examples/ex_joystick_hotplugging.c b/examples/ex_joystick_hotplugging.c index ba4f6d354..728a3120a 100644 --- a/examples/ex_joystick_hotplugging.c +++ b/examples/ex_joystick_hotplugging.c @@ -1,72 +1,104 @@ +#include #include + #include +#include +#include #include #include "common.c" -static void print_joystick_info(ALLEGRO_JOYSTICK *joy) -{ - int i, n, a; - if (!joy) - return; +typedef struct { + ALLEGRO_JOYSTICK *joy; + char last_event[128]; +} JOYSTICK_SLOT; - log_printf("Joystick: '%s'\n", al_get_joystick_name(joy)); +#define NUM_SLOTS (6) +JOYSTICK_SLOT slots[NUM_SLOTS]; +ALLEGRO_FONT *font; - log_printf(" Buttons:"); - n = al_get_joystick_num_buttons(joy); - for (i = 0; i < n; i++) { - log_printf(" '%s'", al_get_joystick_button_name(joy, i)); - } - log_printf("\n"); - n = al_get_joystick_num_sticks(joy); - for (i = 0; i < n; i++) { - log_printf(" Stick %d: '%s'\n", i, al_get_joystick_stick_name(joy, i)); - for (a = 0; a < al_get_joystick_num_axes(joy, i); a++) { - log_printf(" Axis %d: '%s'\n", - a, al_get_joystick_axis_name(joy, i, a)); - } +static JOYSTICK_SLOT *get_slot(ALLEGRO_JOYSTICK *joy) +{ + for (int i = 0; i < NUM_SLOTS; i++) { + if (joy == slots[i].joy) + return &slots[i]; } + return NULL; } -static void draw(ALLEGRO_JOYSTICK *curr_joy) + + +static void fill_slots(void) { - int x = 100; - int y = 100; - ALLEGRO_JOYSTICK_STATE joystate; - int i; - - al_clear_to_color(al_map_rgb(0, 0, 0)); - - if (curr_joy) { - al_get_joystick_state(curr_joy, &joystate); - for (i = 0; i < al_get_joystick_num_sticks(curr_joy); i++) { - al_draw_filled_circle( - x+joystate.stick[i].axis[0]*20 + i * 80, - y+joystate.stick[i].axis[1]*20, - 20, al_map_rgb(255, 255, 255) - ); + int num_joysticks = al_get_num_joysticks(); + for (int i = 0; i < num_joysticks; i++) { + ALLEGRO_JOYSTICK *joy = al_get_joystick(i); + bool found = false; + for (int j = 0; j < NUM_SLOTS; j++) { + if (joy == slots[j].joy) { + found = true; + break; + } } - for (i = 0; i < al_get_joystick_num_buttons(curr_joy); i++) { - if (joystate.button[i]) { - al_draw_filled_circle( - i*20+10, 400, 9, al_map_rgb(255, 255, 255) - ); + if (found) + continue; + for (int j = 0; j < NUM_SLOTS; j++) { + JOYSTICK_SLOT *slot = &slots[j]; + if (slot->joy == NULL) { + slot->joy = joy; + break; } } } +} - al_flip_display(); + + +static void draw_slots(void) +{ + ALLEGRO_COLOR inactive_color = al_map_rgb(0x80, 0x80, 0x80); + + for (int i = 0; i < NUM_SLOTS; i++) { + JOYSTICK_SLOT *slot = &slots[i]; + bool active = slot->joy != NULL && al_get_joystick_active(slot->joy); + ALLEGRO_COLOR active_color = al_color_lch(1., 1., 2 * ALLEGRO_PI * fmod(0.2 * al_get_time() + (float)i / NUM_SLOTS, 1.)); + ALLEGRO_COLOR color = active ? active_color : inactive_color; + + int x = 5; + int y = 5 + i * 80; + int w = 630; + int h = 75; + int dy = al_get_font_line_height(font) + 5; + al_draw_rounded_rectangle(x, y, x + w, y + h, 16, 16, color, 3); + x += 5; + y += 5; + + y += dy; + al_draw_textf(font, color, x, y, 0, "Slot: %d", i); + + if (!active) { + y += dy; + al_draw_textf(font, color, x, y, 0, "Inactive"); + continue; + } + y += dy; + al_draw_textf(font, color, x, y, 0, "Name: %s", al_get_joystick_name(slot->joy)); + + y += dy; + al_draw_textf(font, color, x, y, 0, "Last Event: %s", slot->last_event); + } } + + int main(int argc, char **argv) { - int num_joysticks; ALLEGRO_EVENT_QUEUE *queue; - ALLEGRO_JOYSTICK *curr_joy; ALLEGRO_DISPLAY *display; + ALLEGRO_TIMER *timer; (void)argc; (void)argv; @@ -79,77 +111,70 @@ int main(int argc, char **argv) } al_install_keyboard(); al_init_primitives_addon(); - - open_log(); + al_init_font_addon(); display = al_create_display(640, 480); if (!display) { abort_example("Could not create display.\n"); } + timer = al_create_timer(1. / 60); + al_start_timer(timer); + font = al_create_builtin_font(); + queue = al_create_event_queue(); + al_register_event_source(queue, al_get_timer_event_source(timer)); al_register_event_source(queue, al_get_keyboard_event_source()); al_register_event_source(queue, al_get_joystick_event_source()); al_register_event_source(queue, al_get_display_event_source(display)); - num_joysticks = al_get_num_joysticks(); - log_printf("Num joysticks: %d\n", num_joysticks); - - if (num_joysticks > 0) { - curr_joy = al_get_joystick(0); - print_joystick_info(curr_joy); - } - else { - curr_joy = NULL; - } - - draw(curr_joy); + fill_slots(); + bool redraw = true; while (1) { ALLEGRO_EVENT event; + JOYSTICK_SLOT *slot = NULL; + if (redraw && al_is_event_queue_empty(queue)) { + al_clear_to_color(al_map_rgb(0, 0, 0)); + draw_slots(); + al_flip_display(); + redraw = false; + } al_wait_for_event(queue, &event); - if (event.type == ALLEGRO_EVENT_KEY_DOWN && + + if (event.type == ALLEGRO_EVENT_TIMER) { + redraw = true; + } + else if (event.type == ALLEGRO_EVENT_KEY_DOWN && event.keyboard.keycode == ALLEGRO_KEY_ESCAPE) { break; } else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { break; } - else if (event.type == ALLEGRO_EVENT_KEY_CHAR) { - int n = event.keyboard.unichar - '0'; - if (n >= 0 && n < num_joysticks) { - curr_joy = al_get_joystick(n); - log_printf("switching to joystick %d\n", n); - print_joystick_info(curr_joy); - } - } else if (event.type == ALLEGRO_EVENT_JOYSTICK_CONFIGURATION) { al_reconfigure_joysticks(); - num_joysticks = al_get_num_joysticks(); - log_printf("after reconfiguration num joysticks = %d\n", - num_joysticks); - if (curr_joy) { - log_printf("current joystick is: %s\n", - al_get_joystick_active(curr_joy) ? "active" : "inactive"); - } - curr_joy = al_get_joystick(0); + fill_slots(); } else if (event.type == ALLEGRO_EVENT_JOYSTICK_AXIS) { - log_printf("axis event from %p, stick %d, axis %d\n", event.joystick.id, event.joystick.stick, event.joystick.axis); + if ((slot = get_slot(event.joystick.id))) { + snprintf(slot->last_event, sizeof(slot->last_event), "ALLEGRO_EVENT_JOYSTICK_AXIS, stick: %d, axis: %d, pos: %.3f", + event.joystick.stick, event.joystick.axis, event.joystick.pos); + } } else if (event.type == ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN) { - log_printf("button down event %d from %p\n", - event.joystick.button, event.joystick.id); + if ((slot = get_slot(event.joystick.id))) { + snprintf(slot->last_event, sizeof(slot->last_event), "ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN, button: %d", event.joystick.button); + } } else if (event.type == ALLEGRO_EVENT_JOYSTICK_BUTTON_UP) { - log_printf("button up event %d from %p\n", - event.joystick.button, event.joystick.id); + if ((slot = get_slot(event.joystick.id))) { + snprintf(slot->last_event, sizeof(slot->last_event), "ALLEGRO_EVENT_JOYSTICK_BUTTON_UP, button: %d", event.joystick.button); + } } - - draw(curr_joy); } - close_log(false); + al_uninstall_system(); return 0; } diff --git a/src/linux/ljoynu.c b/src/linux/ljoynu.c index f49cf3203..6b716affa 100644 --- a/src/linux/ljoynu.c +++ b/src/linux/ljoynu.c @@ -622,7 +622,6 @@ static void ljoy_scan(bool configure) ALLEGRO_USTR *device_name; const ALLEGRO_FS_INTERFACE *fs_interface; unsigned i; - int t; /* Clear mark bits. */ for (i = 0; i < _al_vector_size(&joysticks); i++) { @@ -643,47 +642,24 @@ static void ljoy_scan(bool configure) ljoy_device(device_name); } - /* Then scan /dev/input/by-path for *-event-joystick devices and if - * no device is found there scan all files in /dev/input. - * Note: That last step might be overkill, we probably don't need - * to support non-evdev kernels any longer. - */ - static char const *folders[] = {"/dev/input/by-path", "/dev/input"}; - for (t = 0; t < 2; t++) { - bool found = false; - ALLEGRO_FS_ENTRY *dir = al_create_fs_entry(folders[t]); - if (al_open_directory(dir)) { - static char const *suffix = "-event-joystick"; - while (true) { - ALLEGRO_FS_ENTRY *dev = al_read_directory(dir); - if (!dev) { - break; - } - if (al_get_fs_entry_mode(dev) & ALLEGRO_FILEMODE_ISDIR) { - al_destroy_fs_entry(dev); - continue; - } - char const *path = al_get_fs_entry_name(dev); - /* In the second pass in /dev/input we don't filter anymore. - In the first pass, in /dev/input/by-path, strlen(path) > strlen(suffix). */ - if (t == 1 || strcmp(suffix, path + strlen(path) - strlen(suffix)) == 0) { - found = true; - al_ustr_assign_cstr(device_name, path); - ljoy_device(device_name); - } + ALLEGRO_FS_ENTRY *dir = al_create_fs_entry("/dev/input"); + if (al_open_directory(dir)) { + while (true) { + ALLEGRO_FS_ENTRY *dev = al_read_directory(dir); + if (!dev) { + break; + } + if (al_get_fs_entry_mode(dev) & ALLEGRO_FILEMODE_ISDIR) { al_destroy_fs_entry(dev); + continue; } - al_close_directory(dir); - } - al_destroy_fs_entry(dir); - if (found) { - /* Don't scan the second folder if we found something in the - * first as it would be duplicates. - */ - break; + al_ustr_assign_cstr(device_name, al_get_fs_entry_name(dev)); + ljoy_device(device_name); + al_destroy_fs_entry(dev); } - ALLEGRO_WARN("Could not find joysticks in %s\n", folders[t]); + al_close_directory(dir); } + al_destroy_fs_entry(dir); al_ustr_free(device_name); diff --git a/src/macosx/hidjoy.m b/src/macosx/hidjoy.m index 1ba131ad0..151380f9c 100644 --- a/src/macosx/hidjoy.m +++ b/src/macosx/hidjoy.m @@ -158,6 +158,26 @@ static CFMutableDictionaryRef CreateDeviceMatchingDictionary( return NULL; } +static ALLEGRO_JOYSTICK_OSX *find_or_insert_joystick(IOHIDDeviceRef ident) +{ + ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ident); + if (joy) + return joy; + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i); + if (joy->cfg_state == JOY_STATE_UNUSED) { + joy->ident = ident; + return joy; + } + } + + joy = al_calloc(1, sizeof(ALLEGRO_JOYSTICK_OSX)); + joy->ident = ident; + ALLEGRO_JOYSTICK_OSX **back = _al_vector_alloc_back(&joysticks); + *back = joy; + return joy; +} + static const char *get_element_name(IOHIDElementRef elem, const char *default_name) { CFStringRef name = IOHIDElementGetName(elem); @@ -443,20 +463,13 @@ static void add_joystick_device(IOHIDDeviceRef ref, bool emit_reconfigure_event) { al_lock_mutex(add_mutex); - ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref); + ALLEGRO_JOYSTICK_OSX *joy = find_or_insert_joystick(ref); if (joy && (joy->cfg_state == JOY_STATE_BORN || joy->cfg_state == JOY_STATE_ALIVE)) { al_unlock_mutex(add_mutex); return; } - if (joy == NULL) { - joy = al_calloc(1, sizeof(ALLEGRO_JOYSTICK_OSX)); - joy->ident = ref; - ALLEGRO_JOYSTICK_OSX **back = _al_vector_alloc_back(&joysticks); - *back = joy; - } - joy->cfg_state = new_joystick_state; CFArrayRef elements = IOHIDDeviceCopyMatchingElements( diff --git a/src/sdl/sdl_joystick.c b/src/sdl/sdl_joystick.c index bd26943af..80633cb54 100644 --- a/src/sdl/sdl_joystick.c +++ b/src/sdl/sdl_joystick.c @@ -21,7 +21,7 @@ ALLEGRO_DEBUG_CHANNEL("SDL") typedef struct ALLEGRO_JOYSTICK_SDL { - int id; + SDL_JoystickID id; ALLEGRO_JOYSTICK allegro; SDL_Joystick *sdl; SDL_GameController *sdl_gc; @@ -29,23 +29,62 @@ typedef struct ALLEGRO_JOYSTICK_SDL } ALLEGRO_JOYSTICK_SDL; static ALLEGRO_JOYSTICK_DRIVER *vt; -static int count; -static ALLEGRO_JOYSTICK_SDL *joysticks; +static int num_joysticks; /* number of joysticks known to the user */ +static _AL_VECTOR joysticks = _AL_VECTOR_INITIALIZER(ALLEGRO_JOYSTICK_SDL *); /* of ALLEGRO_JOYSTICK_SDL pointers */ static bool compat_5_2_10(void) { /* Mappings. */ return _al_get_joystick_compat_version() < AL_ID(5, 2, 11, 0); } -static int get_id(ALLEGRO_JOYSTICK *allegro) +static ALLEGRO_JOYSTICK_SDL *get_joystick_from_allegro(ALLEGRO_JOYSTICK *allegro) { - int i; - for (i = 0; i < count; i++) { - if (&joysticks[i].allegro == allegro) - return i; + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + ALLEGRO_JOYSTICK_SDL *joy = *(ALLEGRO_JOYSTICK_SDL **)_al_vector_ref(&joysticks, i); + if (&joy->allegro == allegro) + return joy; } ASSERT(false); - return 0; + return NULL; +} + +static ALLEGRO_JOYSTICK_SDL *get_joystick(SDL_JoystickID id) +{ + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + ALLEGRO_JOYSTICK_SDL *joy = *(ALLEGRO_JOYSTICK_SDL **)_al_vector_ref(&joysticks, i); + if (joy->id == id) + return joy; + } + ASSERT(false); + return NULL; +} + +static ALLEGRO_JOYSTICK_SDL *get_or_insert_joystick(SDL_JoystickID id) +{ + ALLEGRO_JOYSTICK_SDL **slot; + ALLEGRO_JOYSTICK_SDL *joy; + + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + slot = _al_vector_ref(&joysticks, i); + joy = *slot; + if (joy->id == id) + return joy; + } + + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + slot = _al_vector_ref(&joysticks, i); + joy = *slot; + if (!SDL_JoystickFromInstanceID(joy->id)) { + joy->id = id; + return joy; + } + } + + joy = al_calloc(1, sizeof *joy); + joy->id = id; + slot = _al_vector_alloc_back(&joysticks); + *slot = joy; + return joy; } void _al_sdl_joystick_event(SDL_Event *e) @@ -56,169 +95,177 @@ void _al_sdl_joystick_event(SDL_Event *e) event.joystick.timestamp = al_get_time(); - if (joysticks[e->jaxis.which].allegro.info.type == ALLEGRO_JOYSTICK_TYPE_GAMEPAD) { - if (e->type == SDL_CONTROLLERAXISMOTION) { - int stick = -1; - int axis = -1; - switch (e->caxis.axis) { - case SDL_CONTROLLER_AXIS_LEFTX: - stick = ALLEGRO_GAMEPAD_STICK_LEFT_THUMB; - axis = 0; - break; - case SDL_CONTROLLER_AXIS_LEFTY: - stick = ALLEGRO_GAMEPAD_STICK_LEFT_THUMB; - axis = 1; - break; - case SDL_CONTROLLER_AXIS_RIGHTX: - stick = ALLEGRO_GAMEPAD_STICK_RIGHT_THUMB; - axis = 0; - break; - case SDL_CONTROLLER_AXIS_RIGHTY: - stick = ALLEGRO_GAMEPAD_STICK_RIGHT_THUMB; - axis = 1; - break; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - stick = ALLEGRO_GAMEPAD_STICK_LEFT_TRIGGER; - axis = 0; - break; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - stick = ALLEGRO_GAMEPAD_STICK_RIGHT_TRIGGER; - axis = 0; - break; - } - if (stick >= 0 && axis >= 0) { - event.joystick.type = ALLEGRO_EVENT_JOYSTICK_AXIS; - event.joystick.id = &joysticks[e->jaxis.which].allegro; - event.joystick.stick = stick; - event.joystick.axis = axis; - event.joystick.pos = e->jaxis.value / 32768.0; - event.joystick.button = 0; - emit = true; - } - } - else if (e->type == SDL_CONTROLLERBUTTONDOWN || e->type == SDL_CONTROLLERBUTTONUP) { - int stick = 0; - int axis = 0; - int button = 0; - float pos = 0.; - bool down = e->type == SDL_CONTROLLERBUTTONDOWN; - int type = down ? ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN : ALLEGRO_EVENT_JOYSTICK_BUTTON_UP; - ALLEGRO_JOYSTICK_SDL *joy = &joysticks[e->cbutton.which]; - - switch (e->cbutton.button) { - case SDL_CONTROLLER_BUTTON_A: - button = ALLEGRO_GAMEPAD_BUTTON_A; - break; - case SDL_CONTROLLER_BUTTON_B: - button = ALLEGRO_GAMEPAD_BUTTON_B; - break; - case SDL_CONTROLLER_BUTTON_X: - button = ALLEGRO_GAMEPAD_BUTTON_X; - break; - case SDL_CONTROLLER_BUTTON_Y: - button = ALLEGRO_GAMEPAD_BUTTON_Y; - break; - case SDL_CONTROLLER_BUTTON_BACK: - button = ALLEGRO_GAMEPAD_BUTTON_BACK; - break; - case SDL_CONTROLLER_BUTTON_GUIDE: - button = ALLEGRO_GAMEPAD_BUTTON_GUIDE; - break; - case SDL_CONTROLLER_BUTTON_START: - button = ALLEGRO_GAMEPAD_BUTTON_START; - break; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - button = ALLEGRO_GAMEPAD_BUTTON_LEFT_SHOULDER; - break; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - button = ALLEGRO_GAMEPAD_BUTTON_RIGHT_SHOULDER; - break; - case SDL_CONTROLLER_BUTTON_LEFTSTICK: - button = ALLEGRO_GAMEPAD_BUTTON_LEFT_THUMB; - break; - case SDL_CONTROLLER_BUTTON_RIGHTSTICK: - button = ALLEGRO_GAMEPAD_BUTTON_RIGHT_THUMB; - break; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - type = ALLEGRO_EVENT_JOYSTICK_AXIS; - axis = 0; - joy->dpad_state[2] = down; - break; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - type = ALLEGRO_EVENT_JOYSTICK_AXIS; - axis = 0; - joy->dpad_state[0] = down; - break; - case SDL_CONTROLLER_BUTTON_DPAD_UP: - type = ALLEGRO_EVENT_JOYSTICK_AXIS; - axis = 1; - joy->dpad_state[3] = down; - break; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - type = ALLEGRO_EVENT_JOYSTICK_AXIS; - axis = 1; - joy->dpad_state[1] = down; - break; - default: - type = 0; - } - if (type == ALLEGRO_EVENT_JOYSTICK_AXIS) { - stick = ALLEGRO_GAMEPAD_STICK_DPAD; - if (axis == 0) - pos = (int)joy->dpad_state[0] - (int)joy->dpad_state[2]; - else - pos = (int)joy->dpad_state[1] - (int)joy->dpad_state[3]; - } - if (type != 0) { - event.joystick.type = type; - event.joystick.id = &joy->allegro; - event.joystick.stick = stick; - event.joystick.axis = axis; - event.joystick.pos = pos; - event.joystick.button = button; - emit = true; - } - } - else if (e->type == SDL_CONTROLLERDEVICEADDED || e->type == SDL_CONTROLLERDEVICEREMOVED) { - event.joystick.type = ALLEGRO_EVENT_JOYSTICK_CONFIGURATION; - } - else { + if (e->type == SDL_CONTROLLERAXISMOTION) { + ALLEGRO_JOYSTICK_SDL *joy = get_joystick(e->caxis.which); + if (joy->allegro.info.type != ALLEGRO_JOYSTICK_TYPE_GAMEPAD) return; + int stick = -1; + int axis = -1; + switch (e->caxis.axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + stick = ALLEGRO_GAMEPAD_STICK_LEFT_THUMB; + axis = 0; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + stick = ALLEGRO_GAMEPAD_STICK_LEFT_THUMB; + axis = 1; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + stick = ALLEGRO_GAMEPAD_STICK_RIGHT_THUMB; + axis = 0; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + stick = ALLEGRO_GAMEPAD_STICK_RIGHT_THUMB; + axis = 1; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + stick = ALLEGRO_GAMEPAD_STICK_LEFT_TRIGGER; + axis = 0; + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + stick = ALLEGRO_GAMEPAD_STICK_RIGHT_TRIGGER; + axis = 0; + break; } - } - else { - if (e->type == SDL_JOYAXISMOTION) { + if (stick >= 0 && axis >= 0) { event.joystick.type = ALLEGRO_EVENT_JOYSTICK_AXIS; - event.joystick.id = &joysticks[e->jaxis.which].allegro; - event.joystick.stick = e->jaxis.axis / 2; - event.joystick.axis = e->jaxis.axis % 2; - event.joystick.pos = e->jaxis.value / 32768.0; + event.joystick.id = &joy->allegro; + event.joystick.stick = stick; + event.joystick.axis = axis; + event.joystick.pos = e->caxis.value / 32768.0; event.joystick.button = 0; + emit = true; } - else if (e->type == SDL_JOYBUTTONDOWN) { - event.joystick.type = ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN; - event.joystick.id = &joysticks[e->jbutton.which].allegro; - event.joystick.stick = 0; - event.joystick.axis = 0; - event.joystick.pos = 0; - event.joystick.button = e->jbutton.button; + } + else if (e->type == SDL_CONTROLLERBUTTONDOWN || e->type == SDL_CONTROLLERBUTTONUP) { + int stick = 0; + int axis = 0; + int button = 0; + float pos = 0.; + bool down = e->type == SDL_CONTROLLERBUTTONDOWN; + int type = down ? ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN : ALLEGRO_EVENT_JOYSTICK_BUTTON_UP; + ALLEGRO_JOYSTICK_SDL *joy = get_joystick(e->cbutton.which); + if (joy->allegro.info.type != ALLEGRO_JOYSTICK_TYPE_GAMEPAD) + return; + + switch (e->cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: + button = ALLEGRO_GAMEPAD_BUTTON_A; + break; + case SDL_CONTROLLER_BUTTON_B: + button = ALLEGRO_GAMEPAD_BUTTON_B; + break; + case SDL_CONTROLLER_BUTTON_X: + button = ALLEGRO_GAMEPAD_BUTTON_X; + break; + case SDL_CONTROLLER_BUTTON_Y: + button = ALLEGRO_GAMEPAD_BUTTON_Y; + break; + case SDL_CONTROLLER_BUTTON_BACK: + button = ALLEGRO_GAMEPAD_BUTTON_BACK; + break; + case SDL_CONTROLLER_BUTTON_GUIDE: + button = ALLEGRO_GAMEPAD_BUTTON_GUIDE; + break; + case SDL_CONTROLLER_BUTTON_START: + button = ALLEGRO_GAMEPAD_BUTTON_START; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + button = ALLEGRO_GAMEPAD_BUTTON_LEFT_SHOULDER; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + button = ALLEGRO_GAMEPAD_BUTTON_RIGHT_SHOULDER; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + button = ALLEGRO_GAMEPAD_BUTTON_LEFT_THUMB; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + button = ALLEGRO_GAMEPAD_BUTTON_RIGHT_THUMB; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + type = ALLEGRO_EVENT_JOYSTICK_AXIS; + axis = 0; + joy->dpad_state[2] = down; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + type = ALLEGRO_EVENT_JOYSTICK_AXIS; + axis = 0; + joy->dpad_state[0] = down; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + type = ALLEGRO_EVENT_JOYSTICK_AXIS; + axis = 1; + joy->dpad_state[3] = down; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + type = ALLEGRO_EVENT_JOYSTICK_AXIS; + axis = 1; + joy->dpad_state[1] = down; + break; + default: + type = 0; } - else if (e->type == SDL_JOYBUTTONUP) { - event.joystick.type = ALLEGRO_EVENT_JOYSTICK_BUTTON_UP; - event.joystick.id = &joysticks[e->jbutton.which].allegro; - event.joystick.stick = 0; - event.joystick.axis = 0; - event.joystick.pos = 0; - event.joystick.button = e->jbutton.button; + if (type == ALLEGRO_EVENT_JOYSTICK_AXIS) { + stick = ALLEGRO_GAMEPAD_STICK_DPAD; + if (axis == 0) + pos = (int)joy->dpad_state[0] - (int)joy->dpad_state[2]; + else + pos = (int)joy->dpad_state[1] - (int)joy->dpad_state[3]; } - else if (e->type == SDL_JOYDEVICEADDED || e->type == SDL_JOYDEVICEREMOVED) { - event.joystick.type = ALLEGRO_EVENT_JOYSTICK_CONFIGURATION; + if (type != 0) { + event.joystick.type = type; + event.joystick.id = &joy->allegro; + event.joystick.stick = stick; + event.joystick.axis = axis; + event.joystick.pos = pos; + event.joystick.button = button; + emit = true; } - else { + } + else if (e->type == SDL_CONTROLLERDEVICEADDED || e->type == SDL_CONTROLLERDEVICEREMOVED + || e->type == SDL_JOYDEVICEADDED || e->type == SDL_JOYDEVICEREMOVED) { + event.joystick.type = ALLEGRO_EVENT_JOYSTICK_CONFIGURATION; + emit = true; + } + else if (e->type == SDL_JOYAXISMOTION) { + ALLEGRO_JOYSTICK_SDL *joy = get_joystick(e->jaxis.which); + if (joy->allegro.info.type != ALLEGRO_JOYSTICK_TYPE_UNKNOWN) return; - } + event.joystick.type = ALLEGRO_EVENT_JOYSTICK_AXIS; + event.joystick.id = &joy->allegro; + event.joystick.stick = e->jaxis.axis / 2; + event.joystick.axis = e->jaxis.axis % 2; + event.joystick.pos = e->jaxis.value / 32768.0; + event.joystick.button = 0; emit = true; } + else if (e->type == SDL_JOYBUTTONDOWN) { + ALLEGRO_JOYSTICK_SDL *joy = get_joystick(e->jbutton.which); + if (joy->allegro.info.type != ALLEGRO_JOYSTICK_TYPE_UNKNOWN) + return; + event.joystick.type = ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN; + event.joystick.id = &joy->allegro; + event.joystick.stick = 0; + event.joystick.axis = 0; + event.joystick.pos = 0; + event.joystick.button = e->jbutton.button; + emit = true; + } + else if (e->type == SDL_JOYBUTTONUP) { + ALLEGRO_JOYSTICK_SDL *joy = get_joystick(e->jbutton.which); + if (joy->allegro.info.type != ALLEGRO_JOYSTICK_TYPE_UNKNOWN) + return; + event.joystick.type = ALLEGRO_EVENT_JOYSTICK_BUTTON_UP; + event.joystick.id = &joy->allegro; + event.joystick.stick = 0; + event.joystick.axis = 0; + event.joystick.pos = 0; + event.joystick.button = e->jbutton.button; + emit = true; + } + else { + return; + } if (emit) { ALLEGRO_EVENT_SOURCE *es = al_get_joystick_event_source(); @@ -228,22 +275,38 @@ void _al_sdl_joystick_event(SDL_Event *e) } } +static void clear_joysticks(void) +{ + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + ALLEGRO_JOYSTICK_SDL *joy = *(ALLEGRO_JOYSTICK_SDL **)_al_vector_ref(&joysticks, i); + if (joy->sdl) { + SDL_JoystickClose(joy->sdl); + joy->sdl = NULL; + } + if (joy->sdl_gc) { + SDL_GameControllerClose(joy->sdl_gc); + joy->sdl_gc = NULL; + } + _al_destroy_joystick_info(&joy->allegro.info); + /* Note that we deliberately keep the joystick id as is, so we can reuse it. */ + } +} + static bool sdl_init_joystick(void) { - count = SDL_NumJoysticks(); - joysticks = count > 0 ? al_calloc(count, sizeof * joysticks) : NULL; - int i; - for (i = 0; i < count; i++) { - ALLEGRO_JOYSTICK_SDL *joy = &joysticks[i]; + num_joysticks = SDL_NumJoysticks(); + for (int i = 0; i < num_joysticks; i++) { + ALLEGRO_JOYSTICK_SDL *joy = get_or_insert_joystick(SDL_JoystickGetDeviceInstanceID(i)); SDL_JoystickGUID sdl_guid = SDL_JoystickGetDeviceGUID(i); memcpy(&joy->allegro.info.guid, &sdl_guid, sizeof(ALLEGRO_JOYSTICK_GUID)); - _al_fill_gamepad_info(&joy->allegro.info); if (SDL_IsGameController(i) && !compat_5_2_10()) { joy->sdl_gc = SDL_GameControllerOpen(i); + _al_fill_gamepad_info(&joy->allegro.info); SDL_GameControllerEventState(SDL_ENABLE); continue; } + joy->sdl = SDL_JoystickOpen(i); _AL_JOYSTICK_INFO *info = &joy->allegro.info; int an = SDL_JoystickNumAxes(joy->sdl); @@ -270,33 +333,33 @@ static bool sdl_init_joystick(void) static void sdl_exit_joystick(void) { - int i; - for (i = 0; i < count; i++) { - if (joysticks[i].sdl) - SDL_JoystickClose(joysticks[i].sdl); - if (joysticks[i].sdl_gc) - SDL_GameControllerClose(joysticks[i].sdl_gc); - _al_destroy_joystick_info(&joysticks[i].allegro.info); - } - count = 0; - al_free(joysticks); - joysticks = NULL; + clear_joysticks(); + num_joysticks = 0; + _al_vector_free(&joysticks); } static bool sdl_reconfigure_joysticks(void) { - sdl_exit_joystick(); + clear_joysticks(); return sdl_init_joystick(); } static int sdl_num_joysticks(void) { - return count; + return num_joysticks; } static ALLEGRO_JOYSTICK *sdl_get_joystick(int joyn) { - return &joysticks[joyn].allegro; + for (int i = 0; i < (int)_al_vector_size(&joysticks); i++) { + ALLEGRO_JOYSTICK_SDL *joy = *(ALLEGRO_JOYSTICK_SDL **)_al_vector_ref(&joysticks, i); + if (SDL_JoystickFromInstanceID(joy->id)) { + if (joyn == 0) + return &joy->allegro; + joyn--; + } + } + return NULL; } static void sdl_release_joystick(ALLEGRO_JOYSTICK *joy) @@ -313,9 +376,9 @@ static void sdl_get_joystick_state(ALLEGRO_JOYSTICK *joy, #define BUTTON(x) ((x) * 32767) #define AXIS(x) ((x) / 32768.0) - int id = get_id(joy); - if (joysticks[id].allegro.info.type == ALLEGRO_JOYSTICK_TYPE_GAMEPAD) { - SDL_GameController *sdl_gc = joysticks[id].sdl_gc; + ALLEGRO_JOYSTICK_SDL *joy_sdl = get_joystick_from_allegro(joy); + if (joy->info.type == ALLEGRO_JOYSTICK_TYPE_GAMEPAD) { + SDL_GameController *sdl_gc = joy_sdl->sdl_gc; ret_state->stick[ALLEGRO_GAMEPAD_STICK_DPAD].axis[0] = SDL_GameControllerGetButton(sdl_gc, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) - SDL_GameControllerGetButton(sdl_gc, SDL_CONTROLLER_BUTTON_DPAD_LEFT); ret_state->stick[ALLEGRO_GAMEPAD_STICK_DPAD].axis[1] = SDL_GameControllerGetButton(sdl_gc, SDL_CONTROLLER_BUTTON_DPAD_DOWN) @@ -340,7 +403,7 @@ static void sdl_get_joystick_state(ALLEGRO_JOYSTICK *joy, ret_state->button[ALLEGRO_GAMEPAD_BUTTON_RIGHT_THUMB] = BUTTON(SDL_GameControllerGetButton(sdl_gc, SDL_CONTROLLER_BUTTON_RIGHTSTICK)); } else { - SDL_Joystick *sdl = joysticks[id].sdl; + SDL_Joystick *sdl = joy_sdl->sdl; int an = SDL_JoystickNumAxes(sdl); int i; for (i = 0; i < an; i++) { @@ -359,20 +422,22 @@ static void sdl_get_joystick_state(ALLEGRO_JOYSTICK *joy, static const char *sdl_get_name(ALLEGRO_JOYSTICK *joy) { - int id = get_id(joy); - if (joysticks[id].sdl) - return SDL_JoystickName(joysticks[id].sdl); - else - return SDL_GameControllerName(joysticks[id].sdl_gc); + ALLEGRO_JOYSTICK_SDL *joy_sdl = get_joystick_from_allegro(joy); + if (joy_sdl->sdl) + return SDL_JoystickName(joy_sdl->sdl); + if (joy_sdl->sdl_gc) + return SDL_GameControllerName(joy_sdl->sdl_gc); + return NULL; } static bool sdl_get_active(ALLEGRO_JOYSTICK *joy) { - int id = get_id(joy); - if (joysticks[id].sdl) - return SDL_JoystickGetAttached(joysticks[id].sdl); - else - return SDL_GameControllerGetAttached(joysticks[id].sdl_gc); + ALLEGRO_JOYSTICK_SDL *joy_sdl = get_joystick_from_allegro(joy); + if (joy_sdl->sdl) + return SDL_JoystickGetAttached(joy_sdl->sdl); + if (joy_sdl->sdl_gc) + return SDL_GameControllerGetAttached(joy_sdl->sdl_gc); + return false; } ALLEGRO_JOYSTICK_DRIVER *_al_sdl_joystick_driver(void) diff --git a/src/win/wjoyxi.c b/src/win/wjoyxi.c index a860ee567..d9a054421 100644 --- a/src/win/wjoyxi.c +++ b/src/win/wjoyxi.c @@ -606,6 +606,89 @@ static void joyxi_init_joystick_info(ALLEGRO_JOYSTICK_XINPUT *xjoy) } } + +// Source: https://github.com/xan105/node-xinput-ffi/blob/master/lib/util/HardwareID.js +static const char *joyxi_lookup_device_name(WORD vid, WORD pid) +{ + if (vid == 0x045E) { + switch (pid) { + case 0x028E: return "Xbox360 Controller"; + case 0x02A1: return "Xbox360 Controller"; + case 0x028F: return "Xbox360 Wireless Controller"; + case 0x02E0: return "Xbox One S Controller"; + case 0x02FF: return "Xbox One Elite Controller"; + case 0x0202: return "Xbox Controller"; + case 0x0285: return "Xbox Controller S"; + case 0x0289: return "Xbox Controller S"; + case 0x02E3: return "Xbox One Elite Controller"; + case 0x02EA: return "Xbox One S Controller"; + case 0x02FD: return "Xbox One S Controller"; + case 0x02D1: return "Xbox One Controller"; + case 0x02DD: return "Xbox One Controller"; + case 0x0B13: return "Xbox Series X/S controller"; + } + return "Microsoft Corp."; + } + if (vid == 0x054C) { + switch (pid) { + case 0x0268: return "DualShock 3 / Sixaxis"; + case 0x05C4: return "DualShock 4"; + case 0x09CC: return "DualShock 4 (v2)"; + case 0x0BA0: return "DualShock 4 USB Wireless Adaptor"; + case 0x0CE6: return "DualSense Wireless Controller"; //PS5 + } + return "Sony Corp."; + } + if (vid == 0x057E) { + switch (pid) { + case 0x0306: return "Wii Remote Controller"; + case 0x0337: return "Wii U GameCube Controller Adapter"; + case 0x2006: return "Joy-Con L"; + case 0x2007: return "Joy-Con R"; + case 0x2009: return "Switch Pro Controller"; + case 0x200E: return "Joy-Con Charging Grip"; + } + return "Nintendo Co., Ltd"; + } + if (vid == 0x28DE) { + switch (pid) { + case 0x11FC: return "Steam Controller"; + case 0x1102: return "Steam Controller"; + case 0x1142: return "Wireless Steam Controller"; + } + return "Valve Corp."; + } + if (vid == 0x046D) { + switch (pid) { + case 0xC21D: return "Logitech Gamepad F310"; + case 0xC21E: return "Logitech Gamepad F510"; + case 0xC21F: return "Logitech Gamepad F710"; + case 0xC242: return "Logitech Chillstream Controller"; + } + return "Logitech Inc."; + } + return ""; +} + + +static void joyxi_set_name(ALLEGRO_JOYSTICK_XINPUT *xjoy) +{ + if (_imp_XInputGetCapabilitiesEx) { + XINPUT_CAPABILITIES_EX xicapas; + int res = _imp_XInputGetCapabilitiesEx(1, xjoy->index, 0, &xicapas); + if (res == ERROR_SUCCESS) { + const char *device_name = joyxi_lookup_device_name(xicapas.VendorId, xicapas.ProductId); + if (device_name[0] != '\0') + sprintf(xjoy->name, device_name); + else + sprintf(xjoy->name, "XInput Joystick vendor: %x product: %x", xicapas.VendorId, xicapas.ProductId); + return; + } + } + sprintf(xjoy->name, "XInput Joystick %d", (int)xjoy->index); +} + + /* Initialization API function. */ static bool joyxi_init_joystick(void) { @@ -632,6 +715,7 @@ static bool joyxi_init_joystick(void) joyxi_joysticks[index].active = false; joyxi_joysticks[index].index = (DWORD)index; joyxi_init_joystick_info(joyxi_joysticks + index); + joyxi_set_name(joyxi_joysticks + index); } /* Now, enable XInput*/ _imp_XInputEnable(TRUE); @@ -709,6 +793,7 @@ static bool joyxi_reconfigure_joysticks(void) if (joyxi_joysticks[index].active) { res = _imp_XInputGetState(joyxi_joysticks[index].index, &joyxi_joysticks[index].state); joyxi_joysticks[index].active = (res == ERROR_SUCCESS); + joyxi_set_name(joyxi_joysticks + index); } } al_unlock_mutex(joyxi_mutex); @@ -765,92 +850,10 @@ static void joyxi_get_joystick_state(ALLEGRO_JOYSTICK *joy, ALLEGRO_JOYSTICK_STA } -// Source: https://github.com/xan105/node-xinput-ffi/blob/master/lib/util/HardwareID.js -static const char *joyxi_lookup_device_name(WORD vid, WORD pid) -{ - if (vid == 0x045E) { - switch (pid) { - case 0x028E: return "Xbox360 Controller"; - case 0x02A1: return "Xbox360 Controller"; - case 0x028F: return "Xbox360 Wireless Controller"; - case 0x02E0: return "Xbox One S Controller"; - case 0x02FF: return "Xbox One Elite Controller"; - case 0x0202: return "Xbox Controller"; - case 0x0285: return "Xbox Controller S"; - case 0x0289: return "Xbox Controller S"; - case 0x02E3: return "Xbox One Elite Controller"; - case 0x02EA: return "Xbox One S Controller"; - case 0x02FD: return "Xbox One S Controller"; - case 0x02D1: return "Xbox One Controller"; - case 0x02DD: return "Xbox One Controller"; - case 0x0B13: return "Xbox Series X/S controller"; - } - return "Microsoft Corp."; - } - if (vid == 0x054C) { - switch (pid) { - case 0x0268: return "DualShock 3 / Sixaxis"; - case 0x05C4: return "DualShock 4"; - case 0x09CC: return "DualShock 4 (v2)"; - case 0x0BA0: return "DualShock 4 USB Wireless Adaptor"; - case 0x0CE6: return "DualSense Wireless Controller"; //PS5 - } - return "Sony Corp."; - } - if (vid == 0x057E) { - switch (pid) { - case 0x0306: return "Wii Remote Controller"; - case 0x0337: return "Wii U GameCube Controller Adapter"; - case 0x2006: return "Joy-Con L"; - case 0x2007: return "Joy-Con R"; - case 0x2009: return "Switch Pro Controller"; - case 0x200E: return "Joy-Con Charging Grip"; - } - return "Nintendo Co., Ltd"; - } - if (vid == 0x28DE) { - switch (pid) { - case 0x11FC: return "Steam Controller"; - case 0x1102: return "Steam Controller"; - case 0x1142: return "Wireless Steam Controller"; - } - return "Valve Corp."; - } - if (vid == 0x046D) { - switch (pid) { - case 0xC21D: return "Logitech Gamepad F310"; - case 0xC21E: return "Logitech Gamepad F510"; - case 0xC21F: return "Logitech Gamepad F710"; - case 0xC242: return "Logitech Chillstream Controller"; - } - return "Logitech Inc."; - } - return ""; -} - - static const char *joyxi_get_name(ALLEGRO_JOYSTICK *joy) { ALLEGRO_JOYSTICK_XINPUT *xjoy = (ALLEGRO_JOYSTICK_XINPUT *)joy; ASSERT(xjoy); - - if (xjoy->name[0] == '\0') { - if (_imp_XInputGetCapabilitiesEx) { - XINPUT_CAPABILITIES_EX xicapas; - int res = _imp_XInputGetCapabilitiesEx(1, xjoy->index, 0, &xicapas); - if (res == ERROR_SUCCESS) { - const char *device_name = joyxi_lookup_device_name(xicapas.VendorId, xicapas.ProductId); - if (device_name[0] != '\0') - sprintf(xjoy->name, device_name); - else - sprintf(xjoy->name, "XInput Joystick vendor: %x product: %x", xicapas.VendorId, xicapas.ProductId); - return xjoy->name; - } - } - - sprintf(xjoy->name, "XInput Joystick %d", xjoy->index); - } - return xjoy->name; }