Add AOA gamepad support

Similar to AOA keyboard and mouse, but for gamepads.

Can be enabled with --gamepad=aoa.

PR #5270 <https://github.com/Genymobile/scrcpy/pull/5270>
This commit is contained in:
Romain Vimont 2024-09-06 23:08:08 +02:00
parent a59c6df4b7
commit a34a62ca4b
10 changed files with 208 additions and 11 deletions

View File

@ -26,6 +26,7 @@ _scrcpy() {
-e --select-tcpip -e --select-tcpip
-f --fullscreen -f --fullscreen
--force-adb-forward --force-adb-forward
--gamepad=
-h --help -h --help
-K -K
--keyboard= --keyboard=
@ -127,6 +128,10 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur")) COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
return return
;; ;;
--gamepad)
COMPREPLY=($(compgen -W 'disabled aoa' -- "$cur"))
return
;;
--orientation|--display-orientation) --orientation|--display-orientation)
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return return

View File

@ -33,6 +33,7 @@ arguments=(
{-e,--select-tcpip}'[Use TCP/IP device]' {-e,--select-tcpip}'[Use TCP/IP device]'
{-f,--fullscreen}'[Start in fullscreen]' {-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--gamepad=[Set the gamepad input mode]:mode:(disabled aoa)'
{-h,--help}'[Print the help]' {-h,--help}'[Print the help]'
'-K[Use UHID keyboard (same as --keyboard=uhid)]' '-K[Use UHID keyboard (same as --keyboard=uhid)]'
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' '--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'

View File

@ -95,6 +95,7 @@ usb_support = get_option('usb')
if usb_support if usb_support
src += [ src += [
'src/usb/aoa_hid.c', 'src/usb/aoa_hid.c',
'src/usb/gamepad_aoa.c',
'src/usb/keyboard_aoa.c', 'src/usb/keyboard_aoa.c',
'src/usb/mouse_aoa.c', 'src/usb/mouse_aoa.c',
'src/usb/scrcpy_otg.c', 'src/usb/scrcpy_otg.c',

View File

@ -175,6 +175,16 @@ Start in fullscreen.
.B \-\-force\-adb\-forward .B \-\-force\-adb\-forward
Do not attempt to use "adb reverse" to connect to the device. Do not attempt to use "adb reverse" to connect to the device.
.TP
.BI "\-\-gamepad " mode
Select how to send gamepad inputs to the device.
Possible values are "disabled" and "aoa":
- "disabled" does not send gamepad inputs to the device.
- "aoa" simulates physical HID gamepads using the AOAv2 protocol. It may only work over USB.
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
.TP .TP
.B \-h, \-\-help .B \-h, \-\-help
Print this help. Print this help.
@ -200,7 +210,7 @@ For "uhid" and "aoa", the keyboard layout must be configured (once and for all)
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected). This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
Also see \fB\-\-mouse\fR. Also see \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
.TP .TP
.B \-\-kill\-adb\-on\-close .B \-\-kill\-adb\-on\-close
@ -267,7 +277,7 @@ In "uhid" and "aoa" modes, the computer mouse is captured to control the device
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
Also see \fB\-\-keyboard\fR. Also see \fB\-\-keyboard\fR and \fB\-\-gamepad\fR.
.TP .TP
.BI "\-\-mouse\-bind " xxxx[:xxxx] .BI "\-\-mouse\-bind " xxxx[:xxxx]
@ -369,7 +379,7 @@ If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable ke
It may only work over USB. It may only work over USB.
See \fB\-\-keyboard\fR and \fB\-\-mouse\fR. See \fB\-\-keyboard\fR, \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
.TP .TP
.BI "\-p, \-\-port " port\fR[:\fIport\fR] .BI "\-p, \-\-port " port\fR[:\fIport\fR]

View File

@ -101,6 +101,7 @@ enum {
OPT_MOUSE_BIND, OPT_MOUSE_BIND,
OPT_NO_MOUSE_HOVER, OPT_NO_MOUSE_HOVER,
OPT_AUDIO_DUP, OPT_AUDIO_DUP,
OPT_GAMEPAD,
}; };
struct sc_option { struct sc_option {
@ -372,6 +373,17 @@ static const struct sc_option options[] = {
.longopt_id = OPT_FORWARD_ALL_CLICKS, .longopt_id = OPT_FORWARD_ALL_CLICKS,
.longopt = "forward-all-clicks", .longopt = "forward-all-clicks",
}, },
{
.longopt_id = OPT_GAMEPAD,
.longopt = "gamepad",
.argdesc = "mode",
.text = "Select how to send gamepad inputs to the device.\n"
"Possible values are \"disabled\" and \"aoa\".\n"
"\"disabled\" does not send gamepad inputs to the device.\n"
"\"aoa\" simulates physical gamepads using the AOAv2 protocol."
"It may only work over USB.\n"
"Also see --keyboard and --mouse.",
},
{ {
.shortopt = 'h', .shortopt = 'h',
.longopt = "help", .longopt = "help",
@ -403,7 +415,7 @@ static const struct sc_option options[] = {
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n" "start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"This option is only available when a HID keyboard is enabled " "This option is only available when a HID keyboard is enabled "
"(or a physical keyboard is connected).\n" "(or a physical keyboard is connected).\n"
"Also see --mouse.", "Also see --mouse and --gamepad.",
}, },
{ {
.longopt_id = OPT_KILL_ADB_ON_CLOSE, .longopt_id = OPT_KILL_ADB_ON_CLOSE,
@ -502,7 +514,7 @@ static const struct sc_option options[] = {
"to control the device directly (relative mouse mode).\n" "to control the device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give " "LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n" "control of the mouse back to the computer.\n"
"Also see --keyboard.", "Also see --keyboard and --gamepad.",
}, },
{ {
.longopt_id = OPT_MOUSE_BIND, .longopt_id = OPT_MOUSE_BIND,
@ -637,7 +649,7 @@ static const struct sc_option options[] = {
"Keyboard and mouse may be disabled separately using" "Keyboard and mouse may be disabled separately using"
"--keyboard=disabled and --mouse=disabled.\n" "--keyboard=disabled and --mouse=disabled.\n"
"It may only work over USB.\n" "It may only work over USB.\n"
"See --keyboard and --mouse.", "See --keyboard, --mouse and --gamepad.",
}, },
{ {
.shortopt = 'p', .shortopt = 'p',
@ -2046,6 +2058,27 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
return false; return false;
} }
static bool
parse_gamepad(const char *optarg, enum sc_gamepad_input_mode *mode) {
if (!strcmp(optarg, "disabled")) {
*mode = SC_GAMEPAD_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_GAMEPAD_INPUT_MODE_AOA;
return true;
#else
LOGE("--gamepad=aoa is disabled.");
return false;
#endif
}
LOGE("Unsupported gamepad: %s (expected disabled or aoa)", optarg);
return false;
}
static bool static bool
parse_time_limit(const char *s, sc_tick *tick) { parse_time_limit(const char *s, sc_tick *tick) {
long value; long value;
@ -2612,6 +2645,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_AUDIO_DUP: case OPT_AUDIO_DUP:
opts->audio_dup = true; opts->audio_dup = true;
break; break;
case OPT_GAMEPAD:
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
return false;
}
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View File

@ -23,6 +23,7 @@ const struct scrcpy_options scrcpy_options_default = {
.record_format = SC_RECORD_FORMAT_AUTO, .record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO, .mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED,
.mouse_bindings = { .mouse_bindings = {
.pri = { .pri = {
.right_click = SC_MOUSE_BINDING_AUTO, .right_click = SC_MOUSE_BINDING_AUTO,

View File

@ -156,6 +156,11 @@ enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_AOA, SC_MOUSE_INPUT_MODE_AOA,
}; };
enum sc_gamepad_input_mode {
SC_GAMEPAD_INPUT_MODE_DISABLED,
SC_GAMEPAD_INPUT_MODE_AOA,
};
enum sc_mouse_binding { enum sc_mouse_binding {
SC_MOUSE_BINDING_AUTO, SC_MOUSE_BINDING_AUTO,
SC_MOUSE_BINDING_DISABLED, SC_MOUSE_BINDING_DISABLED,
@ -231,6 +236,7 @@ struct scrcpy_options {
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode; enum sc_mouse_input_mode mouse_input_mode;
enum sc_gamepad_input_mode gamepad_input_mode;
struct sc_mouse_bindings mouse_bindings; struct sc_mouse_bindings mouse_bindings;
enum sc_camera_facing camera_facing; enum sc_camera_facing camera_facing;
struct sc_port_range port_range; struct sc_port_range port_range;

View File

@ -29,6 +29,7 @@
#include "uhid/mouse_uhid.h" #include "uhid/mouse_uhid.h"
#ifdef HAVE_USB #ifdef HAVE_USB
# include "usb/aoa_hid.h" # include "usb/aoa_hid.h"
# include "usb/gamepad_aoa.h"
# include "usb/keyboard_aoa.h" # include "usb/keyboard_aoa.h"
# include "usb/mouse_aoa.h" # include "usb/mouse_aoa.h"
# include "usb/usb.h" # include "usb/usb.h"
@ -79,6 +80,9 @@ struct scrcpy {
struct sc_mouse_aoa mouse_aoa; struct sc_mouse_aoa mouse_aoa;
#endif #endif
}; };
#ifdef HAVE_USB
struct sc_gamepad_aoa gamepad_aoa;
#endif
struct sc_timeout timeout; struct sc_timeout timeout;
}; };
@ -370,6 +374,7 @@ scrcpy(struct scrcpy_options *options) {
bool aoa_hid_initialized = false; bool aoa_hid_initialized = false;
bool keyboard_aoa_initialized = false; bool keyboard_aoa_initialized = false;
bool mouse_aoa_initialized = false; bool mouse_aoa_initialized = false;
bool gamepad_aoa_initialized = false;
#endif #endif
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
@ -485,9 +490,11 @@ scrcpy(struct scrcpy_options *options) {
} }
} }
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) { if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError()); if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
goto end; LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
goto end;
}
} }
sdl_configure(options->video_playback, options->disable_screensaver); sdl_configure(options->video_playback, options->disable_screensaver);
@ -587,6 +594,7 @@ scrcpy(struct scrcpy_options *options) {
struct sc_controller *controller = NULL; struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL; struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL; struct sc_mouse_processor *mp = NULL;
struct sc_gamepad_processor *gp = NULL;
if (options->control) { if (options->control) {
static const struct sc_controller_callbacks controller_cbs = { static const struct sc_controller_callbacks controller_cbs = {
@ -606,7 +614,9 @@ scrcpy(struct scrcpy_options *options) {
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool use_mouse_aoa = bool use_mouse_aoa =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
if (use_keyboard_aoa || use_mouse_aoa) { bool use_gamepad_aoa =
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
if (use_keyboard_aoa || use_mouse_aoa || use_gamepad_aoa) {
bool ok = sc_acksync_init(&s->acksync); bool ok = sc_acksync_init(&s->acksync);
if (!ok) { if (!ok) {
goto end; goto end;
@ -672,6 +682,12 @@ scrcpy(struct scrcpy_options *options) {
} }
} }
if (use_gamepad_aoa) {
sc_gamepad_aoa_init(&s->gamepad_aoa, &s->aoa);
gp = &s->gamepad_aoa.gamepad_processor;
gamepad_aoa_initialized = true;
}
aoa_complete: aoa_complete:
if (aoa_fail || !sc_aoa_start(&s->aoa)) { if (aoa_fail || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync); sc_acksync_destroy(&s->acksync);
@ -740,7 +756,7 @@ aoa_complete:
.fp = fp, .fp = fp,
.kp = kp, .kp = kp,
.mp = mp, .mp = mp,
.gp = NULL, .gp = gp,
.mouse_bindings = options->mouse_bindings, .mouse_bindings = options->mouse_bindings,
.legacy_paste = options->legacy_paste, .legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync, .clipboard_autosync = options->clipboard_autosync,
@ -878,6 +894,9 @@ end:
if (mouse_aoa_initialized) { if (mouse_aoa_initialized) {
sc_mouse_aoa_destroy(&s->mouse_aoa); sc_mouse_aoa_destroy(&s->mouse_aoa);
} }
if (gamepad_aoa_initialized) {
sc_gamepad_aoa_destroy(&s->gamepad_aoa);
}
sc_aoa_stop(&s->aoa); sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb); sc_usb_stop(&s->usb);
} }

91
app/src/usb/gamepad_aoa.c Normal file
View File

@ -0,0 +1,91 @@
#include "gamepad_aoa.h"
#include "input_events.h"
#include "util/log.h"
/** Downcast gamepad processor to gamepad_aoa */
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
static void
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) {
return;
}
// exit_on_error: false (a gamepad open failure should not exit scrcpy)
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
LOGW("Could not push AOA HID open (gamepad)");
}
} else {
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
event->gamepad_id)) {
return;
}
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
LOGW("Could not push AOA HID close (gamepad)");
}
}
}
static void
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
const struct sc_gamepad_axis_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
struct sc_hid_input hid_input;
if (!sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input,
event)) {
return;
}
if (!sc_aoa_push_input(gamepad->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (gamepad axis)");
}
}
static void
sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
const struct sc_gamepad_button_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
struct sc_hid_input hid_input;
if (!sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input,
event)) {
return;
}
if (!sc_aoa_push_input(gamepad->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (gamepad button)");
}
}
void
sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
gamepad->aoa = aoa;
sc_hid_gamepad_init(&gamepad->hid);
static const struct sc_gamepad_processor_ops ops = {
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
};
gamepad->gamepad_processor.ops = &ops;
}
void
sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad) {
(void) gamepad;
// Do nothing, gamepad->aoa will automatically unregister all devices
}

25
app/src/usb/gamepad_aoa.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef SC_GAMEPAD_AOA_H
#define SC_GAMEPAD_AOA_H
#include "common.h"
#include <stdbool.h>
#include "aoa_hid.h"
#include "hid/hid_gamepad.h"
#include "trait/gamepad_processor.h"
struct sc_gamepad_aoa {
struct sc_gamepad_processor gamepad_processor; // gamepad processor trait
struct sc_hid_gamepad hid;
struct sc_aoa *aoa;
};
void
sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa);
void
sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad);
#endif