Add lshift + printscreen hotkey to take a screenshot of a window (or desktop portal on wayland)

This commit is contained in:
dec05eba
2025-07-20 01:55:02 +02:00
parent b64cb6a3fd
commit ad94cff59e
9 changed files with 107 additions and 18 deletions

2
TODO
View File

@@ -205,3 +205,5 @@ Disable hotkeys if virtual keyboard is found (either at startup or after running
Maybe this can be fixed automatically by grabbing gsr-ui virtual keyboard and releasing it just before we write to it and then release it again. Maybe this can be fixed automatically by grabbing gsr-ui virtual keyboard and releasing it just before we write to it and then release it again.
But wont keyboard remapping software grab the keyboard first if they detect it quickly? But wont keyboard remapping software grab the keyboard first if they detect it quickly?
If we fail to grab it because some other software did then dont grab any keyboards nor gsr-ui virtual keyboards, just listen to them. If we fail to grab it because some other software did then dont grab any keyboards nor gsr-ui virtual keyboards, just listen to them.
Support localization.

View File

@@ -145,6 +145,7 @@ namespace gsr {
std::string save_directory; std::string save_directory;
ConfigHotkey take_screenshot_hotkey; ConfigHotkey take_screenshot_hotkey;
ConfigHotkey take_screenshot_region_hotkey; ConfigHotkey take_screenshot_region_hotkey;
ConfigHotkey take_screenshot_window_hotkey; // Or desktop portal, on wayland
}; };
struct Config { struct Config {

View File

@@ -41,6 +41,12 @@ namespace gsr {
SCREENSHOT SCREENSHOT
}; };
enum class ScreenshotForceType {
NONE,
REGION,
WINDOW
};
class Overlay { class Overlay {
public: public:
Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs); Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs);
@@ -64,6 +70,7 @@ namespace gsr {
void save_replay_10_min(); void save_replay_10_min();
void take_screenshot(); void take_screenshot();
void take_screenshot_region(); void take_screenshot_region();
void take_screenshot_window();
void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target = nullptr); void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target = nullptr);
bool is_open() const; bool is_open() const;
bool should_exit(std::string &reason) const; bool should_exit(std::string &reason) const;
@@ -121,7 +128,7 @@ namespace gsr {
bool on_press_start_replay(bool disable_notification, bool finished_selection); bool on_press_start_replay(bool disable_notification, bool finished_selection);
void on_press_start_record(bool finished_selection); void on_press_start_record(bool finished_selection);
void on_press_start_stream(bool finished_selection); void on_press_start_stream(bool finished_selection);
void on_press_take_screenshot(bool finished_selection, bool force_region_capture); void on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type);
bool update_compositor_texture(const Monitor &monitor); bool update_compositor_texture(const Monitor &monitor);
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options); std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);

View File

@@ -29,6 +29,7 @@ namespace gsr {
STREAM_START_STOP, STREAM_START_STOP,
TAKE_SCREENSHOT, TAKE_SCREENSHOT,
TAKE_SCREENSHOT_REGION, TAKE_SCREENSHOT_REGION,
TAKE_SCREENSHOT_WINDOW,
SHOW_HIDE SHOW_HIDE
}; };
@@ -63,6 +64,7 @@ namespace gsr {
std::unique_ptr<List> create_stream_hotkey_options(); std::unique_ptr<List> create_stream_hotkey_options();
std::unique_ptr<List> create_screenshot_hotkey_options(); std::unique_ptr<List> create_screenshot_hotkey_options();
std::unique_ptr<List> create_screenshot_region_hotkey_options(); std::unique_ptr<List> create_screenshot_region_hotkey_options();
std::unique_ptr<List> create_screenshot_window_hotkey_options();
std::unique_ptr<List> create_hotkey_control_buttons(); std::unique_ptr<List> create_hotkey_control_buttons();
std::unique_ptr<Subsection> create_keyboard_hotkey_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_keyboard_hotkey_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_controller_hotkey_subsection(ScrollablePage *parent_page); std::unique_ptr<Subsection> create_controller_hotkey_subsection(ScrollablePage *parent_page);
@@ -99,6 +101,7 @@ namespace gsr {
Button *start_stop_streaming_button_ptr = nullptr; Button *start_stop_streaming_button_ptr = nullptr;
Button *take_screenshot_button_ptr = nullptr; Button *take_screenshot_button_ptr = nullptr;
Button *take_screenshot_region_button_ptr = nullptr; Button *take_screenshot_region_button_ptr = nullptr;
Button *take_screenshot_window_button_ptr = nullptr;
Button *show_hide_button_ptr = nullptr; Button *show_hide_button_ptr = nullptr;
ConfigHotkey configure_config_hotkey; ConfigHotkey configure_config_hotkey;

View File

@@ -153,6 +153,7 @@ namespace gsr {
screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Printscreen, 0}; screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Printscreen, 0};
screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LCTRL}; screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LCTRL};
screenshot_config.take_screenshot_window_hotkey = {mgl::Keyboard::Printscreen, HOTKEY_MOD_LSHIFT};
main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT}; main_config.show_hide_hotkey = {mgl::Keyboard::Z, HOTKEY_MOD_LALT};
} }
@@ -284,7 +285,8 @@ namespace gsr {
{"screenshot.show_screenshot_saved_notifications", &config.screenshot_config.show_screenshot_saved_notifications}, {"screenshot.show_screenshot_saved_notifications", &config.screenshot_config.show_screenshot_saved_notifications},
{"screenshot.save_directory", &config.screenshot_config.save_directory}, {"screenshot.save_directory", &config.screenshot_config.save_directory},
{"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey}, {"screenshot.take_screenshot_hotkey", &config.screenshot_config.take_screenshot_hotkey},
{"screenshot.take_screenshot_region_hotkey", &config.screenshot_config.take_screenshot_region_hotkey} {"screenshot.take_screenshot_region_hotkey", &config.screenshot_config.take_screenshot_region_hotkey},
{"screenshot.take_screenshot_window_hotkey", &config.screenshot_config.take_screenshot_window_hotkey}
}; };
} }

View File

@@ -378,6 +378,13 @@ namespace gsr {
overlay->take_screenshot_region(); overlay->take_screenshot_region();
}); });
global_hotkeys->bind_key_press(
config_hotkey_to_hotkey(overlay->get_config().screenshot_config.take_screenshot_window_hotkey),
"take_screenshot_window", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->take_screenshot_window();
});
global_hotkeys->bind_key_press( global_hotkeys->bind_key_press(
config_hotkey_to_hotkey(ConfigHotkey{ mgl::Keyboard::Key::Escape, HOTKEY_MOD_LCTRL | HOTKEY_MOD_LSHIFT | HOTKEY_MOD_LALT }), config_hotkey_to_hotkey(ConfigHotkey{ mgl::Keyboard::Key::Escape, HOTKEY_MOD_LCTRL | HOTKEY_MOD_LSHIFT | HOTKEY_MOD_LALT }),
"exit", [overlay](const std::string &id) { "exit", [overlay](const std::string &id) {
@@ -1481,11 +1488,15 @@ namespace gsr {
} }
void Overlay::take_screenshot() { void Overlay::take_screenshot() {
on_press_take_screenshot(false, false); on_press_take_screenshot(false, ScreenshotForceType::NONE);
} }
void Overlay::take_screenshot_region() { void Overlay::take_screenshot_region() {
on_press_take_screenshot(false, true); on_press_take_screenshot(false, ScreenshotForceType::REGION);
}
void Overlay::take_screenshot_window() {
on_press_take_screenshot(false, ScreenshotForceType::WINDOW);
} }
static const char* notification_type_to_string(NotificationType notification_type) { static const char* notification_type_to_string(NotificationType notification_type) {
@@ -2940,7 +2951,7 @@ namespace gsr {
hide_ui = true; hide_ui = true;
} }
void Overlay::on_press_take_screenshot(bool finished_selection, bool force_region_capture) { void Overlay::on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type) {
if(region_selector.is_started() || window_selector.is_started()) if(region_selector.is_started() || window_selector.is_started())
return; return;
@@ -2949,8 +2960,21 @@ namespace gsr {
return; return;
} }
const bool region_capture = config.screenshot_config.record_area_option == "region" || force_region_capture; bool hotkey_window_capture = false;
const char *record_area_option = region_capture ? "region" : config.screenshot_config.record_area_option.c_str(); std::string record_area_option;
switch(force_type) {
case ScreenshotForceType::NONE:
record_area_option = config.screenshot_config.record_area_option;
break;
case ScreenshotForceType::REGION:
record_area_option = "region";
break;
case ScreenshotForceType::WINDOW:
record_area_option = gsr_info.system_info.display_server == DisplayServer::X11 ? "window" : "portal";
hotkey_window_capture = true;
break;
}
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
screenshot_capture_target = get_capture_target(record_area_option, capture_options); screenshot_capture_target = get_capture_target(record_area_option, capture_options);
if(!validate_capture_target(record_area_option, capture_options)) { if(!validate_capture_target(record_area_option, capture_options)) {
@@ -2960,19 +2984,19 @@ namespace gsr {
return; return;
} }
if(region_capture && !finished_selection) { if(record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [this, force_region_capture]() { on_region_selected = [this, force_type]() {
usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this
on_press_take_screenshot(true, force_region_capture); on_press_take_screenshot(true, force_type);
}; };
return; return;
} }
if(config.screenshot_config.record_area_option == "window" && !finished_selection) { if(record_area_option == "window" && !finished_selection) {
start_window_capture = true; start_window_capture = true;
on_window_selected = [this, force_region_capture]() { on_window_selected = [this, force_type]() {
on_press_take_screenshot(true, force_region_capture); on_press_take_screenshot(true, force_type);
}; };
return; return;
} }
@@ -2996,13 +3020,22 @@ namespace gsr {
args.push_back(size); args.push_back(size);
} }
if(config.screenshot_config.restore_portal_session) { if(config.screenshot_config.restore_portal_session && !hotkey_window_capture) {
args.push_back("-restore-portal-session"); args.push_back("-restore-portal-session");
args.push_back("yes"); args.push_back("yes");
} }
const std::string hotkey_window_capture_portal_session_token_filepath = get_config_dir() + "/gpu-screen-recorder/gsr-ui-window-capture-token";
if(record_area_option == "portal") {
hide_ui = true;
if(hotkey_window_capture) {
args.push_back("-portal-session-token-filepath");
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
}
}
char region_str[128]; char region_str[128];
if(region_capture) if(record_area_option == "region")
add_region_command(args, region_str, sizeof(region_str), region_selector); add_region_command(args, region_str, sizeof(region_str), region_selector);
args.push_back(nullptr); args.push_back(nullptr);
@@ -3012,9 +3045,6 @@ namespace gsr {
if(gpu_screen_recorder_screenshot_process == -1) { if(gpu_screen_recorder_screenshot_process == -1) {
show_notification("Failed to launch gpu-screen-recorder to take a screenshot", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT); show_notification("Failed to launch gpu-screen-recorder to take a screenshot", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT);
} }
if(config.screenshot_config.record_area_option == "portal")
hide_ui = true;
} }
bool Overlay::update_compositor_texture(const Monitor &monitor) { bool Overlay::update_compositor_texture(const Monitor &monitor) {

View File

@@ -348,6 +348,27 @@ namespace gsr {
return list; return list;
} }
std::unique_ptr<List> GlobalSettingsPage::create_screenshot_window_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
char str[128];
if(gsr_info->system_info.display_server == DisplayServer::X11)
snprintf(str, sizeof(str), "Take a screenshot of a window:");
else
snprintf(str, sizeof(str), "Take a screenshot with desktop portal:");
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
auto take_screenshot_window_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
take_screenshot_window_button_ptr = take_screenshot_window_button.get();
list->add_widget(std::move(take_screenshot_window_button));
take_screenshot_window_button_ptr->on_click = [this] {
configure_hotkey_start(ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW);
};
return list;
}
std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() { std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
@@ -362,6 +383,7 @@ namespace gsr {
config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0}; config.replay_config.save_10_min_hotkey = {mgl::Keyboard::Unknown, 0};
config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0}; config.screenshot_config.take_screenshot_hotkey = {mgl::Keyboard::Unknown, 0};
config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0}; config.screenshot_config.take_screenshot_region_hotkey = {mgl::Keyboard::Unknown, 0};
config.screenshot_config.take_screenshot_window_hotkey = {mgl::Keyboard::Unknown, 0};
config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0}; config.main_config.show_hide_hotkey = {mgl::Keyboard::Unknown, 0};
load_hotkeys(); load_hotkeys();
overlay->rebind_all_keyboard_hotkeys(); overlay->rebind_all_keyboard_hotkeys();
@@ -404,6 +426,7 @@ namespace gsr {
list_ptr->add_widget(create_stream_hotkey_options()); list_ptr->add_widget(create_stream_hotkey_options());
list_ptr->add_widget(create_screenshot_hotkey_options()); list_ptr->add_widget(create_screenshot_hotkey_options());
list_ptr->add_widget(create_screenshot_region_hotkey_options()); list_ptr->add_widget(create_screenshot_region_hotkey_options());
list_ptr->add_widget(create_screenshot_window_hotkey_options());
list_ptr->add_widget(create_hotkey_control_buttons()); list_ptr->add_widget(create_hotkey_control_buttons());
return subsection; return subsection;
} }
@@ -528,6 +551,7 @@ namespace gsr {
take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string()); take_screenshot_button_ptr->set_text(config.screenshot_config.take_screenshot_hotkey.to_string());
take_screenshot_region_button_ptr->set_text(config.screenshot_config.take_screenshot_region_hotkey.to_string()); take_screenshot_region_button_ptr->set_text(config.screenshot_config.take_screenshot_region_hotkey.to_string());
take_screenshot_window_button_ptr->set_text(config.screenshot_config.take_screenshot_window_hotkey.to_string());
show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string()); show_hide_button_ptr->set_text(config.main_config.show_hide_hotkey.to_string());
} }
@@ -611,6 +635,8 @@ namespace gsr {
return take_screenshot_button_ptr; return take_screenshot_button_ptr;
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION: case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
return take_screenshot_region_button_ptr; return take_screenshot_region_button_ptr;
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW:
return take_screenshot_window_button_ptr;
case ConfigureHotkeyType::SHOW_HIDE: case ConfigureHotkeyType::SHOW_HIDE:
return show_hide_button_ptr; return show_hide_button_ptr;
} }
@@ -639,6 +665,8 @@ namespace gsr {
return &config.screenshot_config.take_screenshot_hotkey; return &config.screenshot_config.take_screenshot_hotkey;
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION: case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
return &config.screenshot_config.take_screenshot_region_hotkey; return &config.screenshot_config.take_screenshot_region_hotkey;
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW:
return &config.screenshot_config.take_screenshot_window_hotkey;
case ConfigureHotkeyType::SHOW_HIDE: case ConfigureHotkeyType::SHOW_HIDE:
return &config.main_config.show_hide_hotkey; return &config.main_config.show_hide_hotkey;
} }
@@ -654,6 +682,7 @@ namespace gsr {
&config.streaming_config.start_stop_hotkey, &config.streaming_config.start_stop_hotkey,
&config.screenshot_config.take_screenshot_hotkey, &config.screenshot_config.take_screenshot_hotkey,
&config.screenshot_config.take_screenshot_region_hotkey, &config.screenshot_config.take_screenshot_region_hotkey,
&config.screenshot_config.take_screenshot_window_hotkey,
&config.main_config.show_hide_hotkey &config.main_config.show_hide_hotkey
}; };
for(ConfigHotkey *config_hotkey : config_hotkeys) { for(ConfigHotkey *config_hotkey : config_hotkeys) {
@@ -702,6 +731,13 @@ namespace gsr {
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION: case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
hotkey_configure_action_name = "Take a screenshot of a region"; hotkey_configure_action_name = "Take a screenshot of a region";
break; break;
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW: {
if(gsr_info->system_info.display_server == DisplayServer::X11)
hotkey_configure_action_name = "Take a screenshot of a window";
else
hotkey_configure_action_name = "Take a screenshot with desktop portal";
break;
}
case ConfigureHotkeyType::SHOW_HIDE: case ConfigureHotkeyType::SHOW_HIDE:
hotkey_configure_action_name = "Show/hide UI"; hotkey_configure_action_name = "Show/hide UI";
break; break;

View File

@@ -95,6 +95,11 @@ static void rpc_add_commands(gsr::Rpc *rpc, gsr::Overlay *overlay) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str()); fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->take_screenshot_region(); overlay->take_screenshot_region();
}); });
rpc->add_handler("take-screenshot-window", [overlay](const std::string &name) {
fprintf(stderr, "rpc command executed: %s\n", name.c_str());
overlay->take_screenshot_window();
});
} }
static bool is_gsr_ui_virtual_keyboard_running() { static bool is_gsr_ui_virtual_keyboard_running() {

View File

@@ -64,6 +64,8 @@ static void usage(void) {
printf(" Take a screenshot.\n"); printf(" Take a screenshot.\n");
printf(" take-screenshot-region\n"); printf(" take-screenshot-region\n");
printf(" Take a screenshot of a region.\n"); printf(" Take a screenshot of a region.\n");
printf(" take-screenshot-window\n");
printf(" Take a screenshot of a window (or desktop portal on Wayland).\n");
printf("\n"); printf("\n");
printf("EXAMPLES:\n"); printf("EXAMPLES:\n");
printf(" gsr-ui-cli toggle-show\n"); printf(" gsr-ui-cli toggle-show\n");
@@ -83,6 +85,7 @@ static bool is_valid_command(const char *command) {
"replay-save-10-min", "replay-save-10-min",
"take-screenshot", "take-screenshot",
"take-screenshot-region", "take-screenshot-region",
"take-screenshot-window",
NULL NULL
}; };