Mercurial > minori
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sys/x11/settings.cc Sun Jul 14 23:23:56 2024 -0400 @@ -0,0 +1,356 @@ +#include "sys/x11/settings.h" +#include "core/endian.h" + +#include <cstring> +#include <cstdint> +#include <climits> +#include <string_view> +#include <memory> +#include <array> +#include <optional> +#include <iostream> +#include <map> + +#include <xcb/xcb.h> + +#include "fmt/core.h" + +namespace x11 { + +bool SettingsItem::VerifyType() { + switch (type) { + case SettingsItem::TypeInt: + case SettingsItem::TypeStr: + case SettingsItem::TypeRgba: + return true; + default: + return false; + } +} + +/* -------------------------------------------------------------------------- */ +/* xsettings parser */ + +static constexpr std::size_t GetPadding(std::size_t length, std::size_t increment) { + /* ripped from xsettingsd */ + return (increment - (length % increment)) % increment; +} + +class Parser { +public: + Parser(std::uint8_t *bytes, std::size_t size); + + std::vector<SettingsItem> ParseAllItems(void); + std::optional<SettingsItem> ParseNextItem(void); + + std::uint32_t GetTotalItems(void); + +private: + /* byte order values */ + enum { + LSBFirst = 0, + MSBFirst = 1, + }; + + template<typename T> + bool ReadData(T& ret) { + if (offset_ + sizeof(T) >= size_) + return false; + + ret = *reinterpret_cast<T*>(bytes_ + offset_); + Advance(sizeof(T)); + return true; + } + + template<typename T> + bool ReadInt(T& ret) { + static_assert(std::is_integral<T>::value); + + if (!ReadData<T>(ret)) + return false; + + switch (byte_order_) { + case LSBFirst: + ret = Endian::byteswap_little_to_host(ret); + break; + case MSBFirst: + ret = Endian::byteswap_big_to_host(ret); + break; + default: + /* can't know for sure. punt */ + return false; + } + + return true; + } + + bool ReadString(std::string& str, std::size_t size); + bool Advance(std::size_t amount); + + /* raw data */ + std::uint8_t *bytes_ = nullptr; + std::size_t offset_ = 0; + std::size_t size_ = 0; + + /* parsed in the constructor */ + std::uint8_t byte_order_ = 0; /* unused */ + std::uint32_t serial_ = 0; + std::uint32_t total_items_ = 0; +}; + +std::uint32_t Parser::GetTotalItems(void) { + return total_items_; +} + +bool Parser::ReadString(std::string& str, std::size_t size) { + if (offset_ + size >= size_) + return false; + + str.assign(reinterpret_cast<const char *>(bytes_ + offset_), size); + Advance(size); + return true; +} + +bool Parser::Advance(std::size_t amount) { + if (offset_ + amount >= size_) + return false; + + offset_ += amount; + return true; +} + +Parser::Parser(std::uint8_t *bytes, std::size_t size) { + bytes_ = bytes; + size_ = size; + + /* unused for now... don't know what the values are! :) + * assuming host byte order */ + if (!ReadData<std::uint8_t>(byte_order_)) + return; + + Advance(3); + + if (!ReadData<std::uint32_t>(serial_)) + return; + + if (!ReadData<std::uint32_t>(total_items_)) + return; +} + +std::optional<SettingsItem> Parser::ParseNextItem(void) { + SettingsItem item; + + /* read one byte */ + if (!ReadInt<std::uint8_t>(item.type)) + return std::nullopt; + + if (!item.VerifyType()) + return std::nullopt; + + /* skip padding */ + if (!Advance(1)) + return std::nullopt; + + /* parse the name */ + std::uint16_t name_size; + if (!ReadInt<std::uint16_t>(name_size)) + return std::nullopt; + + if (!ReadString(item.name, name_size)) + return std::nullopt; + + /* padding */ + if (!Advance(GetPadding(name_size, 4))) + return std::nullopt; + + if (!ReadInt<std::uint32_t>(item.serial)) + return std::nullopt; + + switch (item.type) { + case SettingsItem::TypeInt: { + if (!ReadInt<std::uint32_t>(item.data.integer)) + return std::nullopt; + + break; + } + case SettingsItem::TypeStr: { + std::uint32_t size; + if (!ReadInt<std::uint32_t>(size)) + return std::nullopt; + + if (!ReadString(item.data.string, size)) + return std::nullopt; + + /* padding */ + if (!Advance(GetPadding(size, 4))) + return std::nullopt; + + break; + } + case SettingsItem::TypeRgba: { + /* The order here is important!! */ + if (!ReadInt<std::uint16_t>(item.data.rgba.red) + || !ReadInt<std::uint16_t>(item.data.rgba.blue) + || !ReadInt<std::uint16_t>(item.data.rgba.green) + || !ReadInt<std::uint16_t>(item.data.rgba.alpha)) + return std::nullopt; + + break; + } + default: + /* can't do anything now, can we? */ + return std::nullopt; + } + + return item; +} + +std::vector<SettingsItem> Parser::ParseAllItems(void) { + offset_ = 0; + + std::uint32_t i; + std::vector<SettingsItem> items; + + for (i = 0; i < total_items_; i++) { + std::optional<SettingsItem> item = ParseNextItem(); + if (!item) + break; + + items.push_back(item.value()); + } + + return items; +} + +/* ------------------------------------------------------------------------- */ +/* real X11 code */ + +template<typename T> +struct MallocDestructor { + void operator()(T *t) const { std::free(t); }; +}; + +struct XcbConnectionDestructor { + void operator()(xcb_connection_t *conn) const { ::xcb_disconnect(conn); }; +}; + +template<typename T> +using MallocPtr = std::unique_ptr<T, MallocDestructor<T>>; + +using XcbConnectionPtr = std::unique_ptr<xcb_connection_t, XcbConnectionDestructor>; + +/* RAII is nice */ +struct XcbGrabber { + XcbGrabber(::xcb_connection_t *conn) { ::xcb_grab_server(conn); conn_ = conn; } + ~XcbGrabber() { ::xcb_ungrab_server(conn_); } + +private: + ::xcb_connection_t *conn_; +}; + +static ::xcb_window_t GetSelectionOwner(::xcb_connection_t *conn, ::xcb_atom_t selection) { + ::xcb_window_t owner = XCB_NONE; + MallocPtr<::xcb_get_selection_owner_reply_t> reply(::xcb_get_selection_owner_reply(conn, ::xcb_get_selection_owner(conn, selection), nullptr)); + + if (reply) + owner = reply->owner; + + return owner; +} + +static bool GetRawSettingsData(std::vector<uint8_t>& bytes) { + int screen; + + XcbConnectionPtr conn(::xcb_connect(nullptr, &screen)); + if (::xcb_connection_has_error(conn.get())) + return false; + + /* get our needed atoms, available as atoms[Atom] */ + enum Atom { + XSETTINGS_SCREEN, /* _XSETTINGS_S[N] */ + XSETTINGS_SETTINGS, /* _XSETTINGS_SETTINGS */ + }; + + std::map<Atom, ::xcb_atom_t> atoms; + { + std::map<Atom, std::string> names = { + {XSETTINGS_SCREEN, fmt::format("_XSETTINGS_S{}", screen)}, + {XSETTINGS_SETTINGS, "_XSETTINGS_SETTINGS"}, + }; + + std::map<Atom, ::xcb_intern_atom_cookie_t> atom_cookies; + for (const auto& name : names) + atom_cookies[name.first] = ::xcb_intern_atom(conn.get(), false, name.second.size(), name.second.data()); + + for (const auto& cookie : atom_cookies) { + MallocPtr<::xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(conn.get(), cookie.second, nullptr)); + if (!reply || reply->atom == XCB_NONE) + return false; + + atoms[cookie.first] = reply->atom; + } + } + + MallocPtr<xcb_get_property_reply_t> reply; + { + /* grab the X server as *required* by xsettings docs */ + const XcbGrabber grabber(conn.get()); + + ::xcb_window_t win = GetSelectionOwner(conn.get(), atoms[XSETTINGS_SCREEN]); + if (win == XCB_NONE) + return false; + + 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)); + }; + if (!reply) + return false; + + uint8_t *data = reinterpret_cast<uint8_t *>(xcb_get_property_value(reply.get())); + int size = xcb_get_property_value_length(reply.get()); + if (size < 0) + return false; + + bytes.assign(data, data + size); + + return true; +} + +/* ------------------------------------------------------------------------- */ +/* now for the actual all-important public API stringing all this together */ + +bool GetSettings(std::vector<SettingsItem>& settings) { + std::vector<std::uint8_t> xsettings_raw; + if (!GetRawSettingsData(xsettings_raw)) + return false; + + Parser parser(xsettings_raw.data(), xsettings_raw.size()); + settings = parser.ParseAllItems(); + + return true; +} + +bool FindSetting(const std::string& name, SettingsItem& setting) { + std::vector<std::uint8_t> xsettings_raw; + if (!GetRawSettingsData(xsettings_raw)) + return false; + + Parser parser(xsettings_raw.data(), xsettings_raw.size()); + + std::uint32_t total = parser.GetTotalItems(); + + for (; total; total--) { + std::optional<SettingsItem> opt_item = parser.ParseNextItem(); + if (!opt_item) + return false; + + SettingsItem& item = opt_item.value(); + if (item.name == name) { + setting = item; + return true; + } + } + + return false; +} + +} // namespace x11