Mercurial > minori
view dep/animone/src/win/x11.cc @ 342:adb79bdde329
dep/animone: fix tons of issues
for example, the window ID stuff was just... completely wrong. since we're
supporting multiple different window systems, it *has* to be a union rather
than just a single integer type. HWND is also not a DWORD, it's a pointer(!),
so now it's stored as a std::uintptr_t.
(this probably breaks things)
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Thu, 20 Jun 2024 03:03:05 -0400 |
parents | a7d4e5107531 |
children |
line wrap: on
line source
/* * win/x11.c: support for X11 using XCB * * some things might be wrong here due to * having to use recursive logic, but whatever */ #include "animone/win/x11.h" #include "animone.h" #include "animone/fd.h" /* GetProcessName() */ #include "animone/win.h" #include <xcb/res.h> #include <xcb/xcb.h> #include <climits> #include <cassert> #include <cstdint> #include <cstring> #include <cstdlib> #include <set> #include <string> #include <memory> #include <chrono> #include <unordered_map> #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 animone::internal::x11 { template<typename T> struct XcbDestructor { using pointer = T*; void operator()(pointer t) const { std::free(t); }; }; template<typename T> using XcbPtr = std::unique_ptr<T, XcbDestructor<T>>; /* -------------------------------------------------------------- * atom cruft */ enum class NeededAtom { /* EWMH */ NET_CLIENT_LIST, NET_WM_NAME, UTF8_STRING, /* ICCCM */ WM_STATE, }; static const std::unordered_map<NeededAtom, std::string> atom_strings = { {NeededAtom::NET_CLIENT_LIST, "_NET_CLIENT_LIST"}, {NeededAtom::NET_WM_NAME, "_NET_WM_NAME"}, {NeededAtom::UTF8_STRING, "UTF8_STRING"}, {NeededAtom::WM_STATE, "WM_STATE"}, }; using XcbAtoms = std::unordered_map<NeededAtom, xcb_atom_t>; static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { if (atoms.at(NeededAtom::NET_CLIENT_LIST) == XCB_ATOM_NONE) return false; // BTFO bool success = false; 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, atoms.at(NeededAtom::NET_CLIENT_LIST), XCB_ATOM_ANY, 0L, UINT_MAX)); for (const auto& cookie : cookies) { XcbPtr<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.get())); int len = ::xcb_get_property_value_length(reply.get()) / sizeof(xcb_window_t); for (size_t i = 0; i < len; i++) result.insert(value[i]); success |= true; } } return success; } /* This should be called with a list of toplevels for each root. */ static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows, int windows_len, std::set<xcb_window_t>& result) { /* The level of depth we want to cut off past; since we want to go over each top level window, * we cut off after we've passed the root window and the toplevel. */ static constexpr int CUTOFF = 2; std::vector<xcb_query_tree_cookie_t> cookies; cookies.reserve(windows_len); for (int i = 0; i < windows_len; i++) cookies.push_back(::xcb_query_tree(connection, windows[i])); for (int i = 0; i < cookies.size(); i++) { /* XXX is it *really* okay to ask xcb for a cookie and then never ask for a reply? * valgrind doesn't complain, so I'm not gonna care for now. */ XcbPtr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookies[i], NULL)); xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get()); int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get()); /* search for any window with a WM_STATE property */ std::vector<xcb_get_property_cookie_t> state_cookies; state_cookies.reserve(tree_children_len); for (int i = 0; i < tree_children_len; i++) state_cookies.push_back( ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 4L)); bool found = false; for (int i = 0; i < tree_children_len; i++) { XcbPtr<xcb_get_property_reply_t> get_property_reply(::xcb_get_property_reply(connection, state_cookies[i], NULL)); if (!get_property_reply) continue; /* did we get valid data? */ if (get_property_reply->type == Atom_WM_STATE || get_property_reply->format != 0 || get_property_reply->bytes_after != 0) { int len = ::xcb_get_property_value_length(get_property_reply.get()); if (len < sizeof(uint32_t)) continue; uint32_t state = *reinterpret_cast<uint32_t*>(::xcb_get_property_value(get_property_reply.get())); if (state != 1) // NormalState continue; result.insert(tree_children[i]); found = true; if (depth >= CUTOFF) return true; } } if (found) continue; bool res = WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result); if (depth >= CUTOFF) return res; } return true; } static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { if (atoms.at(NeededAtom::WM_STATE) == XCB_ATOM_NONE) return false; return WalkWindows(connection, 0, atoms.at(NeededAtom::WM_STATE), roots.data(), roots.size(), result); } static XcbAtoms InitializeAtoms(xcb_connection_t* connection) { XcbAtoms atoms; std::unordered_map<NeededAtom, xcb_intern_atom_cookie_t> atom_cookies; for (const auto& [atom, str] : atom_strings) atom_cookies[atom] = ::xcb_intern_atom(connection, 1, str.size(), str.data()); for (const auto& [atom, cookie] : atom_cookies) { XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); if (!reply) atoms[atom] = XCB_ATOM_NONE; atoms[atom] = reply->atom; } return atoms; } bool EnumerateWindows(window_proc_t window_proc) { if (!window_proc) return false; xcb_connection_t* connection = ::xcb_connect(NULL, NULL); if (xcb_connection_has_error(connection)) return false; XcbAtoms atoms = InitializeAtoms(connection); std::set<xcb_window_t> windows; { std::vector<xcb_window_t> roots; { xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection)); for (; iter.rem; ::xcb_screen_next(&iter)) roots.push_back(iter.data->root); } if (!GetAllTopLevelWindowsEWMH(connection, atoms, roots, windows)) GetAllTopLevelWindowsICCCM(connection, atoms, roots, windows); } struct WindowCookies { xcb_window_t window; xcb_get_property_cookie_t class_name; xcb_get_property_cookie_t name_utf8; xcb_get_property_cookie_t name; xcb_res_query_client_ids_cookie_t pid; }; std::vector<WindowCookies> window_cookies; window_cookies.reserve(windows.size()); for (const auto& window : windows) { xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID}; WindowCookies window_cookie = { .window = window, .class_name = ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L), .name_utf8 = ::xcb_get_property(connection, 0, window, atoms[NeededAtom::NET_WM_NAME], atoms[NeededAtom::UTF8_STRING], 0L, UINT_MAX), .name = ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX), .pid = ::xcb_res_query_client_ids(connection, 1, &spec), }; window_cookies.push_back(window_cookie); } for (const auto& window_cookie : window_cookies) { Window win; win.id.x11 = window_cookie.window; { /* Class name */ XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.class_name, NULL)); if (reply && reply->format == 8) { const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); const int data_len = ::xcb_get_property_value_length(reply.get()); 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))); } } { /* Title text */ XcbPtr<xcb_get_property_reply_t> reply_utf8(::xcb_get_property_reply(connection, window_cookie.name_utf8, NULL)); XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.name, NULL)); int utf8_len = ::xcb_get_property_value_length(reply_utf8.get()); int len = ::xcb_get_property_value_length(reply.get()); if (reply_utf8 && utf8_len > 0) { const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply_utf8.get())); win.text = std::string(data, utf8_len); } else if (reply && len > 0) { const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); win.text = std::string(data, len); } } Process proc; proc.platform = ExecutablePlatform::Posix; // not entirely correct, but whatever. who cares { /* PID */ XcbPtr<xcb_res_query_client_ids_reply_t> reply(::xcb_res_query_client_ids_reply(connection, window_cookie.pid, NULL)); if (reply) { xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get()); for (; it.rem; ::xcb_res_client_id_value_next(&it)) { if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { proc.pid = *::xcb_res_client_id_value_value(it.data); GetProcessName(proc.pid, proc.comm); /* fill this in if we can */ break; } } } } /* debug printing std::cout << "window found: " << std::hex << win.id << std::dec << "\n" << " name: " << win.text << "\n" << " class: " << win.class_name << "\n" << " pid: " << proc.pid << "\n" << " comm: " << proc.name << std::endl; */ if (!window_proc(proc, win)) { ::xcb_disconnect(connection); return false; } } ::xcb_disconnect(connection); return true; } } // namespace animone::internal::x11