diff options
Diffstat (limited to 'source/apps')
| -rw-r--r-- | source/apps/CMakeLists.txt | 54 | ||||
| -rw-r--r-- | source/apps/include/wanda/wandac/cli.hpp | 27 | ||||
| -rw-r--r-- | source/apps/include/wanda/wandac/listener.hpp | 26 | ||||
| -rw-r--r-- | source/apps/src/wandac/cli.cpp | 30 | ||||
| -rw-r--r-- | source/apps/src/wandac/listener.cpp | 29 | ||||
| -rw-r--r-- | source/apps/src/wandac/main.cpp | 43 | ||||
| -rw-r--r-- | source/apps/src/wandad/main.cpp | 148 | ||||
| -rw-r--r-- | source/apps/tests/wandac/cli.cpp | 80 |
8 files changed, 437 insertions, 0 deletions
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 <lyra/args.hpp> +#include <lyra/cli_parser.hpp> + +#include <iosfwd> +#include <string> + +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 <wanda/control/commander.hpp> + +#include <boost/asio/io_context.hpp> + +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 <lyra/arg.hpp> +#include <lyra/help.hpp> + +#include <ostream> + +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 <wanda/control/commander.hpp> +#include <wanda/proto/command.hpp> + +#include <boost/asio/io_context.hpp> + +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 <wanda/control/commander.hpp> +#include <wanda/system/environment.hpp> +#include <wanda/system/logging.hpp> +#include <wanda/system/xdg.hpp> + +#include <boost/asio/io_context.hpp> +#include <spdlog/sinks/stdout_color_sinks.h> + +#include <cstdlib> +#include <filesystem> +#include <iostream> + +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<spdlog::sinks::stderr_color_sink_st>()); + + 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 <wanda/control/interface.hpp> +#include <wanda/proto/command.hpp> +#include <wanda/std_ext/optional.hpp> +#include <wanda/system/environment.hpp> +#include <wanda/system/filesystem.hpp> +#include <wanda/system/logging.hpp> +#include <wanda/system/setting.hpp> +#include <wanda/system/wallpaper.hpp> +#include <wanda/system/xdg.hpp> + +#include <boost/asio/io_context.hpp> +#include <boost/asio/signal_set.hpp> +#include <lyra/lyra.hpp> +#include <spdlog/sinks/stdout_color_sinks.h> + +#include <csignal> +#include <cstdlib> +#include <filesystem> +#include <iostream> +#include <set> +#include <string> + +namespace +{ + constexpr auto image_filter = [](auto const & path) { + static auto const extensions = std::set<std::filesystem::path>{ + 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<std::filesystem::path> 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<int>(command.id)); + } + } + + private: + std::vector<std::filesystem::path> 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<spdlog::sinks::stdout_color_sink_st>()); + 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 <catch2/catch_all.hpp> +#include <catch2/catch_test_macros.hpp> +#include <lyra/args.hpp> + +#include <sstream> +#include <string> + +using namespace std::string_literals; + +namespace wanda::tests::app::wandac +{ + + template<typename ...Ts> + auto make_argument_list(Ts const & ... args) -> lyra::args + { + return {"wanda"s, static_cast<std::string>(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 |
