# HG changeset patch # User Paper # Date 1721013836 14400 # Node ID c844f8bb87ce8d047d68cf456ea612b20114a2f3 # Parent daa03aa2262d2572451a848cf977e589ffaed2ce 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 diff -r daa03aa2262d -r c844f8bb87ce CMakeLists.txt --- a/CMakeLists.txt Sun Jul 14 19:12:40 2024 -0400 +++ b/CMakeLists.txt Sun Jul 14 23:23:56 2024 -0400 @@ -238,7 +238,15 @@ list(APPEND SRC_FILES src/sys/glib/dark_theme.cc) list(APPEND INCLUDE ${GLIB_INCLUDE_DIRS}) list(APPEND LIBRARIES ${GLIB_LINK_LIBRARIES}) - list(APPEND DEFINES GLIB) + list(APPEND DEFINES GLIB) # XXX rename HAVE_GLIB or something + endif() + + pkg_check_modules(XCB xcb) + if (XCB_FOUND) + list(APPEND SRC_FILES src/sys/x11/settings.cc src/sys/x11/dark_theme.cc) + list(APPEND INCLUDE ${XCB_INCLUDE_DIRS}) + list(APPEND LIBRARIES ${XCB_LINK_LIBRARIES}) + list(APPEND DEFINES HAVE_XCB) endif() endif() endif() diff -r daa03aa2262d -r c844f8bb87ce include/core/endian.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/core/endian.h Sun Jul 14 23:23:56 2024 -0400 @@ -0,0 +1,121 @@ +#ifndef MINORI_CORE_ENDIAN_H_ +#define MINORI_CORE_ENDIAN_H_ + +/* definition of endian-related stuff. primarily used for*/ + +#include +#include + +class Endian { +private: + static constexpr uint32_t uint32_ = 0x01020304; + static constexpr uint8_t magic_ = static_cast(uint32_); + + /* check for compiler builtins for byteswapping */ +#ifdef __has_builtin +# if __has_builtin(__builtin_bswap16) +# define COMPILER_BUILTIN_BSWAP16(x) __builtin_bswap16(x) +# endif +# if __has_builtin(__builtin_bswap32) +# define COMPILER_BUILTIN_BSWAP32(x) __builtin_bswap32(x) +# endif +# if __has_builtin(__builtin_bswap64) +# define COMPILER_BUILTIN_BSWAP64(x) __builtin_bswap64(x) +# endif +#endif + + static constexpr uint16_t byteswap_16(uint16_t x) { +#ifdef COMPILER_BUILTIN_BSWAP16 + return COMPILER_BUILTIN_BSWAP16(x); +#else + return ( + ((x & UINT16_C(0x00FF)) << 8) + | ((x & UINT16_C(0xFF00)) >> 8) + ); +#endif + } + + static constexpr uint32_t byteswap_32(uint32_t x) { +#ifdef COMPILER_BUILTIN_BSWAP32 + return COMPILER_BUILTIN_BSWAP32(x); +#else + return ( + ((x & UINT32_C(0x000000FF)) << 24) + | ((x & UINT32_C(0x0000FF00)) << 8) + | ((x & UINT32_C(0x00FF0000)) >> 8) + | ((x & UINT32_C(0xFF000000)) >> 24) + ); +#endif + } + + static constexpr uint64_t byteswap_64(uint64_t x) { +#ifdef COMPILER_BUILTIN_BSWAP64 + return COMPILER_BUILTIN_BSWAP64(x); +#else + return ( + ((x & UINT64_C(0x00000000000000FF)) << 56) + | ((x & UINT64_C(0x000000000000FF00)) << 40) + | ((x & UINT64_C(0x0000000000FF0000)) << 24) + | ((x & UINT64_C(0x00000000FF000000)) << 8) + | ((x & UINT64_C(0x000000FF00000000)) >> 8) + | ((x & UINT64_C(0x0000FF0000000000)) >> 24) + | ((x & UINT64_C(0x00FF000000000000)) >> 40) + | ((x & UINT64_C(0xFF00000000000000)) >> 56) + ); +#endif + } + +#ifdef COMPILER_BUILTIN_BSWAP16 +# undef COMPILER_BUILTIN_BSWAP16 +#endif +#ifdef COMPILER_BUILTIN_BSWAP32 +# undef COMPILER_BUILTIN_BSWAP32 +#endif +#ifdef COMPILER_BUILTIN_BSWAP64 +# undef COMPILER_BUILTIN_BSWAP64 +#endif +public: + static constexpr bool little = magic_ == 0x04; + static constexpr bool big = magic_ == 0x01; + static_assert(little || big, "unsupported endianness"); + + template + static constexpr T byteswap(T x) { + static_assert(std::is_integral::value); + static_assert(std::is_unsigned::value); + + if constexpr (std::is_same::value) { + return x; + } else if constexpr (std::is_same::value) { + return byteswap_16(x); + } else if constexpr (std::is_same::value) { + return byteswap_32(x); + } else if constexpr (std::is_same::value) { + return byteswap_64(x); + } else { + static_assert(false, "byteswapping with unknown integer type"); + } + } + + template + static constexpr T byteswap_little_to_host(T x) { + if constexpr (little) { + return x; + } else if constexpr (big) { + return byteswap(x); + } + } + + template + static constexpr T byteswap_big_to_host(T x) { + if constexpr (big) { + return x; + } else if constexpr (little) { + return byteswap(x); + } + } +private: + Endian() = delete; +}; + +#endif /* MINORI_CORE_ENDIAN_H_ */ diff -r daa03aa2262d -r c844f8bb87ce include/sys/glib/dark_theme.h --- a/include/sys/glib/dark_theme.h Sun Jul 14 19:12:40 2024 -0400 +++ b/include/sys/glib/dark_theme.h Sun Jul 14 23:23:56 2024 -0400 @@ -1,8 +1,11 @@ #ifndef MINORI_SYS_GLIB_DARK_THEME_H_ #define MINORI_SYS_GLIB_DARK_THEME_H_ +#include + namespace glib { +bool IsGTKThemeDark(const std::string_view str); bool IsInDarkTheme(); } diff -r daa03aa2262d -r c844f8bb87ce include/sys/x11/dark_theme.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/sys/x11/dark_theme.h Sun Jul 14 23:23:56 2024 -0400 @@ -0,0 +1,10 @@ +#ifndef MINORI_SYS_X11_DARK_THEME_H_ +#define MINORI_SYS_X11_DARK_THEME_H_ + +namespace x11 { + +bool IsInDarkTheme(); + +} + +#endif /* MINORI_SYS_X11_DARK_THEME_H_ */ \ No newline at end of file diff -r daa03aa2262d -r c844f8bb87ce include/sys/x11/settings.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/sys/x11/settings.h Sun Jul 14 23:23:56 2024 -0400 @@ -0,0 +1,40 @@ +#ifndef MINORI_SYS_X11_SETTINGS_H_ +#define MINORI_SYS_X11_SETTINGS_H_ + +#include +#include +#include + +namespace x11 { + +/* stores the item, type, etc */ +struct SettingsItem { + enum Type { + TypeInt = 0, + TypeStr = 1, + TypeRgba = 2, + }; + + /* could technically be a union */ + struct Data { + std::uint32_t integer; + std::string string; + struct { + std::uint16_t red, green, blue, alpha; + } rgba; + }; + + bool VerifyType(); + + std::uint8_t type; /* one of Type */ + std::string name; /* name of the item */ + std::uint32_t serial; /* last-changed serial */ + Data data; /* type-specific data */ +}; + +bool GetSettings(std::vector& settings); +bool FindSetting(const std::string& name, SettingsItem& setting); + +} + +#endif /* MINORI_SYS_X11_SETTINGS_H_ */ \ No newline at end of file diff -r daa03aa2262d -r c844f8bb87ce src/gui/theme.cc --- a/src/gui/theme.cc Sun Jul 14 19:12:40 2024 -0400 +++ b/src/gui/theme.cc Sun Jul 14 23:23:56 2024 -0400 @@ -16,6 +16,9 @@ # ifdef GLIB # include "sys/glib/dark_theme.h" # endif +# ifdef HAVE_XCB +# include "sys/x11/dark_theme.h" +# endif #endif /* Weird quirks of this implementation: @@ -45,11 +48,18 @@ if (win32::DarkThemeAvailable()) return win32::IsInDarkTheme(); #else -# ifdef GLIB - return glib::IsInDarkTheme(); +# ifdef HAVE_XCB + if (x11::IsInDarkTheme()) + return true; # endif +# ifdef GLIB + if (glib::IsInDarkTheme()) + return true; +# endif + break; #endif - default: break; + default: + break; } return (theme == Theme::Dark); } diff -r daa03aa2262d -r c844f8bb87ce src/sys/glib/dark_theme.cc --- a/src/sys/glib/dark_theme.cc Sun Jul 14 19:12:40 2024 -0400 +++ b/src/sys/glib/dark_theme.cc Sun Jul 14 23:23:56 2024 -0400 @@ -5,6 +5,7 @@ #include #include #include +#include namespace glib { @@ -33,6 +34,29 @@ template using GMallocPtr = std::unique_ptr>; +/* not really "glib" but GNOME-related enough */ +bool IsGTKThemeDark(const std::string_view str) { + /* if that doesn't exist, use the GTK theme and check for some known + * suffixes. if one is found, return + * + * XXX probably better to use case folding here */ + static constexpr std::array suffixes = { + "-dark", /* Adwaita-dark */ + "-Dark", /* Arc-Dark */ + "-Darker", /* Arc-Darker */ + }; + + for (const auto& suffix : suffixes) { + if (str.size() < suffix.size()) + continue; + + if (std::equal(str.data() + str.size() - suffix.length(), str.data() + str.size(), suffix.begin(), suffix.end())) + return true; + } + + return false; +} + bool IsInDarkTheme() { GObjectPtr settings(::g_settings_new("org.gnome.desktop.interface")); if (!settings) @@ -50,22 +74,13 @@ if (!str) return false; - bool success = !std::strncmp(str, size, "prefer-dark"); + bool success = !std::strncmp(str, "prefer-dark", size); if (success) return true; } { - /* if that doesn't exist, use the GTK theme and check for some known - * suffixes. if one is found, return - * - * XXX probably better to use case folding here */ - static constexpr std::array suffixes = { - "-dark", /* Adwaita-dark */ - "-Dark", /* Arc-Dark */ - "-Darker", /* Arc-Darker */ - }; GVariantPtr gtk_theme(::g_settings_get_value(settings.get(), "gtk-theme")); if (!gtk_theme) @@ -76,13 +91,8 @@ if (!str) return false; - for (const auto& suffix : suffixes) { - if (size < suffix.size()) - continue; - - if (std::equal(str + size - suffix.length(), str + size, suffix.begin(), suffix.end())) - return true; - } + if (IsGTKThemeDark({str, size})) + return true; } /* welp, we tried */ diff -r daa03aa2262d -r c844f8bb87ce src/sys/x11/dark_theme.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sys/x11/dark_theme.cc Sun Jul 14 23:23:56 2024 -0400 @@ -0,0 +1,17 @@ +#include "sys/x11/dark_theme.h" +#include "sys/x11/settings.h" +#include "sys/glib/dark_theme.h" /* glib::IsGTKThemeDark */ + +#include + +namespace x11 { + +bool IsInDarkTheme() { + SettingsItem setting; + if (!FindSetting(u8"Net/ThemeName", setting)) + return false; + + return glib::IsGTKThemeDark(setting.data.string); +} + +} // namespace glib diff -r daa03aa2262d -r c844f8bb87ce src/sys/x11/settings.cc --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 ParseAllItems(void); + std::optional ParseNextItem(void); + + std::uint32_t GetTotalItems(void); + +private: + /* byte order values */ + enum { + LSBFirst = 0, + MSBFirst = 1, + }; + + template + bool ReadData(T& ret) { + if (offset_ + sizeof(T) >= size_) + return false; + + ret = *reinterpret_cast(bytes_ + offset_); + Advance(sizeof(T)); + return true; + } + + template + bool ReadInt(T& ret) { + static_assert(std::is_integral::value); + + if (!ReadData(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(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(byte_order_)) + return; + + Advance(3); + + if (!ReadData(serial_)) + return; + + if (!ReadData(total_items_)) + return; +} + +std::optional Parser::ParseNextItem(void) { + SettingsItem item; + + /* read one byte */ + if (!ReadInt(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(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(item.serial)) + return std::nullopt; + + switch (item.type) { + case SettingsItem::TypeInt: { + if (!ReadInt(item.data.integer)) + return std::nullopt; + + break; + } + case SettingsItem::TypeStr: { + std::uint32_t size; + if (!ReadInt(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(item.data.rgba.red) + || !ReadInt(item.data.rgba.blue) + || !ReadInt(item.data.rgba.green) + || !ReadInt(item.data.rgba.alpha)) + return std::nullopt; + + break; + } + default: + /* can't do anything now, can we? */ + return std::nullopt; + } + + return item; +} + +std::vector Parser::ParseAllItems(void) { + offset_ = 0; + + std::uint32_t i; + std::vector items; + + for (i = 0; i < total_items_; i++) { + std::optional item = ParseNextItem(); + if (!item) + break; + + items.push_back(item.value()); + } + + return items; +} + +/* ------------------------------------------------------------------------- */ +/* real X11 code */ + +template +struct MallocDestructor { + void operator()(T *t) const { std::free(t); }; +}; + +struct XcbConnectionDestructor { + void operator()(xcb_connection_t *conn) const { ::xcb_disconnect(conn); }; +}; + +template +using MallocPtr = std::unique_ptr>; + +using XcbConnectionPtr = std::unique_ptr; + +/* 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& 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 atoms; + { + std::map names = { + {XSETTINGS_SCREEN, fmt::format("_XSETTINGS_S{}", screen)}, + {XSETTINGS_SETTINGS, "_XSETTINGS_SETTINGS"}, + }; + + std::map 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 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(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& settings) { + std::vector 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 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 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