This commit is contained in:
pierrezurek 2026-04-20 19:13:37 +00:00 committed by GitHub
commit 290d1e084c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 116 additions and 12 deletions

View file

@ -114,6 +114,7 @@ enum {
OPT_NO_VD_SYSTEM_DECORATIONS,
OPT_NO_VD_DESTROY_CONTENT,
OPT_DISPLAY_IME_POLICY,
OPT_OTG_AUTOMATIC_SCREENSHOT,
};
struct sc_option {
@ -1063,6 +1064,13 @@ static const struct sc_option options[] = {
.text = "Set the initial window height.\n"
"Default is 0 (automatic).",
},
{
.longopt_id = OPT_OTG_AUTOMATIC_SCREENSHOT,
.longopt = "otg-automatic-screenshot",
.text = "Take a screenshot automatically each time mouse capture is exited in OTG mode.\n"
"This is particularly useful when OTG mode is used to enable USB debugging on a phone with a broken screen.\n"
"The screenshot will display the mouse pointer that will indicate where to move next."
},
};
static const struct sc_shortcut shortcuts[] = {
@ -2821,6 +2829,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case OPT_OTG_AUTOMATIC_SCREENSHOT:
opts->otg_automatic_screenshot = true;
break;
default:
// getopt prints the error message on stderr
return false;

View file

@ -343,3 +343,17 @@ void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
void sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close) {
hid_close->hid_id = SC_HID_ID_KEYBOARD;
}
void
sc_hid_keyboard_generate_screenshot_press_input(struct sc_hid_input *hid_input) {
sc_hid_keyboard_input_init(hid_input);
uint8_t *keys_data = &hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS];
keys_data[0] = 0x66; // Power : Usage ID 0x66
keys_data[1] = 0x81; // Volume Down : Usage ID 0x81
}
void
sc_hid_keyboard_generate_screenshot_release_input(struct sc_hid_input *hid_input) {
sc_hid_keyboard_input_init(hid_input);
}

View file

@ -13,7 +13,9 @@
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
// HID protocol.
// 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66
// Increase the maximum value to 0x82 instead of the previous value 0x66 so that
// we can send Power (0x66) and Volume Down (0x81) to take screenshots.
#define SC_HID_KEYBOARD_KEYS 0x82
#define SC_HID_ID_KEYBOARD 1
@ -51,4 +53,10 @@ bool
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
uint16_t mods_state);
void
sc_hid_keyboard_generate_screenshot_press_input(struct sc_hid_input *hid_input);
void
sc_hid_keyboard_generate_screenshot_release_input(struct sc_hid_input *hid_input);
#endif

View file

@ -16,14 +16,14 @@ sc_mouse_capture_is_capture_key(struct sc_mouse_capture *mc, SDL_Keycode key) {
return sc_shortcut_mods_is_shortcut_key(mc->sdl_mouse_capture_keys, key);
}
bool
int
sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
const SDL_Event *event) {
switch (event->type) {
case SDL_WINDOWEVENT:
if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
sc_mouse_capture_set_active(mc, false);
return true;
return SC_MOUSE_CAPTURE_EVENT_CONSUMED;
}
break;
case SDL_KEYDOWN: {
@ -37,7 +37,7 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
mc->mouse_capture_key_pressed = 0;
}
// Mouse capture keys are never forwarded to the device
return true;
return SC_MOUSE_CAPTURE_EVENT_CONSUMED;
}
break;
}
@ -52,7 +52,11 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
sc_mouse_capture_toggle(mc);
}
// Mouse capture keys are never forwarded to the device
return true;
if (sc_mouse_capture_is_active(mc)) {
return SC_MOUSE_CAPTURE_EVENT_CONSUMED;
} else {
return SC_MOUSE_CAPTURE_EVENT_CONSUMED_EXIT_CAPTURE_MODE;
}
}
break;
}
@ -62,13 +66,13 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
if (!sc_mouse_capture_is_active(mc)) {
// The mouse will be captured on SDL_MOUSEBUTTONUP, so consume
// the event
return true;
return SC_MOUSE_CAPTURE_EVENT_CONSUMED;
}
break;
case SDL_MOUSEBUTTONUP:
if (!sc_mouse_capture_is_active(mc)) {
sc_mouse_capture_set_active(mc, true);
return true;
return SC_MOUSE_CAPTURE_EVENT_CONSUMED;
}
break;
case SDL_FINGERMOTION:
@ -76,10 +80,10 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
case SDL_FINGERUP:
// Touch events are not compatible with relative mode
// (coordinates are not relative), so consume the event
return true;
return SC_MOUSE_CAPTURE_EVENT_CONSUMED;
}
return false;
return SC_MOUSE_CAPTURE_EVENT_UNCONSUMED;
}
void

View file

@ -17,6 +17,12 @@ struct sc_mouse_capture {
};
enum {
SC_MOUSE_CAPTURE_EVENT_UNCONSUMED,
SC_MOUSE_CAPTURE_EVENT_CONSUMED,
SC_MOUSE_CAPTURE_EVENT_CONSUMED_EXIT_CAPTURE_MODE,
};
void
sc_mouse_capture_init(struct sc_mouse_capture *mc, SDL_Window *window,
uint8_t shortcut_mods);
@ -30,8 +36,8 @@ sc_mouse_capture_is_active(struct sc_mouse_capture *mc);
void
sc_mouse_capture_toggle(struct sc_mouse_capture *mc);
// Return true if it consumed the event
bool
// Return if it consumed the event
int
sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
const SDL_Event *event);

View file

@ -113,6 +113,7 @@ const struct scrcpy_options scrcpy_options_default = {
.angle = NULL,
.vd_destroy_content = true,
.vd_system_decorations = true,
.otg_automatic_screenshot = false,
};
enum sc_orientation

View file

@ -327,6 +327,7 @@ struct scrcpy_options {
const char *start_app;
bool vd_destroy_content;
bool vd_system_decorations;
bool otg_automatic_screenshot;
};
extern const struct scrcpy_options scrcpy_options_default;

View file

@ -56,6 +56,14 @@ struct sc_key_processor_ops {
void
(*process_text)(struct sc_key_processor *kp,
const struct sc_text_event *event);
/**
* Take screenshot
*
* This function is optional.
*/
bool
(*take_screenshot)(struct sc_key_processor *kp);
};
#endif

View file

@ -1,6 +1,7 @@
#include "keyboard_aoa.h"
#include <assert.h>
#include <unistd.h>
#include "input_events.h"
#include "util/log.h"
@ -62,6 +63,31 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
}
}
static bool
sc_key_processor_take_screenshot(struct sc_key_processor *kp) {
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
struct sc_hid_input hid_input;
sc_hid_keyboard_generate_screenshot_press_input(&hid_input);
if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (screenshot press)");
return false;
}
// sleep for 100ms
usleep(100000);
sc_hid_keyboard_generate_screenshot_release_input(&hid_input);
if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (screenshot release)");
return false;
}
return true;
}
bool
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
@ -84,6 +110,7 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
.take_screenshot = sc_key_processor_take_screenshot,
};
// Clipboard synchronization is requested over the control socket, while HID

View file

@ -200,6 +200,7 @@ scrcpy_otg(struct scrcpy_options *options) {
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.shortcut_mods = options->shortcut_mods,
.otg_automatic_screenshot = options->otg_automatic_screenshot,
};
ok = sc_screen_otg_init(&s->screen_otg, &params);

View file

@ -81,6 +81,8 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
sc_mouse_capture_set_active(&screen->mc, true);
}
screen->otg_automatic_screenshot = params->otg_automatic_screenshot;
return true;
error_destroy_window:
@ -118,6 +120,15 @@ sc_screen_otg_process_key(struct sc_screen_otg *screen,
kp->ops->process_key(kp, &evt, SC_SEQUENCE_INVALID);
}
static void
sc_screen_otg_take_screenshot(struct sc_screen_otg *screen) {
assert(screen->keyboard);
struct sc_key_processor *kp = &screen->keyboard->key_processor;
assert(kp->ops->take_screenshot);
kp->ops->take_screenshot(kp);
}
static void
sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
const SDL_MouseMotionEvent *event) {
@ -261,11 +272,21 @@ sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
void
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
if (sc_mouse_capture_handle_event(&screen->mc, event)) {
int event_state = sc_mouse_capture_handle_event(&screen->mc, event);
if (event_state == SC_MOUSE_CAPTURE_EVENT_CONSUMED) {
// The mouse capture handler consumed the event
return;
} else if (event_state == SC_MOUSE_CAPTURE_EVENT_CONSUMED_EXIT_CAPTURE_MODE) {
if (screen->otg_automatic_screenshot) {
// send Volume Down + Power event to take a screenshot
LOGI("Taking screenshot");
sc_screen_otg_take_screenshot(screen);
}
// The mouse capture handler consumed the event
return;
}
// assume event_state is SC_MOUSE_CAPTURE_EVENT_UNCONSUMED
switch (event->type) {
case SDL_WINDOWEVENT:
switch (event->window.event) {

View file

@ -22,6 +22,7 @@ struct sc_screen_otg {
SDL_Texture *texture;
struct sc_mouse_capture mc;
bool otg_automatic_screenshot;
};
struct sc_screen_otg_params {
@ -37,6 +38,7 @@ struct sc_screen_otg_params {
uint16_t window_height;
bool window_borderless;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
bool otg_automatic_screenshot;
};
bool