Mercurial > minori
view 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 |
line wrap: on
line source
#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; 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; if (!ReadData<std::uint8_t>(byte_order_)) return; Advance(3); if (!ReadInt<std::uint32_t>(serial_)) return; if (!ReadInt<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