Fix window capture selection not working if the cursor is hidden and grabbed when starting capture

This commit is contained in:
dec05eba
2026-02-15 18:04:56 +01:00
parent 728ccc40a6
commit 1ce12067aa
10 changed files with 206 additions and 356 deletions

2
TODO
View File

@@ -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). 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. 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). Make inactive buttons gray (in dropdown boxes and in the front page with save, etc when replay is not running).

View File

@@ -9,7 +9,6 @@
#include "GlobalHotkeys/GlobalHotkeysJoystick.hpp" #include "GlobalHotkeys/GlobalHotkeysJoystick.hpp"
#include "AudioPlayer.hpp" #include "AudioPlayer.hpp"
#include "RegionSelector.hpp" #include "RegionSelector.hpp"
#include "WindowSelector.hpp"
#include "ClipboardFile.hpp" #include "ClipboardFile.hpp"
#include "LedIndicator.hpp" #include "LedIndicator.hpp"
#include "CursorTracker/CursorTracker.hpp" #include "CursorTracker/CursorTracker.hpp"
@@ -275,9 +274,7 @@ namespace gsr {
bool start_region_capture = false; bool start_region_capture = false;
std::function<void()> on_region_selected; std::function<void()> on_region_selected;
WindowSelector window_selector;
bool start_window_capture = false; bool start_window_capture = false;
std::function<void()> on_window_selected;
std::string recording_capture_target; std::string recording_capture_target;
std::string screenshot_capture_target; std::string screenshot_capture_target;

View File

@@ -15,14 +15,26 @@ namespace gsr {
mgl::vec2i size; mgl::vec2i size;
}; };
struct RegionWindow {
Window window = None;
mgl::vec2i pos;
mgl::vec2i size;
};
class RegionSelector { class RegionSelector {
public: public:
enum class SelectionType {
NONE,
REGION,
WINDOW
};
RegionSelector(); RegionSelector();
RegionSelector(const RegionSelector&) = delete; RegionSelector(const RegionSelector&) = delete;
RegionSelector& operator=(const RegionSelector&) = delete; RegionSelector& operator=(const RegionSelector&) = delete;
~RegionSelector(); ~RegionSelector();
bool start(mgl::Color border_color); bool start(SelectionType selection_type, mgl::Color border_color);
void stop(); void stop();
bool is_started() const; bool is_started() const;
@@ -30,7 +42,11 @@ namespace gsr {
bool poll_events(); bool poll_events();
bool take_selection(); bool take_selection();
bool take_canceled(); 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: private:
void on_button_press(const void *de); void on_button_press(const void *de);
void on_button_release(const void *de); void on_button_release(const void *de);
@@ -50,6 +66,10 @@ namespace gsr {
bool canceled = false; bool canceled = false;
bool is_wayland = false; bool is_wayland = false;
std::vector<Monitor> monitors; std::vector<Monitor> monitors;
std::vector<RegionWindow> windows; // First window is the window that is on top
std::optional<RegionWindow> focused_window;
mgl::vec2i cursor_pos; mgl::vec2i cursor_pos;
SelectionType selection_type = SelectionType::NONE;
}; };
} }

View File

@@ -1,33 +0,0 @@
#pragma once
#include <X11/Xlib.h>
#include <mglpp/graphics/Color.hpp>
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;
};
}

View File

@@ -27,6 +27,7 @@ namespace gsr {
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window); 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); void set_window_size_not_resizable(Display *dpy, Window window, int width, int height);
Window window_get_target_window_child(Display *display, Window window); 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 get_cursor_position(Display *dpy, Window *window);
mgl::vec2i create_window_get_center_position(Display *display); mgl::vec2i create_window_get_center_position(Display *display);
std::string get_window_manager_name(Display *display); std::string get_window_manager_name(Display *display);

View File

@@ -45,7 +45,6 @@ src = [
'src/KwinWorkaround.cpp', 'src/KwinWorkaround.cpp',
'src/WindowUtils.cpp', 'src/WindowUtils.cpp',
'src/RegionSelector.cpp', 'src/RegionSelector.cpp',
'src/WindowSelector.cpp',
'src/Config.cpp', 'src/Config.cpp',
'src/GsrInfo.cpp', 'src/GsrInfo.cpp',
'src/Process.cpp', 'src/Process.cpp',

View File

@@ -804,24 +804,28 @@ namespace gsr {
if(region_selector.take_canceled()) { if(region_selector.take_canceled()) {
on_region_selected = nullptr; on_region_selected = nullptr;
} else if(region_selector.take_selection() && on_region_selected) { } else if(region_selector.take_selection() && on_region_selected) {
on_region_selected(); switch(region_selector.get_selection_type()) {
on_region_selected = nullptr; 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(); const Window selected_window = region_selector.get_window_selection();
if(window_selector.take_canceled()) { if(selected_window && selected_window != DefaultRootWindow(display)) {
on_window_selected = nullptr; on_region_selected();
} else if(window_selector.take_selection() && on_window_selected) { } else {
mgl_context *context = mgl_get_context(); show_notification(TR("No window selected"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
Display *display = (Display*)context->connection; }
break;
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);
} }
on_window_selected = nullptr; on_region_selected = nullptr;
} }
if(!visible || !window) if(!visible || !window)
@@ -875,7 +879,7 @@ namespace gsr {
if(start_region_capture) { if(start_region_capture) {
start_region_capture = false; start_region_capture = false;
hide(); 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); 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; on_region_selected = nullptr;
} }
@@ -884,13 +888,13 @@ namespace gsr {
if(start_window_capture) { if(start_window_capture) {
start_window_capture = false; start_window_capture = false;
hide(); 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); 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 usleep(5 * 1000); // 5 ms
return true; return true;
} }
@@ -1025,7 +1029,7 @@ namespace gsr {
if(visible) if(visible)
return; return;
if(region_selector.is_started() || window_selector.is_started()) if(region_selector.is_started())
return; return;
drawn_first_frame = false; drawn_first_frame = false;
@@ -2592,7 +2596,7 @@ namespace gsr {
} }
void Overlay::add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size) { void Overlay::add_region_command(std::vector<const char*> &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) { if(region.size.x <= 32 && region.size.y <= 32) {
region.size.x = 0; region.size.x = 0;
region.size.y = 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) { std::string Overlay::get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
if(capture_target == "window") { 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") { } else if(capture_target == "focused_monitor") {
std::optional<CursorInfo> cursor_info; std::optional<CursorInfo> cursor_info;
if(cursor_tracker) { if(cursor_tracker) {
@@ -2871,7 +2875,7 @@ namespace gsr {
} }
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) { 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; return false;
switch(recording_status) { switch(recording_status) {
@@ -2914,15 +2918,6 @@ namespace gsr {
return true; 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) { if(config.replay_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [disable_notification, this]() { on_region_selected = [disable_notification, this]() {
@@ -2933,12 +2928,21 @@ namespace gsr {
if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) { if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) {
start_window_capture = true; start_window_capture = true;
on_window_selected = [disable_notification, this]() { on_region_selected = [disable_notification, this]() {
on_press_start_replay(disable_notification, true); on_press_start_replay(disable_notification, true);
}; };
return false; 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 // TODO: Validate input, fallback to valid values
const std::string fps = std::to_string(config.replay_config.record_options.fps); 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); 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) { 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; return;
switch(recording_status) { switch(recording_status) {
@@ -3154,15 +3158,6 @@ namespace gsr {
break; 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) { if(record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [this, force_type]() { on_region_selected = [this, force_type]() {
@@ -3173,12 +3168,21 @@ namespace gsr {
if(record_area_option == "window" && !finished_selection) { if(record_area_option == "window" && !finished_selection) {
start_window_capture = true; start_window_capture = true;
on_window_selected = [this, force_type]() { on_region_selected = [this, force_type]() {
on_press_start_record(true, force_type); on_press_start_record(true, force_type);
}; };
return; 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(); record_filepath.clear();
// TODO: Validate input, fallback to valid values // TODO: Validate input, fallback to valid values
@@ -3312,7 +3316,7 @@ namespace gsr {
} }
void Overlay::on_press_start_stream(bool finished_selection) { void Overlay::on_press_start_stream(bool finished_selection) {
if(region_selector.is_started() || window_selector.is_started()) if(region_selector.is_started())
return; return;
switch(recording_status) { switch(recording_status) {
@@ -3352,15 +3356,6 @@ namespace gsr {
return; 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) { if(config.streaming_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [this]() { on_region_selected = [this]() {
@@ -3371,12 +3366,21 @@ namespace gsr {
if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) { if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) {
start_window_capture = true; start_window_capture = true;
on_window_selected = [this]() { on_region_selected = [this]() {
on_press_start_stream(true); on_press_start_stream(true);
}; };
return; 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 // TODO: Validate input, fallback to valid values
const std::string fps = std::to_string(config.streaming_config.record_options.fps); 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); 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) { 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; return;
if(gpu_screen_recorder_screenshot_process > 0) { if(gpu_screen_recorder_screenshot_process > 0) {
@@ -3490,15 +3494,6 @@ namespace gsr {
break; 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) { if(record_area_option == "region" && !finished_selection) {
start_region_capture = true; start_region_capture = true;
on_region_selected = [this, force_type]() { on_region_selected = [this, force_type]() {
@@ -3509,12 +3504,21 @@ namespace gsr {
if(record_area_option == "window" && !finished_selection) { if(record_area_option == "window" && !finished_selection) {
start_window_capture = true; start_window_capture = true;
on_window_selected = [this, force_type]() { on_region_selected = [this, force_type]() {
on_press_take_screenshot(true, force_type); on_press_take_screenshot(true, force_type);
}; };
return; 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 // TODO: Validate input, fallback to valid values
std::string output_file; std::string output_file;
if(config.screenshot_config.save_screenshot_to_disk) if(config.screenshot_config.save_screenshot_to_disk)

View File

@@ -2,11 +2,13 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/extensions/XInput2.h> #include <X11/extensions/XInput2.h>
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
#include <X11/extensions/shape.h> #include <X11/extensions/shape.h>
#include <mglpp/system/Rect.hpp>
namespace gsr { namespace gsr {
static const int cursor_window_size = 32; static const int cursor_window_size = 32;
@@ -222,6 +224,42 @@ namespace gsr {
}; };
} }
static std::vector<RegionWindow> query_windows(Display *dpy) {
std::vector<RegionWindow> 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<RegionWindow> get_window_by_position(const std::vector<RegionWindow> &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() { RegionSelector::RegionSelector() {
} }
@@ -230,7 +268,7 @@ namespace gsr {
stop(); stop();
} }
bool RegionSelector::start(mgl::Color border_color) { bool RegionSelector::start(SelectionType selection_type, mgl::Color border_color) {
if(dpy) if(dpy)
return false; return false;
@@ -328,11 +366,25 @@ namespace gsr {
hide_window_from_taskbar(dpy, cursor_window); 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); XFlush(dpy);
selected = false; selected = false;
canceled = false; canceled = false;
this->selection_type = selection_type;
return true; return true;
} }
@@ -375,6 +427,8 @@ namespace gsr {
XCloseDisplay(dpy); XCloseDisplay(dpy);
dpy = nullptr; dpy = nullptr;
selecting_region = false; selecting_region = false;
monitors.clear();
windows.clear();
} }
bool RegionSelector::is_started() const { bool RegionSelector::is_started() const {
@@ -441,20 +495,35 @@ namespace gsr {
return result; 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; Region returned_region = region;
if(is_wayland && x11_dpy && wayland_dpy) if(is_wayland && x11_dpy && wayland_dpy)
returned_region = x11_region_to_wayland_region(x11_dpy, wayland_dpy, returned_region); returned_region = x11_region_to_wayland_region(x11_dpy, wayland_dpy, returned_region);
return 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) { void RegionSelector::on_button_press(const void *de) {
const XIDeviceEvent *device_event = (XIDeviceEvent*)de; const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
if(device_event->detail != Button1) if(device_event->detail != Button1)
return; return;
region.pos = { (int)device_event->root_x, (int)device_event->root_y }; if(selection_type == SelectionType::REGION) {
selecting_region = true; region.pos = { (int)device_event->root_x, (int)device_event->root_y };
selecting_region = true;
}
} }
void RegionSelector::on_button_release(const void *de) { void RegionSelector::on_button_release(const void *de) {
@@ -462,8 +531,23 @@ namespace gsr {
if(device_event->detail != Button1) if(device_event->detail != Button1)
return; return;
if(!selecting_region) if(selection_type == SelectionType::WINDOW) {
return; 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) { if(is_wayland) {
XClearWindow(dpy, region_window); XClearWindow(dpy, region_window);
@@ -473,7 +557,11 @@ namespace gsr {
} }
selecting_region = false; 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) { if(region.size.x < 0) {
region.pos.x += region.size.x; region.pos.x += region.size.x;
@@ -497,6 +585,7 @@ namespace gsr {
void RegionSelector::on_mouse_motion(const void *de) { void RegionSelector::on_mouse_motion(const void *de) {
const XIDeviceEvent *device_event = (XIDeviceEvent*)de; const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
XClearWindow(dpy, region_window); XClearWindow(dpy, region_window);
if(selecting_region) { if(selecting_region) {
region.size.x = device_event->root_x - region.pos.x; region.size.x = device_event->root_x - region.pos.x;
region.size.y = device_event->root_y - region.pos.y; 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); draw_rectangle(dpy, region_window, region_gc, region.pos.x, region.pos.y, region.size.x, region.size.y);
else else
set_region_rectangle(dpy, region_window, region.pos.x, region.pos.y, region.size.x, region.size.y, region_border_size); 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 }; 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); 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); 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); XFlush(dpy);
} }

View File

@@ -1,236 +0,0 @@
#include "../include/WindowSelector.hpp"
#include "../include/WindowUtils.hpp"
#include <stdio.h>
#include <string.h>
#include <X11/Xatom.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));
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;
}
}

View File

@@ -229,7 +229,7 @@ namespace gsr {
wl_display_roundtrip(dpy); 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; Atom ret_property_type = None;
int ret_format = 0; int ret_format = 0;
unsigned long num_items = 0; unsigned long num_items = 0;
@@ -284,14 +284,14 @@ namespace gsr {
return None; return None;
Window found_window = 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])) { if(children[i] && window_is_user_program(display, children[i])) {
found_window = children[i]; found_window = children[i];
goto finished; goto finished;
} }
} }
for(int i = num_children - 1; i >= 0; --i) { for(int i = (int)num_children - 1; i >= 0; --i) {
if(children[i]) { if(children[i]) {
Window win = window_get_target_window_child(display, children[i]); Window win = window_get_target_window_child(display, children[i]);
if(win) { if(win) {