Mercurial > minori
annotate src/sys/x11/settings.cc @ 353:2f094656e775
sys/x11/settings: misc fixups
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Sun, 14 Jul 2024 23:27:43 -0400 |
parents | c844f8bb87ce |
children | 9aaf1e788896 |
rev | line source |
---|---|
351 | 1 #include "sys/x11/settings.h" |
2 #include "core/endian.h" | |
3 | |
4 #include <cstring> | |
5 #include <cstdint> | |
6 #include <climits> | |
7 #include <string_view> | |
8 #include <memory> | |
9 #include <array> | |
10 #include <optional> | |
11 #include <iostream> | |
12 #include <map> | |
13 | |
14 #include <xcb/xcb.h> | |
15 | |
16 #include "fmt/core.h" | |
17 | |
18 namespace x11 { | |
19 | |
20 bool SettingsItem::VerifyType() { | |
21 switch (type) { | |
22 case SettingsItem::TypeInt: | |
23 case SettingsItem::TypeStr: | |
24 case SettingsItem::TypeRgba: | |
25 return true; | |
26 default: | |
27 return false; | |
28 } | |
29 } | |
30 | |
31 /* -------------------------------------------------------------------------- */ | |
32 /* xsettings parser */ | |
33 | |
34 static constexpr std::size_t GetPadding(std::size_t length, std::size_t increment) { | |
35 /* ripped from xsettingsd */ | |
36 return (increment - (length % increment)) % increment; | |
37 } | |
38 | |
39 class Parser { | |
40 public: | |
41 Parser(std::uint8_t *bytes, std::size_t size); | |
42 | |
43 std::vector<SettingsItem> ParseAllItems(void); | |
44 std::optional<SettingsItem> ParseNextItem(void); | |
45 | |
46 std::uint32_t GetTotalItems(void); | |
47 | |
48 private: | |
49 /* byte order values */ | |
50 enum { | |
51 LSBFirst = 0, | |
52 MSBFirst = 1, | |
53 }; | |
54 | |
55 template<typename T> | |
56 bool ReadData(T& ret) { | |
57 if (offset_ + sizeof(T) >= size_) | |
58 return false; | |
59 | |
60 ret = *reinterpret_cast<T*>(bytes_ + offset_); | |
61 Advance(sizeof(T)); | |
62 return true; | |
63 } | |
64 | |
65 template<typename T> | |
66 bool ReadInt(T& ret) { | |
67 static_assert(std::is_integral<T>::value); | |
68 | |
69 if (!ReadData<T>(ret)) | |
70 return false; | |
71 | |
72 switch (byte_order_) { | |
73 case LSBFirst: | |
74 ret = Endian::byteswap_little_to_host(ret); | |
75 break; | |
76 case MSBFirst: | |
77 ret = Endian::byteswap_big_to_host(ret); | |
78 break; | |
79 default: | |
80 /* can't know for sure. punt */ | |
81 return false; | |
82 } | |
83 | |
84 return true; | |
85 } | |
86 | |
87 bool ReadString(std::string& str, std::size_t size); | |
88 bool Advance(std::size_t amount); | |
89 | |
90 /* raw data */ | |
91 std::uint8_t *bytes_ = nullptr; | |
92 std::size_t offset_ = 0; | |
93 std::size_t size_ = 0; | |
94 | |
95 /* parsed in the constructor */ | |
353
2f094656e775
sys/x11/settings: misc fixups
Paper <paper@paper.us.eu.org>
parents:
351
diff
changeset
|
96 std::uint8_t byte_order_ = 0; |
351 | 97 std::uint32_t serial_ = 0; |
98 std::uint32_t total_items_ = 0; | |
99 }; | |
100 | |
101 std::uint32_t Parser::GetTotalItems(void) { | |
102 return total_items_; | |
103 } | |
104 | |
105 bool Parser::ReadString(std::string& str, std::size_t size) { | |
106 if (offset_ + size >= size_) | |
107 return false; | |
108 | |
109 str.assign(reinterpret_cast<const char *>(bytes_ + offset_), size); | |
110 Advance(size); | |
111 return true; | |
112 } | |
113 | |
114 bool Parser::Advance(std::size_t amount) { | |
115 if (offset_ + amount >= size_) | |
116 return false; | |
117 | |
118 offset_ += amount; | |
119 return true; | |
120 } | |
121 | |
122 Parser::Parser(std::uint8_t *bytes, std::size_t size) { | |
123 bytes_ = bytes; | |
124 size_ = size; | |
125 | |
126 if (!ReadData<std::uint8_t>(byte_order_)) | |
127 return; | |
128 | |
129 Advance(3); | |
130 | |
353
2f094656e775
sys/x11/settings: misc fixups
Paper <paper@paper.us.eu.org>
parents:
351
diff
changeset
|
131 if (!ReadInt<std::uint32_t>(serial_)) |
351 | 132 return; |
133 | |
353
2f094656e775
sys/x11/settings: misc fixups
Paper <paper@paper.us.eu.org>
parents:
351
diff
changeset
|
134 if (!ReadInt<std::uint32_t>(total_items_)) |
351 | 135 return; |
136 } | |
137 | |
138 std::optional<SettingsItem> Parser::ParseNextItem(void) { | |
139 SettingsItem item; | |
140 | |
141 /* read one byte */ | |
142 if (!ReadInt<std::uint8_t>(item.type)) | |
143 return std::nullopt; | |
144 | |
145 if (!item.VerifyType()) | |
146 return std::nullopt; | |
147 | |
148 /* skip padding */ | |
149 if (!Advance(1)) | |
150 return std::nullopt; | |
151 | |
152 /* parse the name */ | |
153 std::uint16_t name_size; | |
154 if (!ReadInt<std::uint16_t>(name_size)) | |
155 return std::nullopt; | |
156 | |
157 if (!ReadString(item.name, name_size)) | |
158 return std::nullopt; | |
159 | |
160 /* padding */ | |
161 if (!Advance(GetPadding(name_size, 4))) | |
162 return std::nullopt; | |
163 | |
164 if (!ReadInt<std::uint32_t>(item.serial)) | |
165 return std::nullopt; | |
166 | |
167 switch (item.type) { | |
168 case SettingsItem::TypeInt: { | |
169 if (!ReadInt<std::uint32_t>(item.data.integer)) | |
170 return std::nullopt; | |
171 | |
172 break; | |
173 } | |
174 case SettingsItem::TypeStr: { | |
175 std::uint32_t size; | |
176 if (!ReadInt<std::uint32_t>(size)) | |
177 return std::nullopt; | |
178 | |
179 if (!ReadString(item.data.string, size)) | |
180 return std::nullopt; | |
181 | |
182 /* padding */ | |
183 if (!Advance(GetPadding(size, 4))) | |
184 return std::nullopt; | |
185 | |
186 break; | |
187 } | |
188 case SettingsItem::TypeRgba: { | |
189 /* The order here is important!! */ | |
190 if (!ReadInt<std::uint16_t>(item.data.rgba.red) | |
191 || !ReadInt<std::uint16_t>(item.data.rgba.blue) | |
192 || !ReadInt<std::uint16_t>(item.data.rgba.green) | |
193 || !ReadInt<std::uint16_t>(item.data.rgba.alpha)) | |
194 return std::nullopt; | |
195 | |
196 break; | |
197 } | |
198 default: | |
199 /* can't do anything now, can we? */ | |
200 return std::nullopt; | |
201 } | |
202 | |
203 return item; | |
204 } | |
205 | |
206 std::vector<SettingsItem> Parser::ParseAllItems(void) { | |
207 offset_ = 0; | |
208 | |
209 std::uint32_t i; | |
210 std::vector<SettingsItem> items; | |
211 | |
212 for (i = 0; i < total_items_; i++) { | |
213 std::optional<SettingsItem> item = ParseNextItem(); | |
214 if (!item) | |
215 break; | |
216 | |
217 items.push_back(item.value()); | |
218 } | |
219 | |
220 return items; | |
221 } | |
222 | |
223 /* ------------------------------------------------------------------------- */ | |
224 /* real X11 code */ | |
225 | |
226 template<typename T> | |
227 struct MallocDestructor { | |
228 void operator()(T *t) const { std::free(t); }; | |
229 }; | |
230 | |
231 struct XcbConnectionDestructor { | |
232 void operator()(xcb_connection_t *conn) const { ::xcb_disconnect(conn); }; | |
233 }; | |
234 | |
235 template<typename T> | |
236 using MallocPtr = std::unique_ptr<T, MallocDestructor<T>>; | |
237 | |
238 using XcbConnectionPtr = std::unique_ptr<xcb_connection_t, XcbConnectionDestructor>; | |
239 | |
240 /* RAII is nice */ | |
241 struct XcbGrabber { | |
242 XcbGrabber(::xcb_connection_t *conn) { ::xcb_grab_server(conn); conn_ = conn; } | |
243 ~XcbGrabber() { ::xcb_ungrab_server(conn_); } | |
244 | |
245 private: | |
246 ::xcb_connection_t *conn_; | |
247 }; | |
248 | |
249 static ::xcb_window_t GetSelectionOwner(::xcb_connection_t *conn, ::xcb_atom_t selection) { | |
250 ::xcb_window_t owner = XCB_NONE; | |
251 MallocPtr<::xcb_get_selection_owner_reply_t> reply(::xcb_get_selection_owner_reply(conn, ::xcb_get_selection_owner(conn, selection), nullptr)); | |
252 | |
253 if (reply) | |
254 owner = reply->owner; | |
255 | |
256 return owner; | |
257 } | |
258 | |
259 static bool GetRawSettingsData(std::vector<uint8_t>& bytes) { | |
260 int screen; | |
261 | |
262 XcbConnectionPtr conn(::xcb_connect(nullptr, &screen)); | |
263 if (::xcb_connection_has_error(conn.get())) | |
264 return false; | |
265 | |
266 /* get our needed atoms, available as atoms[Atom] */ | |
267 enum Atom { | |
268 XSETTINGS_SCREEN, /* _XSETTINGS_S[N] */ | |
269 XSETTINGS_SETTINGS, /* _XSETTINGS_SETTINGS */ | |
270 }; | |
271 | |
272 std::map<Atom, ::xcb_atom_t> atoms; | |
273 { | |
274 std::map<Atom, std::string> names = { | |
275 {XSETTINGS_SCREEN, fmt::format("_XSETTINGS_S{}", screen)}, | |
276 {XSETTINGS_SETTINGS, "_XSETTINGS_SETTINGS"}, | |
277 }; | |
278 | |
279 std::map<Atom, ::xcb_intern_atom_cookie_t> atom_cookies; | |
280 for (const auto& name : names) | |
281 atom_cookies[name.first] = ::xcb_intern_atom(conn.get(), false, name.second.size(), name.second.data()); | |
282 | |
283 for (const auto& cookie : atom_cookies) { | |
284 MallocPtr<::xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(conn.get(), cookie.second, nullptr)); | |
285 if (!reply || reply->atom == XCB_NONE) | |
286 return false; | |
287 | |
288 atoms[cookie.first] = reply->atom; | |
289 } | |
290 } | |
291 | |
292 MallocPtr<xcb_get_property_reply_t> reply; | |
293 { | |
294 /* grab the X server as *required* by xsettings docs */ | |
295 const XcbGrabber grabber(conn.get()); | |
296 | |
297 ::xcb_window_t win = GetSelectionOwner(conn.get(), atoms[XSETTINGS_SCREEN]); | |
298 if (win == XCB_NONE) | |
299 return false; | |
300 | |
301 reply.reset(::xcb_get_property_reply(conn.get(), ::xcb_get_property(conn.get(), 0, win, atoms[XSETTINGS_SETTINGS], XCB_ATOM_ANY, 0L, UINT_MAX), nullptr)); | |
302 }; | |
303 if (!reply) | |
304 return false; | |
305 | |
306 uint8_t *data = reinterpret_cast<uint8_t *>(xcb_get_property_value(reply.get())); | |
307 int size = xcb_get_property_value_length(reply.get()); | |
308 if (size < 0) | |
309 return false; | |
310 | |
311 bytes.assign(data, data + size); | |
312 | |
313 return true; | |
314 } | |
315 | |
316 /* ------------------------------------------------------------------------- */ | |
317 /* now for the actual all-important public API stringing all this together */ | |
318 | |
319 bool GetSettings(std::vector<SettingsItem>& settings) { | |
320 std::vector<std::uint8_t> xsettings_raw; | |
321 if (!GetRawSettingsData(xsettings_raw)) | |
322 return false; | |
323 | |
324 Parser parser(xsettings_raw.data(), xsettings_raw.size()); | |
325 settings = parser.ParseAllItems(); | |
326 | |
327 return true; | |
328 } | |
329 | |
330 bool FindSetting(const std::string& name, SettingsItem& setting) { | |
331 std::vector<std::uint8_t> xsettings_raw; | |
332 if (!GetRawSettingsData(xsettings_raw)) | |
333 return false; | |
334 | |
335 Parser parser(xsettings_raw.data(), xsettings_raw.size()); | |
336 | |
337 std::uint32_t total = parser.GetTotalItems(); | |
338 | |
339 for (; total; total--) { | |
340 std::optional<SettingsItem> opt_item = parser.ParseNextItem(); | |
341 if (!opt_item) | |
342 return false; | |
343 | |
344 SettingsItem& item = opt_item.value(); | |
345 if (item.name == name) { | |
346 setting = item; | |
347 return true; | |
348 } | |
349 } | |
350 | |
351 return false; | |
352 } | |
353 | |
354 } // namespace x11 |