changeset 213:58e81b42a0d6

dep/animia: win/x11: find toplevels WAY faster completely different approach. oops!
author Paper <mrpapersonic@gmail.com>
date Sun, 07 Jan 2024 11:11:27 -0500
parents 6b08fbd7f206
children 8d35061e7505
files dep/animia/src/win/x11.cc
diffstat 1 files changed, 110 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/dep/animia/src/win/x11.cc	Sun Jan 07 09:54:50 2024 -0500
+++ b/dep/animia/src/win/x11.cc	Sun Jan 07 11:11:27 2024 -0500
@@ -13,10 +13,6 @@
 #include <string>
 #include <set>
 
-/* The code for this is very fugly because X11 uses lots of generic type names
-   (i.e., Window, Display), so I have to use :: when defining vars to distinguish
-   between Animia's types and X11's types */
-
 namespace animia::internal::x11 {
 
 /* specify that these are X types. */
@@ -26,6 +22,9 @@
 
 /* should return UTF8_STRING or STRING */
 static bool GetWindowPropertyAsString(XDisplay* display, XWindow window, XAtom atom, std::string& result, XAtom reqtype = AnyPropertyType) {
+	if (atom == None || reqtype == None)
+		return false;
+
 	int format;
 	unsigned long leftover_bytes, num_of_items;
 	XAtom type;
@@ -70,12 +69,16 @@
 	}
 #endif
 
+	XAtom Atom__NET_WM_PID = ::XInternAtom(display, "_NET_WM_PID", True);
+	if (Atom__NET_WM_PID == None)
+		return false;
+
 	int format;
 	unsigned long leftover_bytes, num_of_items;
-	XAtom atom = ::XInternAtom(display, "_NET_WM_PID", False), type;
+	XAtom type;
 	unsigned char* data;
 
-	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, XA_CARDINAL,
+	int status = ::XGetWindowProperty(display, window, Atom__NET_WM_PID, 0L, (~0L), False, XA_CARDINAL,
 	                                  &type, &format, &num_of_items, &leftover_bytes, &data);
 	if (status != Success || type != XA_CARDINAL || num_of_items < 1)
 		return false;
@@ -89,11 +92,11 @@
 
 static bool FetchName(XDisplay* display, XWindow window, std::string& result) {
 	/* TODO: Check if XInternAtom created None or not... */
-	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", False),
-		                          result, ::XInternAtom(display, "UTF8_STRING", False)))
+	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", True),
+		                          result, ::XInternAtom(display, "UTF8_STRING", True)))
 		return true;
 
-	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "WM_NAME", False),
+	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "WM_NAME", True),
 		                           result, XA_STRING))
 		return true;
 
@@ -125,12 +128,74 @@
 	return true;
 }
 
-static bool WalkWindows(XDisplay* display, std::set<XWindow>& children, const std::set<XWindow>& windows) {
-	/* This can take a VERY long time if many windows are open. */
-	if (windows.empty())
+static bool GetAllTopLevelWindowsEWMH(XDisplay* display, XWindow root, std::set<XWindow>& result) {
+	XAtom Atom__NET_CLIENT_LIST = XInternAtom(display, "_NET_CLIENT_LIST", True);
+	if (Atom__NET_CLIENT_LIST == None)
 		return false;
 
-	for (const XWindow& window : windows) {
+	XAtom actual_type;
+	int format;
+	unsigned long num_of_items, bytes_after;
+	unsigned char* data = nullptr;
+
+	{
+		int status = ::XGetWindowProperty(
+			display, root, Atom__NET_CLIENT_LIST,
+			0L, (~0L), false, AnyPropertyType, &actual_type,
+			&format, &num_of_items, &bytes_after, &data
+		);
+
+		if (status < Success || !num_of_items)
+			return false;
+
+		if (format != 32) {
+			::XFree(data);
+			return false;
+		}
+	}
+
+	XWindow* arr = (XWindow*)data;
+
+	for (uint32_t i = 0; i < num_of_items; i++)
+		result.insert(arr[i]);
+
+	::XFree(data);
+
+	return true;
+}
+
+static bool GetAllTopLevelWindows(XDisplay* display, XWindow root, std::set<XWindow>& result) {
+	// EWMH. Takes about 15 ms on a fairly good PC.
+	if (GetAllTopLevelWindowsEWMH(display, root, result))
+		return true;
+
+	// Fallback to ICCCM. Takes about the same time on a good PC.
+	XAtom Atom_WM_STATE = XInternAtom(display, "WM_STATE", True);
+	if (Atom_WM_STATE == None)
+		return false;
+
+	auto window_has_wm_state = [&](XWindow window) -> bool {
+		int format;
+		Atom actual_type;
+		unsigned long num_of_items, bytes_after;
+		unsigned char* data = nullptr;
+
+		int status = ::XGetWindowProperty(
+			display, window, Atom_WM_STATE,
+			0L, (~0L), false, AnyPropertyType, &actual_type,
+			&format, &num_of_items, &bytes_after, &data
+		);
+
+		::XFree(data);
+
+		return !(actual_type == None && !format && !bytes_after);
+	};
+
+	std::function<bool(XWindow, XWindow&)> immediate_child_get_toplevel = [&](XWindow window, XWindow& result) {
+		result = window;
+		if (window_has_wm_state(window))
+			return true;
+
 		unsigned int num_children = 0;
 		XWindow* children_arr = nullptr;
 
@@ -139,25 +204,47 @@
 
 		int status = ::XQueryTree(display, window, &root_return, &parent_return, &children_arr, &num_children);
 		if (!status || !children_arr)
-			continue;
+			return false;
 
 		if (num_children < 1) {
 			::XFree(children_arr);
-			continue;
+			return false;
+		}
+
+		for (unsigned int i = 0; i < num_children; i++) {
+			if (immediate_child_get_toplevel(children_arr[i], result)) {
+				::XFree(children_arr);
+				return true;
+			}
 		}
 
-		for (int i = 0; i < num_children; i++)
-			if (!children.count(children_arr[i]))
-				children.insert(children_arr[i]);
+		::XFree(children_arr);
+		return false;
+	};
 
-		::XFree(children_arr);
+	unsigned int num_children = 0;
+	XWindow* children_arr = nullptr;
+
+	XWindow root_return;
+	XWindow parent_return;
 
-		std::set<XWindow> children_children;
+	int status = ::XQueryTree(display, root, &root_return, &parent_return, &children_arr, &num_children);
+	if (!status || !children_arr)
+		return false; // how
 
-		if (WalkWindows(display, children_children, children))
-			children.insert(children_children.begin(), children_children.end());
+	if (num_children < 1) {
+		::XFree(children_arr);
+		return false;
 	}
 
+	for (unsigned int i = 0; i < num_children; i++) {
+		XWindow res;
+		if (immediate_child_get_toplevel(children_arr[i], res))
+			result.insert(res);
+	}
+
+	::XFree(children_arr);
+
 	return true;
 }
 
@@ -172,7 +259,7 @@
 	XWindow root = ::XDefaultRootWindow(display);
 
 	std::set<XWindow> windows;
-	WalkWindows(display, windows, {root});
+	GetAllTopLevelWindows(display, root, windows);
 
 	for (const auto& window : windows) {
 		Window win;