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