changeset 234:8ccf0302afb1

dep/animia: convert xlib code to xcb I'm not *entirely* sure if this will work correctly for everything, but it works fine enough
author Paper <mrpapersonic@gmail.com>
date Tue, 16 Jan 2024 08:08:42 -0500
parents 0a5b6a088886
children 593108b3d555
files dep/animia/CMakeLists.txt dep/animia/data/players.anisthesia dep/animia/src/win/x11.cc
diffstat 3 files changed, 179 insertions(+), 260 deletions(-) [+]
line wrap: on
line diff
--- a/dep/animia/CMakeLists.txt	Mon Jan 15 08:10:58 2024 -0500
+++ b/dep/animia/CMakeLists.txt	Tue Jan 16 08:08:42 2024 -0500
@@ -88,53 +88,18 @@
 	endif() # LINUX
 endif() # WIN32 AND APPLE
 
-# It's technically possible to have an X server on macOS, so we
-# should compile X11 support.
 # X11
-find_package(X11 COMPONENTS X11 XRes)
-
-if(X11_FOUND)
-	# Getting PIDs from windows...
-	if (X11_XRes_FOUND)
-		list(APPEND DEFINES HAVE_XRES)
-	else() # NOT X11_XRes_FOUND
-		message(WARNING "libXRes could not be found! Finding PIDs in X11 windows may not work correctly!")
-	endif() # X11_XRes_FOUND
-
-	list(APPEND DEFINES X11)
-	list(APPEND SRC_FILES src/win/x11.cc)
-	list(APPEND INCLUDE_DIRS ${X11_INCLUDE_DIRS})
-	list(APPEND LIBRARIES ${X11_LIBRARIES}) # This will include Xres, I think..
-else() # NOT X11_FOUND
-	# For some systems, i.e. Debian, FindX11 fails to find X11, so we have
-	# to use pkg_config as a fallback
-	find_package(PkgConfig)
-	if(PKG_CONFIG_FOUND)
-		pkg_check_modules(X11 x11)
-		if(X11_FOUND)
-			# Check for XRes the hard way
-			find_path(X11_XRes_HEADER "X11/extensions/XRes.h" PATHS ${X11_INCLUDE_DIRS})
-			find_library(X11_XRes_LIB XRes PATHS ${X11_LIBRARY_DIRS})
-
-			if(X11_XRes_HEADER AND X11_XRes_LIB)
-				# TODO: We should REALLY check for XResQueryClientIds here...
-				list(APPEND DEFINES HAVE_XRES)
-				list(APPEND LIBRARIES ${X11_XRes_LIB})
-			else()
-				message(WARNING "libXRes could not be found! Finding PIDs in X11 windows may not work correctly!")
-			endif()
-
-			list(APPEND DEFINES X11)
-			list(APPEND SRC_FILES src/win/x11.cc)
-			list(APPEND INCLUDE_DIRS ${X11_INCLUDE_DIRS})
-			list(APPEND LIBRARIES ${X11_LINK_LIBRARIES})
-		endif() # X11_FOUND
-	endif() # PKG_CONFIG_FOUND
-endif() # X11_FOUND
-
-# Wayland.
 find_package(PkgConfig)
 if(PKG_CONFIG_FOUND)
+	pkg_check_modules(XCB xcb xcb-res)
+	if (XCB_FOUND)
+		list(APPEND DEFINES X11)
+		list(APPEND LIBRARIES ${XCB_LINK_LIBRARIES})
+		list(APPEND INCLUDE_DIRS ${XCB_INCLUDE_DIRS})
+		list(APPEND SRC_FILES src/win/x11.cc)
+	endif() # XCB_FOUND
+
+	# Wayland
 	pkg_check_modules(WAYLAND wayland-client)
 	if(WAYLAND_FOUND)
 		enable_language(C)
--- a/dep/animia/data/players.anisthesia	Mon Jan 15 08:10:58 2024 -0500
+++ b/dep/animia/data/players.anisthesia	Tue Jan 16 08:08:42 2024 -0500
@@ -355,7 +355,6 @@
 		QWidget
 		# Skinnable interface
 		SkinWindowClass
-
 		# X11
 		vlc
 	executables:
--- a/dep/animia/src/win/x11.cc	Mon Jan 15 08:10:58 2024 -0500
+++ b/dep/animia/src/win/x11.cc	Tue Jan 16 08:08:42 2024 -0500
@@ -2,280 +2,235 @@
 #include "animia/win.h"
 #include "animia.h"
 
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h> // XA_*
-#ifdef HAVE_XRES
-#include <X11/extensions/XRes.h>
-#endif
+#include <xcb/xcb.h>
+#include <xcb/res.h>
 
 #include <cstdint>
+#include <climits>
+#include <cstring>
 #include <string>
 #include <set>
 
+#include <iostream>
+
+static size_t str_nlen(const char* s, size_t len) {
+    size_t i = 0;
+    for (; i < len && s[i]; i++);
+    return i;
+}
+
 namespace animia::internal::x11 {
 
-/* specify that these are X types. */
-typedef ::Window XWindow;
-typedef ::Display XDisplay;
-typedef ::Atom XAtom;
-
-/* should return UTF8_STRING or STRING. this means we are not
- * *guaranteed* a UTF-8 string back.
-*/
-static bool GetWindowPropertyAsString(XDisplay* display, XWindow window, XAtom atom, std::string& result) {
-	if (atom == None)
-		return false;
-
-	XAtom Atom_UTF8_STRING = ::XInternAtom(display, "UTF8_STRING", False);
-
-	int format;
-	unsigned long leftover_bytes, num_of_items;
-	XAtom type;
-	unsigned char* data;
+static void GetWindowPID(xcb_connection_t* connection, const std::vector<xcb_window_t>& windows, std::unordered_map<xcb_window_t, pid_t>& result) {
+	std::vector<xcb_res_query_client_ids_cookie_t> cookies;
+	cookies.reserve(windows.size());
 
-	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, AnyPropertyType,
-	                                  &type, &format, &num_of_items, &leftover_bytes, &data);
-	if (status != Success || !(type == Atom_UTF8_STRING || type == XA_STRING) || !num_of_items)
-		return false;
-
-	result = std::string((char*)data, num_of_items);
-
-	::XFree(data);
-
-	return true;
-}
-
-/* this should return CARDINAL, a 32-bit integer */
-static bool GetWindowPID(XDisplay* display, XWindow window, pid_t& result) {
-#ifdef HAVE_XRES
-	{
-		long num_ids;
-		XResClientIdValue *client_ids;
-		XResClientIdSpec spec = {
+	for (const auto& window : windows) {
+		xcb_res_client_id_spec_t spec = {
 			.client = window,
-			.mask = XRES_CLIENT_ID_PID_MASK
+			.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID
 		};
 
-		::XResQueryClientIds(display, 1, &spec, &num_ids, &client_ids);
+		cookies.push_back(::xcb_res_query_client_ids(connection, 1, &spec));
+	}
+
+	for (size_t i = 0; i < cookies.size(); i++) {
+		xcb_res_query_client_ids_reply_t* reply = ::xcb_res_query_client_ids_reply(connection, cookies.at(i), NULL);
 
-		for (long i = 0; i < num_ids; i++) {
-			if (client_ids[i].spec.mask == XRES_CLIENT_ID_PID_MASK) {
-				result = ::XResGetClientPid(&client_ids[i]);
-				::XResClientIdsDestroy(num_ids, client_ids);
-				return true;
+		xcb_res_client_id_value_iterator_t it = xcb_res_query_client_ids_ids_iterator(reply);
+		for (; it.rem; xcb_res_client_id_value_next(&it)) {
+			if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
+				result[windows.at(i)] = *::xcb_res_client_id_value_value(it.data);
+				continue;
 			}
 		}
-
-		::XResClientIdsDestroy(num_ids, client_ids);
-
-		return false;
 	}
-#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 type;
-	unsigned char* data;
-
-	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;
-
-	result = static_cast<pid_t>(*reinterpret_cast<uint32_t*>(data));
-
-	::XFree(data);
-
-	return true;
-}
-
-static bool FetchName(XDisplay* display, XWindow window, std::string& result) {
-	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", True), result))
-		return true;
-
-	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "WM_NAME", True), result))
-		return true;
-
-	/* Fallback to XGetWMName() */
-	XTextProperty text;
-
-	{
-		int status = ::XGetWMName(display, window, &text);
-		if (!status || !text.value || !text.nitems)
-			return false;
-	}
-
-	char** list;
-
-	{
-		int count;
-
-		int status = ::XmbTextPropertyToTextList(display, &text, &list, &count);
-		if (status != Success || !count || !*list)
-			return false;
-	}
-
-	::XFree(text.value);
-
-	result = *list;
-
-	::XFreeStringList(list);
-
-	return true;
 }
 
-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;
+static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) {
+	const xcb_atom_t Atom__NET_CLIENT_LIST = [connection]{
+		static constexpr std::string_view name = "_NET_CLIENT_LIST";
+		xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
+		xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL);
 
-	XAtom actual_type;
-	int format;
-	unsigned long num_of_items, bytes_after;
-	unsigned char* data = nullptr;
+		xcb_atom_t atom = reply->atom;
+		free(reply);
+		return atom;
+	}();
+	if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE)
+		return false; // BTFO
+
+	bool success = false;
 
-	{
-		int status = ::XGetWindowProperty(
-			display, root, Atom__NET_CLIENT_LIST,
-			0L, (~0L), false, AnyPropertyType, &actual_type,
-			&format, &num_of_items, &bytes_after, &data
-		);
+	std::vector<xcb_get_property_cookie_t> cookies;
+	cookies.reserve(roots.size());
+
+	for (const auto& root : roots)
+		cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX));
 
-		if (status < Success || !num_of_items)
-			return false;
+	for (const auto& cookie : cookies) {
+		xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, cookie, NULL);
+		if (reply) {
+			xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply));
+			int len = ::xcb_get_property_value_length(reply);
+
+			for (size_t i = 0; i < len; i++)
+				result.insert(value[i]);
+			success = true;
+		}
+		free(reply);
 	}
 
-	XWindow* arr = (XWindow*)data;
-
-	for (uint32_t i = 0; i < num_of_items; i++)
-		result.insert(arr[i]);
-
-	::XFree(data);
-
-	return true;
+	return success;
 }
 
-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;
+/* I have no idea why this works. */
+static bool WalkWindows(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) {
+	/* move this somewhere */
+	xcb_atom_t Atom_WM_STATE = [connection]{
+		static constexpr std::string_view name = "WM_STATE";
+		xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
+		xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL);
 
-	// 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)
+		xcb_atom_t atom = reply->atom;
+		free(reply);
+		return atom;
+	}();
+	if (Atom_WM_STATE == XCB_ATOM_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;
+	std::vector<xcb_query_tree_cookie_t> cookies;
+	cookies.reserve(roots.size());
+
+	for (const auto& root : roots)
+		cookies.push_back(::xcb_query_tree(connection, root));
+
+	for (const auto& cookie : cookies) {
+		xcb_query_tree_reply_t* reply = ::xcb_query_tree_reply(connection, cookie, NULL);
 
-		int status = ::XGetWindowProperty(
-			display, window, Atom_WM_STATE,
-			0L, (~0L), false, AnyPropertyType, &actual_type,
-			&format, &num_of_items, &bytes_after, &data
-		);
+		std::vector<xcb_window_t> windows = [reply]{
+			xcb_window_t* windows = ::xcb_query_tree_children(reply);
+			int len = ::xcb_query_tree_children_length(reply);
 
-		::XFree(data);
+			std::vector<xcb_window_t> w;
+			w.reserve(len);
 
-		return !(actual_type == None && !format && !bytes_after);
-	};
+			for (int i = 0; i < len; i++)
+				w.push_back(windows[i]);
 
-	std::function<bool(XWindow, XWindow&)> immediate_child_get_toplevel = [&](XWindow window, XWindow& result) {
-		result = window;
-		if (window_has_wm_state(window))
-			return true;
+			return w;
+		}();
 
-		unsigned int num_children = 0;
-		XWindow* children_arr = nullptr;
+		std::vector<xcb_get_property_cookie_t> state_property_cookies;
+		state_property_cookies.reserve(windows.size());
+
+		for (int i = 0; i < windows.size(); i++)
+			state_property_cookies.push_back(::xcb_get_property(connection, 0, windows[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0));
 
-		XWindow root_return;
-		XWindow parent_return;
+		for (size_t i = 0; i < state_property_cookies.size(); i++) {
+			xcb_generic_error_t* err = NULL;
+			xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, state_property_cookies.at(i), &err);
 
-		int status = ::XQueryTree(display, window, &root_return, &parent_return, &children_arr, &num_children);
-		if (!status || !children_arr)
-			return false;
+			if (reply->format || reply->type || reply->length) {
+				result.insert(windows[i]);
+				free(reply);
+				continue;
+			}
 
-		if (num_children < 1) {
-			::XFree(children_arr);
-			return false;
+			free(reply);
 		}
 
-		for (unsigned int i = 0; i < num_children; i++) {
-			if (immediate_child_get_toplevel(children_arr[i], result)) {
-				::XFree(children_arr);
-				return true;
-			}
-		}
-
-		::XFree(children_arr);
-		return false;
-	};
-
-	unsigned int num_children = 0;
-	XWindow* children_arr = nullptr;
-
-	XWindow root_return;
-	XWindow parent_return;
-
-	int status = ::XQueryTree(display, root, &root_return, &parent_return, &children_arr, &num_children);
-	if (!status || !children_arr)
-		return false; // how
-
-	if (num_children < 1) {
-		::XFree(children_arr);
-		return false;
+		if (WalkWindows(connection, windows, result))
+			continue;
 	}
 
-	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;
+	return false;
 }
 
 bool EnumerateWindows(window_proc_t window_proc) {
 	if (!window_proc)
 		return false;
 
-	XDisplay* display = ::XOpenDisplay(nullptr);
-	if (!display)
+	xcb_connection_t* connection = ::xcb_connect(NULL, NULL);
+	if (!connection)
 		return false;
 
-	XWindow root = ::XDefaultRootWindow(display);
+	std::vector<xcb_screen_t> screens;
+
+	{
+		xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(xcb_get_setup(connection));
+		for (; iter.rem; ::xcb_screen_next(&iter))
+			screens.push_back(*iter.data);
+	}
+
+	std::vector<xcb_window_t> roots;
+	roots.reserve(screens.size());
 
-	std::set<XWindow> windows;
-	GetAllTopLevelWindows(display, root, windows);
+	for (const auto& screen : screens)
+		roots.push_back(screen.root);
+
+	std::set<xcb_window_t> windows;
+	//if (!GetAllTopLevelWindowsEWMH(connection, roots, windows))
+		WalkWindows(connection, roots, windows);
+
+	std::vector<xcb_get_property_cookie_t> class_property_cookies;
+	std::vector<xcb_get_property_cookie_t> name_property_cookies;
+	std::vector<xcb_get_property_cookie_t> pid_property_cookies;
+	class_property_cookies.reserve(windows.size());
+	name_property_cookies.reserve(windows.size());
+	pid_property_cookies.reserve(windows.size());
 
 	for (const auto& window : windows) {
-		Window win;
+		class_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L));
+		name_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX));
+		pid_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_CARDINAL, 0L, 1L));
+	}
+
+	size_t i = 0;
+	for (const auto& window : windows) {
+		Window win = {0};
 		win.id = window;
 		{
-			::XClassHint* hint = ::XAllocClassHint();
-			if (::XGetClassHint(display, window, hint)) {
-				win.class_name = hint->res_class;
-				::XFree(hint);
+			xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, class_property_cookies.at(i), NULL);
+			if (reply && reply->format == 8) {
+				const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply));
+				const int data_len = ::xcb_get_property_value_length(reply);
+
+				int instance_len = str_nlen(data, data_len);
+				const char* class_name = data + instance_len + 1;
+
+				win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1)));
 			}
+			free(reply);
 		}
-		FetchName(display, window, win.text);
+		{
+			xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, name_property_cookies.at(i), NULL);
+			if (reply) {
+				const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply));
+				int len = ::xcb_get_property_value_length(reply);
 
-		Process proc;
-		GetWindowPID(display, window, proc.pid);
+				win.text = std::string((char*)data, len);
+			}
+			free(reply);
+		}
+		std::cout << win.class_name << ": " << win.text << std::endl;
+		Process proc = {0};
+		{
+			xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, pid_property_cookies.at(i), NULL);
+			if (reply)
+				proc.pid = *reinterpret_cast<uint32_t*>(::xcb_get_property_value(reply));
 
-		if (!window_proc(proc, win))
+			free(reply);
+		}
+		if (!window_proc(proc, win)) {
+			::xcb_disconnect(connection);
 			return false;
+		}
+		i++;
 	}
 
+	::xcb_disconnect(connection);
+
 	return true;
 }