diff --git a/app/scrcpy.1 b/app/scrcpy.1
index 8ca4a77302..beaa99ab4d 100644
--- a/app/scrcpy.1
+++ b/app/scrcpy.1
@@ -642,7 +642,11 @@ Enable/disable FPS counter (print frames/second in logs)
.TP
.B Ctrl+click-and-move
-Pinch-to-zoom from the center of the screen
+Pinch-to-zoom and rotate from the center of the screen
+
+.TP
+.B Shift+click-and-move
+Tilt (slide vertically with two fingers)
.TP
.B Drag & drop APK file
diff --git a/app/src/cli.c b/app/src/cli.c
index fd4525f557..c580c95978 100644
--- a/app/src/cli.c
+++ b/app/src/cli.c
@@ -947,7 +947,11 @@ static const struct sc_shortcut shortcuts[] = {
},
{
.shortcuts = { "Ctrl+click-and-move" },
- .text = "Pinch-to-zoom from the center of the screen",
+ .text = "Pinch-to-zoom and rotate from the center of the screen",
+ },
+ {
+ .shortcuts = { "Shift+click-and-move" },
+ .text = "Tilt (slide vertically with two fingers)",
},
{
.shortcuts = { "Drag & drop APK file" },
diff --git a/app/src/input_manager.c b/app/src/input_manager.c
index 9a4878363f..76cfbd9287 100644
--- a/app/src/input_manager.c
+++ b/app/src/input_manager.c
@@ -76,6 +76,8 @@ sc_input_manager_init(struct sc_input_manager *im,
im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false;
+ im->vfinger_invert_x = false;
+ im->vfinger_invert_y = false;
im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0;
@@ -347,9 +349,14 @@ simulate_virtual_finger(struct sc_input_manager *im,
}
static struct sc_point
-inverse_point(struct sc_point point, struct sc_size size) {
- point.x = size.width - point.x;
- point.y = size.height - point.y;
+inverse_point(struct sc_point point, struct sc_size size,
+ bool invert_x, bool invert_y) {
+ if (invert_x) {
+ point.x = size.width - point.x;
+ }
+ if (invert_y) {
+ point.y = size.height - point.y;
+ }
return point;
}
@@ -605,7 +612,9 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
- struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
+ struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
+ im->vfinger_invert_x,
+ im->vfinger_invert_y);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
@@ -726,7 +735,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
return;
}
- // Pinch-to-zoom simulation.
+ // Pinch-to-zoom, rotate and tilt 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
@@ -735,14 +744,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
//
// In other words, the center of the rotation/scaling is the center of the
// screen.
-#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
+ //
+ // To simulate a tilt gesture (a vertical slide with two fingers), Shift
+ // can be used instead of Ctrl. The "virtual finger" has a position
+ // inverted with respect to the vertical axis of symmetry in the middle of
+ // the screen.
+ const SDL_Keymod keymod = SDL_GetModState();
+ const bool ctrl_pressed = keymod & KMOD_CTRL;
+ const bool shift_pressed = keymod & KMOD_SHIFT;
if (event->button == SDL_BUTTON_LEFT &&
- ((down && !im->vfinger_down && CTRL_PRESSED) ||
+ ((down && !im->vfinger_down &&
+ ((ctrl_pressed && !shift_pressed) ||
+ (!ctrl_pressed && shift_pressed))) ||
(!down && im->vfinger_down))) {
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
- struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
+ if (down) {
+ im->vfinger_invert_x = ctrl_pressed || shift_pressed;
+ im->vfinger_invert_y = ctrl_pressed;
+ }
+ struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
+ im->vfinger_invert_x,
+ im->vfinger_invert_y);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
diff --git a/app/src/input_manager.h b/app/src/input_manager.h
index b5a762eb9e..2ce11b03d7 100644
--- a/app/src/input_manager.h
+++ b/app/src/input_manager.h
@@ -32,6 +32,8 @@ struct sc_input_manager {
} sdl_shortcut_mods;
bool vfinger_down;
+ bool vfinger_invert_x;
+ bool vfinger_invert_y;
// Tracks the number of identical consecutive shortcut key down events.
// Not to be confused with event->repeat, which counts the number of
diff --git a/doc/control.md b/doc/control.md
index 0b06077544..595e910ec6 100644
--- a/doc/control.md
+++ b/doc/control.md
@@ -85,7 +85,7 @@ way as MOD+Shift+v).
To disable automatic clipboard synchronization, use
`--no-clipboard-autosync`.
-## Pinch-to-zoom
+## Pinch-to-zoom, rotate and tilt simulation
To simulate "pinch-to-zoom": Ctrl+_click-and-move_.
@@ -93,8 +93,12 @@ More precisely, hold down 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.
+To simulate a tilt gesture: Shift+_click-and-move-up-or-down_.
+
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
-at a location inverted through the center of the screen.
+at a location inverted through the center of the screen. When pressing
+Ctrl the x and y coordinates are inverted. Using Shift
+only inverts x.
## Key repeat
diff --git a/doc/shortcuts.md b/doc/shortcuts.md
index c0fc28421b..21bccbd950 100644
--- a/doc/shortcuts.md
+++ b/doc/shortcuts.md
@@ -49,7 +49,8 @@ _[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_
+ | Pinch-to-zoom/rotate | Ctrl+_click-and-move_
+ | Tilt (slide vertically with 2 fingers) | Shift+_click-and-move_
| Drag & drop APK file | Install APK from computer
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)