diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 904ccdeb..8cc0b157 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -120,7 +120,7 @@ _scrcpy() { return ;; --mouse) - COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur")) + COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur")) return ;; --orientation|--display-orientation) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index f81d2b22..1cf2ae41 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -44,7 +44,7 @@ arguments=( '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)' {-m,--max-size=}'[Limit both the width and height of the video to value]' '--max-fps=[Limit the frame rate of screen capture]' - '--mouse[Set the mouse input mode]:mode:(disabled sdk aoa)' + '--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-N,--no-playback}'[Disable video and audio playback]' '--no-audio[Disable audio forwarding]' diff --git a/app/meson.build b/app/meson.build index 3695e0f9..b0a6aadb 100644 --- a/app/meson.build +++ b/app/meson.build @@ -36,6 +36,7 @@ src = [ 'src/trait/frame_source.c', 'src/trait/packet_source.c', 'src/uhid/keyboard_uhid.c', + 'src/uhid/mouse_uhid.c', 'src/uhid/uhid_output.c', 'src/util/acksync.c', 'src/util/audiobuf.c', diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7e856664..8c0c4cc6 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -240,13 +240,14 @@ Limit the framerate of screen capture (officially supported since Android 10, bu .BI "\-\-mouse " mode Select how to send mouse inputs to the device. -Possible values are "disabled", "sdk" and "aoa": +Possible values are "disabled", "sdk", "uhid" and "aoa": - "disabled" does not send mouse inputs to the device. - "sdk" uses the Android system API to deliver mouse events to applications. + - "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device. - "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB. -In "aoa" mode, the computer mouse is captured to control the device directly (relative mouse mode). +In "uhid" and "aoa" modes, the computer mouse is captured to control the device directly (relative mouse mode). LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. diff --git a/app/src/cli.c b/app/src/cli.c index c1c68e92..dceb8fff 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -462,14 +462,17 @@ static const struct sc_option options[] = { .longopt = "mouse", .argdesc = "mode", .text = "Select how to send mouse inputs to the device.\n" - "Possible values are \"disabled\", \"sdk\" and \"aoa\".\n" + "Possible values are \"disabled\", \"sdk\", \"uhid\" and " + "\"aoa\".\n" "\"disabled\" does not send mouse inputs to the device.\n" "\"sdk\" uses the Android system API to deliver mouse events" "to applications.\n" + "\"uhid\" simulates a physical HID mouse using the Linux UHID " + "kernel module on the device.\n" "\"aoa\" simulates a physical mouse using the AOAv2 protocol. " "It may only work over USB.\n" - "In \"aoa\" mode, the computer mouse is captured to control " - "the device directly (relative mouse mode).\n" + "In \"uhid\" and \"aoa\" modes, the computer mouse is captured " + "to control the device directly (relative mouse mode).\n" "LAlt, LSuper or RSuper toggle the capture mode, to give " "control of the mouse back to the computer.\n" "Also see --keyboard.", @@ -1979,6 +1982,11 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) { return true; } + if (!strcmp(optarg, "uhid")) { + *mode = SC_MOUSE_INPUT_MODE_UHID; + return true; + } + if (!strcmp(optarg, "aoa")) { #ifdef HAVE_USB *mode = SC_MOUSE_INPUT_MODE_AOA; @@ -1989,7 +1997,7 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) { #endif } - LOGE("Unsupported mouse: %s (expected disabled, sdk or aoa)", optarg); + LOGE("Unsupported mouse: %s (expected disabled, sdk, uhid or aoa)", optarg); return false; } diff --git a/app/src/options.h b/app/src/options.h index 6d62fac0..5445e7c8 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -151,6 +151,7 @@ enum sc_mouse_input_mode { SC_MOUSE_INPUT_MODE_AUTO, SC_MOUSE_INPUT_MODE_DISABLED, SC_MOUSE_INPUT_MODE_SDK, + SC_MOUSE_INPUT_MODE_UHID, SC_MOUSE_INPUT_MODE_AOA, }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7b1a6d5c..a40a4dec 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -26,6 +26,7 @@ #include "screen.h" #include "server.h" #include "uhid/keyboard_uhid.h" +#include "uhid/mouse_uhid.h" #ifdef HAVE_USB # include "usb/aoa_hid.h" # include "usb/keyboard_aoa.h" @@ -73,6 +74,7 @@ struct scrcpy { }; union { struct sc_mouse_sdk mouse_sdk; + struct sc_mouse_uhid mouse_uhid; #ifdef HAVE_USB struct sc_mouse_aoa mouse_aoa; #endif @@ -682,6 +684,12 @@ aoa_hid_end: if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); mp = &s->mouse_sdk.mouse_processor; + } else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) { + bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller); + if (!ok) { + goto end; + } + mp = &s->mouse_uhid.mouse_processor; } sc_controller_configure(&s->controller, acksync, uhid_devices); diff --git a/app/src/uhid/mouse_uhid.c b/app/src/uhid/mouse_uhid.c new file mode 100644 index 00000000..77446f9e --- /dev/null +++ b/app/src/uhid/mouse_uhid.c @@ -0,0 +1,89 @@ +#include "mouse_uhid.h" + +#include "hid/hid_mouse.h" +#include "input_events.h" +#include "util/log.h" + +/** Downcast mouse processor to mouse_uhid */ +#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor) + +#define UHID_MOUSE_ID 2 + +static void +sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse, + const struct sc_hid_event *event, const char *name) { + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT; + msg.uhid_input.id = UHID_MOUSE_ID; + + assert(event->size <= SC_HID_MAX_SIZE); + memcpy(msg.uhid_input.data, event->data, event->size); + msg.uhid_input.size = event->size; + + if (!sc_controller_push_msg(mouse->controller, &msg)) { + LOGE("Could not send UHID_INPUT message (%s)", name); + } +} + +static void +sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, + const struct sc_mouse_motion_event *event) { + struct sc_mouse_uhid *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_motion(&hid_event, event); + + sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion"); +} + +static void +sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp, + const struct sc_mouse_click_event *event) { + struct sc_mouse_uhid *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_click(&hid_event, event); + + sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click"); +} + +static void +sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp, + const struct sc_mouse_scroll_event *event) { + struct sc_mouse_uhid *mouse = DOWNCAST(mp); + + struct sc_hid_event hid_event; + sc_hid_mouse_event_from_scroll(&hid_event, event); + + sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll"); +} + +bool +sc_mouse_uhid_init(struct sc_mouse_uhid *mouse, + struct sc_controller *controller) { + mouse->controller = controller; + + static const struct sc_mouse_processor_ops ops = { + .process_mouse_motion = sc_mouse_processor_process_mouse_motion, + .process_mouse_click = sc_mouse_processor_process_mouse_click, + .process_mouse_scroll = sc_mouse_processor_process_mouse_scroll, + // Touch events not supported (coordinates are not relative) + .process_touch = NULL, + }; + + mouse->mouse_processor.ops = &ops; + + mouse->mouse_processor.relative_mode = true; + + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE; + msg.uhid_create.id = UHID_MOUSE_ID; + msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC; + msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN; + if (!sc_controller_push_msg(controller, &msg)) { + LOGE("Could not send UHID_CREATE message (mouse)"); + return false; + } + + return true; +} diff --git a/app/src/uhid/mouse_uhid.h b/app/src/uhid/mouse_uhid.h new file mode 100644 index 00000000..f117ba97 --- /dev/null +++ b/app/src/uhid/mouse_uhid.h @@ -0,0 +1,19 @@ +#ifndef SC_MOUSE_UHID_H +#define SC_MOUSE_UHID_H + +#include + +#include "controller.h" +#include "trait/mouse_processor.h" + +struct sc_mouse_uhid { + struct sc_mouse_processor mouse_processor; // mouse processor trait + + struct sc_controller *controller; +}; + +bool +sc_mouse_uhid_init(struct sc_mouse_uhid *mouse, + struct sc_controller *controller); + +#endif