diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 1938fc4f..5c9fed2f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -121,7 +121,7 @@ _scrcpy() { return ;; --mouse) - COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur")) + COMPREPLY=($(compgen -W 'disabled sdk aoa uhid' -- "$cur")) return ;; --orientation|--display-orientation) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 066c4f70..65141d72 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -45,7 +45,7 @@ arguments=( {-m,--max-size=}'[Limit both the width and height of the video to value]' {-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]' '--max-fps=[Limit the frame rate of screen capture]' - '--mouse[Set the mouse input mode]:mode:(disabled sdk aoa)' + '--mouse[Set the mouse input mode]:mode:(disabled sdk aoa uhid)' {-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 01faa9a6..20200b77 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -247,8 +247,9 @@ Possible values are "disabled", "sdk" and "aoa": - "disabled" does not send mouse inputs to the device. - "sdk" uses the Android system API to deliver mouse events to applications. - "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB. + - "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device. -In "aoa" mode, the computer mouse is captured to control the device directly (relative mouse mode). +In "aoa" and "uhid" 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 50041cbf..24e45de3 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -468,8 +468,10 @@ static const struct sc_option options[] = { "events to applications.\n" "\"aoa\" simulates a physical mouse using the AOAv2 protocol\n" "It may only work over USB.\n" - "In \"aoa\" mode, the computer mouse is captured to control " - "the device directly (relative mouse mode).\n" + "\"uhid\" simulates a physical HID mouse using the Linux UHID " + "kernel module on the device." + "In \"aoa\" and \"uhid\" 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.", @@ -1989,7 +1991,12 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) { #endif } - LOGE("Unsupported mouse: %s (expected disabled, sdk or aoa)", optarg); + if (!strcmp(optarg, "uhid")) { + *mode = SC_MOUSE_INPUT_MODE_UHID; + return true; + } + + LOGE("Unsupported mouse: %s (expected disabled, sdk, aoa or uhid)", optarg); return false; } diff --git a/app/src/options.h b/app/src/options.h index 37e42bd9..019f7ea5 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -152,6 +152,7 @@ enum sc_mouse_input_mode { SC_MOUSE_INPUT_MODE_DISABLED, SC_MOUSE_INPUT_MODE_SDK, SC_MOUSE_INPUT_MODE_AOA, + SC_MOUSE_INPUT_MODE_UHID, }; enum sc_key_inject_mode { diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5c798d3e..c52ac24d 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" @@ -76,6 +77,7 @@ struct scrcpy { #ifdef HAVE_USB struct sc_mouse_aoa mouse_aoa; #endif + struct sc_mouse_uhid mouse_uhid; }; struct sc_timeout timeout; }; @@ -681,6 +683,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..c3f78fd1 --- /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 *kb, + 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(kb->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