Add UHID gamepad support
Similar to UHID keyboard and mouse, but for gamepads. Can be enabled with --gamepad=uhid or -G. It is not enabled by default because not all devices support UHID (there is a permission error on old Android versions). PR #5270 <https://github.com/Genymobile/scrcpy/pull/5270>
This commit is contained in:
parent
e4b012c4c9
commit
a3c0c63380
@ -26,6 +26,7 @@ _scrcpy() {
|
|||||||
-e --select-tcpip
|
-e --select-tcpip
|
||||||
-f --fullscreen
|
-f --fullscreen
|
||||||
--force-adb-forward
|
--force-adb-forward
|
||||||
|
-G
|
||||||
--gamepad=
|
--gamepad=
|
||||||
-h --help
|
-h --help
|
||||||
-K
|
-K
|
||||||
@ -129,7 +130,7 @@ _scrcpy() {
|
|||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--gamepad)
|
--gamepad)
|
||||||
COMPREPLY=($(compgen -W 'disabled aoa' -- "$cur"))
|
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--orientation|--display-orientation)
|
--orientation|--display-orientation)
|
||||||
|
@ -33,7 +33,8 @@ 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)'
|
'-G[Use UHID gamepad (same as --gamepad=uhid)]'
|
||||||
|
'--gamepad=[Set the gamepad input mode]:mode:(disabled uhid 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)'
|
||||||
|
@ -37,6 +37,7 @@ src = [
|
|||||||
'src/hid/hid_mouse.c',
|
'src/hid/hid_mouse.c',
|
||||||
'src/trait/frame_source.c',
|
'src/trait/frame_source.c',
|
||||||
'src/trait/packet_source.c',
|
'src/trait/packet_source.c',
|
||||||
|
'src/uhid/gamepad_uhid.c',
|
||||||
'src/uhid/keyboard_uhid.c',
|
'src/uhid/keyboard_uhid.c',
|
||||||
'src/uhid/mouse_uhid.c',
|
'src/uhid/mouse_uhid.c',
|
||||||
'src/uhid/uhid_output.c',
|
'src/uhid/uhid_output.c',
|
||||||
|
@ -175,13 +175,18 @@ 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
|
||||||
|
.B \-K
|
||||||
|
Same as \fB\-\-gamepad=uhid\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-gamepad " mode
|
.BI "\-\-gamepad " mode
|
||||||
Select how to send gamepad inputs to the device.
|
Select how to send gamepad inputs to the device.
|
||||||
|
|
||||||
Possible values are "disabled" and "aoa":
|
Possible values are "disabled", "uhid" and "aoa":
|
||||||
|
|
||||||
- "disabled" does not send gamepad inputs to the device.
|
- "disabled" does not send gamepad inputs to the device.
|
||||||
|
- "uhid" simulates physical HID gamepads using the Linux HID kernel module on the device.
|
||||||
- "aoa" simulates physical HID gamepads using the AOAv2 protocol. It may only work over USB.
|
- "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.
|
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
|
||||||
|
@ -373,13 +373,19 @@ 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",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'G',
|
||||||
|
.text = "Same as --gamepad=uhid.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_GAMEPAD,
|
.longopt_id = OPT_GAMEPAD,
|
||||||
.longopt = "gamepad",
|
.longopt = "gamepad",
|
||||||
.argdesc = "mode",
|
.argdesc = "mode",
|
||||||
.text = "Select how to send gamepad inputs to the device.\n"
|
.text = "Select how to send gamepad inputs to the device.\n"
|
||||||
"Possible values are \"disabled\" and \"aoa\".\n"
|
"Possible values are \"disabled\", \"uhid\" and \"aoa\".\n"
|
||||||
"\"disabled\" does not send gamepad inputs to the device.\n"
|
"\"disabled\" does not send gamepad inputs to the device.\n"
|
||||||
|
"\"uhid\" simulates physical HID gamepads using the Linux UHID "
|
||||||
|
"kernel module on the device.\n"
|
||||||
"\"aoa\" simulates physical gamepads using the AOAv2 protocol."
|
"\"aoa\" simulates physical gamepads using the AOAv2 protocol."
|
||||||
"It may only work over USB.\n"
|
"It may only work over USB.\n"
|
||||||
"Also see --keyboard and --mouse.",
|
"Also see --keyboard and --mouse.",
|
||||||
@ -2097,6 +2103,11 @@ parse_gamepad(const char *optarg, enum sc_gamepad_input_mode *mode) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(optarg, "uhid")) {
|
||||||
|
*mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(optarg, "aoa")) {
|
if (!strcmp(optarg, "aoa")) {
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
*mode = SC_GAMEPAD_INPUT_MODE_AOA;
|
*mode = SC_GAMEPAD_INPUT_MODE_AOA;
|
||||||
@ -2679,6 +2690,9 @@ 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 'G':
|
||||||
|
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||||
|
break;
|
||||||
case OPT_GAMEPAD:
|
case OPT_GAMEPAD:
|
||||||
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -158,6 +158,7 @@ enum sc_mouse_input_mode {
|
|||||||
|
|
||||||
enum sc_gamepad_input_mode {
|
enum sc_gamepad_input_mode {
|
||||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||||
|
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||||
SC_GAMEPAD_INPUT_MODE_AOA,
|
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "uhid/gamepad_uhid.h"
|
||||||
#include "uhid/keyboard_uhid.h"
|
#include "uhid/keyboard_uhid.h"
|
||||||
#include "uhid/mouse_uhid.h"
|
#include "uhid/mouse_uhid.h"
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
@ -80,9 +81,12 @@ struct scrcpy {
|
|||||||
struct sc_mouse_aoa mouse_aoa;
|
struct sc_mouse_aoa mouse_aoa;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
union {
|
||||||
|
struct sc_gamepad_uhid gamepad_uhid;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
struct sc_gamepad_aoa gamepad_aoa;
|
struct sc_gamepad_aoa gamepad_aoa;
|
||||||
#endif
|
#endif
|
||||||
|
};
|
||||||
struct sc_timeout timeout;
|
struct sc_timeout timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -749,6 +753,11 @@ aoa_complete:
|
|||||||
mp = &s->mouse_uhid.mouse_processor;
|
mp = &s->mouse_uhid.mouse_processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID) {
|
||||||
|
sc_gamepad_uhid_init(&s->gamepad_uhid, &s->controller);
|
||||||
|
gp = &s->gamepad_uhid.gamepad_processor;
|
||||||
|
}
|
||||||
|
|
||||||
sc_controller_configure(&s->controller, acksync, uhid_devices);
|
sc_controller_configure(&s->controller, acksync, uhid_devices);
|
||||||
|
|
||||||
if (!sc_controller_start(&s->controller)) {
|
if (!sc_controller_start(&s->controller)) {
|
||||||
|
122
app/src/uhid/gamepad_uhid.c
Normal file
122
app/src/uhid/gamepad_uhid.c
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#include "gamepad_uhid.h"
|
||||||
|
|
||||||
|
#include "hid/hid_gamepad.h"
|
||||||
|
#include "input_events.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast gamepad processor to sc_gamepad_uhid */
|
||||||
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
||||||
|
const struct sc_hid_input *hid_input,
|
||||||
|
const char *name) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
|
msg.uhid_input.id = hid_input->hid_id;
|
||||||
|
|
||||||
|
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||||
|
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||||
|
msg.uhid_input.size = hid_input->size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||||
|
LOGE("Could not push UHID_INPUT message (%s)", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
||||||
|
const struct sc_hid_open *hid_open) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
|
msg.uhid_create.id = hid_open->hid_id;
|
||||||
|
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||||
|
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||||
|
LOGE("Could not push UHID_CREATE message (gamepad)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
|
||||||
|
const struct sc_hid_close *hid_close) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_DESTROY;
|
||||||
|
msg.uhid_create.id = hid_close->hid_id;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||||
|
LOGE("Could not push UHID_DESTROY message (gamepad)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event) {
|
||||||
|
struct sc_gamepad_uhid *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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_axis_event *event) {
|
||||||
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
struct sc_hid_input hid_input;
|
||||||
|
if (!sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input,
|
||||||
|
event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_input(gamepad, &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_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
struct sc_hid_input hid_input;
|
||||||
|
if (!sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input,
|
||||||
|
event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_input(gamepad, &hid_input, "gamepad button");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
|
||||||
|
struct sc_controller *controller) {
|
||||||
|
sc_hid_gamepad_init(&gamepad->hid);
|
||||||
|
|
||||||
|
gamepad->controller = controller;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
23
app/src/uhid/gamepad_uhid.h
Normal file
23
app/src/uhid/gamepad_uhid.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef SC_GAMEPAD_UHID_H
|
||||||
|
#define SC_GAMEPAD_UHID_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
#include "hid/hid_gamepad.h"
|
||||||
|
#include "trait/gamepad_processor.h"
|
||||||
|
|
||||||
|
struct sc_gamepad_uhid {
|
||||||
|
struct sc_gamepad_processor gamepad_processor; // gamepad processor trait
|
||||||
|
|
||||||
|
struct sc_hid_gamepad hid;
|
||||||
|
struct sc_controller *controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_uhid_init(struct sc_gamepad_uhid *mouse,
|
||||||
|
struct sc_controller *controller);
|
||||||
|
|
||||||
|
#endif
|
@ -215,6 +215,9 @@ public class Controller implements AsyncProcessor {
|
|||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_UHID_DESTROY:
|
||||||
|
getUhidManager().close(msg.getId());
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
openHardKeyboardSettings();
|
openHardKeyboardSettings();
|
||||||
break;
|
break;
|
||||||
|
@ -95,6 +95,12 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void unregisterUhidListener(FileDescriptor fd) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
queue.removeOnFileDescriptorEventListener(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] extractHidOutputData(ByteBuffer buffer) {
|
private static byte[] extractHidOutputData(ByteBuffer buffer) {
|
||||||
/*
|
/*
|
||||||
* #define UHID_DATA_MAX 4096
|
* #define UHID_DATA_MAX 4096
|
||||||
@ -199,9 +205,15 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void close(int id) {
|
public void close(int id) {
|
||||||
FileDescriptor fd = fds.get(id);
|
// Linux: Documentation/hid/uhid.rst
|
||||||
assert fd != null;
|
// If you close() the fd, the device is automatically unregistered and destroyed internally.
|
||||||
close(fd);
|
FileDescriptor fd = fds.remove(id);
|
||||||
|
if (fd != null) {
|
||||||
|
unregisterUhidListener(fd);
|
||||||
|
close(fd);
|
||||||
|
} else {
|
||||||
|
Ln.w("Closing unknown UHID device: " + id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeAll() {
|
public void closeAll() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user