258
+ − 1 #include "animone/win/x11.h"
+ − 2 #include "animone.h"
+ − 3 #include "animone/fd.h" /* GetProcessName() */
+ − 4 #include "animone/win.h"
+ − 5
+ − 6 #include <xcb/res.h>
+ − 7 #include <xcb/xcb.h>
+ − 8
+ − 9 #include <climits>
+ − 10 #include <cstdint>
+ − 11 #include <cstring>
+ − 12 #include <set>
+ − 13 #include <string>
+ − 14
+ − 15 #include <chrono>
+ − 16
+ − 17 #include <iostream>
+ − 18
+ − 19 /* This uses XCB (and it uses it *right*), so it should be plenty fast */
+ − 20
+ − 21 static size_t str_nlen(const char* s, size_t len) {
+ − 22 size_t i = 0;
+ − 23 for (; i < len && s[i]; i++)
+ − 24 ;
+ − 25 return i;
+ − 26 }
+ − 27
+ − 28 namespace animone::internal::x11 {
+ − 29
+ − 30 static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots,
+ − 31 std::set<xcb_window_t>& result) {
+ − 32 const xcb_atom_t Atom__NET_CLIENT_LIST = [connection] {
+ − 33 static constexpr std::string_view name = "_NET_CLIENT_LIST";
+ − 34 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
+ − 35 std::unique_ptr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL));
+ − 36
+ − 37 xcb_atom_t atom = reply->atom;
+ − 38
+ − 39 return atom;
+ − 40 }();
+ − 41 if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE)
+ − 42 return false; // BTFO
+ − 43
+ − 44 bool success = false;
+ − 45
+ − 46 std::vector<xcb_get_property_cookie_t> cookies;
+ − 47 cookies.reserve(roots.size());
+ − 48
+ − 49 for (const auto& root : roots)
+ − 50 cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX));
+ − 51
+ − 52 for (const auto& cookie : cookies) {
+ − 53 std::unique_ptr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL));
+ − 54
+ − 55 if (reply) {
+ − 56 xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply));
+ − 57 int len = ::xcb_get_property_value_length(reply);
+ − 58
+ − 59 for (size_t i = 0; i < len; i++)
+ − 60 result.insert(value[i]);
+ − 61
+ − 62 success |= true;
+ − 63 }
+ − 64 }
+ − 65
+ − 66 return success;
+ − 67 }
+ − 68
+ − 69 /* This is called on every window. What this does is:
+ − 70 * 1. Gets the tree of children
+ − 71 * 2. Searches all children recursively for a WM_STATE property
+ − 72 * 3. If that failed... return the original window
+ − 73 */
+ − 74 static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows,
+ − 75 int windows_len, std::set<xcb_window_t>& result) {
+ − 76 /* The depth we should start returning at. */
+ − 77 static constexpr int CUTOFF = 2;
+ − 78
+ − 79 bool success = false;
+ − 80
+ − 81 std::vector<xcb_query_tree_cookie_t> cookies;
+ − 82 cookies.reserve(windows_len);
+ − 83
+ − 84 for (int i = 0; i < windows_len; i++)
+ − 85 cookies.push_back(::xcb_query_tree(connection, windows[i]));
+ − 86
+ − 87 for (const auto& cookie : cookies) {
+ − 88 std::unique_ptr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL));
+ − 89
+ − 90 xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply);
+ − 91 int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply);
+ − 92
+ − 93 std::vector<xcb_get_property_cookie_t> state_property_cookies;
+ − 94 state_property_cookies.reserve(tree_children_len);
+ − 95
+ − 96 for (int i = 0; i < tree_children_len; i++)
+ − 97 state_property_cookies.push_back(
+ − 98 ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0));
+ − 99
+ − 100 for (int i = 0; i < tree_children_len; i++) {
+ − 101 std::unique_ptr<xcb_get_property_reply_t> get_property_reply(
+ − 102 ::xcb_get_property_reply(connection, state_property_cookies[i], NULL));
+ − 103
+ − 104 /* X11 is unfriendly here. what this means is "did the property exist?" */
+ − 105 if (get_property_reply->format || get_property_reply->type || get_property_reply->length) {
+ − 106 result.insert(tree_children[i]);
+ − 107 if (depth >= CUTOFF)
+ − 108 return true;
+ − 109
+ − 110 success |= true;
+ − 111 continue;
+ − 112 }
+ − 113 }
+ − 114
+ − 115 if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) {
+ − 116 success |= true;
+ − 117 if (depth >= CUTOFF)
+ − 118 return true;
+ − 119 continue;
+ − 120 }
+ − 121 }
+ − 122
+ − 123 return success;
+ − 124 }
+ − 125
+ − 126 static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots,
+ − 127 std::set<xcb_window_t>& result) {
+ − 128 bool success = false;
+ − 129
+ − 130 xcb_atom_t Atom_WM_STATE = [connection] {
+ − 131 static constexpr std::string_view name = "WM_STATE";
+ − 132 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
+ − 133 xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL);
+ − 134
+ − 135 xcb_atom_t atom = reply->atom;
+ − 136 free(reply);
+ − 137 return atom;
+ − 138 }();
+ − 139 if (Atom_WM_STATE == XCB_ATOM_NONE)
+ − 140 return success;
+ − 141
+ − 142 std::vector<xcb_query_tree_cookie_t> cookies;
+ − 143 cookies.reserve(roots.size());
+ − 144
+ − 145 for (const auto& root : roots)
+ − 146 cookies.push_back(::xcb_query_tree(connection, root));
+ − 147
+ − 148 for (const auto& cookie : cookies)
+ − 149 success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result);
+ − 150
+ − 151 return success;
+ − 152 }
+ − 153
+ − 154 bool EnumerateWindows(window_proc_t window_proc) {
+ − 155 if (!window_proc)
+ − 156 return false;
+ − 157
+ − 158 xcb_connection_t* connection = ::xcb_connect(NULL, NULL);
+ − 159 if (!connection)
+ − 160 return false;
+ − 161
+ − 162 std::set<xcb_window_t> windows;
+ − 163 {
+ − 164 std::vector<xcb_window_t> roots;
+ − 165 {
+ − 166 xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(xcb_get_setup(connection));
+ − 167 for (; iter.rem; ::xcb_screen_next(&iter))
+ − 168 roots.push_back(iter.data->root);
+ − 169 }
+ − 170
+ − 171 if (!GetAllTopLevelWindowsEWMH(connection, roots, windows))
+ − 172 GetAllTopLevelWindowsICCCM(connection, roots, windows);
+ − 173 }
+ − 174
+ − 175 struct WindowCookies {
+ − 176 xcb_window_t window;
+ − 177 xcb_get_property_cookie_t class_property_cookie;
+ − 178 xcb_get_property_cookie_t name_property_cookie;
+ − 179 xcb_res_query_client_ids_cookie_t pid_property_cookie;
+ − 180 };
+ − 181
+ − 182 std::vector<WindowCookies> window_cookies;
+ − 183 window_cookies.reserve(windows.size());
+ − 184
+ − 185 for (const auto& window : windows) {
+ − 186 xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID};
+ − 187
+ − 188 WindowCookies window_cookie = {
+ − 189 window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L),
+ − 190 ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX),
+ − 191 ::xcb_res_query_client_ids(connection, 1, &spec)};
+ − 192
+ − 193 window_cookies.push_back(window_cookie);
+ − 194 }
+ − 195
+ − 196 for (const auto& window_cookie : window_cookies) {
+ − 197 Window win = {0};
+ − 198 win.id = window_cookie.window;
+ − 199 {
+ − 200 /* Class name */
+ − 201 std::unique_ptr<xcb_get_property_reply_t> reply(
+ − 202 ::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL));
+ − 203
+ − 204 if (reply && reply->format == 8) {
+ − 205 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply.get()));
+ − 206 const int data_len = ::xcb_get_property_value_length(reply.get());
+ − 207
+ − 208 int instance_len = str_nlen(data, data_len);
+ − 209 const char* class_name = data + instance_len + 1;
+ − 210
+ − 211 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1)));
+ − 212 }
+ − 213 }
+ − 214 {
+ − 215 /* Title text */
+ − 216 std::unique_ptr<xcb_get_property_reply_t> reply(
+ − 217 ::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL));
+ − 218
+ − 219 if (reply) {
+ − 220 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply.get()));
+ − 221 int len = ::xcb_get_property_value_length(reply.get());
+ − 222
+ − 223 win.text = std::string(data, len);
+ − 224 }
+ − 225 }
+ − 226 Process proc = {0};
+ − 227 {
+ − 228 /* PID */
+ − 229 std::unique_ptr<xcb_res_query_client_ids_reply_t> reply(
+ − 230 ::xcb_res_query_client_ids_reply(connection, window_cookie.pid_property_cookie, NULL));
+ − 231
+ − 232 if (reply) {
+ − 233 xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply);
+ − 234 for (; it.rem; ::xcb_res_client_id_value_next(&it)) {
+ − 235 if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
+ − 236 proc.pid = *reinterpret_cast<uint32_t*>(::xcb_res_client_id_value_value(it.data));
+ − 237 fd::GetProcessName(proc.pid, proc.name); /* fill this in if we can */
+ − 238 break;
+ − 239 }
+ − 240 }
+ − 241 }
+ − 242 }
+ − 243
+ − 244 if (!window_proc(proc, win)) {
+ − 245 ::xcb_disconnect(connection);
+ − 246 return false;
+ − 247 }
+ − 248 }
+ − 249
+ − 250 ::xcb_disconnect(connection);
+ − 251
+ − 252 return true;
+ − 253 }
+ − 254
+ − 255 } // namespace animone::internal::x11