comparison src/sys/x11/settings.cc @ 351:c844f8bb87ce

gui/theme: add xsettings backend this also adds newly-necessary endianness methods in core/endian.h which just so happen to be constexpr as well
author Paper <paper@paper.us.eu.org>
date Sun, 14 Jul 2024 23:23:56 -0400
parents
children 2f094656e775
comparison
equal deleted inserted replaced
350:daa03aa2262d 351:c844f8bb87ce
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 */
96 std::uint8_t byte_order_ = 0; /* unused */
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 /* unused for now... don't know what the values are! :)
127 * assuming host byte order */
128 if (!ReadData<std::uint8_t>(byte_order_))
129 return;
130
131 Advance(3);
132
133 if (!ReadData<std::uint32_t>(serial_))
134 return;
135
136 if (!ReadData<std::uint32_t>(total_items_))
137 return;
138 }
139
140 std::optional<SettingsItem> Parser::ParseNextItem(void) {
141 SettingsItem item;
142
143 /* read one byte */
144 if (!ReadInt<std::uint8_t>(item.type))
145 return std::nullopt;
146
147 if (!item.VerifyType())
148 return std::nullopt;
149
150 /* skip padding */
151 if (!Advance(1))
152 return std::nullopt;
153
154 /* parse the name */
155 std::uint16_t name_size;
156 if (!ReadInt<std::uint16_t>(name_size))
157 return std::nullopt;
158
159 if (!ReadString(item.name, name_size))
160 return std::nullopt;
161
162 /* padding */
163 if (!Advance(GetPadding(name_size, 4)))
164 return std::nullopt;
165
166 if (!ReadInt<std::uint32_t>(item.serial))
167 return std::nullopt;
168
169 switch (item.type) {
170 case SettingsItem::TypeInt: {
171 if (!ReadInt<std::uint32_t>(item.data.integer))
172 return std::nullopt;
173
174 break;
175 }
176 case SettingsItem::TypeStr: {
177 std::uint32_t size;
178 if (!ReadInt<std::uint32_t>(size))
179 return std::nullopt;
180
181 if (!ReadString(item.data.string, size))
182 return std::nullopt;
183
184 /* padding */
185 if (!Advance(GetPadding(size, 4)))
186 return std::nullopt;
187
188 break;
189 }
190 case SettingsItem::TypeRgba: {
191 /* The order here is important!! */
192 if (!ReadInt<std::uint16_t>(item.data.rgba.red)
193 || !ReadInt<std::uint16_t>(item.data.rgba.blue)
194 || !ReadInt<std::uint16_t>(item.data.rgba.green)
195 || !ReadInt<std::uint16_t>(item.data.rgba.alpha))
196 return std::nullopt;
197
198 break;
199 }
200 default:
201 /* can't do anything now, can we? */
202 return std::nullopt;
203 }
204
205 return item;
206 }
207
208 std::vector<SettingsItem> Parser::ParseAllItems(void) {
209 offset_ = 0;
210
211 std::uint32_t i;
212 std::vector<SettingsItem> items;
213
214 for (i = 0; i < total_items_; i++) {
215 std::optional<SettingsItem> item = ParseNextItem();
216 if (!item)
217 break;
218
219 items.push_back(item.value());
220 }
221
222 return items;
223 }
224
225 /* ------------------------------------------------------------------------- */
226 /* real X11 code */
227
228 template<typename T>
229 struct MallocDestructor {
230 void operator()(T *t) const { std::free(t); };
231 };
232
233 struct XcbConnectionDestructor {
234 void operator()(xcb_connection_t *conn) const { ::xcb_disconnect(conn); };
235 };
236
237 template<typename T>
238 using MallocPtr = std::unique_ptr<T, MallocDestructor<T>>;
239
240 using XcbConnectionPtr = std::unique_ptr<xcb_connection_t, XcbConnectionDestructor>;
241
242 /* RAII is nice */
243 struct XcbGrabber {
244 XcbGrabber(::xcb_connection_t *conn) { ::xcb_grab_server(conn); conn_ = conn; }
245 ~XcbGrabber() { ::xcb_ungrab_server(conn_); }
246
247 private:
248 ::xcb_connection_t *conn_;
249 };
250
251 static ::xcb_window_t GetSelectionOwner(::xcb_connection_t *conn, ::xcb_atom_t selection) {
252 ::xcb_window_t owner = XCB_NONE;
253 MallocPtr<::xcb_get_selection_owner_reply_t> reply(::xcb_get_selection_owner_reply(conn, ::xcb_get_selection_owner(conn, selection), nullptr));
254
255 if (reply)
256 owner = reply->owner;
257
258 return owner;
259 }
260
261 static bool GetRawSettingsData(std::vector<uint8_t>& bytes) {
262 int screen;
263
264 XcbConnectionPtr conn(::xcb_connect(nullptr, &screen));
265 if (::xcb_connection_has_error(conn.get()))
266 return false;
267
268 /* get our needed atoms, available as atoms[Atom] */
269 enum Atom {
270 XSETTINGS_SCREEN, /* _XSETTINGS_S[N] */
271 XSETTINGS_SETTINGS, /* _XSETTINGS_SETTINGS */
272 };
273
274 std::map<Atom, ::xcb_atom_t> atoms;
275 {
276 std::map<Atom, std::string> names = {
277 {XSETTINGS_SCREEN, fmt::format("_XSETTINGS_S{}", screen)},
278 {XSETTINGS_SETTINGS, "_XSETTINGS_SETTINGS"},
279 };
280
281 std::map<Atom, ::xcb_intern_atom_cookie_t> atom_cookies;
282 for (const auto& name : names)
283 atom_cookies[name.first] = ::xcb_intern_atom(conn.get(), false, name.second.size(), name.second.data());
284
285 for (const auto& cookie : atom_cookies) {
286 MallocPtr<::xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(conn.get(), cookie.second, nullptr));
287 if (!reply || reply->atom == XCB_NONE)
288 return false;
289
290 atoms[cookie.first] = reply->atom;
291 }
292 }
293
294 MallocPtr<xcb_get_property_reply_t> reply;
295 {
296 /* grab the X server as *required* by xsettings docs */
297 const XcbGrabber grabber(conn.get());
298
299 ::xcb_window_t win = GetSelectionOwner(conn.get(), atoms[XSETTINGS_SCREEN]);
300 if (win == XCB_NONE)
301 return false;
302
303 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));
304 };
305 if (!reply)
306 return false;
307
308 uint8_t *data = reinterpret_cast<uint8_t *>(xcb_get_property_value(reply.get()));
309 int size = xcb_get_property_value_length(reply.get());
310 if (size < 0)
311 return false;
312
313 bytes.assign(data, data + size);
314
315 return true;
316 }
317
318 /* ------------------------------------------------------------------------- */
319 /* now for the actual all-important public API stringing all this together */
320
321 bool GetSettings(std::vector<SettingsItem>& settings) {
322 std::vector<std::uint8_t> xsettings_raw;
323 if (!GetRawSettingsData(xsettings_raw))
324 return false;
325
326 Parser parser(xsettings_raw.data(), xsettings_raw.size());
327 settings = parser.ParseAllItems();
328
329 return true;
330 }
331
332 bool FindSetting(const std::string& name, SettingsItem& setting) {
333 std::vector<std::uint8_t> xsettings_raw;
334 if (!GetRawSettingsData(xsettings_raw))
335 return false;
336
337 Parser parser(xsettings_raw.data(), xsettings_raw.size());
338
339 std::uint32_t total = parser.GetTotalItems();
340
341 for (; total; total--) {
342 std::optional<SettingsItem> opt_item = parser.ParseNextItem();
343 if (!opt_item)
344 return false;
345
346 SettingsItem& item = opt_item.value();
347 if (item.name == name) {
348 setting = item;
349 return true;
350 }
351 }
352
353 return false;
354 }
355
356 } // namespace x11