Implemented screen_get_usable_rect for X11 Display Server

Helps with fixing issues with scrolling popups not respecting screen
decorations on the display server side.

Reproduction steps for a simple use case:
- Start the editor project list
- Open the language selection popup

Support for multi-screen:
Handling decorations is supported in different ways depending on the
information the window manager provides:
- _GTK_WORKAREAS is used when available from the WM to get accurate rect
for the different screens directly (available on Gnome).
- Alternatively, strut information is used to calculate available space
for a given desktop manually (XFCE, KDE).
- As last resort _NET_WORKAREA is used. It provides one full rect for all
screens, which doesn't handle decorations on the secondary screen in all
cases.
This commit is contained in:
PouleyKetchoupp 2020-08-27 17:22:24 +02:00
parent 9fb9b99cab
commit 85a934954d

View file

@ -735,21 +735,267 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {
p_screen = window_get_current_screen();
}
// Using Xinerama Extension
int event_base, error_base;
const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
if (!ext_okay) {
return Rect2i(0, 0, 0, 0);
int screen_count = get_screen_count();
// Check if screen is valid.
ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i(0, 0, 0, 0));
bool is_multiscreen = screen_count > 1;
// Use full monitor size as fallback.
Rect2i rect = _screen_get_rect(p_screen);
// There's generally only one screen reported by xlib even in multi-screen setup,
// in this case it's just one virtual screen composed of all physical monitors.
int x11_screen_count = ScreenCount(x11_display);
Window x11_window = RootWindow(x11_display, p_screen < x11_screen_count ? p_screen : 0);
Atom type;
int format = 0;
unsigned long remaining = 0;
// Find active desktop for the root window.
unsigned int desktop_index = 0;
Atom desktop_prop = XInternAtom(x11_display, "_NET_CURRENT_DESKTOP", True);
if (desktop_prop != None) {
unsigned long desktop_len = 0;
unsigned char *desktop_data = nullptr;
if (XGetWindowProperty(x11_display, x11_window, desktop_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &desktop_len, &remaining, &desktop_data) == Success) {
if ((format == 32) && (desktop_len > 0) && desktop_data) {
desktop_index = (unsigned int)desktop_data[0];
}
if (desktop_data) {
XFree(desktop_data);
}
}
}
int count;
XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
bool use_simple_method = true;
// Check if screen is valid
ERR_FAIL_INDEX_V(p_screen, count, Rect2i(0, 0, 0, 0));
// First check for GTK work area, which is more accurate for multi-screen setup.
if (is_multiscreen) {
// Use already calculated work area when available.
Atom gtk_workareas_prop = XInternAtom(x11_display, "_GTK_WORKAREAS", False);
if (gtk_workareas_prop != None) {
char gtk_workarea_prop_name[32];
snprintf(gtk_workarea_prop_name, 32, "_GTK_WORKAREAS_D%d", desktop_index);
Atom gtk_workarea_prop = XInternAtom(x11_display, gtk_workarea_prop_name, True);
if (gtk_workarea_prop != None) {
unsigned long workarea_len = 0;
unsigned char *workarea_data = nullptr;
if (XGetWindowProperty(x11_display, x11_window, gtk_workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {
if ((format == 32) && (workarea_len % 4 == 0) && workarea_data) {
long *rect_data = (long *)workarea_data;
for (uint32_t data_offset = 0; data_offset < workarea_len; data_offset += 4) {
Rect2i workarea_rect;
workarea_rect.position.x = rect_data[data_offset];
workarea_rect.position.y = rect_data[data_offset + 1];
workarea_rect.size.x = rect_data[data_offset + 2];
workarea_rect.size.y = rect_data[data_offset + 3];
// Intersect with actual monitor size to find the correct area,
// because areas are not in the same order as screens from Xinerama.
if (rect.grow(-1).intersects(workarea_rect)) {
rect = rect.intersection(workarea_rect);
XFree(workarea_data);
return rect;
}
}
}
}
if (workarea_data) {
XFree(workarea_data);
}
}
}
// Fallback to calculating work area by hand from struts.
Atom client_list_prop = XInternAtom(x11_display, "_NET_CLIENT_LIST", True);
if (client_list_prop != None) {
unsigned long clients_len = 0;
unsigned char *clients_data = nullptr;
if (XGetWindowProperty(x11_display, x11_window, client_list_prop, 0, LONG_MAX, False, XA_WINDOW, &type, &format, &clients_len, &remaining, &clients_data) == Success) {
if ((format == 32) && (clients_len > 0) && clients_data) {
Window *windows_data = (Window *)clients_data;
Rect2i desktop_rect;
bool desktop_valid = false;
// Get full desktop size.
{
Atom desktop_geometry_prop = XInternAtom(x11_display, "_NET_DESKTOP_GEOMETRY", True);
if (desktop_geometry_prop != None) {
unsigned long geom_len = 0;
unsigned char *geom_data = nullptr;
if (XGetWindowProperty(x11_display, x11_window, desktop_geometry_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &geom_len, &remaining, &geom_data) == Success) {
if ((format == 32) && (geom_len >= 2) && geom_data) {
desktop_valid = true;
long *size_data = (long *)geom_data;
desktop_rect.size.x = size_data[0];
desktop_rect.size.y = size_data[1];
}
}
if (geom_data) {
XFree(geom_data);
}
}
}
// Get full desktop position.
if (desktop_valid) {
Atom desktop_viewport_prop = XInternAtom(x11_display, "_NET_DESKTOP_VIEWPORT", True);
if (desktop_viewport_prop != None) {
unsigned long viewport_len = 0;
unsigned char *viewport_data = nullptr;
if (XGetWindowProperty(x11_display, x11_window, desktop_viewport_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &viewport_len, &remaining, &viewport_data) == Success) {
if ((format == 32) && (viewport_len >= 2) && viewport_data) {
desktop_valid = true;
long *pos_data = (long *)viewport_data;
desktop_rect.position.x = pos_data[0];
desktop_rect.position.y = pos_data[1];
}
}
if (viewport_data) {
XFree(viewport_data);
}
}
}
if (desktop_valid) {
use_simple_method = false;
for (unsigned long win_index = 0; win_index < clients_len; ++win_index) {
// Remove strut size from desktop size to get a more accurate result.
bool strut_found = false;
unsigned long strut_len = 0;
unsigned char *strut_data = nullptr;
Atom strut_partial_prop = XInternAtom(x11_display, "_NET_WM_STRUT_PARTIAL", True);
if (strut_partial_prop != None) {
if (XGetWindowProperty(x11_display, windows_data[win_index], strut_partial_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {
strut_found = true;
}
}
// Fallback to older strut property.
if (!strut_found) {
Atom strut_prop = XInternAtom(x11_display, "_NET_WM_STRUT", True);
if (strut_prop != None) {
if (XGetWindowProperty(x11_display, windows_data[win_index], strut_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &strut_len, &remaining, &strut_data) == Success) {
strut_found = true;
}
}
}
if (strut_found && (format == 32) && (strut_len >= 4) && strut_data) {
long *struts = (long *)strut_data;
long left = struts[0];
long right = struts[1];
long top = struts[2];
long bottom = struts[3];
long left_start_y, left_end_y, right_start_y, right_end_y;
long top_start_x, top_end_x, bottom_start_x, bottom_end_x;
if (strut_len >= 12) {
left_start_y = struts[4];
left_end_y = struts[5];
right_start_y = struts[6];
right_end_y = struts[7];
top_start_x = struts[8];
top_end_x = struts[9];
bottom_start_x = struts[10];
bottom_end_x = struts[11];
} else {
left_start_y = 0;
left_end_y = desktop_rect.size.y;
right_start_y = 0;
right_end_y = desktop_rect.size.y;
top_start_x = 0;
top_end_x = desktop_rect.size.x;
bottom_start_x = 0;
bottom_end_x = desktop_rect.size.x;
}
const Point2i &pos = desktop_rect.position;
const Size2i &size = desktop_rect.size;
Rect2i left_rect(pos.x, pos.y + left_start_y, left, left_end_y - left_start_y);
if (left_rect.size.x > 0) {
Rect2i intersection = rect.intersection(left_rect);
if (!intersection.has_no_area() && intersection.size.x < rect.size.x) {
rect.position.x = left_rect.size.x;
rect.size.x = rect.size.x - intersection.size.x;
}
}
Rect2i right_rect(pos.x + size.x - right, pos.y + right_start_y, right, right_end_y - right_start_y);
if (right_rect.size.x > 0) {
Rect2i intersection = rect.intersection(right_rect);
if (!intersection.has_no_area() && right_rect.size.x < rect.size.x) {
rect.size.x = intersection.position.x - rect.position.x;
}
}
Rect2i top_rect(pos.x + top_start_x, pos.y, top_end_x - top_start_x, top);
if (top_rect.size.y > 0) {
Rect2i intersection = rect.intersection(top_rect);
if (!intersection.has_no_area() && intersection.size.y < rect.size.y) {
rect.position.y = top_rect.size.y;
rect.size.y = rect.size.y - intersection.size.y;
}
}
Rect2i bottom_rect(pos.x + bottom_start_x, pos.y + size.y - bottom, bottom_end_x - bottom_start_x, bottom);
if (bottom_rect.size.y > 0) {
Rect2i intersection = rect.intersection(bottom_rect);
if (!intersection.has_no_area() && right_rect.size.y < rect.size.y) {
rect.size.y = intersection.position.y - rect.position.y;
}
}
}
if (strut_data) {
XFree(strut_data);
}
}
}
}
}
if (clients_data) {
XFree(clients_data);
}
}
}
// Single screen or fallback for multi screen.
if (use_simple_method) {
// Get desktop available size from the global work area.
Atom workarea_prop = XInternAtom(x11_display, "_NET_WORKAREA", True);
if (workarea_prop != None) {
unsigned long workarea_len = 0;
unsigned char *workarea_data = nullptr;
if (XGetWindowProperty(x11_display, x11_window, workarea_prop, 0, LONG_MAX, False, XA_CARDINAL, &type, &format, &workarea_len, &remaining, &workarea_data) == Success) {
if ((format == 32) && (workarea_len >= ((desktop_index + 1) * 4)) && workarea_data) {
long *rect_data = (long *)workarea_data;
int data_offset = desktop_index * 4;
Rect2i workarea_rect;
workarea_rect.position.x = rect_data[data_offset];
workarea_rect.position.y = rect_data[data_offset + 1];
workarea_rect.size.x = rect_data[data_offset + 2];
workarea_rect.size.y = rect_data[data_offset + 3];
// Intersect with actual monitor size to get a proper approximation in multi-screen setup.
if (!is_multiscreen) {
rect = workarea_rect;
} else if (rect.intersects(workarea_rect)) {
rect = rect.intersection(workarea_rect);
}
}
}
if (workarea_data) {
XFree(workarea_data);
}
}
}
Rect2i rect = Rect2i(xsi[p_screen].x_org, xsi[p_screen].y_org, xsi[p_screen].width, xsi[p_screen].height);
XFree(xsi);
return rect;
}