aboutsummaryrefslogtreecommitdiff
path: root/source/lib/include
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@gmail.com>2024-05-17 17:58:38 +0200
committerFelix Morgner <felix.morgner@gmail.com>2024-05-17 17:58:38 +0200
commit577fc0845718ed8ad5bebf02a277c0579a817f77 (patch)
tree3d1cdc53c426a0ba60a7996619a7b787850bb3b3 /source/lib/include
parentde5bf7ca3b7a2bf6be35b86486b00dc6a071b950 (diff)
downloadwanda-577fc0845718ed8ad5bebf02a277c0579a817f77.tar.xz
wanda-577fc0845718ed8ad5bebf02a277c0579a817f77.zip
wanda: restructure source layoutHEADdevelop
Diffstat (limited to 'source/lib/include')
-rw-r--r--source/lib/include/wanda/control/commander.hpp67
-rw-r--r--source/lib/include/wanda/control/connection.hpp116
-rw-r--r--source/lib/include/wanda/control/interface.hpp97
-rw-r--r--source/lib/include/wanda/meta/deferred_failure.hpp21
-rw-r--r--source/lib/include/wanda/meta/keyed.hpp28
-rw-r--r--source/lib/include/wanda/meta/type_wrapper.hpp47
-rw-r--r--source/lib/include/wanda/proto/command.hpp46
-rw-r--r--source/lib/include/wanda/proto/message.hpp84
-rw-r--r--source/lib/include/wanda/proto/version.hpp12
-rw-r--r--source/lib/include/wanda/std_ext/expected.hpp256
-rw-r--r--source/lib/include/wanda/std_ext/optional.hpp65
-rw-r--r--source/lib/include/wanda/system/environment.hpp61
-rw-r--r--source/lib/include/wanda/system/filesystem.hpp39
-rw-r--r--source/lib/include/wanda/system/logging.hpp35
-rw-r--r--source/lib/include/wanda/system/magic.hpp58
-rw-r--r--source/lib/include/wanda/system/setting.hpp156
-rw-r--r--source/lib/include/wanda/system/wallpaper.hpp24
-rw-r--r--source/lib/include/wanda/system/xdg.hpp40
18 files changed, 1252 insertions, 0 deletions
diff --git a/source/lib/include/wanda/control/commander.hpp b/source/lib/include/wanda/control/commander.hpp
new file mode 100644
index 0000000..d7ca73d
--- /dev/null
+++ b/source/lib/include/wanda/control/commander.hpp
@@ -0,0 +1,67 @@
+#ifndef WANDA_CONTROL_COMMANDER_HPP
+#define WANDA_CONTROL_COMMANDER_HPP
+
+#include "wanda/control/connection.hpp"
+#include "wanda/proto/command.hpp"
+#include "wanda/proto/message.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/io_service.hpp>
+
+#include <filesystem>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace wanda::control
+{
+ /**
+ * @brief The remote control client
+ *
+ */
+ struct commander : connection::listener
+ {
+ /**
+ * @brief The interface to be implemented by remote control listeners
+ */
+ struct listener
+ {
+ virtual void on_connected(commander & commander){};
+ virtual void on_response(commander & commander, std::string response){};
+ virtual void on_error(commander & commander, std::string error){};
+ };
+
+ /**
+ * @brief Construct a new commander
+ */
+ commander(boost::asio::io_context & service, std::filesystem::path socket, listener & listener);
+
+ /**
+ * @brief Start communication with the remote daemon endpoint
+ */
+ void start();
+
+ /**
+ * @brief Stop communication with the remote daemon endpoint
+ */
+ void stop();
+
+ /**
+ * @brief Send a command to the remote daemon endpoint
+ */
+ void send(proto::command command);
+
+ void on_error(connection::pointer connection, std::error_code error) override;
+ void on_received(connection::pointer connection, proto::message message) override;
+
+ private:
+ boost::asio::io_context & m_service;
+ wanda::control::connection::protocol::endpoint m_endpoint;
+ wanda::control::connection::protocol::socket m_socket;
+ wanda::control::connection::pointer m_connection;
+ listener & m_listener;
+ };
+
+} // namespace wanda::control
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/control/connection.hpp b/source/lib/include/wanda/control/connection.hpp
new file mode 100644
index 0000000..0284fd7
--- /dev/null
+++ b/source/lib/include/wanda/control/connection.hpp
@@ -0,0 +1,116 @@
+#ifndef WANDA_CONTROL_CONNECTION_HPP
+#define WANDA_CONTROL_CONNECTION_HPP
+
+#include "wanda/meta/keyed.hpp"
+#include "wanda/proto/message.hpp"
+
+#include <boost/asio/local/stream_protocol.hpp>
+#include <boost/asio/streambuf.hpp>
+
+#include <istream>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <system_error>
+
+namespace wanda::control
+{
+ /**
+ * @brief A connection to a remote control endpoint
+ */
+ struct connection : meta::keyed<connection>, std::enable_shared_from_this<connection>
+ {
+ using protocol = boost::asio::local::stream_protocol;
+ using pointer = std::shared_ptr<connection>;
+
+ /**
+ * @brief The interface to be implemented by the control interface listener
+ */
+ struct listener
+ {
+ virtual void on_close(pointer connection) {}
+ virtual void on_received(pointer connection, proto::message message) {}
+ virtual void on_error(pointer connection, std::error_code) {}
+ };
+
+ /**
+ * @brief A enum to describe different connection states
+ */
+ enum struct state : std::underlying_type_t<std::byte>
+ {
+ unknown, //< Connection is in an unknown state
+ fresh, //< Connection is freshly created but not established
+ established, //< Connection has been established
+ };
+
+ /**
+ * @internal
+ * @brief Construct a new control connection object
+ *
+ * @note This constructor is keyed on a private key type so it can only be constructed using the
+ * #wanda::make_connection factory
+ */
+ connection(key, protocol::socket socket);
+
+ /**
+ * @brief Add the given listener to this control connection's listener set
+ *
+ * @returns <code>true</code> iff. the listener was not already in the listener set
+ */
+ bool add(listener * listener);
+
+ /**
+ * @brief Remove the given listener from this control connection's listener set
+ *
+ * @return <code>true</code> iff. the listener was previously registered with this control connection
+ */
+ bool remove(listener * listener);
+
+ /**
+ * @brief Start I/O processing for this control connection
+ */
+ void start();
+
+ /**
+ * @brief Close this control connection
+ */
+ void close();
+
+ /**
+ * @brief Send the given message to the remote endpoint
+ */
+ void send(proto::message message);
+
+ /**
+ * @brief Set the connection state to the provided state
+ */
+ void update(state state);
+
+ /**
+ * @brief Get the current connection state
+ */
+ state current_state() const;
+
+ private:
+ friend pointer make_connection(protocol::socket && socket);
+
+ void perform_read();
+
+ protocol::socket m_socket;
+ boost::asio::streambuf m_in{};
+ boost::asio::streambuf m_out{};
+ std::istream m_input{&m_in};
+ std::ostream m_output{&m_out};
+ std::set<listener *> m_listeners{};
+ state m_state{};
+ };
+
+ /**
+ * @brief Create a new control connection
+ */
+ connection::pointer make_connection(connection::protocol::socket && socket);
+
+} // namespace wanda::control
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/control/interface.hpp b/source/lib/include/wanda/control/interface.hpp
new file mode 100644
index 0000000..e895b68
--- /dev/null
+++ b/source/lib/include/wanda/control/interface.hpp
@@ -0,0 +1,97 @@
+/**
+ * @file interface.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_CONTROL_INTERFACE_HPP
+#define WANDA_CONTROL_INTERFACE_HPP
+
+#include "wanda/control/connection.hpp"
+#include "wanda/meta/keyed.hpp"
+#include "wanda/proto/command.hpp"
+
+#include <boost/asio/local/stream_protocol.hpp>
+#include <boost/asio/io_context.hpp>
+#include <spdlog/spdlog.h>
+
+#include <cstddef>
+#include <filesystem>
+#include <istream>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <type_traits>
+
+namespace wanda::control
+{
+ /**
+ * @brief An RAII type to delete a socket file upon destruction
+ */
+ struct socket_deleter
+ {
+ ~socket_deleter();
+
+ std::filesystem::path path;
+ };
+
+ /**
+ * @brief The daemon control interface
+ */
+ struct interface : connection::listener, meta::keyed<interface>, std::enable_shared_from_this<interface>
+ {
+ using protocol = boost::asio::local::stream_protocol;
+ using pointer = std::shared_ptr<interface>;
+
+ /**
+ * @brief The interface to be implemented by the control interface listener
+ */
+ struct listener
+ {
+ virtual void on_received(interface & interface, proto::command command){};
+ };
+
+ /**
+ * @internal
+ * @brief Construct a new control interface object
+ *
+ * @note This constructor is keyed on a private key type so it can only be constructed using the #wanda::make_interface factory
+ */
+ interface(key, boost::asio::io_context & service, protocol::endpoint endpoint, listener & listener);
+
+ /**
+ * @brief Start handling of controller connections
+ */
+ std::error_code start();
+
+ /**
+ * @brief Stop the control interface
+ */
+ std::error_code shutdown();
+
+ void on_close(connection::pointer connection) override;
+ void on_received(connection::pointer connection, proto::message message) override;
+
+ private:
+ void perform_accept();
+
+ friend pointer make_interface(boost::asio::io_context & service, std::filesystem::path file, interface::listener & listener);
+
+ boost::asio::io_context & m_service;
+ protocol::endpoint m_endpoint;
+ protocol::socket m_socket;
+ protocol::acceptor m_acceptor;
+ listener & m_listener;
+ socket_deleter m_deleter{m_endpoint.path()};
+ std::set<connection::pointer> m_connections;
+ };
+
+ /**
+ * @brief A factory to create new #interface instances
+ */
+ interface::pointer make_interface(boost::asio::io_context & service, std::filesystem::path socket, interface::listener & listener);
+
+} // namespace wanda::control
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/meta/deferred_failure.hpp b/source/lib/include/wanda/meta/deferred_failure.hpp
new file mode 100644
index 0000000..f74d923
--- /dev/null
+++ b/source/lib/include/wanda/meta/deferred_failure.hpp
@@ -0,0 +1,21 @@
+/**
+ * @file deferred_failure.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_META_DEFERRED_FAILURE_HPP
+#define WANDA_META_DEFERRED_FAILURE_HPP
+
+#include <type_traits>
+
+namespace wanda::meta
+{
+ /**
+ * @brief A helper type to defer static_assert failures
+ */
+ template<typename...>
+ using deferred_failure = std::false_type;
+} // namespace wanda::meta
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/meta/keyed.hpp b/source/lib/include/wanda/meta/keyed.hpp
new file mode 100644
index 0000000..a09d1eb
--- /dev/null
+++ b/source/lib/include/wanda/meta/keyed.hpp
@@ -0,0 +1,28 @@
+/**
+ * @file keyed.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_META_KEYED_HPP
+#define WANDA_META_KEYED_HPP
+
+namespace wanda::meta
+{
+ /**
+ * @brief A tag type to prevent construction of a type without a factory
+ */
+ template<typename Derived>
+ struct keyed
+ {
+ protected:
+ struct key
+ {
+ };
+
+ explicit keyed(key) {}
+ };
+
+} // namespace wanda::meta
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/meta/type_wrapper.hpp b/source/lib/include/wanda/meta/type_wrapper.hpp
new file mode 100644
index 0000000..1d34c09
--- /dev/null
+++ b/source/lib/include/wanda/meta/type_wrapper.hpp
@@ -0,0 +1,47 @@
+/**
+ * @file type_wrapper.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_META_TYPE_WRAPPER_HPP
+#define WANDA_META_TYPE_WRAPPER_HPP
+
+#include <utility>
+
+namespace wanda::meta
+{
+ /**
+ * @brief A type to create a distinct type based on an existing type
+ *
+ * @tparam InnerType The type to wrap
+ * @tparam TagType A tag type to identify the distinct type
+ */
+ template<typename InnerType, typename TagType>
+ struct type_wrapper
+ {
+ /**
+ * @brief Construct a new type wrapper object
+ */
+ explicit type_wrapper(InnerType value)
+ : m_value{std::move(value)}
+ {
+ }
+
+ /**
+ * @brief Retrieve the wrapped value with its original type
+ */
+ constexpr explicit operator InnerType const &() const { return get(); }
+
+ /**
+ * @brief Retrieve the wrapped value with its original type
+ */
+ constexpr InnerType const & get() const { return m_value; }
+
+ private:
+ InnerType m_value;
+ };
+
+} // namespace wanda::meta
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/proto/command.hpp b/source/lib/include/wanda/proto/command.hpp
new file mode 100644
index 0000000..c8dae65
--- /dev/null
+++ b/source/lib/include/wanda/proto/command.hpp
@@ -0,0 +1,46 @@
+#ifndef WANDA_PROTO_COMMAND_HPP
+#define WANDA_PROTO_COMMAND_HPP
+
+#include "wanda/proto/message.hpp"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace wanda::proto
+{
+ /**
+ * @brief An enum to describe different command IDs
+ */
+ enum struct command_id : char
+ {
+ change, //< Change the wallpaper
+ };
+
+ /**
+ * @brief A simple type to represent commands transported through the control connection
+ */
+ struct command
+ {
+ command_id const id;
+ std::vector<std::string> const arguments;
+
+ /**
+ * @brief Convert the command to a message for transmission to a remote endpoint
+ */
+ std::optional<wanda::proto::message> message() const;
+ };
+
+ /**
+ * @brief Extract a command from a message
+ */
+ std::optional<command> make_command(message message);
+
+ /**
+ * @brief A simple factory to create a "Change wallpaper" command
+ */
+ command make_change_command();
+
+} // namespace wanda::proto
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/proto/message.hpp b/source/lib/include/wanda/proto/message.hpp
new file mode 100644
index 0000000..03a30c2
--- /dev/null
+++ b/source/lib/include/wanda/proto/message.hpp
@@ -0,0 +1,84 @@
+/**
+ * @file message.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_PROTO_MESSAGE_HPP
+#define WANDA_PROTO_MESSAGE_HPP
+
+#include <spdlog/common.h>
+
+#include <cstddef>
+#include <istream>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace wanda::proto
+{
+ /**
+ * @brief A tag to mark messages originating from the controller
+ */
+ auto constexpr message_source_controller = "C";
+
+ /**
+ * @brief A tag to mark messages originating from the daemon
+ */
+ auto constexpr message_source_daemon = "D";
+
+ /**
+ * @brief The command of the hello message
+ */
+ auto constexpr message_command_hello = "HELLO";
+
+ /**
+ * @brief A control protocol message, consisting of a @p source, @p command, and @p arguments
+ */
+ struct message
+ {
+ /**
+ * @brief Serialize this message into a string
+ */
+ explicit operator std::string() const;
+
+ /**
+ * @brief Get the size of the message as if it was serialized
+ */
+ std::size_t size() const;
+
+ /**
+ * @brief The source of the message
+ */
+ std::string source;
+
+ /**
+ * @brief The command of the message
+ */
+ std::string command;
+
+ /**
+ * @brief The arguments of the message command
+ */
+ std::optional<std::string> argument;
+ };
+
+ /**
+ * @brief Deserialize a message from the given stream
+ */
+ std::istream & operator>>(std::istream & in, message & message);
+
+ /**
+ * @brief Serialize a message to the given stream
+ */
+ std::ostream & operator<<(std::ostream & out, message const & message);
+
+} // namespace wanda::proto
+
+template<>
+struct spdlog::fmt_lib::formatter<wanda::proto::message> : spdlog::fmt_lib::formatter<std::string>
+{
+ auto format(wanda::proto::message const & message, format_context & context) const -> decltype(context.out());
+};
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/proto/version.hpp b/source/lib/include/wanda/proto/version.hpp
new file mode 100644
index 0000000..faa17d7
--- /dev/null
+++ b/source/lib/include/wanda/proto/version.hpp
@@ -0,0 +1,12 @@
+#ifndef WANDA_PROTO_VERSION_HPP
+#define WANDA_PROTO_VERSION_HPP
+
+namespace wanda::proto
+{
+ inline namespace v1
+ {
+ auto constexpr version = "1.0.0";
+ }
+} // namespace wanda::proto
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/std_ext/expected.hpp b/source/lib/include/wanda/std_ext/expected.hpp
new file mode 100644
index 0000000..83629db
--- /dev/null
+++ b/source/lib/include/wanda/std_ext/expected.hpp
@@ -0,0 +1,256 @@
+/**
+ * @file expected.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_STD_EXT_EXPECTED_HPP
+#define WANDA_STD_EXT_EXPECTED_HPP
+
+#include <initializer_list>
+#include <type_traits>
+#include <utility>
+
+namespace wanda::std_ext
+{
+ /**
+ * @brief A type to represent the error case of a computation based on #wanda::expected
+ */
+ template<typename ErrorType>
+ struct unexpected
+ {
+ static_assert(!std::is_same_v<ErrorType, void>, "ErrorType can not be 'void'!");
+ static_assert(!std::is_array_v<ErrorType>, "ErrorType can not be an array type!");
+
+ /**
+ * @brief Copy construct a new @p unexpected from another @p unexpected
+ */
+ constexpr unexpected(unexpected const &) = default;
+
+ /**
+ * @brief Move construct a new @p unexpected from another @p unexpected
+ */
+ constexpr unexpected(unexpected &&) = default;
+
+ /**
+ * @brief Construct a new @p unexpected by direct initializing the error object from @p args
+ */
+ template<typename... Args>
+ constexpr explicit unexpected(std::in_place_t, Args &&... args)
+ : m_error(std::forward<Args>(args)...)
+ {
+ }
+
+ /**
+ * @brief Construct a new @p unexpected by direct initializing the error object from @p il and @p args
+ */
+ template<
+ typename U,
+ typename... Args,
+ std::enable_if_t<std::is_constructible_v<ErrorType, std::initializer_list<U>, Args...>> * = nullptr>
+ constexpr explicit unexpected(std::in_place_t, std::initializer_list<U> il, Args &&... args)
+ : m_error(il, std::forward<Args>(args)...)
+ {
+ }
+
+ /**
+ * @brief Construct a new @p unexpected by direct initializing the error object from @p error
+ */
+ template<
+ typename Err = ErrorType,
+ std::enable_if_t<std::is_constructible_v<ErrorType, Err> &&
+ !std::is_same_v<std::remove_cv_t<std::remove_reference_t<Err>>, std::in_place_t> &&
+ !std::is_same_v<std::remove_cv_t<std::remove_reference_t<Err>>, unexpected>> * = nullptr>
+ constexpr explicit unexpected(Err && error)
+ : m_error(std::forward<Err>(error))
+ {
+ }
+
+ /**
+ * @brief Construct a new @p unexpected by copying the value of another @p unexpected of different error type
+ */
+ template<
+ typename Err,
+ std::enable_if_t<!(
+ std::is_constructible_v<ErrorType, Err> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err>> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const> &&
+ !std::is_convertible_v<unexpected<Err> &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err>, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const, ErrorType>)> * = nullptr,
+ std::enable_if_t<!std::is_convertible_v<Err, ErrorType>> * = nullptr>
+ constexpr explicit unexpected(unexpected<Err> const & error)
+ : m_error(error.m_error)
+ {
+ }
+
+ /**
+ * @brief Construct a new @p unexpected by copying the value of another @p unexpected of different error type
+ */
+ template<
+ typename Err,
+ std::enable_if_t<!(
+ std::is_constructible_v<ErrorType, Err> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err>> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const> &&
+ !std::is_convertible_v<unexpected<Err> &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err>, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const, ErrorType>)> * = nullptr,
+ std::enable_if_t<std::is_convertible_v<Err, ErrorType>> * = nullptr>
+ constexpr unexpected(unexpected<Err> const & error)
+ : m_error(error.m_error)
+ {
+ }
+
+ /**
+ * @brief Construct a new @p unexpected by moving the value of another @p unexpected of different error type
+ */
+ template<
+ typename Err,
+ std::enable_if_t<!(
+ std::is_constructible_v<ErrorType, Err> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err>> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const> &&
+ !std::is_convertible_v<unexpected<Err> &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err>, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const, ErrorType>)> * = nullptr,
+ std::enable_if_t<!std::is_convertible_v<Err, ErrorType>> * = nullptr>
+ constexpr explicit unexpected(unexpected<Err> && error)
+ : m_error(std::move(error.m_error))
+ {
+ }
+
+ /**
+ * @brief Construct a new @p unexpected by moving the value of another @p unexpected of different error type
+ */
+ template<
+ typename Err,
+ std::enable_if_t<!(
+ std::is_constructible_v<ErrorType, Err> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err>> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const &> &&
+ !std::is_constructible_v<ErrorType, unexpected<Err> const> &&
+ !std::is_convertible_v<unexpected<Err> &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err>, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const &, ErrorType> &&
+ !std::is_convertible_v<unexpected<Err> const, ErrorType>)> * = nullptr,
+ std::enable_if_t<std::is_convertible_v<Err, ErrorType>> * = nullptr>
+ constexpr unexpected(unexpected<Err> && error)
+ : m_error(std::move(error.m_error))
+ {
+ }
+
+ /**
+ * @brief Get the error value contained in this @p unexpected instance
+ */
+ constexpr ErrorType const & value() const &
+ {
+ return m_error;
+ }
+
+ /**
+ * @brief Get the error value contained in this @p unexpected instance
+ */
+ constexpr ErrorType & value() &
+ {
+ return m_error;
+ }
+
+ /**
+ * @brief Get the error value contained in this @p unexpected instance
+ */
+ constexpr ErrorType && value() &&
+ {
+ return std::move(m_error);
+ }
+
+ /**
+ * @brief Get the error value contained in this @p unexpected instance
+ */
+ constexpr ErrorType const && value() const &&
+ {
+ return std::move(m_error);
+ }
+
+ /**
+ * @brief Swap the error value of this @p unexpected instance with the one of @p other
+ */
+ void swap(unexpected & other) noexcept(std::is_nothrow_swappable_v<ErrorType>)
+ {
+ using std::swap;
+ swap(m_error, other.m_error);
+ }
+
+ template<typename ErrorType1, typename ErrorType2>
+ friend constexpr bool operator==(unexpected<ErrorType1> const & lhs, unexpected<ErrorType2> const & rhs);
+
+ template<typename ErrorType1, typename ErrorType2>
+ friend constexpr bool operator!=(unexpected<ErrorType1> const & lhs, unexpected<ErrorType2> const & rhs);
+
+ template<
+ typename Err,
+ std::enable_if_t<std::is_swappable_v<Err>> *>
+ friend void swap(unexpected<Err> & lhs, unexpected<Err> & rhs);
+
+ private:
+ ErrorType m_error;
+ };
+
+ template<typename ErrorType>
+ unexpected(ErrorType) -> unexpected<ErrorType>;
+
+ /**
+ * @brief Compare two @p unexpected instances for equality
+ */
+ template<typename ErrorType1, typename ErrorType2>
+ constexpr bool operator==(unexpected<ErrorType1> const & lhs, unexpected<ErrorType2> const & rhs)
+ {
+ return lhs.m_error == rhs.m_error;
+ }
+
+ /**
+ * @brief Compare two @p unexpected instances for inequality
+ */
+ template<typename ErrorType1, typename ErrorType2>
+ constexpr bool operator!=(unexpected<ErrorType1> const & lhs, unexpected<ErrorType2> const & rhs)
+ {
+ return lhs.m_error != rhs.m_error;
+ }
+
+ /**
+ * @brief Swap the error values of two @p unexpected instances
+ */
+ template<
+ typename Err,
+ std::enable_if_t<std::is_swappable_v<Err>> * = nullptr>
+ void swap(unexpected<Err> & lhs, unexpected<Err> & rhs)
+ {
+ lhs.swap(rhs);
+ }
+
+ /**
+ * @brief A tag type for @p unexpected
+ */
+ struct unexpect_t
+ {
+ explicit unexpect_t() = default;
+ };
+
+ /**
+ * @brief A tap for @p unexpected
+ */
+ inline constexpr unexpect_t unexpect{};
+
+} // namespace wanda::std_ext
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/std_ext/optional.hpp b/source/lib/include/wanda/std_ext/optional.hpp
new file mode 100644
index 0000000..763e8ac
--- /dev/null
+++ b/source/lib/include/wanda/std_ext/optional.hpp
@@ -0,0 +1,65 @@
+/**
+ * @file optional.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_STD_EXT_OPTIONAL_HPP
+#define WANDA_STD_EXT_OPTIONAL_HPP
+
+#include <optional>
+
+namespace wanda::std_ext
+{
+ /**
+ * @brief A type to represent a computation that could fail
+ */
+ struct failable
+ {
+ /**
+ * @brief A factory to create a successful computation
+ */
+ constexpr static auto success() { return failable{false}; }
+
+ /**
+ * @brief A factory to create a failed computation
+ */
+ constexpr static auto failure() { return failable{true}; }
+
+ /**
+ * @brief Execute the given handler if the computation failed
+ */
+ template<typename Handler>
+ constexpr void operator||(Handler handler) const
+ {
+ if (m_failed)
+ {
+ handler();
+ }
+ }
+
+ private:
+ constexpr explicit failable(bool failed)
+ : m_failed{failed} {};
+ bool const m_failed;
+ };
+
+ /**
+ * @brief Unwrap the given optional object, if present, and pass it to the handler
+ *
+ * @return A successful computation iff. the object was present, a failed computation otherwise.
+ */
+ template<typename ObjectType, typename HandlerType>
+ auto with(std::optional<ObjectType> && object, HandlerType handler)
+ {
+ if (object)
+ {
+ handler(object.value());
+ return failable::success();
+ }
+ return failable::failure();
+ }
+
+} // namespace wanda::std_ext
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/environment.hpp b/source/lib/include/wanda/system/environment.hpp
new file mode 100644
index 0000000..8cd9ecf
--- /dev/null
+++ b/source/lib/include/wanda/system/environment.hpp
@@ -0,0 +1,61 @@
+/**
+ * @file environment.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_SYSTEM_ENVIRONMENT_HPP
+#define WANDA_SYSTEM_ENVIRONMENT_HPP
+
+#include <unistd.h>
+
+#include <map>
+#include <string>
+
+namespace wanda::system
+{
+ /**
+ * @brief A type to provide access to the runtime environment
+ */
+ struct environment
+ {
+ using map_type = std::map<std::string, std::string>;
+ using iterator = map_type::iterator;
+ using const_iterator = map_type::const_iterator;
+ using reference = map_type::reference;
+ using const_reference = map_type::const_reference;
+
+ /**
+ * @brief Construct a new environment from the given string array
+ */
+ explicit environment(char const * const * env = ::environ);
+
+ /**
+ * @brief Get the value of the given variable
+ *
+ * @return A mutable reference to the value of the given environment variable
+ */
+ std::string & operator[](std::string const & variable);
+
+ /**
+ * @brief Get the value of the given variable
+ *
+ * @return An immutable reference to the value of the given environment variable
+ */
+ std::string const & operator[](std::string const & variable) const;
+
+ iterator begin();
+ const_iterator begin() const;
+ const_iterator cbegin() const;
+
+ iterator end();
+ const_iterator end() const;
+ const_iterator cend() const;
+
+ private:
+ map_type m_cache{};
+ };
+
+} // namespace wanda::system
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/filesystem.hpp b/source/lib/include/wanda/system/filesystem.hpp
new file mode 100644
index 0000000..971db90
--- /dev/null
+++ b/source/lib/include/wanda/system/filesystem.hpp
@@ -0,0 +1,39 @@
+/**
+ * @file filesystem.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_SYSTEM_FILESYSTEM_HPP
+#define WANDA_SYSTEM_FILESYSTEM_HPP
+
+#include <filesystem>
+#include <optional>
+#include <vector>
+
+namespace wanda::system
+{
+ /**
+ * @brief Covenience alias for path lists
+ */
+ using path_list = std::vector<std::filesystem::path>;
+
+ /**
+ * @brief The default scan filter, allowing only regular files to pass
+ */
+ constexpr inline auto default_filter = [](std::filesystem::path const & path) {
+ return is_regular_file(path);
+ };
+
+ /**
+ * @brief Scan the given folder for files
+ */
+ std::optional<path_list> scan(std::filesystem::path folder, bool(filter)(std::filesystem::path const &) = default_filter);
+
+ /**
+ * @brief Pick a random path from the given list
+ */
+ std::filesystem::path random_pick(path_list const & paths);
+} // namespace wanda::system
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/logging.hpp b/source/lib/include/wanda/system/logging.hpp
new file mode 100644
index 0000000..8a9a90e
--- /dev/null
+++ b/source/lib/include/wanda/system/logging.hpp
@@ -0,0 +1,35 @@
+/**
+ * @file logging.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_SYSTEM_LOGGING_HPP
+#define WANDA_SYSTEM_LOGGING_HPP
+
+#include <spdlog/sinks/null_sink.h>
+#include <spdlog/spdlog.h>
+
+#include <memory>
+
+namespace wanda::system
+{
+ /**
+ * @brief A covenience alias to represent a handle for a logger
+ */
+ using logger_ptr = std::shared_ptr<spdlog::logger>;
+
+ /**
+ * @brief Initialize the shared logger
+ *
+ * @note The logger will only ever be initialized once, even if this function is called multiple times
+ */
+ void initialize_logger(spdlog::sink_ptr sink = std::make_shared<spdlog::sinks::null_sink_st>());
+
+ /**
+ * @brief Get the shared logger
+ */
+ logger_ptr get_logger();
+} // namespace wanda::system
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/magic.hpp b/source/lib/include/wanda/system/magic.hpp
new file mode 100644
index 0000000..a8ea1ba
--- /dev/null
+++ b/source/lib/include/wanda/system/magic.hpp
@@ -0,0 +1,58 @@
+#ifndef WANDA_SYSTEM_MAGIC_HPP
+#define WANDA_SYSTEM_MAGIC_HPP
+
+#include <magic.h>
+
+#include <filesystem>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+namespace wanda::system
+{
+ struct magic
+ {
+ struct closer
+ {
+ auto operator()(magic_t handle) const noexcept -> void
+ {
+ magic_close(handle);
+ }
+ };
+
+ enum struct mime_type
+ {
+ unknown,
+ image_jpeg,
+ image_png,
+ };
+
+ magic()
+ : m_handle{magic_open(MAGIC_MIME_TYPE)}
+ {
+ magic_load(m_handle.get(), nullptr);
+ }
+
+ auto type(std::filesystem::path path) -> mime_type
+ {
+ auto magic_type = std::string{magic_file(m_handle.get(), path.native().c_str())};
+
+ if (magic_type == "image/jpeg")
+ {
+ return mime_type::image_jpeg;
+ }
+ else if (magic_type == "image/png")
+ {
+ return mime_type::image_png;
+ }
+
+ return mime_type::unknown;
+ }
+
+ private:
+ std::unique_ptr<std::remove_pointer_t<magic_t>, closer> m_handle;
+ };
+
+} // namespace wanda::system
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/setting.hpp b/source/lib/include/wanda/system/setting.hpp
new file mode 100644
index 0000000..e0be3f4
--- /dev/null
+++ b/source/lib/include/wanda/system/setting.hpp
@@ -0,0 +1,156 @@
+/**
+ * @file setting.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_SYSTEM_SETTING_HPP
+#define WANDA_SYSTEM_SETTING_HPP
+
+#include "wanda/meta/deferred_failure.hpp"
+#include "wanda/meta/type_wrapper.hpp"
+
+#include <gio/gio.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace wanda::system
+{
+ struct setting;
+
+ /**
+ * @brief A convenience type to represent setting keys
+ */
+ using key = meta::type_wrapper<std::string, struct KeyTag>;
+
+ namespace literals
+ {
+ /**
+ * @brief UDL to create setting keys
+ */
+ key operator""_key(char const * str, std::size_t len);
+
+ /**
+ * @brief UDL to create setting schemas
+ */
+ std::optional<setting> operator""_setting(char const * str, std::size_t lent);
+ } // namespace literals
+
+ /**
+ * @brief A simple wrapper for GSettings Schemas
+ */
+ struct setting
+ {
+ struct entry
+ {
+ using value_type = std::variant<std::monostate, bool, std::int32_t, std::int64_t, std::uint32_t, std::uint64_t, double, std::string, std::vector<std::string>>;
+
+ /**
+ * @brief Get the value of the settings entry
+ */
+ value_type operator*() const;
+
+ /**
+ * @brief Assign the given @p value to the settings entry
+ *
+ * @returns @p true iff. the value could be successfully assigned
+ */
+ template<typename Type>
+ bool operator=(Type value)
+ {
+ struct setting_applier
+ {
+ setting_applier(GSettings * setting, gchar const * key, Type value) noexcept
+ : m_result{[&] {
+ if constexpr (std::is_same_v<Type, bool>)
+ {
+ return g_settings_set_boolean(setting, key, value);
+ }
+ else if constexpr (std::is_same_v<Type, std::int32_t>)
+ {
+ return g_settings_set_int(setting, key, value);
+ }
+ else if constexpr (std::is_same_v<Type, std::int64_t>)
+ {
+ return g_settings_set_int64(setting, key, value);
+ }
+ else if constexpr (std::is_same_v<Type, std::uint32_t>)
+ {
+ return g_settings_set_uint(setting, key, value);
+ }
+ else if constexpr (std::is_same_v<Type, std::uint64_t>)
+ {
+ return g_settings_set_uint64(setting, key, value);
+ }
+ else if constexpr (std::is_same_v<Type, double>)
+ {
+ return g_settings_set_double(setting, key, value);
+ }
+ else if constexpr (std::is_same_v<Type, std::string>)
+ {
+ return g_settings_set_string(setting, key, value.c_str());
+ }
+ else if constexpr (std::is_same_v<Type, std::vector<std::string>>)
+ {
+ auto temp = std::vector<gchar const *>{value.size() + 1};
+ std::transform(value.begin(), value.end(), temp.begin(), [](auto const & str) { return str.c_str(); });
+ return g_settings_set_strv(setting, key, temp.data());
+ }
+ }()}
+ {
+ }
+
+ ~setting_applier()
+ {
+ g_settings_sync();
+ }
+
+ operator bool() const
+ {
+ return m_result;
+ }
+
+ private:
+ gboolean const m_result;
+ };
+
+ return setting_applier{m_settings.get(), m_key.get().c_str(), value};
+ }
+
+ private:
+ entry(setting const & schema, key key);
+
+ std::unique_ptr<GSettings, decltype(&g_object_unref)> m_settings;
+
+ key m_key;
+
+ friend setting;
+ };
+
+ /**
+ * @brief Get the entry for the given key
+ *
+ * @return An <code>std::optional</code> wrapping the entry associated with
+ * the given key, or an empty <code>std::optional</code> if the desired key
+ * does not exist in the setting's schema.
+ */
+ std::optional<entry> operator[](key key) const;
+
+ private:
+ explicit setting(GSettingsSchema * schema);
+
+ std::unique_ptr<GSettingsSchema, decltype(&g_settings_schema_unref)> m_schema;
+
+ friend std::optional<setting> literals::operator""_setting(char const *, std::size_t);
+ };
+
+} // namespace wanda::system
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/wallpaper.hpp b/source/lib/include/wanda/system/wallpaper.hpp
new file mode 100644
index 0000000..7965fb0
--- /dev/null
+++ b/source/lib/include/wanda/system/wallpaper.hpp
@@ -0,0 +1,24 @@
+/**
+ * @file wallpaper.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_SYSTEM_WALLPAPER_HPP
+#define WANDA_SYSTEM_WALLPAPER_HPP
+
+#include <spdlog/sinks/null_sink.h>
+#include <spdlog/spdlog.h>
+
+#include <filesystem>
+#include <memory>
+
+namespace wanda::system
+{
+ /**
+ * @brief Set the wallpaper to the file specified by the given path
+ */
+ void set_wallpaper(std::filesystem::path wallpaper);
+} // namespace wanda::system
+
+#endif \ No newline at end of file
diff --git a/source/lib/include/wanda/system/xdg.hpp b/source/lib/include/wanda/system/xdg.hpp
new file mode 100644
index 0000000..ae01feb
--- /dev/null
+++ b/source/lib/include/wanda/system/xdg.hpp
@@ -0,0 +1,40 @@
+/**
+ * @file xdg.hpp
+ * @author Felix Morgner (felix.morgner@gmail.com)
+ * @since 1.0.0
+ */
+
+#ifndef WANDA_SYSTEM_XDG_HPP
+#define WANDA_SYSTEM_XDG_HPP
+
+#include "wanda/system/environment.hpp"
+
+#include <cstddef>
+#include <filesystem>
+#include <type_traits>
+
+namespace wanda::system
+{
+ /**
+ * @brief An @p enum to represet the standardized XDG directories
+ */
+ enum struct xdg_directory : std::underlying_type_t<std::byte>
+ {
+ data_home,
+ config_home,
+ cache_home,
+ runtime_dir,
+ };
+
+ /**
+ * @brief Get the name of the environment variable associated with the given XDG directory
+ */
+ std::string xdg_variable(xdg_directory directory);
+
+ /**
+ * @brief Get the path to the given @p directory given the provided @p environment
+ */
+ std::filesystem::path xdg_path_for(xdg_directory directory, environment const & environment);
+} // namespace wanda::system
+
+#endif \ No newline at end of file