changeset 352:a0e96f50bcce

chore: merge diverging branches
author Paper <paper@paper.us.eu.org>
date Sun, 14 Jul 2024 23:24:19 -0400
parents c844f8bb87ce (diff) 7e97c566cce4 (current diff)
children 2f094656e775
files
diffstat 9 files changed, 639 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Jun 25 16:27:38 2024 -0400
+++ b/CMakeLists.txt	Sun Jul 14 23:24:19 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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/core/endian.h	Sun Jul 14 23:24:19 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 <cstdint>
+#include <type_traits>
+
+class Endian {
+private:
+	static constexpr uint32_t uint32_ = 0x01020304;
+	static constexpr uint8_t magic_ = static_cast<const uint8_t&>(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<typename T>
+	static constexpr T byteswap(T x) {
+		static_assert(std::is_integral<T>::value);
+		static_assert(std::is_unsigned<T>::value);
+
+		if constexpr (std::is_same<T, uint8_t>::value) {
+			return x;
+		} else if constexpr (std::is_same<T, uint16_t>::value) {
+			return byteswap_16(x);
+		} else if constexpr (std::is_same<T, uint32_t>::value) {
+			return byteswap_32(x);
+		} else if constexpr (std::is_same<T, uint64_t>::value) {
+			return byteswap_64(x);
+		} else {
+			static_assert(false, "byteswapping with unknown integer type");
+		}
+	}
+
+	template<typename T>
+	static constexpr T byteswap_little_to_host(T x) {
+		if constexpr (little) {
+			return x;
+		} else if constexpr (big) {
+			return byteswap(x);
+		}
+	}
+
+	template<typename T>
+	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_ */
--- a/include/sys/glib/dark_theme.h	Tue Jun 25 16:27:38 2024 -0400
+++ b/include/sys/glib/dark_theme.h	Sun Jul 14 23:24:19 2024 -0400
@@ -1,8 +1,11 @@
 #ifndef MINORI_SYS_GLIB_DARK_THEME_H_
 #define MINORI_SYS_GLIB_DARK_THEME_H_
 
+#include <string_view>
+
 namespace glib {
 
+bool IsGTKThemeDark(const std::string_view str);
 bool IsInDarkTheme();
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/sys/x11/dark_theme.h	Sun Jul 14 23:24:19 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/sys/x11/settings.h	Sun Jul 14 23:24:19 2024 -0400
@@ -0,0 +1,40 @@
+#ifndef MINORI_SYS_X11_SETTINGS_H_
+#define MINORI_SYS_X11_SETTINGS_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+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<SettingsItem>& settings);
+bool FindSetting(const std::string& name, SettingsItem& setting);
+
+}
+
+#endif /* MINORI_SYS_X11_SETTINGS_H_ */
\ No newline at end of file
--- a/src/gui/theme.cc	Tue Jun 25 16:27:38 2024 -0400
+++ b/src/gui/theme.cc	Sun Jul 14 23:24:19 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);
 }
--- a/src/sys/glib/dark_theme.cc	Tue Jun 25 16:27:38 2024 -0400
+++ b/src/sys/glib/dark_theme.cc	Sun Jul 14 23:24:19 2024 -0400
@@ -1,74 +1,101 @@
+#include "sys/glib/dark_theme.h"
+
 #include <cstring>
 #include <gio/gio.h>
 #include <string_view>
-
-/* this file uses the regular gio C interface because I don't
- * see any real benefit to using the C++ bindings. */
+#include <memory>
+#include <array>
+#include <iostream>
 
 namespace glib {
 
+/* deleters */
+template<typename T>
+struct g_object_del {
+    void operator()(T* p) const { ::g_object_unref(p); };
+};
+
+template<typename T>
+struct g_variant_del {
+    void operator()(T* p) const { ::g_variant_unref(p); };
+};
+
+template<typename T>
+struct g_malloc_del {
+    void operator()(T* p) const { ::g_free(p); };
+};
+
+template<typename T>
+using GObjectPtr = std::unique_ptr<T, g_object_del<T>>;
+
+template<typename T>
+using GVariantPtr = std::unique_ptr<T, g_variant_del<T>>;
+
+template<typename T>
+using GMallocPtr = std::unique_ptr<T, g_malloc_del<T>>;
+
+/* 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<std::string_view, 3> 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() {
-	GSettings* settings = ::g_settings_new("org.gnome.desktop.interface");
+	GObjectPtr<GSettings> settings(::g_settings_new("org.gnome.desktop.interface"));
 	if (!settings)
 		return false;
 
 	{
-		GVariant* val = ::g_settings_get_value(settings, "color-scheme");
-		if (!val) {
-			::g_object_unref(settings);
+		/* first attempt to get the colorscheme */
+		GVariantPtr<GVariant> val(::g_settings_get_value(settings.get(), "color-scheme"));
+		if (!val)
 			return false;
-		}
 
-		const gchar* str = nullptr;
-		::g_variant_get(val, "&s", &str); /* should not be freed */
-		if (!str) {                       /* how */
-			::g_variant_unref(val);
-			::g_object_unref(settings);
+		/* this is free'd upon deconstruction of the GVariantPtr */
+		gsize size;
+		const gchar* str = ::g_variant_get_string(val.get(), &size);
+		if (!str)
 			return false;
-		}
 
-		bool success = !std::strcmp(str, "prefer-dark");
-
-		::g_variant_unref(val);
+		bool success = !std::strncmp(str, "prefer-dark", size);
 
 		if (success)
 			return true;
 	}
 
 	{
-		GVariant* gtk_theme = ::g_settings_get_value(settings, "gtk-theme");
-		if (!gtk_theme) {
-			::g_object_unref(settings);
+
+		GVariantPtr<GVariant> gtk_theme(::g_settings_get_value(settings.get(), "gtk-theme"));
+		if (!gtk_theme)
 			return false;
-		}
-
-		const gchar* gtk_theme_str = nullptr;
-		::g_variant_get(gtk_theme, "&s", gtk_theme_str);
-		if (!gtk_theme_str) {
-			::g_variant_unref(gtk_theme);
-			::g_object_unref(settings);
-			return false;
-		}
 
-		static constexpr std::string_view suffix = "-dark";
-
-		size_t gtk_theme_len = strlen(gtk_theme_str);
-
-		if (gtk_theme_len < suffix.length()) {
-			::g_variant_unref(gtk_theme);
-			::g_object_unref(settings);
+		gsize size;
+		const gchar* str = ::g_variant_get_string(gtk_theme.get(), &size);
+		if (!str)
 			return false;
-		}
 
-		bool success = !std::strncmp(gtk_theme_str + gtk_theme_len - suffix.length(), suffix.data(), suffix.length());
-
-		::g_variant_unref(gtk_theme);
-		::g_object_unref(settings);
-
-		if (success)
+		if (IsGTKThemeDark({str, size}))
 			return true;
 	}
 
+	/* welp, we tried */
 	return false;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sys/x11/dark_theme.cc	Sun Jul 14 23:24:19 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 <iostream>
+
+namespace x11 {
+
+bool IsInDarkTheme() {
+	SettingsItem setting;
+	if (!FindSetting(u8"Net/ThemeName", setting))
+		return false;
+
+	return glib::IsGTKThemeDark(setting.data.string);
+}
+
+} // namespace glib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sys/x11/settings.cc	Sun Jul 14 23:24:19 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