Mercurial > minori
annotate dep/animone/src/win/x11.cc @ 272:5437009cb10e
dep/animone: get macOS side building
| author | Paper <paper@paper.us.eu.org> |
|---|---|
| date | Thu, 18 Apr 2024 16:51:35 -0400 |
| parents | 1a6a5d3a94cd |
| children | a796e97cc86d |
| rev | line source |
|---|---|
| 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) { | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
56 xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply.get())); |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
57 int len = ::xcb_get_property_value_length(reply.get()); |
| 258 | 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. */ | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
77 static constexpr int CUTOFF = 1; |
| 258 | 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 | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
90 xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get()); |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
91 int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get()); |
| 258 | 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 { | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
166 xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection)); |
| 258 | 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) { | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
205 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); |
| 258 | 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) { | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
220 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); |
| 258 | 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) { | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
233 xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get()); |
| 258 | 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) { | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
236 proc.pid = *::xcb_res_client_id_value_value(it.data); |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
237 GetProcessName(proc.pid, proc.name); /* fill this in if we can */ |
| 258 | 238 break; |
| 239 } | |
| 240 } | |
| 241 } | |
| 242 } | |
| 243 | |
|
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
244 std::cout << "got window: " << win.id << "\n" |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
245 << "class name: " << win.class_name << "\n" |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
246 << "title: " << win.text << "\n" |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
247 << "PID: " << proc.pid << "\n" |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
248 << "executable: " << proc.name << "\n" << std::endl; |
|
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
249 |
| 258 | 250 if (!window_proc(proc, win)) { |
| 251 ::xcb_disconnect(connection); | |
| 252 return false; | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 ::xcb_disconnect(connection); | |
| 257 | |
| 258 return true; | |
| 259 } | |
| 260 | |
| 261 } // namespace animone::internal::x11 |
