Compare commits

...

38 Commits

Author SHA1 Message Date
Romain Vimont
b587014ccf Add UHID gamepad rumble WIP 2024-09-09 08:38:45 +02:00
Romain Vimont
890ee4d893 Simplify UHID outputs routing
There was a registration mechanism to listen to HID outputs with a
specific HID id.

However, the UHID gamepad processor handles several ids, so it will not
work. We could complexify the registration mechanism, but instead,
directly dispatch to the expected processor based on the UHID id.

Concretely, instead of passing a sc_uhid_devices instance to construct a
sc_keyboard_uhid, so that it can register itself, construct the
sc_uhid_devices with all the UHID instances (currently only
sc_keyboard_uhid) so that it can dispatch HID outputs directly.
2024-09-09 08:38:45 +02:00
Romain Vimont
2e79f4b9ef Expose custom UHID device name
Initialize UHID devices with a custom name, starting with a "scrcpy: "
prefix, followed by:
 - "Keyboard" for the keyboard;
 - "Mouse" for the mouse;
 - the actual gamepad name (as reported by SDL) for gamepads.

This is especially convenient for gamepads.
2024-09-09 08:38:45 +02:00
Romain Vimont
676a83610d Reorder function parameters for consistency
Make the local function write_string() accept the output buffer as a
first parameter, like the other similar functions.
2024-09-09 08:38:45 +02:00
Romain Vimont
f151992e08 Make -K -M and -G use AOA in OTG mode
For convenience, short options were added to select UHID input modes:
 - -K for --keyboard=uhid
 - -M for --mouse=uhid
 - -G for --gamepad=uhid

In OTG mode, UHID is not available, so the short options should select
AOA instead.
2024-09-09 08:38:45 +02:00
Romain Vimont
54b6fa5f6e 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).
2024-09-09 08:38:45 +02:00
Romain Vimont
2d32cbc716 Add UHID_DESTROY control message
This message will be sent on gamepad disconnection.

Contrary to keyboard and mouse, which are registered once and are
unregistered when scrcpy exists, each gamepad is mapped with its own HID
id, and they can be plugged/unplugged dynamically.
2024-09-09 08:38:45 +02:00
Romain Vimont
29105b240b Add gamepad support in OTG mode
Implement gamepad support for OTG.
2024-09-09 08:38:45 +02:00
Romain Vimont
d14cc19c91 Add AOA gamepad support
Similar to AOA keyboard and mouse, but for gamepads.

Can be enabled with --gamepad=aoa.
2024-09-09 08:38:45 +02:00
Romain Vimont
385b31fb06 Implement HID gamepad
Implement the HID protocol for gamepads, that will be used in further
commits by the AOA and UHID gamepad processor implementations.
2024-09-09 08:38:45 +02:00
Romain Vimont
c4b6bb312c Add util functions to write in little-endian
This will be helpful for writing HID values.
2024-09-09 08:26:24 +02:00
Romain Vimont
abec04e8e7 Add connected gamepads on start
Trigger SDL_CONTROLLERDEVICEADDED for all gamepads already connected
when scrcpy starts. We want to handle both the gamepads initially
connected and the gamepads connected while scrcpy is running.

This is not racy, because this event may not trigged automatically until
SDL events are "pumped" (SDL_PumpEvents/SDL_WaitEvent).
2024-09-09 08:26:24 +02:00
Romain Vimont
0ea20f90be 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.

Co-authored-by: Luiz Henrique Laurini <luizhenriquelaurini@gmail.com>
2024-09-09 08:26:24 +02:00
Romain Vimont
a5ffe5b060 Fix HID comments
Fix typo and reference the latest version of "HID Usage Tables"
specifications.
2024-09-09 08:26:24 +02:00
Romain Vimont
6f1d79ba17 Make AOA keyboard/mouse open error fatal
Now that the AOA open/close are asynchronous, an open error did not make
scrcpy exit anymore.

Add a mechanism to exit if the AOA device could not be opened
asynchronously.
2024-09-09 08:26:24 +02:00
Romain Vimont
44e29989ee Unregister all AOA devices automatically on exit
Pushing a close event from the keyboard_aoa or mouse_aoa implementation
was racy, because the AOA thread might be stopped before these events
were processed.

Instead, keep the list of open AOA devices to close them automatically
from the AOA thread before exiting.
2024-09-09 08:26:24 +02:00
Romain Vimont
00786942be Make HID logs uniform 2024-09-09 08:26:24 +02:00
Romain Vimont
e6017cdc5d Add AOA open/close verbose logs 2024-09-09 08:26:24 +02:00
Romain Vimont
8fb87b5e6b Introduce hid_open and hid_close events
This allows to handle HID open/close at the same place as HID input
events (in the HID layer).

This will be especially useful to manage HID gamepads, to avoid
implementing one part in the HID layer and another part in the gamepad
processor implementation.
2024-09-09 08:26:24 +02:00
Romain Vimont
24f7ea5894 Rename hid_event to hid_input
The sc_hid_event structure represents HID input data. Rename it so that
we can add other hid event structs without confusion.
2024-09-09 08:26:24 +02:00
Romain Vimont
dbdfd9c8bf Make AOA open and close asynchronous
For AOA keyboard and mouse, only input events were asynchronous.
Register/unregister were called from the main thread.

This had the benefit to fail immediately if the AOA registration failed,
but to support gamepads we want to open/close AOA devices dynamically.

Also, it is better to avoid USB I/O from the main thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
d58eb616f0 Reorder AOA functions
This will allow sc_aoa_setup_hid() to compile even when
sc_aoa_unregister_hid() will be made static.
2024-09-09 08:26:24 +02:00
Romain Vimont
230e6b4079 Refactor AOA handling
Extract event processing to a separate function.

This will make the code more readable when more event types will be
added.
2024-09-09 08:26:24 +02:00
Romain Vimont
3bd07aa8ff Move HID ids to common HID code
The HID ids (accessory ids or UHID ids) were defined by the keyboard and
mouse implementations.

Instead, define them in the common HID part, and make that id part of
the sc_hid_event.

This prepares the introduction of gamepad support, which will handle
several gamepads (and ids) in the common HID gamepad code.
2024-09-09 08:26:24 +02:00
Romain Vimont
28c91ecba2 Fix HID mouse header guard 2024-09-09 08:26:24 +02:00
Romain Vimont
9ebb836b20 Add missing SC_ prefix for HID mouse event size 2024-09-09 08:26:24 +02:00
Romain Vimont
1559940cee Remove duplicate definition SC_HID_MAX_SIZE
This constant is defined in hid_event.h.
2024-09-09 08:26:24 +02:00
Romain Vimont
0cc1a855dc Fail on AOA keyboard/mouse initialization error
If the AOA keyboard or the AOA mouse fails to be initialized, this is a
fatal error.
2024-09-09 08:26:24 +02:00
Romain Vimont
b49064064c Introduce non-droppable control messages
Control messages are queued from the main thread and sent to the device
from a separate thread.

When the queue is full, messages are just dropped. This avoids to
accumulate too much delay between the client and the device in case of
network issue.

However, some messages should not be dropped: for example, dropping a
UHID_CREATE message would make invalid all further UHID_INPUT messages.
Therefore, mark these messages as non-droppable.

A non-droppable event is queued anyway (resizing the queue if
necessary, unless the allocation fails).
2024-09-09 08:26:24 +02:00
Romain Vimont
ba342c398d Remove atomics from keyboard_uhid
The UHID output callback is now called from the same (main) thread as
the process_key() function.
2024-09-09 08:26:24 +02:00
Romain Vimont
d7fe119c8e Process UHID outputs events from the main thread
This will guarantee that the callbacks of UHID devices implementations
will always be called from the same (main) thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
c5ccae5538 Set clipboard from the main thread
The clipboard changes from the device are received from a separate
thread, but it must be handled from the main thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
87d9d68c07 Add mechanism to execute code on the main thread
This allows to schedule a runnable to be executed on the main thread,
until the event loop is explicitly terminated.

It is guaranteed that all accepted runnables will be executed (this
avoids possible memory leaks if a runnable owns resources).
2024-09-09 08:26:24 +02:00
Romain Vimont
684e2b632e Expose main thread id
This will allow to assert that a function is called from the main
thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
8598e7d7a8 Extract sc_push_event()
Expose a convenience function to push an event without args to the main
thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
8b372ae809 Store events numbers in an enum
This avoids to manually set an explicit value for each item.
2024-09-09 08:26:24 +02:00
Romain Vimont
28512d3872 Fix deprecated reference in scrcpy manpage
The options --hid-keyboard and --hid-mouse do not exist anymore. They
have been replaced by --keyboard=XXX and --mouse=XXX.
2024-09-09 08:26:24 +02:00
Romain Vimont
c11f07c1e8 Do not send uninitialized HID event
If the function returns false, then there is nothing to send.
2024-09-09 08:26:24 +02:00
53 changed files with 2314 additions and 434 deletions

View File

@ -26,6 +26,8 @@ _scrcpy() {
-e --select-tcpip -e --select-tcpip
-f --fullscreen -f --fullscreen
--force-adb-forward --force-adb-forward
-G
--gamepad=
-h --help -h --help
-K -K
--keyboard= --keyboard=
@ -127,6 +129,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 uhid 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,8 +33,10 @@ 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]'
'-G[Use UHID/AOA gamepad (same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode)]'
'--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/AOA keyboard (same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode)]'
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' '--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
'--kill-adb-on-close[Kill adb when scrcpy terminates]' '--kill-adb-on-close[Kill adb when scrcpy terminates]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
@ -44,7 +46,7 @@ arguments=(
'--list-encoders[List video and audio encoders available on the device]' '--list-encoders[List video and audio encoders available on the device]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
{-m,--max-size=}'[Limit both the width and height of the video to value]' {-m,--max-size=}'[Limit both the width and height of the video to value]'
'-M[Use UHID mouse (same as --mouse=uhid)]' '-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
'--max-fps=[Limit the frame rate of screen capture]' '--max-fps=[Limit the frame rate of screen capture]'
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' '--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
'--mouse-bind=[Configure bindings of secondary clicks]' '--mouse-bind=[Configure bindings of secondary clicks]'

View File

@ -15,6 +15,7 @@ src = [
'src/demuxer.c', 'src/demuxer.c',
'src/device_msg.c', 'src/device_msg.c',
'src/display.c', 'src/display.c',
'src/events.c',
'src/icon.c', 'src/icon.c',
'src/file_pusher.c', 'src/file_pusher.c',
'src/fps_counter.c', 'src/fps_counter.c',
@ -31,10 +32,12 @@ src = [
'src/screen.c', 'src/screen.c',
'src/server.c', 'src/server.c',
'src/version.c', 'src/version.c',
'src/hid/hid_gamepad.c',
'src/hid/hid_keyboard.c', 'src/hid/hid_keyboard.c',
'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',
@ -93,6 +96,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,13 +175,28 @@ 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 \-G
Same as \fB\-\-gamepad=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
.TP
.BI "\-\-gamepad " mode
Select how to send gamepad inputs to the device.
Possible values are "disabled", "uhid" and "aoa":
- "disabled" does not send keyboard inputs to the device.
- "uhid" simulates a physical HID gamepad using the Linux HID kernel module on the device.
- "aoa" simulates a physical HID gamepad 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.
.TP .TP
.B \-K .B \-K
Same as \fB\-\-keyboard=uhid\fR. Same as \fB\-\-keyboard=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
.TP .TP
.BI "\-\-keyboard " mode .BI "\-\-keyboard " mode
@ -200,7 +215,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
@ -246,7 +261,7 @@ Default is 0 (unlimited).
.TP .TP
.B \-M .B \-M
Same as \fB\-\-mouse=uhid\fR. Same as \fB\-\-mouse=uhid\fR, or \fB\-\-mouse=aoa\fR if \fB\-\-otg\fR is set.
.TP .TP
.BI "\-\-max\-fps " value .BI "\-\-max\-fps " value
@ -267,7 +282,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 +384,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\-\-hid\-keyboard\fR and \fB\-\-hid\-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,23 @@ 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, or --gamepad=aoa if --otg is set.",
},
{
.longopt_id = OPT_GAMEPAD,
.longopt = "gamepad",
.argdesc = "mode",
.text = "Select how to send gamepad inputs to the device.\n"
"Possible values are \"disabled\", \"uhid\" and \"aoa\".\n"
"\"disabled\" does not send gamepad inputs to the device.\n"
"\"uhid\" simulates a physical HID gamepad using the Linux "
"UHID kernel module on the device.\n"
"\"aoa\" simulates a physical gamepad using the AOAv2 "
"protocol. It may only work over USB.\n"
"Also see --keyboard and --mouse.",
},
{ {
.shortopt = 'h', .shortopt = 'h',
.longopt = "help", .longopt = "help",
@ -379,7 +397,7 @@ static const struct sc_option options[] = {
}, },
{ {
.shortopt = 'K', .shortopt = 'K',
.text = "Same as --keyboard=uhid.", .text = "Same as --keyboard=uhid, or --keyboard=aoa if --otg is set.",
}, },
{ {
.longopt_id = OPT_KEYBOARD, .longopt_id = OPT_KEYBOARD,
@ -403,7 +421,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,
@ -475,7 +493,7 @@ static const struct sc_option options[] = {
}, },
{ {
.shortopt = 'M', .shortopt = 'M',
.text = "Same as --mouse=uhid.", .text = "Same as --mouse=uhid, or --mouse=aoa if --otg is set.",
}, },
{ {
.longopt_id = OPT_MAX_FPS, .longopt_id = OPT_MAX_FPS,
@ -502,7 +520,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 +655,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',
@ -2058,6 +2076,32 @@ 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, "uhid")) {
*mode = SC_GAMEPAD_INPUT_MODE_UHID;
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;
@ -2220,7 +2264,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true; args->help = true;
break; break;
case 'K': case 'K':
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID; opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA;
break; break;
case OPT_KEYBOARD: case OPT_KEYBOARD:
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) { if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
@ -2242,7 +2286,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
break; break;
case 'M': case 'M':
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID_OR_AOA;
break; break;
case OPT_MOUSE: case OPT_MOUSE:
if (!parse_mouse(optarg, &opts->mouse_input_mode)) { if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
@ -2626,6 +2670,14 @@ 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_OR_AOA;
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;
@ -2743,7 +2795,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) { if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK; : SC_KEYBOARD_INPUT_MODE_SDK;
} else if (opts->keyboard_input_mode
== SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_UHID;
} }
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) { if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
if (otg) { if (otg) {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
@ -2753,11 +2810,25 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} else { } else {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
} }
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID_OR_AOA) {
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
: SC_MOUSE_INPUT_MODE_UHID;
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK } else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
&& !opts->video_playback) { && !opts->video_playback) {
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid."); LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
return false; return false;
} }
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AUTO) {
// UHID does not work on all devices (with old Android
// versions), so it cannot be enabled by default
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
: SC_GAMEPAD_INPUT_MODE_DISABLED;
} else if (opts->gamepad_input_mode
== SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
: SC_GAMEPAD_INPUT_MODE_UHID;
}
} }
// If mouse bindings are not explicitly set, configure default bindings // If mouse bindings are not explicitly set, configure default bindings
@ -2814,9 +2885,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
enum sc_gamepad_input_mode gmode = opts->gamepad_input_mode;
if (gmode != SC_GAMEPAD_INPUT_MODE_AOA
&& gmode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
LOGE("In OTG mode, --gamepad only supports aoa or disabled.");
}
if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED
&& mmode == SC_MOUSE_INPUT_MODE_DISABLED) { && mmode == SC_MOUSE_INPUT_MODE_DISABLED
LOGE("Cannot disable both keyboard and mouse in OTG mode."); && gmode == SC_GAMEPAD_INPUT_MODE_DISABLED) {
LOGE("Cannot not disable all inputs in OTG mode.");
return false; return false;
} }
} }

View File

@ -83,15 +83,34 @@ write_position(uint8_t *buf, const struct sc_position *position) {
sc_write16be(&buf[10], position->screen_size.height); sc_write16be(&buf[10], position->screen_size.height);
} }
// write length (4 bytes) + string (non null-terminated) // Write truncated string, and return the size
static size_t static size_t
write_string(const char *utf8, size_t max_len, uint8_t *buf) { write_string_payload(uint8_t *payload, const char *utf8, size_t max_len) {
if (!utf8) {
return 0;
}
size_t len = sc_str_utf8_truncation_index(utf8, max_len); size_t len = sc_str_utf8_truncation_index(utf8, max_len);
memcpy(payload, utf8, len);
return len;
}
// Write length (4 bytes) + string (non null-terminated)
static size_t
write_string(uint8_t *buf, const char *utf8, size_t max_len) {
size_t len = write_string_payload(buf + 4, utf8, max_len);
sc_write32be(buf, len); sc_write32be(buf, len);
memcpy(&buf[4], utf8, len);
return 4 + len; return 4 + len;
} }
// Write length (1 byte) + string (non null-terminated)
static size_t
write_string_tiny(uint8_t *buf, const char *utf8, size_t max_len) {
assert(max_len <= 0xFF);
size_t len = write_string_payload(buf + 1, utf8, max_len);
buf[0] = len;
return 1 + len;
}
size_t size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
buf[0] = msg->type; buf[0] = msg->type;
@ -103,9 +122,8 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
sc_write32be(&buf[10], msg->inject_keycode.metastate); sc_write32be(&buf[10], msg->inject_keycode.metastate);
return 14; return 14;
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: { case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len = size_t len = write_string(&buf[1], msg->inject_text.text,
write_string(msg->inject_text.text, SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len; return 1 + len;
} }
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
@ -137,24 +155,34 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
sc_write64be(&buf[1], msg->set_clipboard.sequence); sc_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste; buf[9] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text, size_t len = write_string(&buf[10], msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH, SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
&buf[10]);
return 10 + len; return 10 + len;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode; buf[1] = msg->set_screen_power_mode.mode;
return 2; return 2;
case SC_CONTROL_MSG_TYPE_UHID_CREATE: case SC_CONTROL_MSG_TYPE_UHID_CREATE:
sc_write16be(&buf[1], msg->uhid_create.id); sc_write16be(&buf[1], msg->uhid_create.id);
sc_write16be(&buf[3], msg->uhid_create.report_desc_size);
memcpy(&buf[5], msg->uhid_create.report_desc, size_t index = 3;
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
index += 2;
memcpy(&buf[index], msg->uhid_create.report_desc,
msg->uhid_create.report_desc_size); msg->uhid_create.report_desc_size);
return 5 + msg->uhid_create.report_desc_size; index += msg->uhid_create.report_desc_size;
return index;
case SC_CONTROL_MSG_TYPE_UHID_INPUT: case SC_CONTROL_MSG_TYPE_UHID_INPUT:
sc_write16be(&buf[1], msg->uhid_input.id); sc_write16be(&buf[1], msg->uhid_input.id);
sc_write16be(&buf[3], msg->uhid_input.size); sc_write16be(&buf[3], msg->uhid_input.size);
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size); memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
return 5 + msg->uhid_input.size; return 5 + msg->uhid_input.size;
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
sc_write16be(&buf[1], msg->uhid_destroy.id);
return 3;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
@ -252,10 +280,15 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device"); LOG_CMSG("rotate device");
break; break;
case SC_CONTROL_MSG_TYPE_UHID_CREATE: case SC_CONTROL_MSG_TYPE_UHID_CREATE: {
LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16, // Quote only if name is not null
msg->uhid_create.id, msg->uhid_create.report_desc_size); const char *name = msg->uhid_create.name;
const char *quote = name ? "\"" : "";
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
"report_desc_size=%" PRIu16, msg->uhid_create.id,
quote, name, quote, msg->uhid_create.report_desc_size);
break; break;
}
case SC_CONTROL_MSG_TYPE_UHID_INPUT: { case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
char *hex = sc_str_to_hex_string(msg->uhid_input.data, char *hex = sc_str_to_hex_string(msg->uhid_input.data,
msg->uhid_input.size); msg->uhid_input.size);
@ -269,6 +302,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} }
break; break;
} }
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
LOG_CMSG("UHID destroy [%" PRIu16 "]", msg->uhid_destroy.id);
break;
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
LOG_CMSG("open hard keyboard settings"); LOG_CMSG("open hard keyboard settings");
break; break;
@ -278,6 +314,16 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} }
} }
bool
sc_control_msg_is_droppable(const struct sc_control_msg *msg) {
// Cannot drop UHID_CREATE messages, because it would cause all further
// UHID_INPUT messages for this device to be invalid.
// Cannot drop UHID_DESTROY messages either, because a further UHID_CREATE
// with the same id may fail.
return msg->type != SC_CONTROL_MSG_TYPE_UHID_CREATE
&& msg->type != SC_CONTROL_MSG_TYPE_UHID_DESTROY;
}
void void
sc_control_msg_destroy(struct sc_control_msg *msg) { sc_control_msg_destroy(struct sc_control_msg *msg) {
switch (msg->type) { switch (msg->type) {

View File

@ -39,6 +39,7 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT, SC_CONTROL_MSG_TYPE_UHID_INPUT,
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
}; };
@ -97,6 +98,7 @@ struct sc_control_msg {
} set_screen_power_mode; } set_screen_power_mode;
struct { struct {
uint16_t id; uint16_t id;
const char *name; // pointer to static data
uint16_t report_desc_size; uint16_t report_desc_size;
const uint8_t *report_desc; // pointer to static data const uint8_t *report_desc; // pointer to static data
} uhid_create; } uhid_create;
@ -105,6 +107,9 @@ struct sc_control_msg {
uint16_t size; uint16_t size;
uint8_t data[SC_HID_MAX_SIZE]; uint8_t data[SC_HID_MAX_SIZE];
} uhid_input; } uhid_input;
struct {
uint16_t id;
} uhid_destroy;
}; };
}; };
@ -116,6 +121,11 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf);
void void
sc_control_msg_log(const struct sc_control_msg *msg); sc_control_msg_log(const struct sc_control_msg *msg);
// Even when the buffer is "full", some messages must absolutely not be dropped
// to avoid inconsistencies.
bool
sc_control_msg_is_droppable(const struct sc_control_msg *msg);
void void
sc_control_msg_destroy(struct sc_control_msg *msg); sc_control_msg_destroy(struct sc_control_msg *msg);

View File

@ -4,7 +4,8 @@
#include "util/log.h" #include "util/log.h"
#define SC_CONTROL_MSG_QUEUE_MAX 64 // Drop droppable events above this limit
#define SC_CONTROL_MSG_QUEUE_LIMIT 60
static void static void
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error, sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
@ -22,7 +23,9 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
void *cbs_userdata) { void *cbs_userdata) {
sc_vecdeque_init(&controller->queue); sc_vecdeque_init(&controller->queue);
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX); // Add 4 to support 4 non-droppable events without re-allocation
bool ok = sc_vecdeque_reserve(&controller->queue,
SC_CONTROL_MSG_QUEUE_LIMIT + 4);
if (!ok) { if (!ok) {
return false; return false;
} }
@ -93,20 +96,31 @@ sc_controller_push_msg(struct sc_controller *controller,
sc_control_msg_log(msg); sc_control_msg_log(msg);
} }
bool pushed = false;
sc_mutex_lock(&controller->mutex); sc_mutex_lock(&controller->mutex);
bool full = sc_vecdeque_is_full(&controller->queue); size_t size = sc_vecdeque_size(&controller->queue);
if (!full) { if (size < SC_CONTROL_MSG_QUEUE_LIMIT) {
bool was_empty = sc_vecdeque_is_empty(&controller->queue); bool was_empty = sc_vecdeque_is_empty(&controller->queue);
sc_vecdeque_push_noresize(&controller->queue, *msg); sc_vecdeque_push_noresize(&controller->queue, *msg);
pushed = true;
if (was_empty) { if (was_empty) {
sc_cond_signal(&controller->msg_cond); sc_cond_signal(&controller->msg_cond);
} }
} else if (!sc_control_msg_is_droppable(msg)) {
bool ok = sc_vecdeque_push(&controller->queue, *msg);
if (ok) {
pushed = true;
} else {
// A non-droppable event must be dropped anyway
LOG_OOM();
}
} }
// Otherwise (if the queue is full), the msg is discarded // Otherwise (if the queue is full), the msg is discarded
sc_mutex_unlock(&controller->mutex); sc_mutex_unlock(&controller->mutex);
return !full; return pushed;
} }
static bool static bool

66
app/src/events.c Normal file
View File

@ -0,0 +1,66 @@
#include "events.h"
#include "util/log.h"
#include "util/thread.h"
bool
sc_push_event_impl(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
int ret = SDL_PushEvent(&event);
// ret < 0: error (queue full)
// ret == 0: event was filtered
// ret == 1: success
if (ret != 1) {
LOGE("Could not post %s event: %s", name, SDL_GetError());
return false;
}
return true;
}
bool
sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
SDL_Event event = {
.user = {
.type = SC_EVENT_RUN_ON_MAIN_THREAD,
.data1 = run,
.data2 = userdata,
},
};
int ret = SDL_PushEvent(&event);
// ret < 0: error (queue full)
// ret == 0: event was filtered
// ret == 1: success
if (ret != 1) {
if (ret == 0) {
// if ret == 0, this is expected on exit, log in debug mode
LOGD("Could not post runnable to main thread (filtered)");
} else {
assert(ret < 0);
LOGW("Coud not post to main thread: %s", SDL_GetError());
}
return false;
}
return true;
}
static int SDLCALL
task_event_filter(void *userdata, SDL_Event *event) {
(void) userdata;
if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) {
// Reject this event type from now on
return 0;
}
return 1;
}
void
sc_reject_new_runnables(void) {
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
SDL_SetEventFilter(task_event_filter, NULL);
}

View File

@ -1,10 +1,38 @@
#define SC_EVENT_NEW_FRAME SDL_USEREVENT #ifndef SC_EVENTS_H
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1) #define SC_EVENTS_H
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3) #include "common.h"
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5) #include <stdbool.h>
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6) #include <stdint.h>
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7) #include <SDL_events.h>
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9) enum {
SC_EVENT_NEW_FRAME = SDL_USEREVENT,
SC_EVENT_RUN_ON_MAIN_THREAD,
SC_EVENT_DEVICE_DISCONNECTED,
SC_EVENT_SERVER_CONNECTION_FAILED,
SC_EVENT_SERVER_CONNECTED,
SC_EVENT_USB_DEVICE_DISCONNECTED,
SC_EVENT_DEMUXER_ERROR,
SC_EVENT_RECORDER_ERROR,
SC_EVENT_SCREEN_INIT_SIZE,
SC_EVENT_TIME_LIMIT_REACHED,
SC_EVENT_CONTROLLER_ERROR,
SC_EVENT_AOA_OPEN_ERROR,
};
bool
sc_push_event_impl(uint32_t type, const char *name);
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE)
typedef void (*sc_runnable_fn)(void *userdata);
bool
sc_post_to_main_thread(sc_runnable_fn run, void *userdata);
void
sc_reject_new_runnables(void);
#endif

View File

@ -5,11 +5,23 @@
#include <stdint.h> #include <stdint.h>
#define SC_HID_MAX_SIZE 8 #define SC_HID_MAX_SIZE 15
struct sc_hid_event { struct sc_hid_input {
uint16_t hid_id;
uint8_t data[SC_HID_MAX_SIZE]; uint8_t data[SC_HID_MAX_SIZE];
uint8_t size; uint8_t size;
}; };
struct sc_hid_open {
uint16_t hid_id;
const char *name; // pointer to static memory
const uint8_t *report_desc; // pointer to static memory
size_t report_desc_size;
};
struct sc_hid_close {
uint16_t hid_id;
};
#endif #endif

458
app/src/hid/hid_gamepad.c Normal file
View File

@ -0,0 +1,458 @@
#include "hid_gamepad.h"
#include <assert.h>
#include <inttypes.h>
#include "util/binary.h"
#include "util/log.h"
// 2 bytes for left stick (X, Y)
// 2 bytes for right stick (Z, Rz)
// 2 bytes for L2/R2 triggers
// 2 bytes for buttons + padding,
// 1 byte for hat switch (dpad) + padding
#define SC_HID_GAMEPAD_EVENT_SIZE 15
// The ->buttons field stores the state for all buttons, but only some of them
// (the 16 LSB) must be transmitted "as is". The DPAD (hat switch) are stored
// locally in the MSB, but not transmitted as is: they are transformed to
// generate another specific byte.
#define SC_HID_BUTTONS_MASK 0xFFFF
// outside SC_HID_BUTTONS_MASK
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_UP UINT32_C(0x10000)
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN UINT32_C(0x20000)
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT UINT32_C(0x40000)
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT UINT32_C(0x80000)
/**
* Gamepad descriptor manually crafted to transmit the events.
*
* The HID specification is available here:
* <https://www.usb.org/document-library/device-class-definition-hid-111>
*
* The HID Usage Tables is also useful:
* <https://www.usb.org/document-library/hid-usage-tables-15>
*/
static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Gamepad)
0x09, 0x05,
// Collection (Application)
0xA1, 0x01,
// Collection (Physical)
0xA1, 0x00,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (X) Left stick x
0x09, 0x30,
// Usage (Y) Left stick y
0x09, 0x31,
// Usage (Z) Right stick x
0x09, 0x32,
// Usage (Rz) Right stick y
0x09, 0x35,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (65535)
// Cannot use 26 FF FF because 0xFFFF is interpreted as signed 16-bit
0x27, 0xFF, 0xFF, 0x00, 0x00, // little-endian
// Report Size (16)
0x75, 0x10,
// Report Count (4)
0x95, 0x04,
// Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
0x81, 0x02,
// Usage Page (Simulation Controls)
0x05, 0x02,
// Usage (Brake)
0x09, 0xC5,
// Usage (Accelerator)
0x09, 0xC4,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (32767)
0x26, 0xFF, 0x7F,
// Report Size (16)
0x75, 0x10,
// Report Count (2)
0x95, 0x02,
// Input (Data, Variable, Absolute): 2 bytes (L2, R2)
0x81, 0x02,
// Usage Page (Buttons)
0x05, 0x09,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (16)
0x29, 0x10,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Count (16)
0x95, 0x10,
// Report Size (1)
0x75, 0x01,
// Input (Data, Variable, Absolute): 16 buttons bits
0x81, 0x02,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Hat switch)
0x09, 0x39,
// Logical Minimum (1)
0x15, 0x01,
// Logical Maximum (8)
0x25, 0x08,
// Report Size (4)
0x75, 0x04,
// Report Count (1)
0x95, 0x01,
// Input (Data, Variable, Null State): 4-bit value
0x81, 0x42,
// End Collection
0xC0,
// End Collection
0xC0,
};
/**
* A gamepad HID event is 15 bytes long:
* - bytes 0-3: left stick state
* - bytes 4-7: right stick state
* - bytes 8-11: L2/R2 triggers state
* - bytes 12-13: buttons state
* - bytes 14: hat switch position (dpad)
*
* +---------------+
* byte 0: |. . . . . . . .|
* | | left stick x state (0-65535)
* byte 1: |. . . . . . . .|
* +---------------+
* byte 2: |. . . . . . . .|
* | | left stick y state (0-65535)
* byte 3: |. . . . . . . .|
* +---------------+
* byte 4: |. . . . . . . .|
* | | right stick x state (0-65535)
* byte 5: |. . . . . . . .|
* +---------------+
* byte 6: |. . . . . . . .|
* | | right stick y state (0-65535)
* byte 7: |. . . . . . . .|
* +---------------+
* byte 8: |. . . . . . . .|
* | | L2 trigger state (0-65535)
* byte 9: |. . . . . . . .|
* +---------------+
* byte 10: |. . . . . . . .|
* | | R2 trigger state (0-65535)
* byte 11: |. . . . . . . .|
* +---------------+
*
* ,--------------- SC_GAMEPAD_BUTTON_RIGHT_SHOULDER
* | ,------------- SC_GAMEPAD_BUTTON_LEFT_SHOULDER
* | |
* | | ,--------- SC_GAMEPAD_BUTTON_NORTH
* | | | ,------- SC_GAMEPAD_BUTTON_WEST
* | | | |
* | | | | ,--- SC_GAMEPAD_BUTTON_EAST
* | | | | | ,- SC_GAMEPAD_BUTTON_SOUTH
* v v v v v v
* +---------------+
* byte 12: |. . 0 . . 0 . .|
* | | Buttons (16-bit little-endian)
* byte 13: |0 . . . . . 0 0|
* +---------------+
* ^ ^ ^ ^ ^
* | | | | |
* | | | | |
* | | | | `----- SC_GAMEPAD_BUTTON_BACK
* | | | `------- SC_GAMEPAD_BUTTON_START
* | | `--------- SC_GAMEPAD_BUTTON_GUIDE
* | `----------- SC_GAMEPAD_BUTTON_LEFT_STICK
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
*
* +---------------+
* byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
* +---------------+
* 9 possible positions and their values:
* 8 1 2
* 7 0 3
* 6 5 4
* (8 is top-left, 1 is top, 2 is top-right, etc.)
*/
static void
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
uint32_t gamepad_id) {
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
slot->gamepad_id = gamepad_id;
slot->buttons = 0;
slot->axis_left_x = 0;
slot->axis_left_y = 0;
slot->axis_right_x = 0;
slot->axis_right_y = 0;
slot->axis_left_trigger = 0;
slot->axis_right_trigger = 0;
}
static ssize_t
sc_hid_gamepad_slot_find(struct sc_hid_gamepad *hid, uint32_t gamepad_id) {
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
if (gamepad_id == hid->slots[i].gamepad_id) {
// found
return i;
}
}
return -1;
}
void
sc_hid_gamepad_init(struct sc_hid_gamepad *hid) {
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
hid->slots[i].gamepad_id = SC_GAMEPAD_ID_INVALID;
}
}
static inline uint16_t
sc_hid_gamepad_slot_get_id(size_t slot_idx) {
assert(slot_idx < SC_MAX_GAMEPADS);
return SC_HID_ID_GAMEPAD_FIRST + slot_idx;
}
bool
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
struct sc_hid_open *hid_open,
uint32_t gamepad_id) {
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, SC_GAMEPAD_ID_INVALID);
if (slot_idx == -1) {
LOGW("No gamepad slot available for new gamepad %" PRIu32, gamepad_id);
return false;
}
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(gamepad_id);
assert(game_controller);
const char *name = SDL_GameControllerName(game_controller);
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
hid_open->hid_id = hid_id;
hid_open->name = name;
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
return true;
}
bool
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
struct sc_hid_close *hid_close,
uint32_t gamepad_id) {
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
if (slot_idx == -1) {
LOGW("Unknown gamepad removed %" PRIu32, gamepad_id);
return false;
}
hid->slots[slot_idx].gamepad_id = SC_GAMEPAD_ID_INVALID;
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
hid_close->hid_id = hid_id;
return true;
}
static uint8_t
sc_hid_gamepad_get_dpad_value(uint32_t buttons) {
// Value depending on direction:
// 8 1 2
// 7 0 3
// 6 5 4
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_UP) {
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
return 8;
}
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
return 2;
}
return 1;
}
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN) {
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
return 6;
}
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
return 4;
}
return 5;
}
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
return 7;
}
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
return 3;
}
return 0;
}
static void
sc_hid_gamepad_event_from_slot(uint16_t hid_id,
const struct sc_hid_gamepad_slot *slot,
struct sc_hid_input *hid_input) {
hid_input->hid_id = hid_id;
hid_input->size = SC_HID_GAMEPAD_EVENT_SIZE;
uint8_t *data = hid_input->data;
// Values must be written in little-endian
sc_write16le(data, slot->axis_left_x);
sc_write16le(data + 2, slot->axis_left_y);
sc_write16le(data + 4, slot->axis_right_x);
sc_write16le(data + 6, slot->axis_right_y);
sc_write16le(data + 8, slot->axis_left_trigger);
sc_write16le(data + 10, slot->axis_right_trigger);
sc_write16le(data + 12, slot->buttons & SC_HID_BUTTONS_MASK);
data[14] = sc_hid_gamepad_get_dpad_value(slot->buttons);
}
static uint32_t
sc_hid_gamepad_get_button_id(enum sc_gamepad_button button) {
switch (button) {
case SC_GAMEPAD_BUTTON_SOUTH:
return 0x0001;
case SC_GAMEPAD_BUTTON_EAST:
return 0x0002;
case SC_GAMEPAD_BUTTON_WEST:
return 0x0008;
case SC_GAMEPAD_BUTTON_NORTH:
return 0x0010;
case SC_GAMEPAD_BUTTON_BACK:
return 0x0400;
case SC_GAMEPAD_BUTTON_GUIDE:
return 0x1000;
case SC_GAMEPAD_BUTTON_START:
return 0x0800;
case SC_GAMEPAD_BUTTON_LEFT_STICK:
return 0x2000;
case SC_GAMEPAD_BUTTON_RIGHT_STICK:
return 0x4000;
case SC_GAMEPAD_BUTTON_LEFT_SHOULDER:
return 0x0040;
case SC_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return 0x0080;
case SC_GAMEPAD_BUTTON_DPAD_UP:
return SC_GAMEPAD_BUTTONS_BIT_DPAD_UP;
case SC_GAMEPAD_BUTTON_DPAD_DOWN:
return SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN;
case SC_GAMEPAD_BUTTON_DPAD_LEFT:
return SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT;
case SC_GAMEPAD_BUTTON_DPAD_RIGHT:
return SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT;
default:
// unknown button, ignore
return 0;
}
}
bool
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
struct sc_hid_input *hid_input,
const struct sc_gamepad_button_event *event) {
if ((event->button < 0) || (event->button > 15)) {
return false;
}
uint32_t gamepad_id = event->gamepad_id;
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
if (slot_idx == -1) {
LOGW("Axis event for unknown gamepad %" PRIu32, gamepad_id);
return false;
}
assert(slot_idx < SC_MAX_GAMEPADS);
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
uint32_t button = sc_hid_gamepad_get_button_id(event->button);
if (!button) {
// unknown button, ignore
return false;
}
if (event->action == SC_ACTION_DOWN) {
slot->buttons |= button;
} else {
assert(event->action == SC_ACTION_UP);
slot->buttons &= ~button;
}
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
return true;
}
bool
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
struct sc_hid_input *hid_input,
const struct sc_gamepad_axis_event *event) {
uint32_t gamepad_id = event->gamepad_id;
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
if (slot_idx == -1) {
LOGW("Button event for unknown gamepad %" PRIu32, gamepad_id);
return false;
}
assert(slot_idx < SC_MAX_GAMEPADS);
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
// [-32768 to 32767] -> [0 to 65535]
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
switch (event->axis) {
case SC_GAMEPAD_AXIS_LEFTX:
slot->axis_left_x = AXIS_RESCALE(event->value);
break;
case SC_GAMEPAD_AXIS_LEFTY:
slot->axis_left_y = AXIS_RESCALE(event->value);
break;
case SC_GAMEPAD_AXIS_RIGHTX:
slot->axis_right_x = AXIS_RESCALE(event->value);
break;
case SC_GAMEPAD_AXIS_RIGHTY:
slot->axis_right_y = AXIS_RESCALE(event->value);
break;
case SC_GAMEPAD_AXIS_LEFT_TRIGGER:
// Trigger is always positive between 0 and 32767
slot->axis_left_trigger = MAX(0, event->value);
break;
case SC_GAMEPAD_AXIS_RIGHT_TRIGGER:
// Trigger is always positive between 0 and 32767
slot->axis_right_trigger = MAX(0, event->value);
break;
default:
return false;
}
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
return true;
}

53
app/src/hid/hid_gamepad.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef SC_HID_GAMEPAD_H
#define SC_HID_GAMEPAD_H
#include "common.h"
#include <stdbool.h>
#include "hid/hid_event.h"
#include "input_events.h"
#define SC_MAX_GAMEPADS 8
#define SC_HID_ID_GAMEPAD_FIRST 3
#define SC_HID_ID_GAMEPAD_LAST (SC_HID_ID_GAMEPAD_FIRST + SC_MAX_GAMEPADS - 1)
struct sc_hid_gamepad_slot {
uint32_t gamepad_id;
uint32_t buttons;
uint16_t axis_left_x;
uint16_t axis_left_y;
uint16_t axis_right_x;
uint16_t axis_right_y;
uint16_t axis_left_trigger;
uint16_t axis_right_trigger;
};
struct sc_hid_gamepad {
struct sc_hid_gamepad_slot slots[SC_MAX_GAMEPADS];
};
void
sc_hid_gamepad_init(struct sc_hid_gamepad *hid);
bool
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
struct sc_hid_open *hid_open,
uint32_t gamepad_id);
bool
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
struct sc_hid_close *hid_close,
uint32_t gamepad_id);
bool
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
struct sc_hid_input *hid_input,
const struct sc_gamepad_button_event *event);
bool
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
struct sc_hid_input *hid_input,
const struct sc_gamepad_axis_event *event);
#endif

View File

@ -21,7 +21,7 @@
// keyboard support, though OS could support more keys via modifying the report // keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy. // desc. 6 should be enough for scrcpy.
#define SC_HID_KEYBOARD_MAX_KEYS 6 #define SC_HID_KEYBOARD_MAX_KEYS 6
#define SC_HID_KEYBOARD_EVENT_SIZE \ #define SC_HID_KEYBOARD_INPUT_SIZE \
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS) (SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
#define SC_HID_RESERVED 0x00 #define SC_HID_RESERVED 0x00
@ -31,13 +31,16 @@
* For HID, only report descriptor is needed. * For HID, only report descriptor is needed.
* *
* The specification is available here: * The specification is available here:
* <https://www.usb.org/sites/default/files/hid1_11.pdf> * <https://www.usb.org/document-library/device-class-definition-hid-111>
* *
* In particular, read: * In particular, read:
* - 6.2.2 Report Descriptor * - §6.2.2 Report Descriptor
* - Appendix B.1 Protocol 1 (Keyboard) * - Appendix B.1 Protocol 1 (Keyboard)
* - Appendix C: Keyboard Implementation * - Appendix C: Keyboard Implementation
* *
* The HID Usage Tables is also useful:
* <https://www.usb.org/document-library/hid-usage-tables-15>
*
* Normally a basic HID keyboard uses 8 bytes: * Normally a basic HID keyboard uses 8 bytes:
* Modifier Reserved Key Key Key Key Key Key * Modifier Reserved Key Key Key Key Key Key
* *
@ -47,7 +50,7 @@
* *
* (change vid:pid' to your device's vendor ID and product ID). * (change vid:pid' to your device's vendor ID and product ID).
*/ */
const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = { static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
// Usage Page (Generic Desktop) // Usage Page (Generic Desktop)
0x05, 0x01, 0x05, 0x01,
// Usage (Keyboard) // Usage (Keyboard)
@ -121,9 +124,6 @@ const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
0xC0 0xC0
}; };
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
/** /**
* A keyboard HID event is 8 bytes long: * A keyboard HID event is 8 bytes long:
* *
@ -199,10 +199,11 @@ const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
*/ */
static void static void
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { sc_hid_keyboard_input_init(struct sc_hid_input *hid_input) {
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE; hid_input->hid_id = SC_HID_ID_KEYBOARD;
hid_input->size = SC_HID_KEYBOARD_INPUT_SIZE;
uint8_t *data = hid_event->data; uint8_t *data = hid_input->data;
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE; data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
data[1] = SC_HID_RESERVED; data[1] = SC_HID_RESERVED;
@ -250,8 +251,8 @@ scancode_is_modifier(enum sc_scancode scancode) {
} }
bool bool
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid, sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event, struct sc_hid_input *hid_input,
const struct sc_key_event *event) { const struct sc_key_event *event) {
enum sc_scancode scancode = event->scancode; enum sc_scancode scancode = event->scancode;
assert(scancode >= 0); assert(scancode >= 0);
@ -264,7 +265,7 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
return false; return false;
} }
sc_hid_keyboard_event_init(hid_event); sc_hid_keyboard_input_init(hid_input);
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state); uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
@ -275,9 +276,9 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
hid->keys[scancode] ? "true" : "false"); hid->keys[scancode] ? "true" : "false");
} }
hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods; hid_input->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
uint8_t *keys_data = &hid_event->data[SC_HID_KEYBOARD_INDEX_KEYS]; uint8_t *keys_data = &hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time // Re-calculate pressed keys every time
int keys_pressed_count = 0; int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) { for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
@ -308,7 +309,7 @@ end:
} }
bool bool
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event, sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
uint16_t mods_state) { uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS; bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM; bool numlock = mods_state & SC_MOD_NUM;
@ -317,17 +318,28 @@ sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
return false; return false;
} }
sc_hid_keyboard_event_init(event); sc_hid_keyboard_input_init(hid_input);
unsigned i = 0; unsigned i = 0;
if (capslock) { if (capslock) {
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK; hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i; ++i;
} }
if (numlock) { if (numlock) {
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK; hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i; ++i;
} }
return true; return true;
} }
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_KEYBOARD;
hid_open->name = "Keyboard";
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
}
void sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close) {
hid_close->hid_id = SC_HID_ID_KEYBOARD;
}

View File

@ -14,8 +14,7 @@
// 0x65 is Application, typically AT-101 Keyboard ends here. // 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66 #define SC_HID_KEYBOARD_KEYS 0x66
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[]; #define SC_HID_ID_KEYBOARD 1
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
/** /**
* HID keyboard events are sequence-based, every time keyboard state changes * HID keyboard events are sequence-based, every time keyboard state changes
@ -36,13 +35,19 @@ struct sc_hid_keyboard {
void void
sc_hid_keyboard_init(struct sc_hid_keyboard *hid); sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
void
sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open);
void
sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close);
bool bool
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid, sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event, struct sc_hid_input *hid_input,
const struct sc_key_event *event); const struct sc_key_event *event);
bool bool
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event, sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
uint16_t mods_state); uint16_t mods_state);
#endif #endif

View File

@ -2,17 +2,17 @@
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position, // 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
// 1 byte for wheel motion // 1 byte for wheel motion
#define HID_MOUSE_EVENT_SIZE 4 #define SC_HID_MOUSE_INPUT_SIZE 4
/** /**
* Mouse descriptor from the specification: * Mouse descriptor from the specification:
* <https://www.usb.org/sites/default/files/hid1_11.pdf> * <https://www.usb.org/document-library/device-class-definition-hid-111>
* *
* Appendix E (p71): §E.10 Report Descriptor (Mouse) * Appendix E (p71): §E.10 Report Descriptor (Mouse)
* *
* The usage tags (like Wheel) are listed in "HID Usage Tables": * The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf> * <https://www.usb.org/document-library/hid-usage-tables-15>
* §4 Generic Desktop Page (0x01) (p26) * §4 Generic Desktop Page (0x01) (p32)
*/ */
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = { const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
// Usage Page (Generic Desktop) // Usage Page (Generic Desktop)
@ -62,9 +62,9 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
0x09, 0x31, 0x09, 0x31,
// Usage (Wheel) // Usage (Wheel)
0x09, 0x38, 0x09, 0x38,
// Local Minimum (-127) // Logical Minimum (-127)
0x15, 0x81, 0x15, 0x81,
// Local Maximum (127) // Logical Maximum (127)
0x25, 0x7F, 0x25, 0x7F,
// Report Size (8) // Report Size (8)
0x75, 0x08, 0x75, 0x08,
@ -80,9 +80,6 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
0xC0, 0xC0,
}; };
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
sizeof(SC_HID_MOUSE_REPORT_DESC);
/** /**
* A mouse HID event is 4 bytes long: * A mouse HID event is 4 bytes long:
* *
@ -125,10 +122,10 @@ const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
*/ */
static void static void
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) { sc_hid_mouse_input_init(struct sc_hid_input *hid_input) {
hid_event->size = HID_MOUSE_EVENT_SIZE; hid_input->hid_id = SC_HID_ID_MOUSE;
// Leave hid_event->data uninitialized, it will be fully initialized by hid_input->size = SC_HID_MOUSE_INPUT_SIZE;
// callers // Leave ->data uninitialized, it will be fully initialized by callers
} }
static uint8_t static uint8_t
@ -153,11 +150,11 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
} }
void void
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event, sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
const struct sc_mouse_motion_event *event) { const struct sc_mouse_motion_event *event) {
sc_hid_mouse_event_init(hid_event); sc_hid_mouse_input_init(hid_input);
uint8_t *data = hid_event->data; uint8_t *data = hid_input->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state); data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = CLAMP(event->xrel, -127, 127); data[1] = CLAMP(event->xrel, -127, 127);
data[2] = CLAMP(event->yrel, -127, 127); data[2] = CLAMP(event->yrel, -127, 127);
@ -165,11 +162,11 @@ sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
} }
void void
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event, sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
const struct sc_mouse_click_event *event) { const struct sc_mouse_click_event *event) {
sc_hid_mouse_event_init(hid_event); sc_hid_mouse_input_init(hid_input);
uint8_t *data = hid_event->data; uint8_t *data = hid_input->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state); data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = 0; // no x motion data[1] = 0; // no x motion
data[2] = 0; // no y motion data[2] = 0; // no y motion
@ -177,11 +174,11 @@ sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
} }
void void
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event, sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
const struct sc_mouse_scroll_event *event) { const struct sc_mouse_scroll_event *event) {
sc_hid_mouse_event_init(hid_event); sc_hid_mouse_input_init(hid_input);
uint8_t *data = hid_event->data; uint8_t *data = hid_input->data;
data[0] = 0; // buttons state irrelevant (and unknown) data[0] = 0; // buttons state irrelevant (and unknown)
data[1] = 0; // no x motion data[1] = 0; // no x motion
data[2] = 0; // no y motion data[2] = 0; // no y motion
@ -190,3 +187,14 @@ sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
data[3] = CLAMP(event->vscroll, -127, 127); data[3] = CLAMP(event->vscroll, -127, 127);
// Horizontal scrolling ignored // Horizontal scrolling ignored
} }
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_MOUSE;
hid_open->name = "Mouse";
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
}
void sc_hid_mouse_generate_close(struct sc_hid_close *hid_close) {
hid_close->hid_id = SC_HID_ID_MOUSE;
}

View File

@ -1,8 +1,6 @@
#ifndef SC_HID_MOUSE_H #ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H #define SC_HID_MOUSE_H
#endif
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
@ -10,17 +8,24 @@
#include "hid/hid_event.h" #include "hid/hid_event.h"
#include "input_events.h" #include "input_events.h"
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[]; #define SC_HID_ID_MOUSE 2
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
void void
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event, sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
void
sc_hid_mouse_generate_close(struct sc_hid_close *hid_close);
void
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
const struct sc_mouse_motion_event *event); const struct sc_mouse_motion_event *event);
void void
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event, sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
const struct sc_mouse_click_event *event); const struct sc_mouse_click_event *event);
void void
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event, sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
const struct sc_mouse_scroll_event *event); const struct sc_mouse_scroll_event *event);
#endif

View File

@ -323,6 +323,38 @@ enum sc_mouse_button {
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2), SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
}; };
// Use the naming from SDL3 for gamepad axis and buttons:
// <https://wiki.libsdl.org/SDL3/README/migration>
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), static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
"SDL_Keymod must be convertible to sc_mod"); "SDL_Keymod must be convertible to sc_mod");
@ -380,6 +412,33 @@ struct sc_touch_event {
float pressure; float pressure;
}; };
enum sc_gamepad_device_event_type {
SC_GAMEPAD_DEVICE_ADDED,
SC_GAMEPAD_DEVICE_REMOVED,
};
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
// 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 static inline uint16_t
sc_mods_state_from_sdl(uint16_t mods_state) { sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state; return mods_state;
@ -444,4 +503,43 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
return 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) {
// SDL_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 #endif

View File

@ -56,16 +56,18 @@ void
sc_input_manager_init(struct sc_input_manager *im, sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) { const struct sc_input_manager_params *params) {
// A key/mouse processor may not be present if there is no controller // 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 // A processor must have ops initialized
assert(!params->kp || params->kp->ops); assert(!params->kp || params->kp->ops);
assert(!params->mp || params->mp->ops); assert(!params->mp || params->mp->ops);
assert(!params->gp || params->gp->ops);
im->controller = params->controller; im->controller = params->controller;
im->fp = params->fp; im->fp = params->fp;
im->screen = params->screen; im->screen = params->screen;
im->kp = params->kp; im->kp = params->kp;
im->mp = params->mp; im->mp = params->mp;
im->gp = params->gp;
im->mouse_bindings = params->mouse_bindings; im->mouse_bindings = params->mouse_bindings;
im->legacy_paste = params->legacy_paste; im->legacy_paste = params->legacy_paste;
@ -906,6 +908,68 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
im->mp->ops->process_mouse_scroll(im->mp, &evt); 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) {
struct sc_gamepad_axis_event evt = {
.gamepad_id = event->which,
.axis = sc_gamepad_axis_from_sdl(event->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) {
struct sc_gamepad_button_event evt = {
.gamepad_id = event->which,
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = sc_gamepad_button_from_sdl(event->button),
};
im->gp->ops->process_gamepad_button(im->gp, &evt);
}
static bool static bool
is_apk(const char *file) { is_apk(const char *file) {
const char *ext = strrchr(file, '.'); const char *ext = strrchr(file, '.');
@ -978,6 +1042,27 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
} }
sc_input_manager_process_touch(im, &event->tfinger); sc_input_manager_process_touch(im, &event->tfinger);
break; 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: { case SDL_DROPFILE: {
if (!control) { if (!control) {
break; break;

View File

@ -11,6 +11,7 @@
#include "file_pusher.h" #include "file_pusher.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "options.h" #include "options.h"
#include "trait/gamepad_processor.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
#include "trait/mouse_processor.h" #include "trait/mouse_processor.h"
@ -21,6 +22,7 @@ struct sc_input_manager {
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
struct sc_gamepad_processor *gp;
struct sc_mouse_bindings mouse_bindings; struct sc_mouse_bindings mouse_bindings;
bool legacy_paste; bool legacy_paste;
@ -50,6 +52,7 @@ struct sc_input_manager_params {
struct sc_screen *screen; struct sc_screen *screen;
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
struct sc_gamepad_processor *gp;
struct sc_mouse_bindings mouse_bindings; struct sc_mouse_bindings mouse_bindings;
bool legacy_paste; bool legacy_paste;

View File

@ -16,6 +16,7 @@
#include "usb/scrcpy_otg.h" #include "usb/scrcpy_otg.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
#include "version.h" #include "version.h"
#ifdef _WIN32 #ifdef _WIN32
@ -67,6 +68,9 @@ main_scrcpy(int argc, char *argv[]) {
goto end; goto end;
} }
// The current thread is the main thread
SC_MAIN_THREAD_ID = sc_thread_get_id();
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all(); av_register_all();
#endif #endif

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_AUTO,
.mouse_bindings = { .mouse_bindings = {
.pri = { .pri = {
.right_click = SC_MOUSE_BINDING_AUTO, .right_click = SC_MOUSE_BINDING_AUTO,

View File

@ -142,6 +142,7 @@ enum sc_lock_video_orientation {
enum sc_keyboard_input_mode { enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_AUTO, SC_KEYBOARD_INPUT_MODE_AUTO,
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
SC_KEYBOARD_INPUT_MODE_DISABLED, SC_KEYBOARD_INPUT_MODE_DISABLED,
SC_KEYBOARD_INPUT_MODE_SDK, SC_KEYBOARD_INPUT_MODE_SDK,
SC_KEYBOARD_INPUT_MODE_UHID, SC_KEYBOARD_INPUT_MODE_UHID,
@ -150,12 +151,21 @@ enum sc_keyboard_input_mode {
enum sc_mouse_input_mode { enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_AUTO, SC_MOUSE_INPUT_MODE_AUTO,
SC_MOUSE_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
SC_MOUSE_INPUT_MODE_DISABLED, SC_MOUSE_INPUT_MODE_DISABLED,
SC_MOUSE_INPUT_MODE_SDK, SC_MOUSE_INPUT_MODE_SDK,
SC_MOUSE_INPUT_MODE_UHID, SC_MOUSE_INPUT_MODE_UHID,
SC_MOUSE_INPUT_MODE_AOA, SC_MOUSE_INPUT_MODE_AOA,
}; };
enum sc_gamepad_input_mode {
SC_GAMEPAD_INPUT_MODE_AUTO,
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
SC_GAMEPAD_INPUT_MODE_DISABLED,
SC_GAMEPAD_INPUT_MODE_UHID,
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 +241,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

@ -6,9 +6,17 @@
#include <SDL2/SDL_clipboard.h> #include <SDL2/SDL_clipboard.h>
#include "device_msg.h" #include "device_msg.h"
#include "events.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str.h"
struct sc_uhid_output_task_data {
struct sc_uhid_devices *uhid_devices;
uint16_t id;
uint16_t size;
uint8_t *data;
};
bool bool
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) { const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
@ -34,19 +42,48 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
} }
static void static void
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) { task_set_clipboard(void *userdata) {
switch (msg->type) { char *text = userdata;
case DEVICE_MSG_TYPE_CLIPBOARD: {
char *current = SDL_GetClipboardText(); char *current = SDL_GetClipboardText();
bool same = current && !strcmp(current, msg->clipboard.text); bool same = current && !strcmp(current, text);
SDL_free(current); SDL_free(current);
if (same) { if (same) {
LOGD("Computer clipboard unchanged"); LOGD("Computer clipboard unchanged");
free(text);
return; return;
} }
LOGI("Device clipboard copied"); LOGI("Device clipboard copied");
SDL_SetClipboardText(msg->clipboard.text); SDL_SetClipboardText(text);
free(text);
}
static void
task_uhid_output(void *userdata) {
struct sc_uhid_output_task_data *data = userdata;
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
data->size);
free(data->data);
free(data);
}
static void
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
// Take ownership of the text (do not destroy the msg)
char *text = msg->clipboard.text;
bool ok = sc_post_to_main_thread(task_set_clipboard, text);
if (!ok) {
LOGW("Could not post clipboard to main thread");
free(text);
return;
}
break; break;
} }
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
@ -64,6 +101,7 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
} }
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence); sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
// No allocation to free in the msg
break; break;
case DEVICE_MSG_TYPE_UHID_OUTPUT: case DEVICE_MSG_TYPE_UHID_OUTPUT:
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
@ -86,19 +124,33 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
// Also check at runtime (do not trust the server) // Also check at runtime (do not trust the server)
if (!receiver->uhid_devices) { if (!receiver->uhid_devices) {
LOGE("Received unexpected HID output message"); LOGE("Received unexpected HID output message");
sc_device_msg_destroy(msg);
return; return;
} }
struct sc_uhid_receiver *uhid_receiver = struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
sc_uhid_devices_get_receiver(receiver->uhid_devices, if (!data) {
msg->uhid_output.id); LOG_OOM();
if (uhid_receiver) { return;
uhid_receiver->ops->process_output(uhid_receiver,
msg->uhid_output.data,
msg->uhid_output.size);
} else {
LOGW("No UHID receiver for id %" PRIu16, msg->uhid_output.id);
} }
// It is guaranteed that these pointers will still be valid when
// the main thread will process them (the main thread will stop
// processing SC_EVENT_RUN_ON_MAIN_THREAD on exit, when everything
// gets deinitialized)
data->uhid_devices = receiver->uhid_devices;
data->id = msg->uhid_output.id;
data->data = msg->uhid_output.data; // take ownership
data->size = msg->uhid_output.size;
bool ok = sc_post_to_main_thread(task_uhid_output, data);
if (!ok) {
LOGW("Could not post UHID output to main thread");
free(data->data);
free(data);
return;
}
break; break;
} }
} }
@ -117,7 +169,7 @@ process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
} }
process_msg(receiver, &msg); process_msg(receiver, &msg);
sc_device_msg_destroy(&msg); // the device msg must be destroyed by process_msg()
head += r; head += r;
assert(head <= len); assert(head <= len);

View File

@ -25,10 +25,12 @@
#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
# 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"
@ -77,27 +79,21 @@ struct scrcpy {
struct sc_mouse_uhid mouse_uhid; struct sc_mouse_uhid mouse_uhid;
#ifdef HAVE_USB #ifdef HAVE_USB
struct sc_mouse_aoa mouse_aoa; struct sc_mouse_aoa mouse_aoa;
#endif
};
union {
struct sc_gamepad_uhid gamepad_uhid;
#ifdef HAVE_USB
struct sc_gamepad_aoa gamepad_aoa;
#endif #endif
}; };
struct sc_timeout timeout; struct sc_timeout timeout;
}; };
static inline void
push_event(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post %s event: %s", name, SDL_GetError());
// What could we do?
}
}
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
#ifdef _WIN32 #ifdef _WIN32
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) { if (ctrl_type == CTRL_C_EVENT) {
PUSH_EVENT(SDL_QUIT); sc_push_event(SDL_QUIT);
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -180,12 +176,20 @@ event_loop(struct scrcpy *s) {
case SC_EVENT_RECORDER_ERROR: case SC_EVENT_RECORDER_ERROR:
LOGE("Recorder error"); LOGE("Recorder error");
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
case SC_EVENT_AOA_OPEN_ERROR:
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_TIME_LIMIT_REACHED: case SC_EVENT_TIME_LIMIT_REACHED:
LOGI("Time limit reached"); LOGI("Time limit reached");
return SCRCPY_EXIT_SUCCESS; return SCRCPY_EXIT_SUCCESS;
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS; return SCRCPY_EXIT_SUCCESS;
case SC_EVENT_RUN_ON_MAIN_THREAD:
sc_runnable_fn run = event.user.data1;
void *userdata = event.user.data2;
run(userdata);
break;
default: default:
if (!sc_screen_handle_event(&s->screen, &event)) { if (!sc_screen_handle_event(&s->screen, &event)) {
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
@ -196,6 +200,21 @@ event_loop(struct scrcpy *s) {
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
} }
static void
terminate_event_loop(void) {
sc_reject_new_runnables();
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SC_EVENT_RUN_ON_MAIN_THREAD) {
// Make sure all posted runnables are run, to avoid memory leaks
sc_runnable_fn run = event.user.data1;
void *userdata = event.user.data2;
run(userdata);
}
}
}
// Return true on success, false on error // Return true on success, false on error
static bool static bool
await_for_server(bool *connected) { await_for_server(bool *connected) {
@ -230,7 +249,7 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
(void) userdata; (void) userdata;
if (!success) { if (!success) {
PUSH_EVENT(SC_EVENT_RECORDER_ERROR); sc_push_event(SC_EVENT_RECORDER_ERROR);
} }
} }
@ -244,9 +263,9 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer,
assert(status != SC_DEMUXER_STATUS_DISABLED); assert(status != SC_DEMUXER_STATUS_DISABLED);
if (status == SC_DEMUXER_STATUS_EOS) { if (status == SC_DEMUXER_STATUS_EOS) {
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED); sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
} else { } else {
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); sc_push_event(SC_EVENT_DEMUXER_ERROR);
} }
} }
@ -260,11 +279,11 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
// Contrary to the video demuxer, keep mirroring if only the audio fails // Contrary to the video demuxer, keep mirroring if only the audio fails
// (unless --require-audio is set). // (unless --require-audio is set).
if (status == SC_DEMUXER_STATUS_EOS) { if (status == SC_DEMUXER_STATUS_EOS) {
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED); sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
} else if (status == SC_DEMUXER_STATUS_ERROR } else if (status == SC_DEMUXER_STATUS_ERROR
|| (status == SC_DEMUXER_STATUS_DISABLED || (status == SC_DEMUXER_STATUS_DISABLED
&& options->require_audio)) { && options->require_audio)) {
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); sc_push_event(SC_EVENT_DEMUXER_ERROR);
} }
} }
@ -277,9 +296,9 @@ sc_controller_on_ended(struct sc_controller *controller, bool error,
(void) userdata; (void) userdata;
if (error) { if (error) {
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR); sc_push_event(SC_EVENT_CONTROLLER_ERROR);
} else { } else {
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED); sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
} }
} }
@ -288,7 +307,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
(void) server; (void) server;
(void) userdata; (void) userdata;
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED); sc_push_event(SC_EVENT_SERVER_CONNECTION_FAILED);
} }
static void static void
@ -296,7 +315,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
(void) server; (void) server;
(void) userdata; (void) userdata;
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED); sc_push_event(SC_EVENT_SERVER_CONNECTED);
} }
static void static void
@ -314,7 +333,7 @@ sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) {
(void) timeout; (void) timeout;
(void) userdata; (void) userdata;
PUSH_EVENT(SC_EVENT_TIME_LIMIT_REACHED); sc_push_event(SC_EVENT_TIME_LIMIT_REACHED);
} }
// Generate a scrcpy id to differentiate multiple running scrcpy instances // Generate a scrcpy id to differentiate multiple running scrcpy instances
@ -326,6 +345,21 @@ scrcpy_generate_scid(void) {
return sc_rand_u32(&rand) & 0x7FFFFFFF; return sc_rand_u32(&rand) & 0x7FFFFFFF;
} }
static void
init_sdl_gamepads(void) {
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
// connected
int num_joysticks = SDL_NumJoysticks();
for (int i = 0; i < num_joysticks; ++i) {
if (SDL_IsGameController(i)) {
SDL_Event event;
event.cdevice.type = SDL_CONTROLLERDEVICEADDED;
event.cdevice.which = i;
SDL_PushEvent(&event);
}
}
}
enum scrcpy_exit_code enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
@ -358,6 +392,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;
@ -366,7 +401,6 @@ scrcpy(struct scrcpy_options *options) {
bool timeout_started = false; bool timeout_started = false;
struct sc_acksync *acksync = NULL; struct sc_acksync *acksync = NULL;
struct sc_uhid_devices *uhid_devices = NULL;
uint32_t scid = scrcpy_generate_scid(); uint32_t scid = scrcpy_generate_scid();
@ -473,6 +507,13 @@ scrcpy(struct scrcpy_options *options) {
} }
} }
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); sdl_configure(options->video_playback, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling // Await for server without blocking Ctrl+C handling
@ -570,6 +611,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 = {
@ -589,7 +631,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;
@ -632,12 +676,15 @@ scrcpy(struct scrcpy_options *options) {
goto end; goto end;
} }
bool aoa_fail = false;
if (use_keyboard_aoa) { if (use_keyboard_aoa) {
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) { if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
keyboard_aoa_initialized = true; keyboard_aoa_initialized = true;
kp = &s->keyboard_aoa.key_processor; kp = &s->keyboard_aoa.key_processor;
} else { } else {
LOGE("Could not initialize HID keyboard"); LOGE("Could not initialize HID keyboard");
aoa_fail = true;
goto aoa_complete;
} }
} }
@ -647,12 +694,19 @@ scrcpy(struct scrcpy_options *options) {
mp = &s->mouse_aoa.mouse_processor; mp = &s->mouse_aoa.mouse_processor;
} else { } else {
LOGE("Could not initialized HID mouse"); LOGE("Could not initialized HID mouse");
aoa_fail = true;
goto aoa_complete;
} }
} }
bool need_aoa = keyboard_aoa_initialized || mouse_aoa_initialized; if (use_gamepad_aoa) {
sc_gamepad_aoa_init(&s->gamepad_aoa, &s->aoa);
gp = &s->gamepad_aoa.gamepad_processor;
gamepad_aoa_initialized = true;
}
if (!need_aoa || !sc_aoa_start(&s->aoa)) { aoa_complete:
if (aoa_fail || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync); sc_acksync_destroy(&s->acksync);
sc_usb_disconnect(&s->usb); sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb); sc_usb_destroy(&s->usb);
@ -669,6 +723,9 @@ scrcpy(struct scrcpy_options *options) {
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA); assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
#endif #endif
struct sc_keyboard_uhid *uhid_keyboard = NULL;
struct sc_gamepad_uhid *uhid_gamepad = NULL;
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) { if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller, sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
options->key_inject_mode, options->key_inject_mode,
@ -676,14 +733,12 @@ scrcpy(struct scrcpy_options *options) {
kp = &s->keyboard_sdk.key_processor; kp = &s->keyboard_sdk.key_processor;
} else if (options->keyboard_input_mode } else if (options->keyboard_input_mode
== SC_KEYBOARD_INPUT_MODE_UHID) { == SC_KEYBOARD_INPUT_MODE_UHID) {
sc_uhid_devices_init(&s->uhid_devices); bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller,
&s->uhid_devices);
if (!ok) { if (!ok) {
goto end; goto end;
} }
uhid_devices = &s->uhid_devices;
kp = &s->keyboard_uhid.key_processor; kp = &s->keyboard_uhid.key_processor;
uhid_keyboard = &s->keyboard_uhid;
} }
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
@ -698,6 +753,18 @@ scrcpy(struct scrcpy_options *options) {
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;
uhid_gamepad = &s->gamepad_uhid;
}
struct sc_uhid_devices *uhid_devices = NULL;
if (uhid_keyboard || uhid_gamepad) {
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard, uhid_gamepad);
uhid_devices = &s->uhid_devices;
}
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)) {
@ -719,6 +786,7 @@ scrcpy(struct scrcpy_options *options) {
.fp = fp, .fp = fp,
.kp = kp, .kp = kp,
.mp = mp, .mp = mp,
.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,
@ -830,7 +898,13 @@ scrcpy(struct scrcpy_options *options) {
timeout_started = true; timeout_started = true;
} }
bool use_gamepads = true;
if (use_gamepads) {
init_sdl_gamepads();
}
ret = event_loop(s); ret = event_loop(s);
terminate_event_loop();
LOGD("quit..."); LOGD("quit...");
if (options->video_playback) { if (options->video_playback) {
@ -855,6 +929,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);
} }

View File

@ -306,13 +306,9 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
screen->frame_size.width = ctx->width; screen->frame_size.width = ctx->width;
screen->frame_size.height = ctx->height; screen->frame_size.height = ctx->height;
static SDL_Event event = {
.type = SC_EVENT_SCREEN_INIT_SIZE,
};
// Post the event on the UI thread (the texture must be created from there) // Post the event on the UI thread (the texture must be created from there)
int ret = SDL_PushEvent(&event); bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
if (ret < 0) { if (!ok) {
LOGW("Could not post init size event: %s", SDL_GetError()); LOGW("Could not post init size event: %s", SDL_GetError());
return false; return false;
} }
@ -352,13 +348,9 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume // The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead // this new frame instead
} else { } else {
static SDL_Event new_frame_event = {
.type = SC_EVENT_NEW_FRAME,
};
// Post the event on the UI thread // Post the event on the UI thread
int ret = SDL_PushEvent(&new_frame_event); bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
if (ret < 0) { if (!ok) {
LOGW("Could not post new frame event: %s", SDL_GetError()); LOGW("Could not post new frame event: %s", SDL_GetError());
return false; return false;
} }
@ -481,6 +473,7 @@ sc_screen_init(struct sc_screen *screen,
.screen = screen, .screen = screen,
.kp = params->kp, .kp = params->kp,
.mp = params->mp, .mp = params->mp,
.gp = params->gp,
.mouse_bindings = params->mouse_bindings, .mouse_bindings = params->mouse_bindings,
.legacy_paste = params->legacy_paste, .legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync, .clipboard_autosync = params->clipboard_autosync,

View File

@ -78,6 +78,7 @@ struct sc_screen_params {
struct sc_file_pusher *fp; struct sc_file_pusher *fp;
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
struct sc_gamepad_processor *gp;
struct sc_mouse_bindings mouse_bindings; struct sc_mouse_bindings mouse_bindings;
bool legacy_paste; bool legacy_paste;

View File

@ -0,0 +1,50 @@
#ifndef SC_GAMEPAD_PROCESSOR_H
#define SC_GAMEPAD_PROCESSOR_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#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

140
app/src/uhid/gamepad_uhid.c Normal file
View File

@ -0,0 +1,140 @@
#include "gamepad_uhid.h"
#include "hid/hid_gamepad.h"
#include "input_events.h"
#include "util/log.h"
#include "util/str.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.name = hid_open->name;
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_process_hid_output(struct sc_gamepad_uhid *gamepad,
uint16_t hid_id, const uint8_t *data,
size_t size) {
(void) gamepad;
char *hex = sc_str_to_hex_string(data, size);
if (hex) {
LOGI("==== HID output [%" PRIu16 "] %s", hid_id, hex);
free(hex);
} else {
LOGI("==== HID output [%" PRIu16 "]", hid_id);
}
// TODO
}
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;
}

View File

@ -0,0 +1,28 @@
#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);
void
sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
uint16_t hid_id, const uint8_t *data,
size_t size);
#endif

View File

@ -9,21 +9,19 @@
#define DOWNCAST_RECEIVER(UR) \ #define DOWNCAST_RECEIVER(UR) \
container_of(UR, struct sc_keyboard_uhid, uhid_receiver) container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
#define UHID_KEYBOARD_ID 1
static void static void
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb, sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
const struct sc_hid_event *event) { const struct sc_hid_input *hid_input) {
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
msg.uhid_input.id = UHID_KEYBOARD_ID; msg.uhid_input.id = hid_input->hid_id;
assert(event->size <= SC_HID_MAX_SIZE); assert(hid_input->size <= SC_HID_MAX_SIZE);
memcpy(msg.uhid_input.data, event->data, event->size); memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
msg.uhid_input.size = event->size; msg.uhid_input.size = hid_input->size;
if (!sc_controller_push_msg(kb->controller, &msg)) { if (!sc_controller_push_msg(kb->controller, &msg)) {
LOGE("Could not send UHID_INPUT message (key)"); LOGE("Could not push UHID_INPUT message (key)");
} }
} }
@ -31,23 +29,22 @@ static void
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) { sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
SDL_Keymod sdl_mod = SDL_GetModState(); SDL_Keymod sdl_mod = SDL_GetModState();
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM); uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
uint16_t diff = mod ^ kb->device_mod;
uint16_t device_mod =
atomic_load_explicit(&kb->device_mod, memory_order_relaxed);
uint16_t diff = mod ^ device_mod;
if (diff) { if (diff) {
// Inherently racy (the HID output reports arrive asynchronously in // Inherently racy (the HID output reports arrive asynchronously in
// response to key presses), but will re-synchronize on next key press // response to key presses), but will re-synchronize on next key press
// or HID output anyway // or HID output anyway
atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed); kb->device_mod = mod;
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_keyboard_event_from_mods(&hid_event, diff); if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, diff)) {
return;
}
LOGV("HID keyboard state synchronized"); LOGV("HID keyboard state synchronized");
sc_keyboard_uhid_send_input(kb, &hid_event); sc_keyboard_uhid_send_input(kb, &hid_input);
} }
} }
@ -57,6 +54,8 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
uint64_t ack_to_wait) { uint64_t ack_to_wait) {
(void) ack_to_wait; (void) ack_to_wait;
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
if (event->repeat) { if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so // In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here. // just ignore key repeat here.
@ -65,22 +64,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
struct sc_keyboard_uhid *kb = DOWNCAST(kp); struct sc_keyboard_uhid *kb = DOWNCAST(kp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
// Not all keys are supported, just ignore unsupported keys // Not all keys are supported, just ignore unsupported keys
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) { if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
if (event->scancode == SC_SCANCODE_CAPSLOCK) { if (event->scancode == SC_SCANCODE_CAPSLOCK) {
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS, kb->device_mod ^= SC_MOD_CAPS;
memory_order_relaxed);
} else if (event->scancode == SC_SCANCODE_NUMLOCK) { } else if (event->scancode == SC_SCANCODE_NUMLOCK) {
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM, kb->device_mod ^= SC_MOD_NUM;
memory_order_relaxed);
} else { } else {
// Synchronize modifiers (only if the scancode itself does not // Synchronize modifiers (only if the scancode itself does not
// change the modifiers) // change the modifiers)
sc_keyboard_uhid_synchronize_mod(kb); sc_keyboard_uhid_synchronize_mod(kb);
} }
sc_keyboard_uhid_send_input(kb, &hid_event); sc_keyboard_uhid_send_input(kb, &hid_input);
} }
} }
@ -98,34 +95,31 @@ sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) {
return mod; return mod;
} }
static void void
sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver, sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
const uint8_t *data, size_t len) { const uint8_t *data, size_t size) {
// Called from the thread receiving device messages assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
assert(len); assert(size);
// Also check at runtime (do not trust the server) // Also check at runtime (do not trust the server)
if (!len) { if (!size) {
LOGE("Unexpected empty HID output message"); LOGE("Unexpected empty HID output message");
return; return;
} }
struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver);
uint8_t hid_led = data[0]; uint8_t hid_led = data[0];
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led); uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
atomic_store_explicit(&kb->device_mod, device_mod, memory_order_relaxed); kb->device_mod = device_mod;
} }
bool bool
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_controller *controller, struct sc_controller *controller) {
struct sc_uhid_devices *uhid_devices) {
sc_hid_keyboard_init(&kb->hid); sc_hid_keyboard_init(&kb->hid);
kb->controller = controller; kb->controller = controller;
atomic_init(&kb->device_mod, 0); kb->device_mod = 0;
static const struct sc_key_processor_ops ops = { static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key, .process_key = sc_key_processor_process_key,
@ -140,19 +134,16 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
kb->key_processor.hid = true; kb->key_processor.hid = true;
kb->key_processor.ops = &ops; kb->key_processor.ops = &ops;
static const struct sc_uhid_receiver_ops uhid_receiver_ops = { struct sc_hid_open hid_open;
.process_output = sc_uhid_receiver_process_output, sc_hid_keyboard_generate_open(&hid_open);
}; assert(hid_open.hid_id == SC_HID_ID_KEYBOARD);
kb->uhid_receiver.id = UHID_KEYBOARD_ID;
kb->uhid_receiver.ops = &uhid_receiver_ops;
sc_uhid_devices_add_receiver(uhid_devices, &kb->uhid_receiver);
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = UHID_KEYBOARD_ID; msg.uhid_create.id = SC_HID_ID_KEYBOARD;
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC; msg.uhid_create.name = hid_open.name;
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN; 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(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGE("Could not send UHID_CREATE message (keyboard)"); LOGE("Could not send UHID_CREATE message (keyboard)");
return false; return false;

View File

@ -7,21 +7,22 @@
#include "controller.h" #include "controller.h"
#include "hid/hid_keyboard.h" #include "hid/hid_keyboard.h"
#include "uhid/uhid_output.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
struct sc_keyboard_uhid { struct sc_keyboard_uhid {
struct sc_key_processor key_processor; // key processor trait struct sc_key_processor key_processor; // key processor trait
struct sc_uhid_receiver uhid_receiver;
struct sc_hid_keyboard hid; struct sc_hid_keyboard hid;
struct sc_controller *controller; struct sc_controller *controller;
atomic_uint_least16_t device_mod; uint16_t device_mod;
}; };
bool bool
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb, sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_controller *controller, struct sc_controller *controller);
struct sc_uhid_devices *uhid_devices);
void
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
const uint8_t *data, size_t size);
#endif #endif

View File

@ -7,21 +7,20 @@
/** Downcast mouse processor to mouse_uhid */ /** Downcast mouse processor to mouse_uhid */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor) #define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
#define UHID_MOUSE_ID 2
static void static void
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse, sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
const struct sc_hid_event *event, const char *name) { const struct sc_hid_input *hid_input,
const char *name) {
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
msg.uhid_input.id = UHID_MOUSE_ID; msg.uhid_input.id = hid_input->hid_id;
assert(event->size <= SC_HID_MAX_SIZE); assert(hid_input->size <= SC_HID_MAX_SIZE);
memcpy(msg.uhid_input.data, event->data, event->size); memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
msg.uhid_input.size = event->size; msg.uhid_input.size = hid_input->size;
if (!sc_controller_push_msg(mouse->controller, &msg)) { if (!sc_controller_push_msg(mouse->controller, &msg)) {
LOGE("Could not send UHID_INPUT message (%s)", name); LOGE("Could not push UHID_INPUT message (%s)", name);
} }
} }
@ -30,10 +29,10 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) { const struct sc_mouse_motion_event *event) {
struct sc_mouse_uhid *mouse = DOWNCAST(mp); struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_mouse_event_from_motion(&hid_event, event); sc_hid_mouse_generate_input_from_motion(&hid_input, event);
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion"); sc_mouse_uhid_send_input(mouse, &hid_input, "mouse motion");
} }
static void static void
@ -41,10 +40,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) { const struct sc_mouse_click_event *event) {
struct sc_mouse_uhid *mouse = DOWNCAST(mp); struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_mouse_event_from_click(&hid_event, event); sc_hid_mouse_generate_input_from_click(&hid_input, event);
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click"); sc_mouse_uhid_send_input(mouse, &hid_input, "mouse click");
} }
static void static void
@ -52,10 +51,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) { const struct sc_mouse_scroll_event *event) {
struct sc_mouse_uhid *mouse = DOWNCAST(mp); struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_mouse_event_from_scroll(&hid_event, event); sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll"); sc_mouse_uhid_send_input(mouse, &hid_input, "mouse scroll");
} }
bool bool
@ -75,13 +74,18 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
mouse->mouse_processor.relative_mode = true; mouse->mouse_processor.relative_mode = true;
struct sc_hid_open hid_open;
sc_hid_mouse_generate_open(&hid_open);
assert(hid_open.hid_id == SC_HID_ID_MOUSE);
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = UHID_MOUSE_ID; msg.uhid_create.id = SC_HID_ID_MOUSE;
msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC; msg.uhid_create.name = hid_open.name;
msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN; 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(controller, &msg)) { if (!sc_controller_push_msg(controller, &msg)) {
LOGE("Could not send UHID_CREATE message (mouse)"); LOGE("Could not push UHID_CREATE message (mouse)");
return false; return false;
} }

View File

@ -1,25 +1,37 @@
#include "uhid_output.h" #include "uhid_output.h"
#include <assert.h> #include <assert.h>
#include <inttypes.h>
#include "uhid/keyboard_uhid.h"
#include "uhid/gamepad_uhid.h"
#include "util/log.h"
void void
sc_uhid_devices_init(struct sc_uhid_devices *devices) { sc_uhid_devices_init(struct sc_uhid_devices *devices,
devices->count = 0; struct sc_keyboard_uhid *keyboard,
struct sc_gamepad_uhid *gamepad) {
devices->keyboard = keyboard;
devices->gamepad = gamepad;
} }
void void
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices, sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
struct sc_uhid_receiver *receiver) { const uint8_t *data, size_t size) {
assert(devices->count < SC_UHID_MAX_RECEIVERS); if (id == SC_HID_ID_KEYBOARD) {
devices->receivers[devices->count++] = receiver; if (devices->keyboard) {
} sc_keyboard_uhid_process_hid_output(devices->keyboard, data, size);
} else {
struct sc_uhid_receiver * LOGW("Unexpected keyboard HID output without UHID keyboard");
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id) {
for (size_t i = 0; i < devices->count; ++i) {
if (devices->receivers[i]->id == id) {
return devices->receivers[i];
} }
} else if (id >= SC_HID_ID_GAMEPAD_FIRST && id <= SC_HID_ID_GAMEPAD_LAST) {
if (devices->gamepad) {
sc_gamepad_uhid_process_hid_output(devices->gamepad, id, data,
size);
} else {
LOGW("Unexpected gamepad HID output without UHID gamepad");
}
} else {
LOGW("HID output ignored for id %" PRIu16, id);
} }
return NULL;
} }

View File

@ -9,37 +9,21 @@
/** /**
* The communication with UHID devices is bidirectional. * The communication with UHID devices is bidirectional.
* *
* This component manages the registration of receivers to handle UHID output * This component dispatches HID outputs to the expected processor.
* messages (sent from the device to the computer).
*/ */
struct sc_uhid_receiver {
uint16_t id;
const struct sc_uhid_receiver_ops *ops;
};
struct sc_uhid_receiver_ops {
void
(*process_output)(struct sc_uhid_receiver *receiver,
const uint8_t *data, size_t len);
};
#define SC_UHID_MAX_RECEIVERS 1
struct sc_uhid_devices { struct sc_uhid_devices {
struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS]; struct sc_keyboard_uhid *keyboard;
unsigned count; struct sc_gamepad_uhid *gamepad;
}; };
void void
sc_uhid_devices_init(struct sc_uhid_devices *devices); sc_uhid_devices_init(struct sc_uhid_devices *devices,
struct sc_keyboard_uhid *keyboard,
struct sc_gamepad_uhid *gamepad);
void void
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices, sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
struct sc_uhid_receiver *receiver); const uint8_t *data, size_t size);
struct sc_uhid_receiver *
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id);
#endif #endif

View File

@ -1,11 +1,14 @@
#include "util/log.h" #include "util/log.h"
#include <assert.h> #include <assert.h>
#include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include "aoa_hid.h" #include "aoa_hid.h"
#include "events.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str.h"
#include "util/vector.h"
// See <https://source.android.com/devices/accessories/aoa2#hid-support>. // See <https://source.android.com/devices/accessories/aoa2#hid-support>.
#define ACCESSORY_REGISTER_HID 54 #define ACCESSORY_REGISTER_HID 54
@ -15,26 +18,49 @@
#define DEFAULT_TIMEOUT 1000 #define DEFAULT_TIMEOUT 1000
#define SC_AOA_EVENT_QUEUE_MAX 64 // Drop droppable events above this limit
#define SC_AOA_EVENT_QUEUE_LIMIT 60
struct sc_vec_hid_ids SC_VECTOR(uint16_t);
static void static void
sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) { sc_hid_input_log(const struct sc_hid_input *hid_input) {
// HID Event: [00] FF FF FF FF... // HID input: [00] FF FF FF FF...
assert(event->size); assert(hid_input->size);
char *hex = sc_str_to_hex_string(event->data, event->size); char *hex = sc_str_to_hex_string(hid_input->data, hid_input->size);
if (!hex) { if (!hex) {
return; return;
} }
LOGV("HID Event: [%d] %s", accessory_id, hex); LOGV("HID input: [%" PRIu16 "] %s", hid_input->hid_id, hex);
free(hex); free(hex);
} }
static void
sc_hid_open_log(const struct sc_hid_open *hid_open) {
// HID open: [00] FF FF FF FF...
assert(hid_open->report_desc_size);
char *hex = sc_str_to_hex_string(hid_open->report_desc,
hid_open->report_desc_size);
if (!hex) {
return;
}
LOGV("HID open: [%" PRIu16 "] %s", hid_open->hid_id, hex);
free(hex);
}
static void
sc_hid_close_log(const struct sc_hid_close *hid_close) {
// HID close: [00]
LOGV("HD close: [%" PRIu16 "]", hid_close->hid_id);
}
bool bool
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
struct sc_acksync *acksync) { struct sc_acksync *acksync) {
sc_vecdeque_init(&aoa->queue); sc_vecdeque_init(&aoa->queue);
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) { // Add 4 to support 4 non-droppable events without re-allocation
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_LIMIT + 4)) {
return false; return false;
} }
@ -125,38 +151,18 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
return true; return true;
} }
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const uint8_t *report_desc, uint16_t report_desc_size) {
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
if (!ok) {
return false;
}
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
report_desc_size);
if (!ok) {
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
LOGW("Could not unregister HID");
}
return false;
}
return true;
}
static bool static bool
sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id, sc_aoa_send_hid_event(struct sc_aoa *aoa,
const struct sc_hid_event *event) { const struct sc_hid_input *hid_input) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SEND_HID_EVENT; uint8_t request = ACCESSORY_SEND_HID_EVENT;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support> // <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device // value (arg0): accessory assigned ID for the HID device
// index (arg1): 0 (unused) // index (arg1): 0 (unused)
uint16_t value = accessory_id; uint16_t value = hid_input->hid_id;
uint16_t index = 0; uint16_t index = 0;
unsigned char *data = (uint8_t *) event->data; // discard const unsigned char *data = (uint8_t *) hid_input->data; // discard const
uint16_t length = event->size; uint16_t length = hid_input->size;
int result = libusb_control_transfer(aoa->usb->handle, request_type, int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, data, length, request, value, index, data, length,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
@ -169,7 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
return true; return true;
} }
bool static bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) { sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_UNREGISTER_HID; uint8_t request = ACCESSORY_UNREGISTER_HID;
@ -192,13 +198,32 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
return true; return true;
} }
static bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const uint8_t *report_desc, uint16_t report_desc_size) {
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
if (!ok) {
return false;
}
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
report_desc_size);
if (!ok) {
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
LOGW("Could not unregister HID");
}
return false;
}
return true;
}
bool bool
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
uint16_t accessory_id, const struct sc_hid_input *hid_input,
const struct sc_hid_event *event,
uint64_t ack_to_wait) { uint64_t ack_to_wait) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_event_log(accessory_id, event); sc_hid_input_log(hid_input);
} }
sc_mutex_lock(&aoa->mutex); sc_mutex_lock(&aoa->mutex);
@ -208,9 +233,9 @@ sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
struct sc_aoa_event *aoa_event = struct sc_aoa_event *aoa_event =
sc_vecdeque_push_hole_noresize(&aoa->queue); sc_vecdeque_push_hole_noresize(&aoa->queue);
aoa_event->hid = *event; aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
aoa_event->accessory_id = accessory_id; aoa_event->input.hid = *hid_input;
aoa_event->ack_to_wait = ack_to_wait; aoa_event->input.ack_to_wait = ack_to_wait;
if (was_empty) { if (was_empty) {
sc_cond_signal(&aoa->event_cond); sc_cond_signal(&aoa->event_cond);
@ -223,10 +248,159 @@ sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
return !full; return !full;
} }
bool
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
bool exit_on_open_error) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_open_log(hid_open);
}
sc_mutex_lock(&aoa->mutex);
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
// an OPEN event is non-droppable, so push it to the queue even above the
// SC_AOA_EVENT_QUEUE_LIMIT
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
if (!aoa_event) {
LOG_OOM();
sc_mutex_unlock(&aoa->mutex);
return false;
}
aoa_event->type = SC_AOA_EVENT_TYPE_OPEN;
aoa_event->open.hid = *hid_open;
aoa_event->open.exit_on_error = exit_on_open_error;
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
}
sc_mutex_unlock(&aoa->mutex);
return true;
}
bool
sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_close_log(hid_close);
}
sc_mutex_lock(&aoa->mutex);
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
// an OPEN event is non-droppable, so push it to the queue even above the
// SC_AOA_EVENT_QUEUE_LIMIT
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
if (!aoa_event) {
LOG_OOM();
sc_mutex_unlock(&aoa->mutex);
return false;
}
aoa_event->type = SC_AOA_EVENT_TYPE_CLOSE;
aoa_event->close.hid = *hid_close;
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
}
sc_mutex_unlock(&aoa->mutex);
return true;
}
static bool
sc_aoa_process_event(struct sc_aoa *aoa, struct sc_aoa_event *event,
struct sc_vec_hid_ids *vec_open) {
switch (event->type) {
case SC_AOA_EVENT_TYPE_INPUT: {
uint64_t ack_to_wait = event->input.ack_to_wait;
if (ack_to_wait != SC_SEQUENCE_INVALID) {
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
// If some events have ack_to_wait set, then sc_aoa must have
// been initialized with a non NULL acksync
assert(aoa->acksync);
// Do not block the loop indefinitely if the ack never comes (it
// should never happen)
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
enum sc_acksync_wait_result result =
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
LOGW("Ack not received after 500ms, discarding HID event");
// continue to process events
return true;
} else if (result == SC_ACKSYNC_WAIT_INTR) {
// stopped
return false;
}
}
struct sc_hid_input *hid_input = &event->input.hid;
bool ok = sc_aoa_send_hid_event(aoa, hid_input);
if (!ok) {
LOGW("Could not send HID event to USB device: %" PRIu16,
hid_input->hid_id);
}
break;
}
case SC_AOA_EVENT_TYPE_OPEN: {
struct sc_hid_open *hid_open = &event->open.hid;
bool ok = sc_aoa_setup_hid(aoa, hid_open->hid_id,
hid_open->report_desc,
hid_open->report_desc_size);
if (ok) {
// The device is now open, add it to the list of devices to
// close automatically on exit
bool pushed = sc_vector_push(vec_open, hid_open->hid_id);
if (!pushed) {
LOG_OOM();
// this is not fatal, the HID device will just not be
// explicitly unregistered
}
} else {
LOGW("Could not open AOA device: %" PRIu16, hid_open->hid_id);
if (event->open.exit_on_error) {
// Notify the error to the main thread, which will exit
sc_push_event(SC_EVENT_AOA_OPEN_ERROR);
}
}
break;
}
case SC_AOA_EVENT_TYPE_CLOSE: {
struct sc_hid_close *hid_close = &event->close.hid;
bool ok = sc_aoa_unregister_hid(aoa, hid_close->hid_id);
if (ok) {
// The device is not open anymore, remove it from the list of
// devices to close automatically on exit
ssize_t idx = sc_vector_index_of(vec_open, hid_close->hid_id);
if (idx >= 0) {
sc_vector_remove(vec_open, idx);
}
} else {
LOGW("Could not close AOA device: %" PRIu16, hid_close->hid_id);
}
break;
}
}
// continue to process events
return true;
}
static int static int
run_aoa_thread(void *data) { run_aoa_thread(void *data) {
struct sc_aoa *aoa = data; struct sc_aoa *aoa = data;
// Store the HID ids of opened devices to unregister them all before exiting
struct sc_vec_hid_ids vec_open = SC_VECTOR_INITIALIZER;
for (;;) { for (;;) {
sc_mutex_lock(&aoa->mutex); sc_mutex_lock(&aoa->mutex);
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) { while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
@ -240,36 +414,26 @@ run_aoa_thread(void *data) {
assert(!sc_vecdeque_is_empty(&aoa->queue)); assert(!sc_vecdeque_is_empty(&aoa->queue));
struct sc_aoa_event event = sc_vecdeque_pop(&aoa->queue); struct sc_aoa_event event = sc_vecdeque_pop(&aoa->queue);
uint64_t ack_to_wait = event.ack_to_wait;
sc_mutex_unlock(&aoa->mutex); sc_mutex_unlock(&aoa->mutex);
if (ack_to_wait != SC_SEQUENCE_INVALID) { bool cont = sc_aoa_process_event(aoa, &event, &vec_open);
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait); if (!cont) {
// If some events have ack_to_wait set, then sc_aoa must have been
// initialized with a non NULL acksync
assert(aoa->acksync);
// Do not block the loop indefinitely if the ack never comes (it
// should never happen)
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
enum sc_acksync_wait_result result =
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
LOGW("Ack not received after 500ms, discarding HID event");
continue;
} else if (result == SC_ACKSYNC_WAIT_INTR) {
// stopped // stopped
break; break;
} }
} }
bool ok = sc_aoa_send_hid_event(aoa, event.accessory_id, &event.hid); // Explicitly unregister all registered HID ids before exiting
for (size_t i = 0; i < vec_open.size; ++i) {
uint16_t hid_id = vec_open.data[i];
LOGD("Unregistering AOA device %" PRIu16 "...", hid_id);
bool ok = sc_aoa_unregister_hid(aoa, hid_id);
if (!ok) { if (!ok) {
LOGW("Could not send HID event to USB device"); LOGW("Could not close AOA device: %" PRIu16, hid_id);
} }
} }
sc_vector_destroy(&vec_open);
return 0; return 0;
} }

View File

@ -13,12 +13,27 @@
#include "util/tick.h" #include "util/tick.h"
#include "util/vecdeque.h" #include "util/vecdeque.h"
#define SC_HID_MAX_SIZE 8 enum sc_aoa_event_type {
SC_AOA_EVENT_TYPE_OPEN,
SC_AOA_EVENT_TYPE_INPUT,
SC_AOA_EVENT_TYPE_CLOSE,
};
struct sc_aoa_event { struct sc_aoa_event {
struct sc_hid_event hid; enum sc_aoa_event_type type;
uint16_t accessory_id; union {
struct {
struct sc_hid_open hid;
bool exit_on_error;
} open;
struct {
struct sc_hid_close hid;
} close;
struct {
struct sc_hid_input hid;
uint64_t ack_to_wait; uint64_t ack_to_wait;
} input;
};
}; };
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event); struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
@ -49,23 +64,30 @@ sc_aoa_stop(struct sc_aoa *aoa);
void void
sc_aoa_join(struct sc_aoa *aoa); sc_aoa_join(struct sc_aoa *aoa);
//bool
//sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
// const uint8_t *report_desc, uint16_t report_desc_size);
//
//bool
//sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
// report_desc must be a pointer to static memory, accessed at any time from
// another thread
bool bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
const uint8_t *report_desc, uint16_t report_desc_size); bool exit_on_open_error);
bool bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close);
bool bool
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa, sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
uint16_t accessory_id, const struct sc_hid_input *hid_input,
const struct sc_hid_event *event,
uint64_t ack_to_wait); uint64_t ack_to_wait);
static inline bool static inline bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id, sc_aoa_push_input(struct sc_aoa *aoa, const struct sc_hid_input *hid_input) {
const struct sc_hid_event *event) { return sc_aoa_push_input_with_ack_to_wait(aoa, hid_input,
return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event,
SC_SEQUENCE_INVALID); SC_SEQUENCE_INVALID);
} }

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

View File

@ -8,19 +8,16 @@
/** Downcast key processor to keyboard_aoa */ /** Downcast key processor to keyboard_aoa */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor) #define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
#define HID_KEYBOARD_ACCESSORY_ID 1
static bool static bool
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) { push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) { if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, mods_state)) {
// Nothing to do // Nothing to do
return true; return true;
} }
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID, if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
&hid_event)) { LOGW("Could not push AOA HID input (mod lock state)");
LOGW("Could not request HID event (mod lock state)");
return false; return false;
} }
@ -41,10 +38,10 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
struct sc_keyboard_aoa *kb = DOWNCAST(kp); struct sc_keyboard_aoa *kb = DOWNCAST(kp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
// Not all keys are supported, just ignore unsupported keys // Not all keys are supported, just ignore unsupported keys
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) { if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
if (!kb->mod_lock_synchronized) { if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize // Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state // keyboard state
@ -58,11 +55,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
// synchronization is acknowledged by the server, otherwise it could // synchronization is acknowledged by the server, otherwise it could
// paste the old clipboard content. // paste the old clipboard content.
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa, if (!sc_aoa_push_input_with_ack_to_wait(kb->aoa, &hid_input,
HID_KEYBOARD_ACCESSORY_ID,
&hid_event,
ack_to_wait)) { ack_to_wait)) {
LOGW("Could not request HID event (key)"); LOGW("Could not push AOA HID input (key)");
} }
} }
} }
@ -71,11 +66,12 @@ bool
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) { sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
kb->aoa = aoa; kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID, struct sc_hid_open hid_open;
SC_HID_KEYBOARD_REPORT_DESC, sc_hid_keyboard_generate_open(&hid_open);
SC_HID_KEYBOARD_REPORT_DESC_LEN);
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
if (!ok) { if (!ok) {
LOGW("Register HID keyboard failed"); LOGW("Could not push AOA HID open (keyboard)");
return false; return false;
} }
@ -102,9 +98,6 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
void void
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) { sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android (void) kb;
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID); // Do nothing, kb->aoa will automatically unregister all devices
if (!ok) {
LOGW("Could not unregister HID keyboard");
}
} }

View File

@ -9,19 +9,16 @@
/** Downcast mouse processor to mouse_aoa */ /** Downcast mouse processor to mouse_aoa */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor) #define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
#define HID_MOUSE_ACCESSORY_ID 2
static void static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) { const struct sc_mouse_motion_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp); struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_mouse_event_from_motion(&hid_event, event); sc_hid_mouse_generate_input_from_motion(&hid_input, event);
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
&hid_event)) { LOGW("Could not push AOA HID input (mouse motion)");
LOGW("Could not request HID event (mouse motion)");
} }
} }
@ -30,12 +27,11 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) { const struct sc_mouse_click_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp); struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_mouse_event_from_click(&hid_event, event); sc_hid_mouse_generate_input_from_click(&hid_input, event);
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
&hid_event)) { LOGW("Could not push AOA HID input (mouse click)");
LOGW("Could not request HID event (mouse click)");
} }
} }
@ -44,12 +40,11 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) { const struct sc_mouse_scroll_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp); struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event; struct sc_hid_input hid_input;
sc_hid_mouse_event_from_scroll(&hid_event, event); sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID, if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
&hid_event)) { LOGW("Could not push AOA HID input (mouse scroll)");
LOGW("Could not request HID event (mouse scroll)");
} }
} }
@ -57,11 +52,12 @@ bool
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) { sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
mouse->aoa = aoa; mouse->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, struct sc_hid_open hid_open;
SC_HID_MOUSE_REPORT_DESC, sc_hid_mouse_generate_open(&hid_open);
SC_HID_MOUSE_REPORT_DESC_LEN);
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
if (!ok) { if (!ok) {
LOGW("Register HID mouse failed"); LOGW("Could not push AOA HID open (mouse)");
return false; return false;
} }
@ -82,8 +78,6 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
void void
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) { sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID); (void) mouse;
if (!ok) { // Do nothing, mouse->aoa will automatically unregister all devices
LOGW("Could not unregister HID mouse");
}
} }

View File

@ -12,6 +12,7 @@ struct scrcpy_otg {
struct sc_aoa aoa; struct sc_aoa aoa;
struct sc_keyboard_aoa keyboard; struct sc_keyboard_aoa keyboard;
struct sc_mouse_aoa mouse; struct sc_mouse_aoa mouse;
struct sc_gamepad_aoa gamepad;
struct sc_screen_otg screen_otg; struct sc_screen_otg screen_otg;
}; };
@ -21,10 +22,8 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb; (void) usb;
(void) userdata; (void) userdata;
SDL_Event event; bool ok = sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
event.type = SC_EVENT_USB_DEVICE_DISCONNECTED; if (!ok) {
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post USB disconnection event: %s", SDL_GetError()); LOGE("Could not post USB disconnection event: %s", SDL_GetError());
} }
} }
@ -37,6 +36,9 @@ event_loop(struct scrcpy_otg *s) {
case SC_EVENT_USB_DEVICE_DISCONNECTED: case SC_EVENT_USB_DEVICE_DISCONNECTED:
LOGW("Device disconnected"); LOGW("Device disconnected");
return SCRCPY_EXIT_DISCONNECTED; return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_AOA_OPEN_ERROR:
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS; return SCRCPY_EXIT_SUCCESS;
@ -65,6 +67,13 @@ scrcpy_otg(struct scrcpy_options *options) {
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
} }
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
// Not fatal, keyboard/mouse should still work
}
}
atexit(SDL_Quit); atexit(SDL_Quit);
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) { if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
@ -75,6 +84,7 @@ scrcpy_otg(struct scrcpy_options *options) {
struct sc_keyboard_aoa *keyboard = NULL; struct sc_keyboard_aoa *keyboard = NULL;
struct sc_mouse_aoa *mouse = NULL; struct sc_mouse_aoa *mouse = NULL;
struct sc_gamepad_aoa *gamepad = NULL;
bool usb_device_initialized = false; bool usb_device_initialized = false;
bool usb_connected = false; bool usb_connected = false;
bool aoa_started = false; bool aoa_started = false;
@ -121,11 +131,15 @@ scrcpy_otg(struct scrcpy_options *options) {
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED); || options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED); || options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
assert(options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA
|| options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_DISABLED);
bool enable_keyboard = bool enable_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool enable_mouse = bool enable_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA; options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
bool enable_gamepad =
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
if (enable_keyboard) { if (enable_keyboard) {
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa); ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
@ -143,6 +157,11 @@ scrcpy_otg(struct scrcpy_options *options) {
mouse = &s->mouse; mouse = &s->mouse;
} }
if (enable_gamepad) {
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
gamepad = &s->gamepad;
}
ok = sc_aoa_start(&s->aoa); ok = sc_aoa_start(&s->aoa);
if (!ok) { if (!ok) {
goto end; goto end;
@ -157,6 +176,7 @@ scrcpy_otg(struct scrcpy_options *options) {
struct sc_screen_otg_params params = { struct sc_screen_otg_params params = {
.keyboard = keyboard, .keyboard = keyboard,
.mouse = mouse, .mouse = mouse,
.gamepad = gamepad,
.window_title = window_title, .window_title = window_title,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
.window_x = options->window_x, .window_x = options->window_x,
@ -190,6 +210,9 @@ end:
if (keyboard) { if (keyboard) {
sc_keyboard_aoa_destroy(&s->keyboard); sc_keyboard_aoa_destroy(&s->keyboard);
} }
if (gamepad) {
sc_gamepad_aoa_destroy(&s->gamepad);
}
if (aoa_initialized) { if (aoa_initialized) {
sc_aoa_join(&s->aoa); sc_aoa_join(&s->aoa);

View File

@ -59,6 +59,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
const struct sc_screen_otg_params *params) { const struct sc_screen_otg_params *params) {
screen->keyboard = params->keyboard; screen->keyboard = params->keyboard;
screen->mouse = params->mouse; screen->mouse = params->mouse;
screen->gamepad = params->gamepad;
screen->mouse_capture_key_pressed = 0; screen->mouse_capture_key_pressed = 0;
@ -214,6 +215,77 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
mp->ops->process_mouse_scroll(mp, &evt); mp->ops->process_mouse_scroll(mp, &evt);
} }
static void
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
const SDL_ControllerDeviceEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
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,
};
gp->ops->process_gamepad_device(gp, &evt);
}
static void
sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
const SDL_ControllerAxisEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
struct sc_gamepad_axis_event evt = {
.gamepad_id = event->which,
.axis = sc_gamepad_axis_from_sdl(event->axis),
.value = event->value,
};
gp->ops->process_gamepad_axis(gp, &evt);
}
static void
sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
const SDL_ControllerButtonEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
struct sc_gamepad_button_event evt = {
.gamepad_id = event->which,
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = sc_gamepad_button_from_sdl(event->button),
};
gp->ops->process_gamepad_button(gp, &evt);
}
void void
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) { sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
switch (event->type) { switch (event->type) {
@ -293,5 +365,23 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
sc_screen_otg_process_mouse_wheel(screen, &event->wheel); sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
} }
break; break;
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// Handle device added or removed even if paused
if (screen->gamepad) {
sc_screen_otg_process_gamepad_device(screen, &event->cdevice);
}
break;
case SDL_CONTROLLERAXISMOTION:
if (screen->gamepad) {
sc_screen_otg_process_gamepad_axis(screen, &event->caxis);
}
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (screen->gamepad) {
sc_screen_otg_process_gamepad_button(screen, &event->cbutton);
}
break;
} }
} }

View File

@ -8,10 +8,12 @@
#include "keyboard_aoa.h" #include "keyboard_aoa.h"
#include "mouse_aoa.h" #include "mouse_aoa.h"
#include "gamepad_aoa.h"
struct sc_screen_otg { struct sc_screen_otg {
struct sc_keyboard_aoa *keyboard; struct sc_keyboard_aoa *keyboard;
struct sc_mouse_aoa *mouse; struct sc_mouse_aoa *mouse;
struct sc_gamepad_aoa *gamepad;
SDL_Window *window; SDL_Window *window;
SDL_Renderer *renderer; SDL_Renderer *renderer;
@ -24,6 +26,7 @@ struct sc_screen_otg {
struct sc_screen_otg_params { struct sc_screen_otg_params {
struct sc_keyboard_aoa *keyboard; struct sc_keyboard_aoa *keyboard;
struct sc_mouse_aoa *mouse; struct sc_mouse_aoa *mouse;
struct sc_gamepad_aoa *gamepad;
const char *window_title; const char *window_title;
bool always_on_top; bool always_on_top;

View File

@ -13,6 +13,12 @@ sc_write16be(uint8_t *buf, uint16_t value) {
buf[1] = value; buf[1] = value;
} }
static inline void
sc_write16le(uint8_t *buf, uint16_t value) {
buf[0] = value;
buf[1] = value >> 8;
}
static inline void static inline void
sc_write32be(uint8_t *buf, uint32_t value) { sc_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24; buf[0] = value >> 24;
@ -21,12 +27,26 @@ sc_write32be(uint8_t *buf, uint32_t value) {
buf[3] = value; buf[3] = value;
} }
static inline void
sc_write32le(uint8_t *buf, uint32_t value) {
buf[0] = value;
buf[1] = value >> 8;
buf[2] = value >> 16;
buf[3] = value >> 24;
}
static inline void static inline void
sc_write64be(uint8_t *buf, uint64_t value) { sc_write64be(uint8_t *buf, uint64_t value) {
sc_write32be(buf, value >> 32); sc_write32be(buf, value >> 32);
sc_write32be(&buf[4], (uint32_t) value); sc_write32be(&buf[4], (uint32_t) value);
} }
static inline void
sc_write64le(uint8_t *buf, uint64_t value) {
sc_write32le(buf, (uint32_t) value);
sc_write32le(buf, value >> 32);
}
static inline uint16_t static inline uint16_t
sc_read16be(const uint8_t *buf) { sc_read16be(const uint8_t *buf) {
return (buf[0] << 8) | buf[1]; return (buf[0] << 8) | buf[1];

View File

@ -6,6 +6,8 @@
#include "log.h" #include "log.h"
sc_thread_id SC_MAIN_THREAD_ID;
bool bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) { void *userdata) {

View File

@ -39,6 +39,8 @@ typedef struct sc_cond {
SDL_cond *cond; SDL_cond *cond;
} sc_cond; } sc_cond;
extern sc_thread_id SC_MAIN_THREAD_ID;
bool bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name, sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata); void *userdata);

View File

@ -329,6 +329,7 @@ static void test_serialize_uhid_create(void) {
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE, .type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
.uhid_create = { .uhid_create = {
.id = 42, .id = 42,
.name = "ABC",
.report_desc_size = sizeof(report_desc), .report_desc_size = sizeof(report_desc),
.report_desc = report_desc, .report_desc = report_desc,
}, },
@ -336,12 +337,14 @@ static void test_serialize_uhid_create(void) {
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf); size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 16); assert(size == 20);
const uint8_t expected[] = { const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_CREATE,
0, 42, // id 0, 42, // id
0, 11, // size 3, // name size
65, 66, 67, // "ABC"
0, 11, // report desc size
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
@ -370,6 +373,25 @@ static void test_serialize_uhid_input(void) {
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
static void test_serialize_uhid_destroy(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_UHID_DESTROY,
.uhid_destroy = {
.id = 42,
},
};
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 3);
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
0, 42, // id
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_open_hard_keyboard(void) { static void test_serialize_open_hard_keyboard(void) {
struct sc_control_msg msg = { struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, .type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
@ -405,6 +427,7 @@ int main(int argc, char *argv[]) {
test_serialize_rotate_device(); test_serialize_rotate_device();
test_serialize_uhid_create(); test_serialize_uhid_create();
test_serialize_uhid_input(); test_serialize_uhid_input();
test_serialize_uhid_destroy();
test_serialize_open_hard_keyboard(); test_serialize_open_hard_keyboard();
return 0; return 0;
} }

View File

@ -21,7 +21,8 @@ public final class ControlMessage {
public static final int TYPE_ROTATE_DEVICE = 11; public static final int TYPE_ROTATE_DEVICE = 11;
public static final int TYPE_UHID_CREATE = 12; public static final int TYPE_UHID_CREATE = 12;
public static final int TYPE_UHID_INPUT = 13; public static final int TYPE_UHID_INPUT = 13;
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14; public static final int TYPE_UHID_DESTROY = 14;
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
public static final long SEQUENCE_INVALID = 0; public static final long SEQUENCE_INVALID = 0;
@ -130,10 +131,11 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createUhidCreate(int id, byte[] reportDesc) { public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_UHID_CREATE; msg.type = TYPE_UHID_CREATE;
msg.id = id; msg.id = id;
msg.text = name;
msg.data = reportDesc; msg.data = reportDesc;
return msg; return msg;
} }
@ -146,6 +148,13 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createUhidDestroy(int id) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_UHID_DESTROY;
msg.id = id;
return msg;
}
public int getType() { public int getType() {
return type; return type;
} }

View File

@ -51,6 +51,8 @@ public class ControlMessageReader {
return parseUhidCreate(); return parseUhidCreate();
case ControlMessage.TYPE_UHID_INPUT: case ControlMessage.TYPE_UHID_INPUT:
return parseUhidInput(); return parseUhidInput();
case ControlMessage.TYPE_UHID_DESTROY:
return parseUhidDestroy();
default: default:
throw new ControlProtocolException("Unknown event type: " + type); throw new ControlProtocolException("Unknown event type: " + type);
} }
@ -73,6 +75,12 @@ public class ControlMessageReader {
return value; return value;
} }
private String parseString(int sizeBytes) throws IOException {
assert sizeBytes > 0 && sizeBytes <= 4;
byte[] data = parseByteArray(sizeBytes);
return new String(data, StandardCharsets.UTF_8);
}
private String parseString() throws IOException { private String parseString() throws IOException {
byte[] data = parseByteArray(4); byte[] data = parseByteArray(4);
return new String(data, StandardCharsets.UTF_8); return new String(data, StandardCharsets.UTF_8);
@ -132,8 +140,9 @@ public class ControlMessageReader {
private ControlMessage parseUhidCreate() throws IOException { private ControlMessage parseUhidCreate() throws IOException {
int id = dis.readUnsignedShort(); int id = dis.readUnsignedShort();
String name = parseString(1);
byte[] data = parseByteArray(2); byte[] data = parseByteArray(2);
return ControlMessage.createUhidCreate(id, data); return ControlMessage.createUhidCreate(id, name, data);
} }
private ControlMessage parseUhidInput() throws IOException { private ControlMessage parseUhidInput() throws IOException {
@ -142,6 +151,11 @@ public class ControlMessageReader {
return ControlMessage.createUhidInput(id, data); return ControlMessage.createUhidInput(id, data);
} }
private ControlMessage parseUhidDestroy() throws IOException {
int id = dis.readUnsignedShort();
return ControlMessage.createUhidDestroy(id);
}
private Position parsePosition() throws IOException { private Position parsePosition() throws IOException {
int x = dis.readInt(); int x = dis.readInt();
int y = dis.readInt(); int y = dis.readInt();

View File

@ -210,11 +210,14 @@ public class Controller implements AsyncProcessor {
device.rotateDevice(); device.rotateDevice();
break; break;
case ControlMessage.TYPE_UHID_CREATE: case ControlMessage.TYPE_UHID_CREATE:
getUhidManager().open(msg.getId(), msg.getData()); getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
break; break;
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;

View File

@ -1,6 +1,7 @@
package com.genymobile.scrcpy.control; package com.genymobile.scrcpy.control;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.StringUtils;
import android.os.Build; import android.os.Build;
import android.os.HandlerThread; import android.os.HandlerThread;
@ -46,7 +47,7 @@ public final class UhidManager {
} }
} }
public void open(int id, byte[] reportDesc) throws IOException { public void open(int id, String name, byte[] reportDesc) throws IOException {
try { try {
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
try { try {
@ -56,7 +57,7 @@ public final class UhidManager {
close(old); close(old);
} }
byte[] req = buildUhidCreate2Req(reportDesc); byte[] req = buildUhidCreate2Req(name, reportDesc);
Os.write(fd, req, 0, req.length); Os.write(fd, req, 0, req.length);
registerUhidListener(id, fd); registerUhidListener(id, fd);
@ -95,6 +96,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
@ -140,7 +147,7 @@ public final class UhidManager {
} }
} }
private static byte[] buildUhidCreate2Req(byte[] reportDesc) { private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
/* /*
* struct uhid_event { * struct uhid_event {
* uint32_t type; * uint32_t type;
@ -165,8 +172,19 @@ public final class UhidManager {
byte[] empty = new byte[256]; byte[] empty = new byte[256];
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder()); ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_CREATE2); buf.putInt(UHID_CREATE2);
buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII));
buf.put(empty, 0, 256 - "scrcpy".length()); final String prefix = "scrcpy: ";
if (name.isEmpty()) {
name = "(no name)";
}
byte[] utf8Name = name.getBytes(StandardCharsets.UTF_8);
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127 - prefix.length());
int nameLen = prefix.length() + len;
assert nameLen <= 127;
buf.put(prefix.getBytes(StandardCharsets.US_ASCII));
buf.put(utf8Name, 0, len);
buf.put(empty, 0, 256 - nameLen);
buf.putShort((short) reportDesc.length); buf.putShort((short) reportDesc.length);
buf.putShort(BUS_VIRTUAL); buf.putShort(BUS_VIRTUAL);
buf.putInt(0); // vendor id buf.putInt(0); // vendor id
@ -199,9 +217,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.
FileDescriptor fd = fds.remove(id);
if (fd != null) {
unregisterUhidListener(fd);
close(fd); close(fd);
} else {
Ln.w("Closing unknown UHID device: " + id);
}
} }
public void closeAll() { public void closeAll() {

View File

@ -324,8 +324,10 @@ public class ControlMessageReaderTest {
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_CREATE); dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
dos.writeShort(42); // id dos.writeShort(42); // id
dos.writeByte(3); // name size
dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
dos.writeShort(data.length); // size dos.writeShort(data.length); // report desc size
dos.write(data); dos.write(data);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@ -335,6 +337,7 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.read(); ControlMessage event = reader.read();
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
Assert.assertEquals(42, event.getId()); Assert.assertEquals(42, event.getId());
Assert.assertEquals("ABC", event.getText());
Assert.assertArrayEquals(data, event.getData()); Assert.assertArrayEquals(data, event.getData());
Assert.assertEquals(-1, bis.read()); // EOS Assert.assertEquals(-1, bis.read()); // EOS
@ -362,6 +365,24 @@ public class ControlMessageReaderTest {
Assert.assertEquals(-1, bis.read()); // EOS Assert.assertEquals(-1, bis.read()); // EOS
} }
@Test
public void testParseUhidDestroy() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_DESTROY);
dos.writeShort(42); // id
byte[] packet = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(packet);
ControlMessageReader reader = new ControlMessageReader(bis);
ControlMessage event = reader.read();
Assert.assertEquals(ControlMessage.TYPE_UHID_DESTROY, event.getType());
Assert.assertEquals(42, event.getId());
Assert.assertEquals(-1, bis.read()); // EOS
}
@Test @Test
public void testParseOpenHardKeyboardSettings() throws IOException { public void testParseOpenHardKeyboardSettings() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();