diff --git a/app/src/cli.c b/app/src/cli.c index b2e3e30a53..05346596d2 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -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; diff --git a/app/src/hid/hid_keyboard.c b/app/src/hid/hid_keyboard.c index 6477396a55..8e7c24b011 100644 --- a/app/src/hid/hid_keyboard.c +++ b/app/src/hid/hid_keyboard.c @@ -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); +} diff --git a/app/src/hid/hid_keyboard.h b/app/src/hid/hid_keyboard.h index 5ecfd8cff1..2e58b7fc02 100644 --- a/app/src/hid/hid_keyboard.h +++ b/app/src/hid/hid_keyboard.h @@ -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 diff --git a/app/src/mouse_capture.c b/app/src/mouse_capture.c index 25345faadd..aeb18f60bf 100644 --- a/app/src/mouse_capture.c +++ b/app/src/mouse_capture.c @@ -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 diff --git a/app/src/mouse_capture.h b/app/src/mouse_capture.h index f352cc133f..a31edd59df 100644 --- a/app/src/mouse_capture.h +++ b/app/src/mouse_capture.h @@ -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); diff --git a/app/src/options.c b/app/src/options.c index 0fe82d291b..e45a700c6a 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -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 diff --git a/app/src/options.h b/app/src/options.h index 03b4291344..cdc328da40 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -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; diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h index 9e9bb86ed1..e8530e9e44 100644 --- a/app/src/trait/key_processor.h +++ b/app/src/trait/key_processor.h @@ -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 diff --git a/app/src/usb/keyboard_aoa.c b/app/src/usb/keyboard_aoa.c index 8f5cb75525..07291f7ca2 100644 --- a/app/src/usb/keyboard_aoa.c +++ b/app/src/usb/keyboard_aoa.c @@ -1,6 +1,7 @@ #include "keyboard_aoa.h" #include +#include #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 diff --git a/app/src/usb/scrcpy_otg.c b/app/src/usb/scrcpy_otg.c index 1a9cc46ee1..246c255e3e 100644 --- a/app/src/usb/scrcpy_otg.c +++ b/app/src/usb/scrcpy_otg.c @@ -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, ¶ms); diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index 5c580df994..245ffd7598 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -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) { diff --git a/app/src/usb/screen_otg.h b/app/src/usb/screen_otg.h index 08b76ae761..adffd7491c 100644 --- a/app/src/usb/screen_otg.h +++ b/app/src/usb/screen_otg.h @@ -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