view dep/animia/src/win/x11.cc @ 198:bc1ae1810855

dep/animia: switch from using classes to global functions the old idea was ok, but sort of hackish; this method doesn't use classes at all, and this way (especially important!) we can do wayland stuff AND x11 at the same time, which wasn't really possible without stupid workarounds in the other method
author Paper <mrpapersonic@gmail.com>
date Sun, 24 Dec 2023 02:59:42 -0500
parents 0fc126d52de4
children 9f3534f6b8c4
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) {
	/* This sucks. It takes waaaay too long to finish.
	 * TODO: Look at the code for xwininfo to see what they do.
	*/
	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 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;
}

}