view dep/animia/src/win/x11.cc @ 187:9613d72b097e

*: multiple performance improvements like marking `static const` when it makes sense... date: change old stupid heap-based method to a structure which should make copying the thing actually make a copy. also many performance-based changes, like removing the std::tie dependency and forward-declaring nlohmann json *: replace every instance of QString::fromUtf8 to Strings::ToQString. the main difference is that our function will always convert exactly what is in the string, while some other times it would only convert up to the nearest NUL byte
author Paper <mrpapersonic@gmail.com>
date Wed, 06 Dec 2023 13:43:54 -0500
parents 31735c8592bc
children 0fc126d52de4
line wrap: on
line source

#include "animia/win/x11.h"
#include "animia/win.h"
#include "animia.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h> // XA_*

#include <cstdint>
#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 {

/* should return UTF8_STRING or STRING */
static bool GetWindowPropertyAsString(::Display* display, ::Window window, ::Atom atom, std::string& result, ::Atom reqtype = AnyPropertyType) {
	int format;
	unsigned long leftover_bytes, num_of_items;
	::Atom type;
	unsigned char* data;

	int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, reqtype,
	                                  &type, &format, &num_of_items, &leftover_bytes, &data);
	if (status != Success || !(reqtype == AnyPropertyType || type == reqtype) || !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(::Display* display, ::Window window, pid_t& result) {
	int format;
	unsigned long leftover_bytes, num_of_items;
	::Atom atom = ::XInternAtom(display, "_NET_WM_PID", False), type;
	unsigned char* data;

	int status = ::XGetWindowProperty(display, window, atom, 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>(*(uint32_t*)data);

	::XFree(data);

	return true;
}

static bool FetchName(::Display* display, ::Window 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)))
		return true;

	if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "WM_NAME", False),
		                           result, XA_STRING))
		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 WalkWindows(::Display* display, std::set<::Window>& children, const std::set<::Window>& windows) {
	if (windows.empty())
		return false;

	for (const ::Window& window : windows) {
		unsigned int num_children = 0;
		::Window* children_arr = nullptr;

		::Window root_return;
		::Window parent_return;

		int status = ::XQueryTree(display, window, &root_return, &parent_return, &children_arr, &num_children);
		if (!status || !children_arr)
			continue;

		if (num_children < 1) {
			::XFree(children_arr);
			continue;
		}

		for (int i = 0; i < num_children; i++)
			if (!children.count(children_arr[i]))
				children.insert(children_arr[i]);

		::XFree(children_arr);

		std::set<::Window> children_children;

		if (WalkWindows(display, children_children, children))
			children.insert(children_children.begin(), children_children.end());
	}

	return true;
}

bool X11WinTools::EnumerateWindows(window_proc_t window_proc) {
	if (!window_proc)
		return false;

	::Display* display = ::XOpenDisplay(nullptr);
	if (!display)
		return false;

	::Window root = DefaultRootWindow(display);

	std::set<::Window> windows;
	WalkWindows(display, windows, {root});

	for (const auto& window : windows) {
		Window win;
		win.id = window;
		{
			::XClassHint* hint = ::XAllocClassHint();
			if (::XGetClassHint(display, window, hint)) {
				win.class_name = hint->res_class;
				::XFree(hint);
			}
		}
		FetchName(display, window, win.text);

		Process proc;
		GetWindowPID(display, window, proc.pid);

		if (!window_proc(proc, win))
			return false;
	}

	return true;
}

}