From 577fc0845718ed8ad5bebf02a277c0579a817f77 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 17 May 2024 17:58:38 +0200 Subject: wanda: restructure source layout --- source/CMakeLists.txt | 7 +- source/app/CMakeLists.txt | 6 - source/app/wandac/CMakeLists.txt | 37 --- source/app/wandac/include/wandac/cli.hpp | 27 --- source/app/wandac/include/wandac/listener.hpp | 26 --- source/app/wandac/src/cli.cpp | 30 --- source/app/wandac/src/listener.cpp | 30 --- source/app/wandac/src/main.cpp | 43 ---- source/app/wandad/CMakeLists.txt | 21 -- source/app/wandad/src/main.cpp | 148 ------------ source/apps/CMakeLists.txt | 54 +++++ source/apps/include/wanda/wandac/cli.hpp | 27 +++ source/apps/include/wanda/wandac/listener.hpp | 26 +++ source/apps/src/wandac/cli.cpp | 30 +++ source/apps/src/wandac/listener.cpp | 29 +++ source/apps/src/wandac/main.cpp | 43 ++++ source/apps/src/wandad/main.cpp | 148 ++++++++++++ source/apps/tests/wandac/cli.cpp | 80 +++++++ source/lib/CMakeLists.txt | 76 +++++- source/lib/control/CMakeLists.txt | 43 ---- .../control/include/wanda/control/commander.hpp | 67 ------ .../control/include/wanda/control/connection.hpp | 116 ---------- .../control/include/wanda/control/interface.hpp | 97 -------- source/lib/control/src/commander.cpp | 80 ------- source/lib/control/src/connection.cpp | 130 ----------- source/lib/control/src/interface.cpp | 160 ------------- source/lib/include/wanda/control/commander.hpp | 67 ++++++ source/lib/include/wanda/control/connection.hpp | 116 ++++++++++ source/lib/include/wanda/control/interface.hpp | 97 ++++++++ source/lib/include/wanda/meta/deferred_failure.hpp | 21 ++ source/lib/include/wanda/meta/keyed.hpp | 28 +++ source/lib/include/wanda/meta/type_wrapper.hpp | 47 ++++ source/lib/include/wanda/proto/command.hpp | 46 ++++ source/lib/include/wanda/proto/message.hpp | 84 +++++++ source/lib/include/wanda/proto/version.hpp | 12 + source/lib/include/wanda/std_ext/expected.hpp | 256 +++++++++++++++++++++ source/lib/include/wanda/std_ext/optional.hpp | 65 ++++++ source/lib/include/wanda/system/environment.hpp | 61 +++++ source/lib/include/wanda/system/filesystem.hpp | 39 ++++ source/lib/include/wanda/system/logging.hpp | 35 +++ source/lib/include/wanda/system/magic.hpp | 58 +++++ source/lib/include/wanda/system/setting.hpp | 156 +++++++++++++ source/lib/include/wanda/system/wallpaper.hpp | 24 ++ source/lib/include/wanda/system/xdg.hpp | 40 ++++ source/lib/meta/CMakeLists.txt | 31 --- .../meta/include/wanda/meta/deferred_failure.hpp | 21 -- source/lib/meta/include/wanda/meta/keyed.hpp | 28 --- .../lib/meta/include/wanda/meta/type_wrapper.hpp | 47 ---- source/lib/proto/CMakeLists.txt | 38 --- source/lib/proto/include/wanda/proto/command.hpp | 46 ---- source/lib/proto/include/wanda/proto/message.hpp | 84 ------- source/lib/proto/include/wanda/proto/version.hpp | 12 - source/lib/proto/src/command.cpp | 47 ---- source/lib/proto/src/message.cpp | 82 ------- source/lib/src/control/commander.cpp | 80 +++++++ source/lib/src/control/connection.cpp | 130 +++++++++++ source/lib/src/control/interface.cpp | 160 +++++++++++++ source/lib/src/proto/command.cpp | 47 ++++ source/lib/src/proto/message.cpp | 82 +++++++ source/lib/src/system/environment.cpp | 71 ++++++ source/lib/src/system/filesystem.cpp | 31 +++ source/lib/src/system/logging.cpp | 21 ++ source/lib/src/system/setting.cpp | 102 ++++++++ source/lib/src/system/wallpaper.cpp | 91 ++++++++ source/lib/src/system/xdg.cpp | 46 ++++ source/lib/std_ext/CMakeLists.txt | 31 --- .../lib/std_ext/include/wanda/std_ext/expected.hpp | 256 --------------------- .../lib/std_ext/include/wanda/std_ext/optional.hpp | 65 ------ source/lib/system/CMakeLists.txt | 63 ----- .../system/include/wanda/system/environment.hpp | 61 ----- .../lib/system/include/wanda/system/filesystem.hpp | 39 ---- source/lib/system/include/wanda/system/logging.hpp | 35 --- source/lib/system/include/wanda/system/magic.hpp | 58 ----- source/lib/system/include/wanda/system/setting.hpp | 156 ------------- .../lib/system/include/wanda/system/wallpaper.hpp | 24 -- source/lib/system/include/wanda/system/xdg.hpp | 40 ---- source/lib/system/src/environment.cpp | 71 ------ source/lib/system/src/filesystem.cpp | 31 --- source/lib/system/src/logging.cpp | 21 -- source/lib/system/src/setting.cpp | 102 -------- source/lib/system/src/wallpaper.cpp | 91 -------- source/lib/system/src/xdg.cpp | 46 ---- source/lib/tests/xdg.cpp | 171 ++++++++++++++ source/tests/CMakeLists.txt | 4 - source/tests/app/wandac/CMakeLists.txt | 11 - source/tests/app/wandac/src/cli.cpp | 80 ------- source/tests/lib/system/CMakeLists.txt | 11 - source/tests/lib/system/src/xdg.cpp | 171 -------------- 88 files changed, 2790 insertions(+), 2978 deletions(-) delete mode 100644 source/app/CMakeLists.txt delete mode 100644 source/app/wandac/CMakeLists.txt delete mode 100644 source/app/wandac/include/wandac/cli.hpp delete mode 100644 source/app/wandac/include/wandac/listener.hpp delete mode 100644 source/app/wandac/src/cli.cpp delete mode 100644 source/app/wandac/src/listener.cpp delete mode 100644 source/app/wandac/src/main.cpp delete mode 100644 source/app/wandad/CMakeLists.txt delete mode 100644 source/app/wandad/src/main.cpp create mode 100644 source/apps/CMakeLists.txt create mode 100644 source/apps/include/wanda/wandac/cli.hpp create mode 100644 source/apps/include/wanda/wandac/listener.hpp create mode 100644 source/apps/src/wandac/cli.cpp create mode 100644 source/apps/src/wandac/listener.cpp create mode 100644 source/apps/src/wandac/main.cpp create mode 100644 source/apps/src/wandad/main.cpp create mode 100644 source/apps/tests/wandac/cli.cpp delete mode 100644 source/lib/control/CMakeLists.txt delete mode 100644 source/lib/control/include/wanda/control/commander.hpp delete mode 100644 source/lib/control/include/wanda/control/connection.hpp delete mode 100644 source/lib/control/include/wanda/control/interface.hpp delete mode 100644 source/lib/control/src/commander.cpp delete mode 100644 source/lib/control/src/connection.cpp delete mode 100644 source/lib/control/src/interface.cpp create mode 100644 source/lib/include/wanda/control/commander.hpp create mode 100644 source/lib/include/wanda/control/connection.hpp create mode 100644 source/lib/include/wanda/control/interface.hpp create mode 100644 source/lib/include/wanda/meta/deferred_failure.hpp create mode 100644 source/lib/include/wanda/meta/keyed.hpp create mode 100644 source/lib/include/wanda/meta/type_wrapper.hpp create mode 100644 source/lib/include/wanda/proto/command.hpp create mode 100644 source/lib/include/wanda/proto/message.hpp create mode 100644 source/lib/include/wanda/proto/version.hpp create mode 100644 source/lib/include/wanda/std_ext/expected.hpp create mode 100644 source/lib/include/wanda/std_ext/optional.hpp create mode 100644 source/lib/include/wanda/system/environment.hpp create mode 100644 source/lib/include/wanda/system/filesystem.hpp create mode 100644 source/lib/include/wanda/system/logging.hpp create mode 100644 source/lib/include/wanda/system/magic.hpp create mode 100644 source/lib/include/wanda/system/setting.hpp create mode 100644 source/lib/include/wanda/system/wallpaper.hpp create mode 100644 source/lib/include/wanda/system/xdg.hpp delete mode 100644 source/lib/meta/CMakeLists.txt delete mode 100644 source/lib/meta/include/wanda/meta/deferred_failure.hpp delete mode 100644 source/lib/meta/include/wanda/meta/keyed.hpp delete mode 100644 source/lib/meta/include/wanda/meta/type_wrapper.hpp delete mode 100644 source/lib/proto/CMakeLists.txt delete mode 100644 source/lib/proto/include/wanda/proto/command.hpp delete mode 100644 source/lib/proto/include/wanda/proto/message.hpp delete mode 100644 source/lib/proto/include/wanda/proto/version.hpp delete mode 100644 source/lib/proto/src/command.cpp delete mode 100644 source/lib/proto/src/message.cpp create mode 100644 source/lib/src/control/commander.cpp create mode 100644 source/lib/src/control/connection.cpp create mode 100644 source/lib/src/control/interface.cpp create mode 100644 source/lib/src/proto/command.cpp create mode 100644 source/lib/src/proto/message.cpp create mode 100644 source/lib/src/system/environment.cpp create mode 100644 source/lib/src/system/filesystem.cpp create mode 100644 source/lib/src/system/logging.cpp create mode 100644 source/lib/src/system/setting.cpp create mode 100644 source/lib/src/system/wallpaper.cpp create mode 100644 source/lib/src/system/xdg.cpp delete mode 100644 source/lib/std_ext/CMakeLists.txt delete mode 100644 source/lib/std_ext/include/wanda/std_ext/expected.hpp delete mode 100644 source/lib/std_ext/include/wanda/std_ext/optional.hpp delete mode 100644 source/lib/system/CMakeLists.txt delete mode 100644 source/lib/system/include/wanda/system/environment.hpp delete mode 100644 source/lib/system/include/wanda/system/filesystem.hpp delete mode 100644 source/lib/system/include/wanda/system/logging.hpp delete mode 100644 source/lib/system/include/wanda/system/magic.hpp delete mode 100644 source/lib/system/include/wanda/system/setting.hpp delete mode 100644 source/lib/system/include/wanda/system/wallpaper.hpp delete mode 100644 source/lib/system/include/wanda/system/xdg.hpp delete mode 100644 source/lib/system/src/environment.cpp delete mode 100644 source/lib/system/src/filesystem.cpp delete mode 100644 source/lib/system/src/logging.cpp delete mode 100644 source/lib/system/src/setting.cpp delete mode 100644 source/lib/system/src/wallpaper.cpp delete mode 100644 source/lib/system/src/xdg.cpp create mode 100644 source/lib/tests/xdg.cpp delete mode 100644 source/tests/CMakeLists.txt delete mode 100644 source/tests/app/wandac/CMakeLists.txt delete mode 100644 source/tests/app/wandac/src/cli.cpp delete mode 100644 source/tests/lib/system/CMakeLists.txt delete mode 100644 source/tests/lib/system/src/xdg.cpp (limited to 'source') diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 77c6bc3..ad7f819 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -9,14 +9,11 @@ project("wanda" find_package("Boost" REQUIRED COMPONENTS "headers") find_package("Catch2" REQUIRED COMPONENTS "Catch2WithMain") -find_package("JPEG" REQUIRED) -find_package("lyra" REQUIRED) find_package("spdlog" REQUIRED) -find_package("PNG" REQUIRED) + include("CTest") include("Catch") -add_subdirectory("app") +add_subdirectory("apps") add_subdirectory("lib") -add_subdirectory("tests") diff --git a/source/app/CMakeLists.txt b/source/app/CMakeLists.txt deleted file mode 100644 index 1ac7ab1..0000000 --- a/source/app/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -include("CheckIPOSupported") - -check_ipo_supported(RESULT "WANDA_IPO_SUPPORTED") - -add_subdirectory("wandac") -add_subdirectory("wandad") \ No newline at end of file diff --git a/source/app/wandac/CMakeLists.txt b/source/app/wandac/CMakeLists.txt deleted file mode 100644 index 71aff36..0000000 --- a/source/app/wandac/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -add_library("${PROJECT_NAME}c-components" OBJECT - "src/cli.cpp" - "src/listener.cpp" -) - -target_include_directories("${PROJECT_NAME}c-components" PUBLIC - "$" -) - -target_link_libraries("${PROJECT_NAME}c-components" PUBLIC - "wanda::control" - "wanda::proto" - - "bfg::lyra" - "Boost::headers" -) - -add_executable("wandac" - "src/main.cpp" -) - - -target_link_libraries("${PROJECT_NAME}c" PRIVATE - "${PROJECT_NAME}c-components" - - "wanda::system" - - "spdlog::spdlog_header_only" -) - -set_target_properties("wandac" PROPERTIES - CXX_STANDARD_REQUIRED YES - CXX_EXTENSIONS OFF - INTERPROCEDURAL_OPTIMIZATION ${WANDA_IPO_SUPPORTED} -) - -install(TARGETS "wandac") diff --git a/source/app/wandac/include/wandac/cli.hpp b/source/app/wandac/include/wandac/cli.hpp deleted file mode 100644 index c04b138..0000000 --- a/source/app/wandac/include/wandac/cli.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef WANDA_APP_WANDAC_CLI_HPP -#define WANDA_APP_WANDAC_CLI_HPP - -#include -#include - -#include -#include - -namespace wandac -{ - - struct cli - { - auto parse(lyra::args arguments, std::ostream & error_stream) -> bool; - auto print_usage(std::ostream & output_stream) -> void; - - std::string command{}; - bool help{}; - - private: - lyra::cli_parser parser{}; - }; - -} // namespace wandac - -#endif \ No newline at end of file diff --git a/source/app/wandac/include/wandac/listener.hpp b/source/app/wandac/include/wandac/listener.hpp deleted file mode 100644 index 49b75b6..0000000 --- a/source/app/wandac/include/wandac/listener.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef WANDA_APP_WANDAC_LISTENER_HPP -#define WANDA_APP_WANDAC_LISTENER_HPP - -#include "wandac/cli.hpp" - -#include - -#include - -namespace wandac -{ - - struct listener : wanda::control::commander::listener - { - listener(wandac::cli const & cli, boost::asio::io_context & service); - - auto on_connected(wanda::control::commander & commander) -> void override ; - - private: - wandac::cli const & m_cli; - boost::asio::io_context & m_service; - }; - -} // namespace wandac - -#endif \ No newline at end of file diff --git a/source/app/wandac/src/cli.cpp b/source/app/wandac/src/cli.cpp deleted file mode 100644 index 954ca63..0000000 --- a/source/app/wandac/src/cli.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "wandac/cli.hpp" - -#include -#include - -#include - -namespace wandac -{ - - auto cli::parse(lyra::args arguments, std::ostream & error_stream) -> bool - { - parser |= // - lyra::help(help) | // - lyra::arg{command, "command"}("The command to send to the deamon").required(); - - auto result = parser.parse(arguments); - - if (!result) - { - error_stream << "Error while processing command line arguments: " << result.message() << '\n' << parser << '\n'; - return false; - } - - return true; - } - - auto cli::print_usage(std::ostream & output_stream) -> void { output_stream << parser << '\n'; } - -} // namespace wandac diff --git a/source/app/wandac/src/listener.cpp b/source/app/wandac/src/listener.cpp deleted file mode 100644 index 9655390..0000000 --- a/source/app/wandac/src/listener.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "wandac/listener.hpp" - -#include "wanda/control/commander.hpp" -#include "wandac/cli.hpp" - -#include -#include - -#include - -namespace wandac -{ - - listener::listener(wandac::cli const & cli, boost::asio::io_context & service) - : m_cli{cli} - , m_service{service} - { - } - - auto listener::on_connected(wanda::control::commander & commander) -> void - { - if (m_cli.command == "change") - { - commander.send(wanda::proto::make_change_command()); - - post(m_service, [&] { commander.stop(); }); - } - } - -} // namespace wandac diff --git a/source/app/wandac/src/main.cpp b/source/app/wandac/src/main.cpp deleted file mode 100644 index 4e68e0b..0000000 --- a/source/app/wandac/src/main.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "wandac/cli.hpp" -#include "wandac/listener.hpp" - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -int main(int argc, char const * const * argv) -{ - auto cli = wandac::cli{}; - if (!cli.parse({argc, argv}, std::cerr)) - { - return EXIT_FAILURE; - } - else if (cli.help) - { - cli.print_usage(std::cout); - return EXIT_SUCCESS; - } - - wanda::system::initialize_logger(std::make_shared()); - - auto environment = wanda::system::environment{}; - auto runtime_dir = wanda::system::xdg_path_for(wanda::system::xdg_directory::runtime_dir, environment); - auto interface = runtime_dir / ".wanda_interface"; - auto service = boost::asio::io_context{}; - auto listener = wandac::listener{cli, service}; - - auto commander = wanda::control::commander{service, interface, listener}; - - wanda::system::get_logger()->info("trying to connect to wanda control interface on '{}'", interface.native()); - commander.start(); - - service.run(); -} diff --git a/source/app/wandad/CMakeLists.txt b/source/app/wandad/CMakeLists.txt deleted file mode 100644 index 9bf0ec5..0000000 --- a/source/app/wandad/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_executable("wandad" - "src/main.cpp" -) - -target_link_libraries("wandad" PRIVATE - "wanda::control" - "wanda::proto" - "wanda::std_ext" - "wanda::system" - - "bfg::lyra" - "spdlog::spdlog_header_only" -) - -set_target_properties("wandad" PROPERTIES - CXX_STANDARD_REQUIRED YES - CXX_EXTENSIONS OFF - INTERPROCEDURAL_OPTIMIZATION ${WANDA_IPO_SUPPORTED} -) - -install(TARGETS "wandad") diff --git a/source/app/wandad/src/main.cpp b/source/app/wandad/src/main.cpp deleted file mode 100644 index 9feac82..0000000 --- a/source/app/wandad/src/main.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace -{ - constexpr auto image_filter = [](auto const & path) { - static auto const extensions = std::set{ - std::filesystem::path{".jpg"}, - std::filesystem::path{".png"}, - }; - - if (!std::filesystem::is_regular_file(path)) - { - return false; - } - - return extensions.find(path.extension()) != extensions.cend(); - }; - - struct cli - { - std::string wallpaper_directory{}; - bool help{}; - - lyra::cli_parser parser{}; - - auto parse(int argc, char const * const * argv, std::ostream & error) - { - parser |= // - lyra::help(help) | // - lyra::arg{wallpaper_directory, "directory"}("The wallpaper source directory").required(); - - auto result = parser.parse({argc, argv}); - - if (!result) - { - error << "Error while processing command line arguments: " << result.message() << '\n' << parser << '\n'; - return false; - } - - return true; - } - }; - - struct listener : wanda::control::interface::listener - { - listener(std::vector const & wallpapers) - : m_wallpapers{wallpapers} - { - } - - void on_received(wanda::control::interface & interface, wanda::proto::command command) override - { - switch (command.id) - { - case wanda::proto::command_id::change: { - auto wallpaper = wanda::system::random_pick(m_wallpapers); - wanda::system::get_logger()->info("changing wallpaper to '{}'", wallpaper.native()); - wanda::system::set_wallpaper(wallpaper); - break; - } - default: - wanda::system::get_logger()->error("received unknown command '{}'", static_cast(command.id)); - } - } - - private: - std::vector const & m_wallpapers; - }; - -} // namespace - -int main(int argc, char const * const * argv) -{ - using namespace wanda::std_ext; - - auto cli = ::cli{}; - if (!cli.parse(argc, argv, std::cerr)) - { - return EXIT_FAILURE; - } - else if (cli.help) - { - std::cout << cli.parser << '\n'; - return EXIT_SUCCESS; - } - - wanda::system::initialize_logger(std::make_shared()); - wanda::system::get_logger()->info("wanda is starting up"); - - with(wanda::system::scan({cli.wallpaper_directory}, image_filter), - [&](auto const & list) { - auto service = boost::asio::io_context{}; - auto socket_path = - wanda::system::xdg_path_for(wanda::system::xdg_directory::runtime_dir, wanda::system::environment{}) / - ".wanda_interface"; - - wanda::system::get_logger()->info("starting control interface on '{}'", socket_path.native()); - auto listener = ::listener{list}; - auto interface = wanda::control::make_interface(service, socket_path, listener); - - if (!interface) - { - wanda::system::get_logger()->error("failed to start control interface"); - return; - } - - if (interface->start()) - { - return; - } - - auto signals = boost::asio::signal_set{service, SIGINT, SIGTERM}; - signals.async_wait([&](auto const & error, auto const signal) { - if (!error) - { - wanda::system::get_logger()->info("Received signal {}. terminating...", signal); - interface->shutdown(); - service.stop(); - } - }); - - auto wallpaper = wanda::system::random_pick(list); - wanda::system::set_wallpaper(wallpaper); - - service.run(); - }) || - [&] { wanda::system::get_logger()->error("wallpaper directory does not exist"); }; -} diff --git a/source/apps/CMakeLists.txt b/source/apps/CMakeLists.txt new file mode 100644 index 0000000..2da5537 --- /dev/null +++ b/source/apps/CMakeLists.txt @@ -0,0 +1,54 @@ +include("CheckIPOSupported") + +check_ipo_supported(RESULT "WANDA_IPO_SUPPORTED") + +find_package("lyra" REQUIRED) + +add_library("wandac-components" OBJECT + "src/wandac/cli.cpp" + "src/wandac/listener.cpp" +) + +target_link_libraries("wandac-components" PUBLIC + "wanda" + "bfg::lyra" +) + +target_include_directories("wandac-components" PUBLIC "include") + +add_executable("wandac" + "src/wandac/main.cpp" +) + +target_link_libraries("wandac" PRIVATE + "wandac-components" +) + +add_executable("wandac-tests" + "tests/wandac/cli.cpp" +) + +target_link_libraries("wandac-tests" PRIVATE + "wandac-components" + "Catch2::Catch2WithMain" +) + +catch_discover_tests("wandac-tests") + +add_executable("wandad" + "src/wandad/main.cpp" +) + +target_link_libraries("wandad" PRIVATE + "wanda" + "bfg::lyra" +) + +set_target_properties("wandac" "wandad" PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ${WANDA_IPO_SUPPORTED} +) + +install(TARGETS + "wandac" + "wandad" +) diff --git a/source/apps/include/wanda/wandac/cli.hpp b/source/apps/include/wanda/wandac/cli.hpp new file mode 100644 index 0000000..c04b138 --- /dev/null +++ b/source/apps/include/wanda/wandac/cli.hpp @@ -0,0 +1,27 @@ +#ifndef WANDA_APP_WANDAC_CLI_HPP +#define WANDA_APP_WANDAC_CLI_HPP + +#include +#include + +#include +#include + +namespace wandac +{ + + struct cli + { + auto parse(lyra::args arguments, std::ostream & error_stream) -> bool; + auto print_usage(std::ostream & output_stream) -> void; + + std::string command{}; + bool help{}; + + private: + lyra::cli_parser parser{}; + }; + +} // namespace wandac + +#endif \ No newline at end of file diff --git a/source/apps/include/wanda/wandac/listener.hpp b/source/apps/include/wanda/wandac/listener.hpp new file mode 100644 index 0000000..28b0cfc --- /dev/null +++ b/source/apps/include/wanda/wandac/listener.hpp @@ -0,0 +1,26 @@ +#ifndef WANDA_APP_WANDAC_LISTENER_HPP +#define WANDA_APP_WANDAC_LISTENER_HPP + +#include "wanda/wandac/cli.hpp" + +#include + +#include + +namespace wandac +{ + + struct listener : wanda::control::commander::listener + { + listener(wandac::cli const & cli, boost::asio::io_context & service); + + auto on_connected(wanda::control::commander & commander) -> void override ; + + private: + wandac::cli const & m_cli; + boost::asio::io_context & m_service; + }; + +} // namespace wandac + +#endif \ No newline at end of file diff --git a/source/apps/src/wandac/cli.cpp b/source/apps/src/wandac/cli.cpp new file mode 100644 index 0000000..6be1e18 --- /dev/null +++ b/source/apps/src/wandac/cli.cpp @@ -0,0 +1,30 @@ +#include "wanda/wandac/cli.hpp" + +#include +#include + +#include + +namespace wandac +{ + + auto cli::parse(lyra::args arguments, std::ostream & error_stream) -> bool + { + parser |= // + lyra::help(help) | // + lyra::arg{command, "command"}("The command to send to the deamon").required(); + + auto result = parser.parse(arguments); + + if (!result) + { + error_stream << "Error while processing command line arguments: " << result.message() << '\n' << parser << '\n'; + return false; + } + + return true; + } + + auto cli::print_usage(std::ostream & output_stream) -> void { output_stream << parser << '\n'; } + +} // namespace wandac diff --git a/source/apps/src/wandac/listener.cpp b/source/apps/src/wandac/listener.cpp new file mode 100644 index 0000000..29ccace --- /dev/null +++ b/source/apps/src/wandac/listener.cpp @@ -0,0 +1,29 @@ +#include "wanda/wandac/listener.hpp" + +#include "wanda/wandac/cli.hpp" + +#include +#include + +#include + +namespace wandac +{ + + listener::listener(wandac::cli const & cli, boost::asio::io_context & service) + : m_cli{cli} + , m_service{service} + { + } + + auto listener::on_connected(wanda::control::commander & commander) -> void + { + if (m_cli.command == "change") + { + commander.send(wanda::proto::make_change_command()); + + post(m_service, [&] { commander.stop(); }); + } + } + +} // namespace wandac diff --git a/source/apps/src/wandac/main.cpp b/source/apps/src/wandac/main.cpp new file mode 100644 index 0000000..1533a62 --- /dev/null +++ b/source/apps/src/wandac/main.cpp @@ -0,0 +1,43 @@ +#include "wanda/wandac/cli.hpp" +#include "wanda/wandac/listener.hpp" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +int main(int argc, char const * const * argv) +{ + auto cli = wandac::cli{}; + if (!cli.parse({argc, argv}, std::cerr)) + { + return EXIT_FAILURE; + } + else if (cli.help) + { + cli.print_usage(std::cout); + return EXIT_SUCCESS; + } + + wanda::system::initialize_logger(std::make_shared()); + + auto environment = wanda::system::environment{}; + auto runtime_dir = wanda::system::xdg_path_for(wanda::system::xdg_directory::runtime_dir, environment); + auto interface = runtime_dir / ".wanda_interface"; + auto service = boost::asio::io_context{}; + auto listener = wandac::listener{cli, service}; + + auto commander = wanda::control::commander{service, interface, listener}; + + wanda::system::get_logger()->info("trying to connect to wanda control interface on '{}'", interface.native()); + commander.start(); + + service.run(); +} diff --git a/source/apps/src/wandad/main.cpp b/source/apps/src/wandad/main.cpp new file mode 100644 index 0000000..9feac82 --- /dev/null +++ b/source/apps/src/wandad/main.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ + constexpr auto image_filter = [](auto const & path) { + static auto const extensions = std::set{ + std::filesystem::path{".jpg"}, + std::filesystem::path{".png"}, + }; + + if (!std::filesystem::is_regular_file(path)) + { + return false; + } + + return extensions.find(path.extension()) != extensions.cend(); + }; + + struct cli + { + std::string wallpaper_directory{}; + bool help{}; + + lyra::cli_parser parser{}; + + auto parse(int argc, char const * const * argv, std::ostream & error) + { + parser |= // + lyra::help(help) | // + lyra::arg{wallpaper_directory, "directory"}("The wallpaper source directory").required(); + + auto result = parser.parse({argc, argv}); + + if (!result) + { + error << "Error while processing command line arguments: " << result.message() << '\n' << parser << '\n'; + return false; + } + + return true; + } + }; + + struct listener : wanda::control::interface::listener + { + listener(std::vector const & wallpapers) + : m_wallpapers{wallpapers} + { + } + + void on_received(wanda::control::interface & interface, wanda::proto::command command) override + { + switch (command.id) + { + case wanda::proto::command_id::change: { + auto wallpaper = wanda::system::random_pick(m_wallpapers); + wanda::system::get_logger()->info("changing wallpaper to '{}'", wallpaper.native()); + wanda::system::set_wallpaper(wallpaper); + break; + } + default: + wanda::system::get_logger()->error("received unknown command '{}'", static_cast(command.id)); + } + } + + private: + std::vector const & m_wallpapers; + }; + +} // namespace + +int main(int argc, char const * const * argv) +{ + using namespace wanda::std_ext; + + auto cli = ::cli{}; + if (!cli.parse(argc, argv, std::cerr)) + { + return EXIT_FAILURE; + } + else if (cli.help) + { + std::cout << cli.parser << '\n'; + return EXIT_SUCCESS; + } + + wanda::system::initialize_logger(std::make_shared()); + wanda::system::get_logger()->info("wanda is starting up"); + + with(wanda::system::scan({cli.wallpaper_directory}, image_filter), + [&](auto const & list) { + auto service = boost::asio::io_context{}; + auto socket_path = + wanda::system::xdg_path_for(wanda::system::xdg_directory::runtime_dir, wanda::system::environment{}) / + ".wanda_interface"; + + wanda::system::get_logger()->info("starting control interface on '{}'", socket_path.native()); + auto listener = ::listener{list}; + auto interface = wanda::control::make_interface(service, socket_path, listener); + + if (!interface) + { + wanda::system::get_logger()->error("failed to start control interface"); + return; + } + + if (interface->start()) + { + return; + } + + auto signals = boost::asio::signal_set{service, SIGINT, SIGTERM}; + signals.async_wait([&](auto const & error, auto const signal) { + if (!error) + { + wanda::system::get_logger()->info("Received signal {}. terminating...", signal); + interface->shutdown(); + service.stop(); + } + }); + + auto wallpaper = wanda::system::random_pick(list); + wanda::system::set_wallpaper(wallpaper); + + service.run(); + }) || + [&] { wanda::system::get_logger()->error("wallpaper directory does not exist"); }; +} diff --git a/source/apps/tests/wandac/cli.cpp b/source/apps/tests/wandac/cli.cpp new file mode 100644 index 0000000..57d2a8f --- /dev/null +++ b/source/apps/tests/wandac/cli.cpp @@ -0,0 +1,80 @@ +#include "wanda/wandac/cli.hpp" + +#include +#include +#include + +#include +#include + +using namespace std::string_literals; + +namespace wanda::tests::app::wandac +{ + + template + auto make_argument_list(Ts const & ... args) -> lyra::args + { + return {"wanda"s, static_cast(args)...}; + } + + SCENARIO("Empty argument list parsing", "[app][client][cli]") + { + GIVEN("A fresh cli instance and error stream") + { + auto cli = ::wandac::cli{}; + auto error_stream = std::ostringstream{}; + + WHEN("invoking parse without any program arguments") + { + auto result = cli.parse(make_argument_list(), error_stream); + + THEN("the return value is false") { REQUIRE_FALSE(result); } + THEN("the error stream is not empty") { REQUIRE_FALSE(error_stream.view().empty()); } + THEN("the help flag is not set") { REQUIRE_FALSE(cli.help); } + THEN("the command is empty") { REQUIRE(cli.command.empty()); } + } + } + } + + SCENARIO("Valid argument list parsing", "[app][client][cli]") + { + GIVEN("A fresh cli instance and error stream") + { + auto cli = ::wandac::cli{}; + auto error_stream = std::ostringstream{}; + + AND_GIVEN("'-h' in the argument list") + { + auto argument_list = make_argument_list("-h"); + + WHEN("invoking parse without additional arguments") + { + auto result = cli.parse(argument_list, error_stream); + + THEN("the return value is true") { REQUIRE(result); } + THEN("the error stream is empty") { REQUIRE(error_stream.view().empty()); } + THEN("the help flag is set") { REQUIRE(cli.help); } + THEN("the command is empty") { REQUIRE(cli.command.empty()); } + } + } + + AND_GIVEN("'change' in the argument list") + { + auto argument_list = make_argument_list("change"); + + WHEN("invoking parse without additional arguments") + { + auto result = cli.parse(argument_list, error_stream); + + THEN("the return valis is true") { REQUIRE(result); } + THEN("the error stream is empty") { REQUIRE(error_stream.view().empty()); } + THEN("the help flag is not set") { REQUIRE_FALSE(cli.help); } + THEN("the command is not empty") { REQUIRE_FALSE(cli.command.empty()); } + THEN("the command is 'change'") { REQUIRE(cli.command == "change"); } + } + } + } + } + +} // namespace wanda::tests::app::wandac diff --git a/source/lib/CMakeLists.txt b/source/lib/CMakeLists.txt index b9cafe7..f8f2bdb 100644 --- a/source/lib/CMakeLists.txt +++ b/source/lib/CMakeLists.txt @@ -1,9 +1,67 @@ -if(WANDA_APPLICATIONS_ONLY) - set(WANDA_LIBRARY_TYPE OBJECT) -endif() - -add_subdirectory("control") -add_subdirectory("meta") -add_subdirectory("proto") -add_subdirectory("std_ext") -add_subdirectory("system") +file(GLOB_RECURSE HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "include/*.hpp") +file(GLOB_RECURSE SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "src/*.cpp") + +find_package("JPEG" REQUIRED) +find_package("PNG" REQUIRED) +find_package("PkgConfig" REQUIRED) + +pkg_check_modules("GIO" + REQUIRED + IMPORTED_TARGET + GLOBAL + "gio-2.0" +) + +pkg_check_modules("libmagic" + REQUIRED + IMPORTED_TARGET + GLOBAL + "libmagic" +) + +add_library("wanda" + ${SOURCES} +) + +target_sources("wanda" INTERFACE + FILE_SET HEADERS + FILES ${HEADERS} + BASE_DIRS "include" +) + +target_include_directories("wanda" PUBLIC + "$" +) + +target_include_directories("wanda" SYSTEM PUBLIC + "$" +) + +target_compile_features("wanda" PUBLIC + "cxx_std_20" +) + +target_link_libraries("wanda" PUBLIC + "Boost::headers" + "JPEG::JPEG" + "PNG::PNG" + "spdlog::spdlog_header_only" + + "PkgConfig::libmagic" + "PkgConfig::GIO" +) + +add_executable("wanda-tests" + "tests/xdg.cpp" +) + +target_link_libraries("wanda-tests" PRIVATE + "wanda" + "Catch2::Catch2WithMain" +) + +catch_discover_tests("wanda-tests") + +install(TARGETS "wanda" + FILE_SET HEADERS +) diff --git a/source/lib/control/CMakeLists.txt b/source/lib/control/CMakeLists.txt deleted file mode 100644 index b4a4b02..0000000 --- a/source/lib/control/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LIB_NAME) - -file(GLOB_RECURSE LIB_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.hpp") -file(GLOB_RECURSE LIB_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.cpp") - -add_library("wanda-${LIB_NAME}" ${WANDA_LIBRARY_TYPE} - ${LIB_SOURCES} -) - -target_sources("wanda-${LIB_NAME}" INTERFACE - FILE_SET HEADERS - FILES ${LIB_HEADERS} - BASE_DIRS "include" -) - -target_include_directories("wanda-${LIB_NAME}" PUBLIC - "$" -) - -target_include_directories("wanda-${LIB_NAME}" SYSTEM PUBLIC - "$" -) - -target_compile_features("wanda-${LIB_NAME}" PUBLIC - "cxx_std_20" -) - -target_link_libraries("wanda-${LIB_NAME}" PUBLIC - "wanda::meta" - "wanda::proto" - "wanda::system" - - "Boost::headers" - "spdlog::spdlog_header_only" -) - -if(NOT WANDA_APPLICATIONS_ONLY) - install(TARGETS "wanda-${LIB_NAME}" - FILE_SET HEADERS - ) -endif() - -add_library("wanda::${LIB_NAME}" ALIAS "wanda-${LIB_NAME}") diff --git a/source/lib/control/include/wanda/control/commander.hpp b/source/lib/control/include/wanda/control/commander.hpp deleted file mode 100644 index d7ca73d..0000000 --- a/source/lib/control/include/wanda/control/commander.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#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 -#include - -#include -#include -#include -#include -#include - -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/control/include/wanda/control/connection.hpp b/source/lib/control/include/wanda/control/connection.hpp deleted file mode 100644 index 0284fd7..0000000 --- a/source/lib/control/include/wanda/control/connection.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef WANDA_CONTROL_CONNECTION_HPP -#define WANDA_CONTROL_CONNECTION_HPP - -#include "wanda/meta/keyed.hpp" -#include "wanda/proto/message.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace wanda::control -{ - /** - * @brief A connection to a remote control endpoint - */ - struct connection : meta::keyed, std::enable_shared_from_this - { - using protocol = boost::asio::local::stream_protocol; - using pointer = std::shared_ptr; - - /** - * @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 - { - 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 true 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 true 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 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/control/include/wanda/control/interface.hpp b/source/lib/control/include/wanda/control/interface.hpp deleted file mode 100644 index e895b68..0000000 --- a/source/lib/control/include/wanda/control/interface.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @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 -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -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, std::enable_shared_from_this - { - using protocol = boost::asio::local::stream_protocol; - using pointer = std::shared_ptr; - - /** - * @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 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/control/src/commander.cpp b/source/lib/control/src/commander.cpp deleted file mode 100644 index 4490bb7..0000000 --- a/source/lib/control/src/commander.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "wanda/control/commander.hpp" - -#include "wanda/proto/message.hpp" -#include "wanda/proto/version.hpp" -#include "wanda/std_ext/optional.hpp" -#include "wanda/system/logging.hpp" - -#include -#include - -namespace wanda::control -{ - commander::commander(boost::asio::io_context & service, std::filesystem::path socket, listener & listener) - : m_service{service} - , m_endpoint{socket.string()} - , m_socket{service} - , m_listener{listener} - { - } - - void commander::start() - { - m_socket.async_connect(m_endpoint, [&](auto const & error) { - if (error) - { - system::get_logger()->error("error while connecting to control interface: '{}'", error.message()); - } - else - { - system::get_logger()->info("establishing connection to wanda deamon"); - m_connection = wanda::control::make_connection(std::move(m_socket)); - m_connection->add(this); - m_connection->start(); - m_connection->send({proto::message_source_controller, proto::message_command_hello, proto::version}); - } - }); - } - - void commander::stop() - { - system::get_logger()->info("closing control connection"); - m_connection->close(); - } - - void commander::send(proto::command command) - { - using namespace wanda::std_ext; - - if (!m_connection || m_connection->current_state() != connection::state::established) - { - system::get_logger()->error("tried to send command without an established connection"); - m_listener.on_error(*this, "tried to send command without an established connection"); - return; - } - - with(command.message(), [&](auto const & message) { m_connection->send(message); }) || - [&] { system::get_logger()->error("unknown command"); }; - } - - void commander::on_error(connection::pointer connection, std::error_code error) - { - system::get_logger()->error("control interface communication error: '{}'", error.message()); - } - - void commander::on_received(connection::pointer connection, proto::message message) - { - if (auto state = connection->current_state(); message.command == "HELLO" && state == connection::state::fresh) - { - system::get_logger()->info("connection to wanda deamon successfully established"); - connection->update(connection::state::established); - m_listener.on_connected(*this); - } - else - { - system::get_logger()->error("unexpected message: '{}'", message); - m_listener.on_error(*this, "unexpected message '" + static_cast(message) + '\''); - } - } - -} // namespace wanda::control \ No newline at end of file diff --git a/source/lib/control/src/connection.cpp b/source/lib/control/src/connection.cpp deleted file mode 100644 index 97f41dd..0000000 --- a/source/lib/control/src/connection.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "wanda/control/connection.hpp" - -#include "wanda/proto/message.hpp" - -#include -#include -#include -#include -#include - -#include - -namespace wanda::control -{ - connection::pointer make_connection(connection::protocol::socket && socket) - { - return std::make_shared(connection::key{}, std::move(socket)); - } - - connection::connection(connection::key key, connection::protocol::socket socket) - : keyed{key} - , m_socket{std::move(socket)} - { - } - - bool connection::add(listener * listener) - { - auto [_, inserted] = m_listeners.insert(listener); - return inserted; - } - - bool connection::remove(listener * listener) - { - return m_listeners.erase(listener); - } - - void connection::start() - { - if (m_state == state::unknown) - { - m_state = state::fresh; - perform_read(); - } - } - - void connection::send(proto::message message) - { - m_output << message << '\n'; - boost::asio::async_write(m_socket, m_out, boost::asio::transfer_exactly(message.size() + 1), [that = shared_from_this(), this](auto const & error, auto const length) { - if (error) - { - // TODO: Handle error - } - else - { - m_out.consume(length); - } - }); - } - - void connection::close() - { - auto error = boost::system::error_code{}; - - if (m_socket.cancel(error), error) - { - for (auto & listener : m_listeners) - { - listener->on_error(shared_from_this(), error); - } - } - - if (m_socket.close(error), error) - { - for (auto & listener : m_listeners) - { - listener->on_error(shared_from_this(), error); - } - } - - for (auto & listener : m_listeners) - { - listener->on_close(shared_from_this()); - } - m_listeners.clear(); - } - - void connection::update(state state) - { - m_state = state; - } - - connection::state connection::current_state() const - { - return m_state; - } - - void connection::perform_read() - { - boost::asio::async_read_until(m_socket, m_in, '\n', [that = shared_from_this(), this](auto const & error, auto const length) { - if (error) - { - for (auto & listener : m_listeners) - { - listener->on_error(shared_from_this(), error); - } - close(); - } - else - { - auto msg = proto::message{}; - m_input >> msg; - if (!m_input) - { - m_input.ignore(std::numeric_limits::max()); - m_input.clear(); - } - else - { - for (auto & listener : m_listeners) - { - listener->on_received(shared_from_this(), msg); - } - } - perform_read(); - } - }); - } - -} // namespace wanda::control \ No newline at end of file diff --git a/source/lib/control/src/interface.cpp b/source/lib/control/src/interface.cpp deleted file mode 100644 index 3ebc55a..0000000 --- a/source/lib/control/src/interface.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include "wanda/control/interface.hpp" - -#include "wanda/proto/version.hpp" -#include "wanda/std_ext/optional.hpp" -#include "wanda/system/logging.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace wanda::control -{ - // 'socket_deleter' implementation - - socket_deleter::~socket_deleter() - { - if (std::filesystem::exists(path)) - { - std::filesystem::remove(path); - } - } - - // 'interface' implementation - - interface::interface(interface::key key, boost::asio::io_context & service, interface::protocol::endpoint endpoint, listener & listener) - : keyed{key} - , m_service{service} - , m_endpoint{std::move(endpoint)} - , m_socket{m_service} - , m_acceptor{m_service} - , m_listener{listener} - { - } - - std::error_code interface::start() - { - if (auto error = boost::system::error_code{}; m_acceptor.open(m_endpoint.protocol(), error), error) - { - return error; - } - - if (auto error = boost::system::error_code{}; m_acceptor.bind(m_endpoint, error), error) - { - return error; - } - - if (auto error = boost::system::error_code{}; m_acceptor.listen(128, error), error) - { - return error; - } - else - { - perform_accept(); - return error; - } - } - - std::error_code interface::shutdown() - { - for (auto & connection : m_connections) - { - connection->close(); - } - - auto error = boost::system::error_code{}; - return m_acceptor.close(error), error; - } - - void interface::perform_accept() - { - m_acceptor.async_accept(m_socket, [that = shared_from_this(), this](auto const & error) { - if (error && error != boost::asio::error::operation_aborted) - { - system::get_logger()->error("failed to accept connection because '{}'", error.message()); - } - else - { - system::get_logger()->info("new incoming controller connection"); - auto [connection, inserted] = m_connections.insert(make_connection(std::move(m_socket))); - if (inserted) - { - (*connection)->add(this); - (*connection)->start(); - } - perform_accept(); - } - }); - } - - void interface::on_close(connection::pointer connection) - { - if (static_cast(connection->current_state()) >= static_cast(connection::state::established)) - { - system::get_logger()->info("controller connection closed"); - } - else - { - system::get_logger()->info("controller connection aborted before it could be established"); - } - m_connections.erase(connection); - } - - void interface::on_received(connection::pointer connection, proto::message message) - { - using namespace wanda::std_ext; - - if (m_connections.find(connection) == m_connections.cend()) - { - system::get_logger()->error("received message from an unknown connection"); - return; - } - - if (message.source != proto::message_source_controller) - { - system::get_logger()->error("received a deamon message"); - return; - } - - if (auto state = connection->current_state(); message.command == proto::message_command_hello && state == connection::state::fresh) - { - system::get_logger()->info("controller connection established"); - if (message.argument.has_value()) - { - system::get_logger()->info("remote controller version '{}'", *message.argument); - } - connection->send({proto::message_source_daemon, proto::message_command_hello, proto::version}); - connection->update(connection::state::established); - } - else - { - with(make_command(message), [&](auto const & command) { - m_listener.on_received(*this, command); - }) || - [&] { system::get_logger()->warn("ignoring unknown message '{}'", message); }; - } - } - - interface::pointer make_interface(boost::asio::io_context & service, std::filesystem::path socket, interface::listener & listener) - { - if (std::filesystem::exists(socket)) - { - system::get_logger()->error("socket '{}' exists", socket.native()); - return {}; - } - - interface::protocol::endpoint endpoint - { - socket.string() - }; - return std::make_shared(interface::key{}, service, std::move(endpoint), listener); - } - -} // namespace wanda::control \ No newline at end of file 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 +#include + +#include +#include +#include +#include +#include + +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 +#include + +#include +#include +#include +#include +#include +#include + +namespace wanda::control +{ + /** + * @brief A connection to a remote control endpoint + */ + struct connection : meta::keyed, std::enable_shared_from_this + { + using protocol = boost::asio::local::stream_protocol; + using pointer = std::shared_ptr; + + /** + * @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 + { + 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 true 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 true 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 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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, std::enable_shared_from_this + { + using protocol = boost::asio::local::stream_protocol; + using pointer = std::shared_ptr; + + /** + * @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 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 + +namespace wanda::meta +{ + /** + * @brief A helper type to defer static_assert failures + */ + template + 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 + 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 + +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 + 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 +#include +#include + +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 const arguments; + + /** + * @brief Convert the command to a message for transmission to a remote endpoint + */ + std::optional message() const; + }; + + /** + * @brief Extract a command from a message + */ + std::optional 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 + +#include +#include +#include +#include +#include + +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 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 : spdlog::fmt_lib::formatter +{ + 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 +#include +#include + +namespace wanda::std_ext +{ + /** + * @brief A type to represent the error case of a computation based on #wanda::expected + */ + template + struct unexpected + { + static_assert(!std::is_same_v, "ErrorType can not be 'void'!"); + static_assert(!std::is_array_v, "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 + constexpr explicit unexpected(std::in_place_t, Args &&... args) + : m_error(std::forward(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, Args...>> * = nullptr> + constexpr explicit unexpected(std::in_place_t, std::initializer_list il, Args &&... args) + : m_error(il, std::forward(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_same_v>, std::in_place_t> && + !std::is_same_v>, unexpected>> * = nullptr> + constexpr explicit unexpected(Err && error) + : m_error(std::forward(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 &> && + !std::is_constructible_v> && + !std::is_constructible_v const &> && + !std::is_constructible_v const> && + !std::is_convertible_v &, ErrorType> && + !std::is_convertible_v, ErrorType> && + !std::is_convertible_v const &, ErrorType> && + !std::is_convertible_v const, ErrorType>)> * = nullptr, + std::enable_if_t> * = nullptr> + constexpr explicit unexpected(unexpected 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 &> && + !std::is_constructible_v> && + !std::is_constructible_v const &> && + !std::is_constructible_v const> && + !std::is_convertible_v &, ErrorType> && + !std::is_convertible_v, ErrorType> && + !std::is_convertible_v const &, ErrorType> && + !std::is_convertible_v const, ErrorType>)> * = nullptr, + std::enable_if_t> * = nullptr> + constexpr unexpected(unexpected 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 &> && + !std::is_constructible_v> && + !std::is_constructible_v const &> && + !std::is_constructible_v const> && + !std::is_convertible_v &, ErrorType> && + !std::is_convertible_v, ErrorType> && + !std::is_convertible_v const &, ErrorType> && + !std::is_convertible_v const, ErrorType>)> * = nullptr, + std::enable_if_t> * = nullptr> + constexpr explicit unexpected(unexpected && 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 &> && + !std::is_constructible_v> && + !std::is_constructible_v const &> && + !std::is_constructible_v const> && + !std::is_convertible_v &, ErrorType> && + !std::is_convertible_v, ErrorType> && + !std::is_convertible_v const &, ErrorType> && + !std::is_convertible_v const, ErrorType>)> * = nullptr, + std::enable_if_t> * = nullptr> + constexpr unexpected(unexpected && 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) + { + using std::swap; + swap(m_error, other.m_error); + } + + template + friend constexpr bool operator==(unexpected const & lhs, unexpected const & rhs); + + template + friend constexpr bool operator!=(unexpected const & lhs, unexpected const & rhs); + + template< + typename Err, + std::enable_if_t> *> + friend void swap(unexpected & lhs, unexpected & rhs); + + private: + ErrorType m_error; + }; + + template + unexpected(ErrorType) -> unexpected; + + /** + * @brief Compare two @p unexpected instances for equality + */ + template + constexpr bool operator==(unexpected const & lhs, unexpected const & rhs) + { + return lhs.m_error == rhs.m_error; + } + + /** + * @brief Compare two @p unexpected instances for inequality + */ + template + constexpr bool operator!=(unexpected const & lhs, unexpected 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> * = nullptr> + void swap(unexpected & lhs, unexpected & 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 + +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 + 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 + auto with(std::optional && 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 + +#include +#include + +namespace wanda::system +{ + /** + * @brief A type to provide access to the runtime environment + */ + struct environment + { + using map_type = std::map; + 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 +#include +#include + +namespace wanda::system +{ + /** + * @brief Covenience alias for path lists + */ + using path_list = std::vector; + + /** + * @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 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 +#include + +#include + +namespace wanda::system +{ + /** + * @brief A covenience alias to represent a handle for a logger + */ + using logger_ptr = std::shared_ptr; + + /** + * @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()); + + /** + * @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 + +#include +#include +#include +#include + +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, 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wanda::system +{ + struct setting; + + /** + * @brief A convenience type to represent setting keys + */ + using key = meta::type_wrapper; + + 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 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>; + + /** + * @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 + 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) + { + return g_settings_set_boolean(setting, key, value); + } + else if constexpr (std::is_same_v) + { + return g_settings_set_int(setting, key, value); + } + else if constexpr (std::is_same_v) + { + return g_settings_set_int64(setting, key, value); + } + else if constexpr (std::is_same_v) + { + return g_settings_set_uint(setting, key, value); + } + else if constexpr (std::is_same_v) + { + return g_settings_set_uint64(setting, key, value); + } + else if constexpr (std::is_same_v) + { + return g_settings_set_double(setting, key, value); + } + else if constexpr (std::is_same_v) + { + return g_settings_set_string(setting, key, value.c_str()); + } + else if constexpr (std::is_same_v>) + { + auto temp = std::vector{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 m_settings; + + key m_key; + + friend setting; + }; + + /** + * @brief Get the entry for the given key + * + * @return An std::optional wrapping the entry associated with + * the given key, or an empty std::optional if the desired key + * does not exist in the setting's schema. + */ + std::optional operator[](key key) const; + + private: + explicit setting(GSettingsSchema * schema); + + std::unique_ptr m_schema; + + friend std::optional 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 +#include + +#include +#include + +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 +#include +#include + +namespace wanda::system +{ + /** + * @brief An @p enum to represet the standardized XDG directories + */ + enum struct xdg_directory : std::underlying_type_t + { + 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 diff --git a/source/lib/meta/CMakeLists.txt b/source/lib/meta/CMakeLists.txt deleted file mode 100644 index d21d45c..0000000 --- a/source/lib/meta/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LIB_NAME) - -file(GLOB_RECURSE LIB_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.hpp") - -add_library("wanda-${LIB_NAME}" INTERFACE) - -target_sources("wanda-${LIB_NAME}" INTERFACE - FILE_SET HEADERS - FILES ${LIB_HEADERS} - BASE_DIRS "include" -) - -target_include_directories("wanda-${LIB_NAME}" INTERFACE - "$" -) - -target_include_directories("wanda-${LIB_NAME}" SYSTEM INTERFACE - "$" -) - -target_compile_features("wanda-${LIB_NAME}" INTERFACE - "cxx_std_20" -) - -if(NOT WANDA_APPLICATIONS_ONLY) - install(TARGETS "wanda-${LIB_NAME}" - FILE_SET HEADERS - ) -endif() - -add_library("wanda::${LIB_NAME}" ALIAS "wanda-${LIB_NAME}") diff --git a/source/lib/meta/include/wanda/meta/deferred_failure.hpp b/source/lib/meta/include/wanda/meta/deferred_failure.hpp deleted file mode 100644 index f74d923..0000000 --- a/source/lib/meta/include/wanda/meta/deferred_failure.hpp +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @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 - -namespace wanda::meta -{ - /** - * @brief A helper type to defer static_assert failures - */ - template - using deferred_failure = std::false_type; -} // namespace wanda::meta - -#endif \ No newline at end of file diff --git a/source/lib/meta/include/wanda/meta/keyed.hpp b/source/lib/meta/include/wanda/meta/keyed.hpp deleted file mode 100644 index a09d1eb..0000000 --- a/source/lib/meta/include/wanda/meta/keyed.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @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 - struct keyed - { - protected: - struct key - { - }; - - explicit keyed(key) {} - }; - -} // namespace wanda::meta - -#endif \ No newline at end of file diff --git a/source/lib/meta/include/wanda/meta/type_wrapper.hpp b/source/lib/meta/include/wanda/meta/type_wrapper.hpp deleted file mode 100644 index 1d34c09..0000000 --- a/source/lib/meta/include/wanda/meta/type_wrapper.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @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 - -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 - 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/proto/CMakeLists.txt b/source/lib/proto/CMakeLists.txt deleted file mode 100644 index b20663c..0000000 --- a/source/lib/proto/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LIB_NAME) - -file(GLOB_RECURSE LIB_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.hpp") -file(GLOB_RECURSE LIB_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.cpp") - -add_library("wanda-${LIB_NAME}" ${WANDA_LIBRARY_TYPE} - ${LIB_SOURCES} -) - -target_sources("wanda-${LIB_NAME}" INTERFACE - FILE_SET HEADERS - FILES ${LIB_HEADERS} - BASE_DIRS "include" -) - -target_include_directories("wanda-${LIB_NAME}" PUBLIC - "$" -) - -target_include_directories("wanda-${LIB_NAME}" SYSTEM PUBLIC - "$" -) - -target_compile_features("wanda-${LIB_NAME}" PUBLIC - "cxx_std_20" -) - -target_link_libraries("wanda-${LIB_NAME}" PUBLIC - "spdlog::spdlog_header_only" -) - -if(NOT WANDA_APPLICATIONS_ONLY) - install(TARGETS "wanda-${LIB_NAME}" - FILE_SET HEADERS - ) -endif() - -add_library("wanda::${LIB_NAME}" ALIAS "wanda-${LIB_NAME}") diff --git a/source/lib/proto/include/wanda/proto/command.hpp b/source/lib/proto/include/wanda/proto/command.hpp deleted file mode 100644 index c8dae65..0000000 --- a/source/lib/proto/include/wanda/proto/command.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef WANDA_PROTO_COMMAND_HPP -#define WANDA_PROTO_COMMAND_HPP - -#include "wanda/proto/message.hpp" - -#include -#include -#include - -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 const arguments; - - /** - * @brief Convert the command to a message for transmission to a remote endpoint - */ - std::optional message() const; - }; - - /** - * @brief Extract a command from a message - */ - std::optional 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/proto/include/wanda/proto/message.hpp b/source/lib/proto/include/wanda/proto/message.hpp deleted file mode 100644 index 03a30c2..0000000 --- a/source/lib/proto/include/wanda/proto/message.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @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 - -#include -#include -#include -#include -#include - -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 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 : spdlog::fmt_lib::formatter -{ - 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/proto/include/wanda/proto/version.hpp b/source/lib/proto/include/wanda/proto/version.hpp deleted file mode 100644 index faa17d7..0000000 --- a/source/lib/proto/include/wanda/proto/version.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#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/proto/src/command.cpp b/source/lib/proto/src/command.cpp deleted file mode 100644 index 5a669f5..0000000 --- a/source/lib/proto/src/command.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "wanda/proto/command.hpp" - -namespace wanda::proto -{ - std::optional command::message() const - { - using namespace std::string_literals; - auto const command = [this] { - switch (id) - { - case command_id::change: - return "CHANGE"s; - default: - return ""s; - } - }(); - - auto argument_string = std::string{}; - for (int index = 0ul; index < arguments.size(); ++index) - { - argument_string += (index) ? "," + arguments[index] : arguments[index]; - } - - if (command.empty()) - { - return std::nullopt; - } - - return wanda::proto::message{"C", command, argument_string}; - } - - std::optional make_command(message message) - { - if (message.command == "CHANGE") - { - return {{command_id::change}}; - } - - return std::nullopt; - } - - command make_change_command() - { - return {command_id::change}; - } - -} // namespace wanda::proto \ No newline at end of file diff --git a/source/lib/proto/src/message.cpp b/source/lib/proto/src/message.cpp deleted file mode 100644 index f44ca06..0000000 --- a/source/lib/proto/src/message.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "wanda/proto/message.hpp" - -#include - -#include -#include -#include - -namespace wanda::proto -{ - message::operator std::string() const - { - std::ostringstream buffer{}; - buffer << source - << ':' - << command; - if (argument.has_value()) - { - buffer << ':' << *argument; - } - return buffer.str(); - } - - std::size_t message::size() const - { - return static_cast(*this).size(); - } - - template - OutputIt copy_until(InputIt first, InputIt last, OutputIt out, UnaryPredicate predicate) - { - while (first != last && !predicate(*first)) - { - *out++ = *first++; - } - return out; - } - - std::istream & operator>>(std::istream & in, message & message) - { - auto pos = std::istream_iterator{in}; - auto end = std::istream_iterator{}; - auto buffer = std::string{}; - - copy_until(pos, end, std::back_inserter(buffer), [](auto const & c) { return c == ':'; }); - if (in.eof() || buffer.size() != 1) - { - in.setstate(std::ios_base::failbit); - return in; - } - message.source = buffer; - - buffer.clear(); - copy_until(++pos, end, std::back_inserter(buffer), [](auto const & c) { return c == ':'; }); - if (in.eof()) - { - in.setstate(std::ios_base::failbit); - } - message.command = buffer; - - buffer.clear(); - copy(++pos, end, std::back_inserter(buffer)); - if (buffer.size()) - { - message.argument = std::optional{std::move(buffer)}; - } - - in.clear(in.rdstate() ^ std::ios_base::failbit); - return in; - } - - std::ostream & operator<<(std::ostream & out, message const & message) - { - return out << static_cast(message); - } - -} // namespace wanda::proto - -auto spdlog::fmt_lib::formatter::format(wanda::proto::message const & message, format_context & context) const -> decltype(context.out()) -{ - return formatter::format(static_cast(message), context); -} diff --git a/source/lib/src/control/commander.cpp b/source/lib/src/control/commander.cpp new file mode 100644 index 0000000..4490bb7 --- /dev/null +++ b/source/lib/src/control/commander.cpp @@ -0,0 +1,80 @@ +#include "wanda/control/commander.hpp" + +#include "wanda/proto/message.hpp" +#include "wanda/proto/version.hpp" +#include "wanda/std_ext/optional.hpp" +#include "wanda/system/logging.hpp" + +#include +#include + +namespace wanda::control +{ + commander::commander(boost::asio::io_context & service, std::filesystem::path socket, listener & listener) + : m_service{service} + , m_endpoint{socket.string()} + , m_socket{service} + , m_listener{listener} + { + } + + void commander::start() + { + m_socket.async_connect(m_endpoint, [&](auto const & error) { + if (error) + { + system::get_logger()->error("error while connecting to control interface: '{}'", error.message()); + } + else + { + system::get_logger()->info("establishing connection to wanda deamon"); + m_connection = wanda::control::make_connection(std::move(m_socket)); + m_connection->add(this); + m_connection->start(); + m_connection->send({proto::message_source_controller, proto::message_command_hello, proto::version}); + } + }); + } + + void commander::stop() + { + system::get_logger()->info("closing control connection"); + m_connection->close(); + } + + void commander::send(proto::command command) + { + using namespace wanda::std_ext; + + if (!m_connection || m_connection->current_state() != connection::state::established) + { + system::get_logger()->error("tried to send command without an established connection"); + m_listener.on_error(*this, "tried to send command without an established connection"); + return; + } + + with(command.message(), [&](auto const & message) { m_connection->send(message); }) || + [&] { system::get_logger()->error("unknown command"); }; + } + + void commander::on_error(connection::pointer connection, std::error_code error) + { + system::get_logger()->error("control interface communication error: '{}'", error.message()); + } + + void commander::on_received(connection::pointer connection, proto::message message) + { + if (auto state = connection->current_state(); message.command == "HELLO" && state == connection::state::fresh) + { + system::get_logger()->info("connection to wanda deamon successfully established"); + connection->update(connection::state::established); + m_listener.on_connected(*this); + } + else + { + system::get_logger()->error("unexpected message: '{}'", message); + m_listener.on_error(*this, "unexpected message '" + static_cast(message) + '\''); + } + } + +} // namespace wanda::control \ No newline at end of file diff --git a/source/lib/src/control/connection.cpp b/source/lib/src/control/connection.cpp new file mode 100644 index 0000000..97f41dd --- /dev/null +++ b/source/lib/src/control/connection.cpp @@ -0,0 +1,130 @@ +#include "wanda/control/connection.hpp" + +#include "wanda/proto/message.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace wanda::control +{ + connection::pointer make_connection(connection::protocol::socket && socket) + { + return std::make_shared(connection::key{}, std::move(socket)); + } + + connection::connection(connection::key key, connection::protocol::socket socket) + : keyed{key} + , m_socket{std::move(socket)} + { + } + + bool connection::add(listener * listener) + { + auto [_, inserted] = m_listeners.insert(listener); + return inserted; + } + + bool connection::remove(listener * listener) + { + return m_listeners.erase(listener); + } + + void connection::start() + { + if (m_state == state::unknown) + { + m_state = state::fresh; + perform_read(); + } + } + + void connection::send(proto::message message) + { + m_output << message << '\n'; + boost::asio::async_write(m_socket, m_out, boost::asio::transfer_exactly(message.size() + 1), [that = shared_from_this(), this](auto const & error, auto const length) { + if (error) + { + // TODO: Handle error + } + else + { + m_out.consume(length); + } + }); + } + + void connection::close() + { + auto error = boost::system::error_code{}; + + if (m_socket.cancel(error), error) + { + for (auto & listener : m_listeners) + { + listener->on_error(shared_from_this(), error); + } + } + + if (m_socket.close(error), error) + { + for (auto & listener : m_listeners) + { + listener->on_error(shared_from_this(), error); + } + } + + for (auto & listener : m_listeners) + { + listener->on_close(shared_from_this()); + } + m_listeners.clear(); + } + + void connection::update(state state) + { + m_state = state; + } + + connection::state connection::current_state() const + { + return m_state; + } + + void connection::perform_read() + { + boost::asio::async_read_until(m_socket, m_in, '\n', [that = shared_from_this(), this](auto const & error, auto const length) { + if (error) + { + for (auto & listener : m_listeners) + { + listener->on_error(shared_from_this(), error); + } + close(); + } + else + { + auto msg = proto::message{}; + m_input >> msg; + if (!m_input) + { + m_input.ignore(std::numeric_limits::max()); + m_input.clear(); + } + else + { + for (auto & listener : m_listeners) + { + listener->on_received(shared_from_this(), msg); + } + } + perform_read(); + } + }); + } + +} // namespace wanda::control \ No newline at end of file diff --git a/source/lib/src/control/interface.cpp b/source/lib/src/control/interface.cpp new file mode 100644 index 0000000..3ebc55a --- /dev/null +++ b/source/lib/src/control/interface.cpp @@ -0,0 +1,160 @@ +#include "wanda/control/interface.hpp" + +#include "wanda/proto/version.hpp" +#include "wanda/std_ext/optional.hpp" +#include "wanda/system/logging.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace wanda::control +{ + // 'socket_deleter' implementation + + socket_deleter::~socket_deleter() + { + if (std::filesystem::exists(path)) + { + std::filesystem::remove(path); + } + } + + // 'interface' implementation + + interface::interface(interface::key key, boost::asio::io_context & service, interface::protocol::endpoint endpoint, listener & listener) + : keyed{key} + , m_service{service} + , m_endpoint{std::move(endpoint)} + , m_socket{m_service} + , m_acceptor{m_service} + , m_listener{listener} + { + } + + std::error_code interface::start() + { + if (auto error = boost::system::error_code{}; m_acceptor.open(m_endpoint.protocol(), error), error) + { + return error; + } + + if (auto error = boost::system::error_code{}; m_acceptor.bind(m_endpoint, error), error) + { + return error; + } + + if (auto error = boost::system::error_code{}; m_acceptor.listen(128, error), error) + { + return error; + } + else + { + perform_accept(); + return error; + } + } + + std::error_code interface::shutdown() + { + for (auto & connection : m_connections) + { + connection->close(); + } + + auto error = boost::system::error_code{}; + return m_acceptor.close(error), error; + } + + void interface::perform_accept() + { + m_acceptor.async_accept(m_socket, [that = shared_from_this(), this](auto const & error) { + if (error && error != boost::asio::error::operation_aborted) + { + system::get_logger()->error("failed to accept connection because '{}'", error.message()); + } + else + { + system::get_logger()->info("new incoming controller connection"); + auto [connection, inserted] = m_connections.insert(make_connection(std::move(m_socket))); + if (inserted) + { + (*connection)->add(this); + (*connection)->start(); + } + perform_accept(); + } + }); + } + + void interface::on_close(connection::pointer connection) + { + if (static_cast(connection->current_state()) >= static_cast(connection::state::established)) + { + system::get_logger()->info("controller connection closed"); + } + else + { + system::get_logger()->info("controller connection aborted before it could be established"); + } + m_connections.erase(connection); + } + + void interface::on_received(connection::pointer connection, proto::message message) + { + using namespace wanda::std_ext; + + if (m_connections.find(connection) == m_connections.cend()) + { + system::get_logger()->error("received message from an unknown connection"); + return; + } + + if (message.source != proto::message_source_controller) + { + system::get_logger()->error("received a deamon message"); + return; + } + + if (auto state = connection->current_state(); message.command == proto::message_command_hello && state == connection::state::fresh) + { + system::get_logger()->info("controller connection established"); + if (message.argument.has_value()) + { + system::get_logger()->info("remote controller version '{}'", *message.argument); + } + connection->send({proto::message_source_daemon, proto::message_command_hello, proto::version}); + connection->update(connection::state::established); + } + else + { + with(make_command(message), [&](auto const & command) { + m_listener.on_received(*this, command); + }) || + [&] { system::get_logger()->warn("ignoring unknown message '{}'", message); }; + } + } + + interface::pointer make_interface(boost::asio::io_context & service, std::filesystem::path socket, interface::listener & listener) + { + if (std::filesystem::exists(socket)) + { + system::get_logger()->error("socket '{}' exists", socket.native()); + return {}; + } + + interface::protocol::endpoint endpoint + { + socket.string() + }; + return std::make_shared(interface::key{}, service, std::move(endpoint), listener); + } + +} // namespace wanda::control \ No newline at end of file diff --git a/source/lib/src/proto/command.cpp b/source/lib/src/proto/command.cpp new file mode 100644 index 0000000..5a669f5 --- /dev/null +++ b/source/lib/src/proto/command.cpp @@ -0,0 +1,47 @@ +#include "wanda/proto/command.hpp" + +namespace wanda::proto +{ + std::optional command::message() const + { + using namespace std::string_literals; + auto const command = [this] { + switch (id) + { + case command_id::change: + return "CHANGE"s; + default: + return ""s; + } + }(); + + auto argument_string = std::string{}; + for (int index = 0ul; index < arguments.size(); ++index) + { + argument_string += (index) ? "," + arguments[index] : arguments[index]; + } + + if (command.empty()) + { + return std::nullopt; + } + + return wanda::proto::message{"C", command, argument_string}; + } + + std::optional make_command(message message) + { + if (message.command == "CHANGE") + { + return {{command_id::change}}; + } + + return std::nullopt; + } + + command make_change_command() + { + return {command_id::change}; + } + +} // namespace wanda::proto \ No newline at end of file diff --git a/source/lib/src/proto/message.cpp b/source/lib/src/proto/message.cpp new file mode 100644 index 0000000..f44ca06 --- /dev/null +++ b/source/lib/src/proto/message.cpp @@ -0,0 +1,82 @@ +#include "wanda/proto/message.hpp" + +#include + +#include +#include +#include + +namespace wanda::proto +{ + message::operator std::string() const + { + std::ostringstream buffer{}; + buffer << source + << ':' + << command; + if (argument.has_value()) + { + buffer << ':' << *argument; + } + return buffer.str(); + } + + std::size_t message::size() const + { + return static_cast(*this).size(); + } + + template + OutputIt copy_until(InputIt first, InputIt last, OutputIt out, UnaryPredicate predicate) + { + while (first != last && !predicate(*first)) + { + *out++ = *first++; + } + return out; + } + + std::istream & operator>>(std::istream & in, message & message) + { + auto pos = std::istream_iterator{in}; + auto end = std::istream_iterator{}; + auto buffer = std::string{}; + + copy_until(pos, end, std::back_inserter(buffer), [](auto const & c) { return c == ':'; }); + if (in.eof() || buffer.size() != 1) + { + in.setstate(std::ios_base::failbit); + return in; + } + message.source = buffer; + + buffer.clear(); + copy_until(++pos, end, std::back_inserter(buffer), [](auto const & c) { return c == ':'; }); + if (in.eof()) + { + in.setstate(std::ios_base::failbit); + } + message.command = buffer; + + buffer.clear(); + copy(++pos, end, std::back_inserter(buffer)); + if (buffer.size()) + { + message.argument = std::optional{std::move(buffer)}; + } + + in.clear(in.rdstate() ^ std::ios_base::failbit); + return in; + } + + std::ostream & operator<<(std::ostream & out, message const & message) + { + return out << static_cast(message); + } + +} // namespace wanda::proto + +auto spdlog::fmt_lib::formatter::format(wanda::proto::message const & message, format_context & context) const -> decltype(context.out()) +{ + return formatter::format(static_cast(message), context); +} diff --git a/source/lib/src/system/environment.cpp b/source/lib/src/system/environment.cpp new file mode 100644 index 0000000..8f79531 --- /dev/null +++ b/source/lib/src/system/environment.cpp @@ -0,0 +1,71 @@ +#include "wanda/system/environment.hpp" + +#include + +namespace wanda::system +{ + environment::environment(char const * const * env) + { + if (!env) + { + return; + } + + std::string buffer{}; + for (; *env != nullptr; ++env) + { + buffer = *env; + int split_point = buffer.find('='); + if (split_point != std::string::npos) + { + m_cache[buffer.substr(0, split_point)] = buffer.substr(split_point + 1); + } + } + } + + std::string & environment::operator[](std::string const & variable) + { + return m_cache[variable]; + } + + std::string const & environment::operator[](std::string const & variable) const + { + static std::string const empty{}; + if (auto needle = m_cache.find(variable); needle != cend()) + { + return needle->second; + } + return empty; + } + + environment::iterator environment::begin() + { + return m_cache.begin(); + } + + environment::const_iterator environment::begin() const + { + return m_cache.begin(); + } + + environment::const_iterator environment::cbegin() const + { + return m_cache.cbegin(); + } + + environment::iterator environment::end() + { + return m_cache.end(); + } + + environment::const_iterator environment::end() const + { + return m_cache.end(); + } + + environment::const_iterator environment::cend() const + { + return m_cache.cend(); + } + +} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/src/system/filesystem.cpp b/source/lib/src/system/filesystem.cpp new file mode 100644 index 0000000..04dfff3 --- /dev/null +++ b/source/lib/src/system/filesystem.cpp @@ -0,0 +1,31 @@ +#include "wanda/system/filesystem.hpp" + +#include +#include + +namespace wanda::system +{ + std::optional scan(std::filesystem::path source, bool(filter)(std::filesystem::path const &)) + { + if (!std::filesystem::is_directory(source)) + { + return std::nullopt; + } + auto entries = std::filesystem::recursive_directory_iterator{source}; + auto result = path_list{}; + for (auto & entry : entries | std::views::filter(filter)) + { + result.push_back(entry.path()); + } + return result; + } + + std::filesystem::path random_pick(path_list const & paths) + { + static auto generator = std::mt19937{std::random_device{}()}; + auto distribution = std::uniform_int_distribution{0, paths.size() - 1}; + + return paths[distribution(generator)]; + } + +} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/src/system/logging.cpp b/source/lib/src/system/logging.cpp new file mode 100644 index 0000000..83dc9f6 --- /dev/null +++ b/source/lib/src/system/logging.cpp @@ -0,0 +1,21 @@ +#include "wanda/system/logging.hpp" + +namespace wanda::system +{ + std::function initializer = [](spdlog::sink_ptr sink) { + spdlog::register_logger(std::make_shared("wanda", sink)); + initializer = [](auto) {}; + }; + + void initialize_logger(spdlog::sink_ptr sink) + { + initializer(sink); + } + + logger_ptr get_logger() + { + initialize_logger(); + return spdlog::get("wanda"); + } + +} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/src/system/setting.cpp b/source/lib/src/system/setting.cpp new file mode 100644 index 0000000..1553eae --- /dev/null +++ b/source/lib/src/system/setting.cpp @@ -0,0 +1,102 @@ +#include "wanda/system/setting.hpp" + +#include +#include + +namespace wanda::system +{ + // UDL implementations + + key literals::operator""_key(char const * str, std::size_t len) + { + return key{{str, len}}; + } + + std::optional literals::operator""_setting(char const * str, std::size_t len) + { + auto source = g_settings_schema_source_get_default(); + if (!source) + { + return std::nullopt; + } + + auto schema = g_settings_schema_source_lookup(source, str, true); + if (!schema) + { + return std::nullopt; + } + + return setting{schema}; + } + + // 'setting' implementation + + setting::setting(GSettingsSchema * schema) + : m_schema{schema, &g_settings_schema_unref} + { + } + + std::optional setting::operator[](key key) const + { + if (!g_settings_schema_has_key(m_schema.get(), key.get().c_str())) + { + return std::nullopt; + } + + return setting::entry{*this, std::move(key)}; + } + + // 'setting::entry' implementation + + setting::entry::entry(setting const & setting, key key) + : m_settings{g_settings_new(g_settings_schema_get_id(setting.m_schema.get())), &g_object_unref} + , m_key{key.get()} + { + } + + setting::entry::value_type setting::entry::operator*() const + { + auto value = std::unique_ptr{g_settings_get_value(m_settings.get(), m_key.get().c_str()), &g_variant_unref}; + auto raw = value.get(); + + if (g_variant_is_of_type(raw, G_VARIANT_TYPE_BOOLEAN)) + { + return static_cast(g_variant_get_boolean(raw)); + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_INT32)) + { + return static_cast(g_variant_get_int32(raw)); + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_INT64)) + { + return static_cast(g_variant_get_int64(raw)); + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_UINT32)) + { + return static_cast(g_variant_get_uint32(raw)); + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_UINT64)) + { + return static_cast(g_variant_get_uint64(raw)); + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_DOUBLE)) + { + return static_cast(g_variant_get_double(raw)); + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_STRING)) + { + auto size = gsize{}; + auto string = g_variant_get_string(raw, &size); + return std::string{string, size}; + } + else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_STRING_ARRAY)) + { + auto length = gsize{}; + auto data = g_variant_get_strv(raw, &length); + return std::vector{data, data + length}; + } + + return {}; + } + +} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/src/system/wallpaper.cpp b/source/lib/src/system/wallpaper.cpp new file mode 100644 index 0000000..11a6402 --- /dev/null +++ b/source/lib/src/system/wallpaper.cpp @@ -0,0 +1,91 @@ +#include "wanda/system/wallpaper.hpp" + +#include "wanda/std_ext/optional.hpp" +#include "wanda/system/logging.hpp" +#include "wanda/system/magic.hpp" +#include "wanda/system/setting.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace wanda::system +{ + namespace + { + auto magic_instance = magic{}; + + auto load_image(std::filesystem::path wallpaper) + { + auto image = boost::gil::rgb8_image_t{}; + + switch (magic_instance.type(wallpaper)) + { + case magic::mime_type::image_jpeg: + boost::gil::read_and_convert_image(wallpaper.native(), image, boost::gil::jpeg_tag{}); + break; + case magic::mime_type::image_png: + boost::gil::read_and_convert_image(wallpaper.native(), image, boost::gil::png_tag{}); + break; + } + + return image; + } + + auto average_colors(boost::gil::rgb8_image_t image) + { + auto accumulator = boost::gil::rgb64f_pixel_t{}; + auto view = const_view(image); + + std::ranges::for_each(view, [&](auto const & source_pixel) { + at_c<0>(accumulator) += std::pow(boost::gil::at_c<0>(source_pixel), 2); + at_c<1>(accumulator) += std::pow(boost::gil::at_c<1>(source_pixel), 2); + at_c<2>(accumulator) += std::pow(boost::gil::at_c<2>(source_pixel), 2); + }); + + at_c<0>(accumulator) = std::sqrt(at_c<0>(accumulator) / view.size()); + at_c<1>(accumulator) = std::sqrt(at_c<1>(accumulator) / view.size()); + at_c<2>(accumulator) = std::sqrt(at_c<2>(accumulator) / view.size()); + + return accumulator; + } + + } // namespace + + void set_wallpaper(std::filesystem::path wallpaper) + { + using namespace wanda::system::literals; + using namespace wanda::std_ext; + using namespace std::string_literals; + + auto image = load_image(wallpaper); + auto color = average_colors(std::move(image)); + auto hexstring = spdlog::fmt_lib::format("#{:02X}{:02X}{:02X}", + static_cast(at_c<0>(color)), + static_cast(at_c<1>(color)), + static_cast(at_c<2>(color))); + + with("org.gnome.desktop.background"_setting, [&](auto & setting) { + with(setting["primary-color"_key], [&](auto & value) { + value = hexstring; + }); + with(setting["picture-uri"_key], [&](auto & value) { + value = "file://" + wallpaper.native(); + }) || + [&] { get_logger()->error("invalid settings key"); }; + with(setting["picture-uri-dark"_key], [&](auto & value) { + value = "file://" + wallpaper.native(); + }) || + [&] { get_logger()->error("invalid settings key"); }; + }) || + [&] { get_logger()->error("invalid setting"); }; + } + +} // namespace wanda::system diff --git a/source/lib/src/system/xdg.cpp b/source/lib/src/system/xdg.cpp new file mode 100644 index 0000000..109beb1 --- /dev/null +++ b/source/lib/src/system/xdg.cpp @@ -0,0 +1,46 @@ +#include "wanda/system/xdg.hpp" + +#include + +namespace wanda::system +{ + std::string xdg_variable(xdg_directory directory) + { + switch (directory) + { + case xdg_directory::data_home: + return "XDG_DATA_HOME"; + case xdg_directory::config_home: + return "XDG_CONFIG_HOME"; + case xdg_directory::cache_home: + return "XDG_CACHE_HOME"; + case xdg_directory::runtime_dir: + return "XDG_RUNTIME_DIR"; + } + return "XDG_INVALID_PATH"; + } + + std::filesystem::path xdg_path_for(xdg_directory directory, environment const & environment) + { + if (auto path = environment[xdg_variable(directory)]; !path.empty()) + { + return path; + } + + auto home = std::filesystem::path{environment["HOME"]}; + switch (directory) + { + case xdg_directory::data_home: + return home / ".local/share"; + case xdg_directory::config_home: + return home / ".config"; + case xdg_directory::cache_home: + return home / ".cache"; + case xdg_directory::runtime_dir: + return std::filesystem::path{"/run/user"} / std::to_string(::getuid()); + } + + return ""; + } + +} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/std_ext/CMakeLists.txt b/source/lib/std_ext/CMakeLists.txt deleted file mode 100644 index d21d45c..0000000 --- a/source/lib/std_ext/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LIB_NAME) - -file(GLOB_RECURSE LIB_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.hpp") - -add_library("wanda-${LIB_NAME}" INTERFACE) - -target_sources("wanda-${LIB_NAME}" INTERFACE - FILE_SET HEADERS - FILES ${LIB_HEADERS} - BASE_DIRS "include" -) - -target_include_directories("wanda-${LIB_NAME}" INTERFACE - "$" -) - -target_include_directories("wanda-${LIB_NAME}" SYSTEM INTERFACE - "$" -) - -target_compile_features("wanda-${LIB_NAME}" INTERFACE - "cxx_std_20" -) - -if(NOT WANDA_APPLICATIONS_ONLY) - install(TARGETS "wanda-${LIB_NAME}" - FILE_SET HEADERS - ) -endif() - -add_library("wanda::${LIB_NAME}" ALIAS "wanda-${LIB_NAME}") diff --git a/source/lib/std_ext/include/wanda/std_ext/expected.hpp b/source/lib/std_ext/include/wanda/std_ext/expected.hpp deleted file mode 100644 index 83629db..0000000 --- a/source/lib/std_ext/include/wanda/std_ext/expected.hpp +++ /dev/null @@ -1,256 +0,0 @@ -/** - * @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 -#include -#include - -namespace wanda::std_ext -{ - /** - * @brief A type to represent the error case of a computation based on #wanda::expected - */ - template - struct unexpected - { - static_assert(!std::is_same_v, "ErrorType can not be 'void'!"); - static_assert(!std::is_array_v, "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 - constexpr explicit unexpected(std::in_place_t, Args &&... args) - : m_error(std::forward(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, Args...>> * = nullptr> - constexpr explicit unexpected(std::in_place_t, std::initializer_list il, Args &&... args) - : m_error(il, std::forward(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_same_v>, std::in_place_t> && - !std::is_same_v>, unexpected>> * = nullptr> - constexpr explicit unexpected(Err && error) - : m_error(std::forward(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 &> && - !std::is_constructible_v> && - !std::is_constructible_v const &> && - !std::is_constructible_v const> && - !std::is_convertible_v &, ErrorType> && - !std::is_convertible_v, ErrorType> && - !std::is_convertible_v const &, ErrorType> && - !std::is_convertible_v const, ErrorType>)> * = nullptr, - std::enable_if_t> * = nullptr> - constexpr explicit unexpected(unexpected 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 &> && - !std::is_constructible_v> && - !std::is_constructible_v const &> && - !std::is_constructible_v const> && - !std::is_convertible_v &, ErrorType> && - !std::is_convertible_v, ErrorType> && - !std::is_convertible_v const &, ErrorType> && - !std::is_convertible_v const, ErrorType>)> * = nullptr, - std::enable_if_t> * = nullptr> - constexpr unexpected(unexpected 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 &> && - !std::is_constructible_v> && - !std::is_constructible_v const &> && - !std::is_constructible_v const> && - !std::is_convertible_v &, ErrorType> && - !std::is_convertible_v, ErrorType> && - !std::is_convertible_v const &, ErrorType> && - !std::is_convertible_v const, ErrorType>)> * = nullptr, - std::enable_if_t> * = nullptr> - constexpr explicit unexpected(unexpected && 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 &> && - !std::is_constructible_v> && - !std::is_constructible_v const &> && - !std::is_constructible_v const> && - !std::is_convertible_v &, ErrorType> && - !std::is_convertible_v, ErrorType> && - !std::is_convertible_v const &, ErrorType> && - !std::is_convertible_v const, ErrorType>)> * = nullptr, - std::enable_if_t> * = nullptr> - constexpr unexpected(unexpected && 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) - { - using std::swap; - swap(m_error, other.m_error); - } - - template - friend constexpr bool operator==(unexpected const & lhs, unexpected const & rhs); - - template - friend constexpr bool operator!=(unexpected const & lhs, unexpected const & rhs); - - template< - typename Err, - std::enable_if_t> *> - friend void swap(unexpected & lhs, unexpected & rhs); - - private: - ErrorType m_error; - }; - - template - unexpected(ErrorType) -> unexpected; - - /** - * @brief Compare two @p unexpected instances for equality - */ - template - constexpr bool operator==(unexpected const & lhs, unexpected const & rhs) - { - return lhs.m_error == rhs.m_error; - } - - /** - * @brief Compare two @p unexpected instances for inequality - */ - template - constexpr bool operator!=(unexpected const & lhs, unexpected 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> * = nullptr> - void swap(unexpected & lhs, unexpected & 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/std_ext/include/wanda/std_ext/optional.hpp b/source/lib/std_ext/include/wanda/std_ext/optional.hpp deleted file mode 100644 index 763e8ac..0000000 --- a/source/lib/std_ext/include/wanda/std_ext/optional.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @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 - -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 - 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 - auto with(std::optional && 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/system/CMakeLists.txt b/source/lib/system/CMakeLists.txt deleted file mode 100644 index 7589c56..0000000 --- a/source/lib/system/CMakeLists.txt +++ /dev/null @@ -1,63 +0,0 @@ -cmake_path(GET CMAKE_CURRENT_SOURCE_DIR STEM LIB_NAME) - -file(GLOB_RECURSE LIB_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.hpp") -file(GLOB_RECURSE LIB_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" CONFIGURE_DEPENDS "**/*.cpp") - -find_package("PkgConfig" REQUIRED) - -pkg_check_modules("GIO" - REQUIRED - IMPORTED_TARGET - GLOBAL - "gio-2.0" -) - -pkg_check_modules("libmagic" - REQUIRED - IMPORTED_TARGET - GLOBAL - "libmagic" -) - -add_library("wanda-${LIB_NAME}" ${WANDA_LIBRARY_TYPE} - ${LIB_SOURCES} -) - -target_sources("wanda-${LIB_NAME}" INTERFACE - FILE_SET HEADERS - FILES ${LIB_HEADERS} - BASE_DIRS "include" -) - -target_include_directories("wanda-${LIB_NAME}" PUBLIC - "$" -) - -target_include_directories("wanda-${LIB_NAME}" SYSTEM PUBLIC - "$" -) - -target_compile_features("wanda-${LIB_NAME}" PUBLIC - "cxx_std_20" -) - -target_link_libraries("wanda-${LIB_NAME}" PUBLIC - "wanda::meta" - "wanda::std_ext" - - "Boost::headers" - "JPEG::JPEG" - "PNG::PNG" - "spdlog::spdlog_header_only" - - "PkgConfig::libmagic" - "PkgConfig::GIO" -) - -if(NOT WANDA_APPLICATIONS_ONLY) - install(TARGETS "wanda-${LIB_NAME}" - FILE_SET HEADERS - ) -endif() - -add_library("wanda::${LIB_NAME}" ALIAS "wanda-${LIB_NAME}") diff --git a/source/lib/system/include/wanda/system/environment.hpp b/source/lib/system/include/wanda/system/environment.hpp deleted file mode 100644 index 8cd9ecf..0000000 --- a/source/lib/system/include/wanda/system/environment.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @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 - -#include -#include - -namespace wanda::system -{ - /** - * @brief A type to provide access to the runtime environment - */ - struct environment - { - using map_type = std::map; - 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/system/include/wanda/system/filesystem.hpp b/source/lib/system/include/wanda/system/filesystem.hpp deleted file mode 100644 index 971db90..0000000 --- a/source/lib/system/include/wanda/system/filesystem.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @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 -#include -#include - -namespace wanda::system -{ - /** - * @brief Covenience alias for path lists - */ - using path_list = std::vector; - - /** - * @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 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/system/include/wanda/system/logging.hpp b/source/lib/system/include/wanda/system/logging.hpp deleted file mode 100644 index 8a9a90e..0000000 --- a/source/lib/system/include/wanda/system/logging.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @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 -#include - -#include - -namespace wanda::system -{ - /** - * @brief A covenience alias to represent a handle for a logger - */ - using logger_ptr = std::shared_ptr; - - /** - * @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()); - - /** - * @brief Get the shared logger - */ - logger_ptr get_logger(); -} // namespace wanda::system - -#endif \ No newline at end of file diff --git a/source/lib/system/include/wanda/system/magic.hpp b/source/lib/system/include/wanda/system/magic.hpp deleted file mode 100644 index a8ea1ba..0000000 --- a/source/lib/system/include/wanda/system/magic.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef WANDA_SYSTEM_MAGIC_HPP -#define WANDA_SYSTEM_MAGIC_HPP - -#include - -#include -#include -#include -#include - -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, closer> m_handle; - }; - -} // namespace wanda::system - -#endif \ No newline at end of file diff --git a/source/lib/system/include/wanda/system/setting.hpp b/source/lib/system/include/wanda/system/setting.hpp deleted file mode 100644 index e0be3f4..0000000 --- a/source/lib/system/include/wanda/system/setting.hpp +++ /dev/null @@ -1,156 +0,0 @@ -/** - * @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 - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace wanda::system -{ - struct setting; - - /** - * @brief A convenience type to represent setting keys - */ - using key = meta::type_wrapper; - - 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 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>; - - /** - * @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 - 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) - { - return g_settings_set_boolean(setting, key, value); - } - else if constexpr (std::is_same_v) - { - return g_settings_set_int(setting, key, value); - } - else if constexpr (std::is_same_v) - { - return g_settings_set_int64(setting, key, value); - } - else if constexpr (std::is_same_v) - { - return g_settings_set_uint(setting, key, value); - } - else if constexpr (std::is_same_v) - { - return g_settings_set_uint64(setting, key, value); - } - else if constexpr (std::is_same_v) - { - return g_settings_set_double(setting, key, value); - } - else if constexpr (std::is_same_v) - { - return g_settings_set_string(setting, key, value.c_str()); - } - else if constexpr (std::is_same_v>) - { - auto temp = std::vector{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 m_settings; - - key m_key; - - friend setting; - }; - - /** - * @brief Get the entry for the given key - * - * @return An std::optional wrapping the entry associated with - * the given key, or an empty std::optional if the desired key - * does not exist in the setting's schema. - */ - std::optional operator[](key key) const; - - private: - explicit setting(GSettingsSchema * schema); - - std::unique_ptr m_schema; - - friend std::optional literals::operator""_setting(char const *, std::size_t); - }; - -} // namespace wanda::system - -#endif \ No newline at end of file diff --git a/source/lib/system/include/wanda/system/wallpaper.hpp b/source/lib/system/include/wanda/system/wallpaper.hpp deleted file mode 100644 index 7965fb0..0000000 --- a/source/lib/system/include/wanda/system/wallpaper.hpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @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 -#include - -#include -#include - -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/system/include/wanda/system/xdg.hpp b/source/lib/system/include/wanda/system/xdg.hpp deleted file mode 100644 index ae01feb..0000000 --- a/source/lib/system/include/wanda/system/xdg.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @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 -#include -#include - -namespace wanda::system -{ - /** - * @brief An @p enum to represet the standardized XDG directories - */ - enum struct xdg_directory : std::underlying_type_t - { - 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 diff --git a/source/lib/system/src/environment.cpp b/source/lib/system/src/environment.cpp deleted file mode 100644 index 8f79531..0000000 --- a/source/lib/system/src/environment.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "wanda/system/environment.hpp" - -#include - -namespace wanda::system -{ - environment::environment(char const * const * env) - { - if (!env) - { - return; - } - - std::string buffer{}; - for (; *env != nullptr; ++env) - { - buffer = *env; - int split_point = buffer.find('='); - if (split_point != std::string::npos) - { - m_cache[buffer.substr(0, split_point)] = buffer.substr(split_point + 1); - } - } - } - - std::string & environment::operator[](std::string const & variable) - { - return m_cache[variable]; - } - - std::string const & environment::operator[](std::string const & variable) const - { - static std::string const empty{}; - if (auto needle = m_cache.find(variable); needle != cend()) - { - return needle->second; - } - return empty; - } - - environment::iterator environment::begin() - { - return m_cache.begin(); - } - - environment::const_iterator environment::begin() const - { - return m_cache.begin(); - } - - environment::const_iterator environment::cbegin() const - { - return m_cache.cbegin(); - } - - environment::iterator environment::end() - { - return m_cache.end(); - } - - environment::const_iterator environment::end() const - { - return m_cache.end(); - } - - environment::const_iterator environment::cend() const - { - return m_cache.cend(); - } - -} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/system/src/filesystem.cpp b/source/lib/system/src/filesystem.cpp deleted file mode 100644 index 04dfff3..0000000 --- a/source/lib/system/src/filesystem.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "wanda/system/filesystem.hpp" - -#include -#include - -namespace wanda::system -{ - std::optional scan(std::filesystem::path source, bool(filter)(std::filesystem::path const &)) - { - if (!std::filesystem::is_directory(source)) - { - return std::nullopt; - } - auto entries = std::filesystem::recursive_directory_iterator{source}; - auto result = path_list{}; - for (auto & entry : entries | std::views::filter(filter)) - { - result.push_back(entry.path()); - } - return result; - } - - std::filesystem::path random_pick(path_list const & paths) - { - static auto generator = std::mt19937{std::random_device{}()}; - auto distribution = std::uniform_int_distribution{0, paths.size() - 1}; - - return paths[distribution(generator)]; - } - -} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/system/src/logging.cpp b/source/lib/system/src/logging.cpp deleted file mode 100644 index 83dc9f6..0000000 --- a/source/lib/system/src/logging.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "wanda/system/logging.hpp" - -namespace wanda::system -{ - std::function initializer = [](spdlog::sink_ptr sink) { - spdlog::register_logger(std::make_shared("wanda", sink)); - initializer = [](auto) {}; - }; - - void initialize_logger(spdlog::sink_ptr sink) - { - initializer(sink); - } - - logger_ptr get_logger() - { - initialize_logger(); - return spdlog::get("wanda"); - } - -} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/system/src/setting.cpp b/source/lib/system/src/setting.cpp deleted file mode 100644 index 1553eae..0000000 --- a/source/lib/system/src/setting.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "wanda/system/setting.hpp" - -#include -#include - -namespace wanda::system -{ - // UDL implementations - - key literals::operator""_key(char const * str, std::size_t len) - { - return key{{str, len}}; - } - - std::optional literals::operator""_setting(char const * str, std::size_t len) - { - auto source = g_settings_schema_source_get_default(); - if (!source) - { - return std::nullopt; - } - - auto schema = g_settings_schema_source_lookup(source, str, true); - if (!schema) - { - return std::nullopt; - } - - return setting{schema}; - } - - // 'setting' implementation - - setting::setting(GSettingsSchema * schema) - : m_schema{schema, &g_settings_schema_unref} - { - } - - std::optional setting::operator[](key key) const - { - if (!g_settings_schema_has_key(m_schema.get(), key.get().c_str())) - { - return std::nullopt; - } - - return setting::entry{*this, std::move(key)}; - } - - // 'setting::entry' implementation - - setting::entry::entry(setting const & setting, key key) - : m_settings{g_settings_new(g_settings_schema_get_id(setting.m_schema.get())), &g_object_unref} - , m_key{key.get()} - { - } - - setting::entry::value_type setting::entry::operator*() const - { - auto value = std::unique_ptr{g_settings_get_value(m_settings.get(), m_key.get().c_str()), &g_variant_unref}; - auto raw = value.get(); - - if (g_variant_is_of_type(raw, G_VARIANT_TYPE_BOOLEAN)) - { - return static_cast(g_variant_get_boolean(raw)); - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_INT32)) - { - return static_cast(g_variant_get_int32(raw)); - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_INT64)) - { - return static_cast(g_variant_get_int64(raw)); - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_UINT32)) - { - return static_cast(g_variant_get_uint32(raw)); - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_UINT64)) - { - return static_cast(g_variant_get_uint64(raw)); - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_DOUBLE)) - { - return static_cast(g_variant_get_double(raw)); - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_STRING)) - { - auto size = gsize{}; - auto string = g_variant_get_string(raw, &size); - return std::string{string, size}; - } - else if (g_variant_is_of_type(raw, G_VARIANT_TYPE_STRING_ARRAY)) - { - auto length = gsize{}; - auto data = g_variant_get_strv(raw, &length); - return std::vector{data, data + length}; - } - - return {}; - } - -} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/system/src/wallpaper.cpp b/source/lib/system/src/wallpaper.cpp deleted file mode 100644 index 11a6402..0000000 --- a/source/lib/system/src/wallpaper.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "wanda/system/wallpaper.hpp" - -#include "wanda/std_ext/optional.hpp" -#include "wanda/system/logging.hpp" -#include "wanda/system/magic.hpp" -#include "wanda/system/setting.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace wanda::system -{ - namespace - { - auto magic_instance = magic{}; - - auto load_image(std::filesystem::path wallpaper) - { - auto image = boost::gil::rgb8_image_t{}; - - switch (magic_instance.type(wallpaper)) - { - case magic::mime_type::image_jpeg: - boost::gil::read_and_convert_image(wallpaper.native(), image, boost::gil::jpeg_tag{}); - break; - case magic::mime_type::image_png: - boost::gil::read_and_convert_image(wallpaper.native(), image, boost::gil::png_tag{}); - break; - } - - return image; - } - - auto average_colors(boost::gil::rgb8_image_t image) - { - auto accumulator = boost::gil::rgb64f_pixel_t{}; - auto view = const_view(image); - - std::ranges::for_each(view, [&](auto const & source_pixel) { - at_c<0>(accumulator) += std::pow(boost::gil::at_c<0>(source_pixel), 2); - at_c<1>(accumulator) += std::pow(boost::gil::at_c<1>(source_pixel), 2); - at_c<2>(accumulator) += std::pow(boost::gil::at_c<2>(source_pixel), 2); - }); - - at_c<0>(accumulator) = std::sqrt(at_c<0>(accumulator) / view.size()); - at_c<1>(accumulator) = std::sqrt(at_c<1>(accumulator) / view.size()); - at_c<2>(accumulator) = std::sqrt(at_c<2>(accumulator) / view.size()); - - return accumulator; - } - - } // namespace - - void set_wallpaper(std::filesystem::path wallpaper) - { - using namespace wanda::system::literals; - using namespace wanda::std_ext; - using namespace std::string_literals; - - auto image = load_image(wallpaper); - auto color = average_colors(std::move(image)); - auto hexstring = spdlog::fmt_lib::format("#{:02X}{:02X}{:02X}", - static_cast(at_c<0>(color)), - static_cast(at_c<1>(color)), - static_cast(at_c<2>(color))); - - with("org.gnome.desktop.background"_setting, [&](auto & setting) { - with(setting["primary-color"_key], [&](auto & value) { - value = hexstring; - }); - with(setting["picture-uri"_key], [&](auto & value) { - value = "file://" + wallpaper.native(); - }) || - [&] { get_logger()->error("invalid settings key"); }; - with(setting["picture-uri-dark"_key], [&](auto & value) { - value = "file://" + wallpaper.native(); - }) || - [&] { get_logger()->error("invalid settings key"); }; - }) || - [&] { get_logger()->error("invalid setting"); }; - } - -} // namespace wanda::system diff --git a/source/lib/system/src/xdg.cpp b/source/lib/system/src/xdg.cpp deleted file mode 100644 index 109beb1..0000000 --- a/source/lib/system/src/xdg.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "wanda/system/xdg.hpp" - -#include - -namespace wanda::system -{ - std::string xdg_variable(xdg_directory directory) - { - switch (directory) - { - case xdg_directory::data_home: - return "XDG_DATA_HOME"; - case xdg_directory::config_home: - return "XDG_CONFIG_HOME"; - case xdg_directory::cache_home: - return "XDG_CACHE_HOME"; - case xdg_directory::runtime_dir: - return "XDG_RUNTIME_DIR"; - } - return "XDG_INVALID_PATH"; - } - - std::filesystem::path xdg_path_for(xdg_directory directory, environment const & environment) - { - if (auto path = environment[xdg_variable(directory)]; !path.empty()) - { - return path; - } - - auto home = std::filesystem::path{environment["HOME"]}; - switch (directory) - { - case xdg_directory::data_home: - return home / ".local/share"; - case xdg_directory::config_home: - return home / ".config"; - case xdg_directory::cache_home: - return home / ".cache"; - case xdg_directory::runtime_dir: - return std::filesystem::path{"/run/user"} / std::to_string(::getuid()); - } - - return ""; - } - -} // namespace wanda::system \ No newline at end of file diff --git a/source/lib/tests/xdg.cpp b/source/lib/tests/xdg.cpp new file mode 100644 index 0000000..5872dc6 --- /dev/null +++ b/source/lib/tests/xdg.cpp @@ -0,0 +1,171 @@ +#include "wanda/system/xdg.hpp" + +#include "wanda/system/environment.hpp" + +#include +#include + +#include +#include +#include + +using namespace std::string_literals; + +namespace wanda::system::test +{ + + TEST_CASE("XDG variable names match the specification", "[system][xdg][spec]") + { + REQUIRE(xdg_variable(xdg_directory::data_home) == "XDG_DATA_HOME"); + REQUIRE(xdg_variable(xdg_directory::config_home) == "XDG_CONFIG_HOME"); + REQUIRE(xdg_variable(xdg_directory::cache_home) == "XDG_CACHE_HOME"); + REQUIRE(xdg_variable(xdg_directory::runtime_dir) == "XDG_RUNTIME_DIR"); + } + + SCENARIO("The values of the XDG variables depend on the environment", "[system][xdg][spec]") + { + auto const home_directory = std::filesystem::path{"/home/test"}; + auto const run_directory = std::filesystem::path{"/run/user"}; + auto const uid = std::to_string(::getuid()); + + GIVEN("An environment that only contains HOME") + { + auto home = "HOME="s + home_directory.native(); + char const * env_data[] = {home.c_str(), nullptr}; + auto env = environment{env_data}; + + THEN("the data home is '/.local/share") + { + REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); + } + + THEN("the config home is '/.config") + { + REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); + } + + THEN("the cache home is '/.cache") + { + REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); + } + + THEN("the runtime directory is '/'") + { + REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); + } + } + + GIVEN("An environment that only contains HOME and XDG_DATA_HOME") + { + auto home = "HOME="s + home_directory.native(); + auto data_home = "XDG_DATA_HOME=/home/test/xdg_data_home"s; + char const * env_data[] = {home.c_str(), data_home.c_str(), nullptr}; + auto env = environment{env_data}; + + THEN("the data home is '$XDG_DATA_HOME") + { + REQUIRE(xdg_path_for(xdg_directory::data_home, env) == env["XDG_DATA_HOME"]); + } + + THEN("the config home is '/.config") + { + REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); + } + + THEN("the cache home is '/.cache") + { + REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); + } + + THEN("the runtime directory is '/'") + { + REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); + } + } + + GIVEN("An environment that only contains HOME and XDG_CONFIG_HOME") + { + auto home = "HOME="s + home_directory.native(); + auto config_home = "XDG_CONFIG_HOME=/home/test/xdg_config_home"s; + char const * env_data[] = {home.c_str(), config_home.c_str(), nullptr}; + auto env = environment{env_data}; + + THEN("the data home is '/.local/share") + { + REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); + } + + THEN("the config home is '$XDG_CONFIG_HOME") + { + REQUIRE(xdg_path_for(xdg_directory::config_home, env) == env["XDG_CONFIG_HOME"]); + } + + THEN("the cache home is '/.cache") + { + REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); + } + + THEN("the runtime directory is '/'") + { + REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); + } + } + + GIVEN("An environment that only contains HOME and XDG_CACHE_HOME") + { + auto home = "HOME="s + home_directory.native(); + auto cache_home = "XDG_CACHE_HOME=/home/test/xdg_cache_home"s; + char const * env_data[] = {home.c_str(), cache_home.c_str(), nullptr}; + auto env = environment{env_data}; + + THEN("the data home is '/.local/share") + { + REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); + } + + THEN("the cache home is '/.config") + { + REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); + } + + THEN("the config home is '$XDG_CACHE_HOME") + { + REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == env["XDG_CACHE_HOME"]); + } + + THEN("the runtime directory is '/'") + { + REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); + } + } + + GIVEN("An environment that only contains HOME and XDG_RUNTIME_DIR") + { + auto home = "HOME="s + home_directory.native(); + auto runtime_dir = "XDG_RUNTIME_DIR=/home/test/xdg_runtime_dir"s; + char const * env_data[] = {home.c_str(), runtime_dir.c_str(), nullptr}; + auto env = environment{env_data}; + + THEN("the data home is '/.local/share") + { + REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); + } + + THEN("the config home is '/.config") + { + REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); + } + + THEN("the cache home is '/.cache") + { + REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); + } + + THEN("the runtime directory is '$XDG_RUNTIME_DIR'") + { + REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == env["XDG_RUNTIME_DIR"]); + } + } + } + +} // namespace wanda::system::test \ No newline at end of file diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt deleted file mode 100644 index 07b922b..0000000 --- a/source/tests/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -include("Catch") - -add_subdirectory("app/wandac") -add_subdirectory("lib/system") \ No newline at end of file diff --git a/source/tests/app/wandac/CMakeLists.txt b/source/tests/app/wandac/CMakeLists.txt deleted file mode 100644 index ab07030..0000000 --- a/source/tests/app/wandac/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_executable("wanda-tests-wandac" - "src/cli.cpp" -) - -target_link_libraries("wanda-tests-wandac" PRIVATE - "${PROJECT_NAME}c-components" - - "Catch2::Catch2WithMain" -) - -catch_discover_tests("wanda-tests-wandac") \ No newline at end of file diff --git a/source/tests/app/wandac/src/cli.cpp b/source/tests/app/wandac/src/cli.cpp deleted file mode 100644 index ed02b4a..0000000 --- a/source/tests/app/wandac/src/cli.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "wandac/cli.hpp" - -#include -#include -#include - -#include -#include - -using namespace std::string_literals; - -namespace wanda::tests::app::wandac -{ - - template - auto make_argument_list(Ts const & ... args) -> lyra::args - { - return {"wanda"s, static_cast(args)...}; - } - - SCENARIO("Empty argument list parsing", "[app][client][cli]") - { - GIVEN("A fresh cli instance and error stream") - { - auto cli = ::wandac::cli{}; - auto error_stream = std::ostringstream{}; - - WHEN("invoking parse without any program arguments") - { - auto result = cli.parse(make_argument_list(), error_stream); - - THEN("the return value is false") { REQUIRE_FALSE(result); } - THEN("the error stream is not empty") { REQUIRE_FALSE(error_stream.view().empty()); } - THEN("the help flag is not set") { REQUIRE_FALSE(cli.help); } - THEN("the command is empty") { REQUIRE(cli.command.empty()); } - } - } - } - - SCENARIO("Valid argument list parsing", "[app][client][cli]") - { - GIVEN("A fresh cli instance and error stream") - { - auto cli = ::wandac::cli{}; - auto error_stream = std::ostringstream{}; - - AND_GIVEN("'-h' in the argument list") - { - auto argument_list = make_argument_list("-h"); - - WHEN("invoking parse without additional arguments") - { - auto result = cli.parse(argument_list, error_stream); - - THEN("the return value is true") { REQUIRE(result); } - THEN("the error stream is empty") { REQUIRE(error_stream.view().empty()); } - THEN("the help flag is set") { REQUIRE(cli.help); } - THEN("the command is empty") { REQUIRE(cli.command.empty()); } - } - } - - AND_GIVEN("'change' in the argument list") - { - auto argument_list = make_argument_list("change"); - - WHEN("invoking parse without additional arguments") - { - auto result = cli.parse(argument_list, error_stream); - - THEN("the return valis is true") { REQUIRE(result); } - THEN("the error stream is empty") { REQUIRE(error_stream.view().empty()); } - THEN("the help flag is not set") { REQUIRE_FALSE(cli.help); } - THEN("the command is not empty") { REQUIRE_FALSE(cli.command.empty()); } - THEN("the command is 'change'") { REQUIRE(cli.command == "change"); } - } - } - } - } - -} // namespace wanda::tests::app::wandac diff --git a/source/tests/lib/system/CMakeLists.txt b/source/tests/lib/system/CMakeLists.txt deleted file mode 100644 index a33c495..0000000 --- a/source/tests/lib/system/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_executable("wanda-tests-system" - "src/xdg.cpp" -) - -target_link_libraries("wanda-tests-system" PRIVATE - "wanda::system" - - "Catch2::Catch2WithMain" -) - -catch_discover_tests("wanda-tests-system") \ No newline at end of file diff --git a/source/tests/lib/system/src/xdg.cpp b/source/tests/lib/system/src/xdg.cpp deleted file mode 100644 index 5872dc6..0000000 --- a/source/tests/lib/system/src/xdg.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "wanda/system/xdg.hpp" - -#include "wanda/system/environment.hpp" - -#include -#include - -#include -#include -#include - -using namespace std::string_literals; - -namespace wanda::system::test -{ - - TEST_CASE("XDG variable names match the specification", "[system][xdg][spec]") - { - REQUIRE(xdg_variable(xdg_directory::data_home) == "XDG_DATA_HOME"); - REQUIRE(xdg_variable(xdg_directory::config_home) == "XDG_CONFIG_HOME"); - REQUIRE(xdg_variable(xdg_directory::cache_home) == "XDG_CACHE_HOME"); - REQUIRE(xdg_variable(xdg_directory::runtime_dir) == "XDG_RUNTIME_DIR"); - } - - SCENARIO("The values of the XDG variables depend on the environment", "[system][xdg][spec]") - { - auto const home_directory = std::filesystem::path{"/home/test"}; - auto const run_directory = std::filesystem::path{"/run/user"}; - auto const uid = std::to_string(::getuid()); - - GIVEN("An environment that only contains HOME") - { - auto home = "HOME="s + home_directory.native(); - char const * env_data[] = {home.c_str(), nullptr}; - auto env = environment{env_data}; - - THEN("the data home is '/.local/share") - { - REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); - } - - THEN("the config home is '/.config") - { - REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); - } - - THEN("the cache home is '/.cache") - { - REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); - } - - THEN("the runtime directory is '/'") - { - REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); - } - } - - GIVEN("An environment that only contains HOME and XDG_DATA_HOME") - { - auto home = "HOME="s + home_directory.native(); - auto data_home = "XDG_DATA_HOME=/home/test/xdg_data_home"s; - char const * env_data[] = {home.c_str(), data_home.c_str(), nullptr}; - auto env = environment{env_data}; - - THEN("the data home is '$XDG_DATA_HOME") - { - REQUIRE(xdg_path_for(xdg_directory::data_home, env) == env["XDG_DATA_HOME"]); - } - - THEN("the config home is '/.config") - { - REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); - } - - THEN("the cache home is '/.cache") - { - REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); - } - - THEN("the runtime directory is '/'") - { - REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); - } - } - - GIVEN("An environment that only contains HOME and XDG_CONFIG_HOME") - { - auto home = "HOME="s + home_directory.native(); - auto config_home = "XDG_CONFIG_HOME=/home/test/xdg_config_home"s; - char const * env_data[] = {home.c_str(), config_home.c_str(), nullptr}; - auto env = environment{env_data}; - - THEN("the data home is '/.local/share") - { - REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); - } - - THEN("the config home is '$XDG_CONFIG_HOME") - { - REQUIRE(xdg_path_for(xdg_directory::config_home, env) == env["XDG_CONFIG_HOME"]); - } - - THEN("the cache home is '/.cache") - { - REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); - } - - THEN("the runtime directory is '/'") - { - REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); - } - } - - GIVEN("An environment that only contains HOME and XDG_CACHE_HOME") - { - auto home = "HOME="s + home_directory.native(); - auto cache_home = "XDG_CACHE_HOME=/home/test/xdg_cache_home"s; - char const * env_data[] = {home.c_str(), cache_home.c_str(), nullptr}; - auto env = environment{env_data}; - - THEN("the data home is '/.local/share") - { - REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); - } - - THEN("the cache home is '/.config") - { - REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); - } - - THEN("the config home is '$XDG_CACHE_HOME") - { - REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == env["XDG_CACHE_HOME"]); - } - - THEN("the runtime directory is '/'") - { - REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == run_directory / uid); - } - } - - GIVEN("An environment that only contains HOME and XDG_RUNTIME_DIR") - { - auto home = "HOME="s + home_directory.native(); - auto runtime_dir = "XDG_RUNTIME_DIR=/home/test/xdg_runtime_dir"s; - char const * env_data[] = {home.c_str(), runtime_dir.c_str(), nullptr}; - auto env = environment{env_data}; - - THEN("the data home is '/.local/share") - { - REQUIRE(xdg_path_for(xdg_directory::data_home, env) == home_directory / ".local/share"); - } - - THEN("the config home is '/.config") - { - REQUIRE(xdg_path_for(xdg_directory::config_home, env) == home_directory / ".config"); - } - - THEN("the cache home is '/.cache") - { - REQUIRE(xdg_path_for(xdg_directory::cache_home, env) == home_directory / ".cache"); - } - - THEN("the runtime directory is '$XDG_RUNTIME_DIR'") - { - REQUIRE(xdg_path_for(xdg_directory::runtime_dir, env) == env["XDG_RUNTIME_DIR"]); - } - } - } - -} // namespace wanda::system::test \ No newline at end of file -- cgit v1.2.3