diff --git a/CMakeLists.txt b/CMakeLists.txt index d871e9a37..411577627 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,7 @@ add_library(component src/ftxui/component/resizable_split.cpp src/ftxui/component/screen_interactive.cpp src/ftxui/component/slider.cpp + src/ftxui/component/terminal_id.cpp src/ftxui/component/terminal_input_parser.cpp src/ftxui/component/terminal_input_parser.hpp src/ftxui/component/util.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 3444d5637..1ec572e96 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -46,6 +46,7 @@ example(slider_direction) example(slider_rgb) example(tab_horizontal) example(tab_vertical) +example(terminal_id) example(textarea) example(toggle) example(window) diff --git a/examples/component/terminal_id.cpp b/examples/component/terminal_id.cpp new file mode 100644 index 000000000..d99ddead9 --- /dev/null +++ b/examples/component/terminal_id.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include "ftxui/component/component.hpp" // for Renderer +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for text, hbox, separator, Element, operator|, vbox, border + +int main() { + using namespace ftxui; + + std::string terminal_id = "UNKNOWN"; + + auto screen = + ScreenInteractive::TerminalOutput(); + + screen.OnTerminalIDUpdate([&terminal_id] ( + TerminalID const& terminal_id_update) + { + std::stringstream stream; + stream << terminal_id_update; + terminal_id = stream.str(); + }); + + auto renderer = Renderer([&] + { + return vbox({ + text("Terminal id " + terminal_id), + }) | border; + }); + + screen.Loop(renderer); +} diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index f95af3104..cac5e16a5 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -4,8 +4,9 @@ #ifndef FTXUI_COMPONENT_EVENT_HPP #define FTXUI_COMPONENT_EVENT_HPP -#include // for Mouse -#include // for string, operator== +#include // for Mouse +#include // for TerminalID +#include // for string, operator== namespace ftxui { @@ -35,6 +36,7 @@ struct Event { static Event Mouse(std::string, Mouse mouse); static Event CursorPosition(std::string, int x, int y); // Internal static Event CursorShape(std::string, int shape); // Internal + static Event TerminalID(std::string input, enum TerminalID terminal_id); // Internal // --- Arrow --- static const Event ArrowLeft; @@ -117,6 +119,9 @@ struct Event { bool is_cursor_shape() const { return type_ == Type::CursorShape; } int cursor_shape() const { return data_.cursor_shape; } + bool is_terminal_id() const { return type_ == Type::TerminalID; } + enum TerminalID terminal_id() const { return data_.terminal_id; } + // Debug std::string DebugString() const; @@ -132,6 +137,7 @@ struct Event { Mouse, CursorPosition, CursorShape, + TerminalID }; Type type_ = Type::Unknown; @@ -144,6 +150,7 @@ struct Event { struct Mouse mouse; struct Cursor cursor; int cursor_shape; + enum TerminalID terminal_id; } data_ = {}; std::string input_; diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index f9220ff8e..272f9856d 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -10,6 +10,7 @@ #include // for shared_ptr #include // for string #include // for thread +#include #include "ftxui/component/animation.hpp" // for TimePoint #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse @@ -72,6 +73,13 @@ class ScreenInteractive : public Screen { void ForceHandleCtrlC(bool force); void ForceHandleCtrlZ(bool force); + TerminalID TerminalId() const; + + typedef std::function TerminalIDUpdateCallback; + + void OnTerminalIDUpdate( + TerminalIDUpdateCallback const& callback); + // Selection API. std::string GetSelection(); void SelectionChange(std::function callback); @@ -156,6 +164,11 @@ class ScreenInteractive : public Screen { std::unique_ptr selection_; std::function selection_on_change_; + TerminalID m_terminal_id; + + typedef std::list TerminalIDUpdateCallbackContainer; + TerminalIDUpdateCallbackContainer m_terminal_id_update_callbacks; + friend class Loop; public: diff --git a/include/ftxui/component/terminal_id.hpp b/include/ftxui/component/terminal_id.hpp new file mode 100644 index 000000000..ab9b1bea6 --- /dev/null +++ b/include/ftxui/component/terminal_id.hpp @@ -0,0 +1,35 @@ +// Copyright 2025 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#ifndef FTXUI_COMPONENT_TERMINAL_ID_HPP +#define FTXUI_COMPONENT_TERMINAL_ID_HPP + +#include +#include + +namespace ftxui { + +std::string const TERMINAL_ID_REQUEST("\x1b[0c"); + +/// @brief A mouse event. It contains the coordinate of the mouse, the button +/// pressed and the modifier (shift, ctrl, meta). +/// @ingroup component + +enum class TerminalID +{ + UNKNOWN, + XTERM, + KONSOLE, + URXVT, + VTE, + LINUXVC +}; + +std::ostream& operator<<( + std::ostream& os, + TerminalID terminal_id); + +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_TERMINAL_ID_HPP */ diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index dfcb6b27c..dbc6af2a0 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -87,6 +87,16 @@ Event Event::CursorPosition(std::string input, int x, int y) { return event; } +/// @internal +// static +Event Event::TerminalID(std::string input, enum TerminalID terminal_id) { + Event event; + event.input_ = std::move(input); + event.type_ = Type::TerminalID; + event.data_.terminal_id = terminal_id; + return event; +} + /// @brief Return a string representation of the event. std::string Event::DebugString() const { static std::map event_to_string = { diff --git a/src/ftxui/component/loop.cpp b/src/ftxui/component/loop.cpp index 5cdb7d970..28ce6eaf9 100644 --- a/src/ftxui/component/loop.cpp +++ b/src/ftxui/component/loop.cpp @@ -1,7 +1,9 @@ // Copyright 2022 Arthur Sonzogni. All rights reserved. // Use of this source code is governed by the MIT license that can be found in // the LICENSE file. +#include #include "ftxui/component/loop.hpp" +#include "ftxui/component/terminal_id.hpp" #include // for move @@ -47,6 +49,9 @@ void Loop::RunOnceBlocking() { /// Execute the loop, blocking the current thread, up until the loop has /// quitted. void Loop::Run() { + // Ensure we perform a single terminal id request before we are starting the loop itself. + std::cout << TERMINAL_ID_REQUEST; + while (!HasQuitted()) { RunOnceBlocking(); } diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index dadeff7e8..3cb414cb5 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -352,7 +352,8 @@ ScreenInteractive::ScreenInteractive(Dimension dimension, bool use_alternative_screen) : Screen(dimx, dimy), dimension_(dimension), - use_alternative_screen_(use_alternative_screen) { + use_alternative_screen_(use_alternative_screen), + m_terminal_id(TerminalID::UNKNOWN) { task_receiver_ = MakeReceiver(); } @@ -799,6 +800,29 @@ void ScreenInteractive::HandleTask(Component component, Task& task) { arg.mouse().y -= cursor_y_; } + if (arg.is_terminal_id()) + { + m_terminal_id = + arg.terminal_id(); + + for (auto itr = m_terminal_id_update_callbacks.begin(), + end_itr = m_terminal_id_update_callbacks.end(); + (itr != end_itr); + ++itr) + { + if (*itr) + { + (*itr)(m_terminal_id); + } + else + { + // The callback function is invalid and will be removed + m_terminal_id_update_callbacks.erase( + itr); + } + } + } + arg.screen_ = this; bool handled = component->OnEvent(arg); @@ -1084,4 +1108,16 @@ bool ScreenInteractive::SelectionData::operator!=( return !(*this == other); } +TerminalID ScreenInteractive::TerminalId() const +{ + return m_terminal_id; +} + +void ScreenInteractive::OnTerminalIDUpdate( + TerminalIDUpdateCallback const& callback) +{ + m_terminal_id_update_callbacks.push_back( + callback); +} + } // namespace ftxui. diff --git a/src/ftxui/component/terminal_id.cpp b/src/ftxui/component/terminal_id.cpp new file mode 100644 index 000000000..b925f104f --- /dev/null +++ b/src/ftxui/component/terminal_id.cpp @@ -0,0 +1,51 @@ +// Copyright 2025 Arthur Sonzogni. All rights reserved. +// Use of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +#include "ftxui/component/terminal_id.hpp" + +namespace ftxui +{ + +std::ostream& operator<<( + std::ostream& os, + TerminalID terminal_id) +{ + switch(terminal_id) + { + case TerminalID::UNKNOWN: + { + os << "UNKNOWN"; + break; + } + case TerminalID::XTERM: + { + os << "XTERM"; + break; + } + case TerminalID::KONSOLE: + { + os << "KONSOLE"; + break; + } + case TerminalID::URXVT: + { + os << "URXVT"; + break; + } + case TerminalID::VTE: + { + os << "VTE"; + break; + } + case TerminalID::LINUXVC: + { + os << "LINUXVC"; + break; + } + } + + return os; +} + +} // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser.cpp b/src/ftxui/component/terminal_input_parser.cpp index 2100d9edd..387e2fe52 100644 --- a/src/ftxui/component/terminal_input_parser.cpp +++ b/src/ftxui/component/terminal_input_parser.cpp @@ -161,6 +161,11 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) { out_->Send(Event::CursorShape(std::move(pending_), output.cursor_shape)); pending_.clear(); return; + + case TERMINAL_ID: + out_->Send(Event::TerminalID(std::move(pending_), output.terminal_id)); + pending_.clear(); + return; } // NOT_REACHED(). } @@ -374,6 +379,8 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() { return ParseMouse(altered, false, std::move(arguments)); case 'R': return ParseCursorPosition(std::move(arguments)); + case 'c': + return ParseTerminalID(std::move(arguments)); default: return SPECIAL; } @@ -461,4 +468,53 @@ TerminalInputParser::Output TerminalInputParser::ParseCursorPosition( return output; } +TerminalInputParser::Output TerminalInputParser::ParseTerminalID( + std::vector arguments) +{ + Output output(TERMINAL_ID); + + if (!arguments.empty()) + { + switch(arguments[0]) + { + case 1: + { + output.terminal_id = + TerminalID::URXVT; + + break; + } + case 6: + { + output.terminal_id = + TerminalID::LINUXVC; + + break; + } + case 62: + { + output.terminal_id = + TerminalID::KONSOLE; + + break; + } + default: + { + output.terminal_id = + TerminalID::UNKNOWN; + + break; + } + } + } + else + { + output.terminal_id = + TerminalID::UNKNOWN; + } + + return output; +} + + } // namespace ftxui diff --git a/src/ftxui/component/terminal_input_parser.hpp b/src/ftxui/component/terminal_input_parser.hpp index 524994a2f..e9dbcbabc 100644 --- a/src/ftxui/component/terminal_input_parser.hpp +++ b/src/ftxui/component/terminal_input_parser.hpp @@ -7,9 +7,10 @@ #include // for string #include // for vector -#include "ftxui/component/mouse.hpp" // for Mouse -#include "ftxui/component/receiver.hpp" // for Sender -#include "ftxui/component/task.hpp" // for Task +#include "ftxui/component/mouse.hpp" // for Mouse +#include "ftxui/component/terminal_id.hpp" // for TerminalId +#include "ftxui/component/receiver.hpp" // for Sender +#include "ftxui/component/task.hpp" // for Task namespace ftxui { struct Event; @@ -33,6 +34,7 @@ class TerminalInputParser { CURSOR_POSITION, CURSOR_SHAPE, SPECIAL, + TERMINAL_ID }; struct CursorPosition { @@ -46,6 +48,7 @@ class TerminalInputParser { Mouse mouse; CursorPosition cursor{}; int cursor_shape; + TerminalID terminal_id; }; Output(Type t) // NOLINT @@ -61,6 +64,7 @@ class TerminalInputParser { Output ParseOSC(); Output ParseMouse(bool altered, bool pressed, std::vector arguments); Output ParseCursorPosition(std::vector arguments); + Output ParseTerminalID(std::vector arguments); Sender out_; int position_ = -1; diff --git a/src/ftxui/component/terminal_input_parser_test.cpp b/src/ftxui/component/terminal_input_parser_test.cpp index fec73d9de..5ae3fd064 100644 --- a/src/ftxui/component/terminal_input_parser_test.cpp +++ b/src/ftxui/component/terminal_input_parser_test.cpp @@ -510,5 +510,28 @@ TEST(Event, DeviceControlString) { EXPECT_FALSE(event_receiver->Receive(&received)); } +TEST(Event, TerminalID) { + + // Test terminal id for KDE konsole + auto event_receiver = MakeReceiver(); + { + auto parser = TerminalInputParser(event_receiver->MakeSender()); + parser.Add('\x1B'); + parser.Add('['); + parser.Add('?'); + parser.Add('6'); + parser.Add('2'); + parser.Add(';'); + parser.Add('2'); + parser.Add('c'); + } + + Task received; + EXPECT_TRUE(event_receiver->Receive(&received)); + EXPECT_TRUE(std::get(received).is_terminal_id()); + EXPECT_EQ(TerminalID::KONSOLE, std::get(received).terminal_id()); + EXPECT_FALSE(event_receiver->Receive(&received)); +} + } // namespace ftxui // NOLINTEND