Mercurial > minori
comparison dep/animia/src/win/x11.cc @ 235:593108b3d555
dep/animia: x11: finalize xcb conversion
| author | Paper <paper@paper.us.eu.org> |
|---|---|
| date | Tue, 16 Jan 2024 15:22:29 -0500 |
| parents | 8ccf0302afb1 |
| children | 4d461ef7d424 |
comparison
equal
deleted
inserted
replaced
| 234:8ccf0302afb1 | 235:593108b3d555 |
|---|---|
| 1 /* | |
| 2 * win/x11.cc: provides support for X11 clients via XCB | |
| 3 * | |
| 4 * This code is fairly fast (I think...). As a result, | |
| 5 * a lot of it is hard to read if you're unfamiliar with | |
| 6 * asynchronous programming, but it works much better than | |
| 7 * Xlib (which can take much longer). | |
| 8 */ | |
| 1 #include "animia/win/x11.h" | 9 #include "animia/win/x11.h" |
| 2 #include "animia/win.h" | 10 #include "animia/win.h" |
| 3 #include "animia.h" | 11 #include "animia.h" |
| 4 | 12 |
| 5 #include <xcb/xcb.h> | 13 #include <xcb/xcb.h> |
| 9 #include <climits> | 17 #include <climits> |
| 10 #include <cstring> | 18 #include <cstring> |
| 11 #include <string> | 19 #include <string> |
| 12 #include <set> | 20 #include <set> |
| 13 | 21 |
| 14 #include <iostream> | |
| 15 | |
| 16 static size_t str_nlen(const char* s, size_t len) { | 22 static size_t str_nlen(const char* s, size_t len) { |
| 17 size_t i = 0; | 23 size_t i = 0; |
| 18 for (; i < len && s[i]; i++); | 24 for (; i < len && s[i]; i++); |
| 19 return i; | 25 return i; |
| 20 } | 26 } |
| 21 | 27 |
| 22 namespace animia::internal::x11 { | 28 namespace animia::internal::x11 { |
| 23 | |
| 24 static void GetWindowPID(xcb_connection_t* connection, const std::vector<xcb_window_t>& windows, std::unordered_map<xcb_window_t, pid_t>& result) { | |
| 25 std::vector<xcb_res_query_client_ids_cookie_t> cookies; | |
| 26 cookies.reserve(windows.size()); | |
| 27 | |
| 28 for (const auto& window : windows) { | |
| 29 xcb_res_client_id_spec_t spec = { | |
| 30 .client = window, | |
| 31 .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID | |
| 32 }; | |
| 33 | |
| 34 cookies.push_back(::xcb_res_query_client_ids(connection, 1, &spec)); | |
| 35 } | |
| 36 | |
| 37 for (size_t i = 0; i < cookies.size(); i++) { | |
| 38 xcb_res_query_client_ids_reply_t* reply = ::xcb_res_query_client_ids_reply(connection, cookies.at(i), NULL); | |
| 39 | |
| 40 xcb_res_client_id_value_iterator_t it = xcb_res_query_client_ids_ids_iterator(reply); | |
| 41 for (; it.rem; xcb_res_client_id_value_next(&it)) { | |
| 42 if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { | |
| 43 result[windows.at(i)] = *::xcb_res_client_id_value_value(it.data); | |
| 44 continue; | |
| 45 } | |
| 46 } | |
| 47 } | |
| 48 } | |
| 49 | 29 |
| 50 static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { | 30 static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { |
| 51 const xcb_atom_t Atom__NET_CLIENT_LIST = [connection]{ | 31 const xcb_atom_t Atom__NET_CLIENT_LIST = [connection]{ |
| 52 static constexpr std::string_view name = "_NET_CLIENT_LIST"; | 32 static constexpr std::string_view name = "_NET_CLIENT_LIST"; |
| 53 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); | 33 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); |
| 68 for (const auto& root : roots) | 48 for (const auto& root : roots) |
| 69 cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX)); | 49 cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX)); |
| 70 | 50 |
| 71 for (const auto& cookie : cookies) { | 51 for (const auto& cookie : cookies) { |
| 72 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, cookie, NULL); | 52 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, cookie, NULL); |
| 73 if (reply) { | 53 |
| 54 if (reply && reply->length == 32) { | |
| 74 xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply)); | 55 xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply)); |
| 75 int len = ::xcb_get_property_value_length(reply); | 56 int len = ::xcb_get_property_value_length(reply); |
| 76 | 57 |
| 77 for (size_t i = 0; i < len; i++) | 58 for (size_t i = 0; i < len; i++) |
| 78 result.insert(value[i]); | 59 result.insert(value[i]); |
| 60 | |
| 79 success = true; | 61 success = true; |
| 80 } | 62 } |
| 63 | |
| 81 free(reply); | 64 free(reply); |
| 82 } | 65 } |
| 83 | 66 |
| 84 return success; | 67 return success; |
| 85 } | 68 } |
| 86 | 69 |
| 87 /* I have no idea why this works. */ | |
| 88 static bool WalkWindows(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { | 70 static bool WalkWindows(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { |
| 89 /* move this somewhere */ | 71 /* move this somewhere else pl0x */ |
| 90 xcb_atom_t Atom_WM_STATE = [connection]{ | 72 xcb_atom_t Atom_WM_STATE = [connection]{ |
| 91 static constexpr std::string_view name = "WM_STATE"; | 73 static constexpr std::string_view name = "WM_STATE"; |
| 92 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); | 74 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); |
| 93 xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL); | 75 xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL); |
| 94 | 76 |
| 97 return atom; | 79 return atom; |
| 98 }(); | 80 }(); |
| 99 if (Atom_WM_STATE == XCB_ATOM_NONE) | 81 if (Atom_WM_STATE == XCB_ATOM_NONE) |
| 100 return false; | 82 return false; |
| 101 | 83 |
| 84 /* for each toplevel: search recursively for */ | |
| 85 std::function<bool(const xcb_window_t*, const int, xcb_window_t&)> find_wm_state_window = [&](const xcb_window_t* wins, const int wins_len, xcb_window_t& out) { | |
| 86 /* Check for wm_state */ | |
| 87 { | |
| 88 std::vector<xcb_get_property_cookie_t> property_cookies; | |
| 89 property_cookies.reserve(wins_len); | |
| 90 | |
| 91 for (size_t j = 0; j < wins_len; j++) | |
| 92 property_cookies.push_back(::xcb_get_property(connection, 0, wins[j], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); | |
| 93 | |
| 94 for (size_t j = 0; j < property_cookies.size(); j++) { | |
| 95 xcb_generic_error_t* err = NULL; | |
| 96 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, property_cookies.at(j), &err); | |
| 97 | |
| 98 if (reply->format || reply->type || reply->length) { | |
| 99 out = wins[j]; | |
| 100 free(reply); | |
| 101 return true; | |
| 102 } | |
| 103 | |
| 104 free(reply); | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 /* Query tree for recursion */ | |
| 109 { | |
| 110 std::vector<xcb_query_tree_cookie_t> cookies; | |
| 111 cookies.reserve(wins_len); | |
| 112 | |
| 113 for (size_t j = 0; j < wins_len; j++) | |
| 114 cookies.push_back(::xcb_query_tree(connection, wins[j])); | |
| 115 | |
| 116 for (const auto& cookie : cookies) { | |
| 117 xcb_query_tree_reply_t* reply = ::xcb_query_tree_reply(connection, cookie, NULL); | |
| 118 | |
| 119 xcb_window_t* windows = ::xcb_query_tree_children(reply); | |
| 120 int len = ::xcb_query_tree_children_length(reply); | |
| 121 | |
| 122 if (find_wm_state_window(windows, len, out)) { | |
| 123 free(reply); | |
| 124 return true; | |
| 125 } | |
| 126 | |
| 127 free(reply); | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 return false; | |
| 132 }; | |
| 133 | |
| 134 /* Get the tree for each root */ | |
| 102 std::vector<xcb_query_tree_cookie_t> cookies; | 135 std::vector<xcb_query_tree_cookie_t> cookies; |
| 103 cookies.reserve(roots.size()); | 136 cookies.reserve(roots.size()); |
| 104 | 137 |
| 105 for (const auto& root : roots) | 138 for (const auto& root : roots) |
| 106 cookies.push_back(::xcb_query_tree(connection, root)); | 139 cookies.push_back(::xcb_query_tree(connection, root)); |
| 107 | 140 |
| 108 for (const auto& cookie : cookies) { | 141 for (size_t i = 0; i < cookies.size(); i++) { |
| 109 xcb_query_tree_reply_t* reply = ::xcb_query_tree_reply(connection, cookie, NULL); | 142 xcb_query_tree_reply_t* reply = ::xcb_query_tree_reply(connection, cookies.at(i), NULL); |
| 110 | 143 |
| 111 std::vector<xcb_window_t> windows = [reply]{ | 144 xcb_window_t* windows = ::xcb_query_tree_children(reply); |
| 112 xcb_window_t* windows = ::xcb_query_tree_children(reply); | 145 int len = ::xcb_query_tree_children_length(reply); |
| 113 int len = ::xcb_query_tree_children_length(reply); | 146 if (len < 1) |
| 114 | 147 continue; |
| 115 std::vector<xcb_window_t> w; | 148 |
| 116 w.reserve(len); | 149 /* Then get the tree of each child window. */ |
| 117 | 150 std::vector<xcb_query_tree_cookie_t> cookies; |
| 118 for (int i = 0; i < len; i++) | 151 cookies.reserve(len); |
| 119 w.push_back(windows[i]); | 152 |
| 120 | 153 for (size_t j = 0; j < len; j++) |
| 121 return w; | 154 cookies.push_back(::xcb_query_tree(connection, windows[j])); |
| 122 }(); | 155 |
| 123 | 156 /* For each child window... */ |
| 124 std::vector<xcb_get_property_cookie_t> state_property_cookies; | 157 for (size_t j = 0; j < cookies.size(); j++) { |
| 125 state_property_cookies.reserve(windows.size()); | 158 xcb_query_tree_reply_t* reply = ::xcb_query_tree_reply(connection, cookies.at(j), NULL); |
| 126 | 159 |
| 127 for (int i = 0; i < windows.size(); i++) | 160 xcb_window_t* children = ::xcb_query_tree_children(reply); |
| 128 state_property_cookies.push_back(::xcb_get_property(connection, 0, windows[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); | 161 int children_len = ::xcb_query_tree_children_length(reply); |
| 129 | 162 if (children_len < 1) { |
| 130 for (size_t i = 0; i < state_property_cookies.size(); i++) { | 163 result.insert(windows[j]); |
| 131 xcb_generic_error_t* err = NULL; | |
| 132 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, state_property_cookies.at(i), &err); | |
| 133 | |
| 134 if (reply->format || reply->type || reply->length) { | |
| 135 result.insert(windows[i]); | |
| 136 free(reply); | |
| 137 continue; | 164 continue; |
| 138 } | 165 } |
| 139 | 166 |
| 140 free(reply); | 167 xcb_window_t out = windows[j]; |
| 141 } | 168 /* Search recursively for a window with WM_STATE. If we don't, |
| 142 | 169 * just add the toplevel. |
| 143 if (WalkWindows(connection, windows, result)) | 170 */ |
| 144 continue; | 171 find_wm_state_window(children, children_len, out); |
| 172 result.insert(out); | |
| 173 } | |
| 174 | |
| 175 free(reply); | |
| 145 } | 176 } |
| 146 | 177 |
| 147 return false; | 178 return false; |
| 148 } | 179 } |
| 149 | 180 |
| 168 | 199 |
| 169 for (const auto& screen : screens) | 200 for (const auto& screen : screens) |
| 170 roots.push_back(screen.root); | 201 roots.push_back(screen.root); |
| 171 | 202 |
| 172 std::set<xcb_window_t> windows; | 203 std::set<xcb_window_t> windows; |
| 173 //if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) | 204 if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) |
| 174 WalkWindows(connection, roots, windows); | 205 WalkWindows(connection, roots, windows); |
| 175 | 206 |
| 176 std::vector<xcb_get_property_cookie_t> class_property_cookies; | 207 std::vector<xcb_get_property_cookie_t> class_property_cookies; |
| 177 std::vector<xcb_get_property_cookie_t> name_property_cookies; | 208 std::vector<xcb_get_property_cookie_t> name_property_cookies; |
| 178 std::vector<xcb_get_property_cookie_t> pid_property_cookies; | 209 std::vector<xcb_res_query_client_ids_cookie_t> pid_property_cookies; |
| 179 class_property_cookies.reserve(windows.size()); | 210 class_property_cookies.reserve(windows.size()); |
| 180 name_property_cookies.reserve(windows.size()); | 211 name_property_cookies.reserve(windows.size()); |
| 181 pid_property_cookies.reserve(windows.size()); | 212 pid_property_cookies.reserve(windows.size()); |
| 182 | 213 |
| 183 for (const auto& window : windows) { | 214 for (const auto& window : windows) { |
| 184 class_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L)); | 215 class_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L)); |
| 185 name_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX)); | 216 name_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX)); |
| 186 pid_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_CARDINAL, 0L, 1L)); | 217 |
| 218 xcb_res_client_id_spec_t spec = { | |
| 219 .client = window, | |
| 220 .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID | |
| 221 }; | |
| 222 | |
| 223 pid_property_cookies.push_back(::xcb_res_query_client_ids(connection, 1, &spec)); | |
| 187 } | 224 } |
| 188 | 225 |
| 189 size_t i = 0; | 226 size_t i = 0; |
| 190 for (const auto& window : windows) { | 227 for (const auto& window : windows) { |
| 191 Window win = {0}; | 228 Window win = {0}; |
| 192 win.id = window; | 229 win.id = window; |
| 193 { | 230 { |
| 194 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, class_property_cookies.at(i), NULL); | 231 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, class_property_cookies.at(i), NULL); |
| 232 | |
| 195 if (reply && reply->format == 8) { | 233 if (reply && reply->format == 8) { |
| 196 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply)); | 234 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply)); |
| 197 const int data_len = ::xcb_get_property_value_length(reply); | 235 const int data_len = ::xcb_get_property_value_length(reply); |
| 198 | 236 |
| 199 int instance_len = str_nlen(data, data_len); | 237 int instance_len = str_nlen(data, data_len); |
| 200 const char* class_name = data + instance_len + 1; | 238 const char* class_name = data + instance_len + 1; |
| 201 | 239 |
| 202 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1))); | 240 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1))); |
| 203 } | 241 } |
| 242 | |
| 204 free(reply); | 243 free(reply); |
| 205 } | 244 } |
| 206 { | 245 { |
| 207 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, name_property_cookies.at(i), NULL); | 246 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, name_property_cookies.at(i), NULL); |
| 208 if (reply) { | 247 |
| 248 if (reply && reply->format == 8) { | |
| 209 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply)); | 249 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply)); |
| 210 int len = ::xcb_get_property_value_length(reply); | 250 int len = ::xcb_get_property_value_length(reply); |
| 211 | 251 |
| 212 win.text = std::string((char*)data, len); | 252 win.text = std::string(data, len); |
| 213 } | 253 } |
| 254 | |
| 214 free(reply); | 255 free(reply); |
| 215 } | 256 } |
| 216 std::cout << win.class_name << ": " << win.text << std::endl; | |
| 217 Process proc = {0}; | 257 Process proc = {0}; |
| 218 { | 258 { |
| 219 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, pid_property_cookies.at(i), NULL); | 259 xcb_res_query_client_ids_reply_t* reply = ::xcb_res_query_client_ids_reply(connection, pid_property_cookies.at(i), NULL); |
| 220 if (reply) | 260 |
| 221 proc.pid = *reinterpret_cast<uint32_t*>(::xcb_get_property_value(reply)); | 261 if (reply->length) { |
| 262 xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply); | |
| 263 for (; it.rem; ::xcb_res_client_id_value_next(&it)) { | |
| 264 if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { | |
| 265 proc.pid = *::xcb_res_client_id_value_value(it.data); | |
| 266 break; | |
| 267 } | |
| 268 } | |
| 269 } | |
| 222 | 270 |
| 223 free(reply); | 271 free(reply); |
| 224 } | 272 } |
| 273 | |
| 225 if (!window_proc(proc, win)) { | 274 if (!window_proc(proc, win)) { |
| 226 ::xcb_disconnect(connection); | 275 ::xcb_disconnect(connection); |
| 227 return false; | 276 return false; |
| 228 } | 277 } |
| 229 i++; | 278 i++; |
