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).
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).

View File

@@ -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<void()> on_region_selected;
WindowSelector window_selector;
bool start_window_capture = false;
std::function<void()> on_window_selected;
std::string recording_capture_target;
std::string screenshot_capture_target;

View File

@@ -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<Monitor> monitors;
std::vector<RegionWindow> windows; // First window is the window that is on top
std::optional<RegionWindow> focused_window;
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);
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);

View File

@@ -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',

View File

@@ -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<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) {
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<CursorInfo> 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)

View File

@@ -2,11 +2,13 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <X11/Xatom.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/shape.h>
#include <mglpp/system/Rect.hpp>
namespace gsr {
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() {
}
@@ -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);
}

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);
}
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) {