diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-10-26 20:21:43 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-10-26 20:21:43 +0100 |
| commit | b60b454920886a2cade3de27cd16bf4951105606 (patch) | |
| tree | 27707409e7a0c4605f515145e948d3f96bca4e44 /ttwhy/terminal | |
| parent | 0304a0602e19b9ec4feb0d12a2cfb5a94e4cb237 (diff) | |
| download | ttwhy-b60b454920886a2cade3de27cd16bf4951105606.tar.xz ttwhy-b60b454920886a2cade3de27cd16bf4951105606.zip | |
terminal: echo function and control keys
Diffstat (limited to 'ttwhy/terminal')
| -rw-r--r-- | ttwhy/terminal/echo.cppm | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/ttwhy/terminal/echo.cppm b/ttwhy/terminal/echo.cppm index 6f1ac2e..19d15fc 100644 --- a/ttwhy/terminal/echo.cppm +++ b/ttwhy/terminal/echo.cppm @@ -27,6 +27,106 @@ namespace ttwhy::terminal constexpr auto vte_cursor_left_sequence_format = "\x1b[{}D"sv; constexpr auto vte_cursor_right_sequence_format = "\x1b[{}C"sv; + namespace detail + { + //! Maps a function key to its CSI parameter code (F5 through F20). + auto function_key_parameter(function_key key) -> unsigned int + { + switch (key) + { + case function_key::f5: + return 15; + case function_key::f6: + return 17; + case function_key::f7: + return 18; + case function_key::f8: + return 19; + case function_key::f9: + return 20; + case function_key::f10: + return 21; + case function_key::f11: + return 23; + case function_key::f12: + return 24; + case function_key::f13: + return 25; + case function_key::f14: + return 26; + case function_key::f15: + return 28; + case function_key::f16: + return 29; + case function_key::f17: + return 31; + case function_key::f18: + return 32; + case function_key::f19: + return 33; + case function_key::f20: + return 34; + default: + return 0; + } + } + + //! Appends an xterm-style modifier parameter to a CSI sequence. + auto apply_modifier(std::string sequence, modifier_set modifiers) -> std::string + { + auto const mask = static_cast<std::uint8_t>(modifiers); + auto const modifier_param = [&mask]() -> unsigned int { + if ((mask & static_cast<std::uint8_t>(modifier::shift)) != 0) + { + return 2; + } + if ((mask & static_cast<std::uint8_t>(modifier::alt)) != 0) + { + return 3; + } + if ((mask & static_cast<std::uint8_t>(modifier::ctrl)) != 0) + { + return 4; + } + if ((mask & static_cast<std::uint8_t>(modifier::meta)) != 0) + { + return 5; + } + return 1; + }(); + + if (modifier_param == 1) + { + return sequence; + } + + auto const terminator = sequence.find_last_of('~'); + if (terminator == std::string::npos) + { + return sequence; + } + return sequence.substr(0, terminator) + std::to_string(modifier_param) + sequence.substr(terminator); + } + + //! Builds the terminal escape sequence that represents a function key press. + auto function_key_sequence(function_key key, modifier_set modifiers) -> std::string + { + switch (key) + { + case function_key::f1: + return apply_modifier(std::string{"\x1bOP"}, modifiers); + case function_key::f2: + return apply_modifier(std::string{"\x1bOQ"}, modifiers); + case function_key::f3: + return apply_modifier(std::string{"\x1bOR"}, modifiers); + case function_key::f4: + return apply_modifier(std::string{"\x1bOS"}, modifiers); + default: + return apply_modifier(std::format("\x1b[{}~", function_key_parameter(key)), modifiers); + } + } + } // namespace detail + //! An asynchronous router that echoes characters and handles terminal navigation. //! //! @tparam StreamType The output stream descriptor type. @@ -78,11 +178,33 @@ namespace ttwhy::terminal { co_await process_navigation_key(event); } + else if constexpr (std::same_as<function_key_event, event_type>) + { + co_await process_function_key(event); + } + else if constexpr (std::same_as<ctrl_chord_event, event_type>) + { + co_await process_ctrl_chord(event); + } }, event); } private: + auto process_function_key(function_key_event event) -> asio::awaitable<void> + { + auto const sequence = detail::function_key_sequence(event.key, event.modifiers); + co_await asio::async_write(m_output_stream, asio::buffer(sequence.data(), sequence.size()), + asio::as_tuple(asio::use_awaitable)); + } + + auto process_ctrl_chord(ctrl_chord_event event) -> asio::awaitable<void> + { + auto const control_character = static_cast<char>(static_cast<unsigned char>(event.key) - 0x40u); + co_await asio::async_write(m_output_stream, asio::buffer(&control_character, 1), + asio::as_tuple(asio::use_awaitable)); + } + auto process_character(char character) -> asio::awaitable<void> { auto [error, bytes_written] = |
