diff --git a/app/src/cli.c b/app/src/cli.c index b2e3e30a..acabe5da 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -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."); diff --git a/app/src/display.c b/app/src/display.c index 15f9a1f1..24af39f4 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -1,5 +1,163 @@ #include "display.h" +#include +#include +#include +#include + +// 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 #include #include @@ -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; -} +} \ No newline at end of file diff --git a/app/src/display.h b/app/src/display.h index 49110994..c0e51ee6 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -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); diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 3e4dd0f3..9bfe4d8c 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #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); } diff --git a/app/src/input_manager.h b/app/src/input_manager.h index af4cbc69..3b4298ca 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -8,6 +8,8 @@ #include #include +#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 diff --git a/app/src/options.c b/app/src/options.c index 0fe82d29..5ab3f525 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -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 diff --git a/app/src/options.h b/app/src/options.h index 03b42913..d62b763b 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -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; // [x][/] 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; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index aedfdf9c..709b8490 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -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)) { diff --git a/app/src/screen.c b/app/src/screen.c index da17df0e..d2134aa0 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -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; } diff --git a/app/src/screen.h b/app/src/screen.h index 6621b2d2..1f074ac4 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -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)