Compare commits

...

15 Commits

Author SHA1 Message Date
Simon Chan
adb69e1870 Add UHID keyboard support
Use the following command:

    scrcpy --keyboard=uhid

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-02-22 19:12:36 +01:00
Romain Vimont
cbd3289db0 Initialize controller before keyboards
The UHID keyboard initializer will need the controller.
2024-02-22 18:52:58 +01:00
Romain Vimont
56fdc01fd2 Replace unsigned char by uint8_t for buffers
For consistency.
2024-02-22 18:52:58 +01:00
Romain Vimont
d527a5c84c Rename default keyboard implementation to "sdk"
Rename {keyboard,mouse}_inject to {keyboard,mouse}_sdk.

All implementations "inject" key events and mouse events, what differs
is the mechanism. For these implementations, the Android SDK API is used
to inject events.

Note that the input mode enum variants were already renamed
(SC_KEYBOARD_INPUT_MODE_SDK and SC_MOUSE_INPUT_MODE_SDK).
2024-02-22 18:52:58 +01:00
Romain Vimont
ea38a75fc5 Extract mouse HID handling
Split the mouse implementation using AOA and the code handling HID
events, so that HID events can be reused for another protocol (UHID).
2024-02-22 18:52:58 +01:00
Romain Vimont
cdbaaa4398 Extract keyboard HID handling
Split the keyboard implementation using AOA and the code handling HID
events, so that HID events can be reused for another protocol (UHID).
2024-02-22 18:52:58 +01:00
Romain Vimont
569b6e02d5 Extract HID events struct
An event contained several fields:
 - the accessory id
 - the HID event data
 - a field ack_to_wait specific to the AOA implementation.

Extract the HID event part to prepare the factorization of HID event
creation.
2024-02-22 18:52:58 +01:00
Romain Vimont
5583fd8394 Embed HID event data
In the implementation, an HID event is at most 8 bytes. Embed the data
in the HID event structure to avoid allocations and simplify the code.
2024-02-22 18:52:58 +01:00
Romain Vimont
52ee5e676f Rename hid event "buffer" to "data"
This fields contains the HID event data (there is no "bufferization").
2024-02-22 18:52:58 +01:00
Romain Vimont
72792ecdf1 Rename "buffer" to "data"
The variable name is intended to match the parameter name of
libusb_control_transfer().
2024-02-22 18:52:58 +01:00
Romain Vimont
a2ecff3720 Fix HID mouse documentation
The size of a mouse HID event is 4 bytes.
2024-02-22 18:52:58 +01:00
Simon Chan
dae225aa52 Introduce --keyboard and --mouse
Until now, there was two modes for keyboard and mouse:
 - event injection using the Android system API (default)
 - HID/AOA over USB

For this reason, the options were exposed as simple flags:
 - -K or --hid-keyboard to enable physical keyboard simulation (AOA)
 - -M or --hid-mouse to enable physical mouse simulation (AOA)

Replace them by explicit --keyboard and --mouse options, with 3 possible
values:
 - disabled
 - sdk (default)
 - aoa

This will allow to add a new mode (uhid).

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-02-22 18:52:58 +01:00
Romain Vimont
1c1c448e0c Accept disabled keyboard or mouse
The input manager assumed that if a controller was present, then both a
key processor and a mouse processor were present.

Remove this assumption, to support disabling keyboard and mouse
separately. This prepares the introduction of new command line options
--keyboard and --mouse.
2024-02-22 18:52:58 +01:00
Romain Vimont
491644ddde Always pass input manager instance
Some functions in input_manager.c only have access to a sub-object (for
example the controller). For consistency, always pass the whole
input manager instance.

This will allow to add assertions when keyboard and mouse could be
disabled separately.
2024-02-22 18:52:58 +01:00
Romain Vimont
a56f26e3b5 Extract ControlChannel class
This avoids that too many components depend on the whole
DesktopConnection.
2024-02-22 18:52:57 +01:00
47 changed files with 1637 additions and 914 deletions

View File

@ -27,8 +27,8 @@ _scrcpy() {
--force-adb-forward
--forward-all-clicks
-h --help
--keyboard
--kill-adb-on-close
-K --hid-keyboard
--legacy-paste
--list-camera-sizes
--list-cameras
@ -39,6 +39,7 @@ _scrcpy() {
-m --max-size=
-M --hid-mouse
--max-fps=
--mouse=
-n --no-control
-N --no-playback
--no-audio
@ -115,6 +116,14 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--keyboard)
COMPREPLY=($(compgen -W 'disabled sdk aoa uhid' -- "$cur"))
return
;;
--mouse)
COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur"))
return
;;
--orientation|--display-orientation)
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return

View File

@ -34,8 +34,8 @@ arguments=(
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-h,--help}'[Print the help]'
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk aoa uhid)'
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-camera-sizes[List the valid camera capture sizes]'
'--list-cameras[List cameras available on the device]'
@ -45,6 +45,7 @@ arguments=(
{-m,--max-size=}'[Limit both the width and height of the video to value]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
'--max-fps=[Limit the frame rate of screen capture]'
'--mouse[Set the mouse input mode]:mode:(disabled sdk aoa)'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-playback}'[Disable video and audio playback]'
'--no-audio[Disable audio forwarding]'

View File

@ -20,8 +20,8 @@ src = [
'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c',
'src/keyboard_inject.c',
'src/mouse_inject.c',
'src/keyboard_sdk.c',
'src/mouse_sdk.c',
'src/opengl.c',
'src/options.c',
'src/packet_merger.c',
@ -33,6 +33,7 @@ src = [
'src/version.c',
'src/trait/frame_source.c',
'src/trait/packet_source.c',
'src/uhid/keyboard_uhid.c',
'src/util/acksync.c',
'src/util/audiobuf.c',
'src/util/average.c',
@ -87,9 +88,11 @@ endif
usb_support = get_option('usb')
if usb_support
src += [
'src/hid/hid_keyboard.c',
'src/hid/hid_mouse.c',
'src/usb/aoa_hid.c',
'src/usb/hid_keyboard.c',
'src/usb/hid_mouse.c',
'src/usb/keyboard_aoa.c',
'src/usb/mouse_aoa.c',
'src/usb/scrcpy_otg.c',
'src/usb/screen_otg.c',
'src/usb/usb.c',

View File

@ -172,24 +172,27 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO
Print this help.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.BI "\-\-keyboard " mode
Select how to send keyboard inputs to the device.
.TP
.B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2.
Possible values are "disabled", "sdk" and "aoa":
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
- "disabled" does not send keyboard inputs to the device.
- "sdk" uses the Android system API to deliver keyboard events to applications.
- "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB.
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
It may only work over USB.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
For "aoa" and "uhid", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
However, the 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\-\-hid\-mouse\fR.
Also see \fB\-\-mouse\fR.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.TP
.B \-\-legacy\-paste
@ -230,20 +233,25 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited).
.TP
.B \-M, \-\-hid\-mouse
Simulate a physical mouse by using HID over AOAv2.
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
.TP
.BI "\-\-mouse " mode
Select how to send mouse inputs to the device.
Possible values are "disabled", "sdk" and "aoa":
- "disabled" does not send mouse inputs to the device.
- "sdk" uses the Android system API to deliver mouse events to applications.
- "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB.
In "aoa" mode, the computer mouse is captured to control the device directly (relative mouse mode).
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB.
Also see \fB\-\-keyboard\fR.
Also see \fB\-\-hid\-keyboard\fR.
.TP
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
.TP
.B \-n, \-\-no\-control

View File

@ -93,6 +93,8 @@ enum {
OPT_DISPLAY_ORIENTATION,
OPT_RECORD_ORIENTATION,
OPT_ORIENTATION,
OPT_KEYBOARD,
OPT_MOUSE,
};
struct sc_option {
@ -358,27 +360,38 @@ static const struct sc_option options[] = {
.longopt = "help",
.text = "Print this help.",
},
{
.longopt_id = OPT_KEYBOARD,
.longopt = "keyboard",
.argdesc = "mode",
.text = "Select how to send keyboard inputs to the device.\n"
"Possible values are \"disabled\", \"sdk\", \"aoa\" and "
"\"uhid\".\n"
"\"disabled\" does not send keyboard inputs to the device.\n"
"\"sdk\" uses the Android system API to deliver keyboard\n"
"events to applications.\n"
"\"aoa\" simulates a physical HID keyboard using the AOAv2\n"
"protocol. It may only work over USB.\n"
"\"uhid\" simulates a physical HID keyboard using the Linux "
"UHID kernel module on the device."
"For \"aoa\" and \"uhid\", the keyboard layout must be "
"configured (once and for all) on the device, via Settings -> "
"System -> Languages and input -> Physical keyboard. This "
"settings page can be started directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"This option is only available when a HID keyboard is enabled "
"enabled (or a physical keyboard is connected).\n"
"Also see --mouse.",
},
{
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
.longopt = "kill-adb-on-close",
.text = "Kill adb when scrcpy terminates.",
},
{
// deprecated
.shortopt = 'K',
.longopt = "hid-keyboard",
.text = "Simulate a physical keyboard by using HID over AOAv2.\n"
"It provides a better experience for IME users, and allows to "
"generate non-ASCII characters, contrary to the default "
"injection method.\n"
"It may only work over USB.\n"
"The keyboard layout must be configured (once and for all) on "
"the device, via Settings -> System -> Languages and input -> "
"Physical keyboard. This settings page can be started "
"directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.",
},
{
.longopt_id = OPT_LEGACY_PASTE,
@ -432,15 +445,9 @@ static const struct sc_option options[] = {
"Default is 0 (unlimited).",
},
{
// deprecated
.shortopt = 'M',
.longopt = "hid-mouse",
.text = "Simulate a physical mouse by using HID over AOAv2.\n"
"In this mode, the computer mouse is captured to control the "
"device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"It may only work over USB.\n"
"Also see --hid-keyboard.",
},
{
.longopt_id = OPT_MAX_FPS,
@ -449,6 +456,23 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported "
"since Android 10, but may work on earlier versions).",
},
{
.longopt_id = OPT_MOUSE,
.longopt = "mouse",
.argdesc = "mode",
.text = "Select how to send mouse inputs to the device.\n"
"Possible values are \"disabled\", \"sdk\" and \"aoa\".\n"
"\"disabled\" does not send mouse inputs to the device.\n"
"\"sdk\" uses the Android system API to deliver mouse\n"
"events to applications.\n"
"\"aoa\" simulates a physical mouse using the AOAv2 protocol\n"
"It may only work over USB.\n"
"In \"aoa\" mode, the computer mouse is captured to control "
"the device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"Also see --keyboard.",
},
{
.shortopt = 'n',
.longopt = "no-control",
@ -543,10 +567,10 @@ static const struct sc_option options[] = {
"mirroring is disabled.\n"
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
"control of the mouse back to the computer.\n"
"If any of --hid-keyboard or --hid-mouse is set, only enable "
"keyboard or mouse respectively, otherwise enable both.\n"
"Keyboard and mouse may be disabled separately using\n"
"--keyboard=disabled and --mouse=disabled.\n"
"It may only work over USB.\n"
"See --hid-keyboard and --hid-mouse.",
"See --keyboard and --mouse.",
},
{
.shortopt = 'p',
@ -1906,6 +1930,64 @@ parse_camera_fps(const char *s, uint16_t *camera_fps) {
return true;
}
static bool
parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) {
if (!strcmp(optarg, "disabled")) {
*mode = SC_KEYBOARD_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "sdk")) {
*mode = SC_KEYBOARD_INPUT_MODE_SDK;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_KEYBOARD_INPUT_MODE_AOA;
return true;
#else
LOGE("--keyboard=aoa is disabled.");
return false;
#endif
}
if (!strcmp(optarg, "uhid")) {
*mode = SC_KEYBOARD_INPUT_MODE_UHID;
return true;
}
LOGE("Unsupported keyboard: %s (expected disabled, sdk, aoa or uhid)",
optarg);
return false;
}
static bool
parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
if (!strcmp(optarg, "disabled")) {
*mode = SC_MOUSE_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "sdk")) {
*mode = SC_MOUSE_INPUT_MODE_SDK;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_MOUSE_INPUT_MODE_AOA;
return true;
#else
LOGE("--mouse=aoa is disabled.");
return false;
#endif
}
LOGE("Unsupported mouse: %s (expected disabled, sdk or aoa)", optarg);
return false;
}
static bool
parse_time_limit(const char *s, sc_tick *tick) {
long value;
@ -1995,12 +2077,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break;
case 'K':
#ifdef HAVE_USB
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
LOGW("-K/--hid-keyboard is deprecated, use --keyboard=aoa "
"instead.");
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AOA;
break;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
return false;
#endif
case OPT_KEYBOARD:
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
return false;
}
break;
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;
@ -2013,12 +2102,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break;
case 'M':
#ifdef HAVE_USB
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
LOGW("-M/--hid-mouse is deprecated, use --mouse=aoa instead.");
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
break;
#else
LOGE("HID over AOA (-M/--hid-mouse) is disabled.");
return false;
#endif
case OPT_MOUSE:
if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
return false;
}
break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
@ -2465,6 +2560,31 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#endif
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK;
}
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
: SC_MOUSE_INPUT_MODE_SDK;
}
if (otg) {
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
LOGE("In OTG mode, --keyboard only supports aoa or disabled.");
return false;
}
enum sc_mouse_input_mode mmode = opts->mouse_input_mode;
if (mmode != SC_MOUSE_INPUT_MODE_AOA
&& mmode != SC_MOUSE_INPUT_MODE_DISABLED) {
LOGE("In OTG mode, --mouse only supports aoa or disabled.");
return false;
}
}
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled.");
@ -2625,12 +2745,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
# ifdef _WIN32
if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) {
if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA)) {
LOGE("On Windows, it is not possible to open a USB device already open "
"by another process (like adb).");
LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in "
"OTG mode (--otg).");
LOGE("Therefore, --keyboard=aoa and --mouse=aoa may only work in OTG"
"mode (--otg).");
return false;
}
# endif

View File

@ -87,7 +87,7 @@ write_position(uint8_t *buf, const struct sc_position *position) {
// write length (4 bytes) + string (non null-terminated)
static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
write_string(const char *utf8, size_t max_len, uint8_t *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
sc_write32be(buf, len);
memcpy(&buf[4], utf8, len);
@ -95,7 +95,7 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
}
size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
buf[0] = msg->type;
switch (msg->type) {
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
@ -152,6 +152,17 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
sc_write32be(&buf[1], msg->uhid_create.id);
sc_write16be(&buf[5], msg->uhid_create.report_desc_size);
memcpy(&buf[7], msg->uhid_create.report_desc,
msg->uhid_create.report_desc_size);
return 7 + msg->uhid_create.report_desc_size;
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
sc_write32be(&buf[1], msg->uhid_input.id);
buf[5] = msg->uhid_input.size;
memcpy(&buf[6], msg->uhid_input.data, msg->uhid_input.size);
return 6 + msg->uhid_input.size;
default:
LOGW("Unknown message type: %u", (unsigned) msg->type);
return 0;
@ -242,6 +253,13 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
LOG_CMSG("UHID create (report_desc size=%" PRIu16 ")",
msg->uhid_create.report_desc_size);
break;
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
LOG_CMSG("UHID input (size=%" PRIu8 ")", msg->uhid_input.size);
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;

View File

@ -10,6 +10,7 @@
#include "android/input.h"
#include "android/keycodes.h"
#include "coords.h"
#include "hid/hid_event.h"
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
@ -37,6 +38,8 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT,
};
enum sc_screen_power_mode {
@ -92,13 +95,23 @@ struct sc_control_msg {
struct {
enum sc_screen_power_mode mode;
} set_screen_power_mode;
struct {
uint32_t id;
const uint8_t *report_desc; // pointer to static data
uint16_t report_desc_size;
} uhid_create;
struct {
uint32_t id;
uint8_t data[SC_HID_MAX_SIZE];
uint8_t size;
} uhid_input;
};
};
// buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written
size_t
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf);
void
sc_control_msg_log(const struct sc_control_msg *msg);

View File

@ -84,7 +84,7 @@ sc_controller_push_msg(struct sc_controller *controller,
static bool
process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;

View File

@ -8,8 +8,7 @@
#include "util/log.h"
ssize_t
device_msg_deserialize(const unsigned char *buf, size_t len,
struct device_msg *msg) {
device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg) {
if (len < 5) {
// at least type + empty string length
return 0; // not available

View File

@ -30,8 +30,7 @@ struct device_msg {
// return the number of bytes consumed (0 for no msg available, -1 on error)
ssize_t
device_msg_deserialize(const unsigned char *buf, size_t len,
struct device_msg *msg);
device_msg_deserialize(const uint8_t *buf, size_t len, struct device_msg *msg);
void
device_msg_destroy(struct device_msg *msg);

15
app/src/hid/hid_event.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef SC_HID_EVENT_H
#define SC_HID_EVENT_H
#include "common.h"
#include <stdint.h>
#define SC_HID_MAX_SIZE 8
struct sc_hid_event {
uint8_t data[SC_HID_MAX_SIZE];
uint8_t size;
};
#endif

View File

@ -1,40 +1,34 @@
#include "hid_keyboard.h"
#include <assert.h>
#include <string.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast key processor to hid_keyboard */
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor)
#define SC_HID_MOD_NONE 0x00
#define SC_HID_MOD_LEFT_CONTROL (1 << 0)
#define SC_HID_MOD_LEFT_SHIFT (1 << 1)
#define SC_HID_MOD_LEFT_ALT (1 << 2)
#define SC_HID_MOD_LEFT_GUI (1 << 3)
#define SC_HID_MOD_RIGHT_CONTROL (1 << 4)
#define SC_HID_MOD_RIGHT_SHIFT (1 << 5)
#define SC_HID_MOD_RIGHT_ALT (1 << 6)
#define SC_HID_MOD_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_ACCESSORY_ID 1
#define HID_MODIFIER_NONE 0x00
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
#define HID_MODIFIER_LEFT_ALT (1 << 2)
#define HID_MODIFIER_LEFT_GUI (1 << 3)
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_INDEX_MODIFIER 0
#define HID_KEYBOARD_INDEX_KEYS 2
#define SC_HID_KEYBOARD_INDEX_MODS 0
#define SC_HID_KEYBOARD_INDEX_KEYS 2
// USB HID protocol says 6 keys in an event is the requirement for BIOS
// keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy.
#define HID_KEYBOARD_MAX_KEYS 6
#define HID_KEYBOARD_EVENT_SIZE \
(HID_KEYBOARD_INDEX_KEYS + HID_KEYBOARD_MAX_KEYS)
#define SC_HID_KEYBOARD_MAX_KEYS 6
#define SC_HID_KEYBOARD_EVENT_SIZE \
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
#define HID_RESERVED 0x00
#define HID_ERROR_ROLL_OVER 0x01
#define SC_HID_RESERVED 0x00
#define SC_HID_ERROR_ROLL_OVER 0x01
/**
* For HID over AOAv2, only report descriptor is needed.
* For HID, only report descriptor is needed.
*
* The specification is available here:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
@ -53,7 +47,7 @@
*
* (change vid:pid' to your device's vendor ID and product ID).
*/
static const unsigned char keyboard_report_desc[] = {
const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Keyboard)
@ -119,7 +113,7 @@ static const unsigned char keyboard_report_desc[] = {
// Report Size (8)
0x75, 0x08,
// Report Count (6)
0x95, HID_KEYBOARD_MAX_KEYS,
0x95, SC_HID_KEYBOARD_MAX_KEYS,
// Input (Data, Array): Keys
0x81, 0x00,
@ -127,6 +121,9 @@ static const unsigned char keyboard_report_desc[] = {
0xC0
};
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
/**
* A keyboard HID event is 8 bytes long:
*
@ -201,51 +198,50 @@ static const unsigned char keyboard_report_desc[] = {
* +---------------+
*/
static unsigned char
sdl_keymod_to_hid_modifiers(uint16_t mod) {
unsigned char modifiers = HID_MODIFIER_NONE;
static void
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE;
uint8_t *data = hid_event->data;
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
data[1] = SC_HID_RESERVED;
memset(&data[SC_HID_KEYBOARD_INDEX_KEYS], 0, SC_HID_KEYBOARD_MAX_KEYS);
}
static uint16_t
sc_hid_mod_from_sdl_keymod(uint16_t mod) {
uint16_t mods = SC_HID_MOD_NONE;
if (mod & SC_MOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL;
mods |= SC_HID_MOD_LEFT_CONTROL;
}
if (mod & SC_MOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT;
mods |= SC_HID_MOD_LEFT_SHIFT;
}
if (mod & SC_MOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT;
mods |= SC_HID_MOD_LEFT_ALT;
}
if (mod & SC_MOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI;
mods |= SC_HID_MOD_LEFT_GUI;
}
if (mod & SC_MOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
mods |= SC_HID_MOD_RIGHT_CONTROL;
}
if (mod & SC_MOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
mods |= SC_HID_MOD_RIGHT_SHIFT;
}
if (mod & SC_MOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT;
mods |= SC_HID_MOD_RIGHT_ALT;
}
if (mod & SC_MOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI;
mods |= SC_HID_MOD_RIGHT_GUI;
}
return modifiers;
return mods;
}
static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) {
LOG_OOM();
return false;
}
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
buffer[1] = HID_RESERVED;
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
HID_KEYBOARD_EVENT_SIZE);
return true;
void
sc_hid_keyboard_init(struct sc_hid_keyboard *hid) {
memset(hid->keys, false, SC_HID_KEYBOARD_KEYS);
}
static inline bool
@ -253,8 +249,8 @@ scancode_is_modifier(enum sc_scancode scancode) {
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
}
static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
bool
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event,
const struct sc_key_event *event) {
enum sc_scancode scancode = event->scancode;
@ -268,39 +264,37 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
return false;
}
if (!sc_hid_keyboard_event_init(hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
sc_hid_keyboard_event_init(hid_event);
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state);
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false
kb->keys[scancode] = (event->action == SC_ACTION_DOWN);
hid->keys[scancode] = (event->action == SC_ACTION_DOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
hid->keys[scancode] ? "true" : "false");
}
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
uint8_t *keys_data = &hid_event->data[SC_HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
if (kb->keys[i]) {
if (hid->keys[i]) {
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
if (keys_pressed_count >= SC_HID_KEYBOARD_MAX_KEYS) {
// Phantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_MAX_KEYS
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
memset(keys_data, SC_HID_ERROR_ROLL_OVER,
SC_HID_KEYBOARD_MAX_KEYS);
goto end;
}
keys_buffer[keys_pressed_count] = i;
keys_data[keys_pressed_count] = i;
++keys_pressed_count;
}
}
@ -308,124 +302,30 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
event->scancode, modifiers);
return true;
}
static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
}
struct sc_hid_event hid_event;
if (!sc_hid_keyboard_event_init(&hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (mod lock state)");
return false;
}
LOGD("HID keyboard state synchronized");
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
return;
}
struct sc_hid_keyboard *kb = DOWNCAST(kp);
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->mods_state)) {
kb->mod_lock_synchronized = true;
}
}
if (ack_to_wait) {
// Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait until clipboard synchronization is acknowledged
// by the server, otherwise it could paste the old clipboard
// content.
hid_event.ack_to_wait = ack_to_wait;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (key)");
}
}
}
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
keyboard_report_desc,
ARRAY_LEN(keyboard_report_desc));
if (!ok) {
LOGW("Register HID keyboard failed");
return false;
}
// Reset all states
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
kb->mod_lock_synchronized = false;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
};
// Clipboard synchronization is requested over the control socket, while HID
// events are sent over AOA, so it must wait for clipboard synchronization
// to be acknowledged by the device before injecting Ctrl+v.
kb->key_processor.async_paste = true;
kb->key_processor.ops = &ops;
event->scancode, mods);
return true;
}
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID keyboard");
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return;
}
sc_hid_keyboard_event_init(event);
unsigned i = 0;
if (capslock) {
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
}

View File

@ -5,8 +5,8 @@
#include <stdbool.h>
#include "aoa_hid.h"
#include "trait/key_processor.h"
#include "hid/hid_event.h"
#include "input_events.h"
// See "SDL2/SDL_scancode.h".
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
@ -14,6 +14,9 @@
// 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[];
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
/**
* HID keyboard events are sequence-based, every time keyboard state changes
* it sends an array of currently pressed keys, the host is responsible for
@ -27,18 +30,19 @@
* phantom state.
*/
struct sc_hid_keyboard {
struct sc_key_processor key_processor; // key processor trait
struct sc_aoa *aoa;
bool keys[SC_HID_KEYBOARD_KEYS];
bool mod_lock_synchronized;
};
void
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event,
const struct sc_key_event *event);
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
uint16_t mods_state);
#endif

192
app/src/hid/hid_mouse.c Normal file
View File

@ -0,0 +1,192 @@
#include "hid_mouse.h"
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
// 1 byte for wheel motion
#define HID_MOUSE_EVENT_SIZE 4
/**
* Mouse descriptor from the specification:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §4 Generic Desktop Page (0x01) (p26)
*/
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Mouse)
0x09, 0x02,
// Collection (Application)
0xA1, 0x01,
// Usage (Pointer)
0x09, 0x01,
// Collection (Physical)
0xA1, 0x00,
// Usage Page (Buttons)
0x05, 0x09,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Count (5)
0x95, 0x05,
// Report Size (1)
0x75, 0x01,
// Input (Data, Variable, Absolute): 5 buttons bits
0x81, 0x02,
// Report Count (1)
0x95, 0x01,
// Report Size (3)
0x75, 0x03,
// Input (Constant): 3 bits padding
0x81, 0x01,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (X)
0x09, 0x30,
// Usage (Y)
0x09, 0x31,
// Usage (Wheel)
0x09, 0x38,
// Local Minimum (-127)
0x15, 0x81,
// Local Maximum (127)
0x25, 0x7F,
// Report Size (8)
0x75, 0x08,
// Report Count (3)
0x95, 0x03,
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
0x81, 0x06,
// End Collection
0xC0,
// End Collection
0xC0,
};
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
sizeof(SC_HID_MOUSE_REPORT_DESC);
/**
* A mouse HID event is 4 bytes long:
*
* - byte 0: buttons state
* - byte 1: relative x motion (signed byte from -127 to 127)
* - byte 2: relative y motion (signed byte from -127 to 127)
* - byte 3: wheel motion (-1, 0 or 1)
*
* 7 6 5 4 3 2 1 0
* +---------------+
* byte 0: |0 0 0 . . . . .| buttons state
* +---------------+
* ^ ^ ^ ^ ^
* | | | | `- left button
* | | | `--- right button
* | | `----- middle button
* | `------- button 4
* `--------- button 5
*
* +---------------+
* byte 1: |. . . . . . . .| relative x motion
* +---------------+
* byte 2: |. . . . . . . .| relative y motion
* +---------------+
* byte 3: |. . . . . . . .| wheel motion
* +---------------+
*
* As an example, here is the report for a motion of (x=5, y=-4) with left
* button pressed:
*
* +---------------+
* |0 0 0 0 0 0 0 1| left button pressed
* +---------------+
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
* +---------------+
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
* +---------------+
* |0 0 0 0 0 0 0 0| wheel motion
* +---------------+
*/
static void
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
hid_event->size = HID_MOUSE_EVENT_SIZE;
// Leave hid_event->data uninitialized, it will be fully initialized by
// callers
}
static uint8_t
sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
uint8_t c = 0;
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
c |= 1 << 0;
}
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
c |= 1 << 1;
}
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
c |= 1 << 2;
}
if (buttons_state & SC_MOUSE_BUTTON_X1) {
c |= 1 << 3;
}
if (buttons_state & SC_MOUSE_BUTTON_X2) {
c |= 1 << 4;
}
return c;
}
void
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
const struct sc_mouse_motion_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_event->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = CLAMP(event->xrel, -127, 127);
data[2] = CLAMP(event->yrel, -127, 127);
data[3] = 0; // wheel coordinates only used for scrolling
}
void
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
const struct sc_mouse_click_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_event->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = 0; // no x motion
data[2] = 0; // no y motion
data[3] = 0; // wheel coordinates only used for scrolling
}
void
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
const struct sc_mouse_scroll_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_event->data;
data[0] = 0; // buttons state irrelevant (and unknown)
data[1] = 0; // no x motion
data[2] = 0; // no y motion
// In practice, vscroll is always -1, 0 or 1, but in theory other values
// are possible
data[3] = CLAMP(event->vscroll, -127, 127);
// Horizontal scrolling ignored
}

26
app/src/hid/hid_mouse.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#endif
#include "common.h"
#include <stdbool.h>
#include "hid/hid_event.h"
#include "input_events.h"
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
void
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
const struct sc_mouse_motion_event *event);
void
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
const struct sc_mouse_click_event *event);
void
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
const struct sc_mouse_scroll_event *event);

View File

@ -52,8 +52,11 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) {
assert(!params->controller || (params->kp && params->kp->ops));
assert(!params->controller || (params->mp && params->mp->ops));
// A key/mouse processor may not be present if there is no controller
assert((!params->kp && !params->mp) || params->controller);
// A processor must have ops initialized
assert(!params->kp || params->kp->ops);
assert(!params->mp || params->mp->ops);
im->controller = params->controller;
im->fp = params->fp;
@ -87,8 +90,10 @@ sc_input_manager_init(struct sc_input_manager *im,
}
static void
send_keycode(struct sc_controller *controller, enum android_keycode keycode,
send_keycode(struct sc_input_manager *im, enum android_keycode keycode,
enum sc_action action, const char *name) {
assert(im->controller && im->kp);
// send DOWN event
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
@ -99,100 +104,109 @@ send_keycode(struct sc_controller *controller, enum android_keycode keycode,
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
}
}
static inline void
action_home(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, action, "HOME");
action_home(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_HOME, action, "HOME");
}
static inline void
action_back(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, action, "BACK");
action_back(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_BACK, action, "BACK");
}
static inline void
action_app_switch(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
action_app_switch(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
}
static inline void
action_power(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, action, "POWER");
action_power(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_POWER, action, "POWER");
}
static inline void
action_volume_up(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
action_volume_up(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
}
static inline void
action_volume_down(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
action_volume_down(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
}
static inline void
action_menu(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, action, "MENU");
action_menu(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_MENU, action, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void
press_back_or_turn_screen_on(struct sc_controller *controller,
press_back_or_turn_screen_on(struct sc_input_manager *im,
enum sc_action action) {
assert(im->controller && im->kp);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
}
static void
expand_notification_panel(struct sc_controller *controller) {
expand_notification_panel(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'expand notification panel'");
}
}
static void
expand_settings_panel(struct sc_controller *controller) {
expand_settings_panel(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct sc_controller *controller) {
collapse_panels(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'collapse notification panel'");
}
}
static bool
get_device_clipboard(struct sc_controller *controller,
enum sc_copy_key copy_key) {
get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) {
assert(im->controller && im->kp);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
@ -201,8 +215,10 @@ get_device_clipboard(struct sc_controller *controller,
}
static bool
set_device_clipboard(struct sc_controller *controller, bool paste,
set_device_clipboard(struct sc_input_manager *im, bool paste,
uint64_t sequence) {
assert(im->controller && im->kp);
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -222,7 +238,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
free(text_dup);
LOGW("Could not request 'set device clipboard'");
return false;
@ -232,19 +248,23 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
}
static void
set_screen_power_mode(struct sc_controller *controller,
set_screen_power_mode(struct sc_input_manager *im,
enum sc_screen_power_mode mode) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = mode;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
static void
switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
switch_fps_counter_state(struct sc_input_manager *im) {
struct sc_fps_counter *fps_counter = &im->screen->fps_counter;
// the started state can only be written from the current thread, so there
// is no ToCToU issue
if (sc_fps_counter_is_started(fps_counter)) {
@ -256,7 +276,9 @@ switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
}
static void
clipboard_paste(struct sc_controller *controller) {
clipboard_paste(struct sc_input_manager *im) {
assert(im->controller && im->kp);
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@ -278,25 +300,28 @@ clipboard_paste(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
static void
rotate_device(struct sc_controller *controller) {
rotate_device(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request device rotation");
}
}
static void
apply_orientation_transform(struct sc_screen *screen,
apply_orientation_transform(struct sc_input_manager *im,
enum sc_orientation transform) {
struct sc_screen *screen = im->screen;
enum sc_orientation new_orientation =
sc_orientation_apply(screen->orientation, transform);
sc_screen_set_orientation(screen, new_orientation);
@ -364,7 +389,7 @@ static void
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested
struct sc_controller *controller = im->controller;
bool control = im->controller;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@ -390,68 +415,68 @@ sc_input_manager_process_key(struct sc_input_manager *im,
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
if (controller && !shift && !repeat) {
action_home(controller, action);
if (im->kp && !shift && !repeat) {
action_home(im, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (controller && !shift && !repeat) {
action_back(controller, action);
if (im->kp && !shift && !repeat) {
action_back(im, action);
}
return;
case SDLK_s:
if (controller && !shift && !repeat) {
action_app_switch(controller, action);
if (im->kp && !shift && !repeat) {
action_app_switch(im, action);
}
return;
case SDLK_m:
if (controller && !shift && !repeat) {
action_menu(controller, action);
if (im->kp && !shift && !repeat) {
action_menu(im, action);
}
return;
case SDLK_p:
if (controller && !shift && !repeat) {
action_power(controller, action);
if (im->kp && !shift && !repeat) {
action_power(im, action);
}
return;
case SDLK_o:
if (controller && !repeat && down) {
if (control && !repeat && down) {
enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
set_screen_power_mode(im, mode);
}
return;
case SDLK_DOWN:
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
} else if (im->kp) {
// forward repeated events
action_volume_down(controller, action);
action_volume_down(im, action);
}
return;
case SDLK_UP:
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
} else if (im->kp) {
// forward repeated events
action_volume_up(controller, action);
action_volume_up(im, action);
}
return;
case SDLK_LEFT:
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_270);
}
}
@ -459,34 +484,33 @@ sc_input_manager_process_key(struct sc_input_manager *im,
case SDLK_RIGHT:
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_90);
}
}
return;
case SDLK_c:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_COPY);
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_CUT);
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_CUT);
}
return;
case SDLK_v:
if (controller && !repeat && down) {
if (im->kp && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
clipboard_paste(im);
} else {
// store the text in the device clipboard and paste,
// without requesting an acknowledgment
set_device_clipboard(controller, true,
SC_SEQUENCE_INVALID);
set_device_clipboard(im, true, SC_SEQUENCE_INVALID);
}
}
return;
@ -507,23 +531,23 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
case SDLK_i:
if (!shift && !repeat && down) {
switch_fps_counter_state(&im->screen->fps_counter);
switch_fps_counter_state(im);
}
return;
case SDLK_n:
if (controller && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_panels(controller);
collapse_panels(im);
} else if (im->key_repeat == 0) {
expand_notification_panel(controller);
expand_notification_panel(im);
} else {
expand_settings_panel(controller);
expand_settings_panel(im);
}
}
return;
case SDLK_r:
if (controller && !shift && !repeat && down) {
rotate_device(controller);
if (control && !shift && !repeat && down) {
rotate_device(im);
}
return;
}
@ -531,7 +555,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
if (!controller) {
if (!im->kp) {
return;
}
@ -540,7 +564,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
clipboard_paste(im);
return;
}
@ -550,7 +574,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
bool ok = set_device_clipboard(controller, false, sequence);
bool ok = set_device_clipboard(im, false, sequence);
if (!ok) {
LOGW("Clipboard could not be synchronized, Ctrl+v not injected");
return;
@ -652,7 +676,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
static void
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
struct sc_controller *controller = im->controller;
bool control = im->controller;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
@ -661,27 +685,27 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
if (controller) {
if (control) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (event->button == SDL_BUTTON_X1) {
action_app_switch(controller, action);
if (im->kp && event->button == SDL_BUTTON_X1) {
action_app_switch(im, action);
return;
}
if (event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(controller);
expand_notification_panel(im);
} else {
expand_settings_panel(controller);
expand_settings_panel(im);
}
return;
}
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(controller, action);
if (im->kp && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im, action);
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(controller, action);
if (im->kp && event->button == SDL_BUTTON_MIDDLE) {
action_home(im, action);
return;
}
}
@ -704,7 +728,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// otherwise, send the click event to the device
}
if (!controller) {
if (!im->mp) {
return;
}
@ -844,7 +868,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
bool control = im->controller;
switch (event->type) {
case SDL_TEXTINPUT:
if (!control) {
if (!im->kp) {
break;
}
sc_input_manager_process_text_input(im, &event->text);
@ -856,13 +880,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
sc_input_manager_process_key(im, &event->key);
break;
case SDL_MOUSEMOTION:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_wheel(im, &event->wheel);
@ -876,7 +900,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);

View File

@ -1,4 +1,4 @@
#include "keyboard_inject.h"
#include "keyboard_sdk.h"
#include <assert.h>
@ -9,8 +9,8 @@
#include "util/intmap.h"
#include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
/** Downcast key processor to sc_keyboard_sdk */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_sdk, key_processor)
static enum android_keyevent_action
convert_keycode_action(enum sc_action action) {
@ -271,20 +271,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
// is set before injecting Ctrl+v.
(void) ack_to_wait;
struct sc_keyboard_inject *ki = DOWNCAST(kp);
struct sc_keyboard_sdk *kb = DOWNCAST(kp);
if (event->repeat) {
if (!ki->forward_key_repeat) {
if (!kb->forward_key_repeat) {
return;
}
++ki->repeat;
++kb->repeat;
} else {
ki->repeat = 0;
kb->repeat = 0;
}
struct sc_control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!sc_controller_push_msg(ki->controller, &msg)) {
if (convert_input_key(event, &msg, kb->key_inject_mode, kb->repeat)) {
if (!sc_controller_push_msg(kb->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
@ -293,14 +293,14 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const struct sc_text_event *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
struct sc_keyboard_sdk *kb = DOWNCAST(kp);
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
if (kb->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
// Never inject text events
return;
}
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
if (kb->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0');
@ -316,22 +316,22 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
LOGW("Could not strdup input text");
return;
}
if (!sc_controller_push_msg(ki->controller, &msg)) {
if (!sc_controller_push_msg(kb->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
ki->controller = controller;
ki->key_inject_mode = key_inject_mode;
ki->forward_key_repeat = forward_key_repeat;
kb->controller = controller;
kb->key_inject_mode = key_inject_mode;
kb->forward_key_repeat = forward_key_repeat;
ki->repeat = 0;
kb->repeat = 0;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
@ -339,6 +339,6 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
};
// Key injection and clipboard synchronization are serialized
ki->key_processor.async_paste = false;
ki->key_processor.ops = &ops;
kb->key_processor.async_paste = false;
kb->key_processor.ops = &ops;
}

View File

@ -1,5 +1,5 @@
#ifndef SC_KEYBOARD_INJECT_H
#define SC_KEYBOARD_INJECT_H
#ifndef SC_KEYBOARD_SDK_H
#define SC_KEYBOARD_SDK_H
#include "common.h"
@ -9,7 +9,7 @@
#include "options.h"
#include "trait/key_processor.h"
struct sc_keyboard_inject {
struct sc_keyboard_sdk {
struct sc_key_processor key_processor; // key processor trait
struct sc_controller *controller;
@ -23,7 +23,7 @@ struct sc_keyboard_inject {
};
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);

View File

@ -1,4 +1,4 @@
#include "mouse_inject.h"
#include "mouse_sdk.h"
#include <assert.h>
@ -9,8 +9,8 @@
#include "util/intmap.h"
#include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor)
/** Downcast mouse processor to sc_mouse_sdk */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_sdk, mouse_processor)
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
@ -63,7 +63,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
return;
}
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
@ -76,7 +76,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
}
@ -84,7 +84,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
@ -98,7 +98,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
@ -106,7 +106,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
@ -118,7 +118,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
@ -126,7 +126,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
@ -139,15 +139,14 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(m->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller) {
mi->controller = controller;
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) {
m->controller = controller;
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
@ -156,7 +155,7 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi,
.process_touch = sc_mouse_processor_process_touch,
};
mi->mouse_processor.ops = &ops;
m->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
m->mouse_processor.relative_mode = false;
}

View File

@ -1,5 +1,5 @@
#ifndef SC_MOUSE_INJECT_H
#define SC_MOUSE_INJECT_H
#ifndef SC_MOUSE_SDK_H
#define SC_MOUSE_SDK_H
#include "common.h"
@ -9,14 +9,13 @@
#include "screen.h"
#include "trait/mouse_processor.h"
struct sc_mouse_inject {
struct sc_mouse_sdk {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller);
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller);
#endif

View File

@ -21,8 +21,8 @@ const struct scrcpy_options scrcpy_options_default = {
.video_source = SC_VIDEO_SOURCE_DISPLAY,
.audio_source = SC_AUDIO_SOURCE_AUTO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.camera_facing = SC_CAMERA_FACING_ANY,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,

View File

@ -140,13 +140,18 @@ enum sc_lock_video_orientation {
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
SC_KEYBOARD_INPUT_MODE_AUTO,
SC_KEYBOARD_INPUT_MODE_DISABLED,
SC_KEYBOARD_INPUT_MODE_SDK,
SC_KEYBOARD_INPUT_MODE_AOA,
SC_KEYBOARD_INPUT_MODE_UHID,
};
enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_INJECT,
SC_MOUSE_INPUT_MODE_HID,
SC_MOUSE_INPUT_MODE_AUTO,
SC_MOUSE_INPUT_MODE_DISABLED,
SC_MOUSE_INPUT_MODE_SDK,
SC_MOUSE_INPUT_MODE_AOA,
};
enum sc_key_inject_mode {

View File

@ -1,6 +1,7 @@
#include "receiver.h"
#include <assert.h>
#include <stdint.h>
#include <SDL2/SDL_clipboard.h>
#include "device_msg.h"
@ -51,7 +52,7 @@ process_msg(struct sc_receiver *receiver, struct device_msg *msg) {
}
static ssize_t
process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len) {
process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
size_t head = 0;
for (;;) {
struct device_msg msg;
@ -78,7 +79,7 @@ static int
run_receiver(void *data) {
struct sc_receiver *receiver = data;
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
size_t head = 0;
for (;;) {

View File

@ -20,15 +20,16 @@
#include "demuxer.h"
#include "events.h"
#include "file_pusher.h"
#include "keyboard_inject.h"
#include "mouse_inject.h"
#include "keyboard_sdk.h"
#include "mouse_sdk.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "uhid/keyboard_uhid.h"
#ifdef HAVE_USB
# include "usb/aoa_hid.h"
# include "usb/hid_keyboard.h"
# include "usb/hid_mouse.h"
# include "usb/keyboard_aoa.h"
# include "usb/mouse_aoa.h"
# include "usb/usb.h"
#endif
#include "util/acksync.h"
@ -63,15 +64,16 @@ struct scrcpy {
struct sc_acksync acksync;
#endif
union {
struct sc_keyboard_inject keyboard_inject;
struct sc_keyboard_sdk keyboard_sdk;
#ifdef HAVE_USB
struct sc_hid_keyboard keyboard_hid;
struct sc_keyboard_aoa keyboard_aoa;
#endif
struct sc_keyboard_uhid keyboard_uhid;
};
union {
struct sc_mouse_inject mouse_inject;
struct sc_mouse_sdk mouse_sdk;
#ifdef HAVE_USB
struct sc_hid_mouse mouse_hid;
struct sc_mouse_aoa mouse_aoa;
#endif
};
struct sc_timeout timeout;
@ -330,8 +332,8 @@ scrcpy(struct scrcpy_options *options) {
bool audio_demuxer_started = false;
#ifdef HAVE_USB
bool aoa_hid_initialized = false;
bool hid_keyboard_initialized = false;
bool hid_mouse_initialized = false;
bool keyboard_aoa_initialized = false;
bool mouse_aoa_initialized = false;
#endif
bool controller_initialized = false;
bool controller_started = false;
@ -542,12 +544,24 @@ scrcpy(struct scrcpy_options *options) {
struct sc_mouse_processor *mp = NULL;
if (options->control) {
if (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end;
}
controller_initialized = true;
if (!sc_controller_start(&s->controller)) {
goto end;
}
controller_started = true;
controller = &s->controller;
#ifdef HAVE_USB
bool use_hid_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool use_hid_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
if (use_hid_keyboard || use_hid_mouse) {
bool use_keyboard_aoa =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool use_mouse_aoa =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
if (use_keyboard_aoa || use_mouse_aoa) {
bool ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end;
@ -590,25 +604,25 @@ scrcpy(struct scrcpy_options *options) {
goto aoa_hid_end;
}
if (use_hid_keyboard) {
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
hid_keyboard_initialized = true;
kp = &s->keyboard_hid.key_processor;
if (use_keyboard_aoa) {
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
keyboard_aoa_initialized = true;
kp = &s->keyboard_aoa.key_processor;
} else {
LOGE("Could not initialize HID keyboard");
}
}
if (use_hid_mouse) {
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
hid_mouse_initialized = true;
mp = &s->mouse_hid.mouse_processor;
if (use_mouse_aoa) {
if (sc_mouse_aoa_init(&s->mouse_aoa, &s->aoa)) {
mouse_aoa_initialized = true;
mp = &s->mouse_aoa.mouse_processor;
} else {
LOGE("Could not initialized HID mouse");
}
}
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
bool need_aoa = keyboard_aoa_initialized || mouse_aoa_initialized;
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync);
@ -624,58 +638,50 @@ scrcpy(struct scrcpy_options *options) {
aoa_hid_end:
if (!aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
hid_keyboard_initialized = false;
if (keyboard_aoa_initialized) {
sc_keyboard_aoa_destroy(&s->keyboard_aoa);
keyboard_aoa_initialized = false;
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
hid_mouse_initialized = false;
if (mouse_aoa_initialized) {
sc_mouse_aoa_destroy(&s->mouse_aoa);
mouse_aoa_initialized = false;
}
}
if (use_hid_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
if (use_keyboard_aoa && !keyboard_aoa_initialized) {
LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK;
}
if (use_hid_mouse && !hid_mouse_initialized) {
LOGE("Fallback to default mouse injection method "
"(-M/--hid-mouse ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
if (use_mouse_aoa && !mouse_aoa_initialized) {
LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
}
}
#else
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
#endif
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
options->key_inject_mode,
options->forward_key_repeat);
kp = &s->keyboard_inject.key_processor;
kp = &s->keyboard_sdk.key_processor;
} else if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_UHID) {
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
if (!ok) {
goto end;
}
kp = &s->keyboard_uhid.key_processor;
}
// mouse_input_mode may have been reset if HID mode failed
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
mp = &s->mouse_inject.mouse_processor;
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller);
mp = &s->mouse_sdk.mouse_processor;
}
if (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end;
}
controller_initialized = true;
if (!sc_controller_start(&s->controller)) {
goto end;
}
controller_started = true;
controller = &s->controller;
}
// There is a controller if and only if control is enabled
@ -815,11 +821,11 @@ end:
// end-of-stream
#ifdef HAVE_USB
if (aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
if (keyboard_aoa_initialized) {
sc_keyboard_aoa_destroy(&s->keyboard_aoa);
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
if (mouse_aoa_initialized) {
sc_mouse_aoa_destroy(&s->mouse_aoa);
}
sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb);

View File

@ -498,7 +498,7 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
static bool
device_read_info(struct sc_intr *intr, sc_socket device_socket,
struct sc_server_info *info) {
unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH];
uint8_t buf[SC_DEVICE_NAME_FIELD_LENGTH];
ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf));
if (r < SC_DEVICE_NAME_FIELD_LENGTH) {
LOGE("Could not retrieve device information");

View File

@ -0,0 +1,72 @@
#include "keyboard_uhid.h"
#include "util/log.h"
/** Downcast key processor to keyboard_uhid */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_uhid, key_processor)
#define UHID_KEYBOARD_ID 1
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
(void) ack_to_wait;
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
return;
}
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
msg.uhid_input.id = UHID_KEYBOARD_ID;
assert(hid_event.size <= SC_HID_MAX_SIZE);
memcpy(msg.uhid_input.data, hid_event.data, hid_event.size);
msg.uhid_input.size = hid_event.size;
if (!sc_controller_push_msg(kb->controller, &msg)) {
LOGE("Could not send UHID_INPUT message (key)");
}
}
}
bool
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_controller *controller) {
sc_hid_keyboard_init(&kb->hid);
kb->controller = controller;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
};
// Clipboard synchronization is requested over the same control socket, so
// there is no need for a specific synchronization mechanism
kb->key_processor.async_paste = false;
kb->key_processor.ops = &ops;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = UHID_KEYBOARD_ID;
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
if (!sc_controller_push_msg(controller, &msg)) {
LOGE("Could not send UHID_CREATE message (keyboard)");
return false;
}
return true;
}

View File

@ -0,0 +1,23 @@
#ifndef SC_KEYBOARD_UHID_H
#define SC_KEYBOARD_UHID_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "hid/hid_keyboard.h"
#include "trait/key_processor.h"
struct sc_keyboard_uhid {
struct sc_key_processor key_processor; // key processor trait
struct sc_hid_keyboard hid;
struct sc_controller *controller;
};
bool
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_controller *controller);
#endif

View File

@ -14,10 +14,10 @@
#define DEFAULT_TIMEOUT 1000
#define SC_HID_EVENT_QUEUE_MAX 64
#define SC_AOA_EVENT_QUEUE_MAX 64
static void
sc_hid_event_log(const struct sc_hid_event *event) {
sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) {
// HID Event: [00] FF FF FF FF...
assert(event->size);
unsigned buffer_size = event->size * 3 + 1;
@ -27,32 +27,18 @@ sc_hid_event_log(const struct sc_hid_event *event) {
return;
}
for (unsigned i = 0; i < event->size; ++i) {
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
snprintf(buffer + i * 3, 4, " %02x", event->data[i]);
}
LOGV("HID Event: [%d]%s", event->accessory_id, buffer);
LOGV("HID Event: [%d]%s", accessory_id, buffer);
free(buffer);
}
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size) {
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
}
void
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer);
}
bool
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
struct sc_acksync *acksync) {
sc_vecdeque_init(&aoa->queue);
if (!sc_vecdeque_reserve(&aoa->queue, SC_HID_EVENT_QUEUE_MAX)) {
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) {
return false;
}
@ -76,12 +62,7 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
void
sc_aoa_destroy(struct sc_aoa *aoa) {
// Destroy remaining events
while (!sc_vecdeque_is_empty(&aoa->queue)) {
struct sc_hid_event *event = sc_vecdeque_popref(&aoa->queue);
assert(event);
sc_hid_event_destroy(event);
}
sc_vecdeque_destroy(&aoa->queue);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
@ -97,10 +78,10 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
// index (arg1): total length of the HID report descriptor
uint16_t value = accessory_id;
uint16_t index = report_desc_size;
unsigned char *buffer = NULL;
unsigned char *data = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
request, value, index, data, length,
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result));
@ -113,7 +94,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
static bool
sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc,
const uint8_t *report_desc,
uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SET_HID_REPORT_DESC;
@ -130,14 +111,14 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
* See <https://libusb.sourceforge.io/api-1.0/libusb_packetoverflow.html>
*/
// value (arg0): accessory assigned ID for the HID device
// index (arg1): offset of data (buffer) in descriptor
// index (arg1): offset of data in descriptor
uint16_t value = accessory_id;
uint16_t index = 0;
// libusb_control_transfer expects a pointer to non-const
unsigned char *buffer = (unsigned char *) report_desc;
unsigned char *data = (unsigned char *) report_desc;
uint16_t length = report_desc_size;
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
request, value, index, data, length,
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result));
@ -150,7 +131,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size) {
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;
@ -169,18 +150,19 @@ sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
}
static bool
sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
const struct sc_hid_event *event) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SEND_HID_EVENT;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): 0 (unused)
uint16_t value = event->accessory_id;
uint16_t value = accessory_id;
uint16_t index = 0;
unsigned char *buffer = event->buffer;
unsigned char *data = (uint8_t *) event->data; // discard const
uint16_t length = event->size;
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
request, value, index, data, length,
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result));
@ -192,7 +174,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
}
bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, const 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 = ACCESSORY_UNREGISTER_HID;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
@ -200,10 +182,10 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
// index (arg1): 0
uint16_t value = accessory_id;
uint16_t index = 0;
unsigned char *buffer = NULL;
unsigned char *data = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
request, value, index, data, length,
DEFAULT_TIMEOUT);
if (result < 0) {
LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result));
@ -215,16 +197,25 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
}
bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
uint16_t accessory_id,
const struct sc_hid_event *event,
uint64_t ack_to_wait) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_event_log(event);
sc_hid_event_log(accessory_id, event);
}
sc_mutex_lock(&aoa->mutex);
bool full = sc_vecdeque_is_full(&aoa->queue);
if (!full) {
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
sc_vecdeque_push_noresize(&aoa->queue, *event);
struct sc_aoa_event *aoa_event =
sc_vecdeque_push_hole_noresize(&aoa->queue);
aoa_event->hid = *event;
aoa_event->accessory_id = accessory_id;
aoa_event->ack_to_wait = ack_to_wait;
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
}
@ -252,7 +243,7 @@ run_aoa_thread(void *data) {
}
assert(!sc_vecdeque_is_empty(&aoa->queue));
struct sc_hid_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);
@ -271,17 +262,14 @@ run_aoa_thread(void *data) {
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
LOGW("Ack not received after 500ms, discarding HID event");
sc_hid_event_destroy(&event);
continue;
} else if (result == SC_ACKSYNC_WAIT_INTR) {
// stopped
sc_hid_event_destroy(&event);
break;
}
}
bool ok = sc_aoa_send_hid_event(aoa, &event);
sc_hid_event_destroy(&event);
bool ok = sc_aoa_send_hid_event(aoa, event.accessory_id, &event.hid);
if (!ok) {
LOGW("Could not send HID event to USB device");
}

View File

@ -6,28 +6,22 @@
#include <libusb-1.0/libusb.h>
#include "hid/hid_event.h"
#include "usb.h"
#include "util/acksync.h"
#include "util/thread.h"
#include "util/tick.h"
#include "util/vecdeque.h"
struct sc_hid_event {
#define SC_HID_MAX_SIZE 8
struct sc_aoa_event {
struct sc_hid_event hid;
uint16_t accessory_id;
unsigned char *buffer;
uint16_t size;
uint64_t ack_to_wait;
};
// Takes ownership of buffer
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size);
void
sc_hid_event_destroy(struct sc_hid_event *hid_event);
struct sc_hid_event_queue SC_VECDEQUE(struct sc_hid_event);
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
struct sc_aoa {
struct sc_usb *usb;
@ -35,7 +29,7 @@ struct sc_aoa {
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
struct sc_hid_event_queue queue;
struct sc_aoa_event_queue queue;
struct sc_acksync *acksync;
};
@ -57,12 +51,22 @@ sc_aoa_join(struct sc_aoa *aoa);
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size);
const uint8_t *report_desc, uint16_t report_desc_size);
bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event);
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
uint16_t accessory_id,
const struct sc_hid_event *event,
uint64_t ack_to_wait);
static inline bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
const struct sc_hid_event *event) {
return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event,
SC_SEQUENCE_INVALID);
}
#endif

View File

@ -1,267 +0,0 @@
#include "hid_mouse.h"
#include <assert.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast mouse processor to hid_mouse */
#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor)
#define HID_MOUSE_ACCESSORY_ID 2
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position
#define HID_MOUSE_EVENT_SIZE 4
/**
* Mouse descriptor from the specification:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §4 Generic Desktop Page (0x01) (p26)
*/
static const unsigned char mouse_report_desc[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Mouse)
0x09, 0x02,
// Collection (Application)
0xA1, 0x01,
// Usage (Pointer)
0x09, 0x01,
// Collection (Physical)
0xA1, 0x00,
// Usage Page (Buttons)
0x05, 0x09,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Count (5)
0x95, 0x05,
// Report Size (1)
0x75, 0x01,
// Input (Data, Variable, Absolute): 5 buttons bits
0x81, 0x02,
// Report Count (1)
0x95, 0x01,
// Report Size (3)
0x75, 0x03,
// Input (Constant): 3 bits padding
0x81, 0x01,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (X)
0x09, 0x30,
// Usage (Y)
0x09, 0x31,
// Usage (Wheel)
0x09, 0x38,
// Local Minimum (-127)
0x15, 0x81,
// Local Maximum (127)
0x25, 0x7F,
// Report Size (8)
0x75, 0x08,
// Report Count (3)
0x95, 0x03,
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
0x81, 0x06,
// End Collection
0xC0,
// End Collection
0xC0,
};
/**
* A mouse HID event is 3 bytes long:
*
* - byte 0: buttons state
* - byte 1: relative x motion (signed byte from -127 to 127)
* - byte 2: relative y motion (signed byte from -127 to 127)
*
* 7 6 5 4 3 2 1 0
* +---------------+
* byte 0: |0 0 0 . . . . .| buttons state
* +---------------+
* ^ ^ ^ ^ ^
* | | | | `- left button
* | | | `--- right button
* | | `----- middle button
* | `------- button 4
* `--------- button 5
*
* +---------------+
* byte 1: |. . . . . . . .| relative x motion
* +---------------+
* byte 2: |. . . . . . . .| relative y motion
* +---------------+
* byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1)
* +---------------+
*
* As an example, here is the report for a motion of (x=5, y=-4) with left
* button pressed:
*
* +---------------+
* |0 0 0 0 0 0 0 1| left button pressed
* +---------------+
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
* +---------------+
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
* +---------------+
* |0 0 0 0 0 0 0 0| wheel motion
* +---------------+
*/
static bool
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE);
if (!buffer) {
LOG_OOM();
return false;
}
sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer,
HID_MOUSE_EVENT_SIZE);
return true;
}
static unsigned char
buttons_state_to_hid_buttons(uint8_t buttons_state) {
unsigned char c = 0;
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
c |= 1 << 0;
}
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
c |= 1 << 1;
}
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
c |= 1 << 2;
}
if (buttons_state & SC_MOUSE_BUTTON_X1) {
c |= 1 << 3;
}
if (buttons_state & SC_MOUSE_BUTTON_X2) {
c |= 1 << 4;
}
return c;
}
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
buffer[1] = CLAMP(event->xrel, -127, 127);
buffer[2] = CLAMP(event->yrel, -127, 127);
buffer[3] = 0; // wheel coordinates only used for scrolling
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (mouse motion)");
}
}
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
buffer[1] = 0; // no x motion
buffer[2] = 0; // no y motion
buffer[3] = 0; // wheel coordinates only used for scrolling
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (mouse click)");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = 0; // buttons state irrelevant (and unknown)
buffer[1] = 0; // no x motion
buffer[2] = 0; // no y motion
// In practice, vscroll is always -1, 0 or 1, but in theory other values
// are possible
buffer[3] = CLAMP(event->vscroll, -127, 127);
// Horizontal scrolling ignored
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could not request HID event (mouse scroll)");
}
}
bool
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) {
mouse->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc,
ARRAY_LEN(mouse_report_desc));
if (!ok) {
LOGW("Register HID mouse failed");
return false;
}
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
// Touch events not supported (coordinates are not relative)
.process_touch = NULL,
};
mouse->mouse_processor.ops = &ops;
mouse->mouse_processor.relative_mode = true;
return true;
}
void
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID mouse");
}
}

106
app/src/usb/keyboard_aoa.c Normal file
View File

@ -0,0 +1,106 @@
#include "keyboard_aoa.h"
#include <assert.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast key processor to keyboard_aoa */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
#define HID_KEYBOARD_ACCESSORY_ID 1
static bool
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
struct sc_hid_event hid_event;
sc_hid_keyboard_event_from_mods(&hid_event, mods_state);
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mod lock state)");
return false;
}
LOGD("HID keyboard state synchronized");
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
return;
}
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->mods_state)) {
kb->mod_lock_synchronized = true;
}
}
// If ack_to_wait is != SC_SEQUENCE_INVALID, then Ctrl+v is pressed, so
// clipboard synchronization has been requested. Wait until clipboard
// synchronization is acknowledged by the server, otherwise it could
// paste the old clipboard content.
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa,
HID_KEYBOARD_ACCESSORY_ID,
&hid_event,
ack_to_wait)) {
LOGW("Could not request HID event (key)");
}
}
}
bool
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
SC_HID_KEYBOARD_REPORT_DESC,
SC_HID_KEYBOARD_REPORT_DESC_LEN);
if (!ok) {
LOGW("Register HID keyboard failed");
return false;
}
sc_hid_keyboard_init(&kb->hid);
kb->mod_lock_synchronized = false;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
};
// Clipboard synchronization is requested over the control socket, while HID
// events are sent over AOA, so it must wait for clipboard synchronization
// to be acknowledged by the device before injecting Ctrl+v.
kb->key_processor.async_paste = true;
kb->key_processor.ops = &ops;
return true;
}
void
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID keyboard");
}
}

View File

@ -0,0 +1,27 @@
#ifndef SC_KEYBOARD_AOA_H
#define SC_KEYBOARD_AOA_H
#include "common.h"
#include <stdbool.h>
#include "aoa_hid.h"
#include "hid/hid_keyboard.h"
#include "trait/key_processor.h"
struct sc_keyboard_aoa {
struct sc_key_processor key_processor; // key processor trait
struct sc_hid_keyboard hid;
struct sc_aoa *aoa;
bool mod_lock_synchronized;
};
bool
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa);
void
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb);
#endif

89
app/src/usb/mouse_aoa.c Normal file
View File

@ -0,0 +1,89 @@
#include "mouse_aoa.h"
#include <assert.h>
#include "hid/hid_mouse.h"
#include "input_events.h"
#include "util/log.h"
/** Downcast mouse processor to mouse_aoa */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
#define HID_MOUSE_ACCESSORY_ID 2
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_motion(&hid_event, event);
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mouse motion)");
}
}
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_click(&hid_event, event);
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mouse click)");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_scroll(&hid_event, event);
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mouse scroll)");
}
}
bool
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
mouse->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID,
SC_HID_MOUSE_REPORT_DESC,
SC_HID_MOUSE_REPORT_DESC_LEN);
if (!ok) {
LOGW("Register HID mouse failed");
return false;
}
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
// Touch events not supported (coordinates are not relative)
.process_touch = NULL,
};
mouse->mouse_processor.ops = &ops;
mouse->mouse_processor.relative_mode = true;
return true;
}
void
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID mouse");
}
}

View File

@ -1,5 +1,5 @@
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#ifndef SC_MOUSE_AOA_H
#define SC_MOUSE_AOA_H
#include "common.h"
@ -8,16 +8,16 @@
#include "aoa_hid.h"
#include "trait/mouse_processor.h"
struct sc_hid_mouse {
struct sc_mouse_aoa {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_aoa *aoa;
};
bool
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa);
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa);
void
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse);
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse);
#endif

View File

@ -10,8 +10,8 @@
struct scrcpy_otg {
struct sc_usb usb;
struct sc_aoa aoa;
struct sc_hid_keyboard keyboard;
struct sc_hid_mouse mouse;
struct sc_keyboard_aoa keyboard;
struct sc_mouse_aoa mouse;
struct sc_screen_otg screen_otg;
};
@ -73,8 +73,8 @@ scrcpy_otg(struct scrcpy_options *options) {
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
struct sc_hid_keyboard *keyboard = NULL;
struct sc_hid_mouse *mouse = NULL;
struct sc_keyboard_aoa *keyboard = NULL;
struct sc_mouse_aoa *mouse = NULL;
bool usb_device_initialized = false;
bool usb_connected = false;
bool aoa_started = false;
@ -117,10 +117,15 @@ scrcpy_otg(struct scrcpy_options *options) {
}
aoa_initialized = true;
assert(options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
bool enable_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool enable_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
// If neither --hid-keyboard or --hid-mouse is passed, enable both
if (!enable_keyboard && !enable_mouse) {
@ -129,7 +134,7 @@ scrcpy_otg(struct scrcpy_options *options) {
}
if (enable_keyboard) {
ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa);
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
if (!ok) {
goto end;
}
@ -137,7 +142,7 @@ scrcpy_otg(struct scrcpy_options *options) {
}
if (enable_mouse) {
ok = sc_hid_mouse_init(&s->mouse, &s->aoa);
ok = sc_mouse_aoa_init(&s->mouse, &s->aoa);
if (!ok) {
goto end;
}
@ -186,10 +191,10 @@ end:
sc_usb_stop(&s->usb);
if (mouse) {
sc_hid_mouse_destroy(&s->mouse);
sc_mouse_aoa_destroy(&s->mouse);
}
if (keyboard) {
sc_hid_keyboard_destroy(&s->keyboard);
sc_keyboard_aoa_destroy(&s->keyboard);
}
if (aoa_initialized) {

View File

@ -6,12 +6,12 @@
#include <stdbool.h>
#include <SDL2/SDL.h>
#include "hid_keyboard.h"
#include "hid_mouse.h"
#include "keyboard_aoa.h"
#include "mouse_aoa.h"
struct sc_screen_otg {
struct sc_hid_keyboard *keyboard;
struct sc_hid_mouse *mouse;
struct sc_keyboard_aoa *keyboard;
struct sc_mouse_aoa *mouse;
SDL_Window *window;
SDL_Renderer *renderer;
@ -22,8 +22,8 @@ struct sc_screen_otg {
};
struct sc_screen_otg_params {
struct sc_hid_keyboard *keyboard;
struct sc_hid_mouse *mouse;
struct sc_keyboard_aoa *keyboard;
struct sc_mouse_aoa *mouse;
const char *window_title;
bool always_on_top;

View File

@ -1,6 +1,7 @@
#include "common.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "control_msg.h"
@ -16,11 +17,11 @@ static void test_serialize_inject_keycode(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 14);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
0x01, // AKEY_EVENT_ACTION_UP
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
@ -38,11 +39,11 @@ static void test_serialize_inject_text(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 18);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
@ -58,11 +59,11 @@ static void test_serialize_inject_text_long(void) {
text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
uint8_t expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x00;
expected[2] = 0x00;
@ -95,11 +96,11 @@ static void test_serialize_inject_touch_event(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 32);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN
0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id
@ -132,11 +133,11 @@ static void test_serialize_inject_scroll_event(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 21);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920
@ -155,11 +156,11 @@ static void test_serialize_back_or_screen_on(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
0x01, // AKEY_EVENT_ACTION_UP
};
@ -171,11 +172,11 @@ static void test_serialize_expand_notification_panel(void) {
.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -186,11 +187,11 @@ static void test_serialize_expand_settings_panel(void) {
.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -201,11 +202,11 @@ static void test_serialize_collapse_panels(void) {
.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
assert(!memcmp(buf, expected, sizeof(expected)));
@ -219,11 +220,11 @@ static void test_serialize_get_clipboard(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_COPY_KEY_COPY,
};
@ -240,11 +241,11 @@ static void test_serialize_set_clipboard(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 27);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
@ -269,11 +270,11 @@ static void test_serialize_set_clipboard_long(void) {
text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0';
msg.set_clipboard.text = text;
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == SC_CONTROL_MSG_MAX_SIZE);
unsigned char expected[SC_CONTROL_MSG_MAX_SIZE] = {
uint8_t expected[SC_CONTROL_MSG_MAX_SIZE] = {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
@ -296,11 +297,11 @@ static void test_serialize_set_screen_power_mode(void) {
},
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
0x02, // SC_SCREEN_POWER_MODE_NORMAL
};
@ -312,16 +313,63 @@ static void test_serialize_rotate_device(void) {
.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_uhid_create(void) {
const uint8_t report_desc[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
.uhid_create = {
.id = 42,
.report_desc = report_desc,
.report_desc_size = sizeof(report_desc),
},
};
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 18);
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_UHID_CREATE,
0, 0, 0, 42, // id
0, 11, // size
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_uhid_input(void) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_UHID_INPUT,
.uhid_input = {
.id = 42,
.data = {1, 2, 3, 4, 5},
.size = 5,
},
};
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 11);
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_UHID_INPUT,
0, 0, 0, 42, // id
5, // size
1, 2, 3, 4, 5,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
@ -340,5 +388,7 @@ int main(int argc, char *argv[]) {
test_serialize_set_clipboard_long();
test_serialize_set_screen_power_mode();
test_serialize_rotate_device();
test_serialize_uhid_create();
test_serialize_uhid_input();
return 0;
}

View File

@ -1,14 +1,14 @@
#include "common.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "device_msg.h"
#include <stdio.h>
static void test_deserialize_clipboard(void) {
const unsigned char input[] = {
const uint8_t input[] = {
DEVICE_MSG_TYPE_CLIPBOARD,
0x00, 0x00, 0x00, 0x03, // text length
0x41, 0x42, 0x43, // "ABC"
@ -26,7 +26,7 @@ static void test_deserialize_clipboard(void) {
}
static void test_deserialize_clipboard_big(void) {
unsigned char input[DEVICE_MSG_MAX_SIZE];
uint8_t input[DEVICE_MSG_MAX_SIZE];
input[0] = DEVICE_MSG_TYPE_CLIPBOARD;
input[1] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0xff000000u) >> 24;
input[2] = (DEVICE_MSG_TEXT_MAX_LENGTH & 0x00ff0000u) >> 16;
@ -48,7 +48,7 @@ static void test_deserialize_clipboard_big(void) {
}
static void test_deserialize_ack_set_clipboard(void) {
const unsigned char input[] = {
const uint8_t input[] = {
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
};

View File

@ -0,0 +1,33 @@
package com.genymobile.scrcpy;
import android.net.LocalSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public final class ControlChannel {
private final InputStream inputStream;
private final OutputStream outputStream;
private final ControlMessageReader reader = new ControlMessageReader();
private final DeviceMessageWriter writer = new DeviceMessageWriter();
public ControlChannel(LocalSocket controlSocket) throws IOException {
this.inputStream = controlSocket.getInputStream();
this.outputStream = controlSocket.getOutputStream();
}
public ControlMessage recv() throws IOException {
ControlMessage msg = reader.next();
while (msg == null) {
reader.readFrom(inputStream);
msg = reader.next();
}
return msg;
}
public void send(DeviceMessage msg) throws IOException {
writer.writeTo(msg, outputStream);
}
}

View File

@ -17,6 +17,8 @@ public final class ControlMessage {
public static final int TYPE_SET_CLIPBOARD = 9;
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
public static final int TYPE_ROTATE_DEVICE = 11;
public static final int TYPE_UHID_CREATE = 12;
public static final int TYPE_UHID_INPUT = 13;
public static final long SEQUENCE_INVALID = 0;
@ -40,6 +42,8 @@ public final class ControlMessage {
private boolean paste;
private int repeat;
private long sequence;
private int id;
private byte[] data;
private ControlMessage() {
}
@ -123,6 +127,22 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createUhidCreate(int id, byte[] reportDesc) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_UHID_CREATE;
msg.id = id;
msg.data = reportDesc;
return msg;
}
public static ControlMessage createUhidInput(int id, byte[] data) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_UHID_INPUT;
msg.id = id;
msg.data = data;
return msg;
}
public int getType() {
return type;
}
@ -186,4 +206,12 @@ public final class ControlMessage {
public long getSequence() {
return sequence;
}
public int getId() {
return id;
}
public byte[] getData() {
return data;
}
}

View File

@ -15,6 +15,8 @@ public class ControlMessageReader {
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int GET_CLIPBOARD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 6;
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 5;
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
@ -86,6 +88,12 @@ public class ControlMessageReader {
case ControlMessage.TYPE_ROTATE_DEVICE:
msg = ControlMessage.createEmpty(type);
break;
case ControlMessage.TYPE_UHID_CREATE:
msg = parseUhidCreate();
break;
case ControlMessage.TYPE_UHID_INPUT:
msg = parseUhidInput();
break;
default:
Ln.w("Unknown event type: " + type);
msg = null;
@ -110,12 +118,21 @@ public class ControlMessageReader {
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
}
private String parseString() {
if (buffer.remaining() < 4) {
return null;
private int parseBufferLength(int sizeBytes) {
assert sizeBytes > 0 && sizeBytes <= 4;
if (buffer.remaining() < sizeBytes) {
return -1;
}
int len = buffer.getInt();
if (buffer.remaining() < len) {
int value = 0;
for (int i = 0; i < sizeBytes; ++i) {
value = (value << 8) | (buffer.get() & 0xFF);
}
return value;
}
private String parseString() {
int len = parseBufferLength(4);
if (len == -1 || buffer.remaining() < len) {
return null;
}
int position = buffer.position();
@ -124,6 +141,16 @@ public class ControlMessageReader {
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
}
private byte[] parseByteArray(int sizeBytes) {
int len = parseBufferLength(sizeBytes);
if (len == -1 || buffer.remaining() < len) {
return null;
}
byte[] data = new byte[len];
buffer.get(data);
return data;
}
private ControlMessage parseInjectText() {
String text = parseString();
if (text == null) {
@ -193,6 +220,30 @@ public class ControlMessageReader {
return ControlMessage.createSetScreenPowerMode(mode);
}
private ControlMessage parseUhidCreate() {
if (buffer.remaining() < UHID_CREATE_FIXED_PAYLOAD_LENGTH) {
return null;
}
int id = buffer.getInt();
byte[] data = parseByteArray(2);
if (data == null) {
return null;
}
return ControlMessage.createUhidCreate(id, data);
}
private ControlMessage parseUhidInput() {
if (buffer.remaining() < UHID_INPUT_FIXED_PAYLOAD_LENGTH) {
return null;
}
int id = buffer.getInt();
byte[] data = parseByteArray(1);
if (data == null) {
return null;
}
return ControlMessage.createUhidInput(id, data);
}
private static Position readPosition(ByteBuffer buffer) {
int x = buffer.getInt();
int y = buffer.getInt();

View File

@ -26,8 +26,10 @@ public class Controller implements AsyncProcessor {
private Thread thread;
private final UhidManager uhidManager;
private final Device device;
private final DesktopConnection connection;
private final ControlChannel controlChannel;
private final CleanUp cleanUp;
private final DeviceMessageSender sender;
private final boolean clipboardAutosync;
@ -42,14 +44,15 @@ public class Controller implements AsyncProcessor {
private boolean keepPowerModeOff;
public Controller(Device device, DesktopConnection connection, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
this.device = device;
this.connection = connection;
this.controlChannel = controlChannel;
this.cleanUp = cleanUp;
this.clipboardAutosync = clipboardAutosync;
this.powerOn = powerOn;
initPointers();
sender = new DeviceMessageSender(connection);
sender = new DeviceMessageSender(controlChannel);
uhidManager = new UhidManager();
}
private void initPointers() {
@ -95,6 +98,7 @@ public class Controller implements AsyncProcessor {
// this is expected on close
} finally {
Ln.d("Controller stopped");
uhidManager.closeAll();
listener.onTerminated(true);
}
}, "control-recv");
@ -123,7 +127,7 @@ public class Controller implements AsyncProcessor {
}
private void handleEvent() throws IOException {
ControlMessage msg = connection.receiveControlMessage();
ControlMessage msg = controlChannel.recv();
switch (msg.getType()) {
case ControlMessage.TYPE_INJECT_KEYCODE:
if (device.supportsInputEvents()) {
@ -182,6 +186,12 @@ public class Controller implements AsyncProcessor {
case ControlMessage.TYPE_ROTATE_DEVICE:
Device.rotateDevice();
break;
case ControlMessage.TYPE_UHID_CREATE:
uhidManager.open(msg.getId(), msg.getData());
break;
case ControlMessage.TYPE_UHID_INPUT:
uhidManager.write(msg.getId(), msg.getData());
break;
default:
// do nothing
}

View File

@ -7,8 +7,6 @@ import android.net.LocalSocketAddress;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable {
@ -24,25 +22,16 @@ public final class DesktopConnection implements Closeable {
private final FileDescriptor audioFd;
private final LocalSocket controlSocket;
private final InputStream controlInputStream;
private final OutputStream controlOutputStream;
private final ControlMessageReader reader = new ControlMessageReader();
private final DeviceMessageWriter writer = new DeviceMessageWriter();
private final ControlChannel controlChannel;
private DesktopConnection(LocalSocket videoSocket, LocalSocket audioSocket, LocalSocket controlSocket) throws IOException {
this.videoSocket = videoSocket;
this.controlSocket = controlSocket;
this.audioSocket = audioSocket;
if (controlSocket != null) {
controlInputStream = controlSocket.getInputStream();
controlOutputStream = controlSocket.getOutputStream();
} else {
controlInputStream = null;
controlOutputStream = null;
}
this.controlSocket = controlSocket;
videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null;
audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null;
controlChannel = controlSocket != null ? new ControlChannel(controlSocket) : null;
}
private static LocalSocket connect(String abstractName) throws IOException {
@ -179,16 +168,7 @@ public final class DesktopConnection implements Closeable {
return audioFd;
}
public ControlMessage receiveControlMessage() throws IOException {
ControlMessage msg = reader.next();
while (msg == null) {
reader.readFrom(controlInputStream);
msg = reader.next();
}
return msg;
}
public void sendDeviceMessage(DeviceMessage msg) throws IOException {
writer.writeTo(msg, controlOutputStream);
public ControlChannel getControlChannel() {
return controlChannel;
}
}

View File

@ -4,7 +4,7 @@ import java.io.IOException;
public final class DeviceMessageSender {
private final DesktopConnection connection;
private final ControlChannel controlChannel;
private Thread thread;
@ -12,8 +12,8 @@ public final class DeviceMessageSender {
private long ack;
public DeviceMessageSender(DesktopConnection connection) {
this.connection = connection;
public DeviceMessageSender(ControlChannel controlChannel) {
this.controlChannel = controlChannel;
}
public synchronized void pushClipboardText(String text) {
@ -43,11 +43,11 @@ public final class DeviceMessageSender {
if (sequence != DeviceMessage.SEQUENCE_INVALID) {
DeviceMessage event = DeviceMessage.createAckClipboard(sequence);
connection.sendDeviceMessage(event);
controlChannel.send(event);
}
if (text != null) {
DeviceMessage event = DeviceMessage.createClipboard(text);
connection.sendDeviceMessage(event);
controlChannel.send(event);
}
}
}

View File

@ -131,7 +131,8 @@ public final class Server {
}
if (control) {
Controller controller = new Controller(device, connection, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
ControlChannel controlChannel = connection.getControlChannel();
Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
device.setClipboardListener(text -> controller.getSender().pushClipboardText(text));
asyncProcessors.add(controller);
}

View File

@ -0,0 +1,138 @@
package com.genymobile.scrcpy;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
public final class UhidManager {
// Linux: include/uapi/linux/uhid.h
private static final int UHID_CREATE2 = 11;
private static final int UHID_INPUT2 = 12;
// Linux: include/uapi/linux/input.h
private static final short BUS_VIRTUAL = 0x06;
private final ArrayMap<Integer, FileDescriptor> fds = new ArrayMap<>();
public void open(int id, byte[] reportDesc) throws IOException {
try {
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
try {
FileDescriptor old = fds.put(id, fd);
if (old != null) {
Ln.w("Duplicate UHID id: " + id);
close(old);
}
byte[] req = buildUhidCreate2Req(reportDesc);
Os.write(fd, req, 0, req.length);
} catch (Exception e) {
close(fd);
throw e;
}
} catch (ErrnoException e) {
throw new IOException(e);
}
}
public void write(int id, byte[] data) throws IOException {
FileDescriptor fd = fds.get(id);
if (fd == null) {
Ln.w("Unknown UHID id: " + id);
return;
}
try {
byte[] req = buildUhidInput2Req(data);
Os.write(fd, req, 0, req.length);
} catch (ErrnoException e) {
throw new IOException(e);
}
}
private static byte[] buildUhidCreate2Req(byte[] reportDesc) {
/*
* struct uhid_event {
* uint32_t type;
* union {
* // ...
* struct uhid_create2_req {
* uint8_t name[128];
* uint8_t phys[64];
* uint8_t uniq[64];
* uint16_t rd_size;
* uint16_t bus;
* uint32_t vendor;
* uint32_t product;
* uint32_t version;
* uint32_t country;
* uint8_t rd_data[ HID_MAX_DESCRIPTOR_SIZE];
* };
* };
* } __attribute__((__packed__));
*/
byte[] empty = new byte[256];
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_CREATE2);
buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII));
buf.put(empty, 0, 256 - "scrcpy".length());
buf.putShort((short) reportDesc.length);
buf.putShort(BUS_VIRTUAL);
buf.putInt(0); // vendor id
buf.putInt(0); // product id
buf.putInt(0); // version
buf.putInt(0); // country;
buf.put(reportDesc);
return buf.array();
}
private static byte[] buildUhidInput2Req(byte[] data) {
/*
* struct uhid_event {
* uint32_t type;
* union {
* // ...
* struct uhid_input2_req {
* uint16_t size;
* uint8_t data[UHID_DATA_MAX];
* };
* };
* } __attribute__((__packed__));
*/
ByteBuffer buf = ByteBuffer.allocate(6 + data.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_INPUT2);
buf.putShort((short) data.length);
buf.put(data);
return buf.array();
}
public void close(int id) {
FileDescriptor fd = fds.get(id);
assert fd != null;
close(fd);
}
public void closeAll() {
for (FileDescriptor fd : fds.values()) {
close(fd);
}
}
private static void close(FileDescriptor fd) {
try {
Os.close(fd);
} catch (ErrnoException e) {
Ln.e("Failed to close uhid: " + e.getMessage());
}
}
}

View File

@ -322,6 +322,50 @@ public class ControlMessageReaderTest {
Assert.assertEquals(ControlMessage.TYPE_ROTATE_DEVICE, event.getType());
}
@Test
public void testParseUhidCreate() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
dos.writeInt(42); // id
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
dos.writeShort(data.length); // size
dos.write(data);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
Assert.assertEquals(42, event.getId());
Assert.assertArrayEquals(data, event.getData());
}
@Test
public void testParseUhidInput() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_INPUT);
dos.writeInt(42); // id
byte[] data = {1, 2, 3, 4, 5};
dos.writeByte(data.length); // size
dos.write(data);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_UHID_INPUT, event.getType());
Assert.assertEquals(42, event.getId());
Assert.assertArrayEquals(data, event.getData());
}
@Test
public void testMultiEvents() throws IOException {
ControlMessageReader reader = new ControlMessageReader();