Mercurial > minori
diff dep/animone/src/win/x11.cc @ 337:a7d4e5107531
dep/animone: REFACTOR ALL THE THINGS
1: animone now has its own syntax divergent from anisthesia,
making different platforms actually have their own sections
2: process names in animone are now called `comm' (this will
probably break things). this is what its called in bsd/linux
so I'm just going to use it everywhere
3: the X11 code now checks for the existence of a UTF-8 window title
and passes it if available
4: ANYTHING THATS NOT LINUX IS 100% UNTESTED AND CAN AND WILL BREAK!
I still actually need to test the bsd code. to be honest I'm probably
going to move all of the bsds into separate files because they're
all essentially different operating systems at this point
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Wed, 19 Jun 2024 12:51:15 -0400 |
parents | d260549151d6 |
children | adb79bdde329 |
line wrap: on
line diff
--- a/dep/animone/src/win/x11.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/win/x11.cc Wed Jun 19 12:51:15 2024 -0400 @@ -13,6 +13,7 @@ #include <xcb/xcb.h> #include <climits> +#include <cassert> #include <cstdint> #include <cstring> #include <cstdlib> @@ -21,6 +22,7 @@ #include <memory> #include <chrono> +#include <unordered_map> #include <iostream> @@ -42,18 +44,32 @@ template<typename T> using XcbPtr = std::unique_ptr<T, XcbDestructor<T>>; -static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, +/* -------------------------------------------------------------- + * 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) { - 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()); - XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); - - xcb_atom_t atom = reply->atom; - - return atom; - }(); - if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE) + if (atoms.at(NeededAtom::NET_CLIENT_LIST) == XCB_ATOM_NONE) return false; // BTFO bool success = false; @@ -62,7 +78,7 @@ 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)); + 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)); @@ -81,88 +97,95 @@ return success; } -/* This is called on every window. What this does is: - * 1. Gets the tree of children - * 2. Searches all children recursively for a WM_STATE property - * 3. If that failed... return the original window - */ +/* 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 depth we should start returning at. */ - static constexpr int CUTOFF = 1; - - bool success = false; - + /* 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 (const auto& cookie : cookies) { - XcbPtr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL)); + 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()); - std::vector<xcb_get_property_cookie_t> state_property_cookies; - state_property_cookies.reserve(tree_children_len); + /* 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_property_cookies.push_back( - ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); + 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_property_cookies[i], NULL)); + XcbPtr<xcb_get_property_reply_t> get_property_reply(::xcb_get_property_reply(connection, state_cookies[i], NULL)); + if (!get_property_reply) + continue; - /* X11 is unfriendly here. what this means is "did the property exist?" */ - if (get_property_reply->format || get_property_reply->type || get_property_reply->length) { + /* 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; - - success |= true; - continue; } } - if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) { - success |= 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 success; + return true; } -static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, +static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { - bool success = false; + if (atoms.at(NeededAtom::WM_STATE) == XCB_ATOM_NONE) + return false; - 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()); - XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); + return WalkWindows(connection, 0, atoms.at(NeededAtom::WM_STATE), roots.data(), roots.size(), result); +} - xcb_atom_t atom = reply->atom; +static XcbAtoms InitializeAtoms(xcb_connection_t* connection) { + XcbAtoms atoms; + + std::unordered_map<NeededAtom, xcb_intern_atom_cookie_t> atom_cookies; - return atom; - }(); - if (Atom_WM_STATE == XCB_ATOM_NONE) - return success; - - std::vector<xcb_query_tree_cookie_t> cookies; - cookies.reserve(roots.size()); + for (const auto& [atom, str] : atom_strings) + atom_cookies[atom] = ::xcb_intern_atom(connection, 1, str.size(), str.data()); - for (const auto& root : roots) - cookies.push_back(::xcb_query_tree(connection, root)); + 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; - for (const auto& cookie : cookies) - success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result); + atoms[atom] = reply->atom; + } - return success; + return atoms; } bool EnumerateWindows(window_proc_t window_proc) { @@ -173,6 +196,8 @@ if (xcb_connection_has_error(connection)) return false; + XcbAtoms atoms = InitializeAtoms(connection); + std::set<xcb_window_t> windows; { std::vector<xcb_window_t> roots; @@ -182,15 +207,16 @@ roots.push_back(iter.data->root); } - if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) - GetAllTopLevelWindowsICCCM(connection, roots, windows); + if (!GetAllTopLevelWindowsEWMH(connection, atoms, roots, windows)) + GetAllTopLevelWindowsICCCM(connection, atoms, roots, windows); } struct WindowCookies { xcb_window_t window; - xcb_get_property_cookie_t class_property_cookie; - xcb_get_property_cookie_t name_property_cookie; - xcb_res_query_client_ids_cookie_t pid_property_cookie; + 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; @@ -200,19 +226,22 @@ xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID}; WindowCookies window_cookie = { - window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L), - ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX), - ::xcb_res_query_client_ids(connection, 1, &spec)}; + .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 = {0}; + Window win; win.id = window_cookie.window; { /* Class name */ - XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL)); + 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())); @@ -226,32 +255,47 @@ } { /* Title text */ - XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL)); + 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) { + 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())); - int len = ::xcb_get_property_value_length(reply.get()); win.text = std::string(data, len); } } - Process proc = {0}; + 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_property_cookie, NULL)); + 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.name); /* fill this in if we can */ + 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;