view dep/animia/src/win/x11.cc @ 199:9f3534f6b8c4

dep/animia: initial Wayland support, drop non-working kvm fd plugin
author Paper <mrpapersonic@gmail.com>
date Tue, 02 Jan 2024 02:34:27 -0500
parents bc1ae1810855
children 58e81b42a0d6
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_*
#ifdef HAVE_XRES
#include <X11/extensions/XRes.h>
#endif

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

/* specify that these are X types. */
typedef ::Window XWindow;
typedef ::Display XDisplay;
typedef ::Atom XAtom;

/* should return UTF8_STRING or STRING */
static bool GetWindowPropertyAsString(XDisplay* display, XWindow window, XAtom atom, std::string& result, XAtom reqtype = AnyPropertyType) {
	int format;
	unsigned long leftover_bytes, num_of_items;
	XAtom 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(XDisplay* display, XWindow window, pid_t& result) {
#ifdef HAVE_XRES
	{
		long num_ids;
		XResClientIdValue *client_ids;
		XResClientIdSpec spec = {
			.client = window,
			.mask = XRES_CLIENT_ID_PID_MASK
		};

		::XResQueryClientIds(display, 1, &spec, &num_ids, &client_ids);

		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;
			}
		}

		::XResClientIdsDestroy(num_ids, client_ids);

		return false;
	}
#endif

	int format;
	unsigned long leftover_bytes, num_of_items;
	XAtom 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>(*reinterpret_cast<uint32_t*>(data));

	::XFree(data);

	return true;
}

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)))
		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(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())
		return false;

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

		XWindow root_return;
		XWindow 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<XWindow> 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;

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

	XWindow root = ::XDefaultRootWindow(display);

	std::set<XWindow> 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;
}

}