This commit is contained in:
Brant Pastore 2026-04-18 22:32:49 -05:00 committed by GitHub
commit a3f594db20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 898 additions and 6 deletions

View file

@ -98,6 +98,7 @@ enum {
OPT_ORIENTATION,
OPT_KEYBOARD,
OPT_MOUSE,
OPT_SCROLL_ACTION,
OPT_HID_KEYBOARD_DEPRECATED,
OPT_HID_MOUSE_DEPRECATED,
OPT_NO_WINDOW,
@ -114,6 +115,7 @@ enum {
OPT_NO_VD_SYSTEM_DECORATIONS,
OPT_NO_VD_DESTROY_CONTENT,
OPT_DISPLAY_IME_POLICY,
OPT_OVERLAY,
};
struct sc_option {
@ -205,6 +207,11 @@ static const struct sc_option options[] = {
"This feature is only available with --audio-source=playback."
},
{
.longopt_id = OPT_OVERLAY,
.longopt = "overlay",
.text = "Keep the on-screen debug overlay visible (useful for debugging).",
},
{
.longopt_id = OPT_AUDIO_ENCODER,
.longopt = "audio-encoder",
@ -2230,6 +2237,22 @@ parse_gamepad(const char *optarg, enum sc_gamepad_input_mode *mode) {
return false;
}
static bool
parse_scroll_action(const char *optarg, enum sc_scroll_action *action) {
if (!strcmp(optarg, "scroll")) {
*action = SC_SCROLL_ACTION_SCROLL;
return true;
}
if (!strcmp(optarg, "zoom")) {
*action = SC_SCROLL_ACTION_ZOOM;
return true;
}
LOGE("Unsupported scroll-action: %s (expected scroll or zoom)", optarg);
return false;
}
static bool
parse_time_limit(const char *s, sc_tick *tick) {
long value;
@ -2438,9 +2461,17 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case OPT_SCROLL_ACTION:
if (!parse_scroll_action(optarg, &opts->scroll_action)) {
return false;
}
break;
case OPT_NO_MOUSE_HOVER:
opts->mouse_hover = false;
break;
case OPT_OVERLAY:
opts->overlay_persistent = true;
break;
case OPT_HID_MOUSE_DEPRECATED:
LOGE("--hid-mouse has been removed, use --mouse=aoa or "
"--mouse=uhid instead.");

View file

@ -1,5 +1,163 @@
#include "display.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <SDL2/SDL.h>
// Render the overlay UI: checkboxes, labels, and info line
void sc_render_overlay_ui(struct sc_display *display) {
// 8x12 font for ASCII 32..127 (printable)
static const uint8_t font8x12[96][12] = {
{0,0,0,0,0,0,0,0,0,0,0,0}, // (space)
{24,24,24,24,24,24,24,0,24,24,0,0}, // !
{54,54,54,36,0,0,0,0,0,0,0,0}, // "
{0,36,36,126,36,36,36,126,36,36,0,0}, // #
{8,62,73,72,62,9,73,62,8,8,0,0}, // $
{0,99,147,102,12,24,48,102,201,198,0,0}, // %
{28,34,34,20,40,82,73,70,61,0,0,0}, // &
{24,24,24,16,0,0,0,0,0,0,0,0}, // '
{12,24,48,48,48,48,48,48,24,12,0,0}, // (
{48,24,12,12,12,12,12,12,24,48,0,0}, // )
{0,0,36,24,126,24,36,0,0,0,0,0}, // *
{0,0,24,24,126,24,24,0,0,0,0,0}, // +
{0,0,0,0,0,0,0,24,24,16,0,0}, // ,
{0,0,0,0,126,0,0,0,0,0,0,0}, // -
{0,0,0,0,0,0,0,24,24,0,0,0}, // .
{0,3,6,12,24,48,96,192,128,0,0,0}, // /
{60,66,99,115,91,79,70,66,60,0,0,0}, // 0
{24,56,24,24,24,24,24,24,126,0,0,0}, // 1
{60,66,3,6,12,24,48,96,127,0,0,0}, // 2
{60,66,3,6,28,6,3,66,60,0,0,0}, // 3
{6,14,30,54,102,127,6,6,15,0,0,0}, // 4
{127,64,64,124,66,3,3,66,60,0,0,0}, // 5
{28,48,96,124,102,99,99,102,60,0,0,0}, // 6
{127,3,6,12,24,48,48,48,48,0,0,0}, // 7
{60,66,99,102,60,102,99,66,60,0,0,0}, // 8
{60,102,99,99,63,6,12,24,56,0,0,0}, // 9
{0,0,24,24,0,0,24,24,0,0,0,0}, // :
{0,0,24,24,0,0,24,24,16,0,0,0}, // ;
{12,24,48,96,192,96,48,24,12,0,0,0}, // <
{0,0,126,0,126,0,0,0,0,0,0,0}, // =
{48,24,12,6,3,6,12,24,48,0,0,0}, // >
{60,66,3,6,12,24,24,0,24,24,0,0}, // ?
{60,66,99,111,107,111,96,62,0,0,0,0}, // @
{24,60,102,102,126,102,102,102,231,0,0,0}, // A
{124,102,102,124,102,102,102,102,124,0,0,0}, // B
{60,102,96,96,96,96,96,102,60,0,0,0}, // C
{120,108,102,102,102,102,102,108,120,0,0,0}, // D
{126,96,96,124,96,96,96,96,126,0,0,0}, // E
{126,96,96,124,96,96,96,96,96,0,0,0}, // F
{60,102,96,96,110,102,102,102,60,0,0,0}, // G
{102,102,102,126,102,102,102,102,102,0,0,0}, // H
{60,24,24,24,24,24,24,24,60,0,0,0}, // I
{6,6,6,6,6,6,6,102,60,0,0,0}, // J
{102,108,120,112,120,108,102,102,102,0,0,0}, // K
{96,96,96,96,96,96,96,96,126,0,0,0}, // L
{99,119,127,107,99,99,99,99,99,0,0,0}, // M
{102,102,118,126,110,102,102,102,102,0,0,0}, // N
{60,102,99,99,99,99,99,102,60,0,0,0}, // O
{124,102,102,102,124,96,96,96,96,0,0,0}, // P
{60,102,99,99,99,99,107,102,61,0,0,0}, // Q
{124,102,102,102,124,108,102,102,102,0,0,0}, // R
{60,102,96,60,6,3,99,102,60,0,0,0}, // S
{126,24,24,24,24,24,24,24,24,0,0,0}, // T
{102,102,102,102,102,102,102,102,60,0,0,0}, // U
{99,99,99,99,99,99,54,28,8,0,0,0}, // V
{99,99,99,99,99,107,127,119,99,0,0,0}, // W
{99,99,54,28,8,28,54,99,99,0,0,0}, // X
{99,99,99,54,28,8,8,8,8,0,0,0}, // Y
{127,3,6,12,24,48,96,96,127,0,0,0}, // Z
{60,48,48,48,48,48,48,48,60,0,0,0}, // [
{0,192,96,48,24,12,6,3,0,0,0,0}, // backslash
{60,12,12,12,12,12,12,12,60,0,0,0}, // ]
{8,28,54,99,0,0,0,0,0,0,0,0}, // ^
{0,0,0,0,0,0,0,0,0,0,255,0}, // _
{24,24,24,12,0,0,0,0,0,0,0,0}, // `
{0,0,60,6,62,102,102,102,59,0,0,0}, // a
{96,96,124,102,102,102,102,102,124,0,0,0}, // b
{0,0,60,102,96,96,96,102,60,0,0,0}, // c
{6,6,62,102,102,102,102,102,62,0,0,0}, // d
{0,0,60,102,126,96,96,102,60,0,0,0}, // e
{28,54,48,120,48,48,48,48,120,0,0,0}, // f
{0,0,62,102,102,102,62,6,102,60,0,0}, // g
{96,96,124,102,102,102,102,102,102,0,0,0}, // h
{24,0,56,24,24,24,24,24,60,0,0,0}, // i
{6,0,6,6,6,6,6,102,102,60,0,0}, // j
{96,96,102,108,120,120,108,102,102,0,0,0}, // k
{56,24,24,24,24,24,24,24,60,0,0,0}, // l
{0,0,102,127,107,107,99,99,99,0,0,0}, // m
{0,0,124,102,102,102,102,102,102,0,0,0}, // n
{0,0,60,102,102,102,102,102,60,0,0,0}, // o
{0,0,124,102,102,102,124,96,96,240,0,0}, // p
{0,0,62,102,102,102,62,6,6,15,0,0}, // q
{0,0,124,102,96,96,96,96,240,0,0,0}, // r
{0,0,62,96,60,6,6,102,60,0,0,0}, // s
{16,16,124,16,16,16,16,16,14,0,0,0}, // t
{0,0,102,102,102,102,102,102,62,0,0,0}, // u
{0,0,102,102,102,102,102,60,24,0,0,0}, // v
{0,0,99,99,99,107,127,54,54,0,0,0}, // w
{0,0,102,102,60,24,60,102,102,0,0,0}, // x
{0,0,102,102,102,62,6,102,60,0,0,0}, // y
{0,0,126,12,24,48,96,96,126,0,0,0}, // z
{12,24,24,24,48,24,24,24,12,0,0,0}, // {
{24,24,24,24,24,24,24,24,24,0,0,0}, // |
{48,24,24,24,12,24,24,24,48,0,0,0}, // }
{0,0,0,51,102,204,0,0,0,0,0,0}, // ~
};
const int char_w = 8;
const int char_h = 12;
const int spacing = 2;
const int scale = 2;
// Compose overlay lines
char line1[64], line2[64], line3[128];
snprintf(line1, sizeof(line1), "%s Pinch Zoom on Scroll", display->overlay_pinch_zoom_enabled ? "[x]" : "[ ]");
snprintf(line2, sizeof(line2), "%s Overlay Toggle", display->overlay_toggle_enabled ? "[x]" : "[ ]");
snprintf(line3, sizeof(line3), "%s", display->overlay_text);
// Layout: 2 checkboxes + 1 info line
int lines = 3;
int maxlen = (int)strlen(line1);
if ((int)strlen(line2) > maxlen) maxlen = (int)strlen(line2);
if ((int)strlen(line3) > maxlen) maxlen = (int)strlen(line3);
int text_w = maxlen * (char_w * scale + spacing);
int text_h = lines * (char_h * scale) + (lines - 1) * (2 * scale);
SDL_SetRenderDrawBlendMode(display->renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(display->renderer, 0, 0, 0, 200); // dark semi-transparent background
SDL_Rect bg = { display->overlay_x, display->overlay_y, text_w + 8, text_h + 8 };
SDL_RenderFillRect(display->renderer, &bg);
SDL_SetRenderDrawColor(display->renderer, 255, 255, 255, 255); // white text for readability
int x = display->overlay_x + 4;
int y = display->overlay_y + 4;
const char *linestr[3] = { line1, line2, line3 };
for (int l = 0; l < lines; ++l) {
const char *text = linestr[l];
int len = (int)strlen(text);
int xx = x;
for (int i = 0; i < len; ++i) {
unsigned char c = text[i];
if (c < 32 || c > 127) {
xx += char_w * scale + spacing;
continue;
}
const uint8_t *glyph = font8x12[c - 32];
for (int row = 0; row < char_h; ++row) {
uint8_t bits = glyph[row];
for (int col = 0; col < char_w; ++col) {
if (bits & (1 << (7 - col))) {
SDL_Rect r = { xx + col * scale, y + row * scale,
scale, scale };
SDL_RenderFillRect(display->renderer, &r);
}
}
}
xx += char_w * scale + spacing;
}
y += char_h * scale + 2 * scale;
}
// Set overlay_w and overlay_h for click detection
display->overlay_w = text_w + 8;
display->overlay_h = text_h + 8;
}
#include <assert.h>
#include <inttypes.h>
#include <string.h>
@ -308,6 +466,9 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
if (!ok) {
return SC_DISPLAY_RESULT_PENDING;
}
SDL_RenderPresent(display->renderer);
return SC_DISPLAY_RESULT_OK;
}
SDL_Renderer *renderer = display->renderer;
@ -346,6 +507,11 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
}
}
// draw overlay UI (checkboxes, labels, info line) if requested
if (display->overlay_enabled) {
// Call the new overlay rendering function (to be implemented if not present)
sc_render_overlay_ui(display);
}
SDL_RenderPresent(display->renderer);
return SC_DISPLAY_RESULT_OK;
}
}

View file

@ -36,6 +36,16 @@ struct sc_display {
} pending;
bool has_frame;
// transient overlay filled by the caller before rendering
bool overlay_enabled;
int overlay_x;
int overlay_y;
char overlay_text[128];
int overlay_w;
int overlay_h;
// overlay checkbox state
bool overlay_pinch_zoom_enabled;
bool overlay_toggle_enabled;
};
enum sc_display_result {
@ -48,6 +58,9 @@ bool
sc_display_init(struct sc_display *display, SDL_Window *window,
SDL_Surface *icon_novideo, bool mipmaps);
void
sc_render_overlay_ui(struct sc_display *display);
void
sc_display_destroy(struct sc_display *display);

View file

@ -3,6 +3,7 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <SDL2/SDL.h>
#include "android/input.h"
@ -29,6 +30,8 @@ sc_input_manager_init(struct sc_input_manager *im,
im->mp = params->mp;
im->gp = params->gp;
im->scroll_action = params->scroll_action;
im->mouse_bindings = params->mouse_bindings;
im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = params->clipboard_autosync;
@ -267,7 +270,6 @@ clipboard_paste(struct sc_input_manager *im) {
static void
rotate_device(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
@ -336,6 +338,13 @@ simulate_virtual_finger(struct sc_input_manager *im,
struct sc_point point) {
bool up = action == AMOTION_EVENT_ACTION_UP;
fprintf(stderr,
"INJECT: virtual finger action=%d pos=%d,%d pressure=%.1f\n",
action,
point.x,
point.y,
up ? 0.0f : 1.0f);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
msg.inject_touch_event.action = action;
@ -354,6 +363,160 @@ simulate_virtual_finger(struct sc_input_manager *im,
return true;
}
static struct sc_point
inverse_point(struct sc_point point, struct sc_size size,
bool invert_x, bool invert_y);
static SDL_Point
sc_screen_convert_frame_to_window_point(struct sc_screen *screen,
struct sc_point frame_point);
static void
sc_input_manager_process_synthetic_mouse_motion(struct sc_input_manager *im,
int32_t x, int32_t y,
int32_t xrel, int32_t yrel);
static struct sc_position
sc_input_manager_get_position(struct sc_input_manager *im, int32_t x,
int32_t y);
static void
sc_input_manager_process_synthetic_mouse_click(struct sc_input_manager *im,
enum android_motionevent_action action,
int32_t x, int32_t y);
static void
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event);
bool
sc_input_manager_handle_mouse_wheel_pinch(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event) {
if (!im || !im->screen || event->y == 0) {
return false;
}
int mouse_x;
int mouse_y;
(void) SDL_GetMouseState(&mouse_x, &mouse_y);
// Ensure the wheel pinch originates from a point on the device video,
// not from the overlay or outside the frame content.
SDL_Rect rect = im->screen->rect;
if (mouse_x < rect.x || mouse_x >= rect.x + rect.w
|| mouse_y < rect.y || mouse_y >= rect.y + rect.h) {
mouse_x = rect.x + rect.w / 2;
mouse_y = rect.y + rect.h / 2;
}
struct sc_point mouse = sc_screen_convert_window_to_frame_coords(
im->screen, mouse_x, mouse_y);
int zoom_delta = event->y > 0
? (int)(im->screen->frame_size.height / 16)
: -(int)(im->screen->frame_size.height / 16);
struct sc_point p1a = mouse;
struct sc_point p2a = inverse_point(mouse, im->screen->frame_size,
true, true);
p2a.x = CLAMP(p2a.x, 0, im->screen->frame_size.width - 1);
p2a.y = CLAMP(p2a.y, 0, im->screen->frame_size.height - 1);
struct sc_point p1b;
int32_t dx = mouse.x - im->screen->frame_size.width / 2;
int32_t dy = mouse.y - im->screen->frame_size.height / 2;
if (dx == 0 && dy == 0) {
p1b.x = mouse.x;
p1b.y = CLAMP(mouse.y + zoom_delta, 0,
im->screen->frame_size.height - 1);
} else {
double dist = sqrt((double) dx * dx + (double) dy * dy);
double scale = (double) zoom_delta / dist;
p1b.x = CLAMP(mouse.x + (int32_t) round(dx * scale), 0,
im->screen->frame_size.width - 1);
p1b.y = CLAMP(mouse.y + (int32_t) round(dy * scale), 0,
im->screen->frame_size.height - 1);
}
struct sc_point p2b = inverse_point(p1b, im->screen->frame_size,
true, true);
p2b.x = CLAMP(p2b.x, 0, im->screen->frame_size.width - 1);
p2b.y = CLAMP(p2b.y, 0, im->screen->frame_size.height - 1);
fprintf(stderr,
"PINCH: wheel gesture start mouse=%d,%d p1a=%d,%d p2a=%d,%d p1b=%d,%d p2b=%d,%d\n",
mouse_x, mouse_y,
p1a.x, p1a.y,
p2a.x, p2a.y,
p1b.x, p1b.y,
p2b.x, p2b.y);
fflush(stderr);
SDL_Point target = sc_screen_convert_frame_to_window_point(im->screen,
p1b);
fprintf(stderr,
"PINCH: synthetic motion target window=%d,%d frame=%d,%d vfinger=%d buttons=0x%02x\n",
target.x, target.y,
p1b.x, p1b.y,
im->vfinger_down,
im->mouse_buttons_state);
fflush(stderr);
bool saved_vfinger_down = im->vfinger_down;
bool saved_invert_x = im->vfinger_invert_x;
bool saved_invert_y = im->vfinger_invert_y;
uint8_t saved_buttons_state = im->mouse_buttons_state;
// Use the existing control-drag pinch flow for wheel zoom:
// generic finger down, virtual finger down, synthetic move, then up.
SDL_Point start = sc_screen_convert_frame_to_window_point(im->screen,
p1a);
SDL_Point end = sc_screen_convert_frame_to_window_point(im->screen,
p1b);
im->mouse_buttons_state = saved_buttons_state | SC_MOUSE_BUTTON_LEFT;
sc_input_manager_process_synthetic_mouse_click(im,
AMOTION_EVENT_ACTION_DOWN,
start.x, start.y);
if (!simulate_virtual_finger(im, AMOTION_EVENT_ACTION_DOWN, p2a)) {
im->mouse_buttons_state = saved_buttons_state;
return false;
}
im->vfinger_invert_x = true;
im->vfinger_invert_y = true;
im->vfinger_down = true;
im->mouse_buttons_state = saved_buttons_state | SC_MOUSE_BUTTON_LEFT;
const int steps = 3;
int32_t prev_x = mouse_x;
int32_t prev_y = mouse_y;
for (int i = 1; i <= steps; ++i) {
int32_t step_x = mouse_x + (target.x - mouse_x) * i / steps;
int32_t step_y = mouse_y + (target.y - mouse_y) * i / steps;
int32_t step_xrel = step_x - prev_x;
int32_t step_yrel = step_y - prev_y;
sc_input_manager_process_synthetic_mouse_motion(im, step_x, step_y,
step_xrel, step_yrel);
prev_x = step_x;
prev_y = step_y;
}
sc_input_manager_process_synthetic_mouse_click(im,
AMOTION_EVENT_ACTION_UP,
end.x, end.y);
if (!simulate_virtual_finger(im, AMOTION_EVENT_ACTION_UP, p2b)) {
im->mouse_buttons_state = saved_buttons_state;
im->vfinger_down = saved_vfinger_down;
im->vfinger_invert_x = saved_invert_x;
im->vfinger_invert_y = saved_invert_y;
return false;
}
im->mouse_buttons_state = saved_buttons_state;
im->vfinger_down = saved_vfinger_down;
im->vfinger_invert_x = saved_invert_x;
im->vfinger_invert_y = saved_invert_y;
return true;
}
static struct sc_point
inverse_point(struct sc_point point, struct sc_size size,
bool invert_x, bool invert_y) {
@ -366,6 +529,110 @@ inverse_point(struct sc_point point, struct sc_size size,
return point;
}
static SDL_Point
sc_screen_convert_frame_to_window_point(struct sc_screen *screen,
struct sc_point frame_point) {
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
struct sc_point drawable;
switch (screen->orientation) {
case SC_ORIENTATION_0:
drawable.x = frame_point.x;
drawable.y = frame_point.y;
break;
case SC_ORIENTATION_90:
drawable.x = h - frame_point.y;
drawable.y = frame_point.x;
break;
case SC_ORIENTATION_180:
drawable.x = w - frame_point.x;
drawable.y = h - frame_point.y;
break;
case SC_ORIENTATION_270:
drawable.x = frame_point.y;
drawable.y = w - frame_point.x;
break;
case SC_ORIENTATION_FLIP_0:
drawable.x = w - frame_point.x;
drawable.y = frame_point.y;
break;
case SC_ORIENTATION_FLIP_90:
drawable.x = h - frame_point.y;
drawable.y = w - frame_point.x;
break;
case SC_ORIENTATION_FLIP_180:
drawable.x = frame_point.x;
drawable.y = h - frame_point.y;
break;
default:
assert(screen->orientation == SC_ORIENTATION_FLIP_270);
drawable.x = frame_point.y;
drawable.y = frame_point.x;
break;
}
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
int32_t x = screen->rect.x + (int64_t) drawable.x * screen->rect.w / w;
int32_t y = screen->rect.y + (int64_t) drawable.y * screen->rect.h / h;
SDL_Point result = {
.x = (int64_t) x * ww / dw,
.y = (int64_t) y * wh / dh,
};
return result;
}
static void
sc_input_manager_process_synthetic_mouse_motion(struct sc_input_manager *im,
int32_t x, int32_t y,
int32_t xrel, int32_t yrel) {
fprintf(stderr,
"SYNTH: process motion x=%d y=%d xrel=%d yrel=%d vfinger=%d buttons=0x%02x\n",
x, y, xrel, yrel, im->vfinger_down, im->mouse_buttons_state);
fflush(stderr);
SDL_MouseMotionEvent event = {
.type = SDL_MOUSEMOTION,
.which = 0,
.x = x,
.y = y,
.xrel = xrel,
.yrel = yrel,
.state = SDL_BUTTON(SDL_BUTTON_LEFT),
};
sc_input_manager_process_mouse_motion(im, &event);
}
static void
sc_input_manager_process_synthetic_mouse_click(struct sc_input_manager *im,
enum android_motionevent_action action,
int32_t x, int32_t y) {
if (!im || !im->mp || !im->mp->ops->process_mouse_click) {
return;
}
struct sc_mouse_click_event evt = {
.position = sc_input_manager_get_position(im, x, y),
.action = action == AMOTION_EVENT_ACTION_DOWN
? SC_ACTION_DOWN : SC_ACTION_UP,
.button = SC_MOUSE_BUTTON_LEFT,
.pointer_id = SC_POINTER_ID_GENERIC_FINGER,
.buttons_state = action == AMOTION_EVENT_ACTION_DOWN
? SC_MOUSE_BUTTON_LEFT : 0,
};
fprintf(stderr,
"SYNTH: process click action=%d x=%d y=%d buttons=0x%02x\n",
action,
x,
y,
evt.buttons_state);
fflush(stderr);
im->mp->ops->process_mouse_click(im->mp, &evt);
}
static void
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
@ -637,6 +904,12 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
return;
}
fprintf(stderr,
"SYNTH: mouse motion event x=%d y=%d xrel=%d yrel=%d buttons=0x%02x vfinger=%d\n",
event->x, event->y, event->xrel, event->yrel,
im->mouse_buttons_state, im->vfinger_down);
fflush(stderr);
struct sc_mouse_motion_event evt = {
.position = sc_input_manager_get_position(im, event->x, event->y),
.pointer_id = im->vfinger_down ? SC_POINTER_ID_GENERIC_FINGER
@ -657,6 +930,13 @@ 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);
LOGD("Virtual finger move: mouse=%d,%d frame=%d,%d invert_x=%d invert_y=%d",
event->x,
event->y,
mouse.x,
mouse.y,
im->vfinger_invert_x,
im->vfinger_invert_y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
@ -725,6 +1005,16 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool paused = im->screen->paused;
bool down = event->type == SDL_MOUSEBUTTONDOWN;
fprintf(stderr, "INPUT: mouse button %s button=%d clicks=%d pos=%d,%d which=%d control=%d paused=%d\n",
down ? "down" : "up",
event->button,
event->clicks,
event->x,
event->y,
event->which,
control,
paused);
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
if (button == SC_MOUSE_BUTTON_UNKNOWN) {
return;
@ -747,6 +1037,10 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
: &im->mouse_bindings.sec;
enum sc_mouse_binding binding =
sc_input_manager_get_binding(bindings, event->button);
LOGD("Mouse button binding: button=%d -> binding=%d action=%s",
event->button,
binding,
down ? "down" : "up");
assert(binding != SC_MOUSE_BINDING_AUTO);
switch (binding) {
case SC_MOUSE_BINDING_DISABLED:
@ -824,6 +1118,14 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
.buttons_state = im->mouse_buttons_state,
};
LOGD("Dispatching click event: pointer_id=%llu button=%d action=%d pos=%d,%d buttons_state=0x%02x",
evt.pointer_id,
evt.button,
evt.action,
(int) evt.position.point.x,
(int) evt.position.point.y,
evt.buttons_state);
assert(im->mp->ops->process_mouse_click);
im->mp->ops->process_mouse_click(im->mp, &evt);
@ -866,6 +1168,13 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// 1 1 0 1 horizontal tilt
im->vfinger_invert_x = ctrl_pressed ^ shift_pressed;
im->vfinger_invert_y = ctrl_pressed;
LOGD("Virtual finger mode start: invert_x=%d invert_y=%d mouse=%d,%d frame=%d,%d",
im->vfinger_invert_x,
im->vfinger_invert_y,
event->x,
event->y,
mouse.x,
mouse.y);
}
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
@ -873,6 +1182,10 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
LOGD("Virtual finger event: action=%d vfinger=%d,%d",
(int) action,
vfinger.x,
vfinger.y);
if (!simulate_virtual_finger(im, action, vfinger)) {
return;
}
@ -883,7 +1196,56 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
static void
sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event) {
LOGD("Mouse wheel event: x=%d y=%d direction=%d preciseX=%.2f preciseY=%.2f scroll_action=%d",
event->x,
event->y,
event->direction,
#if SDL_VERSION_ATLEAST(2, 0, 18)
event->preciseX,
event->preciseY,
#else
0.0f,
0.0f,
#endif
im->scroll_action);
// If configured to map scroll to zoom, synthesize zoom key events
if (im->scroll_action == SC_SCROLL_ACTION_ZOOM) {
// Prefer integer delta when available
#if SDL_VERSION_ATLEAST(2, 0, 18)
int v = event->y; // integer ticks
#else
int v = event->y;
#endif
if (v == 0) {
return;
}
enum android_keycode key = v > 0 ? AKEYCODE_ZOOM_IN : AKEYCODE_ZOOM_OUT;
// For each tick, send a key down + key up
int ticks = v > 0 ? v : -v;
LOGD("Synthesizing zoom key events: key=%d ticks=%d", key, ticks);
for (int i = 0; i < ticks; ++i) {
struct sc_control_msg msg = {0};
msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
msg.inject_keycode.keycode = key;
msg.inject_keycode.repeat = 0;
msg.inject_keycode.metastate = 0;
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject zoom key down'");
}
msg.inject_keycode.action = AKEY_EVENT_ACTION_UP;
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject zoom key up'");
}
}
return;
}
if (!im->mp->ops->process_mouse_scroll) {
LOGD("Mouse scroll drop: processor does not support scroll");
// The mouse processor does not support scroll events
return;
}
@ -908,6 +1270,59 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
.buttons_state = im->mouse_buttons_state,
};
LOGD("Mouse scroll forwarded: pos=%d,%d hscroll=%.2f vscroll=%.2f h_int=%d v_int=%d buttons_state=0x%02x",
(int) evt.position.point.x,
(int) evt.position.point.y,
evt.hscroll,
evt.vscroll,
(int) evt.hscroll_int,
(int) evt.vscroll_int,
evt.buttons_state);
/* Debug logging: show scroll event details */
/* Also print to stderr directly to ensure visibility even if SDL logs
are redirected or filtered by the environment/IDE. */
fprintf(stderr, "SCROLL: pos=%d,%d h=%.2f v=%.2f h_int=%d v_int=%d\n",
(int) evt.position.point.x,
(int) evt.position.point.y,
evt.hscroll,
evt.vscroll,
(int) evt.hscroll_int,
(int) evt.vscroll_int);
fflush(stderr);
LOGI("mouse wheel event: pos=%d,%d h=%f v=%f h_int=%d v_int=%d",
(int) evt.position.point.x,
(int) evt.position.point.y,
evt.hscroll,
evt.vscroll,
(int) evt.hscroll_int,
(int) evt.vscroll_int);
/* Also update the window title briefly so it's visible on-screen */
if (im->screen && im->screen->window) {
char _titlebuf[128];
snprintf(_titlebuf, sizeof _titlebuf, "scrcpy - scroll h=%.2f v=%.2f",
evt.hscroll, evt.vscroll);
SDL_SetWindowTitle(im->screen->window, _titlebuf);
}
/* Update on-screen overlay so the user sees the scroll info visually */
if (im->screen) {
// Update the overlay info line for the new overlay UI
snprintf(im->screen->overlay_text, sizeof im->screen->overlay_text,
"h=%.2f v=%.2f x=%d y=%d",
evt.hscroll, evt.vscroll,
(int) evt.position.point.x, (int) evt.position.point.y);
// Always show the overlay if persistent, otherwise show temporarily
if (!im->screen->overlay_persistent) {
im->screen->overlay_visible = true;
im->screen->overlay_ttl = 120; // ~2 seconds at 60 FPS
}
LOGD("Overlay info updated from wheel: %s", im->screen->overlay_text);
// If persistent, overlay_visible is managed elsewhere
}
im->mp->ops->process_mouse_scroll(im->mp, &evt);
}

View file

@ -8,6 +8,8 @@
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keycode.h>
#include "android/input.h"
#include "controller.h"
#include "file_pusher.h"
#include "options.h"
@ -44,6 +46,7 @@ struct sc_input_manager {
uint16_t last_mod;
uint64_t next_sequence; // used for request acknowledgements
enum sc_scroll_action scroll_action;
};
struct sc_input_manager_params {
@ -58,6 +61,7 @@ struct sc_input_manager_params {
bool legacy_paste;
bool clipboard_autosync;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
enum sc_scroll_action scroll_action;
};
void
@ -68,4 +72,8 @@ void
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event);
bool
sc_input_manager_handle_mouse_wheel_pinch(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event);
#endif

View file

@ -108,11 +108,13 @@ const struct scrcpy_options scrcpy_options_default = {
.window = true,
.mouse_hover = true,
.audio_dup = false,
.overlay_persistent = false,
.new_display = NULL,
.start_app = NULL,
.angle = NULL,
.vd_destroy_content = true,
.vd_system_decorations = true,
.scroll_action = SC_SCROLL_ACTION_SCROLL,
};
enum sc_orientation

View file

@ -192,6 +192,11 @@ struct sc_mouse_binding_set {
enum sc_mouse_binding click5;
};
enum sc_scroll_action {
SC_SCROLL_ACTION_SCROLL,
SC_SCROLL_ACTION_ZOOM,
};
struct sc_mouse_bindings {
struct sc_mouse_binding_set pri;
struct sc_mouse_binding_set sec; // When Shift is pressed
@ -323,10 +328,14 @@ struct scrcpy_options {
bool window;
bool mouse_hover;
bool audio_dup;
/* Keep on-screen overlay visible (debug) */
bool overlay_persistent;
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
const char *start_app;
bool vd_destroy_content;
bool vd_system_decorations;
/* Behavior for mouse wheel: scroll (default) or zoom (send zoom keycodes) */
enum sc_scroll_action scroll_action;
};
extern const struct scrcpy_options scrcpy_options_default;

View file

@ -813,6 +813,7 @@ aoa_complete:
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = options->shortcut_mods,
.scroll_action = options->scroll_action,
.window_title = window_title,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
@ -824,6 +825,7 @@ aoa_complete:
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,
.overlay_persistent = options->overlay_persistent,
};
if (!sc_screen_init(&s->screen, &screen_params)) {

View file

@ -162,6 +162,9 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
return screen->im.mp && screen->im.mp->relative_mode;
}
static void sc_screen_log_overlay_state(struct sc_screen *screen,
const char *reason);
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
assert(screen->video);
@ -213,9 +216,45 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
sc_screen_update_content_rect(screen);
}
// Show overlay when the toggle is enabled or when a temporary overlay TTL is active
bool overlay_should_be_visible = screen->overlay_toggle_enabled || screen->overlay_ttl > 0;
if (overlay_should_be_visible != screen->overlay_visible) {
screen->overlay_visible = overlay_should_be_visible;
sc_screen_log_overlay_state(screen, "render visibility update");
}
if (screen->overlay_visible) {
screen->display.overlay_enabled = true;
screen->display.overlay_x = screen->overlay_x;
screen->display.overlay_y = screen->overlay_y;
// copy info line
strncpy(screen->display.overlay_text, screen->overlay_text,
sizeof(screen->display.overlay_text) - 1);
screen->display.overlay_text[sizeof(screen->display.overlay_text) - 1] = '\0';
// forward checkbox state
screen->display.overlay_pinch_zoom_enabled = screen->overlay_pinch_zoom_enabled;
screen->display.overlay_toggle_enabled = screen->overlay_toggle_enabled;
// forward overlay size for click detection
screen->overlay_w = screen->display.overlay_w;
screen->overlay_h = screen->display.overlay_h;
} else {
screen->display.overlay_enabled = false;
screen->display.overlay_text[0] = '\0';
screen->display.overlay_pinch_zoom_enabled = false;
screen->display.overlay_toggle_enabled = false;
}
enum sc_display_result res =
sc_display_render(&screen->display, &screen->rect, screen->orientation);
(void) res; // any error already logged
// decrement overlay TTL (frames)
if (screen->overlay_ttl > 0) {
--screen->overlay_ttl;
if (screen->overlay_ttl == 0) {
LOGD("Overlay TTL expired, hiding overlay");
screen->overlay_visible = false;
}
}
}
static void
@ -249,7 +288,6 @@ event_watcher(void *data, SDL_Event *event) {
return 0;
}
#endif
static bool
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx) {
@ -343,6 +381,30 @@ sc_screen_init(struct sc_screen *screen,
screen->req.fullscreen = params->fullscreen;
screen->req.start_fps_counter = params->start_fps_counter;
// overlay defaults
screen->overlay_visible = false;
screen->overlay_x = 20;
screen->overlay_y = 20;
screen->overlay_w = 220;
screen->overlay_h = 36;
screen->overlay_text[0] = '\0';
screen->overlay_ttl = 0;
screen->overlay_dragging = false;
screen->overlay_drag_offset_x = 0;
screen->overlay_drag_offset_y = 0;
/* persistent overlay requested by options */
screen->overlay_persistent = params->overlay_persistent;
if (screen->overlay_persistent) {
screen->overlay_visible = true;
LOGD("Overlay persistent enabled at init");
}
// Initialize new overlay checkboxes
screen->overlay_pinch_zoom_enabled = false;
screen->overlay_toggle_enabled = screen->overlay_persistent;
if (screen->overlay_toggle_enabled) {
screen->overlay_visible = true;
}
bool ok = sc_frame_buffer_init(&screen->fb);
if (!ok) {
return false;
@ -440,6 +502,7 @@ sc_screen_init(struct sc_screen *screen,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
.scroll_action = params->scroll_action,
};
sc_input_manager_init(&screen->im, &im_params);
@ -598,9 +661,7 @@ sc_screen_set_orientation(struct sc_screen *screen,
set_content_size(screen, new_content_size);
screen->orientation = orientation;
LOGI("Display orientation set to %s", sc_orientation_get_name(orientation));
sc_screen_render(screen, true);
}
static bool
@ -798,8 +859,77 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
content_size.height);
}
static bool
sc_screen_handle_mouse_wheel_pinch(struct sc_screen *screen,
const SDL_MouseWheelEvent *wheel) {
if (!screen->overlay_pinch_zoom_enabled) {
fprintf(stderr, "PINCH: wheel ignored, pinch disabled\n");
return false;
}
fprintf(stderr, "PINCH: intercepting mouse wheel for pinch zoom\n");
LOGI("Intercepting mouse wheel for pinch zoom: visible=%d toggle_enabled=%d pinch_enabled=%d",
screen->overlay_visible,
screen->overlay_toggle_enabled,
screen->overlay_pinch_zoom_enabled);
return sc_input_manager_handle_mouse_wheel_pinch(&screen->im, wheel);
}
static void
sc_screen_log_overlay_state(struct sc_screen *screen, const char *reason) {
LOGD("Overlay state changed (%s): visible=%d persistent=%d toggle_enabled=%d pinch_zoom_enabled=%d ttl=%d",
reason,
screen->overlay_visible,
screen->overlay_persistent,
screen->overlay_toggle_enabled,
screen->overlay_pinch_zoom_enabled,
screen->overlay_ttl);
}
bool
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
// Overlay toggle hotkey: F10
if (event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_F10) {
screen->overlay_toggle_enabled = !screen->overlay_toggle_enabled;
if (!screen->overlay_toggle_enabled) {
screen->overlay_visible = false;
} else {
screen->overlay_visible = true;
}
sc_screen_log_overlay_state(screen, "hotkey F10");
return true;
}
// If overlay toggle is off, keep normal event flow and allow the overlay
// to remain visible temporarily while the TTL is active.
if (!screen->overlay_toggle_enabled) {
// Allow toggling overlay via checkbox click
if (event->type == SDL_MOUSEBUTTONDOWN) {
int mx = event->button.x;
int my = event->button.y;
sc_screen_hidpi_scale_coords(screen, &mx, &my);
int char_h = 12, scale = 2;
int x = screen->overlay_x + 4;
int y2 = screen->overlay_y + 4 + char_h * scale + 2 * scale;
int cb_w = screen->overlay_w - 8;
int cb_h = char_h * scale;
if (mx >= x && mx < x + cb_w && my >= y2 && my < y2 + cb_h) {
screen->overlay_toggle_enabled = true;
screen->overlay_visible = true;
LOGD("Overlay toggle re-enabled via hidden click");
fprintf(stderr, "OVERLAY: re-enabled via hidden click\n");
fprintf(stderr, "OVERLAY STATE: toggle=%d pinch=%d visible=%d ttl=%d\n",
screen->overlay_toggle_enabled,
screen->overlay_pinch_zoom_enabled,
screen->overlay_visible,
screen->overlay_ttl);
sc_screen_log_overlay_state(screen, "hidden click on toggle line");
return true;
}
}
}
switch (event->type) {
case SC_EVENT_SCREEN_INIT_SIZE: {
// The initial size is passed via screen->frame_size
@ -818,6 +948,12 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
}
return true;
}
case SDL_MOUSEWHEEL: {
if (sc_screen_handle_mouse_wheel_pinch(screen, &event->wheel)) {
return true;
}
break;
}
case SDL_WINDOWEVENT:
if (!screen->video
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
@ -867,6 +1003,98 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
return true;
}
// Handle overlay drag/move before forwarding to input manager
switch (event->type) {
case SDL_MOUSEBUTTONDOWN: {
fprintf(stderr, "SCREEN MOUSE DOWN: x=%d y=%d button=%d overlay_visible=%d toggle_enabled=%d pinch_enabled=%d\n",
event->button.x,
event->button.y,
event->button.button,
screen->overlay_visible,
screen->overlay_toggle_enabled,
screen->overlay_pinch_zoom_enabled);
// Overlay checkbox click handling
if (screen->overlay_visible && event->button.button == SDL_BUTTON_LEFT) {
int mx = event->button.x;
int my = event->button.y;
sc_screen_hidpi_scale_coords(screen, &mx, &my);
// Overlay layout must match display.c rendering
// Font: 8x12, scale: 2, spacing: 2, padding: 4, 3 lines
int char_h = 12, scale = 2;
int x = screen->overlay_x + 4;
int y = screen->overlay_y + 4;
// Checkbox area: full visible line width for easier clicking
int cb_w = screen->overlay_w - 8;
int cb_h = char_h * scale;
// Pinch Zoom checkbox (line 1)
if (mx >= x && mx < x + cb_w && my >= y && my < y + cb_h) {
screen->overlay_pinch_zoom_enabled = !screen->overlay_pinch_zoom_enabled;
LOGD("Overlay pinch zoom toggled: %d", screen->overlay_pinch_zoom_enabled);
fprintf(stderr, "OVERLAY: pinch zoom toggled=%d\n",
screen->overlay_pinch_zoom_enabled);
sc_screen_log_overlay_state(screen, "checkbox pinch zoom");
return true;
}
// Overlay Toggle checkbox (line 2)
int y2 = y + char_h * scale + 2 * scale;
if (mx >= x && mx < x + cb_w && my >= y2 && my < y2 + cb_h) {
screen->overlay_toggle_enabled = !screen->overlay_toggle_enabled;
screen->overlay_visible = screen->overlay_toggle_enabled;
LOGD("Overlay toggle toggled: %d", screen->overlay_toggle_enabled);
fprintf(stderr, "OVERLAY: toggle enabled=%d\n",
screen->overlay_toggle_enabled);
sc_screen_log_overlay_state(screen, "checkbox overlay toggle");
return true;
}
}
if (screen->overlay_visible && event->button.button == SDL_BUTTON_LEFT) {
int mx = event->button.x;
int my = event->button.y;
sc_screen_hidpi_scale_coords(screen, &mx, &my);
int char_h = 12, scale = 2;
int drag_h = char_h * scale + 2 * scale;
int drag_w = 3 * (8 * scale + 2) + 8;
// Only start dragging if the click is in the top overlay header
if (mx >= screen->overlay_x && mx < screen->overlay_x + drag_w
&& my >= screen->overlay_y && my < screen->overlay_y + drag_h) {
screen->overlay_dragging = true;
screen->overlay_drag_offset_x = mx - screen->overlay_x;
screen->overlay_drag_offset_y = my - screen->overlay_y;
// consume the event (do not forward to device)
return true;
}
}
break;
}
case SDL_MOUSEBUTTONUP: {
if (screen->overlay_dragging && event->button.button == SDL_BUTTON_LEFT) {
screen->overlay_dragging = false;
return true;
}
break;
}
case SDL_MOUSEMOTION: {
if (screen->overlay_dragging) {
int mx = event->motion.x;
int my = event->motion.y;
sc_screen_hidpi_scale_coords(screen, &mx, &my);
screen->overlay_x = mx - screen->overlay_drag_offset_x;
screen->overlay_y = my - screen->overlay_drag_offset_y;
// clamp to window bounds
int ww, wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
if (screen->overlay_x < 0) screen->overlay_x = 0;
if (screen->overlay_y < 0) screen->overlay_y = 0;
if (screen->overlay_x + screen->overlay_w > ww)
screen->overlay_x = ww - screen->overlay_w;
if (screen->overlay_y + screen->overlay_h > wh)
screen->overlay_y = wh - screen->overlay_h;
return true;
}
break;
}
}
sc_input_manager_handle_event(&screen->im, event);
return true;
}

View file

@ -69,6 +69,22 @@ struct sc_screen {
bool paused;
AVFrame *resume_frame;
// simple debug overlay (movable)
bool overlay_visible;
bool overlay_persistent;
int overlay_x;
int overlay_y;
int overlay_w;
int overlay_h;
char overlay_text[128];
int overlay_ttl; // frames remaining to show
bool overlay_dragging;
int overlay_drag_offset_x;
int overlay_drag_offset_y;
// New: overlay checkboxes
bool overlay_pinch_zoom_enabled;
bool overlay_toggle_enabled;
};
struct sc_screen_params {
@ -100,6 +116,8 @@ struct sc_screen_params {
bool fullscreen;
bool start_fps_counter;
enum sc_scroll_action scroll_action;
bool overlay_persistent;
};
// initialize screen, create window, renderer and texture (window is hidden)