Add pinch-to-zoom simulation
If Ctrl is hold when the left-click button is pressed, enable pinch-to-zoom to scale and rotate relative to the center of the screen. Fixes #24 <https://github.com/Genymobile/scrcpy/issues/24>
This commit is contained in:
parent
95f1ea0d80
commit
198346d148
6 changed files with 100 additions and 8 deletions
14
README.md
14
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
|
its content. You should avoid to paste sensitive content (like passwords) that
|
||||||
way.
|
way.
|
||||||
|
|
||||||
|
|
||||||
|
#### Pinch-to-zoom
|
||||||
|
|
||||||
|
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
||||||
|
|
||||||
|
More precisely, hold <kbd>Ctrl</kbd> 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
|
#### Text injection preference
|
||||||
|
|
||||||
There are two kinds of [events][textevents] generated when typing text:
|
There are two kinds of [events][textevents] generated when typing text:
|
||||||
|
@ -659,6 +672,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||||
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
|
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
|
|
|
@ -316,6 +316,10 @@ Inject computer clipboard text as a sequence of key events
|
||||||
.B MOD+i
|
.B MOD+i
|
||||||
Enable/disable FPS counter (print frames/second in logs)
|
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
|
.TP
|
||||||
.B Drag & drop APK file
|
.B Drag & drop APK file
|
||||||
Install APK from computer
|
Install APK from computer
|
||||||
|
|
|
@ -279,6 +279,9 @@ scrcpy_print_usage(const char *arg0) {
|
||||||
" MOD+i\n"
|
" MOD+i\n"
|
||||||
" Enable/disable FPS counter (print frames/second in logs)\n"
|
" Enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" Ctrl+click-and-move\n"
|
||||||
|
" Pinch-to-zoom from the center of the screen\n"
|
||||||
|
"\n"
|
||||||
" Drag & drop APK file\n"
|
" Drag & drop APK file\n"
|
||||||
" Install APK from computer\n"
|
" Install APK from computer\n"
|
||||||
"\n",
|
"\n",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
#define POINTER_ID_MOUSE UINT64_C(-1);
|
||||||
|
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
|
||||||
|
|
||||||
enum control_msg_type {
|
enum control_msg_type {
|
||||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
|
|
|
@ -70,6 +70,8 @@ input_manager_init(struct input_manager *im,
|
||||||
im->sdl_shortcut_mods.data[i] = sdl_mod;
|
im->sdl_shortcut_mods.data[i] = sdl_mod;
|
||||||
}
|
}
|
||||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||||
|
|
||||||
|
im->vfinger_down = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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
|
static bool
|
||||||
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
||||||
bool prefer_text, uint32_t repeat) {
|
bool prefer_text, uint32_t repeat) {
|
||||||
|
@ -512,10 +544,18 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_mouse_motion(event, im->screen, &msg)) {
|
if (!convert_mouse_motion(event, im->screen, &msg)) {
|
||||||
if (!controller_push_msg(im->controller, &msg)) {
|
return;
|
||||||
LOGW("Could not request 'inject mouse motion event'");
|
}
|
||||||
}
|
|
||||||
|
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
|
// simulated from touch events, so it's a duplicate
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
|
||||||
|
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||||
|
if (down) {
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||||
press_back_or_turn_screen_on(im->controller);
|
press_back_or_turn_screen_on(im->controller);
|
||||||
return;
|
return;
|
||||||
|
@ -618,10 +660,36 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
if (convert_mouse_button(event, im->screen, &msg)) {
|
if (!convert_mouse_button(event, im->screen, &msg)) {
|
||||||
if (!controller_push_msg(im->controller, &msg)) {
|
return;
|
||||||
LOGW("Could not request 'inject mouse button event'");
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ struct input_manager {
|
||||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
unsigned count;
|
unsigned count;
|
||||||
} sdl_shortcut_mods;
|
} sdl_shortcut_mods;
|
||||||
|
|
||||||
|
bool vfinger_down;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
Loading…
Reference in a new issue