summaryrefslogtreecommitdiff
path: root/platform/linuxbsd/display_server_x11.cpp
diff options
context:
space:
mode:
authorPouleyKetchoupp <pouleyketchoup@gmail.com>2020-08-27 17:22:24 +0200
committerPouleyKetchoupp <pouleyketchoup@gmail.com>2021-10-29 11:56:08 -0700
commit85a934954d785994e2fd95d074a85b5df1f85bb0 (patch)
tree8d9f3fac62feab04c112dd4ac7a33be077811e51 /platform/linuxbsd/display_server_x11.cpp
parent9fb9b99cab51173c569f147664c334690f9d5d72 (diff)
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.
Diffstat (limited to 'platform/linuxbsd/display_server_x11.cpp')
-rw-r--r--platform/linuxbsd/display_server_x11.cpp268
1 files changed, 257 insertions, 11 deletions
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index dd296ec3b4..e1eca498ae 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -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;
+
+ // 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);
+ }
+ }
+ }
- // Check if screen is valid
- ERR_FAIL_INDEX_V(p_screen, count, Rect2i(0, 0, 0, 0));
+ // 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;
}