diff options
Diffstat (limited to 'source/lib/proto')
| -rw-r--r-- | source/lib/proto/CMakeLists.txt | 38 | ||||
| -rw-r--r-- | source/lib/proto/include/wanda/proto/command.hpp | 46 | ||||
| -rw-r--r-- | source/lib/proto/include/wanda/proto/message.hpp | 84 | ||||
| -rw-r--r-- | source/lib/proto/include/wanda/proto/version.hpp | 12 | ||||
| -rw-r--r-- | source/lib/proto/src/command.cpp | 47 | ||||
| -rw-r--r-- | source/lib/proto/src/message.cpp | 82 |
6 files changed, 309 insertions, 0 deletions
diff --git a/source/lib/proto/CMakeLists.txt b/source/lib/proto/CMakeLists.txt new file mode 100644 index 0000000..9967a95 --- /dev/null +++ b/source/lib/proto/CMakeLists.txt @@ -0,0 +1,38 @@ +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("fmt" REQUIRED) + +add_library("wanda-${LIB_NAME}" + ${LIB_SOURCES} +) + +target_sources("wanda-${LIB_NAME}" INTERFACE + FILE_SET HEADERS + FILES ${LIB_HEADERS} + BASE_DIRS "include" +) + +target_include_directories("wanda-${LIB_NAME}" PUBLIC + "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>" +) + +target_include_directories("wanda-${LIB_NAME}" SYSTEM PUBLIC + "$<INSTALL_INTERFACE:include>" +) + +target_compile_features("wanda-${LIB_NAME}" PUBLIC + "cxx_std_20" +) + +target_link_libraries("wanda-${LIB_NAME}" PUBLIC + "fmt::fmt" +) + +install(TARGETS "wanda-${LIB_NAME}" + FILE_SET HEADERS +) + +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 new file mode 100644 index 0000000..c8dae65 --- /dev/null +++ b/source/lib/proto/include/wanda/proto/command.hpp @@ -0,0 +1,46 @@ +#ifndef WANDA_PROTO_COMMAND_HPP +#define WANDA_PROTO_COMMAND_HPP + +#include "wanda/proto/message.hpp" + +#include <optional> +#include <string> +#include <vector> + +namespace wanda::proto +{ + /** + * @brief An enum to describe different command IDs + */ + enum struct command_id : char + { + change, //< Change the wallpaper + }; + + /** + * @brief A simple type to represent commands transported through the control connection + */ + struct command + { + command_id const id; + std::vector<std::string> const arguments; + + /** + * @brief Convert the command to a message for transmission to a remote endpoint + */ + std::optional<wanda::proto::message> message() const; + }; + + /** + * @brief Extract a command from a message + */ + std::optional<command> make_command(message message); + + /** + * @brief A simple factory to create a "Change wallpaper" command + */ + command make_change_command(); + +} // namespace wanda::proto + +#endif
\ No newline at end of file diff --git a/source/lib/proto/include/wanda/proto/message.hpp b/source/lib/proto/include/wanda/proto/message.hpp new file mode 100644 index 0000000..f4a6a3b --- /dev/null +++ b/source/lib/proto/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 <fmt/core.h> + +#include <cstddef> +#include <istream> +#include <optional> +#include <string> +#include <string_view> + +namespace wanda::proto +{ + /** + * @brief A tag to mark messages originating from the controller + */ + auto constexpr message_source_controller = "C"; + + /** + * @brief A tag to mark messages originating from the daemon + */ + auto constexpr message_source_daemon = "D"; + + /** + * @brief The command of the hello message + */ + auto constexpr message_command_hello = "HELLO"; + + /** + * @brief A control protocol message, consisting of a @p source, @p command, and @p arguments + */ + struct message + { + /** + * @brief Serialize this message into a string + */ + explicit operator std::string() const; + + /** + * @brief Get the size of the message as if it was serialized + */ + std::size_t size() const; + + /** + * @brief The source of the message + */ + std::string source; + + /** + * @brief The command of the message + */ + std::string command; + + /** + * @brief The arguments of the message command + */ + std::optional<std::string> argument; + }; + + /** + * @brief Deserialize a message from the given stream + */ + std::istream & operator>>(std::istream & in, message & message); + + /** + * @brief Serialize a message to the given stream + */ + std::ostream & operator<<(std::ostream & out, message const & message); + +} // namespace wanda::proto + +template<> +struct fmt::formatter<wanda::proto::message> : fmt::formatter<std::string> +{ + auto format(wanda::proto::message const & message, format_context & context) const -> decltype(context.out()); +}; + +#endif
\ No newline at end of file diff --git a/source/lib/proto/include/wanda/proto/version.hpp b/source/lib/proto/include/wanda/proto/version.hpp new file mode 100644 index 0000000..faa17d7 --- /dev/null +++ b/source/lib/proto/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/proto/src/command.cpp b/source/lib/proto/src/command.cpp new file mode 100644 index 0000000..5a669f5 --- /dev/null +++ b/source/lib/proto/src/command.cpp @@ -0,0 +1,47 @@ +#include "wanda/proto/command.hpp" + +namespace wanda::proto +{ + std::optional<message> 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<command> 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 new file mode 100644 index 0000000..dad3e86 --- /dev/null +++ b/source/lib/proto/src/message.cpp @@ -0,0 +1,82 @@ +#include "wanda/proto/message.hpp" + +#include <fmt/format.h> + +#include <ios> +#include <iterator> +#include <sstream> + +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<std::string>(*this).size(); + } + + template<typename InputIt, typename OutputIt, typename UnaryPredicate> + 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<char>{in}; + auto end = std::istream_iterator<char>{}; + 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<std::string>(message); + } + +} // namespace wanda::proto + +auto fmt::formatter<wanda::proto::message>::format(wanda::proto::message const & message, format_context & context) const -> decltype(context.out()) +{ + return formatter<std::string>::format(static_cast<std::string>(message), context); +} |
