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 }