From 6210b51dcc35b8164e08eac880bccc8612738356 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 6 Sep 2024 23:08:08 +0200 Subject: [PATCH] Handle SDL gamepad events Introduce a gamepad processor trait, similar to the keyboard processor and mouse processor traits. Handle gamepad events received from SDL, convert them to scrcpy-specific gamepad events, and forward them to the gamepad processor. Further commits will provide AOA and UHID implementations of the gamepad processor trait. PR #5270 Co-authored-by: Luiz Henrique Laurini --- app/src/input_events.h | 98 +++++++++++++++++++++++++++++++ app/src/input_manager.c | 97 +++++++++++++++++++++++++++++- app/src/input_manager.h | 3 + app/src/scrcpy.c | 6 ++ app/src/screen.c | 1 + app/src/screen.h | 1 + app/src/trait/gamepad_processor.h | 50 ++++++++++++++++ 7 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 app/src/trait/gamepad_processor.h diff --git a/app/src/input_events.h b/app/src/input_events.h index bbf4372f..c8966a35 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -323,6 +323,38 @@ enum sc_mouse_button { SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2), }; +// Use the naming from SDL3 for gamepad axis and buttons: +// + +enum sc_gamepad_axis { + SC_GAMEPAD_AXIS_UNKNOWN = -1, + SC_GAMEPAD_AXIS_LEFTX = SDL_CONTROLLER_AXIS_LEFTX, + SC_GAMEPAD_AXIS_LEFTY = SDL_CONTROLLER_AXIS_LEFTY, + SC_GAMEPAD_AXIS_RIGHTX = SDL_CONTROLLER_AXIS_RIGHTX, + SC_GAMEPAD_AXIS_RIGHTY = SDL_CONTROLLER_AXIS_RIGHTY, + SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERLEFT, + SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERRIGHT, +}; + +enum sc_gamepad_button { + SC_GAMEPAD_BUTTON_UNKNOWN = -1, + SC_GAMEPAD_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A, + SC_GAMEPAD_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B, + SC_GAMEPAD_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X, + SC_GAMEPAD_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y, + SC_GAMEPAD_BUTTON_BACK = SDL_CONTROLLER_BUTTON_BACK, + SC_GAMEPAD_BUTTON_GUIDE = SDL_CONTROLLER_BUTTON_GUIDE, + SC_GAMEPAD_BUTTON_START = SDL_CONTROLLER_BUTTON_START, + SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_CONTROLLER_BUTTON_LEFTSTICK, + SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SC_GAMEPAD_BUTTON_DPAD_UP = SDL_CONTROLLER_BUTTON_DPAD_UP, + SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_CONTROLLER_BUTTON_DPAD_RIGHT, +}; + static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod), "SDL_Keymod must be convertible to sc_mod"); @@ -380,6 +412,33 @@ struct sc_touch_event { float pressure; }; +enum sc_gamepad_device_event_type { + SC_GAMEPAD_DEVICE_ADDED, + SC_GAMEPAD_DEVICE_REMOVED, +}; + +// As documented in : +// The ID value starts at 0 and increments from there. The value -1 is an +// invalid ID. +#define SC_GAMEPAD_ID_INVALID UINT32_C(-1) + +struct sc_gamepad_device_event { + enum sc_gamepad_device_event_type type; + uint32_t gamepad_id; +}; + +struct sc_gamepad_button_event { + uint32_t gamepad_id; + enum sc_action action; + enum sc_gamepad_button button; +}; + +struct sc_gamepad_axis_event { + uint32_t gamepad_id; + enum sc_gamepad_axis axis; + int16_t value; +}; + static inline uint16_t sc_mods_state_from_sdl(uint16_t mods_state) { return mods_state; @@ -444,4 +503,43 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) { return buttons_state; } +static inline enum sc_gamepad_device_event_type +sc_gamepad_device_event_type_from_sdl_type(uint32_t type) { + assert(type == SDL_CONTROLLERDEVICEADDED + || type == SDL_CONTROLLERDEVICEREMOVED); + if (type == SDL_CONTROLLERDEVICEADDED) { + return SC_GAMEPAD_DEVICE_ADDED; + } + return SC_GAMEPAD_DEVICE_REMOVED; +} + +static inline enum sc_gamepad_axis +sc_gamepad_axis_from_sdl(uint8_t axis) { + if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + // SC_GAMEPAD_AXIS_* constants are initialized from + // SDL_CONTROLLER_AXIS_* + return axis; + } + return SC_GAMEPAD_AXIS_UNKNOWN; +} + +static inline enum sc_gamepad_button +sc_gamepad_button_from_sdl(uint8_t button) { + if (button <= SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { + // SC_GAMEPAD_BUTTON_* constants are initialized from + // SDL_CONTROLLER_BUTTON_* + return button; + } + return SC_GAMEPAD_BUTTON_UNKNOWN; +} + +static inline enum sc_action +sc_action_from_sdl_controllerbutton_type(uint32_t type) { + assert(type == SDL_CONTROLLERBUTTONDOWN || type == SDL_CONTROLLERBUTTONUP); + if (type == SDL_CONTROLLERBUTTONDOWN) { + return SC_ACTION_DOWN; + } + return SC_ACTION_UP; +} + #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 00f06777..77cb4f1d 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -56,16 +56,18 @@ void sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params) { // A key/mouse processor may not be present if there is no controller - assert((!params->kp && !params->mp) || params->controller); + assert((!params->kp && !params->mp && !params->gp) || params->controller); // A processor must have ops initialized assert(!params->kp || params->kp->ops); assert(!params->mp || params->mp->ops); + assert(!params->gp || params->gp->ops); im->controller = params->controller; im->fp = params->fp; im->screen = params->screen; im->kp = params->kp; im->mp = params->mp; + im->gp = params->gp; im->mouse_bindings = params->mouse_bindings; im->legacy_paste = params->legacy_paste; @@ -920,6 +922,78 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, im->mp->ops->process_mouse_scroll(im->mp, &evt); } +static void +sc_input_manager_process_gamepad_device(struct sc_input_manager *im, + const SDL_ControllerDeviceEvent *event) { + SDL_JoystickID id; + if (event->type == SDL_CONTROLLERDEVICEADDED) { + SDL_GameController *gc = SDL_GameControllerOpen(event->which); + if (!gc) { + LOGW("Could not open game controller"); + return; + } + + SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc); + if (!joystick) { + LOGW("Could not get controller joystick"); + SDL_GameControllerClose(gc); + return; + } + + id = SDL_JoystickInstanceID(joystick); + } else if (event->type == SDL_CONTROLLERDEVICEREMOVED) { + id = event->which; + + SDL_GameController *gc = SDL_GameControllerFromInstanceID(id); + if (gc) { + SDL_GameControllerClose(gc); + } else { + LOGW("Unknown gamepad device removed"); + } + } else { + // Nothing to do + return; + } + + struct sc_gamepad_device_event evt = { + .type = sc_gamepad_device_event_type_from_sdl_type(event->type), + .gamepad_id = id, + }; + im->gp->ops->process_gamepad_device(im->gp, &evt); +} + +static void +sc_input_manager_process_gamepad_axis(struct sc_input_manager *im, + const SDL_ControllerAxisEvent *event) { + enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis); + if (axis == SC_GAMEPAD_AXIS_UNKNOWN) { + return; + } + + struct sc_gamepad_axis_event evt = { + .gamepad_id = event->which, + .axis = axis, + .value = event->value, + }; + im->gp->ops->process_gamepad_axis(im->gp, &evt); +} + +static void +sc_input_manager_process_gamepad_button(struct sc_input_manager *im, + const SDL_ControllerButtonEvent *event) { + enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button); + if (button == SC_GAMEPAD_BUTTON_UNKNOWN) { + return; + } + + struct sc_gamepad_button_event evt = { + .gamepad_id = event->which, + .action = sc_action_from_sdl_controllerbutton_type(event->type), + .button = button, + }; + im->gp->ops->process_gamepad_button(im->gp, &evt); +} + static bool is_apk(const char *file) { const char *ext = strrchr(file, '.'); @@ -992,6 +1066,27 @@ sc_input_manager_handle_event(struct sc_input_manager *im, } sc_input_manager_process_touch(im, &event->tfinger); break; + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + // Handle device added or removed even if paused + if (!im->gp) { + break; + } + sc_input_manager_process_gamepad_device(im, &event->cdevice); + break; + case SDL_CONTROLLERAXISMOTION: + if (!im->gp || paused) { + break; + } + sc_input_manager_process_gamepad_axis(im, &event->caxis); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + if (!im->gp || paused) { + break; + } + sc_input_manager_process_gamepad_button(im, &event->cbutton); + break; case SDL_DROPFILE: { if (!control) { break; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 88558549..8efd0153 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -11,6 +11,7 @@ #include "file_pusher.h" #include "fps_counter.h" #include "options.h" +#include "trait/gamepad_processor.h" #include "trait/key_processor.h" #include "trait/mouse_processor.h" @@ -21,6 +22,7 @@ struct sc_input_manager { struct sc_key_processor *kp; struct sc_mouse_processor *mp; + struct sc_gamepad_processor *gp; struct sc_mouse_bindings mouse_bindings; bool legacy_paste; @@ -50,6 +52,7 @@ struct sc_input_manager_params { struct sc_screen *screen; struct sc_key_processor *kp; struct sc_mouse_processor *mp; + struct sc_gamepad_processor *gp; struct sc_mouse_bindings mouse_bindings; bool legacy_paste; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d1592240..fbae2d13 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -484,6 +484,11 @@ scrcpy(struct scrcpy_options *options) { } } + if (SDL_Init(SDL_INIT_GAMECONTROLLER)) { + LOGE("Could not initialize SDL gamepad: %s", SDL_GetError()); + goto end; + } + sdl_configure(options->video_playback, options->disable_screensaver); // Await for server without blocking Ctrl+C handling @@ -734,6 +739,7 @@ aoa_complete: .fp = fp, .kp = kp, .mp = mp, + .gp = NULL, .mouse_bindings = options->mouse_bindings, .legacy_paste = options->legacy_paste, .clipboard_autosync = options->clipboard_autosync, diff --git a/app/src/screen.c b/app/src/screen.c index 42be554a..cb455cb1 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -477,6 +477,7 @@ sc_screen_init(struct sc_screen *screen, .screen = screen, .kp = params->kp, .mp = params->mp, + .gp = params->gp, .mouse_bindings = params->mouse_bindings, .legacy_paste = params->legacy_paste, .clipboard_autosync = params->clipboard_autosync, diff --git a/app/src/screen.h b/app/src/screen.h index 079d4fbb..7e1f7e6e 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -78,6 +78,7 @@ struct sc_screen_params { struct sc_file_pusher *fp; struct sc_key_processor *kp; struct sc_mouse_processor *mp; + struct sc_gamepad_processor *gp; struct sc_mouse_bindings mouse_bindings; bool legacy_paste; diff --git a/app/src/trait/gamepad_processor.h b/app/src/trait/gamepad_processor.h new file mode 100644 index 00000000..72479783 --- /dev/null +++ b/app/src/trait/gamepad_processor.h @@ -0,0 +1,50 @@ +#ifndef SC_GAMEPAD_PROCESSOR_H +#define SC_GAMEPAD_PROCESSOR_H + +#include "common.h" + +#include +#include + +#include "input_events.h" + +/** + * Gamepad processor trait. + * + * Component able to handle gamepads devices and inject buttons and axis events. + */ +struct sc_gamepad_processor { + const struct sc_gamepad_processor_ops *ops; +}; + +struct sc_gamepad_processor_ops { + + /** + * Process a gamepad device added or removed + * + * This function is mandatory. + */ + void + (*process_gamepad_device)(struct sc_gamepad_processor *gp, + const struct sc_gamepad_device_event *event); + + /** + * Process a gamepad axis event + * + * This function is mandatory. + */ + void + (*process_gamepad_axis)(struct sc_gamepad_processor *gp, + const struct sc_gamepad_axis_event *event); + + /** + * Process a gamepad button event + * + * This function is mandatory. + */ + void + (*process_gamepad_button)(struct sc_gamepad_processor *gp, + const struct sc_gamepad_button_event *event); +}; + +#endif