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