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++; |