diff options
Diffstat (limited to 'src/Overlay.cpp')
-rw-r--r-- | src/Overlay.cpp | 325 |
1 files changed, 228 insertions, 97 deletions
diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 8a60b91..dbf71c7 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -26,6 +26,7 @@ #include <malloc.h> #include <stdexcept> #include <algorithm> +#include <inttypes.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -272,6 +273,33 @@ namespace gsr { return true; } + static bool is_hyprland_waybar_running_as_dock() { + const char *args[] = { "hyprctl", "layers", nullptr }; + std::string stdout_str; + if(exec_program_on_host_get_stdout(args, stdout_str) != 0) + return false; + + int waybar_layer_level = -1; + int current_layer_level = 0; + string_split_char(stdout_str, '\n', [&](const std::string_view line) { + if(line.find("Layer level 0") != std::string_view::npos) + current_layer_level = 0; + else if(line.find("Layer level 1") != std::string_view::npos) + current_layer_level = 1; + else if(line.find("Layer level 2") != std::string_view::npos) + current_layer_level = 2; + else if(line.find("Layer level 3") != std::string_view::npos) + current_layer_level = 3; + else if(line.find("namespace: waybar") != std::string_view::npos) { + waybar_layer_level = current_layer_level; + return false; + } + return true; + }); + + return waybar_layer_level >= 0 && waybar_layer_level <= 1; + } + static Hotkey config_hotkey_to_hotkey(ConfigHotkey config_hotkey) { return { (uint32_t)mgl::Keyboard::key_to_x11_keysym((mgl::Keyboard::Key)config_hotkey.key), @@ -678,6 +706,22 @@ namespace gsr { on_region_selected = nullptr; } + window_selector.poll_events(); + if(window_selector.take_canceled()) { + on_window_selected = nullptr; + } else if(window_selector.take_selection() && on_window_selected) { + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + + const Window selected_window = window_selector.get_selection(); + if(selected_window && selected_window != DefaultRootWindow(display)) { + on_window_selected(); + } else { + show_notification("No window selected", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE); + } + on_window_selected = nullptr; + } + if(!visible || !window) return; @@ -723,7 +767,16 @@ namespace gsr { } } - if(region_selector.is_started()) { + if(start_window_capture) { + start_window_capture = false; + hide(); + if(!window_selector.start(get_color_theme().tint_color)) { + show_notification("Failed to start window capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE); + on_window_selected = nullptr; + } + } + + if(region_selector.is_started() || window_selector.is_started()) { usleep(5 * 1000); // 5 ms return true; } @@ -857,7 +910,7 @@ namespace gsr { if(visible) return; - if(region_selector.is_started()) + if(region_selector.is_started() || window_selector.is_started()) return; drawn_first_frame = false; @@ -878,6 +931,8 @@ namespace gsr { const std::string wm_name = get_window_manager_name(display); const bool is_kwin = wm_name == "KWin"; const bool is_wlroots = wm_name.find("wlroots") != std::string::npos; + const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos; + const bool hyprland_waybar_is_dock = is_hyprland && is_hyprland_waybar_running_as_dock(); std::optional<CursorInfo> cursor_info; if(cursor_tracker) { @@ -905,7 +960,7 @@ namespace gsr { // If the focused window is a wayland application then don't use override redirect and instead create // a fullscreen window for the ui. // TODO: (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor)) - const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots; + const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots || is_hyprland; if(prevent_game_minimizing) { window_pos = focused_monitor->position; @@ -1009,7 +1064,7 @@ namespace gsr { // Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events. xi_grab_all_mouse_devices(xi_display); - if(!is_wlroots) + if(!is_wlroots && !hyprland_waybar_is_dock) window->set_fullscreen(true); visible = true; @@ -1313,6 +1368,7 @@ namespace gsr { visible = false; drawn_first_frame = false; start_region_capture = false; + start_window_capture = false; if(xi_input_xev) { free(xi_input_xev); @@ -1435,6 +1491,24 @@ namespace gsr { return nullptr; } + static void truncate_string(std::string &str, int max_length) { + int index = 0; + size_t byte_index = 0; + + while(index < max_length && byte_index < str.size()) { + uint32_t codepoint = 0; + size_t codepoint_length = 0; + mgl::utf8_decode((const unsigned char*)str.c_str() + byte_index, str.size() - byte_index, &codepoint, &codepoint_length); + if(codepoint_length == 0) + codepoint_length = 1; + + index += 1; + byte_index += codepoint_length; + } + + str.erase(byte_index); + } + static bool is_hex_num(char c) { return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'); } @@ -1462,8 +1536,44 @@ namespace gsr { return is_hex && !hex_start; } + static bool is_number(const char *str) { + const char *p = str; + while(*p) { + char c = *p; + if(c < '0' || c > '9') + return false; + ++p; + } + return true; + } + static bool is_capture_target_monitor(const char *capture_target) { - return strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target); + return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target); + } + + static std::string capture_target_get_notification_name(const char *capture_target) { + std::string result; + if(is_capture_target_monitor(capture_target)) { + result = "this monitor"; + } else if(is_number(capture_target)) { + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + + int64_t window_id = None; + sscanf(capture_target, "%" PRIi64, &window_id); + + const std::optional<std::string> window_title = get_window_title(display, window_id); + if(window_title) { + result = strip(window_title.value()); + truncate_string(result, 20); + result = "window \"" + result + "\""; + } else { + result = std::string("window ") + capture_target; + } + } else { + result = capture_target; + } + return result; } static std::string get_valid_monitor_x11(const std::string &target_monitor_name, const std::vector<Monitor> &monitors) { @@ -1642,24 +1752,6 @@ namespace gsr { return result; } - static void truncate_string(std::string &str, int max_length) { - int index = 0; - size_t byte_index = 0; - - while(index < max_length && byte_index < str.size()) { - uint32_t codepoint = 0; - size_t codepoint_length = 0; - mgl::utf8_decode((const unsigned char*)str.c_str() + byte_index, str.size() - byte_index, &codepoint, &codepoint_length); - if(codepoint_length == 0) - codepoint_length = 1; - - index += 1; - byte_index += codepoint_length; - } - - str.erase(byte_index); - } - void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) { mgl_context *context = mgl_get_context(); Display *display = (Display*)context->connection; @@ -1689,11 +1781,7 @@ namespace gsr { if(!config.record_config.show_video_saved_notifications) return; - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved a recording of this monitor to \"%s\"", focused_window_name.c_str()); - else - snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", recording_capture_target.c_str(), focused_window_name.c_str()); - + snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str()); capture_target = recording_capture_target.c_str(); break; } @@ -1707,11 +1795,7 @@ namespace gsr { else snprintf(duration, sizeof(duration), " "); - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved a%sreplay of this monitor to \"%s\"", duration, focused_window_name.c_str()); - else - snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, recording_capture_target.c_str(), focused_window_name.c_str()); - + snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str()); capture_target = recording_capture_target.c_str(); break; } @@ -1719,11 +1803,7 @@ namespace gsr { if(!config.screenshot_config.show_screenshot_saved_notifications) return; - if(is_capture_target_monitor(screenshot_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved a screenshot of this monitor to \"%s\"", focused_window_name.c_str()); - else - snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", screenshot_capture_target.c_str(), focused_window_name.c_str()); - + snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str(), focused_window_name.c_str()); capture_target = screenshot_capture_target.c_str(); break; } @@ -1756,10 +1836,7 @@ namespace gsr { snprintf(duration, sizeof(duration), " "); char msg[512]; - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved a%sreplay of this monitor", duration); - else - snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, recording_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str()); } } @@ -1880,10 +1957,7 @@ namespace gsr { save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT); } else if(config.screenshot_config.show_screenshot_saved_notifications) { char msg[512]; - if(is_capture_target_monitor(screenshot_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved a screenshot of this monitor"); - else - snprintf(msg, sizeof(msg), "Saved a screenshot of %s", screenshot_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str()); show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str()); } } else { @@ -1982,10 +2056,7 @@ namespace gsr { save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD); } else if(config.record_config.show_video_saved_notifications) { char msg[512]; - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Saved a recording of this monitor"); - else - snprintf(msg, sizeof(msg), "Saved a recording of %s", recording_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Saved a recording of %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); } } else { @@ -2178,11 +2249,12 @@ namespace gsr { } static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) { - // TODO: Also check x11 window when enabled (check if capture_target is a decminal/hex number) - if(capture_target == "region") { - return capture_options.region; + if(capture_target == "window") { + return capture_options.window; } else if(capture_target == "focused") { return capture_options.focused; + } else if(capture_target == "region") { + return capture_options.region; } else if(capture_target == "portal") { return capture_options.portal; } else if(capture_target == "focused_monitor") { @@ -2214,7 +2286,9 @@ namespace gsr { } std::string Overlay::get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) { - if(capture_target == "focused_monitor") { + if(capture_target == "window") { + return std::to_string(window_selector.get_selection()); + } else if(capture_target == "focused_monitor") { std::optional<CursorInfo> cursor_info; if(cursor_tracker) { cursor_tracker->update(); @@ -2283,8 +2357,50 @@ namespace gsr { kill(gpu_screen_recorder_process, SIGRTMIN+5); } - bool Overlay::on_press_start_replay(bool disable_notification, bool finished_region_selection) { - if(region_selector.is_started()) + static const char* switch_video_codec_to_usable_hardware_encoder(const GsrInfo &gsr_info) { + if(gsr_info.supported_video_codecs.h264) + return "h264"; + else if(gsr_info.supported_video_codecs.hevc) + return "hevc"; + else if(gsr_info.supported_video_codecs.av1) + return "av1"; + else if(gsr_info.supported_video_codecs.vp8) + return "vp8"; + else if(gsr_info.supported_video_codecs.vp9) + return "vp9"; + return nullptr; + } + + static const char* change_container_if_codec_not_supported(const char *video_codec, const char *container) { + if(strcmp(video_codec, "vp8") == 0 || strcmp(video_codec, "vp9") == 0) { + if(strcmp(container, "webm") != 0 && strcmp(container, "matroska") != 0) { + fprintf(stderr, "Warning: container '%s' is not compatible with video codec '%s', using webm container instead\n", container, video_codec); + return "webm"; + } + } else if(strcmp(container, "webm") == 0) { + fprintf(stderr, "Warning: container webm is not compatible with video codec '%s', using mp4 container instead\n", video_codec); + return "mp4"; + } + return container; + } + + static void choose_video_codec_and_container_with_fallback(const GsrInfo &gsr_info, const char **video_codec, const char **container, const char **encoder) { + *encoder = "gpu"; + if(strcmp(*video_codec, "h264_software") == 0) { + *video_codec = "h264"; + *encoder = "cpu"; + } else if(strcmp(*video_codec, "auto") == 0) { + *video_codec = switch_video_codec_to_usable_hardware_encoder(gsr_info); + if(!*video_codec) { + *video_codec = "h264"; + *encoder = "cpu"; + } + } + *container = change_container_if_codec_not_supported(*video_codec, *container); + } + + bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) { + if(region_selector.is_started() || window_selector.is_started()) return false; switch(recording_status) { @@ -2334,7 +2450,7 @@ namespace gsr { return false; } - if(config.replay_config.record_options.record_area_option == "region" && !finished_region_selection) { + if(config.replay_config.record_options.record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [disable_notification, this]() { on_press_start_replay(disable_notification, true); @@ -2342,6 +2458,14 @@ namespace gsr { return false; } + if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) { + start_window_capture = true; + on_window_selected = [disable_notification, this]() { + on_press_start_replay(disable_notification, true); + }; + return false; + } + // TODO: Validate input, fallback to valid values const std::string fps = std::to_string(config.replay_config.record_options.fps); const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate); @@ -2349,12 +2473,10 @@ namespace gsr { const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.replay_config.record_options.audio_tracks_list, gsr_info); const std::string framerate_mode = config.replay_config.record_options.framerate_mode == "auto" ? "vfr" : config.replay_config.record_options.framerate_mode; const std::string replay_time = std::to_string(config.replay_config.replay_time); + const char *container = config.replay_config.container.c_str(); const char *video_codec = config.replay_config.record_options.video_codec.c_str(); const char *encoder = "gpu"; - if(strcmp(video_codec, "h264_software") == 0) { - video_codec = "h264"; - encoder = "cpu"; - } + choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder); char size[64]; size[0] = '\0'; @@ -2366,7 +2488,7 @@ namespace gsr { std::vector<const char*> args = { "gpu-screen-recorder", "-w", recording_capture_target.c_str(), - "-c", config.replay_config.container.c_str(), + "-c", container, "-ac", config.replay_config.record_options.audio_codec.c_str(), "-cursor", config.replay_config.record_options.record_cursor ? "yes" : "no", "-cr", config.replay_config.record_options.color_range.c_str(), @@ -2421,18 +2543,15 @@ namespace gsr { // to see when the program has exit. if(!disable_notification && config.replay_config.show_replay_started_notifications) { char msg[256]; - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Started replaying this monitor"); - else - snprintf(msg, sizeof(msg), "Started replaying %s", recording_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str()); } return true; } - void Overlay::on_press_start_record(bool finished_region_selection) { - if(region_selector.is_started()) + void Overlay::on_press_start_record(bool finished_selection) { + if(region_selector.is_started() || window_selector.is_started()) return; switch(recording_status) { @@ -2508,7 +2627,7 @@ namespace gsr { return; } - if(config.record_config.record_options.record_area_option == "region" && !finished_region_selection) { + if(config.record_config.record_options.record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [this]() { on_press_start_record(true); @@ -2516,6 +2635,14 @@ namespace gsr { return; } + if(config.record_config.record_options.record_area_option == "window" && !finished_selection) { + start_window_capture = true; + on_window_selected = [this]() { + on_press_start_record(true); + }; + return; + } + record_filepath.clear(); // TODO: Validate input, fallback to valid values @@ -2524,12 +2651,10 @@ namespace gsr { const std::string output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str()); const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.record_config.record_options.audio_tracks_list, gsr_info); const std::string framerate_mode = config.record_config.record_options.framerate_mode == "auto" ? "vfr" : config.record_config.record_options.framerate_mode; + const char *container = config.record_config.container.c_str(); const char *video_codec = config.record_config.record_options.video_codec.c_str(); const char *encoder = "gpu"; - if(strcmp(video_codec, "h264_software") == 0) { - video_codec = "h264"; - encoder = "cpu"; - } + choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder); char size[64]; size[0] = '\0'; @@ -2541,7 +2666,7 @@ namespace gsr { std::vector<const char*> args = { "gpu-screen-recorder", "-w", recording_capture_target.c_str(), - "-c", config.record_config.container.c_str(), + "-c", container, "-ac", config.record_config.record_options.audio_codec.c_str(), "-cursor", config.record_config.record_options.record_cursor ? "yes" : "no", "-cr", config.record_config.record_options.color_range.c_str(), @@ -2578,10 +2703,7 @@ namespace gsr { // 1... if(config.record_config.show_recording_started_notifications) { char msg[256]; - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Started recording this monitor"); - else - snprintf(msg, sizeof(msg), "Started recording %s", recording_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); } } @@ -2621,8 +2743,8 @@ namespace gsr { return url; } - void Overlay::on_press_start_stream(bool finished_region_selection) { - if(region_selector.is_started()) + void Overlay::on_press_start_stream(bool finished_selection) { + if(region_selector.is_started() || window_selector.is_started()) return; switch(recording_status) { @@ -2668,7 +2790,7 @@ namespace gsr { return; } - if(config.streaming_config.record_options.record_area_option == "region" && !finished_region_selection) { + if(config.streaming_config.record_options.record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [this]() { on_press_start_stream(true); @@ -2676,6 +2798,14 @@ namespace gsr { return; } + if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) { + start_window_capture = true; + on_window_selected = [this]() { + on_press_start_stream(true); + }; + return; + } + // TODO: Validate input, fallback to valid values const std::string fps = std::to_string(config.streaming_config.record_options.fps); const std::string video_bitrate = std::to_string(config.streaming_config.record_options.video_bitrate); @@ -2685,16 +2815,12 @@ namespace gsr { if(audio_tracks.size() > 1) audio_tracks.resize(1); const std::string framerate_mode = config.streaming_config.record_options.framerate_mode == "auto" ? "vfr" : config.streaming_config.record_options.framerate_mode; + const char *container = "flv"; + if(config.streaming_config.streaming_service == "custom") + container = config.streaming_config.custom.container.c_str(); const char *video_codec = config.streaming_config.record_options.video_codec.c_str(); const char *encoder = "gpu"; - if(strcmp(video_codec, "h264_software") == 0) { - video_codec = "h264"; - encoder = "cpu"; - } - - std::string container = "flv"; - if(config.streaming_config.streaming_service == "custom") - container = config.streaming_config.custom.container; + choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder); const std::string url = streaming_get_url(config); @@ -2708,7 +2834,7 @@ namespace gsr { std::vector<const char*> args = { "gpu-screen-recorder", "-w", recording_capture_target.c_str(), - "-c", container.c_str(), + "-c", container, "-ac", config.streaming_config.record_options.audio_codec.c_str(), "-cursor", config.streaming_config.record_options.record_cursor ? "yes" : "no", "-cr", config.streaming_config.record_options.color_range.c_str(), @@ -2751,16 +2877,13 @@ namespace gsr { // to see when the program has exit. if(config.streaming_config.show_streaming_started_notifications) { char msg[256]; - if(is_capture_target_monitor(recording_capture_target.c_str())) - snprintf(msg, sizeof(msg), "Started streaming this monitor"); - else - snprintf(msg, sizeof(msg), "Started streaming %s", recording_capture_target.c_str()); + snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str()); show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str()); } } - void Overlay::on_press_take_screenshot(bool finished_region_selection, bool force_region_capture) { - if(region_selector.is_started()) + void Overlay::on_press_take_screenshot(bool finished_selection, bool force_region_capture) { + if(region_selector.is_started() || window_selector.is_started()) return; if(gpu_screen_recorder_screenshot_process > 0) { @@ -2779,7 +2902,7 @@ namespace gsr { return; } - if(region_capture && !finished_region_selection) { + if(region_capture && !finished_selection) { start_region_capture = true; on_region_selected = [this, force_region_capture]() { usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this @@ -2788,6 +2911,14 @@ namespace gsr { return; } + if(config.screenshot_config.record_area_option == "window" && !finished_selection) { + start_window_capture = true; + on_window_selected = [this, force_region_capture]() { + on_press_take_screenshot(true, force_region_capture); + }; + return; + } + // TODO: Validate input, fallback to valid values const std::string output_file = config.screenshot_config.save_directory + "/Screenshot_" + get_date_str() + "." + config.screenshot_config.image_format; // TODO: Validate image format |