From 7b8578af9780bd647b6a4f6aeae40b684866482c Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:17:35 +0800 Subject: [PATCH 1/2] Initial UHID keyboard support --- app/data/bash-completion/scrcpy | 6 +- app/data/zsh-completion/_scrcpy | 2 +- app/meson.build | 4 +- app/src/cli.c | 71 ++++-- app/src/control_msg.c | 22 ++ app/src/control_msg.h | 16 ++ app/src/device_msg.c | 31 ++- app/src/device_msg.h | 6 + app/src/hid/hid_event.c | 18 ++ app/src/hid/hid_event.h | 21 ++ app/src/{usb => hid}/hid_keyboard.c | 25 ++- app/src/{usb => hid}/hid_keyboard.h | 6 +- app/src/hid/uhid.h | 207 ++++++++++++++++++ app/src/hid/uhid_hid.c | 122 +++++++++++ app/src/hid/uhid_hid.h | 16 ++ app/src/options.c | 2 +- app/src/options.h | 5 +- app/src/receiver.c | 4 + app/src/scrcpy.c | 64 ++++-- app/src/trait/hid_interface.h | 32 +++ app/src/usb/aoa_hid.c | 173 ++++++++------- app/src/usb/aoa_hid.h | 28 +-- app/src/usb/hid_mouse.c | 18 +- app/src/usb/hid_mouse.h | 5 +- app/src/usb/scrcpy_otg.c | 32 +-- app/src/usb/screen_otg.h | 2 +- .../com/genymobile/scrcpy/ControlMessage.java | 36 +++ .../scrcpy/ControlMessageReader.java | 54 ++++- .../com/genymobile/scrcpy/Controller.java | 14 ++ .../com/genymobile/scrcpy/DeviceMessage.java | 19 ++ .../scrcpy/DeviceMessageSender.java | 104 ++++----- .../scrcpy/DeviceMessageWriter.java | 6 + .../com/genymobile/scrcpy/UHidManager.java | 98 +++++++++ 33 files changed, 1028 insertions(+), 241 deletions(-) create mode 100644 app/src/hid/hid_event.c create mode 100644 app/src/hid/hid_event.h rename app/src/{usb => hid}/hid_keyboard.c (93%) rename app/src/{usb => hid}/hid_keyboard.h (88%) create mode 100644 app/src/hid/uhid.h create mode 100644 app/src/hid/uhid_hid.c create mode 100644 app/src/hid/uhid_hid.h create mode 100644 app/src/trait/hid_interface.h create mode 100644 server/src/main/java/com/genymobile/scrcpy/UHidManager.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 0c85431077..6e2181e30b 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -28,7 +28,7 @@ _scrcpy() { --forward-all-clicks -h --help --kill-adb-on-close - -K --hid-keyboard + --keyboard-input-mode= --legacy-paste --list-camera-sizes --list-cameras @@ -158,6 +158,10 @@ _scrcpy() { COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur})) return ;; + --keyboard-input-mode) + COMPREPLY=($(compgen -W "disable inject aoa uhid" -- ${cur})) + return + ;; --audio-bit-rate \ |--audio-buffer \ |-b|--video-bit-rate \ diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 3c7ca2178a..7ab35603f0 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -35,7 +35,7 @@ arguments=( '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' '--kill-adb-on-close[Kill adb when scrcpy terminates]' - {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' + '--keyboard-input-mode=[Set the keyboard input mode]:mode:(disable inject aoa uhid)' '--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]' diff --git a/app/meson.build b/app/meson.build index 88e2df9aa1..e00f3a5bd6 100644 --- a/app/meson.build +++ b/app/meson.build @@ -19,6 +19,9 @@ src = [ 'src/file_pusher.c', 'src/fps_counter.c', 'src/frame_buffer.c', + 'src/hid/hid_event.c', + 'src/hid/hid_keyboard.c', + 'src/hid/uhid_hid.c', 'src/input_manager.c', 'src/keyboard_inject.c', 'src/mouse_inject.c', @@ -88,7 +91,6 @@ usb_support = get_option('usb') if usb_support src += [ 'src/usb/aoa_hid.c', - 'src/usb/hid_keyboard.c', 'src/usb/hid_mouse.c', 'src/usb/scrcpy_otg.c', 'src/usb/screen_otg.c', diff --git a/app/src/cli.c b/app/src/cli.c index fd4525f557..239ff95e15 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -93,6 +93,7 @@ enum { OPT_DISPLAY_ORIENTATION, OPT_RECORD_ORIENTATION, OPT_ORIENTATION, + OPT_KEYBOARD_INPUT_MODE, }; struct sc_option { @@ -366,11 +367,18 @@ static const struct sc_option options[] = { { .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" + }, + { + .longopt_id = OPT_KEYBOARD_INPUT_MODE, + .longopt = "keyboard-input-mode", + .argdesc = "value", + .text = "Select how to send keyboard inputs to the device.\n" + "Possible values are \"disable\", \"inject\", \"aoa\" and \"uhid\".\n" + "\"disable\" doesn't send keyboard inputs to device.\n" + "\"inject\" uses Android system API to deliver keyboard events to applications.\n" + "\"aoa\" simulates a physical HID keyboard using AoAv2 protocol. It only works over USB, but doesn't need USB debugging to be on.\n" + "\"uhid\" simulates a physical HID keyboard using Linux's UHID kernel module. It works over both USB and TCP/IP but requires USB debugging.\n" + "Simulating physical keyboards (via \"aoa\" or \"uhid\") works on a lower level, thus provides better compatibilities with IMEs, allowing to producing non-ASCII characters.\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 " @@ -378,7 +386,8 @@ static const struct sc_option options[] = { "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.", + "Default is \"inject\"." + "Also see --otg, --mouse-input-mode and --gamepad-input-mode.", }, { .longopt_id = OPT_LEGACY_PASTE, @@ -440,7 +449,7 @@ static const struct sc_option options[] = { "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.", + "Also see --keyboard-input-mode.", }, { .longopt_id = OPT_MAX_FPS, @@ -543,10 +552,9 @@ 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-input-mode=disable can be used to disable keyboard separately.\n" "It may only work over USB.\n" - "See --hid-keyboard and --hid-mouse.", + "See --keyboard-input-mode and --hid-mouse.", }, { .shortopt = 'p', @@ -1898,6 +1906,37 @@ parse_camera_fps(const char *s, uint16_t *camera_fps) { return true; } +static bool +parse_keyboard_input_mode(const char *optarg, enum sc_keyboard_input_mode *mode) { + if (!strcmp(optarg, "disable")) { + *mode = SC_KEYBOARD_INPUT_MODE_DISABLED; + return true; + } + + if (!strcmp(optarg, "inject")) { + *mode = SC_KEYBOARD_INPUT_MODE_INJECT; + return true; + } + + if (!strcmp(optarg, "aoa")) { +#ifdef HAVE_USB + *mode = SC_KEYBOARD_INPUT_MODE_AOA; + return true; +#else + LOGE("--keyboard-input-mode=aoa is disabled."); + return false; +#endif + } + + if (!strcmp(optarg, "uhid")) { + *mode = SC_KEYBOARD_INPUT_MODE_UHID; + return true; + } + + LOGE("Unsupported keyboard input mode: %s (expected inject, aoa or uhid)", optarg); + return false; +} + static bool parse_time_limit(const char *s, sc_tick *tick) { long value; @@ -1987,12 +2026,18 @@ 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-input-mode=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_INPUT_MODE: + if (!parse_keyboard_input_mode(optarg, &opts->keyboard_input_mode)){ + return false; + } + break; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { return false; @@ -2615,11 +2660,11 @@ 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 + if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { 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 " + LOGE("Therefore, --keyboard-input-mode=aoa and -M/--hid-mouse may only work in " "OTG mode (--otg)."); return false; } diff --git a/app/src/control_msg.c b/app/src/control_msg.c index d4d6c62a45..a5d6764839 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -152,6 +152,15 @@ 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_OPEN: + case SC_CONTROL_MSG_TYPE_UHID_WRITE: + sc_write32be(&buf[1], msg->uhid_open.id); + sc_write32be(&buf[5], msg->uhid_open.size); + memcpy(&buf[9], msg->uhid_open.data, msg->uhid_open.size); + return 9 + msg->uhid_open.size; + case SC_CONTROL_MSG_TYPE_UHID_CLOSE: + sc_write32be(&buf[1], msg->uhid_close.id); + return 5; default: LOGW("Unknown message type: %u", (unsigned) msg->type); return 0; @@ -242,6 +251,15 @@ 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_OPEN: + LOG_CMSG("uhid open id=%" PRIu32 ", data size=%" PRIu32, msg->uhid_open.id, msg->uhid_open.size); + break; + case SC_CONTROL_MSG_TYPE_UHID_WRITE: + LOG_CMSG("uhid write id=%" PRIu32 ", data size=%" PRIu32, msg->uhid_write.id, msg->uhid_write.size); + break; + case SC_CONTROL_MSG_TYPE_UHID_CLOSE: + LOG_CMSG("uhid close id=%" PRIu32, msg->uhid_close.id); + break; default: LOG_CMSG("unknown type: %u", (unsigned) msg->type); break; @@ -257,6 +275,10 @@ sc_control_msg_destroy(struct sc_control_msg *msg) { case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD: free(msg->set_clipboard.text); break; + case SC_CONTROL_MSG_TYPE_UHID_OPEN: + case SC_CONTROL_MSG_TYPE_UHID_WRITE: + free(msg->uhid_open.data); + break; default: // do nothing break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index b90a00b34c..f7eeb52c91 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -37,6 +37,9 @@ 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_OPEN, + SC_CONTROL_MSG_TYPE_UHID_WRITE, + SC_CONTROL_MSG_TYPE_UHID_CLOSE, }; enum sc_screen_power_mode { @@ -92,6 +95,19 @@ struct sc_control_msg { struct { enum sc_screen_power_mode mode; } set_screen_power_mode; + struct { + int32_t id; + uint8_t *data; + uint16_t size; + } uhid_open; + struct { + int32_t id; + uint8_t *data; + uint16_t size; + } uhid_write; + struct { + int32_t id; + } uhid_close; }; }; diff --git a/app/src/device_msg.c b/app/src/device_msg.c index 265c750574..64d2d34027 100644 --- a/app/src/device_msg.c +++ b/app/src/device_msg.c @@ -40,6 +40,25 @@ device_msg_deserialize(const unsigned char *buf, size_t len, msg->ack_clipboard.sequence = sequence; return 9; } + case DEVICE_MSG_TYPE_UHID_DATA: { + uint32_t id = sc_read32be(&buf[1]); + size_t uhid_data_len = sc_read32be(&buf[5]); + if (uhid_data_len > len - 9) { + return 0; // not available + } + uint8_t *uhid_data = malloc(uhid_data_len); + if (!uhid_data) { + LOG_OOM(); + return -1; + } + if (uhid_data_len) { + memcpy(uhid_data, &buf[9], uhid_data_len); + } + msg->uhid_data.id = id; + msg->uhid_data.data = uhid_data; + msg->uhid_data.len = uhid_data_len; + return 9 + uhid_data_len; + } default: LOGW("Unknown device message type: %d", (int) msg->type); return -1; // error, we cannot recover @@ -48,7 +67,15 @@ device_msg_deserialize(const unsigned char *buf, size_t len, void device_msg_destroy(struct device_msg *msg) { - if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { - free(msg->clipboard.text); + switch (msg->type) { + case DEVICE_MSG_TYPE_CLIPBOARD: + free(msg->clipboard.text); + break; + case DEVICE_MSG_TYPE_UHID_DATA: + free(msg->uhid_data.data); + break; + default: + // nothing to do + break; } } diff --git a/app/src/device_msg.h b/app/src/device_msg.h index e8d9fed428..ae8e4140f6 100644 --- a/app/src/device_msg.h +++ b/app/src/device_msg.h @@ -14,6 +14,7 @@ enum device_msg_type { DEVICE_MSG_TYPE_CLIPBOARD, DEVICE_MSG_TYPE_ACK_CLIPBOARD, + DEVICE_MSG_TYPE_UHID_DATA, }; struct device_msg { @@ -25,6 +26,11 @@ struct device_msg { struct { uint64_t sequence; } ack_clipboard; + struct { + uint32_t id; + uint8_t *data; // owned, to be freed by free() + uint32_t len; + } uhid_data; }; }; diff --git a/app/src/hid/hid_event.c b/app/src/hid/hid_event.c new file mode 100644 index 0000000000..18c2069307 --- /dev/null +++ b/app/src/hid/hid_event.c @@ -0,0 +1,18 @@ +#include + +#include "hid_event.h" +#include "util/acksync.h" + +void +sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, + uint8_t *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); +} diff --git a/app/src/hid/hid_event.h b/app/src/hid/hid_event.h new file mode 100644 index 0000000000..a89d14481d --- /dev/null +++ b/app/src/hid/hid_event.h @@ -0,0 +1,21 @@ +#ifndef SC_HID_EVENT_H +#define SC_HID_EVENT_H + +#include + +struct sc_hid_event { + uint16_t accessory_id; + uint8_t *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, + uint8_t *buffer, uint16_t buffer_size); + +void +sc_hid_event_destroy(struct sc_hid_event *hid_event); + +#endif diff --git a/app/src/usb/hid_keyboard.c b/app/src/hid/hid_keyboard.c similarity index 93% rename from app/src/usb/hid_keyboard.c rename to app/src/hid/hid_keyboard.c index e717006ab6..9d5839842c 100644 --- a/app/src/usb/hid_keyboard.c +++ b/app/src/hid/hid_keyboard.c @@ -1,8 +1,11 @@ #include "hid_keyboard.h" #include +#include +#include #include "input_events.h" +#include "hid_event.h" #include "util/log.h" /** Downcast key processor to hid_keyboard */ @@ -339,7 +342,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) { ++i; } - if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { + if (!kb->hid_interface->ops->process_input(kb->hid_interface, &hid_event)) { sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mod lock state)"); return false; @@ -381,7 +384,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp, hid_event.ack_to_wait = ack_to_wait; } - if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { + if (!kb->hid_interface->ops->process_input(kb->hid_interface, &hid_event)) { sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (key)"); } @@ -389,12 +392,14 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } 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)); +sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_hid_interface *hid_interface) { + kb->hid_interface = hid_interface; + hid_interface->data_opaque = kb; + + bool ok = hid_interface->ops->create(hid_interface, HID_KEYBOARD_ACCESSORY_ID, + 0x1234, 0x5678, + keyboard_report_desc, + ARRAY_LEN(keyboard_report_desc)); if (!ok) { LOGW("Register HID keyboard failed"); return false; @@ -415,7 +420,7 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { // 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.async_paste = hid_interface->async_message; kb->key_processor.ops = &ops; return true; @@ -424,7 +429,7 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { 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); + bool ok = kb->hid_interface->ops->destroy(kb->hid_interface, HID_KEYBOARD_ACCESSORY_ID); if (!ok) { LOGW("Could not unregister HID keyboard"); } diff --git a/app/src/usb/hid_keyboard.h b/app/src/hid/hid_keyboard.h similarity index 88% rename from app/src/usb/hid_keyboard.h rename to app/src/hid/hid_keyboard.h index 7173a898de..2340b19c82 100644 --- a/app/src/usb/hid_keyboard.h +++ b/app/src/hid/hid_keyboard.h @@ -5,7 +5,7 @@ #include -#include "aoa_hid.h" +#include "trait/hid_interface.h" #include "trait/key_processor.h" // See "SDL2/SDL_scancode.h". @@ -29,14 +29,14 @@ struct sc_hid_keyboard { struct sc_key_processor key_processor; // key processor trait - struct sc_aoa *aoa; + struct sc_hid_interface *hid_interface; bool keys[SC_HID_KEYBOARD_KEYS]; bool mod_lock_synchronized; }; bool -sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa); +sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_hid_interface *hid_interface); void sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb); diff --git a/app/src/hid/uhid.h b/app/src/hid/uhid.h new file mode 100644 index 0000000000..50247c6a0c --- /dev/null +++ b/app/src/hid/uhid.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +#ifndef __UHID_H_ +#define __UHID_H_ + +/* + * User-space I/O driver support for HID subsystem + * Copyright (c) 2012 David Herrmann + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Public header for user-space communication. We try to keep every structure + * aligned but to be safe we also use __attribute__((__packed__)). Therefore, + * the communication should be ABI compatible even between architectures. + */ + +#include + +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 + +#define HID_MAX_DESCRIPTOR_SIZE 4096 + +enum uhid_event_type { + __UHID_LEGACY_CREATE, + UHID_DESTROY, + UHID_START, + UHID_STOP, + UHID_OPEN, + UHID_CLOSE, + UHID_OUTPUT, + __UHID_LEGACY_OUTPUT_EV, + __UHID_LEGACY_INPUT, + UHID_GET_REPORT, + UHID_GET_REPORT_REPLY, + UHID_CREATE2, + UHID_INPUT2, + UHID_SET_REPORT, + UHID_SET_REPORT_REPLY, +}; + +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__)); + +enum uhid_dev_flag { + UHID_DEV_NUMBERED_FEATURE_REPORTS = (1ULL << 0), + UHID_DEV_NUMBERED_OUTPUT_REPORTS = (1ULL << 1), + UHID_DEV_NUMBERED_INPUT_REPORTS = (1ULL << 2), +}; + +struct uhid_start_req { + uint64_t dev_flags; +}; + +#define UHID_DATA_MAX 4096 + +enum uhid_report_type { + UHID_FEATURE_REPORT, + UHID_OUTPUT_REPORT, + UHID_INPUT_REPORT, +}; + +struct uhid_input2_req { + uint16_t size; + uint8_t data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +struct uhid_output_req { + uint8_t data[UHID_DATA_MAX]; + uint16_t size; + uint8_t rtype; +} __attribute__((__packed__)); + +struct uhid_get_report_req { + uint32_t id; + uint8_t rnum; + uint8_t rtype; +} __attribute__((__packed__)); + +struct uhid_get_report_reply_req { + uint32_t id; + uint16_t err; + uint16_t size; + uint8_t data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +struct uhid_set_report_req { + uint32_t id; + uint8_t rnum; + uint8_t rtype; + uint16_t size; + uint8_t data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +struct uhid_set_report_reply_req { + uint32_t id; + uint16_t err; +} __attribute__((__packed__)); + +/* + * Compat Layer + * All these commands and requests are obsolete. You should avoid using them in + * new code. We support them for backwards-compatibility, but you might not get + * access to new feature in case you use them. + */ + +enum uhid_legacy_event_type { + UHID_CREATE = __UHID_LEGACY_CREATE, + UHID_OUTPUT_EV = __UHID_LEGACY_OUTPUT_EV, + UHID_INPUT = __UHID_LEGACY_INPUT, + UHID_FEATURE = UHID_GET_REPORT, + UHID_FEATURE_ANSWER = UHID_GET_REPORT_REPLY, +}; + +/* Obsolete! Use UHID_CREATE2. */ +struct uhid_create_req { + uint8_t name[128]; + uint8_t phys[64]; + uint8_t uniq[64]; + uint8_t *rd_data; + uint16_t rd_size; + + uint16_t bus; + uint32_t vendor; + uint32_t product; + uint32_t version; + uint32_t country; +} __attribute__((__packed__)); + +/* Obsolete! Use UHID_INPUT2. */ +struct uhid_input_req { + uint8_t data[UHID_DATA_MAX]; + uint16_t size; +} __attribute__((__packed__)); + +/* Obsolete! Kernel uses UHID_OUTPUT exclusively now. */ +struct uhid_output_ev_req { + uint16_t type; + uint16_t code; + int32_t value; +} __attribute__((__packed__)); + +/* Obsolete! Kernel uses ABI compatible UHID_GET_REPORT. */ +struct uhid_feature_req { + uint32_t id; + uint8_t rnum; + uint8_t rtype; +} __attribute__((__packed__)); + +/* Obsolete! Use ABI compatible UHID_GET_REPORT_REPLY. */ +struct uhid_feature_answer_req { + uint32_t id; + uint16_t err; + uint16_t size; + uint8_t data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +/* + * UHID Events + * All UHID events from and to the kernel are encoded as "struct uhid_event". + * The "type" field contains a UHID_* type identifier. All payload depends on + * that type and can be accessed via ev->u.XYZ accordingly. + * If user-space writes short events, they're extended with 0s by the kernel. If + * the kernel writes short events, user-space shall extend them with 0s. + */ + +struct uhid_event { + uint32_t type; + + union { + struct uhid_create_req create; + struct uhid_input_req input; + struct uhid_output_req output; + struct uhid_output_ev_req output_ev; + struct uhid_feature_req feature; + struct uhid_get_report_req get_report; + struct uhid_feature_answer_req feature_answer; + struct uhid_get_report_reply_req get_report_reply; + struct uhid_create2_req create2; + struct uhid_input2_req input2; + struct uhid_set_report_req set_report; + struct uhid_set_report_reply_req set_report_reply; + struct uhid_start_req start; + } u; +} __attribute__((__packed__)); + +#endif /* __UHID_H_ */ diff --git a/app/src/hid/uhid_hid.c b/app/src/hid/uhid_hid.c new file mode 100644 index 0000000000..ce10c7d783 --- /dev/null +++ b/app/src/hid/uhid_hid.c @@ -0,0 +1,122 @@ +#include +#include + +#include "uhid.h" +#include "uhid_hid.h" +#include "hid_event.h" +#include "util/log.h" + +/** Downcast hid interface to sc_uhid */ +#define DOWNCAST(HI) container_of(HI, struct sc_uhid, hid_interface) + +static bool +sc_uhid_create(struct sc_hid_interface *hid_interface, uint16_t id, + uint16_t vendor_id, uint16_t product_id, + const uint8_t* report_desc, + uint16_t report_desc_size) { + assert(report_desc_size <= HID_MAX_DESCRIPTOR_SIZE); + + struct sc_uhid *uhid = DOWNCAST(hid_interface); + + struct uhid_event *ev = malloc(sizeof(struct uhid_event)); + *ev = (struct uhid_event) { + .type = UHID_CREATE2, + .u.create2 = { + .name = "scrcpy", + .phys = "", + .rd_size = report_desc_size, + .bus = BUS_BLUETOOTH, + .vendor = vendor_id, + .product = product_id, + .version = 0, + .country = 0, + }, + }; + + if (!snprintf((char *)ev->u.create2.uniq, sizeof(ev->u.create2.uniq), "scrcpy-%d", id)) { + LOGW("Could not set unique name"); + return false; + } + memcpy(ev->u.create2.rd_data, report_desc, report_desc_size); + + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_UHID_OPEN, + .uhid_open = { + .id = id, + .data = (uint8_t *) ev, + .size = sizeof(*ev), + }, + }; + + if (!sc_controller_push_msg(uhid->controller, &msg)) { + LOGW("Could not send UHID_OPEN message"); + return false; + } + + return true; +} + +static bool +sc_uhid_process_input(struct sc_hid_interface *hid_interface, + const struct sc_hid_event* event) { + assert(event->size <= UHID_DATA_MAX); + + struct sc_uhid *uhid = DOWNCAST(hid_interface); + + struct uhid_event *ev = malloc(sizeof(struct uhid_event)); + *ev = (struct uhid_event) { + .type = UHID_INPUT2, + .u.input2 = { + .size = event->size, + }, + }; + memcpy(ev->u.input2.data, event->buffer, event->size); + + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_UHID_WRITE, + .uhid_write = { + .id = event->accessory_id, + .data = (uint8_t *) ev, + .size = sizeof(*ev), + }, + }; + + if (!sc_controller_push_msg(uhid->controller, &msg)) { + LOGW("Could not send UHID_WRITE message"); + return false; + } + + return true; +} + +static bool +sc_uhid_destroy(struct sc_hid_interface *hid_interface, uint16_t id) { + struct sc_uhid *uhid = DOWNCAST(hid_interface); + + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_UHID_CLOSE, + .uhid_close = { + .id = id, + }, + }; + + if (!sc_controller_push_msg(uhid->controller, &msg)) { + LOGW("Could not send UHID_CLOSE message"); + return false; + } + + return true; +} + +void sc_uhid_init(struct sc_uhid *uhid, struct sc_controller *controller) { + static const struct sc_hid_interface_ops ops = { + .create = sc_uhid_create, + .process_input = sc_uhid_process_input, + .destroy = sc_uhid_destroy, + }; + + uhid->hid_interface.async_message = false; + uhid->hid_interface.ops = &ops; + + uhid->controller = controller; +} diff --git a/app/src/hid/uhid_hid.h b/app/src/hid/uhid_hid.h new file mode 100644 index 0000000000..38e7d73db6 --- /dev/null +++ b/app/src/hid/uhid_hid.h @@ -0,0 +1,16 @@ +#ifndef SC_UHID_H +#define SC_UHID_H + +#include "common.h" +#include "controller.h" +#include "trait/hid_interface.h" + +struct sc_uhid { + struct sc_hid_interface hid_interface; + + struct sc_controller *controller; +}; + +void sc_uhid_init(struct sc_uhid *uhid, struct sc_controller *controller); + +#endif diff --git a/app/src/options.c b/app/src/options.c index a13df585a6..b91e8fa220 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -21,7 +21,7 @@ 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, + .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, .camera_facing = SC_CAMERA_FACING_ANY, .port_range = { diff --git a/app/src/options.h b/app/src/options.h index 11e64fa19e..48de1953be 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -140,8 +140,11 @@ enum sc_lock_video_orientation { }; enum sc_keyboard_input_mode { + SC_KEYBOARD_INPUT_MODE_AUTO, + SC_KEYBOARD_INPUT_MODE_DISABLED, SC_KEYBOARD_INPUT_MODE_INJECT, - SC_KEYBOARD_INPUT_MODE_HID, + SC_KEYBOARD_INPUT_MODE_AOA, + SC_KEYBOARD_INPUT_MODE_UHID, }; enum sc_mouse_input_mode { diff --git a/app/src/receiver.c b/app/src/receiver.c index e715a8e691..0d57351e73 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -47,6 +47,10 @@ process_msg(struct sc_receiver *receiver, struct device_msg *msg) { msg->ack_clipboard.sequence); sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence); break; + case DEVICE_MSG_TYPE_UHID_DATA: + LOGD("UHID data id=%u len=%u", + msg->uhid_data.id, msg->uhid_data.len); + break; } } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d62a5f520f..9225b1a006 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -25,9 +25,10 @@ #include "recorder.h" #include "screen.h" #include "server.h" +#include "hid/hid_keyboard.h" +#include "hid/uhid_hid.h" #ifdef HAVE_USB # include "usb/aoa_hid.h" -# include "usb/hid_keyboard.h" # include "usb/hid_mouse.h" # include "usb/usb.h" #endif @@ -62,11 +63,10 @@ struct scrcpy { // sequence/ack helper to synchronize clipboard and Ctrl+v via HID struct sc_acksync acksync; #endif + struct sc_uhid uhid; union { struct sc_keyboard_inject keyboard_inject; -#ifdef HAVE_USB struct sc_hid_keyboard keyboard_hid; -#endif }; union { struct sc_mouse_inject mouse_inject; @@ -328,10 +328,10 @@ scrcpy(struct scrcpy_options *options) { #endif bool video_demuxer_started = false; bool audio_demuxer_started = false; -#ifdef HAVE_USB - bool aoa_hid_initialized = false; bool hid_keyboard_initialized = false; bool hid_mouse_initialized = false; +#ifdef HAVE_USB + bool aoa_hid_initialized = false; #endif bool controller_initialized = false; bool controller_started = false; @@ -535,7 +535,7 @@ scrcpy(struct scrcpy_options *options) { if (options->control) { #ifdef HAVE_USB bool use_hid_keyboard = - options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; + options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA; bool use_hid_mouse = options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; if (use_hid_keyboard || use_hid_mouse) { @@ -582,7 +582,7 @@ scrcpy(struct scrcpy_options *options) { } if (use_hid_keyboard) { - if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { + if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa.hid_interface)) { hid_keyboard_initialized = true; kp = &s->keyboard_hid.key_processor; } else { @@ -591,7 +591,7 @@ scrcpy(struct scrcpy_options *options) { } if (use_hid_mouse) { - if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) { + if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa.hid_interface)) { hid_mouse_initialized = true; mp = &s->mouse_hid.mouse_processor; } else { @@ -627,7 +627,7 @@ scrcpy(struct scrcpy_options *options) { if (use_hid_keyboard && !hid_keyboard_initialized) { LOGE("Fallback to default keyboard injection method " - "(-K/--hid-keyboard ignored)"); + "(--keyboard-input-mode=aoa ignored)"); options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; } @@ -638,18 +638,10 @@ scrcpy(struct scrcpy_options *options) { } } #else - assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID); + assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA); assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID); #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, - options->key_inject_mode, - options->forward_key_repeat); - kp = &s->keyboard_inject.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); @@ -667,6 +659,30 @@ scrcpy(struct scrcpy_options *options) { } controller_started = true; controller = &s->controller; + + if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_UHID) { + sc_uhid_init(&s->uhid, &s->controller); + } + + switch (options->keyboard_input_mode) { + case SC_KEYBOARD_INPUT_MODE_AUTO: + case SC_KEYBOARD_INPUT_MODE_INJECT: + sc_keyboard_inject_init(&s->keyboard_inject, &s->controller, + options->key_inject_mode, + options->forward_key_repeat); + kp = &s->keyboard_inject.key_processor; + break; + case SC_KEYBOARD_INPUT_MODE_UHID: + sc_hid_keyboard_init(&s->keyboard_hid, &s->uhid.hid_interface); + hid_keyboard_initialized = true; + kp = &s->keyboard_hid.key_processor; + break; + case SC_KEYBOARD_INPUT_MODE_AOA: + // already handled above + case SC_KEYBOARD_INPUT_MODE_DISABLED: + // nothing to do + break; + } } // There is a controller if and only if control is enabled @@ -804,14 +820,14 @@ scrcpy(struct scrcpy_options *options) { // The demuxer is not stopped explicitly, because it will stop by itself on // end-of-stream + if (hid_keyboard_initialized) { + sc_hid_keyboard_destroy(&s->keyboard_hid); + } + if (hid_mouse_initialized) { + sc_hid_mouse_destroy(&s->mouse_hid); + } #ifdef HAVE_USB if (aoa_hid_initialized) { - if (hid_keyboard_initialized) { - sc_hid_keyboard_destroy(&s->keyboard_hid); - } - if (hid_mouse_initialized) { - sc_hid_mouse_destroy(&s->mouse_hid); - } sc_aoa_stop(&s->aoa); sc_usb_stop(&s->usb); } diff --git a/app/src/trait/hid_interface.h b/app/src/trait/hid_interface.h new file mode 100644 index 0000000000..ce3065c70c --- /dev/null +++ b/app/src/trait/hid_interface.h @@ -0,0 +1,32 @@ +#ifndef SC_KEY_HID_INTERFACE_H +#define SC_KEY_HID_INTERFACE_H + +#include "common.h" +#include "hid/hid_event.h" + +struct sc_hid_interface { + void + (*process_output)(const uint8_t *data, size_t len, void *data_opaque); + + void *data_opaque; + + bool async_message; // Whether this HID interface transfers requests using Scrcpy control protocol or not + + const struct sc_hid_interface_ops *ops; +}; + +struct sc_hid_interface_ops { + bool + (*create)(struct sc_hid_interface *hid_interface, uint16_t id, + uint16_t vendor_id, uint16_t product_id, + const uint8_t* report_desc, + uint16_t report_desc_size); + + bool + (*process_input)(struct sc_hid_interface *hid_interface, const struct sc_hid_event* event); + + bool + (*destroy)(struct sc_hid_interface *hid_interface, uint16_t id); +}; + +#endif diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index fb64e57c2b..4e719430eb 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -3,6 +3,7 @@ #include #include +#include "hid/hid_event.h" #include "aoa_hid.h" #include "util/log.h" @@ -16,6 +17,9 @@ #define SC_HID_EVENT_QUEUE_MAX 64 +/** Downcast hid interface to sc_aoa */ +#define DOWNCAST(HI) container_of(HI, struct sc_aoa, hid_interface) + static void sc_hid_event_log(const struct sc_hid_event *event) { // HID Event: [00] FF FF FF FF... @@ -33,60 +37,6 @@ sc_hid_event_log(const struct sc_hid_event *event) { 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)) { - return false; - } - - if (!sc_mutex_init(&aoa->mutex)) { - sc_vecdeque_destroy(&aoa->queue); - return false; - } - - if (!sc_cond_init(&aoa->event_cond)) { - sc_mutex_destroy(&aoa->mutex); - sc_vecdeque_destroy(&aoa->queue); - return false; - } - - aoa->stopped = false; - aoa->acksync = acksync; - aoa->usb = usb; - - return true; -} - -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_cond_destroy(&aoa->event_cond); - sc_mutex_destroy(&aoa->mutex); -} - static bool sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, uint16_t report_desc_size) { @@ -148,9 +98,40 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, return true; } -bool -sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, - const unsigned char *report_desc, uint16_t report_desc_size) { +static bool +sc_aoa_unregister_hid(struct sc_hid_interface *hi, const uint16_t accessory_id) { + struct sc_aoa *aoa = DOWNCAST(hi); + + uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + uint8_t request = ACCESSORY_UNREGISTER_HID; + // + // value (arg0): accessory assigned ID for the HID device + // index (arg1): 0 + uint16_t value = accessory_id; + uint16_t index = 0; + unsigned char *buffer = NULL; + uint16_t length = 0; + int result = libusb_control_transfer(aoa->usb->handle, request_type, + request, value, index, buffer, length, + DEFAULT_TIMEOUT); + if (result < 0) { + LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); + sc_usb_check_disconnected(aoa->usb, result); + return false; + } + + return true; +} + +static bool +sc_aoa_setup_hid(struct sc_hid_interface *hi, uint16_t accessory_id, + uint16_t vendor_id, uint16_t product_id, + const uint8_t *report_desc, uint16_t report_desc_size) { + (void) vendor_id; + (void) product_id; + + struct sc_aoa *aoa = DOWNCAST(hi); + bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size); if (!ok) { return false; @@ -159,7 +140,7 @@ sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc, report_desc_size); if (!ok) { - if (!sc_aoa_unregister_hid(aoa, accessory_id)) { + if (!sc_aoa_unregister_hid(hi, accessory_id)) { LOGW("Could not unregister HID"); } return false; @@ -191,31 +172,10 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { return true; } -bool -sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { - uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; - uint8_t request = ACCESSORY_UNREGISTER_HID; - // - // value (arg0): accessory assigned ID for the HID device - // index (arg1): 0 - uint16_t value = accessory_id; - uint16_t index = 0; - unsigned char *buffer = NULL; - uint16_t length = 0; - int result = libusb_control_transfer(aoa->usb->handle, request_type, - request, value, index, buffer, length, - DEFAULT_TIMEOUT); - if (result < 0) { - LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); - sc_usb_check_disconnected(aoa->usb, result); - return false; - } - - return true; -} +static bool +sc_aoa_push_hid_event(struct sc_hid_interface *hi, const struct sc_hid_event *event) { + struct sc_aoa *aoa = DOWNCAST(hi); -bool -sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { sc_hid_event_log(event); } @@ -289,6 +249,55 @@ run_aoa_thread(void *data) { return 0; } +bool +sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, + struct sc_acksync *acksync) { + static const struct sc_hid_interface_ops ops = { + .create = sc_aoa_setup_hid, + .process_input = sc_aoa_push_hid_event, + .destroy = sc_aoa_unregister_hid, + }; + + aoa->hid_interface.async_message = true; + aoa->hid_interface.ops = &ops; + + sc_vecdeque_init(&aoa->queue); + + if (!sc_vecdeque_reserve(&aoa->queue, SC_HID_EVENT_QUEUE_MAX)) { + return false; + } + + if (!sc_mutex_init(&aoa->mutex)) { + sc_vecdeque_destroy(&aoa->queue); + return false; + } + + if (!sc_cond_init(&aoa->event_cond)) { + sc_mutex_destroy(&aoa->mutex); + sc_vecdeque_destroy(&aoa->queue); + return false; + } + + aoa->stopped = false; + aoa->acksync = acksync; + aoa->usb = usb; + + return true; +} + +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_cond_destroy(&aoa->event_cond); + sc_mutex_destroy(&aoa->mutex); +} + bool sc_aoa_start(struct sc_aoa *aoa) { LOGD("Starting AOA thread"); diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 8803c1d94b..33367cf417 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -7,29 +7,17 @@ #include #include "usb.h" +#include "trait/hid_interface.h" #include "util/acksync.h" #include "util/thread.h" #include "util/tick.h" #include "util/vecdeque.h" -struct sc_hid_event { - 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 { + struct sc_hid_interface hid_interface; + struct sc_usb *usb; sc_thread thread; sc_mutex mutex; @@ -55,14 +43,4 @@ sc_aoa_stop(struct sc_aoa *aoa); void 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); - -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); - #endif diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index bab8994000..80714b06a6 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -179,7 +179,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, 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)) { + if (!mouse->hid_interface->ops->process_input(mouse->hid_interface, &hid_event)) { sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mouse motion)"); } @@ -201,7 +201,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, 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)) { + if (!mouse->hid_interface->ops->process_input(mouse->hid_interface, &hid_event)) { sc_hid_event_destroy(&hid_event); LOGW("Could not request HID event (mouse click)"); } @@ -226,18 +226,19 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, buffer[3] = CLAMP(event->vscroll, -127, 127); // Horizontal scrolling ignored - if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) { + if (!mouse->hid_interface->ops->process_input(mouse->hid_interface, &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; +sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_hid_interface *hid_interface) { + mouse->hid_interface = hid_interface; - bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc, - ARRAY_LEN(mouse_report_desc)); + bool ok = hid_interface->ops->create(hid_interface, HID_MOUSE_ACCESSORY_ID, + 0x1234, 0x5678, + mouse_report_desc, ARRAY_LEN(mouse_report_desc)); if (!ok) { LOGW("Register HID mouse failed"); return false; @@ -260,7 +261,8 @@ sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) { void sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) { - bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID); + bool ok = mouse->hid_interface->ops->destroy(mouse->hid_interface, + HID_MOUSE_ACCESSORY_ID); if (!ok) { LOGW("Could not unregister HID mouse"); } diff --git a/app/src/usb/hid_mouse.h b/app/src/usb/hid_mouse.h index b89f779576..2c65004ed7 100644 --- a/app/src/usb/hid_mouse.h +++ b/app/src/usb/hid_mouse.h @@ -6,16 +6,17 @@ #include #include "aoa_hid.h" +#include "trait/hid_interface.h" #include "trait/mouse_processor.h" struct sc_hid_mouse { struct sc_mouse_processor mouse_processor; // mouse processor trait - struct sc_aoa *aoa; + struct sc_hid_interface *hid_interface; }; bool -sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa); +sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_hid_interface *hid_interface); void sc_hid_mouse_destroy(struct sc_hid_mouse *mouse); diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 6a7fd79b09..e9fb7da62a 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -53,6 +53,23 @@ scrcpy_otg(struct scrcpy_options *options) { static struct scrcpy_otg scrcpy_otg; struct scrcpy_otg *s = &scrcpy_otg; + bool enable_keyboard = true; + switch (options->keyboard_input_mode) { + case SC_KEYBOARD_INPUT_MODE_AUTO: + case SC_KEYBOARD_INPUT_MODE_AOA: + enable_keyboard = true; + break; + case SC_KEYBOARD_INPUT_MODE_DISABLED: + enable_keyboard = false; + break; + default: + LOGE("In --otg mode, --keyboard-input-mode must be either aoa or disable"); + goto end; + } + + bool enable_mouse = + options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; + const char *serial = options->serial; if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { @@ -117,19 +134,8 @@ scrcpy_otg(struct scrcpy_options *options) { } aoa_initialized = true; - bool enable_keyboard = - options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; - bool enable_mouse = - options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; - - // If neither --hid-keyboard or --hid-mouse is passed, enable both - if (!enable_keyboard && !enable_mouse) { - enable_keyboard = true; - enable_mouse = true; - } - if (enable_keyboard) { - ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa); + ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa.hid_interface); if (!ok) { goto end; } @@ -137,7 +143,7 @@ scrcpy_otg(struct scrcpy_options *options) { } if (enable_mouse) { - ok = sc_hid_mouse_init(&s->mouse, &s->aoa); + ok = sc_hid_mouse_init(&s->mouse, &s->aoa.hid_interface); if (!ok) { goto end; } diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h index a0acf40bda..aa9a438e88 100644 --- a/app/src/usb/screen_otg.h +++ b/app/src/usb/screen_otg.h @@ -6,7 +6,7 @@ #include #include -#include "hid_keyboard.h" +#include "hid/hid_keyboard.h" #include "hid_mouse.h" struct sc_screen_otg { diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index e180037432..11486d3b44 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -17,6 +17,9 @@ 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_OPEN = 12; + public static final int TYPE_UHID_WRITE = 13; + public static final int TYPE_UHID_CLOSE = 14; public static final long SEQUENCE_INVALID = 0; @@ -40,6 +43,8 @@ public final class ControlMessage { private boolean paste; private int repeat; private long sequence; + private int id; + private byte[] data; private ControlMessage() { } @@ -123,6 +128,29 @@ public static ControlMessage createEmpty(int type) { return msg; } + public static ControlMessage createUHidOpen(int id, byte[] data) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_OPEN; + msg.id = id; + msg.data = data; + return msg; + } + + public static ControlMessage createUHidWrite(int id, byte[] data) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_WRITE; + msg.id = id; + msg.data = data; + return msg; + } + + public static ControlMessage createUHidClose(int id) { + ControlMessage msg = new ControlMessage(); + msg.type = TYPE_UHID_CLOSE; + msg.id = id; + return msg; + } + public int getType() { return type; } @@ -186,4 +214,12 @@ public int getRepeat() { public long getSequence() { return sequence; } + + public int getId() { + return id; + } + + public byte[] getData() { + return data; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index d95c36d879..6d7fc17fad 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -86,6 +86,15 @@ public ControlMessage next() { case ControlMessage.TYPE_ROTATE_DEVICE: msg = ControlMessage.createEmpty(type); break; + case ControlMessage.TYPE_UHID_OPEN: + msg = parseUHidOpen(); + break; + case ControlMessage.TYPE_UHID_WRITE: + msg = parseUHidWrite(); + break; + case ControlMessage.TYPE_UHID_CLOSE: + msg = parseUHidClose(); + break; default: Ln.w("Unknown event type: " + type); msg = null; @@ -110,12 +119,20 @@ private ControlMessage parseInjectKeycode() { return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState); } - private String parseString() { + private int parseBufferLength(){ if (buffer.remaining() < 4) { - return null; + return -1; } int len = buffer.getInt(); if (buffer.remaining() < len) { + return -1; + } + return len; + } + + private String parseString() { + int len = parseBufferLength(); + if (len == -1) { return null; } int position = buffer.position(); @@ -124,6 +141,16 @@ private String parseString() { return new String(rawBuffer, position, len, StandardCharsets.UTF_8); } + private byte[] parseByteArray() { + int len = parseBufferLength(); + if (len == -1) { + return null; + } + byte[] data = new byte[len]; + buffer.get(data); + return data; + } + private ControlMessage parseInjectText() { String text = parseString(); if (text == null) { @@ -200,4 +227,27 @@ private static Position readPosition(ByteBuffer buffer) { int screenHeight = Binary.toUnsigned(buffer.getShort()); return new Position(x, y, screenWidth, screenHeight); } + + private ControlMessage parseUHidOpen() { + int id = buffer.getInt(); + byte[] data = parseByteArray(); + if (data == null) { + return null; + } + return ControlMessage.createUHidOpen(id, data); + } + + private ControlMessage parseUHidWrite() { + int id = buffer.getInt(); + byte[] data = parseByteArray(); + if (data == null) { + return null; + } + return ControlMessage.createUHidWrite(id, data); + } + + private ControlMessage parseUHidClose() { + int id = buffer.getInt(); + return ControlMessage.createUHidClose(id); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 3b0e903128..a82d63441e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -29,6 +29,7 @@ public class Controller implements AsyncProcessor { private final Device device; private final DesktopConnection connection; private final DeviceMessageSender sender; + private final UHidManager uHidManager; private final boolean clipboardAutosync; private final boolean powerOn; @@ -48,6 +49,7 @@ public Controller(Device device, DesktopConnection connection, boolean clipboard this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(connection); + uHidManager = new UHidManager(sender); } private void initPointers() { @@ -106,6 +108,7 @@ public void stop() { thread.interrupt(); } sender.stop(); + uHidManager.stop(); } @Override @@ -114,6 +117,7 @@ public void join() throws InterruptedException { thread.join(); } sender.join(); + uHidManager.join(); } public DeviceMessageSender getSender() { @@ -176,6 +180,16 @@ private void handleEvent() throws IOException { case ControlMessage.TYPE_ROTATE_DEVICE: Device.rotateDevice(); break; + case ControlMessage.TYPE_UHID_OPEN: + uHidManager.open(msg.getId()); + uHidManager.write(msg.getId(), msg.getData()); + break; + case ControlMessage.TYPE_UHID_WRITE: + uHidManager.write(msg.getId(), msg.getData()); + break; + case ControlMessage.TYPE_UHID_CLOSE: + uHidManager.close(msg.getId()); + break; default: // do nothing } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java index 5b7c4de5b5..7dc3aed505 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessage.java @@ -4,12 +4,15 @@ public final class DeviceMessage { public static final int TYPE_CLIPBOARD = 0; public static final int TYPE_ACK_CLIPBOARD = 1; + public static final int TYPE_UHID_DATA = 2; public static final long SEQUENCE_INVALID = ControlMessage.SEQUENCE_INVALID; private int type; private String text; private long sequence; + private int id; + private byte[] data; private DeviceMessage() { } @@ -28,6 +31,14 @@ public static DeviceMessage createAckClipboard(long sequence) { return event; } + public static DeviceMessage createUHidData(int id, byte[] data) { + DeviceMessage event = new DeviceMessage(); + event.type = TYPE_UHID_DATA; + event.id = id; + event.data = data; + return event; + } + public int getType() { return type; } @@ -39,4 +50,12 @@ public String getText() { public long getSequence() { return sequence; } + + public int getId() { + return id; + } + + public byte[] getData() { + return data; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java index 94e842eefa..6c3ad87177 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageSender.java @@ -1,79 +1,81 @@ package com.genymobile.scrcpy; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; + import java.io.IOException; public final class DeviceMessageSender { + private static final int MSG_CLIPBOARD_TEXT = 0; + private static final int MSG_ACK_CLIPBOARD = 1; + private static final int MSG_UHID_DATA = 2; private final DesktopConnection connection; - private Thread thread; - - private String clipboardText; - - private long ack; + private final HandlerThread handlerThread = new HandlerThread("DeviceMessageSender"); + private DeviceMessageSenderHandler handler; public DeviceMessageSender(DesktopConnection connection) { this.connection = connection; } - public synchronized void pushClipboardText(String text) { - clipboardText = text; - notify(); + public void start() { + handlerThread.start(); + handler = new DeviceMessageSenderHandler(handlerThread.getLooper()); } - public synchronized void pushAckClipboard(long sequence) { - ack = sequence; - notify(); + public void stop() { + handlerThread.quitSafely(); } - private void loop() throws IOException, InterruptedException { - while (!Thread.currentThread().isInterrupted()) { - String text; - long sequence; - synchronized (this) { - while (ack == DeviceMessage.SEQUENCE_INVALID && clipboardText == null) { - wait(); - } - text = clipboardText; - clipboardText = null; + public void join() { + try { + handlerThread.join(); + } catch (InterruptedException e) { + Ln.e("Failed to join DeviceMessageSender thread: ", e); + } + } - sequence = ack; - ack = DeviceMessage.SEQUENCE_INVALID; - } + public void pushClipboardText(String text) { + handler.sendMessage(handler.obtainMessage(MSG_CLIPBOARD_TEXT, text)); + } - if (sequence != DeviceMessage.SEQUENCE_INVALID) { - DeviceMessage event = DeviceMessage.createAckClipboard(sequence); - connection.sendDeviceMessage(event); - } - if (text != null) { - DeviceMessage event = DeviceMessage.createClipboard(text); - connection.sendDeviceMessage(event); - } - } + public void pushAckClipboard(long sequence) { + handler.sendMessage(handler.obtainMessage(MSG_ACK_CLIPBOARD, sequence)); } - public void start() { - thread = new Thread(() -> { - try { - loop(); - } catch (IOException | InterruptedException e) { - // this is expected on close - } finally { - Ln.d("Device message sender stopped"); - } - }, "control-send"); - thread.start(); + public void pushUHidData(int id, byte[] data) { + handler.sendMessage(handler.obtainMessage(MSG_UHID_DATA, id, 0, data)); } - public void stop() { - if (thread != null) { - thread.interrupt(); + class DeviceMessageSenderHandler extends Handler { + public DeviceMessageSenderHandler(Looper looper) { + super(looper); } - } - public void join() throws InterruptedException { - if (thread != null) { - thread.join(); + @Override + public void handleMessage(Message msg) { + try { + DeviceMessage event; + switch (msg.what) { + case MSG_CLIPBOARD_TEXT: + event = DeviceMessage.createAckClipboard((long) msg.obj); + connection.sendDeviceMessage(event); + break; + case MSG_ACK_CLIPBOARD: + event = DeviceMessage.createClipboard((String) msg.obj); + connection.sendDeviceMessage(event); + break; + case MSG_UHID_DATA: + event = DeviceMessage.createUHidData(msg.arg1, (byte[]) msg.obj); + connection.sendDeviceMessage(event); + break; + } + } catch (IOException e) { + Ln.e("Failed to send device message: ", e); + } } } } diff --git a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java index bcd8d20676..09d1149ffa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java +++ b/server/src/main/java/com/genymobile/scrcpy/DeviceMessageWriter.java @@ -29,6 +29,12 @@ public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { buffer.putLong(msg.getSequence()); output.write(rawBuffer, 0, buffer.position()); break; + case DeviceMessage.TYPE_UHID_DATA: + buffer.putInt(msg.getId()); + buffer.putInt(msg.getData().length); + buffer.put(msg.getData()); + output.write(rawBuffer, 0, buffer.position()); + break; default: Ln.w("Unknown device message: " + msg.getType()); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/UHidManager.java b/server/src/main/java/com/genymobile/scrcpy/UHidManager.java new file mode 100644 index 0000000000..ed621903ab --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/UHidManager.java @@ -0,0 +1,98 @@ +package com.genymobile.scrcpy; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.HandlerThread; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.util.Hashtable; + +public class UHidManager { + private final Hashtable mHandles = new Hashtable<>(); + private final HandlerThread thread = new HandlerThread("UHidManager"); + private final MessageQueue queue; + private final DeviceMessageSender sender; + private final ByteBuffer buffer = ByteBuffer.allocateDirect(5 * 1024); + + @TargetApi(Build.VERSION_CODES.M) + public UHidManager(DeviceMessageSender sender) { + thread.start(); + queue = thread.getLooper().getQueue(); + this.sender = sender; + } + + public void stop() { + thread.quit(); + } + + public void join() { + try { + thread.join(); + } catch (InterruptedException e) { + Ln.e("Failed to join UHidManager thread: ", e); + } + } + + @TargetApi(Build.VERSION_CODES.M) + public void open(int id) { + FileDescriptor fd = null; + try { + fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0); + } catch (ErrnoException e) { + Ln.e("Failed to open uhid", e); + return; + } + + mHandles.put(id, fd); + queue.addOnFileDescriptorEventListener(fd, MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT | MessageQueue.OnFileDescriptorEventListener.EVENT_OUTPUT, (fd2, event) -> { + if (event == MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT) { + try { + buffer.rewind(); + Os.read(fd2, buffer); + // create a copy of buffer up to its current position + byte[] data = new byte[buffer.position()]; + buffer.rewind(); + buffer.get(data); + Ln.v("UHidManager: read " + data.length + " bytes from " + id); + sender.pushUHidData(id, data); + } catch (ErrnoException | InterruptedIOException e) { + return 0; + } + } + return MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; + }); + } + + public void write(int id, byte[] data) { + FileDescriptor fd = mHandles.get(id); + if (fd == null) { + return; + } + + try { + Os.write(fd, data, 0, data.length); + } catch (Exception e) { + Ln.e("Failed to write to uhid: " + e.getMessage()); + } + } + + @TargetApi(Build.VERSION_CODES.M) + public void close(int id) { + FileDescriptor fd = mHandles.get(id); + if (fd == null) { + return; + } + queue.removeOnFileDescriptorEventListener(fd); + try { + Os.close(fd); + } catch (Exception e) { + Ln.e("Failed to close uhid: " + e.getMessage()); + } + } +} From ef995aea0c40bdc80a6bfe63e15fdd5c16070c39 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:32:30 +0800 Subject: [PATCH 2/2] Support UHID output reports and sync keyboard modifiers using that --- app/meson.build | 1 + app/src/controller.c | 4 +-- app/src/controller.h | 2 +- app/src/hid/hid_interface.c | 45 +++++++++++++++++++++++++++ app/src/hid/hid_interface.h | 57 +++++++++++++++++++++++++++++++++++ app/src/hid/hid_keyboard.c | 40 ++++++++++++++++++++++-- app/src/hid/hid_keyboard.h | 6 +++- app/src/hid/uhid_hid.c | 39 +++++++++++++++++++++--- app/src/hid/uhid_hid.h | 9 ++++-- app/src/receiver.c | 9 +++++- app/src/receiver.h | 5 ++- app/src/scrcpy.c | 2 +- app/src/trait/hid_interface.h | 32 -------------------- app/src/usb/aoa_hid.c | 11 ++++--- app/src/usb/aoa_hid.h | 2 +- app/src/usb/hid_mouse.c | 5 ++- app/src/usb/hid_mouse.h | 3 +- release.mk | 4 +-- 18 files changed, 219 insertions(+), 57 deletions(-) create mode 100644 app/src/hid/hid_interface.c create mode 100644 app/src/hid/hid_interface.h delete mode 100644 app/src/trait/hid_interface.h diff --git a/app/meson.build b/app/meson.build index e00f3a5bd6..8968d2a70a 100644 --- a/app/meson.build +++ b/app/meson.build @@ -20,6 +20,7 @@ src = [ 'src/fps_counter.c', 'src/frame_buffer.c', 'src/hid/hid_event.c', + 'src/hid/hid_interface.c', 'src/hid/hid_keyboard.c', 'src/hid/uhid_hid.c', 'src/input_manager.c', diff --git a/app/src/controller.c b/app/src/controller.c index 0139e42cc4..57eb587b09 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -8,7 +8,7 @@ bool sc_controller_init(struct sc_controller *controller, sc_socket control_socket, - struct sc_acksync *acksync) { + struct sc_acksync *acksync, struct sc_uhid *uhid) { sc_vecdeque_init(&controller->queue); bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX); @@ -16,7 +16,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket, return false; } - ok = sc_receiver_init(&controller->receiver, control_socket, acksync); + ok = sc_receiver_init(&controller->receiver, control_socket, acksync, uhid); if (!ok) { sc_vecdeque_destroy(&controller->queue); return false; diff --git a/app/src/controller.h b/app/src/controller.h index a044b2bf00..1633665976 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -26,7 +26,7 @@ struct sc_controller { bool sc_controller_init(struct sc_controller *controller, sc_socket control_socket, - struct sc_acksync *acksync); + struct sc_acksync *acksync, struct sc_uhid *uhid); void sc_controller_destroy(struct sc_controller *controller); diff --git a/app/src/hid/hid_interface.c b/app/src/hid/hid_interface.c new file mode 100644 index 0000000000..780e041b77 --- /dev/null +++ b/app/src/hid/hid_interface.c @@ -0,0 +1,45 @@ +#include "hid_interface.h" + +#include + +#define SC_HID_DEVICE_INITIAL_CAPACITY 4 + +void +sc_hid_interface_init(struct sc_hid_interface *hid_interface) { + hid_interface->async_message = false; + hid_interface->support_output = false; + + hid_interface->devices = malloc(sizeof(struct sc_hid_device) * SC_HID_DEVICE_INITIAL_CAPACITY); + hid_interface->devices_count = 0; + hid_interface->devices_capacity = SC_HID_DEVICE_INITIAL_CAPACITY; +} + +void +sc_hid_interface_add_device(struct sc_hid_interface *hid_interface, struct sc_hid_device *device) { + if (hid_interface->devices_count == hid_interface->devices_capacity) { + hid_interface->devices_capacity *= 2; + hid_interface->devices = realloc(hid_interface->devices, sizeof(struct sc_hid_device) * hid_interface->devices_capacity); + } + hid_interface->devices[hid_interface->devices_count++] = device; +} + +struct sc_hid_device* +sc_hid_interface_get_device(struct sc_hid_interface *hid_interface, uint16_t id) { + for (size_t i = 0; i < hid_interface->devices_count; ++i) { + if (hid_interface->devices[i]->id == id) { + return hid_interface->devices[i]; + } + } + return NULL; +} + +void +sc_hid_interface_remove_device(struct sc_hid_interface *hid_interface, int16_t id) { + for (size_t i = 0; i < hid_interface->devices_count; ++i) { + if (hid_interface->devices[i]->id == id) { + // Swap with the last element + hid_interface->devices[i] = hid_interface->devices[--hid_interface->devices_count]; + return; + } + } +} diff --git a/app/src/hid/hid_interface.h b/app/src/hid/hid_interface.h new file mode 100644 index 0000000000..88e3bd6973 --- /dev/null +++ b/app/src/hid/hid_interface.h @@ -0,0 +1,57 @@ +#ifndef SC_KEY_HID_INTERFACE_H +#define SC_KEY_HID_INTERFACE_H + +#include "common.h" + +#include + +#include "hid/hid_event.h" + +struct sc_hid_device { + uint16_t id; + + void + (*process_output)(struct sc_hid_device *hid_device, int16_t report_id, const uint8_t *data, + size_t size); +}; + +struct sc_hid_interface { + // Whether this HID interface transfers requests using Scrcpy control protocol or not + bool async_message; + // Whether this HID interface supports output reports + bool support_output; + + // A dynamic growing array of devices + struct sc_hid_device **devices; + size_t devices_count; + size_t devices_capacity; + + const struct sc_hid_interface_ops *ops; +}; + +struct sc_hid_interface_ops { + bool + (*create)(struct sc_hid_interface *hid_interface, struct sc_hid_device* device, + uint16_t vendor_id, uint16_t product_id, const uint8_t* report_desc, + uint16_t report_desc_size); + + bool + (*process_input)(struct sc_hid_interface *hid_interface, const struct sc_hid_event* event); + + bool + (*destroy)(struct sc_hid_interface *hid_interface, uint16_t id); +}; + +void +sc_hid_interface_init(struct sc_hid_interface *hid_interface); + +void +sc_hid_interface_add_device(struct sc_hid_interface *hid_interface, struct sc_hid_device *device); + +struct sc_hid_device* +sc_hid_interface_get_device(struct sc_hid_interface *hid_interface, uint16_t id); + +void +sc_hid_interface_remove_device(struct sc_hid_interface *hid_interface, int16_t id); + +#endif diff --git a/app/src/hid/hid_keyboard.c b/app/src/hid/hid_keyboard.c index 9d5839842c..858185c2e1 100644 --- a/app/src/hid/hid_keyboard.c +++ b/app/src/hid/hid_keyboard.c @@ -4,12 +4,15 @@ #include #include +#include + #include "input_events.h" #include "hid_event.h" #include "util/log.h" /** Downcast key processor to hid_keyboard */ #define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor) +#define DOWNCAST_HID_DEVICE(HD) container_of(HD, struct sc_hid_keyboard, hid_device) #define HID_KEYBOARD_ACCESSORY_ID 1 @@ -368,7 +371,21 @@ sc_key_processor_process_key(struct sc_key_processor *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) { + if (kb->hid_interface->support_output) { + SDL_Keymod mod = SDL_GetModState(); + enum sc_mod diff = 0; + if ((mod & KMOD_NUM) != (kb->device_mod & SC_MOD_NUM)) { + diff |= SC_MOD_NUM; + } + if ((mod & KMOD_CAPS) != (kb->device_mod & SC_MOD_CAPS)) { + diff |= SC_MOD_CAPS; + } + if (diff) { + if (push_mod_lock_state(kb, diff)) { + kb->device_mod = mod; + } + } + } else 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)) { @@ -391,12 +408,29 @@ sc_key_processor_process_key(struct sc_key_processor *kp, } } +static void +sc_hid_keyboard_process_output(struct sc_hid_device* device, int16_t report_id, const uint8_t *data, size_t size) { + (void) report_id; + (void) size; + + struct sc_hid_keyboard *kb = DOWNCAST_HID_DEVICE(device); + kb->device_mod = 0; + if (data[0] & 0x01) { + kb->device_mod |= SC_MOD_NUM; + } + if (data[0] & 0x02) { + kb->device_mod |= SC_MOD_CAPS; + } +} + bool sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_hid_interface *hid_interface) { kb->hid_interface = hid_interface; - hid_interface->data_opaque = kb; - bool ok = hid_interface->ops->create(hid_interface, HID_KEYBOARD_ACCESSORY_ID, + kb->hid_device.id = HID_KEYBOARD_ACCESSORY_ID; + kb->hid_device.process_output = sc_hid_keyboard_process_output; + + bool ok = hid_interface->ops->create(hid_interface, &kb->hid_device, 0x1234, 0x5678, keyboard_report_desc, ARRAY_LEN(keyboard_report_desc)); diff --git a/app/src/hid/hid_keyboard.h b/app/src/hid/hid_keyboard.h index 2340b19c82..f300b2e3f9 100644 --- a/app/src/hid/hid_keyboard.h +++ b/app/src/hid/hid_keyboard.h @@ -5,7 +5,7 @@ #include -#include "trait/hid_interface.h" +#include "hid/hid_interface.h" #include "trait/key_processor.h" // See "SDL2/SDL_scancode.h". @@ -30,8 +30,12 @@ struct sc_hid_keyboard { struct sc_key_processor key_processor; // key processor trait struct sc_hid_interface *hid_interface; + struct sc_hid_device hid_device; bool keys[SC_HID_KEYBOARD_KEYS]; + // Device keyboard modifiers. Only available when HID interface supports output reports. + enum sc_mod device_mod; + // Best-effort modifier synchronization when HID interface doesn't support output reports. bool mod_lock_synchronized; }; diff --git a/app/src/hid/uhid_hid.c b/app/src/hid/uhid_hid.c index ce10c7d783..6f20fae0c6 100644 --- a/app/src/hid/uhid_hid.c +++ b/app/src/hid/uhid_hid.c @@ -10,7 +10,7 @@ #define DOWNCAST(HI) container_of(HI, struct sc_uhid, hid_interface) static bool -sc_uhid_create(struct sc_hid_interface *hid_interface, uint16_t id, +sc_uhid_create(struct sc_hid_interface *hid_interface, struct sc_hid_device* device, uint16_t vendor_id, uint16_t product_id, const uint8_t* report_desc, uint16_t report_desc_size) { @@ -33,7 +33,7 @@ sc_uhid_create(struct sc_hid_interface *hid_interface, uint16_t id, }, }; - if (!snprintf((char *)ev->u.create2.uniq, sizeof(ev->u.create2.uniq), "scrcpy-%d", id)) { + if (!snprintf((char *)ev->u.create2.uniq, sizeof(ev->u.create2.uniq), "scrcpy-%d", device->id)) { LOGW("Could not set unique name"); return false; } @@ -42,7 +42,7 @@ sc_uhid_create(struct sc_hid_interface *hid_interface, uint16_t id, struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_UHID_OPEN, .uhid_open = { - .id = id, + .id = device->id, .data = (uint8_t *) ev, .size = sizeof(*ev), }, @@ -53,6 +53,8 @@ sc_uhid_create(struct sc_hid_interface *hid_interface, uint16_t id, return false; } + sc_hid_interface_add_device(hid_interface, device); + return true; } @@ -105,18 +107,47 @@ sc_uhid_destroy(struct sc_hid_interface *hid_interface, uint16_t id) { return false; } + sc_hid_interface_remove_device(hid_interface, id); + return true; } -void sc_uhid_init(struct sc_uhid *uhid, struct sc_controller *controller) { +void +sc_uhid_init(struct sc_uhid *uhid, struct sc_controller *controller) { static const struct sc_hid_interface_ops ops = { .create = sc_uhid_create, .process_input = sc_uhid_process_input, .destroy = sc_uhid_destroy, }; + sc_hid_interface_init(&uhid->hid_interface); uhid->hid_interface.async_message = false; + uhid->hid_interface.support_output = true; uhid->hid_interface.ops = &ops; uhid->controller = controller; } + +void +sc_uhid_process_output(struct sc_uhid *uhid, uint32_t id, const uint8_t *data, uint32_t size) { + (void) size; + + struct uhid_event *ev = (struct uhid_event *) data; + if (ev->type != UHID_OUTPUT) { + // Ignore other events + return; + } + + struct sc_hid_device *device = sc_hid_interface_get_device(&uhid->hid_interface, id); + if (!device) { + LOGW("Could not find device with id=%d", id); + return; + } + + if (!device->process_output) { + // Device ignores output reports. It's not an error. + return; + } + + device->process_output(device, ev->u.output.rtype, ev->u.output.data, ev->u.output.size); +} diff --git a/app/src/hid/uhid_hid.h b/app/src/hid/uhid_hid.h index 38e7d73db6..995cb31c7b 100644 --- a/app/src/hid/uhid_hid.h +++ b/app/src/hid/uhid_hid.h @@ -3,7 +3,8 @@ #include "common.h" #include "controller.h" -#include "trait/hid_interface.h" +#include "device_msg.h" +#include "hid/hid_interface.h" struct sc_uhid { struct sc_hid_interface hid_interface; @@ -11,6 +12,10 @@ struct sc_uhid { struct sc_controller *controller; }; -void sc_uhid_init(struct sc_uhid *uhid, struct sc_controller *controller); +void +sc_uhid_init(struct sc_uhid *uhid, struct sc_controller *controller); + +void +sc_uhid_process_output(struct sc_uhid *uhid, uint32_t id, const uint8_t *data, uint32_t size); #endif diff --git a/app/src/receiver.c b/app/src/receiver.c index 0d57351e73..f2bc9475b9 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -4,11 +4,12 @@ #include #include "device_msg.h" +#include "hid/uhid_hid.h" #include "util/log.h" bool sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, - struct sc_acksync *acksync) { + struct sc_acksync *acksync, struct sc_uhid *uhid) { bool ok = sc_mutex_init(&receiver->mutex); if (!ok) { return false; @@ -16,6 +17,7 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, receiver->control_socket = control_socket; receiver->acksync = acksync; + receiver->uhid = uhid; return true; } @@ -50,6 +52,11 @@ process_msg(struct sc_receiver *receiver, struct device_msg *msg) { case DEVICE_MSG_TYPE_UHID_DATA: LOGD("UHID data id=%u len=%u", msg->uhid_data.id, msg->uhid_data.len); + if (receiver->uhid) { + sc_uhid_process_output(receiver->uhid, msg->uhid_data.id, msg->uhid_data.data, msg->uhid_data.len); + } else { + LOGW("UHID_DATA received but UHID is not initialized"); + } break; } } diff --git a/app/src/receiver.h b/app/src/receiver.h index eb959fb8c3..029105cee4 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -9,6 +9,8 @@ #include "util/net.h" #include "util/thread.h" +struct sc_uhid; + // receive events from the device // managed by the controller struct sc_receiver { @@ -17,11 +19,12 @@ struct sc_receiver { sc_mutex mutex; struct sc_acksync *acksync; + struct sc_uhid *uhid; }; bool sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, - struct sc_acksync *acksync); + struct sc_acksync *acksync, struct sc_uhid *uhid); void sc_receiver_destroy(struct sc_receiver *receiver); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 9225b1a006..34ff259971 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -649,7 +649,7 @@ scrcpy(struct scrcpy_options *options) { } if (!sc_controller_init(&s->controller, s->server.control_socket, - acksync)) { + acksync, &s->uhid)) { goto end; } controller_initialized = true; diff --git a/app/src/trait/hid_interface.h b/app/src/trait/hid_interface.h deleted file mode 100644 index ce3065c70c..0000000000 --- a/app/src/trait/hid_interface.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef SC_KEY_HID_INTERFACE_H -#define SC_KEY_HID_INTERFACE_H - -#include "common.h" -#include "hid/hid_event.h" - -struct sc_hid_interface { - void - (*process_output)(const uint8_t *data, size_t len, void *data_opaque); - - void *data_opaque; - - bool async_message; // Whether this HID interface transfers requests using Scrcpy control protocol or not - - const struct sc_hid_interface_ops *ops; -}; - -struct sc_hid_interface_ops { - bool - (*create)(struct sc_hid_interface *hid_interface, uint16_t id, - uint16_t vendor_id, uint16_t product_id, - const uint8_t* report_desc, - uint16_t report_desc_size); - - bool - (*process_input)(struct sc_hid_interface *hid_interface, const struct sc_hid_event* event); - - bool - (*destroy)(struct sc_hid_interface *hid_interface, uint16_t id); -}; - -#endif diff --git a/app/src/usb/aoa_hid.c b/app/src/usb/aoa_hid.c index 4e719430eb..21227a51a2 100644 --- a/app/src/usb/aoa_hid.c +++ b/app/src/usb/aoa_hid.c @@ -124,7 +124,7 @@ sc_aoa_unregister_hid(struct sc_hid_interface *hi, const uint16_t accessory_id) } static bool -sc_aoa_setup_hid(struct sc_hid_interface *hi, uint16_t accessory_id, +sc_aoa_setup_hid(struct sc_hid_interface *hi, struct sc_hid_device* device, uint16_t vendor_id, uint16_t product_id, const uint8_t *report_desc, uint16_t report_desc_size) { (void) vendor_id; @@ -132,20 +132,22 @@ sc_aoa_setup_hid(struct sc_hid_interface *hi, uint16_t accessory_id, struct sc_aoa *aoa = DOWNCAST(hi); - bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size); + bool ok = sc_aoa_register_hid(aoa, device->id, report_desc_size); if (!ok) { return false; } - ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc, + ok = sc_aoa_set_hid_report_desc(aoa, device->id, report_desc, report_desc_size); if (!ok) { - if (!sc_aoa_unregister_hid(hi, accessory_id)) { + if (!sc_aoa_unregister_hid(hi, device->id)) { LOGW("Could not unregister HID"); } return false; } + sc_hid_interface_add_device(hi, device); + return true; } @@ -258,6 +260,7 @@ sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, .destroy = sc_aoa_unregister_hid, }; + sc_hid_interface_init(&aoa->hid_interface); aoa->hid_interface.async_message = true; aoa->hid_interface.ops = &ops; diff --git a/app/src/usb/aoa_hid.h b/app/src/usb/aoa_hid.h index 33367cf417..ef5e03c287 100644 --- a/app/src/usb/aoa_hid.h +++ b/app/src/usb/aoa_hid.h @@ -7,7 +7,7 @@ #include #include "usb.h" -#include "trait/hid_interface.h" +#include "hid/hid_interface.h" #include "util/acksync.h" #include "util/thread.h" #include "util/tick.h" diff --git a/app/src/usb/hid_mouse.c b/app/src/usb/hid_mouse.c index 80714b06a6..d97911f57f 100644 --- a/app/src/usb/hid_mouse.c +++ b/app/src/usb/hid_mouse.c @@ -236,7 +236,10 @@ bool sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_hid_interface *hid_interface) { mouse->hid_interface = hid_interface; - bool ok = hid_interface->ops->create(hid_interface, HID_MOUSE_ACCESSORY_ID, + mouse->hid_device.id = HID_MOUSE_ACCESSORY_ID; + mouse->hid_device.process_output = NULL; + + bool ok = hid_interface->ops->create(hid_interface, &mouse->hid_device, 0x1234, 0x5678, mouse_report_desc, ARRAY_LEN(mouse_report_desc)); if (!ok) { diff --git a/app/src/usb/hid_mouse.h b/app/src/usb/hid_mouse.h index 2c65004ed7..6ded44e28d 100644 --- a/app/src/usb/hid_mouse.h +++ b/app/src/usb/hid_mouse.h @@ -6,13 +6,14 @@ #include #include "aoa_hid.h" -#include "trait/hid_interface.h" +#include "hid/hid_interface.h" #include "trait/mouse_processor.h" struct sc_hid_mouse { struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_hid_interface *hid_interface; + struct sc_hid_device hid_device; }; bool diff --git a/release.mk b/release.mk index fd969e5a63..0d553c007b 100644 --- a/release.mk +++ b/release.mk @@ -95,7 +95,7 @@ build-win64: prepare-deps -Dc_args="-I$(PWD)/$(WIN64_BUILD_DIR)/local/include" \ -Dc_link_args="-L$(PWD)/$(WIN64_BUILD_DIR)/local/lib" \ --cross-file=cross_win64.txt \ - --buildtype=release --strip -Db_lto=true \ + --buildtype=debug -Db_lto=true \ -Dcompile_server=false \ -Dportable=true ninja -C "$(WIN64_BUILD_DIR)" @@ -113,7 +113,7 @@ dist-win32: build-server build-win32 cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/" -dist-win64: build-server build-win64 +dist-win64: mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"