diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-06-20 11:54:06 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-06-20 11:54:06 +0200 |
| commit | 912e7b10facdd3b2565423206ae25b1d6e4f7dd9 (patch) | |
| tree | e744bc76893e8b1faa216b1aae93c7ec6e933a04 | |
| parent | 280190248bfbadaec4575932dc956401f7bb6390 (diff) | |
| download | ttwhy-912e7b10facdd3b2565423206ae25b1d6e4f7dd9.tar.xz ttwhy-912e7b10facdd3b2565423206ae25b1d6e4f7dd9.zip | |
lib: add xterm policy
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | ttwhy/io.cppm | 6 | ||||
| -rw-r--r-- | ttwhy/routers/echo.cppm | 21 | ||||
| -rw-r--r-- | ttwhy/scanners/events.cppm | 52 | ||||
| -rw-r--r-- | ttwhy/scanners/mod.cppm | 4 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_concepts.cppm (renamed from ttwhy/scanners/concepts.cppm) | 5 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_events.cppm | 100 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_policies.cppm | 180 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_scanner.cppm | 6 |
9 files changed, 264 insertions, 114 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 12536ce..71b27c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,9 +61,9 @@ target_sources("ttwhy-core" PUBLIC "ttwhy/routers/echo.cppm" "ttwhy/routers/mod.cppm" - "ttwhy/scanners/concepts.cppm" - "ttwhy/scanners/events.cppm" "ttwhy/scanners/mod.cppm" + "ttwhy/scanners/terminal_concepts.cppm" + "ttwhy/scanners/terminal_events.cppm" "ttwhy/scanners/terminal_policies.cppm" "ttwhy/scanners/terminal_scanner.cppm" ) diff --git a/ttwhy/io.cppm b/ttwhy/io.cppm index 4b83996..fe973ed 100644 --- a/ttwhy/io.cppm +++ b/ttwhy/io.cppm @@ -74,14 +74,16 @@ namespace ttwhy::io auto sink = [&queue](auto const & event) { queue.push_back(event); }; - auto scanner = scanners::ansi{sink}; + + using terminal_policy = ttwhy::scanners::associated_terminal_policy_t<AppRouter>; + auto scanner = scanners::terminal_scanner<decltype(sink), terminal_policy>{sink}; auto raw_buffer = std::array<char, 64>{}; while (true) { auto error = asio::error_code{}; - auto bytes_read = std::size_t{}; + auto bytes_read = 0uz; if (scanner.is_pending()) { diff --git a/ttwhy/routers/echo.cppm b/ttwhy/routers/echo.cppm index 40e68d6..7f2550e 100644 --- a/ttwhy/routers/echo.cppm +++ b/ttwhy/routers/echo.cppm @@ -22,6 +22,8 @@ namespace ttwhy::routers export template<typename StreamType> struct echo { + using terminal_policy = scanners::xterm_policy; + explicit echo(StreamType & stream) : m_output_stream{stream} {} @@ -42,7 +44,7 @@ namespace ttwhy::routers } else if constexpr (std::same_as<scanners::navigation_event, event_type>) { - co_await process_navigation_key(event.key); + co_await process_navigation_key(event); } }, event); @@ -77,16 +79,23 @@ namespace ttwhy::routers }; } - auto process_navigation_key(scanners::navigation_key key) -> asio::awaitable<void> + auto process_navigation_key(scanners::navigation_event event) -> asio::awaitable<void> { - switch (key) + auto modifiers = std::to_underlying(event.modifiers); + + switch (event.key) { case scanners::navigation_key::delete_key: - co_await asio::async_write(m_output_stream, asio::buffer("{DEL}"), asio::as_tuple(asio::use_awaitable)); + { + auto message = std::format("{{DEL, MODS:{}}}", static_cast<int>(modifiers)); + co_await asio::async_write(m_output_stream, asio::buffer(message), asio::as_tuple(asio::use_awaitable)); break; + } default: - co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{NAV:{}}}", std::to_underlying(key))), - asio::as_tuple(asio::use_awaitable)); + { + auto message = std::format("{{NAV:{}, MODS:{}}}", static_cast<int>(event.key), static_cast<int>(modifiers)); + co_await asio::async_write(m_output_stream, asio::buffer(message), asio::as_tuple(asio::use_awaitable)); + } } } diff --git a/ttwhy/scanners/events.cppm b/ttwhy/scanners/events.cppm deleted file mode 100644 index 03ab9eb..0000000 --- a/ttwhy/scanners/events.cppm +++ /dev/null @@ -1,52 +0,0 @@ -module; - -#include <variant> - -export module ttwhy.scanners:events; - -namespace ttwhy::scanners -{ - export enum class control_key - { - enter, - escape, - tab, - backspace, - }; - - export enum class navigation_key - { - up, - down, - left, - right, - home, - end, - delete_key, - insert_key, - page_up, - page_down, - }; - - export struct character_event - { - char value; - }; - - export struct control_event - { - control_key key; - }; - - export struct navigation_event - { - navigation_key key; - }; - - export struct ctrl_chord_event - { - char key; - }; - - export using input_event = std::variant<character_event, control_event, navigation_event, ctrl_chord_event>; -} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/mod.cppm b/ttwhy/scanners/mod.cppm index 54f1e0e..a1f45c1 100644 --- a/ttwhy/scanners/mod.cppm +++ b/ttwhy/scanners/mod.cppm @@ -1,6 +1,6 @@ export module ttwhy.scanners; -export import :concepts; -export import :events; +export import :terminal_concepts; +export import :terminal_events; export import :terminal_policies; export import :terminal_scanner; diff --git a/ttwhy/scanners/concepts.cppm b/ttwhy/scanners/terminal_concepts.cppm index 8db3ae6..e82a8d0 100644 --- a/ttwhy/scanners/concepts.cppm +++ b/ttwhy/scanners/terminal_concepts.cppm @@ -2,11 +2,10 @@ module; #include <concepts> #include <span> -#include <string> -export module ttwhy.scanners:concepts; +export module ttwhy.scanners:terminal_concepts; -import :events; +import :terminal_events; namespace ttwhy { diff --git a/ttwhy/scanners/terminal_events.cppm b/ttwhy/scanners/terminal_events.cppm new file mode 100644 index 0000000..3e4c849 --- /dev/null +++ b/ttwhy/scanners/terminal_events.cppm @@ -0,0 +1,100 @@ +module; + +#include <cstdint> +#include <utility> +#include <variant> + +export module ttwhy.scanners:terminal_events; + +export namespace ttwhy::scanners +{ + enum struct control_key : std::uint8_t + { + enter, + escape, + tab, + backspace, + }; + + enum struct navigation_key : std::uint8_t + { + up, + down, + left, + right, + home, + end, + delete_key, + insert_key, + page_up, + page_down, + }; + + enum struct modifier : std::uint8_t + { + none = 0, + shift = 1, + alt = 2, + ctrl = 4, + meta = 8, + }; + + enum struct modifier_set : std::uint8_t + { + }; + + constexpr auto operator|(modifier lhs, modifier rhs) -> modifier_set + { + return static_cast<modifier_set>(std::to_underlying(lhs) | std::to_underlying(rhs)); + } + + constexpr auto operator|(modifier_set lhs, modifier rhs) -> modifier_set + { + return static_cast<modifier_set>(std::to_underlying(lhs) | std::to_underlying(rhs)); + } + + constexpr auto operator&(modifier lhs, modifier rhs) -> modifier_set + { + return static_cast<modifier_set>(std::to_underlying(lhs) & std::to_underlying(rhs)); + } + + constexpr auto operator&(modifier_set lhs, modifier rhs) -> modifier_set + { + return static_cast<modifier_set>(std::to_underlying(lhs) & std::to_underlying(rhs)); + } + + template<std::same_as<modifier>... Modifier> + constexpr auto has_all(modifier_set mask, Modifier... flag) -> bool + { + return (true && ... && (mask & flag)); + } + + template<std::same_as<modifier>... Modifier> + constexpr auto has_any(modifier_set mask, Modifier... flag) -> bool + { + return (false || ... || (mask & flag)); + } + + struct character_event + { + char value{}; + }; + + struct control_event + { + control_key key{}; + }; + + struct navigation_event + { + navigation_key key{}; + modifier_set modifiers{}; + }; + + struct ctrl_chord_event + { + char key{}; + }; + + using input_event = std::variant<character_event, control_event, navigation_event, ctrl_chord_event>; +} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/terminal_policies.cppm b/ttwhy/scanners/terminal_policies.cppm index e5863d3..bf7c126 100644 --- a/ttwhy/scanners/terminal_policies.cppm +++ b/ttwhy/scanners/terminal_policies.cppm @@ -1,70 +1,162 @@ module; +#include <optional> #include <string> export module ttwhy.scanners:terminal_policies; -import :events; -import :concepts; +import :terminal_events; +import :terminal_concepts; namespace ttwhy::scanners { + namespace detail + { + constexpr auto map_vt220_keypad(char terminator) -> std::optional<navigation_key> + { + switch (terminator) + { + case '1': + case '7': + return navigation_key::home; + case '2': + return navigation_key::insert_key; + case '3': + return navigation_key::delete_key; + case '4': + case '8': + return navigation_key::end; + case '5': + return navigation_key::page_up; + case '6': + return navigation_key::page_down; + default: + return std::nullopt; + } + } + + constexpr auto map_vt100_cursor(char terminator) -> std::optional<navigation_key> + { + switch (terminator) + { + case 'A': + return navigation_key::up; + case 'B': + return navigation_key::down; + case 'C': + return navigation_key::right; + case 'D': + return navigation_key::left; + case 'H': + return navigation_key::home; + case 'F': + return navigation_key::end; + default: + return std::nullopt; + } + } + + } // namespace detail + + export template<typename T> + using associated_terminal_policy_t = typename T::terminal_policy; + export struct ansi_policy { constexpr auto static resolve_vt220_keypad(std::string & buffer, ttwhy::ansi_sink auto & sink) -> void { - auto key = [](auto terminator) { - switch (terminator) - { - case '1': - case '7': - return navigation_key::home; - case '2': - return navigation_key::insert_key; - case '3': - return navigation_key::delete_key; - case '4': - case '8': - return navigation_key::end; - case '5': - return navigation_key::page_up; - case '6': - return navigation_key::page_down; - default: - return static_cast<navigation_key>(-1); - } - }(buffer.back()); - sink(navigation_event{key}); + if (buffer.empty()) + { + return; + } + + if (auto key = detail::map_vt220_keypad(buffer.front())) + { + sink(navigation_event{*key}); + } + buffer.clear(); } - constexpr auto static resolve_vt100_cursor(char terminator, ttwhy::ansi_sink auto & sink) -> void + constexpr auto static resolve_vt100_cursor(char terminator, std::string &, ttwhy::ansi_sink auto & sink) -> void { - auto key = [terminator] { - switch (terminator) + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key}); + } + } + + constexpr auto static resolve_ss3(char terminator, ttwhy::ansi_sink auto & sink) -> void + { + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key}); + } + } + }; + + export struct xterm_policy + { + constexpr auto static parse_modifiers(std::string_view buffer) -> modifier_set + { + auto delimiter = buffer.find(';'); + if (delimiter == std::string_view::npos) + { + return {}; + } + + auto modifier_string = buffer.substr(delimiter + 1); + auto modifier_value = 0u; + + for (auto character : modifier_string) + { + if (character >= '0' && character <= '9') { - case 'A': - return navigation_key::up; - case 'B': - return navigation_key::down; - case 'C': - return navigation_key::right; - case 'D': - return navigation_key::left; - case 'H': - return navigation_key::home; - case 'F': - return navigation_key::end; - default: - return static_cast<navigation_key>(-1); + modifier_value = modifier_value * 10 + static_cast<unsigned int>(character - '0'); } - }(); - sink(navigation_event{key}); + } + + if (modifier_value <= 1) + { + return {}; + } + + return static_cast<modifier_set>(modifier_value - 1); + } + + constexpr auto static resolve_vt220_keypad(std::string & buffer, ttwhy::ansi_sink auto & sink) -> void + { + if (buffer.empty()) + { + return; + } + + auto modifiers = parse_modifiers(buffer); + + if (auto key = detail::map_vt220_keypad(buffer.front())) + { + sink(navigation_event{*key, modifiers}); + } + buffer.clear(); + } + + constexpr auto static resolve_vt100_cursor(char terminator, std::string & buffer, ttwhy::ansi_sink auto & sink) + -> void + { + auto modifiers = parse_modifiers(buffer); + + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key, modifiers}); + } } constexpr auto static resolve_ss3(char terminator, ttwhy::ansi_sink auto & sink) -> void { - return resolve_vt100_cursor(terminator, sink); + if (auto key = detail::map_vt100_cursor(terminator)) + { + sink(navigation_event{*key}); + } } }; diff --git a/ttwhy/scanners/terminal_scanner.cppm b/ttwhy/scanners/terminal_scanner.cppm index d877350..510299c 100644 --- a/ttwhy/scanners/terminal_scanner.cppm +++ b/ttwhy/scanners/terminal_scanner.cppm @@ -9,8 +9,8 @@ module; export module ttwhy.scanners:terminal_scanner; -import :concepts; -import :events; +import :terminal_concepts; +import :terminal_events; import :terminal_policies; namespace ttwhy::scanners::detail @@ -153,7 +153,7 @@ namespace ttwhy::scanners::detail }; constexpr auto resolve_vt100_cursor = [](byte_received event, std::string & buffer, Sink & sink) { - TerminalPolicy::resolve_vt100_cursor(event.value, sink); + TerminalPolicy::resolve_vt100_cursor(event.value, buffer, sink); buffer.clear(); }; |
