mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-03-31 09:17:04 +09:00
Fix window capture selection not working if the cursor is hidden and grabbed when starting capture
This commit is contained in:
138
src/Overlay.cpp
138
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<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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user