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 */
|
|
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
|