Mercurial > minori
annotate dep/animone/src/win/x11.cc @ 283:969a3e8c79c5
dep/animone/x11: remove stupid debugging code
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Wed, 08 May 2024 16:20:37 -0400 |
parents | c41c14ff8c67 |
children | b1f625b0227c |
rev | line source |
---|---|
258 | 1 #include "animone/win/x11.h" |
2 #include "animone.h" | |
3 #include "animone/fd.h" /* GetProcessName() */ | |
4 #include "animone/win.h" | |
5 | |
6 #include <xcb/res.h> | |
7 #include <xcb/xcb.h> | |
8 | |
9 #include <climits> | |
10 #include <cstdint> | |
11 #include <cstring> | |
12 #include <set> | |
13 #include <string> | |
277
a796e97cc86d
dep/animone: x11: correctly check for connection failure
Paper <paper@paper.us.eu.org>
parents:
266
diff
changeset
|
14 #include <memory> |
258 | 15 |
16 #include <chrono> | |
17 | |
18 #include <iostream> | |
19 | |
20 /* This uses XCB (and it uses it *right*), so it should be plenty fast */ | |
21 | |
22 static size_t str_nlen(const char* s, size_t len) { | |
23 size_t i = 0; | |
24 for (; i < len && s[i]; i++) | |
25 ; | |
26 return i; | |
27 } | |
28 | |
29 namespace animone::internal::x11 { | |
30 | |
31 static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, | |
32 std::set<xcb_window_t>& result) { | |
33 const xcb_atom_t Atom__NET_CLIENT_LIST = [connection] { | |
34 static constexpr std::string_view name = "_NET_CLIENT_LIST"; | |
35 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); | |
36 std::unique_ptr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); | |
37 | |
38 xcb_atom_t atom = reply->atom; | |
39 | |
40 return atom; | |
41 }(); | |
42 if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE) | |
43 return false; // BTFO | |
44 | |
45 bool success = false; | |
46 | |
47 std::vector<xcb_get_property_cookie_t> cookies; | |
48 cookies.reserve(roots.size()); | |
49 | |
50 for (const auto& root : roots) | |
51 cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX)); | |
52 | |
53 for (const auto& cookie : cookies) { | |
54 std::unique_ptr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL)); | |
55 | |
56 if (reply) { | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
57 xcb_window_t* value = reinterpret_cast<xcb_window_t*>(::xcb_get_property_value(reply.get())); |
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
58 int len = ::xcb_get_property_value_length(reply.get()); |
258 | 59 |
60 for (size_t i = 0; i < len; i++) | |
61 result.insert(value[i]); | |
62 | |
63 success |= true; | |
64 } | |
65 } | |
66 | |
67 return success; | |
68 } | |
69 | |
70 /* This is called on every window. What this does is: | |
71 * 1. Gets the tree of children | |
72 * 2. Searches all children recursively for a WM_STATE property | |
73 * 3. If that failed... return the original window | |
74 */ | |
75 static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows, | |
76 int windows_len, std::set<xcb_window_t>& result) { | |
77 /* The depth we should start returning at. */ | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
78 static constexpr int CUTOFF = 1; |
258 | 79 |
80 bool success = false; | |
81 | |
82 std::vector<xcb_query_tree_cookie_t> cookies; | |
83 cookies.reserve(windows_len); | |
84 | |
85 for (int i = 0; i < windows_len; i++) | |
86 cookies.push_back(::xcb_query_tree(connection, windows[i])); | |
87 | |
88 for (const auto& cookie : cookies) { | |
89 std::unique_ptr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL)); | |
90 | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
91 xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get()); |
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
92 int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get()); |
258 | 93 |
94 std::vector<xcb_get_property_cookie_t> state_property_cookies; | |
95 state_property_cookies.reserve(tree_children_len); | |
96 | |
97 for (int i = 0; i < tree_children_len; i++) | |
98 state_property_cookies.push_back( | |
99 ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); | |
100 | |
101 for (int i = 0; i < tree_children_len; i++) { | |
102 std::unique_ptr<xcb_get_property_reply_t> get_property_reply( | |
103 ::xcb_get_property_reply(connection, state_property_cookies[i], NULL)); | |
104 | |
105 /* X11 is unfriendly here. what this means is "did the property exist?" */ | |
106 if (get_property_reply->format || get_property_reply->type || get_property_reply->length) { | |
107 result.insert(tree_children[i]); | |
108 if (depth >= CUTOFF) | |
109 return true; | |
110 | |
111 success |= true; | |
112 continue; | |
113 } | |
114 } | |
115 | |
116 if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) { | |
117 success |= true; | |
118 if (depth >= CUTOFF) | |
119 return true; | |
120 continue; | |
121 } | |
122 } | |
123 | |
124 return success; | |
125 } | |
126 | |
127 static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, | |
128 std::set<xcb_window_t>& result) { | |
129 bool success = false; | |
130 | |
131 xcb_atom_t Atom_WM_STATE = [connection] { | |
132 static constexpr std::string_view name = "WM_STATE"; | |
133 xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); | |
134 xcb_intern_atom_reply_t* reply = ::xcb_intern_atom_reply(connection, cookie, NULL); | |
135 | |
136 xcb_atom_t atom = reply->atom; | |
137 free(reply); | |
138 return atom; | |
139 }(); | |
140 if (Atom_WM_STATE == XCB_ATOM_NONE) | |
141 return success; | |
142 | |
143 std::vector<xcb_query_tree_cookie_t> cookies; | |
144 cookies.reserve(roots.size()); | |
145 | |
146 for (const auto& root : roots) | |
147 cookies.push_back(::xcb_query_tree(connection, root)); | |
148 | |
149 for (const auto& cookie : cookies) | |
150 success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result); | |
151 | |
152 return success; | |
153 } | |
154 | |
155 bool EnumerateWindows(window_proc_t window_proc) { | |
156 if (!window_proc) | |
157 return false; | |
158 | |
159 xcb_connection_t* connection = ::xcb_connect(NULL, NULL); | |
277
a796e97cc86d
dep/animone: x11: correctly check for connection failure
Paper <paper@paper.us.eu.org>
parents:
266
diff
changeset
|
160 if (xcb_connection_has_error(connection)) |
258 | 161 return false; |
162 | |
163 std::set<xcb_window_t> windows; | |
164 { | |
165 std::vector<xcb_window_t> roots; | |
166 { | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
167 xcb_screen_iterator_t iter = ::xcb_setup_roots_iterator(::xcb_get_setup(connection)); |
258 | 168 for (; iter.rem; ::xcb_screen_next(&iter)) |
169 roots.push_back(iter.data->root); | |
170 } | |
171 | |
278
c41c14ff8c67
dep/animone: x11: remove debugging comment
Paper <paper@paper.us.eu.org>
parents:
277
diff
changeset
|
172 if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) |
258 | 173 GetAllTopLevelWindowsICCCM(connection, roots, windows); |
174 } | |
175 | |
176 struct WindowCookies { | |
177 xcb_window_t window; | |
178 xcb_get_property_cookie_t class_property_cookie; | |
179 xcb_get_property_cookie_t name_property_cookie; | |
180 xcb_res_query_client_ids_cookie_t pid_property_cookie; | |
181 }; | |
182 | |
183 std::vector<WindowCookies> window_cookies; | |
184 window_cookies.reserve(windows.size()); | |
185 | |
186 for (const auto& window : windows) { | |
187 xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID}; | |
188 | |
189 WindowCookies window_cookie = { | |
190 window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L), | |
191 ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX), | |
192 ::xcb_res_query_client_ids(connection, 1, &spec)}; | |
193 | |
194 window_cookies.push_back(window_cookie); | |
195 } | |
196 | |
197 for (const auto& window_cookie : window_cookies) { | |
198 Window win = {0}; | |
199 win.id = window_cookie.window; | |
200 { | |
201 /* Class name */ | |
202 std::unique_ptr<xcb_get_property_reply_t> reply( | |
203 ::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL)); | |
204 | |
205 if (reply && reply->format == 8) { | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
206 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); |
258 | 207 const int data_len = ::xcb_get_property_value_length(reply.get()); |
208 | |
209 int instance_len = str_nlen(data, data_len); | |
210 const char* class_name = data + instance_len + 1; | |
211 | |
212 win.class_name = std::string(class_name, str_nlen(class_name, data_len - (instance_len + 1))); | |
213 } | |
214 } | |
215 { | |
216 /* Title text */ | |
217 std::unique_ptr<xcb_get_property_reply_t> reply( | |
218 ::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL)); | |
219 | |
220 if (reply) { | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
221 const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); |
258 | 222 int len = ::xcb_get_property_value_length(reply.get()); |
223 | |
224 win.text = std::string(data, len); | |
225 } | |
226 } | |
227 Process proc = {0}; | |
228 { | |
229 /* PID */ | |
230 std::unique_ptr<xcb_res_query_client_ids_reply_t> reply( | |
231 ::xcb_res_query_client_ids_reply(connection, window_cookie.pid_property_cookie, NULL)); | |
232 | |
233 if (reply) { | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
234 xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get()); |
258 | 235 for (; it.rem; ::xcb_res_client_id_value_next(&it)) { |
236 if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { | |
266
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
237 proc.pid = *::xcb_res_client_id_value_value(it.data); |
1a6a5d3a94cd
dep/animone: make bsd.cc and x11.cc actually work
Paper <paper@paper.us.eu.org>
parents:
258
diff
changeset
|
238 GetProcessName(proc.pid, proc.name); /* fill this in if we can */ |
258 | 239 break; |
240 } | |
241 } | |
242 } | |
243 } | |
244 | |
245 if (!window_proc(proc, win)) { | |
246 ::xcb_disconnect(connection); | |
247 return false; | |
248 } | |
249 } | |
250 | |
251 ::xcb_disconnect(connection); | |
252 | |
253 return true; | |
254 } | |
255 | |
256 } // namespace animone::internal::x11 |