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