diff --git a/README.md b/README.md index ee6978a2..3f6d6b68 100644 --- a/README.md +++ b/README.md @@ -546,6 +546,19 @@ into the device clipboard. As a consequence, any Android application could read its content. You should avoid to paste sensitive content (like passwords) that way. + +#### Pinch-to-zoom + +To simulate "pinch-to-zoom": Ctrl+_click-and-move_. + +More precisely, hold Ctrl while pressing the left-click button. Until +the left-click button is released, all mouse movements scale and rotate the +content (if supported by the app) relative to the center of the screen. + +Concretely, scrcpy generates additional touch events from a "virtual finger" at +a location inverted through the center of the screen. + + #### Text injection preference There are two kinds of [events][textevents] generated when typing text: @@ -659,6 +672,7 @@ _[Super] is typically the Windows or Cmd key._ | Synchronize clipboards and paste³ | MOD+v | Inject computer clipboard text | MOD+Shift+v | Enable/disable FPS counter (on stdout) | MOD+i + | Pinch-to-zoom | Ctrl+_click-and-move_ _¹Double-click on black borders to remove them._ _²Right-click turns the screen on if it was off, presses BACK otherwise._ diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 49fa78dc..4f3a8b9c 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -316,6 +316,10 @@ Inject computer clipboard text as a sequence of key events .B MOD+i Enable/disable FPS counter (print frames/second in logs) +.TP +.B Ctrl+click-and-move +Pinch-to-zoom from the center of the screen + .TP .B Drag & drop APK file Install APK from computer diff --git a/app/src/cli.c b/app/src/cli.c index c957b22f..c960727e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -279,6 +279,9 @@ scrcpy_print_usage(const char *arg0) { " MOD+i\n" " Enable/disable FPS counter (print frames/second in logs)\n" "\n" + " Ctrl+click-and-move\n" + " Pinch-to-zoom from the center of the screen\n" + "\n" " Drag & drop APK file\n" " Install APK from computer\n" "\n", diff --git a/app/src/control_msg.h b/app/src/control_msg.h index e0b480de..6e3f239c 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -17,6 +17,7 @@ #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6) #define POINTER_ID_MOUSE UINT64_C(-1); +#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2); enum control_msg_type { CONTROL_MSG_TYPE_INJECT_KEYCODE, diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 1d73980c..962db1d3 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -70,6 +70,8 @@ input_manager_init(struct input_manager *im, im->sdl_shortcut_mods.data[i] = sdl_mod; } im->sdl_shortcut_mods.count = shortcut_mods->count; + + im->vfinger_down = false; } static void @@ -299,6 +301,36 @@ input_manager_process_text_input(struct input_manager *im, } } +static bool +simulate_virtual_finger(struct input_manager *im, + enum android_motionevent_action action, + struct point point) { + bool up = action == AMOTION_EVENT_ACTION_UP; + + struct control_msg msg; + msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; + msg.inject_touch_event.action = action; + msg.inject_touch_event.position.screen_size = im->screen->frame_size; + msg.inject_touch_event.position.point = point; + msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER; + msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; + msg.inject_touch_event.buttons = 0; + + if (!controller_push_msg(im->controller, &msg)) { + LOGW("Could not request 'inject virtual finger event'"); + return false; + } + + return true; +} + +static struct point +inverse_point(struct point point, struct size size) { + point.x = size.width - point.x; + point.y = size.height - point.y; + return point; +} + static bool convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to, bool prefer_text, uint32_t repeat) { @@ -512,10 +544,18 @@ input_manager_process_mouse_motion(struct input_manager *im, return; } struct control_msg msg; - if (convert_mouse_motion(event, im->screen, &msg)) { - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse motion event'"); - } + if (!convert_mouse_motion(event, im->screen, &msg)) { + return; + } + + if (!controller_push_msg(im->controller, &msg)) { + LOGW("Could not request 'inject mouse motion event'"); + } + + if (im->vfinger_down) { + struct point mouse = msg.inject_touch_event.position.point; + struct point vfinger = inverse_point(mouse, im->screen->frame_size); + simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); } } @@ -587,7 +627,9 @@ input_manager_process_mouse_button(struct input_manager *im, // simulated from touch events, so it's a duplicate return; } - if (event->type == SDL_MOUSEBUTTONDOWN) { + + bool down = event->type == SDL_MOUSEBUTTONDOWN; + if (down) { if (control && event->button == SDL_BUTTON_RIGHT) { press_back_or_turn_screen_on(im->controller); return; @@ -618,10 +660,36 @@ input_manager_process_mouse_button(struct input_manager *im, } struct control_msg msg; - if (convert_mouse_button(event, im->screen, &msg)) { - if (!controller_push_msg(im->controller, &msg)) { - LOGW("Could not request 'inject mouse button event'"); + if (!convert_mouse_button(event, im->screen, &msg)) { + return; + } + + if (!controller_push_msg(im->controller, &msg)) { + LOGW("Could not request 'inject mouse button event'"); + return; + } + + // Pinch-to-zoom simulation. + // + // If Ctrl is hold when the left-click button is pressed, then + // pinch-to-zoom mode is enabled: on every mouse event until the left-click + // button is released, an additional "virtual finger" event is generated, + // having a position inverted through the center of the screen. + // + // In other words, the center of the rotation/scaling is the center of the + // screen. +#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) + if ((down && !im->vfinger_down && CTRL_PRESSED) + || (!down && im->vfinger_down)) { + struct point mouse = msg.inject_touch_event.position.point; + struct point vfinger = inverse_point(mouse, im->screen->frame_size); + enum android_motionevent_action action = down + ? AMOTION_EVENT_ACTION_DOWN + : AMOTION_EVENT_ACTION_UP; + if (!simulate_virtual_finger(im, action, vfinger)) { + return; } + im->vfinger_down = down; } } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 8811c457..c3756e40 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -30,6 +30,8 @@ struct input_manager { unsigned data[SC_MAX_SHORTCUT_MODS]; unsigned count; } sdl_shortcut_mods; + + bool vfinger_down; }; void