From 2aa94b8e020cf5ed37894946e1a918f41488168b Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Fri, 10 Sep 2021 18:57:35 +0800 Subject: [PATCH 1/7] Support USB HID over AoAv2 mode for keyboard input This provides a better input experience via simulate physical keyboard, it converts SDL_KeyboardEvent to proper HID events and send it via HID over AoAv2. This is a rewriting and bugfix of the origin code from [@amosbird](https://github.com/amosbird). Make sdl_keymod_to_hid_modifiers() more readable Support MOD keys in HID mode Enable Ctrl+V on HID mode Support to send media events from hid_keyboard Use existing --serial to replace --usb Use explict option for input mode Move HID keyboard setup code into functions Send HID events in separated thread Let libusb handle max package size Fix HID keyboard report desc --- BUILD.md | 15 +- README.md | 1 - app/meson.build | 5 +- app/scrcpy.1 | 12 ++ app/src/adb.c | 7 + app/src/adb.h | 3 + app/src/aoa_hid.c | 318 ++++++++++++++++++++++++++++++++++++ app/src/aoa_hid.h | 57 +++++++ app/src/cli.c | 36 ++++- app/src/hid_keyboard.c | 347 ++++++++++++++++++++++++++++++++++++++++ app/src/hid_keyboard.h | 89 +++++++++++ app/src/input_manager.c | 52 +++++- app/src/input_manager.h | 9 +- app/src/scrcpy.c | 57 ++++++- app/src/scrcpy.h | 8 + 15 files changed, 997 insertions(+), 19 deletions(-) create mode 100644 app/src/aoa_hid.c create mode 100644 app/src/aoa_hid.h create mode 100644 app/src/hid_keyboard.c create mode 100644 app/src/hid_keyboard.h diff --git a/BUILD.md b/BUILD.md index 87078b715b..864de36e8c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -88,11 +88,12 @@ Install the required packages from your package manager. ```bash # runtime dependencies -sudo apt install ffmpeg libsdl2-2.0-0 adb +sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0 # client build dependencies sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \ - libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev + libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev\ + libusb-dev # server build dependencies sudo apt install openjdk-11-jdk @@ -114,7 +115,7 @@ pip3 install meson sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm # client build dependencies -sudo dnf install SDL2-devel ffms2-devel meson gcc make +sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make # server build dependencies sudo dnf install java-devel @@ -159,7 +160,8 @@ install the required packages: ```bash # runtime dependencies pacman -S mingw-w64-x86_64-SDL2 \ - mingw-w64-x86_64-ffmpeg + mingw-w64-x86_64-ffmpeg \ + mingw-w64-x86_64-libusb # client build dependencies pacman -S mingw-w64-x86_64-make \ @@ -173,7 +175,8 @@ For a 32 bits version, replace `x86_64` by `i686`: ```bash # runtime dependencies pacman -S mingw-w64-i686-SDL2 \ - mingw-w64-i686-ffmpeg + mingw-w64-i686-ffmpeg \ + mingw-w64-i686-libusb # client build dependencies pacman -S mingw-w64-i686-make \ @@ -197,7 +200,7 @@ Install the packages with [Homebrew]: ```bash # runtime dependencies -brew install sdl2 ffmpeg +brew install sdl2 ffmpeg libusb # client build dependencies brew install pkg-config meson diff --git a/README.md b/README.md index 7b1d2e782a..f812e47968 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,6 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) If `--max-size` is also specified, resizing is applied after cropping. - #### Lock video orientation diff --git a/app/meson.build b/app/meson.build index f5345803cc..538d5a40a9 100644 --- a/app/meson.build +++ b/app/meson.build @@ -1,6 +1,7 @@ src = [ 'src/main.c', 'src/adb.c', + 'src/aoa_hid.c', 'src/cli.c', 'src/clock.c', 'src/compat.c', @@ -12,6 +13,7 @@ src = [ 'src/file_handler.c', 'src/fps_counter.c', 'src/frame_buffer.c', + 'src/hid_keyboard.c', 'src/input_manager.c', 'src/opengl.c', 'src/receiver.c', @@ -54,6 +56,7 @@ if not get_option('crossbuild_windows') dependency('libavformat'), dependency('libavcodec'), dependency('libavutil'), + dependency('libusb-1.0'), dependency('sdl2'), ] @@ -62,7 +65,7 @@ if not get_option('crossbuild_windows') endif else - + # TODO: add libusb dependency for windows. # cross-compile mingw32 build (from Linux to Windows) prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 1b69a0650f..eb624e16b1 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -82,6 +82,18 @@ Start in fullscreen. .B \-h, \-\-help Print this help. +.TP +.B \-i, \-\-input\-mode mode +Select input mode for keyboard events. + +Possible values are "auto", "hid" and "inject". + +"auto" is default if not specified, which attemps "hid" first and will fallback to "inject" if failed. + +"hid" uses Android's USB HID over AOAv2 feature to simulate physical keyboard's events, which provides better experience for IME users if supported by you device. + +"inject" is the legacy scrcpy way by injecting keycode events on Android, works on most devices. + .TP .B \-\-legacy\-paste Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). diff --git a/app/src/adb.c b/app/src/adb.c index 5bb9df300d..262b00a5b0 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -224,3 +224,10 @@ adb_install(const char *serial, const char *local) { return proc; } + +process_t +adb_get_serialno(const char *serial) { + const char *const adb_cmd[] = {"get-serialno"}; + process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); + return proc; +} diff --git a/app/src/adb.h b/app/src/adb.h index e27f34fa30..7cf8068bdd 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -31,4 +31,7 @@ adb_push(const char *serial, const char *local, const char *remote); process_t adb_install(const char *serial, const char *local); +process_t +adb_get_serialno(const char *serial); + #endif diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c new file mode 100644 index 0000000000..e4990313d3 --- /dev/null +++ b/app/src/aoa_hid.c @@ -0,0 +1,318 @@ +#include "util/log.h" + +#include + +#include "aoa_hid.h" + +// See . +#define ACCESSORY_REGISTER_HID 54 +#define ACCESSORY_SET_HID_REPORT_DESC 56 +#define ACCESSORY_SEND_HID_EVENT 57 +#define ACCESSORY_UNREGISTER_HID 55 + +#define DEFAULT_TIMEOUT 1000 + +// 128 seems to be enough for serial. +#define SERIAL_BUFFER_SIZE 128 + +void hid_event_log(const struct hid_event *event) { + // HID Event: [00] FF FF FF FF... + unsigned int buffer_size = event->size * 3 + 1; + char *buffer = malloc(sizeof(*buffer) * buffer_size); + if (!buffer) { + return; + } + buffer[0] = '\0'; + for (unsigned int i = 0; i < event->size; ++i) { + snprintf(buffer + i * 3, buffer_size - i * 3, " %02x", + event->buffer[i]); + } + LOGV("HID Event: [%d]%s", event->from_accessory_id, buffer); + free(buffer); + return; +} + +void hid_event_destroy(struct hid_event *event) { + free(event->buffer); +} + +inline static void log_libusb_error(enum libusb_error errcode) { + LOGW("libusb error: %s", libusb_strerror(errcode)); +} + +inline static int +get_usb_serial(libusb_device *device, char *buffer, int size) { + libusb_device_handle *handle; + int result = libusb_open(device, &handle); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + return result; + } + + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(device, &desc); + if (!desc.iSerialNumber) { + libusb_close(handle); + LOGW("USB device %04x:%04x has no serial number", + desc.idVendor, desc.idProduct); + return 1; + } + + result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, + (unsigned char *)buffer, size); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + libusb_close(handle); + return result; + } + + libusb_close(handle); + buffer[SERIAL_BUFFER_SIZE - 1] = '\0'; + return 0; +} + +inline static libusb_device *aoa_find_usb_device(const char *serial) { + if (!serial) { + return NULL; + } + + libusb_device **list; + libusb_device *result = NULL; + ssize_t count = libusb_get_device_list(NULL, &list); + if (count < 0) { + log_libusb_error((enum libusb_error)count); + return NULL; + } + + char buffer[SERIAL_BUFFER_SIZE]; + for (ssize_t i = 0; i < count; ++i) { + libusb_device *device = list[i]; + int error = get_usb_serial(device, buffer, SERIAL_BUFFER_SIZE); + if (!error && !strcmp(buffer, serial)) { + result = libusb_ref_device(device); + break; + } + } + libusb_free_device_list(list, 1); + return result; +} + +inline static int +aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) { + int result = libusb_open(device, handle); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + return result; + } + return 0; +} + +bool aoa_init(struct aoa *aoa, const struct scrcpy_options *options) { + aoa->usb_device = NULL; + aoa->usb_handle = NULL; + aoa->next_accessories_id = 0; + + cbuf_init(&aoa->queue); + + if (!sc_mutex_init(&aoa->mutex)) { + return false; + } + + if (!sc_cond_init(&aoa->event_cond)) { + sc_mutex_destroy(&aoa->mutex); + return false; + } + + libusb_init(NULL); + + aoa->usb_device = aoa_find_usb_device(options->serial); + if (!aoa->usb_device) { + LOGW("USB device of serial %s not found", options->serial); + sc_mutex_destroy(&aoa->mutex); + sc_cond_destroy(&aoa->event_cond); + return false; + } + + if (aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) { + LOGW("Open USB handle failed"); + sc_cond_destroy(&aoa->event_cond); + sc_mutex_destroy(&aoa->mutex); + libusb_unref_device(aoa->usb_device); + return false; + } + + aoa->stopped = false; + + return true; +} + +uint16_t aoa_get_new_accessory_id(struct aoa *aoa) { + return aoa->next_accessories_id++; +} + +int +aoa_register_hid(struct aoa *aoa, const uint16_t accessory_id, + uint16_t report_desc_size) { + const uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + const uint8_t request = ACCESSORY_REGISTER_HID; + // See . + // value (arg0): accessory assigned ID for the HID device + // index (arg1): total length of the HID report descriptor + const uint16_t value = accessory_id; + const uint16_t index = report_desc_size; + unsigned char *buffer = NULL; + const uint16_t length = 0; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + return result; + } + return 0; +} + +int +aoa_set_hid_report_desc(struct aoa *aoa, + const struct report_desc *report_desc) { + const uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + const uint8_t request = ACCESSORY_SET_HID_REPORT_DESC; + /** + * If the HID descriptor is longer than the endpoint zero max packet size, + * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC + * commands. The data for the descriptor must be sent sequentially + * if multiple packets are needed. + * See . + * + * libusb handles packet abstraction internally, so we don't need to care + * about bMaxPacketSize0 here. + * See + */ + // value (arg0): accessory assigned ID for the HID device + // index (arg1): offset of data (buffer) in descriptor + const uint16_t value = report_desc->from_accessory_id; + const uint16_t index = 0; + // libusb_control_transfer expects non-const but should not modify it. + unsigned char *buffer = (unsigned char *)report_desc->buffer; + const uint16_t length = report_desc->size; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + return result; + } + return 0; +} + +int +aoa_send_hid_event(struct aoa *aoa, const struct hid_event *event) { + const uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + const uint8_t request = ACCESSORY_SEND_HID_EVENT; + // See . + // value (arg0): accessory assigned ID for the HID device + // index (arg1): 0 (unused) + const uint16_t value = event->from_accessory_id; + const uint16_t index = 0; + // libusb_control_transfer expects non-const but should not modify it. + unsigned char *buffer = (unsigned char *)event->buffer; + const uint16_t length = event->size; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + return result; + } + return 0; +} + +int aoa_unregister_hid(struct aoa *aoa, const uint16_t accessory_id) { + const uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; + const uint8_t request = ACCESSORY_UNREGISTER_HID; + // See . + // value (arg0): accessory assigned ID for the HID device + // index (arg1): 0 + const uint16_t value = accessory_id; + const uint16_t index = 0; + unsigned char *buffer = NULL; + const uint16_t length = 0; + int result = libusb_control_transfer(aoa->usb_handle, request_type, request, + value, index, buffer, length, DEFAULT_TIMEOUT); + if (result < 0) { + log_libusb_error((enum libusb_error)result); + return result; + } + return 0; +} + +bool aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event) { + hid_event_log(event); + sc_mutex_lock(&aoa->mutex); + bool was_empty = cbuf_is_empty(&aoa->queue); + bool res = cbuf_push(&aoa->queue, *event); + if (was_empty) { + sc_cond_signal(&aoa->event_cond); + } + sc_mutex_unlock(&aoa->mutex); + return res; +} + +inline static bool +process_hid_event(struct aoa *aoa, const struct hid_event *event) { + return aoa_send_hid_event(aoa, event) == 0; +} + +inline static int run_aoa_thread(void *data) { + struct aoa *aoa = data; + while (true) { + sc_mutex_lock(&aoa->mutex); + while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) { + sc_cond_wait(&aoa->event_cond, &aoa->mutex); + } + if (aoa->stopped) { + // Stop immediately, do not process further event. + sc_mutex_unlock(&aoa->mutex); + break; + } + struct hid_event event; + bool non_empty = cbuf_take(&aoa->queue, &event); + assert(non_empty); + (void) non_empty; + sc_mutex_unlock(&aoa->mutex); + bool ok = process_hid_event(aoa, &event); + hid_event_destroy(&event); + if (!ok) { + LOGW("Could not send HID event to USB device"); + } + } + return 0; +} + +bool aoa_thread_start(struct aoa *aoa) { + LOGD("Starting aoa thread"); + + bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa); + + if (!ok) { + LOGC("Could not start aoa thread"); + return false; + } + + return true; +} + +void aoa_thread_stop(struct aoa *aoa) { + sc_mutex_lock(&aoa->mutex); + aoa->stopped = true; + sc_cond_signal(&aoa->event_cond); + sc_mutex_unlock(&aoa->mutex); +} + +void aoa_thread_join(struct aoa *aoa) { + sc_thread_join(&aoa->thread, NULL); +} + +void aoa_destroy(struct aoa *aoa) { + libusb_close(aoa->usb_handle); + libusb_unref_device(aoa->usb_device); + sc_cond_destroy(&aoa->event_cond); + sc_mutex_destroy(&aoa->mutex); +} diff --git a/app/src/aoa_hid.h b/app/src/aoa_hid.h new file mode 100644 index 0000000000..fdf5ceb0b5 --- /dev/null +++ b/app/src/aoa_hid.h @@ -0,0 +1,57 @@ +#ifndef AOA_HID_H +#define AOA_HID_H + +#include +#include + +#include + +#include "scrcpy.h" +#include "util/cbuf.h" +#include "util/thread.h" + +struct report_desc { + uint16_t from_accessory_id; + unsigned char *buffer; + uint16_t size; +}; + +struct hid_event { + uint16_t from_accessory_id; + unsigned char *buffer; + uint16_t size; +}; + +struct hid_event_queue CBUF(struct hid_event, 64); + +struct aoa { + libusb_device *usb_device; + libusb_device_handle *usb_handle; + sc_thread thread; + sc_mutex mutex; + sc_cond event_cond; + bool stopped; + uint16_t next_accessories_id; + struct hid_event_queue queue; +}; + +void hid_event_log(const struct hid_event *event); +void hid_event_destroy(struct hid_event *event); +bool aoa_init(struct aoa *aoa, const struct scrcpy_options *options); +// Generate a different accessory ID. +uint16_t aoa_get_new_accessory_id(struct aoa *aoa); +int +aoa_register_hid(struct aoa *aoa, const uint16_t accessory_id, + uint16_t report_desc_size); +int +aoa_set_hid_report_desc(struct aoa *aoa, const struct report_desc *report_desc); +int +aoa_send_hid_event(struct aoa *aoa, const struct hid_event *event); +int aoa_unregister_hid(struct aoa *aoa, const uint16_t accessory_id); +bool aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event); +bool aoa_thread_start(struct aoa *aoa); +void aoa_thread_stop(struct aoa *aoa); +void aoa_thread_join(struct aoa *aoa); +void aoa_destroy(struct aoa *aoa); + +#endif diff --git a/app/src/cli.c b/app/src/cli.c index d22096cafa..fdf32e482e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -79,6 +79,17 @@ scrcpy_print_usage(const char *arg0) { " -h, --help\n" " Print this help.\n" "\n" + " -i, --input-mode mode\n" + " Select input mode for keyboard events.\n" + " Possible values are \"auto\", \"hid\" and \"inject\".\n" + " \"auto\" is default if not specified, which attemps \"hid\"\n" + " first and will fallback to \"inject\" if failed.\n" + " \"hid\" uses Android's USB HID over AOAv2 feature to\n" + " simulate physical keyboard's events, which provides better\n" + " experience for IME users if supported by you device.\n" + " \"inject\" is the legacy scrcpy way by injecting keycode\n" + " events on Android, works on most devices.\n" + "\n" " --legacy-paste\n" " Inject computer clipboard text as a sequence of key events\n" " on Ctrl+v (like MOD+Shift+v).\n" @@ -673,6 +684,23 @@ parse_record_format(const char *optarg, enum sc_record_format *format) { return false; } +static bool +parse_input_mode(const char *optarg, enum sc_input_mode *input_mode) { + if (!strcmp(optarg, "auto")) { + *input_mode = SC_INPUT_MODE_AUTO; + return true; + } else if (!strcmp(optarg, "hid")) { + *input_mode = SC_INPUT_MODE_HID; + return true; + } else if (!strcmp(optarg, "inject")) { + *input_mode = SC_INPUT_MODE_INJECT; + return true; + } + LOGE("Unsupported input mode: %s (expected auto, hid or inject)", optarg); + return false; +} + + static enum sc_record_format guess_record_format(const char *filename) { size_t len = strlen(filename); @@ -738,6 +766,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { OPT_FORWARD_ALL_CLICKS}, {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, + {"input-mode", required_argument, NULL, 'i'}, {"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE}, {"lock-video-orientation", optional_argument, NULL, OPT_LOCK_VIDEO_ORIENTATION}, @@ -784,7 +813,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w", + while ((c = getopt_long(argc, argv, "b:c:fF:hi:m:nNp:r:s:StTvV:w", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -817,6 +846,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case 'h': args->help = true; break; + case 'i': + if (!parse_input_mode(optarg, &opts->input_mode)) { + return false; + } + break; case OPT_MAX_FPS: if (!parse_max_fps(optarg, &opts->max_fps)) { return false; diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c new file mode 100644 index 0000000000..1105d95aa4 --- /dev/null +++ b/app/src/hid_keyboard.c @@ -0,0 +1,347 @@ +#include "util/log.h" +#include "hid_keyboard.h" + +/** + * For HID over AOAv2, we only need report descriptor. + * Normally a basic HID keyboard uses 8 bytes, + * `Modifier Reserved Key Key Key Key Key Key`. + * See Appendix B.1 Protocol 1 (Keyboard) and + * Appendix C: Keyboard Implementation in + * . + * But if we want to support media keys on keyboard, + * we need to use two reports, + * report id 1 and usage page key codes for basic keyboard, + * report id 2 and usage page consumer for media keys. + * See 8. Report Protocol in + * . + * The former byte is item type prefix, we only use short items here. + * For how to calculate an item, read 6.2.2 Report Descriptor in + * . + * For Consumer Page tags, see 15 Consumer Page (0x0C) in + * . + */ +/** + * You can dump your device's report descriptor with + * `sudo usbhid-dump -m vid:pid -e descriptor`. + * Change `vid:pid` to your device's vendor ID and product ID. + */ +unsigned char kb_report_desc_buffer[] = { + // Usage Page (Generic Desktop) + 0x05, 0x01, + // Usage (Keyboard) + 0x09, 0x06, + + // Collection (Application) + 0xA1, 0x01, + // Report ID (1) + 0x85, 0x01, + + // Usage Page (Keyboard) + 0x05, 0x07, + // Usage Minimum (224) + 0x19, 0xE0, + // Usage Maximum (231) + 0x29, 0xE7, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum (1) + 0x25, 0x01, + // Report Size (1) + 0x75, 0x01, + // Report Count (8) + 0x95, 0x08, + // Input (Data, Variable, Absolute): Modifier byte + 0x81, 0x02, + + // Report Size (8) + 0x75, 0x08, + // Report Count (1) + 0x95, 0x01, + // Input (Constant): Reserved byte + 0x81, 0x01, + + // Usage Page (LEDs) + 0x05, 0x08, + // Usage Minimum (1) + 0x19, 0x01, + // Usage Maximum (5) + 0x29, 0x05, + // Report Size (1) + 0x75, 0x01, + // Report Count (5) + 0x95, 0x05, + // Output (Data, Variable, Absolute): LED report + 0x91, 0x02, + + // Report Size (3) + 0x75, 0x03, + // Report Count (1) + 0x95, 0x01, + // Output (Constant): LED report padding + 0x91, 0x01, + + // Usage Page (Key Codes) + 0x05, 0x07, + // Usage Minimum (0) + 0x19, 0x00, + // Usage Maximum (101) + 0x29, HID_KEYBOARD_KEYS - 1, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum(101) + 0x25, HID_KEYBOARD_KEYS - 1, + // Report Size (8) + 0x75, 0x08, + // Report Count (6) + 0x95, HID_KEYBOARD_MAX_KEYS, + // Input (Data, Array): Keys + 0x81, 0x00, + + // End Collection + 0xC0, + + // Usage Page (Consumer) + 0x05, 0x0C, + // Usage (Consumer Control) + 0x09, 0x01, + + // Collection (Application) + 0xA1, 0x01, + // Report ID (2) + 0x85, 0x02, + + // Usage Page (Consumer) + 0x05, 0x0C, + // Usage (Scan Next Track) + 0x09, 0xB5, + // Usage (Scan Previous Track) + 0x09, 0xB6, + // Usage (Stop) + 0x09, 0xB7, + // Usage (Eject) + 0x09, 0xB8, + // Usage (Play/Pause) + 0x09, 0xCD, + // Usage (Mute) + 0x09, 0xE2, + // Usage (Volume Increment) + 0x09, 0xE9, + // Usage (Volume Decrement) + 0x09, 0xEA, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum (1) + 0x25, 0x01, + // Report Size (1) + 0x75, 0x01, + // Report Count (8) + 0x95, 0x08, + // Input (Data, Array) + 0x81, 0x02, + + // End Collection + 0xC0 +}; + +static unsigned char *create_hid_keyboard_event(void) { + unsigned char *buffer = malloc(sizeof(*buffer) * HID_KEYBOARD_EVENT_SIZE); + if (!buffer) { + return NULL; + } + buffer[0] = HID_KEYBOARD_REPORT_ID; + buffer[1] = HID_KEYBOARD_MODIFIER_NONE; + buffer[2] = HID_KEYBOARD_RESERVED; + memset(buffer + HID_KEYBOARD_MODIFIER_INDEX + 2, + HID_KEYBOARD_RESERVED, HID_KEYBOARD_MAX_KEYS); + return buffer; +} + +static unsigned char *create_hid_media_event(void) { + unsigned char *buffer = malloc(sizeof(*buffer) * HID_MEDIA_EVENT_SIZE); + if (!buffer) { + return NULL; + } + buffer[0] = HID_MEDIA_REPORT_ID; + buffer[1] = HID_MEDIA_KEY_UNDEFINED; + return buffer; +} + +bool +hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa) { + kb->aoa = aoa; + kb->accessory_id = aoa_get_new_accessory_id(aoa); + + struct report_desc report_desc = { + kb->accessory_id, + kb_report_desc_buffer, + sizeof(kb_report_desc_buffer) / sizeof(kb_report_desc_buffer[0]) + }; + + if (aoa_register_hid(aoa, kb->accessory_id, report_desc.size) < 0) { + LOGW("Register HID for keyboard failed"); + return false; + } + + if (aoa_set_hid_report_desc(aoa, &report_desc) < 0) { + LOGW("Set HID report desc for keyboard failed"); + return false; + } + + // Reset all states. + memset(kb->keys, false, HID_KEYBOARD_KEYS); + return true; +} + +inline static unsigned char sdl_keymod_to_hid_modifiers(SDL_Keymod mod) { + unsigned char modifiers = HID_KEYBOARD_MODIFIER_NONE; + // Not so cool, but more readable, and does not rely on actual value. + if (mod & KMOD_LCTRL) { + modifiers |= HID_KEYBOARD_MODIFIER_LEFT_CONTROL; + } + if (mod & KMOD_LSHIFT) { + modifiers |= HID_KEYBOARD_MODIFIER_LEFT_SHIFT; + } + if (mod & KMOD_LALT) { + modifiers |= HID_KEYBOARD_MODIFIER_LEFT_ALT; + } + if (mod & KMOD_LGUI) { + modifiers |= HID_KEYBOARD_MODIFIER_LEFT_GUI; + } + if (mod & KMOD_RCTRL) { + modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_CONTROL; + } + if (mod & KMOD_RSHIFT) { + modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_SHIFT; + } + if (mod & KMOD_RALT) { + modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_ALT; + } + if (mod & KMOD_RGUI) { + modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_GUI; + } + return modifiers; +} + +inline static bool +convert_hid_keyboard_event(struct hid_keyboard *kb, struct hid_event *hid_event, + const SDL_KeyboardEvent *event) { + hid_event->buffer = create_hid_keyboard_event(); + if (!hid_event->buffer) { + return false; + } + hid_event->size = HID_KEYBOARD_EVENT_SIZE; + + unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod); + + SDL_Scancode scancode = event->keysym.scancode; + // SDL also generates event when only modifiers are pressed, + // we cannot ignore them totally, for example press `a` first then + // press `Control`, if we ignore `Control` event, only `a` is sent. + if (scancode >= 0 && scancode < HID_KEYBOARD_KEYS) { + // Pressed is true and released is false. + kb->keys[scancode] = (event->type == SDL_KEYDOWN); + LOGV("keys[%02x] = %s", scancode, + kb->keys[scancode] ? "true" : "false"); + } + + // Re-calculate pressed keys every time. + int keys_pressed_count = 0; + for (int i = 0; i < HID_KEYBOARD_KEYS; ++i) { + // USB HID protocol says that if keys exceeds report count, + // a phantom state should be report. + if (keys_pressed_count > HID_KEYBOARD_MAX_KEYS) { + // Pantom state is made of `ReportID, Modifiers, Reserved, ErrorRollOver, ErrorRollOver, ErrorRollOver, ErrorRollOver, ErrorRollOver, ErrorRollOver`. + memset(hid_event->buffer + HID_KEYBOARD_MODIFIER_INDEX + 2, + HID_KEYBOARD_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS); + // But the modifiers should be report normally for phantom state. + hid_event->buffer[HID_KEYBOARD_MODIFIER_INDEX] = modifiers; + return true; + } + if (kb->keys[i]) { + hid_event->buffer[ + HID_KEYBOARD_MODIFIER_INDEX + 2 + keys_pressed_count] = i; + ++keys_pressed_count; + } + } + hid_event->buffer[HID_KEYBOARD_MODIFIER_INDEX] = modifiers; + + return true; +} + +inline static bool +convert_hid_media_event(struct hid_event *hid_event, + const SDL_KeyboardEvent *event) { + // Re-calculate pressed keys every time. + unsigned char media_key = HID_MEDIA_KEY_UNDEFINED; + + SDL_Scancode scancode = event->keysym.scancode; + if (scancode == SDL_SCANCODE_AUDIONEXT) { + media_key |= HID_MEDIA_KEY_NEXT; + } + if (scancode == SDL_SCANCODE_AUDIOPREV) { + media_key |= HID_MEDIA_KEY_PREVIOUS; + } + if (scancode == SDL_SCANCODE_AUDIOSTOP) { + media_key |= HID_MEDIA_KEY_STOP; + } + if (scancode == SDL_SCANCODE_EJECT) { + media_key |= HID_MEDIA_KEY_EJECT; + } + if (scancode == SDL_SCANCODE_AUDIOPLAY) { + media_key |= HID_MEDIA_KEY_PLAY_PAUSE; + } + if (scancode == SDL_SCANCODE_AUDIOMUTE) { + media_key |= HID_MEDIA_KEY_MUTE; + } + if (scancode == SDL_SCANCODE_AUDIOSTOP) { + media_key |= HID_MEDIA_KEY_STOP; + } + // SDL has no equivalence for HID_MEDIA_KEY_VOLUME_UP and + // HID_MEDIA_KEY_VOLUME_DOWN, it does have SDL_SCANCODE_VOLUMEUP and + // SDL_SCANCODE_VOLUMEDOWN, but they are under Usage Page (0x07), + // which should be a keyboard event. + + // Not all other keys are Usage Page 0x0C, + // we return NULL for unsupported keys. + if (media_key == HID_MEDIA_KEY_UNDEFINED) { + return false; + } + + hid_event->buffer = create_hid_media_event(); + if (!hid_event->buffer) { + return false; + } + hid_event->size = HID_MEDIA_EVENT_SIZE; + + hid_event->buffer[HID_MEDIA_KEY_INDEX] = media_key; + + return true; +} + +bool +hid_keyboard_convert_event(struct hid_keyboard *kb, + struct hid_event *hid_event, const SDL_KeyboardEvent *event) { + LOGV( + "Type: %s, Repeat: %s, Modifiers: %02x, Key: %02x", + event->type == SDL_KEYDOWN ? "down" : "up", + event->repeat != 0 ? "true" : "false", + sdl_keymod_to_hid_modifiers(event->keysym.mod), + event->keysym.scancode + ); + + hid_event->from_accessory_id = kb->accessory_id; + + if (event->keysym.scancode >= 0 && + event->keysym.scancode <= SDL_SCANCODE_MODE) { + // Usage Page 0x07 (Keyboard). + return convert_hid_keyboard_event(kb, hid_event, event); + } else { + // Others. + return convert_hid_media_event(hid_event, event); + } +} + +void hid_keyboard_destroy(struct hid_keyboard *kb) { + // Unregister HID keyboard so the soft keyboard shows again on Android. + aoa_unregister_hid(kb->aoa, kb->accessory_id); +} diff --git a/app/src/hid_keyboard.h b/app/src/hid_keyboard.h new file mode 100644 index 0000000000..75d832904b --- /dev/null +++ b/app/src/hid_keyboard.h @@ -0,0 +1,89 @@ +#ifndef HID_KEYBOARD_H +#define HID_KEYBOARD_H + +#include + +#include + +#include "aoa_hid.h" + +/** + * Because of dual-report, when we send hid events, we need to add report id + * as prefix, so keyboard keys looks like + * `0x01 Modifier Reserved Key Key Key Key Key Key` and media keys looks like + * `0x02 MediaMask` (one key per bit for media keys). + */ +#define HID_REPORT_ID_INDEX 0 +#define HID_KEYBOARD_MODIFIER_INDEX (HID_REPORT_ID_INDEX + 1) +#define HID_KEYBOARD_MODIFIER_NONE 0x00 +#define HID_KEYBOARD_MODIFIER_LEFT_CONTROL (1 << 0) +#define HID_KEYBOARD_MODIFIER_LEFT_SHIFT (1 << 1) +#define HID_KEYBOARD_MODIFIER_LEFT_ALT (1 << 2) +#define HID_KEYBOARD_MODIFIER_LEFT_GUI (1 << 3) +#define HID_KEYBOARD_MODIFIER_RIGHT_CONTROL (1 << 4) +#define HID_KEYBOARD_MODIFIER_RIGHT_SHIFT (1 << 5) +#define HID_KEYBOARD_MODIFIER_RIGHT_ALT (1 << 6) +#define HID_KEYBOARD_MODIFIER_RIGHT_GUI (1 << 7) +// 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, I think 6 is enough for us. +#define HID_KEYBOARD_MAX_KEYS 6 +#define HID_KEYBOARD_EVENT_SIZE (3 + HID_KEYBOARD_MAX_KEYS) +#define HID_KEYBOARD_REPORT_ID 0x01 +#define HID_KEYBOARD_RESERVED 0x00 +#define HID_KEYBOARD_ERROR_ROLL_OVER 0x01 +#define HID_MEDIA_EVENT_SIZE 2 +#define HID_MEDIA_REPORT_ID 0x02 +#define HID_MEDIA_KEY_INDEX (HID_REPORT_ID_INDEX + 1) +/** + * Media keys handle as mask so we define them here. + * Currently not used because desktop environment catches them before window. + */ +#define HID_MEDIA_KEY_UNDEFINED 0x00 +#define HID_MEDIA_KEY_NEXT (1 << 0) +#define HID_MEDIA_KEY_PREVIOUS (1 << 1) +#define HID_MEDIA_KEY_STOP (1 << 2) +#define HID_MEDIA_KEY_EJECT (1 << 3) +#define HID_MEDIA_KEY_PLAY_PAUSE (1 << 4) +#define HID_MEDIA_KEY_MUTE (1 << 5) +#define HID_MEDIA_KEY_VOLUME_UP (1 << 6) +#define HID_MEDIA_KEY_VOLUME_DOWN (1 << 7) + +// See "SDL2/SDL_scancode.h". +// Maybe SDL_Keycode is used by most people, +// but SDL_Scancode is taken from USB HID protocol so I perfer this. +// 0x65 is Application, typically AT-101 Keyboard ends here. +#define HID_KEYBOARD_KEYS 0x66 + +/** + * HID keyboard events are sequence-based, every time keyboard state changes + * it sends an array of currently pressed keys, the host is responsible for + * compare events and determine which key becomes pressed and which key becomes + * released. In order to convert SDL_KeyboardEvent to HID events, we first use + * an array of keys to save each keys' state. And when a SDL_KeyboardEvent was + * emitted, we updated our state, this is done by hid_keyboard_update_state(), + * and then we use a loop to generate HID events, the sequence of array elements + * is unimportant and when too much keys pressed at the same time (more than + * report count), we should generate phantom state. This is done by + * hid_keyboard_get_hid_event(). Don't forget that modifiers should be updated + * too, even for phantom state. + */ +struct hid_keyboard { + struct aoa *aoa; + uint16_t accessory_id; + bool keys[HID_KEYBOARD_KEYS]; +}; + +bool +hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa); +/** + * Return false if unsupported keys is received, + * and be safe to ignore the HID event. + * In fact we are not only convert events, we also UPDATE internal key states. + */ +bool +hid_keyboard_convert_event(struct hid_keyboard *kb, + struct hid_event *hid_event, const SDL_KeyboardEvent *event); +void hid_keyboard_destroy(struct hid_keyboard *kb); + +#endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index a5d0ad07a0..54836c4157 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -4,6 +4,8 @@ #include #include "event_converter.h" +#include "aoa_hid.h" +#include "hid_keyboard.h" #include "util/log.h" static const int ACTION_DOWN = 1; @@ -53,12 +55,16 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) { void input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, + struct screen *screen, struct aoa *aoa, + struct hid_keyboard *hid_keyboard, const struct scrcpy_options *options) { im->controller = controller; im->screen = screen; im->repeat = 0; + im->aoa = aoa; + im->hid_keyboard = hid_keyboard; + im->control = options->control; im->forward_key_repeat = options->forward_key_repeat; im->prefer_text = options->prefer_text; @@ -319,6 +325,11 @@ rotate_client_right(struct screen *screen) { static void input_manager_process_text_input(struct input_manager *im, const SDL_TextInputEvent *event) { + // We don't need this if HID over AOAv2 is used. + if (im->hid_keyboard) { + return; + } + if (is_shortcut_mod(im, SDL_GetModState())) { // A shortcut must never generate text events return; @@ -396,6 +407,30 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, return true; } +static void +input_manager_process_key_inject(struct input_manager *im, + const SDL_KeyboardEvent *event) { + struct controller *controller = im->controller; + struct control_msg msg; + if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { + if (!controller_push_msg(controller, &msg)) { + LOGW("Could not request 'inject keycode'"); + } + } +} + +static void +input_manager_process_key_hid(struct input_manager *im, + const SDL_KeyboardEvent *event) { + struct hid_event hid_event; + // Not all keys are supported, just ignore unsupported keys. + if (hid_keyboard_convert_event(im->hid_keyboard, &hid_event, event)) { + if (!aoa_push_hid_event(im->aoa, &hid_event)) { + LOGW("Could request HID event"); + } + } +} + static void input_manager_process_key(struct input_manager *im, const SDL_KeyboardEvent *event) { @@ -541,7 +576,6 @@ input_manager_process_key(struct input_manager *im, } return; } - return; } @@ -550,7 +584,9 @@ input_manager_process_key(struct input_manager *im, } if (event->repeat) { - if (!im->forward_key_repeat) { + // In USB HID protocol, key repeat is handle by host + // (Android in this case), so just ignore key repeat here. + if (im->hid_keyboard || !im->forward_key_repeat) { return; } ++im->repeat; @@ -558,6 +594,7 @@ input_manager_process_key(struct input_manager *im, im->repeat = 0; } + // FIXME: Seems not work properly on Samsung Galaxy S9+? if (ctrl && !shift && keycode == SDLK_v && down && !repeat) { if (im->legacy_paste) { // inject the text as input events @@ -569,11 +606,10 @@ input_manager_process_key(struct input_manager *im, set_device_clipboard(controller, false); } - struct control_msg msg; - if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) { - if (!controller_push_msg(controller, &msg)) { - LOGW("Could not request 'inject keycode'"); - } + if (im->hid_keyboard) { + input_manager_process_key_hid(im, event); + } else { + input_manager_process_key_inject(im, event); } } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 1dd7825f51..6e841846d3 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -11,11 +11,16 @@ #include "fps_counter.h" #include "scrcpy.h" #include "screen.h" +#include "hid_keyboard.h" struct input_manager { struct controller *controller; struct screen *screen; + struct aoa *aoa; + // If NULL, fallback to inject mode, else prefer HID mode. + struct hid_keyboard *hid_keyboard; + // SDL reports repeated events as a boolean, but Android expects the actual // number of repetitions. This variable keeps track of the count. unsigned repeat; @@ -43,7 +48,9 @@ struct input_manager { void input_manager_init(struct input_manager *im, struct controller *controller, - struct screen *screen, const struct scrcpy_options *options); + struct screen *screen, struct aoa *aoa, + struct hid_keyboard *hid_keyboard, + const struct scrcpy_options *options); bool input_manager_handle_event(struct input_manager *im, SDL_Event *event); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 6a2857884c..376b419cab 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -17,6 +17,7 @@ #include "decoder.h" #include "events.h" #include "file_handler.h" +#include "hid_keyboard.h" #include "input_manager.h" #include "recorder.h" #include "screen.h" @@ -40,6 +41,8 @@ struct scrcpy { #endif struct controller controller; struct file_handler file_handler; + struct hid_keyboard hid_keyboard; + struct aoa aoa; struct input_manager input_manager; }; @@ -257,8 +260,11 @@ scrcpy(const struct scrcpy_options *options) { bool v4l2_sink_initialized = false; #endif bool stream_started = false; + bool hid_keyboard_initialized = false; bool controller_initialized = false; bool controller_started = false; + bool aoa_initialized = false; + bool aoa_thread_started = false; bool screen_initialized = false; bool record = !!options->record_filename; @@ -412,7 +418,46 @@ scrcpy(const struct scrcpy_options *options) { } stream_started = true; - input_manager_init(&s->input_manager, &s->controller, &s->screen, options); + // We don't need HID over AOAv2 support if no control. + if (options->control) { + if (options->input_mode == SC_INPUT_MODE_INJECT) { + LOGD("Starting in inject mode because of --input-mode=inject"); + } else { + LOGD("Starting in HID over AOAv2 mode"); + aoa_initialized = aoa_init(&s->aoa, options); + if (aoa_initialized) { + hid_keyboard_initialized = hid_keyboard_init(&s->hid_keyboard, + &s->aoa); + } + // Init HID keyboard before starting thread, this is thread safe. + if (hid_keyboard_initialized) { + aoa_thread_started = aoa_thread_start(&s->aoa); + } + if (aoa_thread_started) { + LOGD("Successfully set up HID over AOAv2 mode"); + } else { + LOGW("Failed to set up HID over AOAv2 mode"); + if (options->input_mode == SC_INPUT_MODE_HID) { + // HID is specified explicitly, and will exit if failed. + LOGE("Remove --input-mode=hid from parameters to allow input mode fallback"); + goto end; + } + LOGW("Fallback to inject mode"); + if (hid_keyboard_initialized) { + hid_keyboard_destroy(&s->hid_keyboard); + hid_keyboard_initialized = false; + } + if (aoa_initialized) { + aoa_destroy(&s->aoa); + aoa_initialized = false; + } + } + } + } + + input_manager_init(&s->input_manager, &s->controller, &s->screen, + aoa_thread_started ? &s->aoa : NULL, + hid_keyboard_initialized ? &s->hid_keyboard : NULL, options); ret = event_loop(s, options); LOGD("quit..."); @@ -422,6 +467,16 @@ scrcpy(const struct scrcpy_options *options) { screen_hide_window(&s->screen); end: + if (aoa_thread_started) { + aoa_thread_stop(&s->aoa); + } + if (hid_keyboard_initialized) { + hid_keyboard_destroy(&s->hid_keyboard); + } + if (aoa_initialized) { + aoa_destroy(&s->aoa); + } + // The stream is not stopped explicitly, because it will stop by itself on // end-of-stream if (controller_started) { diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 8b76fb25a2..27abe517ef 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -44,6 +44,12 @@ enum sc_shortcut_mod { SC_MOD_RSUPER = 1 << 5, }; +enum sc_input_mode { + SC_INPUT_MODE_AUTO, + SC_INPUT_MODE_HID, + SC_INPUT_MODE_INJECT +}; + struct sc_shortcut_mods { unsigned data[SC_MAX_SHORTCUT_MODS]; unsigned count; @@ -68,6 +74,7 @@ struct scrcpy_options { const char *v4l2_device; enum sc_log_level log_level; enum sc_record_format record_format; + enum sc_input_mode input_mode; struct sc_port_range port_range; struct sc_shortcut_mods shortcut_mods; uint16_t max_size; @@ -112,6 +119,7 @@ struct scrcpy_options { .v4l2_device = NULL, \ .log_level = SC_LOG_LEVEL_INFO, \ .record_format = SC_RECORD_FORMAT_AUTO, \ + .input_mode = SC_INPUT_MODE_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ From f9e3be600053f6bba1cf2af5a0795b0d5de7a37d Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Thu, 16 Sep 2021 01:19:57 +0800 Subject: [PATCH 2/7] Add Windows build support for libusb Update README.md --- README.md | 18 ++++++++++++++++++ app/meson.build | 14 +++++++++++++- app/src/aoa_hid.c | 5 ++++- app/src/aoa_hid.h | 1 + cross_win32.txt | 1 + cross_win64.txt | 1 + prebuilt-deps/Makefile | 18 ++++++++++++++++-- prebuilt-deps/prepare-dep | 29 +++++++++++++++++++++++++---- release.mk | 2 ++ 9 files changed, 81 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f812e47968..8b9295dc06 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,24 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0) If `--max-size` is also specified, resizing is applied after cropping. +#### USB HID over AOAv2 + +Scrcpy can simulate a USB physical keyboard on Android to provide better input +experience, you need to connect your device via USB, not wireless. + +However, due to some limitation of libusb and WinUSB driver, you cannot use HID +over AOAv2 on Windows. + +Currently a USB serial number is needed to use HID over AOAv2. + +```bash +scrcpy --serial XXXXXXXXXXXXXXXX # try HID first and fallback to inject +scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed +scrcpy --serial XXXXXXXXXXXXXXXX --input-mode inject # don't use HID +``` + +Serial number can be found by `adb get-serialno`. + #### Lock video orientation diff --git a/app/meson.build b/app/meson.build index 538d5a40a9..9a7f3854dd 100644 --- a/app/meson.build +++ b/app/meson.build @@ -65,7 +65,7 @@ if not get_option('crossbuild_windows') endif else - # TODO: add libusb dependency for windows. + # cross-compile mingw32 build (from Linux to Windows) prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' @@ -93,8 +93,20 @@ else include_directories: include_directories(ffmpeg_include_dir) ) + prebuilt_libusb = meson.get_cross_property('prebuilt_libusb') + libusb_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_libusb + '/dll' + libusb_include_dir = '../prebuilt-deps/' + prebuilt_libusb + '/include' + + libusb = declare_dependency( + dependencies: [ + cc.find_library('libusb-1.0', dirs: libusb_bin_dir), + ], + include_directories: include_directories(libusb_include_dir) + ) + dependencies = [ ffmpeg, + libusb, sdl2, cc.find_library('mingw32') ] diff --git a/app/src/aoa_hid.c b/app/src/aoa_hid.c index e4990313d3..8012ecf1fc 100644 --- a/app/src/aoa_hid.c +++ b/app/src/aoa_hid.c @@ -1,6 +1,7 @@ #include "util/log.h" #include +#include #include "aoa_hid.h" @@ -108,6 +109,7 @@ aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) { } bool aoa_init(struct aoa *aoa, const struct scrcpy_options *options) { + aoa->usb_context = NULL; aoa->usb_device = NULL; aoa->usb_handle = NULL; aoa->next_accessories_id = 0; @@ -123,7 +125,7 @@ bool aoa_init(struct aoa *aoa, const struct scrcpy_options *options) { return false; } - libusb_init(NULL); + libusb_init(&aoa->usb_context); aoa->usb_device = aoa_find_usb_device(options->serial); if (!aoa->usb_device) { @@ -313,6 +315,7 @@ void aoa_thread_join(struct aoa *aoa) { void aoa_destroy(struct aoa *aoa) { libusb_close(aoa->usb_handle); libusb_unref_device(aoa->usb_device); + libusb_exit(aoa->usb_context); sc_cond_destroy(&aoa->event_cond); sc_mutex_destroy(&aoa->mutex); } diff --git a/app/src/aoa_hid.h b/app/src/aoa_hid.h index fdf5ceb0b5..226210264e 100644 --- a/app/src/aoa_hid.h +++ b/app/src/aoa_hid.h @@ -25,6 +25,7 @@ struct hid_event { struct hid_event_queue CBUF(struct hid_event, 64); struct aoa { + libusb_context *usb_context; libusb_device *usb_device; libusb_device_handle *usb_handle; sc_thread thread; diff --git a/cross_win32.txt b/cross_win32.txt index 4db17be71d..cb4ac03ac1 100644 --- a/cross_win32.txt +++ b/cross_win32.txt @@ -18,3 +18,4 @@ endian = 'little' prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' prebuilt_sdl2 = 'SDL2-2.0.16/i686-w64-mingw32' +prebuilt_libusb = 'libusb-1.0.24/MinGW32' diff --git a/cross_win64.txt b/cross_win64.txt index d03f02722a..13cba3df4d 100644 --- a/cross_win64.txt +++ b/cross_win64.txt @@ -18,3 +18,4 @@ endian = 'little' prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' prebuilt_sdl2 = 'SDL2-2.0.16/x86_64-w64-mingw32' +prebuilt_libusb = 'libusb-1.0.24/MinGW64' diff --git a/prebuilt-deps/Makefile b/prebuilt-deps/Makefile index dced047cbd..04d517c0d8 100644 --- a/prebuilt-deps/Makefile +++ b/prebuilt-deps/Makefile @@ -3,11 +3,14 @@ prepare-ffmpeg-dev-win32 \ prepare-ffmpeg-shared-win64 \ prepare-ffmpeg-dev-win64 \ + prepare-libusb \ prepare-sdl2 \ prepare-adb -prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb -prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb +LIBUSB_DIR := libusb-1.0.24 + +prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-libusb prepare-adb +prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-libusb prepare-adb prepare-ffmpeg-shared-win32: @./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \ @@ -29,6 +32,17 @@ prepare-ffmpeg-dev-win64: 2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \ ffmpeg-4.3.1-win64-dev +prepare-libusb: + # libusb put all things under the root of 7z file, so we pass the last + # argument which means creating extract dir manually. + @./prepare-dep https://github.com/libusb/libusb/releases/download/v1.0.24/libusb-1.0.24.7z \ + 620cec4dbe4868202949294157da5adb75c9fbb4f04266146fc833eef85f90fb \ + "$(LIBUSB_DIR)" \ + 1 + # Our code expects include dir under architechture dir. + cp -a "$(LIBUSB_DIR)"/include "$(LIBUSB_DIR)"/MinGW32 + cp -a "$(LIBUSB_DIR)"/include "$(LIBUSB_DIR)"/MinGW64 + prepare-sdl2: @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.16-mingw.tar.gz \ 2bfe48628aa9635c12eac7d421907e291525de1d0b04b3bca4a5bd6e6c881a6f \ diff --git a/prebuilt-deps/prepare-dep b/prebuilt-deps/prepare-dep index f152e6cf65..179d52ee70 100755 --- a/prebuilt-deps/prepare-dep +++ b/prebuilt-deps/prepare-dep @@ -3,6 +3,7 @@ set -e url="$1" sum="$2" dir="$3" +create_extract_dir="$4" checksum() { local file="$1" @@ -27,13 +28,32 @@ get_file() { extract() { local file="$1" + local create_extract_dir="$2" echo "Extracting $file..." if [[ "$file" == *.zip ]] then - unzip -q "$file" + if [[ -n "$create_extract_dir" ]] + then + unzip -q "$file" -d "$dir" + else + unzip -q "$file" + fi elif [[ "$file" == *.tar.gz ]] then - tar xf "$file" + if [[ -n "$create_extract_dir" ]] + then + tar xf "$file" --one-top-level="$dir" + else + tar xf "$file" + fi + elif [[ "$file" == *.7z ]] + then + if [[ -n "$create_extract_dir" ]] + then + 7z x "$file" -o"$dir" + else + 7z x "$file" + fi else echo "Unsupported file: $file" return 1 @@ -44,6 +64,7 @@ get_dep() { local url="$1" local sum="$2" local dir="$3" + local create_extract_dir="$4" local file="${url##*/}" if [[ -d "$dir" ]] then @@ -51,8 +72,8 @@ get_dep() { else echo "$dir: not found" get_file "$url" "$file" "$sum" - extract "$file" + extract "$file" "$create_extract_dir" fi } -get_dep "$url" "$sum" "$dir" +get_dep "$url" "$sum" "$dir" "$create_extract_dir" diff --git a/release.mk b/release.mk index e327654cfd..5359a5eebc 100644 --- a/release.mk +++ b/release.mk @@ -99,6 +99,7 @@ dist-win32: build-server build-win32 cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/" + cp prebuilt-deps/libusb-1.0.24/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" @@ -115,6 +116,7 @@ dist-win64: build-server build-win64 cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/" + cp prebuilt-deps/libusb-1.0.24/MinGW64/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" From 547d72f312e5607a22e0a756a8af527a75c5b9db Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Tue, 21 Sep 2021 11:57:11 +0800 Subject: [PATCH 3/7] Remove unused adb_get_serialno() --- app/src/adb.c | 7 ------- app/src/adb.h | 3 --- 2 files changed, 10 deletions(-) diff --git a/app/src/adb.c b/app/src/adb.c index 262b00a5b0..5bb9df300d 100644 --- a/app/src/adb.c +++ b/app/src/adb.c @@ -224,10 +224,3 @@ adb_install(const char *serial, const char *local) { return proc; } - -process_t -adb_get_serialno(const char *serial) { - const char *const adb_cmd[] = {"get-serialno"}; - process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); - return proc; -} diff --git a/app/src/adb.h b/app/src/adb.h index 7cf8068bdd..e27f34fa30 100644 --- a/app/src/adb.h +++ b/app/src/adb.h @@ -31,7 +31,4 @@ adb_push(const char *serial, const char *local, const char *remote); process_t adb_install(const char *serial, const char *local); -process_t -adb_get_serialno(const char *serial); - #endif From 8df2eca61513e68acfd8eb4d934b90bc93ddfa3f Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Tue, 21 Sep 2021 18:32:58 +0800 Subject: [PATCH 4/7] Use inject as default input mode --- README.md | 4 ++-- app/scrcpy.1 | 6 ++---- app/src/cli.c | 13 ++++--------- app/src/scrcpy.c | 23 +++++------------------ app/src/scrcpy.h | 3 +-- 5 files changed, 14 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 8b9295dc06..1325f792da 100644 --- a/README.md +++ b/README.md @@ -218,9 +218,9 @@ over AOAv2 on Windows. Currently a USB serial number is needed to use HID over AOAv2. ```bash -scrcpy --serial XXXXXXXXXXXXXXXX # try HID first and fallback to inject -scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed +scrcpy --serial XXXXXXXXXXXXXXXX # don't use HID scrcpy --serial XXXXXXXXXXXXXXXX --input-mode inject # don't use HID +scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed ``` Serial number can be found by `adb get-serialno`. diff --git a/app/scrcpy.1 b/app/scrcpy.1 index eb624e16b1..8d68cdff2c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -86,13 +86,11 @@ Print this help. .B \-i, \-\-input\-mode mode Select input mode for keyboard events. -Possible values are "auto", "hid" and "inject". - -"auto" is default if not specified, which attemps "hid" first and will fallback to "inject" if failed. +Possible values are "hid" and "inject". "hid" uses Android's USB HID over AOAv2 feature to simulate physical keyboard's events, which provides better experience for IME users if supported by you device. -"inject" is the legacy scrcpy way by injecting keycode events on Android, works on most devices. +"inject" is the legacy scrcpy way by injecting keycode events on Android, works on most devices and is the default. .TP .B \-\-legacy\-paste diff --git a/app/src/cli.c b/app/src/cli.c index fdf32e482e..ea42202be7 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -81,14 +81,12 @@ scrcpy_print_usage(const char *arg0) { "\n" " -i, --input-mode mode\n" " Select input mode for keyboard events.\n" - " Possible values are \"auto\", \"hid\" and \"inject\".\n" - " \"auto\" is default if not specified, which attemps \"hid\"\n" - " first and will fallback to \"inject\" if failed.\n" + " Possible values are \"hid\" and \"inject\".\n" " \"hid\" uses Android's USB HID over AOAv2 feature to\n" " simulate physical keyboard's events, which provides better\n" " experience for IME users if supported by you device.\n" " \"inject\" is the legacy scrcpy way by injecting keycode\n" - " events on Android, works on most devices.\n" + " events on Android, works on most devices and is the default.\n" "\n" " --legacy-paste\n" " Inject computer clipboard text as a sequence of key events\n" @@ -686,17 +684,14 @@ parse_record_format(const char *optarg, enum sc_record_format *format) { static bool parse_input_mode(const char *optarg, enum sc_input_mode *input_mode) { - if (!strcmp(optarg, "auto")) { - *input_mode = SC_INPUT_MODE_AUTO; - return true; - } else if (!strcmp(optarg, "hid")) { + if (!strcmp(optarg, "hid")) { *input_mode = SC_INPUT_MODE_HID; return true; } else if (!strcmp(optarg, "inject")) { *input_mode = SC_INPUT_MODE_INJECT; return true; } - LOGE("Unsupported input mode: %s (expected auto, hid or inject)", optarg); + LOGE("Unsupported input mode: %s (expected hid or inject)", optarg); return false; } diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 376b419cab..50d3bd9990 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -420,10 +420,8 @@ scrcpy(const struct scrcpy_options *options) { // We don't need HID over AOAv2 support if no control. if (options->control) { - if (options->input_mode == SC_INPUT_MODE_INJECT) { - LOGD("Starting in inject mode because of --input-mode=inject"); - } else { - LOGD("Starting in HID over AOAv2 mode"); + if (options->input_mode == SC_INPUT_MODE_HID) { + LOGD("Starting in HID over AOAv2 mode because of --input-mode=hid"); aoa_initialized = aoa_init(&s->aoa, options); if (aoa_initialized) { hid_keyboard_initialized = hid_keyboard_init(&s->hid_keyboard, @@ -437,21 +435,10 @@ scrcpy(const struct scrcpy_options *options) { LOGD("Successfully set up HID over AOAv2 mode"); } else { LOGW("Failed to set up HID over AOAv2 mode"); - if (options->input_mode == SC_INPUT_MODE_HID) { - // HID is specified explicitly, and will exit if failed. - LOGE("Remove --input-mode=hid from parameters to allow input mode fallback"); - goto end; - } - LOGW("Fallback to inject mode"); - if (hid_keyboard_initialized) { - hid_keyboard_destroy(&s->hid_keyboard); - hid_keyboard_initialized = false; - } - if (aoa_initialized) { - aoa_destroy(&s->aoa); - aoa_initialized = false; - } + goto end; } + } else { + LOGD("Starting in inject mode"); } } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 27abe517ef..b5cf51b447 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -45,7 +45,6 @@ enum sc_shortcut_mod { }; enum sc_input_mode { - SC_INPUT_MODE_AUTO, SC_INPUT_MODE_HID, SC_INPUT_MODE_INJECT }; @@ -119,7 +118,7 @@ struct scrcpy_options { .v4l2_device = NULL, \ .log_level = SC_LOG_LEVEL_INFO, \ .record_format = SC_RECORD_FORMAT_AUTO, \ - .input_mode = SC_INPUT_MODE_AUTO, \ + .input_mode = SC_INPUT_MODE_INJECT, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .last = DEFAULT_LOCAL_PORT_RANGE_LAST, \ From 101c794c0cf7eae2512978e35db0bbe634cfa167 Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Tue, 21 Sep 2021 18:41:51 +0800 Subject: [PATCH 5/7] Add tips for non-QWERTY users --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1325f792da..1f1e4f4973 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,12 @@ scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed Serial number can be found by `adb get-serialno`. +If you are a non-QWERTY keyboard user and using HID mode, please remember to set +correct physical keyboard layout manually in Android settings, because scrcpy +just forwards scancodes to Android device and Android system is responsible for +converting scancodes to correct keycode on Android device (your system does this +on your PC). + #### Lock video orientation From b2088c51c895efffb51ae7f1f45f5055575ab3ec Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Sat, 25 Sep 2021 20:43:25 +0800 Subject: [PATCH 6/7] Fix report descriptor comment --- app/src/hid_keyboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 1105d95aa4..55fcbca017 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -36,7 +36,7 @@ unsigned char kb_report_desc_buffer[] = { // Report ID (1) 0x85, 0x01, - // Usage Page (Keyboard) + // Usage Page (Key Codes) 0x05, 0x07, // Usage Minimum (224) 0x19, 0xE0, From 8074bdbd58af08cdd63f0a70bd379557eb507934 Mon Sep 17 00:00:00 2001 From: Alynx Zhou Date: Sat, 25 Sep 2021 20:59:24 +0800 Subject: [PATCH 7/7] Remove media keys support for HID keyboard --- app/src/hid_keyboard.c | 106 +---------------------------------------- app/src/hid_keyboard.h | 16 ------- 2 files changed, 2 insertions(+), 120 deletions(-) diff --git a/app/src/hid_keyboard.c b/app/src/hid_keyboard.c index 55fcbca017..08d3f97e2b 100644 --- a/app/src/hid_keyboard.c +++ b/app/src/hid_keyboard.c @@ -34,7 +34,7 @@ unsigned char kb_report_desc_buffer[] = { // Collection (Application) 0xA1, 0x01, // Report ID (1) - 0x85, 0x01, + 0x85, HID_KEYBOARD_REPORT_ID, // Usage Page (Key Codes) 0x05, 0x07, @@ -97,48 +97,6 @@ unsigned char kb_report_desc_buffer[] = { // Input (Data, Array): Keys 0x81, 0x00, - // End Collection - 0xC0, - - // Usage Page (Consumer) - 0x05, 0x0C, - // Usage (Consumer Control) - 0x09, 0x01, - - // Collection (Application) - 0xA1, 0x01, - // Report ID (2) - 0x85, 0x02, - - // Usage Page (Consumer) - 0x05, 0x0C, - // Usage (Scan Next Track) - 0x09, 0xB5, - // Usage (Scan Previous Track) - 0x09, 0xB6, - // Usage (Stop) - 0x09, 0xB7, - // Usage (Eject) - 0x09, 0xB8, - // Usage (Play/Pause) - 0x09, 0xCD, - // Usage (Mute) - 0x09, 0xE2, - // Usage (Volume Increment) - 0x09, 0xE9, - // Usage (Volume Decrement) - 0x09, 0xEA, - // Logical Minimum (0) - 0x15, 0x00, - // Logical Maximum (1) - 0x25, 0x01, - // Report Size (1) - 0x75, 0x01, - // Report Count (8) - 0x95, 0x08, - // Input (Data, Array) - 0x81, 0x02, - // End Collection 0xC0 }; @@ -156,16 +114,6 @@ static unsigned char *create_hid_keyboard_event(void) { return buffer; } -static unsigned char *create_hid_media_event(void) { - unsigned char *buffer = malloc(sizeof(*buffer) * HID_MEDIA_EVENT_SIZE); - if (!buffer) { - return NULL; - } - buffer[0] = HID_MEDIA_REPORT_ID; - buffer[1] = HID_MEDIA_KEY_UNDEFINED; - return buffer; -} - bool hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa) { kb->aoa = aoa; @@ -268,56 +216,6 @@ convert_hid_keyboard_event(struct hid_keyboard *kb, struct hid_event *hid_event, return true; } -inline static bool -convert_hid_media_event(struct hid_event *hid_event, - const SDL_KeyboardEvent *event) { - // Re-calculate pressed keys every time. - unsigned char media_key = HID_MEDIA_KEY_UNDEFINED; - - SDL_Scancode scancode = event->keysym.scancode; - if (scancode == SDL_SCANCODE_AUDIONEXT) { - media_key |= HID_MEDIA_KEY_NEXT; - } - if (scancode == SDL_SCANCODE_AUDIOPREV) { - media_key |= HID_MEDIA_KEY_PREVIOUS; - } - if (scancode == SDL_SCANCODE_AUDIOSTOP) { - media_key |= HID_MEDIA_KEY_STOP; - } - if (scancode == SDL_SCANCODE_EJECT) { - media_key |= HID_MEDIA_KEY_EJECT; - } - if (scancode == SDL_SCANCODE_AUDIOPLAY) { - media_key |= HID_MEDIA_KEY_PLAY_PAUSE; - } - if (scancode == SDL_SCANCODE_AUDIOMUTE) { - media_key |= HID_MEDIA_KEY_MUTE; - } - if (scancode == SDL_SCANCODE_AUDIOSTOP) { - media_key |= HID_MEDIA_KEY_STOP; - } - // SDL has no equivalence for HID_MEDIA_KEY_VOLUME_UP and - // HID_MEDIA_KEY_VOLUME_DOWN, it does have SDL_SCANCODE_VOLUMEUP and - // SDL_SCANCODE_VOLUMEDOWN, but they are under Usage Page (0x07), - // which should be a keyboard event. - - // Not all other keys are Usage Page 0x0C, - // we return NULL for unsupported keys. - if (media_key == HID_MEDIA_KEY_UNDEFINED) { - return false; - } - - hid_event->buffer = create_hid_media_event(); - if (!hid_event->buffer) { - return false; - } - hid_event->size = HID_MEDIA_EVENT_SIZE; - - hid_event->buffer[HID_MEDIA_KEY_INDEX] = media_key; - - return true; -} - bool hid_keyboard_convert_event(struct hid_keyboard *kb, struct hid_event *hid_event, const SDL_KeyboardEvent *event) { @@ -337,7 +235,7 @@ hid_keyboard_convert_event(struct hid_keyboard *kb, return convert_hid_keyboard_event(kb, hid_event, event); } else { // Others. - return convert_hid_media_event(hid_event, event); + return false; } } diff --git a/app/src/hid_keyboard.h b/app/src/hid_keyboard.h index 75d832904b..25dfa67385 100644 --- a/app/src/hid_keyboard.h +++ b/app/src/hid_keyboard.h @@ -32,22 +32,6 @@ #define HID_KEYBOARD_REPORT_ID 0x01 #define HID_KEYBOARD_RESERVED 0x00 #define HID_KEYBOARD_ERROR_ROLL_OVER 0x01 -#define HID_MEDIA_EVENT_SIZE 2 -#define HID_MEDIA_REPORT_ID 0x02 -#define HID_MEDIA_KEY_INDEX (HID_REPORT_ID_INDEX + 1) -/** - * Media keys handle as mask so we define them here. - * Currently not used because desktop environment catches them before window. - */ -#define HID_MEDIA_KEY_UNDEFINED 0x00 -#define HID_MEDIA_KEY_NEXT (1 << 0) -#define HID_MEDIA_KEY_PREVIOUS (1 << 1) -#define HID_MEDIA_KEY_STOP (1 << 2) -#define HID_MEDIA_KEY_EJECT (1 << 3) -#define HID_MEDIA_KEY_PLAY_PAUSE (1 << 4) -#define HID_MEDIA_KEY_MUTE (1 << 5) -#define HID_MEDIA_KEY_VOLUME_UP (1 << 6) -#define HID_MEDIA_KEY_VOLUME_DOWN (1 << 7) // See "SDL2/SDL_scancode.h". // Maybe SDL_Keycode is used by most people,