Mercurial > minori
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 } |