diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-05-31 22:54:14 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-05-31 23:00:42 +0200 |
commit | 4d7526d21ed78f61eefcb88631762127fb0d4e97 (patch) | |
tree | 0d0121c5acee59813eb66b8eb7ba0b095a8060f5 /src | |
parent | fded9b8d57facde1a01e9456d9fd9c3b6955dd23 (diff) |
Add x11 window capture (video and screenshot)
Diffstat (limited to 'src')
-rw-r--r-- | src/Config.cpp | 2 | ||||
-rw-r--r-- | src/Overlay.cpp | 220 | ||||
-rw-r--r-- | src/RegionSelector.cpp | 6 | ||||
-rw-r--r-- | src/Utils.cpp | 22 | ||||
-rw-r--r-- | src/WindowSelector.cpp | 229 | ||||
-rw-r--r-- | src/WindowUtils.cpp | 25 | ||||
-rw-r--r-- | src/gui/ScreenshotSettingsPage.cpp | 16 | ||||
-rw-r--r-- | src/gui/SettingsPage.cpp | 24 |
8 files changed, 407 insertions, 137 deletions
diff --git a/src/Config.cpp b/src/Config.cpp index 9e10534..313cd38 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -119,7 +119,7 @@ namespace gsr { streaming_config.record_options.video_quality = "custom"; streaming_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); - streaming_config.record_options.video_bitrate = 15000; + streaming_config.record_options.video_bitrate = 8000; record_config.save_directory = default_videos_save_directory; record_config.record_options.audio_tracks_list.push_back({std::vector<std::string>{"default_output"}, false}); diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 8a60b91..170759a 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> @@ -678,6 +679,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 +740,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 +883,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; @@ -1313,6 +1339,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 +1462,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 +1507,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 +1723,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 +1752,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 +1766,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 +1774,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 +1807,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 +1928,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 +2027,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 +2220,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 +2257,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 +2328,8 @@ 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()) + 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 +2379,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 +2387,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); @@ -2421,18 +2474,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 +2558,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 +2566,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 @@ -2578,10 +2636,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 +2676,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 +2723,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 +2731,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); @@ -2751,16 +2814,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 +2839,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 +2848,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 diff --git a/src/RegionSelector.cpp b/src/RegionSelector.cpp index 5b7243b..89a0209 100644 --- a/src/RegionSelector.cpp +++ b/src/RegionSelector.cpp @@ -208,7 +208,7 @@ namespace gsr { window_attr.background_pixel = is_wayland ? 0 : border_color_x11; window_attr.border_pixel = 0; window_attr.override_redirect = true; - window_attr.event_mask = StructureNotifyMask | PointerMotionMask; + window_attr.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask; window_attr.colormap = region_window_colormap; Screen *screen = XDefaultScreenOfDisplay(dpy); @@ -366,10 +366,6 @@ namespace gsr { return true; } - bool RegionSelector::is_selected() const { - return selected; - } - bool RegionSelector::take_selection() { const bool result = selected; selected = false; diff --git a/src/Utils.cpp b/src/Utils.cpp index f23a330..c36a64a 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -32,6 +32,28 @@ namespace gsr { return str.size() >= len && memcmp(str.data() + str.size() - len, substr, len) == 0; } + std::string strip(const std::string &str) { + int start_index = 0; + int str_len = str.size(); + + for(int i = 0; i < str_len; ++i) { + if(str[i] != ' ') { + start_index += i; + str_len -= i; + break; + } + } + + for(int i = str_len - 1; i >= 0; --i) { + if(str[i] != ' ') { + str_len = i + 1; + break; + } + } + + return str.substr(start_index, str_len); + } + std::string get_home_dir() { const char *home_dir = getenv("HOME"); if(!home_dir) { diff --git a/src/WindowSelector.cpp b/src/WindowSelector.cpp new file mode 100644 index 0000000..f04d600 --- /dev/null +++ b/src/WindowSelector.cpp @@ -0,0 +1,229 @@ +#include "../include/WindowSelector.hpp" +#include "../include/WindowUtils.hpp" + +#include <stdio.h> +#include <string.h> + +#include <X11/extensions/shape.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> + +namespace gsr { + static const int rectangle_border_size = 2; + + static int max_int(int a, int b) { + return a >= b ? a : b; + } + + static void set_region_rectangle(Display *dpy, Window window, int x, int y, int width, int height, int border_size) { + if(width < 0) { + x += width; + width = abs(width); + } + + if(height < 0) { + y += height; + height = abs(height); + } + + XRectangle rectangles[] = { + { + (short)max_int(0, x), (short)max_int(0, y), + (unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height) + }, // Left + { + (short)max_int(0, x + width - border_size), (short)max_int(0, y), + (unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height) + }, // Right + { + (short)max_int(0, x + border_size), (short)max_int(0, y), + (unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size) + }, // Top + { + (short)max_int(0, x + border_size), (short)max_int(0, y + height - border_size), + (unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size) + }, // Bottom + }; + XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted); + XFlush(dpy); + } + + static unsigned long mgl_color_to_x11_color(mgl::Color color) { + if(color.a == 0) + return 0; + return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF); + } + + static Window get_cursor_window(Display *dpy) { + Window root_window = None; + Window window = None; + int dummy_i; + unsigned int dummy_u; + mgl::vec2i root_pos; + XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u); + return window; + } + + static void get_window_geometry(Display *dpy, Window window, mgl::vec2i &pos, mgl::vec2i &size) { + Window root_window; + int x = 0; + int y = 0; + unsigned int w = 0; + unsigned int h = 0; + unsigned int dummy_border, dummy_depth; + XGetGeometry(dpy, window, &root_window, &x, &y, &w, &h, &dummy_border, &dummy_depth); + pos.x = x; + pos.y = y; + size.x = w; + size.y = h; + } + + WindowSelector::WindowSelector() { + + } + + WindowSelector::~WindowSelector() { + stop(); + } + + bool WindowSelector::start(mgl::Color border_color) { + if(dpy) + return false; + + const unsigned long border_color_x11 = mgl_color_to_x11_color(border_color); + dpy = XOpenDisplay(nullptr); + if(!dpy) { + fprintf(stderr, "Error: WindowSelector::start: failed to connect to the X11 server\n"); + return false; + } + + const Window cursor_window = get_cursor_window(dpy); + mgl::vec2i cursor_window_pos, cursor_window_size; + get_window_geometry(dpy, cursor_window, cursor_window_pos, cursor_window_size); + + XVisualInfo vinfo; + memset(&vinfo, 0, sizeof(vinfo)); + XMatchVisualInfo(dpy, DefaultScreen(dpy), 32, TrueColor, &vinfo); + border_window_colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo.visual, AllocNone); + + XSetWindowAttributes window_attr; + window_attr.background_pixel = border_color_x11; + window_attr.border_pixel = 0; + window_attr.override_redirect = true; + window_attr.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask; + window_attr.colormap = border_window_colormap; + + Screen *screen = XDefaultScreenOfDisplay(dpy); + border_window = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, XWidthOfScreen(screen), XHeightOfScreen(screen), 0, + vinfo.depth, InputOutput, vinfo.visual, CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWEventMask | CWColormap, &window_attr); + if(!border_window) { + fprintf(stderr, "Error: WindowSelector::start: failed to create region window\n"); + stop(); + return false; + } + set_window_size_not_resizable(dpy, border_window, XWidthOfScreen(screen), XHeightOfScreen(screen)); + if(cursor_window && cursor_window != DefaultRootWindow(dpy)) + set_region_rectangle(dpy, border_window, cursor_window_pos.x, cursor_window_pos.y, cursor_window_size.x, cursor_window_size.y, rectangle_border_size); + else + set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0); + make_window_click_through(dpy, border_window); + XMapWindow(dpy, border_window); + + crosshair_cursor = XCreateFontCursor(dpy, XC_crosshair); + XGrabPointer(dpy, DefaultRootWindow(dpy), True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, crosshair_cursor, CurrentTime); + XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime); + XFlush(dpy); + + selected = false; + canceled = false; + selected_window = None; + return true; + } + + void WindowSelector::stop() { + if(!dpy) + return; + + XUngrabPointer(dpy, CurrentTime); + XUngrabKeyboard(dpy, CurrentTime); + + if(border_window_colormap) { + XFreeColormap(dpy, border_window_colormap); + border_window_colormap = 0; + } + + if(border_window) { + XDestroyWindow(dpy, border_window); + border_window = 0; + } + + if(crosshair_cursor) { + XFreeCursor(dpy, crosshair_cursor); + crosshair_cursor = None; + } + + XFlush(dpy); + XCloseDisplay(dpy); + dpy = nullptr; + } + + bool WindowSelector::is_started() const { + return dpy != nullptr; + } + + bool WindowSelector::failed() const { + return !dpy; + } + + bool WindowSelector::poll_events() { + if(!dpy || selected) + return false; + + XEvent xev; + while(XPending(dpy)) { + XNextEvent(dpy, &xev); + + if(xev.type == MotionNotify) { + const Window motion_window = xev.xmotion.subwindow; + mgl::vec2i motion_window_pos, motion_window_size; + get_window_geometry(dpy, motion_window, motion_window_pos, motion_window_size); + if(motion_window && motion_window != DefaultRootWindow(dpy)) + set_region_rectangle(dpy, border_window, motion_window_pos.x, motion_window_pos.y, motion_window_size.x, motion_window_size.y, rectangle_border_size); + else + set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0); + XFlush(dpy); + } else if(xev.type == ButtonRelease && xev.xbutton.button == Button1) { + selected_window = xev.xbutton.subwindow; + const Window clicked_window_real = window_get_target_window_child(dpy, selected_window); + if(clicked_window_real) + selected_window = clicked_window_real; + selected = true; + + stop(); + break; + } else if(xev.type == KeyRelease && XKeycodeToKeysym(dpy, xev.xkey.keycode, 0) == XK_Escape) { + canceled = true; + selected = false; + stop(); + break; + } + } + return true; + } + + bool WindowSelector::take_selection() { + const bool result = selected; + selected = false; + return result; + } + + bool WindowSelector::take_canceled() { + const bool result = canceled; + canceled = false; + return result; + } + + Window WindowSelector::get_selection() const { + return selected_window; + } +}
\ No newline at end of file diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp index eb6080f..c6b278b 100644 --- a/src/WindowUtils.cpp +++ b/src/WindowUtils.cpp @@ -1,4 +1,5 @@ #include "../include/WindowUtils.hpp" +#include "../include/Utils.hpp" #include <X11/Xatom.h> #include <X11/Xutil.h> @@ -62,7 +63,7 @@ namespace gsr { return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom); } - static Window window_get_target_window_child(Display *display, Window window) { + Window window_get_target_window_child(Display *display, Window window) { if(window == None) return None; @@ -212,28 +213,6 @@ namespace gsr { return result; } - static std::string strip(const std::string &str) { - int start_index = 0; - int str_len = str.size(); - - for(int i = 0; i < str_len; ++i) { - if(str[i] != ' ') { - start_index += i; - str_len -= i; - break; - } - } - - for(int i = str_len - 1; i >= 0; --i) { - if(str[i] != ' ') { - str_len = i + 1; - break; - } - } - - return str.substr(start_index, str_len); - } - std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type) { std::string result; const Window focused_window = get_focused_window(dpy, window_capture_type); diff --git a/src/gui/ScreenshotSettingsPage.cpp b/src/gui/ScreenshotSettingsPage.cpp index 5b8efbd..27a94b0 100644 --- a/src/gui/ScreenshotSettingsPage.cpp +++ b/src/gui/ScreenshotSettingsPage.cpp @@ -35,9 +35,8 @@ namespace gsr { std::unique_ptr<ComboBox> ScreenshotSettingsPage::create_record_area_box() { auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font); // TODO: Show options not supported but disable them - // TODO: Enable this - //if(capture_options.window) - // record_area_box->add_item("Window", "window"); + if(capture_options.window) + record_area_box->add_item("Window", "window"); if(capture_options.region) record_area_box->add_item("Region", "region"); if(!capture_options.monitors.empty()) @@ -60,14 +59,6 @@ namespace gsr { return record_area_list; } - std::unique_ptr<List> ScreenshotSettingsPage::create_select_window() { - auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL); - select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color)); - select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120))); - select_window_list_ptr = select_window_list.get(); - return select_window_list; - } - std::unique_ptr<Entry> ScreenshotSettingsPage::create_image_width_entry() { auto image_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3); image_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); @@ -124,7 +115,6 @@ namespace gsr { auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); capture_target_list->add_widget(create_record_area()); - capture_target_list->add_widget(create_select_window()); capture_target_list->add_widget(create_image_resolution_section()); capture_target_list->add_widget(create_restore_portal_session_section()); @@ -258,9 +248,7 @@ namespace gsr { content_page_ptr->add_widget(create_settings()); record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { - const bool window_selected = id == "window"; const bool portal_selected = id == "portal"; - select_window_list_ptr->set_visible(window_selected); image_resolution_list_ptr->set_visible(change_image_resolution_checkbox_ptr->is_checked()); restore_portal_session_list_ptr->set_visible(portal_selected); return true; diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 1230290..26e7335 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -65,13 +65,12 @@ namespace gsr { std::unique_ptr<ComboBox> SettingsPage::create_record_area_box() { auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font); // TODO: Show options not supported but disable them - // TODO: Enable this - //if(capture_options.window) - // record_area_box->add_item("Window", "window"); - if(capture_options.region) - record_area_box->add_item("Region", "region"); + if(capture_options.window) + record_area_box->add_item("Window", "window"); if(capture_options.focused) record_area_box->add_item("Follow focused window", "focused"); + if(capture_options.region) + record_area_box->add_item("Region", "region"); if(!capture_options.monitors.empty()) record_area_box->add_item(gsr_info->system_info.display_server == DisplayServer::WAYLAND ? "Focused monitor (Experimental on Wayland)" : "Focused monitor", "focused_monitor"); for(const auto &monitor : capture_options.monitors) { @@ -92,14 +91,6 @@ namespace gsr { return record_area_list; } - std::unique_ptr<List> SettingsPage::create_select_window() { - auto select_window_list = std::make_unique<List>(List::Orientation::VERTICAL); - select_window_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Select window:", get_color_theme().text_color)); - select_window_list->add_widget(std::make_unique<Button>(&get_theme().body_font, "Click here to select a window...", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120))); - select_window_list_ptr = select_window_list.get(); - return select_window_list; - } - std::unique_ptr<Entry> SettingsPage::create_area_width_entry() { auto area_width_entry = std::make_unique<Entry>(&get_theme().body_font, "1920", get_theme().body_font.get_character_size() * 3); area_width_entry->validate_handler = create_entry_validator_integer_in_range(1, 1 << 15); @@ -186,7 +177,6 @@ namespace gsr { auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); capture_target_list->add_widget(create_record_area()); - capture_target_list->add_widget(create_select_window()); capture_target_list->add_widget(create_area_size_section()); capture_target_list->add_widget(create_video_resolution_section()); capture_target_list->add_widget(create_restore_portal_session_section()); @@ -451,13 +441,13 @@ namespace gsr { std::unique_ptr<List> SettingsPage::create_video_bitrate_entry() { auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); - auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "15000", (int)(get_theme().body_font.get_character_size() * 4.0f)); + auto video_bitrate_entry = std::make_unique<Entry>(&get_theme().body_font, "8000", (int)(get_theme().body_font.get_character_size() * 4.0f)); video_bitrate_entry->validate_handler = create_entry_validator_integer_in_range(1, 500000); video_bitrate_entry_ptr = video_bitrate_entry.get(); list->add_widget(std::move(video_bitrate_entry)); if(type == Type::STREAM) { - auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "1.64MB", get_color_theme().text_color); + auto size_mb_label = std::make_unique<Label>(&get_theme().body_font, "", get_color_theme().text_color); Label *size_mb_label_ptr = size_mb_label.get(); list->add_widget(std::move(size_mb_label)); @@ -634,10 +624,8 @@ namespace gsr { content_page_ptr->add_widget(create_settings()); record_area_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) { - const bool window_selected = id == "window"; const bool focused_selected = id == "focused"; const bool portal_selected = id == "portal"; - select_window_list_ptr->set_visible(window_selected); area_size_list_ptr->set_visible(focused_selected); video_resolution_list_ptr->set_visible(!focused_selected && change_video_resolution_checkbox_ptr->is_checked()); change_video_resolution_checkbox_ptr->set_visible(!focused_selected); |