diff --git a/TODO b/TODO index 99414e0..e09ccca 100644 --- a/TODO +++ b/TODO @@ -117,8 +117,6 @@ System startup option should also support runit and some other init systems, not Use x11 shm instead of XGetImage (https://stackoverflow.com/questions/43442675/how-to-use-xshmgetimage-and-xshmputimage). -Add a hotkey to record/stream/replay region. - Do xi grab for keys as well. Otherwise the ui cant be used for keyboard input if a program has grabbed the keyboard, and there could possibly be a game that grabs the keyboard as well. Make inactive buttons gray (in dropdown boxes and in the front page with save, etc when replay is not running). diff --git a/include/Overlay.hpp b/include/Overlay.hpp index 4b3afbb..f151de7 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -9,7 +9,6 @@ #include "GlobalHotkeys/GlobalHotkeysJoystick.hpp" #include "AudioPlayer.hpp" #include "RegionSelector.hpp" -#include "WindowSelector.hpp" #include "ClipboardFile.hpp" #include "LedIndicator.hpp" #include "CursorTracker/CursorTracker.hpp" @@ -275,9 +274,7 @@ namespace gsr { bool start_region_capture = false; std::function on_region_selected; - WindowSelector window_selector; bool start_window_capture = false; - std::function on_window_selected; std::string recording_capture_target; std::string screenshot_capture_target; diff --git a/include/RegionSelector.hpp b/include/RegionSelector.hpp index 6997c36..5fd8264 100644 --- a/include/RegionSelector.hpp +++ b/include/RegionSelector.hpp @@ -15,14 +15,26 @@ namespace gsr { mgl::vec2i size; }; + struct RegionWindow { + Window window = None; + mgl::vec2i pos; + mgl::vec2i size; + }; + class RegionSelector { public: + enum class SelectionType { + NONE, + REGION, + WINDOW + }; + RegionSelector(); RegionSelector(const RegionSelector&) = delete; RegionSelector& operator=(const RegionSelector&) = delete; ~RegionSelector(); - bool start(mgl::Color border_color); + bool start(SelectionType selection_type, mgl::Color border_color); void stop(); bool is_started() const; @@ -30,7 +42,11 @@ namespace gsr { bool poll_events(); bool take_selection(); bool take_canceled(); - Region get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const; + Region get_region_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const; + // Returns None (0) if none is selected + Window get_window_selection() const; + + SelectionType get_selection_type() const; private: void on_button_press(const void *de); void on_button_release(const void *de); @@ -50,6 +66,10 @@ namespace gsr { bool canceled = false; bool is_wayland = false; std::vector monitors; + std::vector windows; // First window is the window that is on top + std::optional focused_window; mgl::vec2i cursor_pos; + + SelectionType selection_type = SelectionType::NONE; }; } \ No newline at end of file diff --git a/include/WindowSelector.hpp b/include/WindowSelector.hpp deleted file mode 100644 index ab4a85d..0000000 --- a/include/WindowSelector.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -#include - -namespace gsr { - class WindowSelector { - public: - WindowSelector(); - WindowSelector(const WindowSelector&) = delete; - WindowSelector& operator=(const WindowSelector&) = delete; - ~WindowSelector(); - - bool start(mgl::Color border_color); - void stop(); - bool is_started() const; - - bool failed() const; - bool poll_events(); - bool take_selection(); - bool take_canceled(); - Window get_selection() const; - private: - Display *dpy = nullptr; - Cursor crosshair_cursor = None; - Colormap border_window_colormap = None; - Window border_window = None; - Window selected_window = None; - bool selected = false; - bool canceled = false; - }; -} \ No newline at end of file diff --git a/include/WindowUtils.hpp b/include/WindowUtils.hpp index 7ad8e86..7ebe8b9 100644 --- a/include/WindowUtils.hpp +++ b/include/WindowUtils.hpp @@ -27,6 +27,7 @@ namespace gsr { std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window); void set_window_size_not_resizable(Display *dpy, Window window, int width, int height); Window window_get_target_window_child(Display *display, Window window); + unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size); mgl::vec2i get_cursor_position(Display *dpy, Window *window); mgl::vec2i create_window_get_center_position(Display *display); std::string get_window_manager_name(Display *display); diff --git a/meson.build b/meson.build index d974ba7..c3b9797 100644 --- a/meson.build +++ b/meson.build @@ -45,7 +45,6 @@ src = [ 'src/KwinWorkaround.cpp', 'src/WindowUtils.cpp', 'src/RegionSelector.cpp', - 'src/WindowSelector.cpp', 'src/Config.cpp', 'src/GsrInfo.cpp', 'src/Process.cpp', diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 8e744de..310fa04 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -804,24 +804,28 @@ namespace gsr { if(region_selector.take_canceled()) { on_region_selected = nullptr; } else if(region_selector.take_selection() && on_region_selected) { - on_region_selected(); - on_region_selected = nullptr; - } + switch(region_selector.get_selection_type()) { + case RegionSelector::SelectionType::NONE: { + break; + } + case RegionSelector::SelectionType::REGION: { + on_region_selected(); + break; + } + case RegionSelector::SelectionType::WINDOW: { + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; - 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(TR("No window selected"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR); + const Window selected_window = region_selector.get_window_selection(); + if(selected_window && selected_window != DefaultRootWindow(display)) { + on_region_selected(); + } else { + show_notification(TR("No window selected"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR); + } + break; + } } - on_window_selected = nullptr; + on_region_selected = nullptr; } if(!visible || !window) @@ -875,7 +879,7 @@ namespace gsr { if(start_region_capture) { start_region_capture = false; hide(); - if(!region_selector.start(get_color_theme().tint_color)) { + if(!region_selector.start(RegionSelector::SelectionType::REGION, get_color_theme().tint_color)) { show_notification(TR("Failed to start region capture"), notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR); on_region_selected = nullptr; } @@ -884,13 +888,13 @@ namespace gsr { if(start_window_capture) { start_window_capture = false; hide(); - if(!window_selector.start(get_color_theme().tint_color)) { + if(!region_selector.start(RegionSelector::SelectionType::WINDOW, get_color_theme().tint_color)) { show_notification(TR("Failed to start window capture"), notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR); - on_window_selected = nullptr; + on_region_selected = nullptr; } } - if(region_selector.is_started() || window_selector.is_started()) { + if(region_selector.is_started()) { usleep(5 * 1000); // 5 ms return true; } @@ -1025,7 +1029,7 @@ namespace gsr { if(visible) return; - if(region_selector.is_started() || window_selector.is_started()) + if(region_selector.is_started()) return; drawn_first_frame = false; @@ -2592,7 +2596,7 @@ namespace gsr { } void Overlay::add_region_command(std::vector &args, char *region_str, int region_str_size) { - Region region = region_selector.get_selection(x11_dpy, wayland_dpy); + Region region = region_selector.get_region_selection(x11_dpy, wayland_dpy); if(region.size.x <= 32 && region.size.y <= 32) { region.size.x = 0; region.size.y = 0; @@ -2676,7 +2680,7 @@ namespace gsr { std::string Overlay::get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) { if(capture_target == "window") { - return std::to_string(window_selector.get_selection()); + return std::to_string(region_selector.get_window_selection()); } else if(capture_target == "focused_monitor") { std::optional cursor_info; if(cursor_tracker) { @@ -2871,7 +2875,7 @@ namespace gsr { } bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) { - if(region_selector.is_started() || window_selector.is_started()) + if(region_selector.is_started()) return false; switch(recording_status) { @@ -2914,15 +2918,6 @@ namespace gsr { return true; } - const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options); - if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) { - char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), TR("Failed to start replay, capture target \"%s\" is invalid.\nPlease change capture target in settings"), recording_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY, nullptr, NotificationLevel::ERROR); - return false; - } - if(config.replay_config.record_options.record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [disable_notification, this]() { @@ -2933,12 +2928,21 @@ namespace gsr { if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) { start_window_capture = true; - on_window_selected = [disable_notification, this]() { + on_region_selected = [disable_notification, this]() { on_press_start_replay(disable_notification, true); }; return false; } + const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); + recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options); + if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), TR("Failed to start replay, capture target \"%s\" is invalid.\nPlease change capture target in settings"), recording_capture_target.c_str()); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY, nullptr, NotificationLevel::ERROR); + 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); @@ -3040,7 +3044,7 @@ namespace gsr { } void Overlay::on_press_start_record(bool finished_selection, RecordForceType force_type) { - if(region_selector.is_started() || window_selector.is_started()) + if(region_selector.is_started()) return; switch(recording_status) { @@ -3154,15 +3158,6 @@ namespace gsr { break; } - const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - recording_capture_target = get_capture_target(record_area_option, capture_options); - if(!validate_capture_target(record_area_option, capture_options)) { - char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), TR("Failed to start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings"), recording_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD, nullptr, NotificationLevel::ERROR); - return; - } - if(record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [this, force_type]() { @@ -3173,12 +3168,21 @@ namespace gsr { if(record_area_option == "window" && !finished_selection) { start_window_capture = true; - on_window_selected = [this, force_type]() { + on_region_selected = [this, force_type]() { on_press_start_record(true, force_type); }; return; } + const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); + recording_capture_target = get_capture_target(record_area_option, capture_options); + if(!validate_capture_target(record_area_option, capture_options)) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), TR("Failed to start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings"), recording_capture_target.c_str()); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD, nullptr, NotificationLevel::ERROR); + return; + } + record_filepath.clear(); // TODO: Validate input, fallback to valid values @@ -3312,7 +3316,7 @@ namespace gsr { } void Overlay::on_press_start_stream(bool finished_selection) { - if(region_selector.is_started() || window_selector.is_started()) + if(region_selector.is_started()) return; switch(recording_status) { @@ -3352,15 +3356,6 @@ namespace gsr { return; } - const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options); - if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) { - char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), TR("Failed to start streaming, capture target \"%s\" is invalid.\nPlease change capture target in settings"), recording_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM, nullptr, NotificationLevel::ERROR); - return; - } - if(config.streaming_config.record_options.record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [this]() { @@ -3371,12 +3366,21 @@ namespace gsr { if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) { start_window_capture = true; - on_window_selected = [this]() { + on_region_selected = [this]() { on_press_start_stream(true); }; return; } + const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); + recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options); + if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), TR("Failed to start streaming, capture target \"%s\" is invalid.\nPlease change capture target in settings"), recording_capture_target.c_str()); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM, nullptr, NotificationLevel::ERROR); + 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); @@ -3469,7 +3473,7 @@ namespace gsr { } 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()) return; if(gpu_screen_recorder_screenshot_process > 0) { @@ -3490,15 +3494,6 @@ namespace gsr { break; } - const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - screenshot_capture_target = get_capture_target(record_area_option, capture_options); - if(!validate_capture_target(record_area_option, capture_options)) { - char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), TR("Failed to take a screenshot, capture target \"%s\" is invalid.\nPlease change capture target in settings"), screenshot_capture_target.c_str()); - show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT, nullptr, NotificationLevel::ERROR); - return; - } - if(record_area_option == "region" && !finished_selection) { start_region_capture = true; on_region_selected = [this, force_type]() { @@ -3509,12 +3504,21 @@ namespace gsr { if(record_area_option == "window" && !finished_selection) { start_window_capture = true; - on_window_selected = [this, force_type]() { + on_region_selected = [this, force_type]() { on_press_take_screenshot(true, force_type); }; return; } + const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); + screenshot_capture_target = get_capture_target(record_area_option, capture_options); + if(!validate_capture_target(record_area_option, capture_options)) { + char err_msg[256]; + snprintf(err_msg, sizeof(err_msg), TR("Failed to take a screenshot, capture target \"%s\" is invalid.\nPlease change capture target in settings"), screenshot_capture_target.c_str()); + show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT, nullptr, NotificationLevel::ERROR); + return; + } + // TODO: Validate input, fallback to valid values std::string output_file; if(config.screenshot_config.save_screenshot_to_disk) diff --git a/src/RegionSelector.cpp b/src/RegionSelector.cpp index bf7358a..0fd63f9 100644 --- a/src/RegionSelector.cpp +++ b/src/RegionSelector.cpp @@ -2,11 +2,13 @@ #include #include +#include #include #include #include #include +#include namespace gsr { static const int cursor_window_size = 32; @@ -222,6 +224,42 @@ namespace gsr { }; } + static std::vector query_windows(Display *dpy) { + std::vector windows; + + Window root_return = None; + Window parent_return = None; + Window *children_return = nullptr; + unsigned int num_children_return = 0; + if(!XQueryTree(dpy, DefaultRootWindow(dpy), &root_return, &parent_return, &children_return, &num_children_return) || !children_return) + return windows; + + for(int i = (int)num_children_return - 1; i >= 0; --i) { + const Window child_window = children_return[i]; + XWindowAttributes win_attr; + if(XGetWindowAttributes(dpy, child_window, &win_attr) && !win_attr.override_redirect && win_attr.c_class == InputOutput && win_attr.map_state == IsViewable) { + windows.push_back( + RegionWindow{ + child_window, + mgl::vec2i(win_attr.x, win_attr.y), + mgl::vec2i(win_attr.width, win_attr.height) + } + ); + } + } + + XFree(children_return); + return windows; + } + + static std::optional get_window_by_position(const std::vector &windows, mgl::vec2i pos) { + for(const RegionWindow &window : windows) { + if(mgl::IntRect(window.pos, window.size).contains(pos)) + return window; + } + return std::nullopt; + } + RegionSelector::RegionSelector() { } @@ -230,7 +268,7 @@ namespace gsr { stop(); } - bool RegionSelector::start(mgl::Color border_color) { + bool RegionSelector::start(SelectionType selection_type, mgl::Color border_color) { if(dpy) return false; @@ -328,11 +366,25 @@ namespace gsr { hide_window_from_taskbar(dpy, cursor_window); } - draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos); + windows = query_windows(dpy); + + if(selection_type == SelectionType::WINDOW) { + focused_window = get_window_by_position(windows, cursor_pos); + + if(focused_window) { + if(is_wayland) + draw_rectangle(dpy, region_window, region_gc, focused_window->pos.x, focused_window->pos.y, focused_window->size.x, focused_window->size.y); + else + set_region_rectangle(dpy, region_window, focused_window->pos.x, focused_window->pos.y, focused_window->size.x, focused_window->size.y, region_border_size); + } + } else if(selection_type == SelectionType::REGION) { + draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos); + } XFlush(dpy); selected = false; canceled = false; + this->selection_type = selection_type; return true; } @@ -375,6 +427,8 @@ namespace gsr { XCloseDisplay(dpy); dpy = nullptr; selecting_region = false; + monitors.clear(); + windows.clear(); } bool RegionSelector::is_started() const { @@ -441,20 +495,35 @@ namespace gsr { return result; } - Region RegionSelector::get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const { + Region RegionSelector::get_region_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const { + assert(selection_type == SelectionType::REGION); Region returned_region = region; if(is_wayland && x11_dpy && wayland_dpy) returned_region = x11_region_to_wayland_region(x11_dpy, wayland_dpy, returned_region); return returned_region; } + Window RegionSelector::get_window_selection() const { + assert(selection_type == SelectionType::WINDOW); + if(focused_window) + return focused_window->window; + else + return None; + } + + RegionSelector::SelectionType RegionSelector::get_selection_type() const { + return selection_type; + } + void RegionSelector::on_button_press(const void *de) { const XIDeviceEvent *device_event = (XIDeviceEvent*)de; if(device_event->detail != Button1) return; - region.pos = { (int)device_event->root_x, (int)device_event->root_y }; - selecting_region = true; + if(selection_type == SelectionType::REGION) { + region.pos = { (int)device_event->root_x, (int)device_event->root_y }; + selecting_region = true; + } } void RegionSelector::on_button_release(const void *de) { @@ -462,8 +531,23 @@ namespace gsr { if(device_event->detail != Button1) return; - if(!selecting_region) - return; + if(selection_type == SelectionType::WINDOW) { + focused_window = get_window_by_position(windows, mgl::vec2i(device_event->root_x, device_event->root_y)); + if(focused_window) { + const Window real_window = window_get_target_window_child(dpy, focused_window->window); + XWindowAttributes win_attr; + if(XGetWindowAttributes(dpy, real_window, &win_attr)) { + focused_window = RegionWindow{ + real_window, + mgl::vec2i(win_attr.x, win_attr.y), + mgl::vec2i(win_attr.width, win_attr.height) + }; + } + } + } else if(selection_type == SelectionType::REGION) { + if(!selecting_region) + return; + } if(is_wayland) { XClearWindow(dpy, region_window); @@ -473,7 +557,11 @@ namespace gsr { } selecting_region = false; - cursor_pos = region.pos + region.size; + if(selection_type == SelectionType::WINDOW) { + cursor_pos = { (int)device_event->root_x, (int)device_event->root_y }; + } else if(selection_type == SelectionType::REGION) { + cursor_pos = region.pos + region.size; + } if(region.size.x < 0) { region.pos.x += region.size.x; @@ -497,6 +585,7 @@ namespace gsr { void RegionSelector::on_mouse_motion(const void *de) { const XIDeviceEvent *device_event = (XIDeviceEvent*)de; XClearWindow(dpy, region_window); + if(selecting_region) { region.size.x = device_event->root_x - region.pos.x; region.size.y = device_event->root_y - region.pos.y; @@ -506,10 +595,21 @@ namespace gsr { draw_rectangle(dpy, region_window, region_gc, region.pos.x, region.pos.y, region.size.x, region.size.y); else set_region_rectangle(dpy, region_window, region.pos.x, region.pos.y, region.size.x, region.size.y, region_border_size); - } else { + } else if(selection_type == SelectionType::WINDOW) { + cursor_pos = { (int)device_event->root_x, (int)device_event->root_y }; + + focused_window = get_window_by_position(windows, cursor_pos); + if(focused_window) { + if(is_wayland) + draw_rectangle(dpy, region_window, region_gc, focused_window->pos.x, focused_window->pos.y, focused_window->size.x, focused_window->size.y); + else + set_region_rectangle(dpy, region_window, focused_window->pos.x, focused_window->pos.y, focused_window->size.x, focused_window->size.y, region_border_size); + } + } else if(selection_type == SelectionType::REGION) { cursor_pos = { (int)device_event->root_x, (int)device_event->root_y }; draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos); } + update_cursor_window(dpy, region_window, cursor_window, is_wayland, cursor_pos.x, cursor_pos.y, cursor_window_size, cursor_thickness, cursor_gc); XFlush(dpy); } diff --git a/src/WindowSelector.cpp b/src/WindowSelector.cpp deleted file mode 100644 index a9f5b48..0000000 --- a/src/WindowSelector.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "../include/WindowSelector.hpp" -#include "../include/WindowUtils.hpp" - -#include -#include - -#include -#include -#include -#include - -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)); - - unsigned char data = 2; // Prefer being composed to allow transparency. Do this to prevent the compositor from getting turned on/off when taking a screenshot - XChangeProperty(dpy, border_window, XInternAtom(dpy, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); - - 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); - XSync(dpy, False); - - 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 e4e521c..12bccb7 100644 --- a/src/WindowUtils.cpp +++ b/src/WindowUtils.cpp @@ -229,7 +229,7 @@ namespace gsr { wl_display_roundtrip(dpy); } - static unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) { + unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) { Atom ret_property_type = None; int ret_format = 0; unsigned long num_items = 0; @@ -284,14 +284,14 @@ namespace gsr { return None; Window found_window = None; - for(int i = num_children - 1; i >= 0; --i) { + for(int i = (int)num_children - 1; i >= 0; --i) { if(children[i] && window_is_user_program(display, children[i])) { found_window = children[i]; goto finished; } } - for(int i = num_children - 1; i >= 0; --i) { + for(int i = (int)num_children - 1; i >= 0; --i) { if(children[i]) { Window win = window_get_target_window_child(display, children[i]); if(win) {