Mercurial > minori
comparison dep/animia/src/win/x11.cc @ 234:8ccf0302afb1
dep/animia: convert xlib code to xcb
I'm not *entirely* sure if this will work correctly for everything, but it works fine enough
| author | Paper <mrpapersonic@gmail.com> |
|---|---|
| date | Tue, 16 Jan 2024 08:08:42 -0500 |
| parents | 8d35061e7505 |
| children | 593108b3d555 |
comparison
equal
deleted
inserted
replaced
| 233:0a5b6a088886 | 234:8ccf0302afb1 |
|---|---|
| 1 #include "animia/win/x11.h" | 1 #include "animia/win/x11.h" |
| 2 #include "animia/win.h" | 2 #include "animia/win.h" |
| 3 #include "animia.h" | 3 #include "animia.h" |
| 4 | 4 |
| 5 #include <X11/Xlib.h> | 5 #include <xcb/xcb.h> |
| 6 #include <X11/Xutil.h> | 6 #include <xcb/res.h> |
| 7 #include <X11/Xatom.h> // XA_* | |
| 8 #ifdef HAVE_XRES | |
| 9 #include <X11/extensions/XRes.h> | |
| 10 #endif | |
| 11 | 7 |
| 12 #include <cstdint> | 8 #include <cstdint> |
| 9 #include <climits> | |
| 10 #include <cstring> | |
| 13 #include <string> | 11 #include <string> |
| 14 #include <set> | 12 #include <set> |
| 15 | 13 |
| 14 #include <iostream> | |
| 15 | |
| 16 static size_t str_nlen(const char* s, size_t len) { | |
| 17 size_t i = 0; | |
| 18 for (; i < len && s[i]; i++); | |
| 19 return i; | |
| 20 } | |
| 21 | |
| 16 namespace animia::internal::x11 { | 22 namespace animia::internal::x11 { |
| 17 | 23 |
| 18 /* specify that these are X types. */ | 24 static void GetWindowPID(xcb_connection_t* connection, const std::vector<xcb_window_t>& windows, std::unordered_map<xcb_window_t, pid_t>& result) { |
| 19 typedef ::Window XWindow; | 25 std::vector<xcb_res_query_client_ids_cookie_t> cookies; |
| 20 typedef ::Display XDisplay; | 26 cookies.reserve(windows.size()); |
| 21 typedef ::Atom XAtom; | 27 |
| 22 | 28 for (const auto& window : windows) { |
| 23 /* should return UTF8_STRING or STRING. this means we are not | 29 xcb_res_client_id_spec_t spec = { |
| 24 * *guaranteed* a UTF-8 string back. | 30 .client = window, |
| 25 */ | 31 .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID |
| 26 static bool GetWindowPropertyAsString(XDisplay* display, XWindow window, XAtom atom, std::string& result) { | 32 }; |
| 27 if (atom == None) | 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 | |
| 50 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]{ | |
| 52 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()); | |
| 54 xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL); | |
| 55 | |
| 56 xcb_atom_t atom = reply->atom; | |
| 57 free(reply); | |
| 58 return atom; | |
| 59 }(); | |
| 60 if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE) | |
| 61 return false; // BTFO | |
| 62 | |
| 63 bool success = false; | |
| 64 | |
| 65 std::vector<xcb_get_property_cookie_t> cookies; | |
| 66 cookies.reserve(roots.size()); | |
| 67 | |
| 68 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)); | |
| 70 | |
| 71 for (const auto& cookie : cookies) { | |
| 72 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, cookie, NULL); | |
| 73 if (reply) { | |
| 74 xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply)); | |
| 75 int len = ::xcb_get_property_value_length(reply); | |
| 76 | |
| 77 for (size_t i = 0; i < len; i++) | |
| 78 result.insert(value[i]); | |
| 79 success = true; | |
| 80 } | |
| 81 free(reply); | |
| 82 } | |
| 83 | |
| 84 return success; | |
| 85 } | |
| 86 | |
| 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) { | |
| 89 /* move this somewhere */ | |
| 90 xcb_atom_t Atom_WM_STATE = [connection]{ | |
| 91 static constexpr std::string_view name = "WM_STATE"; | |
| 92 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); | |
| 94 | |
| 95 xcb_atom_t atom = reply->atom; | |
| 96 free(reply); | |
| 97 return atom; | |
| 98 }(); | |
| 99 if (Atom_WM_STATE == XCB_ATOM_NONE) | |
| 28 return false; | 100 return false; |
| 29 | 101 |
| 30 XAtom Atom_UTF8_STRING = ::XInternAtom(display, "UTF8_STRING", False); | 102 std::vector<xcb_query_tree_cookie_t> cookies; |
| 31 | 103 cookies.reserve(roots.size()); |
| 32 int format; | 104 |
| 33 unsigned long leftover_bytes, num_of_items; | 105 for (const auto& root : roots) |
| 34 XAtom type; | 106 cookies.push_back(::xcb_query_tree(connection, root)); |
| 35 unsigned char* data; | 107 |
| 36 | 108 for (const auto& cookie : cookies) { |
| 37 int status = ::XGetWindowProperty(display, window, atom, 0L, (~0L), False, AnyPropertyType, | 109 xcb_query_tree_reply_t* reply = ::xcb_query_tree_reply(connection, cookie, NULL); |
| 38 &type, &format, &num_of_items, &leftover_bytes, &data); | 110 |
| 39 if (status != Success || !(type == Atom_UTF8_STRING || type == XA_STRING) || !num_of_items) | 111 std::vector<xcb_window_t> windows = [reply]{ |
| 40 return false; | 112 xcb_window_t* windows = ::xcb_query_tree_children(reply); |
| 41 | 113 int len = ::xcb_query_tree_children_length(reply); |
| 42 result = std::string((char*)data, num_of_items); | 114 |
| 43 | 115 std::vector<xcb_window_t> w; |
| 44 ::XFree(data); | 116 w.reserve(len); |
| 45 | 117 |
| 46 return true; | 118 for (int i = 0; i < len; i++) |
| 47 } | 119 w.push_back(windows[i]); |
| 48 | 120 |
| 49 /* this should return CARDINAL, a 32-bit integer */ | 121 return w; |
| 50 static bool GetWindowPID(XDisplay* display, XWindow window, pid_t& result) { | 122 }(); |
| 51 #ifdef HAVE_XRES | 123 |
| 52 { | 124 std::vector<xcb_get_property_cookie_t> state_property_cookies; |
| 53 long num_ids; | 125 state_property_cookies.reserve(windows.size()); |
| 54 XResClientIdValue *client_ids; | 126 |
| 55 XResClientIdSpec spec = { | 127 for (int i = 0; i < windows.size(); i++) |
| 56 .client = window, | 128 state_property_cookies.push_back(::xcb_get_property(connection, 0, windows[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); |
| 57 .mask = XRES_CLIENT_ID_PID_MASK | 129 |
| 58 }; | 130 for (size_t i = 0; i < state_property_cookies.size(); i++) { |
| 59 | 131 xcb_generic_error_t* err = NULL; |
| 60 ::XResQueryClientIds(display, 1, &spec, &num_ids, &client_ids); | 132 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, state_property_cookies.at(i), &err); |
| 61 | 133 |
| 62 for (long i = 0; i < num_ids; i++) { | 134 if (reply->format || reply->type || reply->length) { |
| 63 if (client_ids[i].spec.mask == XRES_CLIENT_ID_PID_MASK) { | 135 result.insert(windows[i]); |
| 64 result = ::XResGetClientPid(&client_ids[i]); | 136 free(reply); |
| 65 ::XResClientIdsDestroy(num_ids, client_ids); | 137 continue; |
| 66 return true; | 138 } |
| 67 } | 139 |
| 68 } | 140 free(reply); |
| 69 | 141 } |
| 70 ::XResClientIdsDestroy(num_ids, client_ids); | 142 |
| 71 | 143 if (WalkWindows(connection, windows, result)) |
| 72 return false; | 144 continue; |
| 73 } | 145 } |
| 74 #endif | 146 |
| 75 | 147 return false; |
| 76 XAtom Atom__NET_WM_PID = ::XInternAtom(display, "_NET_WM_PID", True); | |
| 77 if (Atom__NET_WM_PID == None) | |
| 78 return false; | |
| 79 | |
| 80 int format; | |
| 81 unsigned long leftover_bytes, num_of_items; | |
| 82 XAtom type; | |
| 83 unsigned char* data; | |
| 84 | |
| 85 int status = ::XGetWindowProperty(display, window, Atom__NET_WM_PID, 0L, (~0L), False, XA_CARDINAL, | |
| 86 &type, &format, &num_of_items, &leftover_bytes, &data); | |
| 87 if (status != Success || type != XA_CARDINAL || num_of_items < 1) | |
| 88 return false; | |
| 89 | |
| 90 result = static_cast<pid_t>(*reinterpret_cast<uint32_t*>(data)); | |
| 91 | |
| 92 ::XFree(data); | |
| 93 | |
| 94 return true; | |
| 95 } | |
| 96 | |
| 97 static bool FetchName(XDisplay* display, XWindow window, std::string& result) { | |
| 98 if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "_NET_WM_NAME", True), result)) | |
| 99 return true; | |
| 100 | |
| 101 if (GetWindowPropertyAsString(display, window, ::XInternAtom(display, "WM_NAME", True), result)) | |
| 102 return true; | |
| 103 | |
| 104 /* Fallback to XGetWMName() */ | |
| 105 XTextProperty text; | |
| 106 | |
| 107 { | |
| 108 int status = ::XGetWMName(display, window, &text); | |
| 109 if (!status || !text.value || !text.nitems) | |
| 110 return false; | |
| 111 } | |
| 112 | |
| 113 char** list; | |
| 114 | |
| 115 { | |
| 116 int count; | |
| 117 | |
| 118 int status = ::XmbTextPropertyToTextList(display, &text, &list, &count); | |
| 119 if (status != Success || !count || !*list) | |
| 120 return false; | |
| 121 } | |
| 122 | |
| 123 ::XFree(text.value); | |
| 124 | |
| 125 result = *list; | |
| 126 | |
| 127 ::XFreeStringList(list); | |
| 128 | |
| 129 return true; | |
| 130 } | |
| 131 | |
| 132 static bool GetAllTopLevelWindowsEWMH(XDisplay* display, XWindow root, std::set<XWindow>& result) { | |
| 133 XAtom Atom__NET_CLIENT_LIST = XInternAtom(display, "_NET_CLIENT_LIST", True); | |
| 134 if (Atom__NET_CLIENT_LIST == None) | |
| 135 return false; | |
| 136 | |
| 137 XAtom actual_type; | |
| 138 int format; | |
| 139 unsigned long num_of_items, bytes_after; | |
| 140 unsigned char* data = nullptr; | |
| 141 | |
| 142 { | |
| 143 int status = ::XGetWindowProperty( | |
| 144 display, root, Atom__NET_CLIENT_LIST, | |
| 145 0L, (~0L), false, AnyPropertyType, &actual_type, | |
| 146 &format, &num_of_items, &bytes_after, &data | |
| 147 ); | |
| 148 | |
| 149 if (status < Success || !num_of_items) | |
| 150 return false; | |
| 151 } | |
| 152 | |
| 153 XWindow* arr = (XWindow*)data; | |
| 154 | |
| 155 for (uint32_t i = 0; i < num_of_items; i++) | |
| 156 result.insert(arr[i]); | |
| 157 | |
| 158 ::XFree(data); | |
| 159 | |
| 160 return true; | |
| 161 } | |
| 162 | |
| 163 static bool GetAllTopLevelWindows(XDisplay* display, XWindow root, std::set<XWindow>& result) { | |
| 164 // EWMH. Takes about 15 ms on a fairly good PC. | |
| 165 if (GetAllTopLevelWindowsEWMH(display, root, result)) | |
| 166 return true; | |
| 167 | |
| 168 // Fallback to ICCCM. Takes about the same time on a good PC. | |
| 169 XAtom Atom_WM_STATE = XInternAtom(display, "WM_STATE", True); | |
| 170 if (Atom_WM_STATE == None) | |
| 171 return false; | |
| 172 | |
| 173 auto window_has_wm_state = [&](XWindow window) -> bool { | |
| 174 int format; | |
| 175 Atom actual_type; | |
| 176 unsigned long num_of_items, bytes_after; | |
| 177 unsigned char* data = nullptr; | |
| 178 | |
| 179 int status = ::XGetWindowProperty( | |
| 180 display, window, Atom_WM_STATE, | |
| 181 0L, (~0L), false, AnyPropertyType, &actual_type, | |
| 182 &format, &num_of_items, &bytes_after, &data | |
| 183 ); | |
| 184 | |
| 185 ::XFree(data); | |
| 186 | |
| 187 return !(actual_type == None && !format && !bytes_after); | |
| 188 }; | |
| 189 | |
| 190 std::function<bool(XWindow, XWindow&)> immediate_child_get_toplevel = [&](XWindow window, XWindow& result) { | |
| 191 result = window; | |
| 192 if (window_has_wm_state(window)) | |
| 193 return true; | |
| 194 | |
| 195 unsigned int num_children = 0; | |
| 196 XWindow* children_arr = nullptr; | |
| 197 | |
| 198 XWindow root_return; | |
| 199 XWindow parent_return; | |
| 200 | |
| 201 int status = ::XQueryTree(display, window, &root_return, &parent_return, &children_arr, &num_children); | |
| 202 if (!status || !children_arr) | |
| 203 return false; | |
| 204 | |
| 205 if (num_children < 1) { | |
| 206 ::XFree(children_arr); | |
| 207 return false; | |
| 208 } | |
| 209 | |
| 210 for (unsigned int i = 0; i < num_children; i++) { | |
| 211 if (immediate_child_get_toplevel(children_arr[i], result)) { | |
| 212 ::XFree(children_arr); | |
| 213 return true; | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 ::XFree(children_arr); | |
| 218 return false; | |
| 219 }; | |
| 220 | |
| 221 unsigned int num_children = 0; | |
| 222 XWindow* children_arr = nullptr; | |
| 223 | |
| 224 XWindow root_return; | |
| 225 XWindow parent_return; | |
| 226 | |
| 227 int status = ::XQueryTree(display, root, &root_return, &parent_return, &children_arr, &num_children); | |
| 228 if (!status || !children_arr) | |
| 229 return false; // how | |
| 230 | |
| 231 if (num_children < 1) { | |
| 232 ::XFree(children_arr); | |
| 233 return false; | |
| 234 } | |
| 235 | |
| 236 for (unsigned int i = 0; i < num_children; i++) { | |
| 237 XWindow res; | |
| 238 if (immediate_child_get_toplevel(children_arr[i], res)) | |
| 239 result.insert(res); | |
| 240 } | |
| 241 | |
| 242 ::XFree(children_arr); | |
| 243 | |
| 244 return true; | |
| 245 } | 148 } |
| 246 | 149 |
| 247 bool EnumerateWindows(window_proc_t window_proc) { | 150 bool EnumerateWindows(window_proc_t window_proc) { |
| 248 if (!window_proc) | 151 if (!window_proc) |
| 249 return false; | 152 return false; |
| 250 | 153 |
| 251 XDisplay* display = ::XOpenDisplay(nullptr); | 154 xcb_connection_t* connection = ::xcb_connect(NULL, NULL); |
| 252 if (!display) | 155 if (!connection) |
| 253 return false; | 156 return false; |
| 254 | 157 |
| 255 XWindow root = ::XDefaultRootWindow(display); | 158 std::vector<xcb_screen_t> screens; |
| 256 | 159 |
| 257 std::set<XWindow> windows; | 160 { |
| 258 GetAllTopLevelWindows(display, root, windows); | 161 xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(xcb_get_setup(connection)); |
| 162 for (; iter.rem; ::xcb_screen_next(&iter)) | |
| 163 screens.push_back(*iter.data); | |
| 164 } | |
| 165 | |
| 166 std::vector<xcb_window_t> roots; | |
| 167 roots.reserve(screens.size()); | |
| 168 | |
| 169 for (const auto& screen : screens) | |
| 170 roots.push_back(screen.root); | |
| 171 | |
| 172 std::set<xcb_window_t> windows; | |
| 173 //if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) | |
| 174 WalkWindows(connection, roots, windows); | |
| 175 | |
| 176 std::vector<xcb_get_property_cookie_t> class_property_cookies; | |
| 177 std::vector<xcb_get_property_cookie_t> name_property_cookies; | |
| 178 std::vector<xcb_get_property_cookie_t> pid_property_cookies; | |
| 179 class_property_cookies.reserve(windows.size()); | |
| 180 name_property_cookies.reserve(windows.size()); | |
| 181 pid_property_cookies.reserve(windows.size()); | |
| 259 | 182 |
| 260 for (const auto& window : windows) { | 183 for (const auto& window : windows) { |
| 261 Window win; | 184 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)); | |
| 186 pid_property_cookies.push_back(::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_CARDINAL, 0L, 1L)); | |
| 187 } | |
| 188 | |
| 189 size_t i = 0; | |
| 190 for (const auto& window : windows) { | |
| 191 Window win = {0}; | |
| 262 win.id = window; | 192 win.id = window; |
| 263 { | 193 { |
| 264 ::XClassHint* hint = ::XAllocClassHint(); | 194 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, class_property_cookies.at(i), NULL); |
| 265 if (::XGetClassHint(display, window, hint)) { | 195 if (reply && reply->format == 8) { |
| 266 win.class_name = hint->res_class; | 196 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply)); |
| 267 ::XFree(hint); | 197 const int data_len = ::xcb_get_property_value_length(reply); |
| 268 } | 198 |
| 269 } | 199 int instance_len = str_nlen(data, data_len); |
| 270 FetchName(display, window, win.text); | 200 const char* class_name = data + instance_len + 1; |
| 271 | 201 |
| 272 Process proc; | 202 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1))); |
| 273 GetWindowPID(display, window, proc.pid); | 203 } |
| 274 | 204 free(reply); |
| 275 if (!window_proc(proc, win)) | 205 } |
| 206 { | |
| 207 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, name_property_cookies.at(i), NULL); | |
| 208 if (reply) { | |
| 209 const char* data = reinterpret_cast<char*>(::xcb_get_property_value(reply)); | |
| 210 int len = ::xcb_get_property_value_length(reply); | |
| 211 | |
| 212 win.text = std::string((char*)data, len); | |
| 213 } | |
| 214 free(reply); | |
| 215 } | |
| 216 std::cout << win.class_name << ": " << win.text << std::endl; | |
| 217 Process proc = {0}; | |
| 218 { | |
| 219 xcb_get_property_reply_t* reply = ::xcb_get_property_reply(connection, pid_property_cookies.at(i), NULL); | |
| 220 if (reply) | |
| 221 proc.pid = *reinterpret_cast<uint32_t*>(::xcb_get_property_value(reply)); | |
| 222 | |
| 223 free(reply); | |
| 224 } | |
| 225 if (!window_proc(proc, win)) { | |
| 226 ::xcb_disconnect(connection); | |
| 276 return false; | 227 return false; |
| 277 } | 228 } |
| 229 i++; | |
| 230 } | |
| 231 | |
| 232 ::xcb_disconnect(connection); | |
| 278 | 233 |
| 279 return true; | 234 return true; |
| 280 } | 235 } |
| 281 | 236 |
| 282 } | 237 } |
