comparison dep/animia/src/win/x11.cc @ 235:593108b3d555

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