mirror of https://github.com/Genymobile/scrcpy
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:
parent
a59c6df4b7
commit
a34a62ca4b
|
@ -26,6 +26,7 @@ _scrcpy() {
|
|||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
--gamepad=
|
||||
-h --help
|
||||
-K
|
||||
--keyboard=
|
||||
|
@ -127,6 +128,10 @@ _scrcpy() {
|
|||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--gamepad)
|
||||
COMPREPLY=($(compgen -W 'disabled aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--orientation|--display-orientation)
|
||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||
return
|
||||
|
|
|
@ -33,6 +33,7 @@ arguments=(
|
|||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||
{-f,--fullscreen}'[Start in fullscreen]'
|
||||
'--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]'
|
||||
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
|
||||
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
|
|
|
@ -95,6 +95,7 @@ usb_support = get_option('usb')
|
|||
if usb_support
|
||||
src += [
|
||||
'src/usb/aoa_hid.c',
|
||||
'src/usb/gamepad_aoa.c',
|
||||
'src/usb/keyboard_aoa.c',
|
||||
'src/usb/mouse_aoa.c',
|
||||
'src/usb/scrcpy_otg.c',
|
||||
|
|
16
app/scrcpy.1
16
app/scrcpy.1
|
@ -175,6 +175,16 @@ Start in fullscreen.
|
|||
.B \-\-force\-adb\-forward
|
||||
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
|
||||
.B \-h, \-\-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).
|
||||
|
||||
Also see \fB\-\-mouse\fR.
|
||||
Also see \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.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.
|
||||
|
||||
Also see \fB\-\-keyboard\fR.
|
||||
Also see \fB\-\-keyboard\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.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.
|
||||
|
||||
See \fB\-\-keyboard\fR and \fB\-\-mouse\fR.
|
||||
See \fB\-\-keyboard\fR, \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
||||
|
|
|
@ -101,6 +101,7 @@ enum {
|
|||
OPT_MOUSE_BIND,
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
OPT_AUDIO_DUP,
|
||||
OPT_GAMEPAD,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
|
@ -372,6 +373,17 @@ static const struct sc_option options[] = {
|
|||
.longopt_id = OPT_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',
|
||||
.longopt = "help",
|
||||
|
@ -403,7 +415,7 @@ static const struct sc_option options[] = {
|
|||
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||
"This option is only available when a HID keyboard is enabled "
|
||||
"(or a physical keyboard is connected).\n"
|
||||
"Also see --mouse.",
|
||||
"Also see --mouse and --gamepad.",
|
||||
},
|
||||
{
|
||||
.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"
|
||||
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
||||
"control of the mouse back to the computer.\n"
|
||||
"Also see --keyboard.",
|
||||
"Also see --keyboard and --gamepad.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MOUSE_BIND,
|
||||
|
@ -637,7 +649,7 @@ static const struct sc_option options[] = {
|
|||
"Keyboard and mouse may be disabled separately using"
|
||||
"--keyboard=disabled and --mouse=disabled.\n"
|
||||
"It may only work over USB.\n"
|
||||
"See --keyboard and --mouse.",
|
||||
"See --keyboard, --mouse and --gamepad.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'p',
|
||||
|
@ -2046,6 +2058,27 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
|
|||
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
|
||||
parse_time_limit(const char *s, sc_tick *tick) {
|
||||
long value;
|
||||
|
@ -2612,6 +2645,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||
case OPT_AUDIO_DUP:
|
||||
opts->audio_dup = true;
|
||||
break;
|
||||
case OPT_GAMEPAD:
|
||||
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
|
|
|
@ -23,6 +23,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
.mouse_bindings = {
|
||||
.pri = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
|
|
|
@ -156,6 +156,11 @@ enum sc_mouse_input_mode {
|
|||
SC_MOUSE_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_gamepad_input_mode {
|
||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_mouse_binding {
|
||||
SC_MOUSE_BINDING_AUTO,
|
||||
SC_MOUSE_BINDING_DISABLED,
|
||||
|
@ -231,6 +236,7 @@ struct scrcpy_options {
|
|||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
enum sc_gamepad_input_mode gamepad_input_mode;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
enum sc_camera_facing camera_facing;
|
||||
struct sc_port_range port_range;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "uhid/mouse_uhid.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/aoa_hid.h"
|
||||
# include "usb/gamepad_aoa.h"
|
||||
# include "usb/keyboard_aoa.h"
|
||||
# include "usb/mouse_aoa.h"
|
||||
# include "usb/usb.h"
|
||||
|
@ -79,6 +80,9 @@ struct scrcpy {
|
|||
struct sc_mouse_aoa mouse_aoa;
|
||||
#endif
|
||||
};
|
||||
#ifdef HAVE_USB
|
||||
struct sc_gamepad_aoa gamepad_aoa;
|
||||
#endif
|
||||
struct sc_timeout timeout;
|
||||
};
|
||||
|
||||
|
@ -370,6 +374,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||
bool aoa_hid_initialized = false;
|
||||
bool keyboard_aoa_initialized = false;
|
||||
bool mouse_aoa_initialized = false;
|
||||
bool gamepad_aoa_initialized = false;
|
||||
#endif
|
||||
bool controller_initialized = false;
|
||||
bool controller_started = false;
|
||||
|
@ -485,9 +490,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||
}
|
||||
}
|
||||
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
|
||||
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||
goto end;
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
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);
|
||||
|
@ -587,6 +594,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||
struct sc_controller *controller = NULL;
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
struct sc_gamepad_processor *gp = NULL;
|
||||
|
||||
if (options->control) {
|
||||
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;
|
||||
bool use_mouse_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);
|
||||
if (!ok) {
|
||||
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:
|
||||
if (aoa_fail || !sc_aoa_start(&s->aoa)) {
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
|
@ -740,7 +756,7 @@ aoa_complete:
|
|||
.fp = fp,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.gp = NULL,
|
||||
.gp = gp,
|
||||
.mouse_bindings = options->mouse_bindings,
|
||||
.legacy_paste = options->legacy_paste,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
|
@ -878,6 +894,9 @@ end:
|
|||
if (mouse_aoa_initialized) {
|
||||
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
||||
}
|
||||
if (gamepad_aoa_initialized) {
|
||||
sc_gamepad_aoa_destroy(&s->gamepad_aoa);
|
||||
}
|
||||
sc_aoa_stop(&s->aoa);
|
||||
sc_usb_stop(&s->usb);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue