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