comparison dep/animone/src/win/x11.cc @ 337:a7d4e5107531

dep/animone: REFACTOR ALL THE THINGS 1: animone now has its own syntax divergent from anisthesia, making different platforms actually have their own sections 2: process names in animone are now called `comm' (this will probably break things). this is what its called in bsd/linux so I'm just going to use it everywhere 3: the X11 code now checks for the existence of a UTF-8 window title and passes it if available 4: ANYTHING THATS NOT LINUX IS 100% UNTESTED AND CAN AND WILL BREAK! I still actually need to test the bsd code. to be honest I'm probably going to move all of the bsds into separate files because they're all essentially different operating systems at this point
author Paper <paper@paper.us.eu.org>
date Wed, 19 Jun 2024 12:51:15 -0400
parents d260549151d6
children adb79bdde329
comparison
equal deleted inserted replaced
336:d260549151d6 337:a7d4e5107531
11 11
12 #include <xcb/res.h> 12 #include <xcb/res.h>
13 #include <xcb/xcb.h> 13 #include <xcb/xcb.h>
14 14
15 #include <climits> 15 #include <climits>
16 #include <cassert>
16 #include <cstdint> 17 #include <cstdint>
17 #include <cstring> 18 #include <cstring>
18 #include <cstdlib> 19 #include <cstdlib>
19 #include <set> 20 #include <set>
20 #include <string> 21 #include <string>
21 #include <memory> 22 #include <memory>
22 23
23 #include <chrono> 24 #include <chrono>
25 #include <unordered_map>
24 26
25 #include <iostream> 27 #include <iostream>
26 28
27 static size_t str_nlen(const char* s, size_t len) { 29 static size_t str_nlen(const char* s, size_t len) {
28 size_t i = 0; 30 size_t i = 0;
40 }; 42 };
41 43
42 template<typename T> 44 template<typename T>
43 using XcbPtr = std::unique_ptr<T, XcbDestructor<T>>; 45 using XcbPtr = std::unique_ptr<T, XcbDestructor<T>>;
44 46
45 static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, 47 /* --------------------------------------------------------------
48 * atom cruft */
49
50 enum class NeededAtom {
51 /* EWMH */
52 NET_CLIENT_LIST,
53 NET_WM_NAME,
54 UTF8_STRING,
55
56 /* ICCCM */
57 WM_STATE,
58 };
59
60 static const std::unordered_map<NeededAtom, std::string> atom_strings = {
61 {NeededAtom::NET_CLIENT_LIST, "_NET_CLIENT_LIST"},
62 {NeededAtom::NET_WM_NAME, "_NET_WM_NAME"},
63 {NeededAtom::UTF8_STRING, "UTF8_STRING"},
64
65 {NeededAtom::WM_STATE, "WM_STATE"},
66 };
67
68 using XcbAtoms = std::unordered_map<NeededAtom, xcb_atom_t>;
69
70 static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots,
46 std::set<xcb_window_t>& result) { 71 std::set<xcb_window_t>& result) {
47 const xcb_atom_t Atom__NET_CLIENT_LIST = [connection] { 72 if (atoms.at(NeededAtom::NET_CLIENT_LIST) == XCB_ATOM_NONE)
48 static constexpr std::string_view name = "_NET_CLIENT_LIST";
49 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data());
50 XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL));
51
52 xcb_atom_t atom = reply->atom;
53
54 return atom;
55 }();
56 if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE)
57 return false; // BTFO 73 return false; // BTFO
58 74
59 bool success = false; 75 bool success = false;
60 76
61 std::vector<xcb_get_property_cookie_t> cookies; 77 std::vector<xcb_get_property_cookie_t> cookies;
62 cookies.reserve(roots.size()); 78 cookies.reserve(roots.size());
63 79
64 for (const auto& root : roots) 80 for (const auto& root : roots)
65 cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX)); 81 cookies.push_back(::xcb_get_property(connection, 0, root, atoms.at(NeededAtom::NET_CLIENT_LIST), XCB_ATOM_ANY, 0L, UINT_MAX));
66 82
67 for (const auto& cookie : cookies) { 83 for (const auto& cookie : cookies) {
68 XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL)); 84 XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL));
69 85
70 if (reply) { 86 if (reply) {
79 } 95 }
80 96
81 return success; 97 return success;
82 } 98 }
83 99
84 /* This is called on every window. What this does is: 100 /* This should be called with a list of toplevels for each root. */
85 * 1. Gets the tree of children
86 * 2. Searches all children recursively for a WM_STATE property
87 * 3. If that failed... return the original window
88 */
89 static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows, 101 static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows,
90 int windows_len, std::set<xcb_window_t>& result) { 102 int windows_len, std::set<xcb_window_t>& result) {
91 /* The depth we should start returning at. */ 103 /* The level of depth we want to cut off past; since we want to go over each top level window,
92 static constexpr int CUTOFF = 1; 104 * we cut off after we've passed the root window and the toplevel. */
93 105 static constexpr int CUTOFF = 2;
94 bool success = false;
95
96 std::vector<xcb_query_tree_cookie_t> cookies; 106 std::vector<xcb_query_tree_cookie_t> cookies;
97 cookies.reserve(windows_len); 107 cookies.reserve(windows_len);
98 108
99 for (int i = 0; i < windows_len; i++) 109 for (int i = 0; i < windows_len; i++)
100 cookies.push_back(::xcb_query_tree(connection, windows[i])); 110 cookies.push_back(::xcb_query_tree(connection, windows[i]));
101 111
102 for (const auto& cookie : cookies) { 112 for (int i = 0; i < cookies.size(); i++) {
103 XcbPtr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL)); 113 /* XXX is it *really* okay to ask xcb for a cookie and then never ask for a reply?
114 * valgrind doesn't complain, so I'm not gonna care for now. */
115 XcbPtr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookies[i], NULL));
104 116
105 xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get()); 117 xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get());
106 int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get()); 118 int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get());
107 119
108 std::vector<xcb_get_property_cookie_t> state_property_cookies; 120 /* search for any window with a WM_STATE property */
109 state_property_cookies.reserve(tree_children_len); 121 std::vector<xcb_get_property_cookie_t> state_cookies;
122 state_cookies.reserve(tree_children_len);
110 123
111 for (int i = 0; i < tree_children_len; i++) 124 for (int i = 0; i < tree_children_len; i++)
112 state_property_cookies.push_back( 125 state_cookies.push_back(
113 ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); 126 ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 4L));
127
128 bool found = false;
114 129
115 for (int i = 0; i < tree_children_len; i++) { 130 for (int i = 0; i < tree_children_len; i++) {
116 XcbPtr<xcb_get_property_reply_t> get_property_reply(::xcb_get_property_reply(connection, state_property_cookies[i], NULL)); 131 XcbPtr<xcb_get_property_reply_t> get_property_reply(::xcb_get_property_reply(connection, state_cookies[i], NULL));
117 132 if (!get_property_reply)
118 /* X11 is unfriendly here. what this means is "did the property exist?" */ 133 continue;
119 if (get_property_reply->format || get_property_reply->type || get_property_reply->length) { 134
135 /* did we get valid data? */
136 if (get_property_reply->type == Atom_WM_STATE || get_property_reply->format != 0 || get_property_reply->bytes_after != 0) {
137 int len = ::xcb_get_property_value_length(get_property_reply.get());
138 if (len < sizeof(uint32_t))
139 continue;
140
141 uint32_t state = *reinterpret_cast<uint32_t*>(::xcb_get_property_value(get_property_reply.get()));
142 if (state != 1) // NormalState
143 continue;
144
120 result.insert(tree_children[i]); 145 result.insert(tree_children[i]);
146 found = true;
121 if (depth >= CUTOFF) 147 if (depth >= CUTOFF)
122 return true; 148 return true;
123
124 success |= true;
125 continue;
126 } 149 }
127 } 150 }
128 151
129 if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) { 152 if (found)
130 success |= true;
131 if (depth >= CUTOFF)
132 return true;
133 continue; 153 continue;
134 } 154
135 } 155 bool res = WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result);
136 156
137 return success; 157 if (depth >= CUTOFF)
138 } 158 return res;
139 159 }
140 static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, 160
161 return true;
162 }
163
164 static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots,
141 std::set<xcb_window_t>& result) { 165 std::set<xcb_window_t>& result) {
142 bool success = false; 166 if (atoms.at(NeededAtom::WM_STATE) == XCB_ATOM_NONE)
143 167 return false;
144 xcb_atom_t Atom_WM_STATE = [connection] { 168
145 static constexpr std::string_view name = "WM_STATE"; 169 return WalkWindows(connection, 0, atoms.at(NeededAtom::WM_STATE), roots.data(), roots.size(), result);
146 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); 170 }
171
172 static XcbAtoms InitializeAtoms(xcb_connection_t* connection) {
173 XcbAtoms atoms;
174
175 std::unordered_map<NeededAtom, xcb_intern_atom_cookie_t> atom_cookies;
176
177 for (const auto& [atom, str] : atom_strings)
178 atom_cookies[atom] = ::xcb_intern_atom(connection, 1, str.size(), str.data());
179
180 for (const auto& [atom, cookie] : atom_cookies) {
147 XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); 181 XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL));
148 182 if (!reply)
149 xcb_atom_t atom = reply->atom; 183 atoms[atom] = XCB_ATOM_NONE;
150 184
151 return atom; 185 atoms[atom] = reply->atom;
152 }(); 186 }
153 if (Atom_WM_STATE == XCB_ATOM_NONE) 187
154 return success; 188 return atoms;
155
156 std::vector<xcb_query_tree_cookie_t> cookies;
157 cookies.reserve(roots.size());
158
159 for (const auto& root : roots)
160 cookies.push_back(::xcb_query_tree(connection, root));
161
162 for (const auto& cookie : cookies)
163 success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result);
164
165 return success;
166 } 189 }
167 190
168 bool EnumerateWindows(window_proc_t window_proc) { 191 bool EnumerateWindows(window_proc_t window_proc) {
169 if (!window_proc) 192 if (!window_proc)
170 return false; 193 return false;
171 194
172 xcb_connection_t* connection = ::xcb_connect(NULL, NULL); 195 xcb_connection_t* connection = ::xcb_connect(NULL, NULL);
173 if (xcb_connection_has_error(connection)) 196 if (xcb_connection_has_error(connection))
174 return false; 197 return false;
198
199 XcbAtoms atoms = InitializeAtoms(connection);
175 200
176 std::set<xcb_window_t> windows; 201 std::set<xcb_window_t> windows;
177 { 202 {
178 std::vector<xcb_window_t> roots; 203 std::vector<xcb_window_t> roots;
179 { 204 {
180 xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection)); 205 xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection));
181 for (; iter.rem; ::xcb_screen_next(&iter)) 206 for (; iter.rem; ::xcb_screen_next(&iter))
182 roots.push_back(iter.data->root); 207 roots.push_back(iter.data->root);
183 } 208 }
184 209
185 if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) 210 if (!GetAllTopLevelWindowsEWMH(connection, atoms, roots, windows))
186 GetAllTopLevelWindowsICCCM(connection, roots, windows); 211 GetAllTopLevelWindowsICCCM(connection, atoms, roots, windows);
187 } 212 }
188 213
189 struct WindowCookies { 214 struct WindowCookies {
190 xcb_window_t window; 215 xcb_window_t window;
191 xcb_get_property_cookie_t class_property_cookie; 216 xcb_get_property_cookie_t class_name;
192 xcb_get_property_cookie_t name_property_cookie; 217 xcb_get_property_cookie_t name_utf8;
193 xcb_res_query_client_ids_cookie_t pid_property_cookie; 218 xcb_get_property_cookie_t name;
219 xcb_res_query_client_ids_cookie_t pid;
194 }; 220 };
195 221
196 std::vector<WindowCookies> window_cookies; 222 std::vector<WindowCookies> window_cookies;
197 window_cookies.reserve(windows.size()); 223 window_cookies.reserve(windows.size());
198 224
199 for (const auto& window : windows) { 225 for (const auto& window : windows) {
200 xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID}; 226 xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID};
201 227
202 WindowCookies window_cookie = { 228 WindowCookies window_cookie = {
203 window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L), 229 .window = window,
204 ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX), 230 .class_name = ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L),
205 ::xcb_res_query_client_ids(connection, 1, &spec)}; 231 .name_utf8 = ::xcb_get_property(connection, 0, window, atoms[NeededAtom::NET_WM_NAME], atoms[NeededAtom::UTF8_STRING], 0L, UINT_MAX),
232 .name = ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX),
233 .pid = ::xcb_res_query_client_ids(connection, 1, &spec),
234 };
206 235
207 window_cookies.push_back(window_cookie); 236 window_cookies.push_back(window_cookie);
208 } 237 }
209 238
210 for (const auto& window_cookie : window_cookies) { 239 for (const auto& window_cookie : window_cookies) {
211 Window win = {0}; 240 Window win;
212 win.id = window_cookie.window; 241 win.id = window_cookie.window;
213 { 242 {
214 /* Class name */ 243 /* Class name */
215 XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL)); 244 XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.class_name, NULL));
216 245
217 if (reply && reply->format == 8) { 246 if (reply && reply->format == 8) {
218 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); 247 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get()));
219 const int data_len = ::xcb_get_property_value_length(reply.get()); 248 const int data_len = ::xcb_get_property_value_length(reply.get());
220 249
224 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1))); 253 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1)));
225 } 254 }
226 } 255 }
227 { 256 {
228 /* Title text */ 257 /* Title text */
229 XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL)); 258 XcbPtr<xcb_get_property_reply_t> reply_utf8(::xcb_get_property_reply(connection, window_cookie.name_utf8, NULL));
230 259 XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.name, NULL));
231 if (reply) { 260 int utf8_len = ::xcb_get_property_value_length(reply_utf8.get());
261 int len = ::xcb_get_property_value_length(reply.get());
262
263 if (reply_utf8 && utf8_len > 0) {
264 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply_utf8.get()));
265
266 win.text = std::string(data, utf8_len);
267 } else if (reply && len > 0) {
232 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); 268 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get()));
233 int len = ::xcb_get_property_value_length(reply.get());
234 269
235 win.text = std::string(data, len); 270 win.text = std::string(data, len);
236 } 271 }
237 } 272 }
238 Process proc = {0}; 273 Process proc;
274 proc.platform = ExecutablePlatform::Posix; // not entirely correct, but whatever. who cares
239 { 275 {
240 /* PID */ 276 /* PID */
241 XcbPtr<xcb_res_query_client_ids_reply_t> reply(::xcb_res_query_client_ids_reply(connection, window_cookie.pid_property_cookie, NULL)); 277 XcbPtr<xcb_res_query_client_ids_reply_t> reply(::xcb_res_query_client_ids_reply(connection, window_cookie.pid, NULL));
242 278
243 if (reply) { 279 if (reply) {
244 xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get()); 280 xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get());
245 for (; it.rem; ::xcb_res_client_id_value_next(&it)) { 281 for (; it.rem; ::xcb_res_client_id_value_next(&it)) {
246 if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { 282 if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
247 proc.pid = *::xcb_res_client_id_value_value(it.data); 283 proc.pid = *::xcb_res_client_id_value_value(it.data);
248 GetProcessName(proc.pid, proc.name); /* fill this in if we can */ 284 GetProcessName(proc.pid, proc.comm); /* fill this in if we can */
249 break; 285 break;
250 } 286 }
251 } 287 }
252 } 288 }
253 } 289 }
254 290
291 /* debug printing
292 std::cout << "window found: " << std::hex << win.id << std::dec << "\n"
293 << " name: " << win.text << "\n"
294 << " class: " << win.class_name << "\n"
295 << " pid: " << proc.pid << "\n"
296 << " comm: " << proc.name << std::endl;
297 */
298
255 if (!window_proc(proc, win)) { 299 if (!window_proc(proc, win)) {
256 ::xcb_disconnect(connection); 300 ::xcb_disconnect(connection);
257 return false; 301 return false;
258 } 302 }
259 } 303 }