mirror of
https://github.com/Genymobile/scrcpy
synced 2024-12-23 23:53:46 +00:00
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
64a25f6e9d
commit
f9d1a333a0
@ -26,6 +26,7 @@ _scrcpy() {
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
-G
|
||||
--gamepad=
|
||||
-h --help
|
||||
-K
|
||||
@ -129,7 +130,7 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--gamepad)
|
||||
COMPREPLY=($(compgen -W 'disabled aoa' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--orientation|--display-orientation)
|
||||
|
@ -33,7 +33,8 @@ 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)'
|
||||
'-G[Use UHID gamepad (same as --gamepad=uhid)]'
|
||||
'--gamepad=[Set the gamepad input mode]:mode:(disabled uhid 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)'
|
||||
|
@ -37,6 +37,7 @@ src = [
|
||||
'src/hid/hid_mouse.c',
|
||||
'src/trait/frame_source.c',
|
||||
'src/trait/packet_source.c',
|
||||
'src/uhid/gamepad_uhid.c',
|
||||
'src/uhid/keyboard_uhid.c',
|
||||
'src/uhid/mouse_uhid.c',
|
||||
'src/uhid/uhid_output.c',
|
||||
|
@ -175,13 +175,18 @@ Start in fullscreen.
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-K
|
||||
Same as \fB\-\-gamepad=uhid\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-gamepad " mode
|
||||
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.
|
||||
- "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.
|
||||
|
||||
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 = "forward-all-clicks",
|
||||
},
|
||||
{
|
||||
.shortopt = 'G',
|
||||
.text = "Same as --gamepad=uhid.",
|
||||
},
|
||||
{
|
||||
.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"
|
||||
"Possible values are \"disabled\", \"uhid\" and \"aoa\".\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."
|
||||
"It may only work over USB.\n"
|
||||
"Also see --keyboard and --mouse.",
|
||||
@ -2065,6 +2071,11 @@ parse_gamepad(const char *optarg, enum sc_gamepad_input_mode *mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "aoa")) {
|
||||
#ifdef HAVE_USB
|
||||
*mode = SC_GAMEPAD_INPUT_MODE_AOA;
|
||||
@ -2645,6 +2656,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_AUDIO_DUP:
|
||||
opts->audio_dup = true;
|
||||
break;
|
||||
case 'G':
|
||||
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||
break;
|
||||
case OPT_GAMEPAD:
|
||||
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
||||
return false;
|
||||
|
@ -158,6 +158,7 @@ enum sc_mouse_input_mode {
|
||||
|
||||
enum sc_gamepad_input_mode {
|
||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "recorder.h"
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
#include "uhid/gamepad_uhid.h"
|
||||
#include "uhid/keyboard_uhid.h"
|
||||
#include "uhid/mouse_uhid.h"
|
||||
#ifdef HAVE_USB
|
||||
@ -80,9 +81,12 @@ struct scrcpy {
|
||||
struct sc_mouse_aoa mouse_aoa;
|
||||
#endif
|
||||
};
|
||||
union {
|
||||
struct sc_gamepad_uhid gamepad_uhid;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_gamepad_aoa gamepad_aoa;
|
||||
struct sc_gamepad_aoa gamepad_aoa;
|
||||
#endif
|
||||
};
|
||||
struct sc_timeout timeout;
|
||||
};
|
||||
|
||||
@ -750,6 +754,11 @@ aoa_complete:
|
||||
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);
|
||||
|
||||
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:
|
||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||
break;
|
||||
case ControlMessage.TYPE_UHID_DESTROY:
|
||||
getUhidManager().close(msg.getId());
|
||||
break;
|
||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
openHardKeyboardSettings();
|
||||
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) {
|
||||
/*
|
||||
* #define UHID_DATA_MAX 4096
|
||||
@ -199,9 +205,15 @@ public final class UhidManager {
|
||||
}
|
||||
|
||||
public void close(int id) {
|
||||
FileDescriptor fd = fds.get(id);
|
||||
assert fd != null;
|
||||
close(fd);
|
||||
// Linux: Documentation/hid/uhid.rst
|
||||
// If you close() the fd, the device is automatically unregistered and destroyed internally.
|
||||
FileDescriptor fd = fds.remove(id);
|
||||
if (fd != null) {
|
||||
unregisterUhidListener(fd);
|
||||
close(fd);
|
||||
} else {
|
||||
Ln.w("Closing unknown UHID device: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAll() {
|
||||
|
Loading…
Reference in New Issue
Block a user