2 Commits

Author SHA1 Message Date
ArthurSonzogni
ce9964131d Coding style + fix tests 2025-06-24 14:53:15 +02:00
Jørn Gustav Larsen
2715503516 First version of supporting extraction of the terminal id. 2025-06-24 11:27:35 +02:00
14 changed files with 233 additions and 12 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -5,6 +5,7 @@
#define FTXUI_COMPONENT_EVENT_HPP
#include <ftxui/component/mouse.hpp> // for Mouse
#include <ftxui/component/terminal_id.hpp> // for TerminalID
#include <string> // 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_;

View File

@@ -10,6 +10,7 @@
#include <memory> // for shared_ptr
#include <string> // for string
#include <thread> // for thread
#include <list>
#include "ftxui/component/animation.hpp" // for TimePoint
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
@@ -24,6 +25,8 @@ class Loop;
struct Event;
using Component = std::shared_ptr<ComponentBase>;
using TerminalIDUpdateCallback = std::function<void (const TerminalID &)>;
class ScreenInteractivePrivate;
/// @brief ScreenInteractive is a `Screen` that can handle events, run a main
@@ -72,6 +75,10 @@ class ScreenInteractive : public Screen {
void ForceHandleCtrlC(bool force);
void ForceHandleCtrlZ(bool force);
TerminalID TerminalId() const;
void OnTerminalIDUpdate(const TerminalIDUpdateCallback& callback);
// Selection API.
std::string GetSelection();
void SelectionChange(std::function<void()> callback);
@@ -156,6 +163,11 @@ class ScreenInteractive : public Screen {
std::unique_ptr<Selection> selection_;
std::function<void()> selection_on_change_;
TerminalID m_terminal_id = TerminalID::Unknown;
using TerminalIDUpdateCallbackContainer = std::list<TerminalIDUpdateCallback>;
TerminalIDUpdateCallbackContainer terminal_id_callback_;
friend class Loop;
public:

View File

@@ -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 <ostream>
#include <string>
namespace ftxui {
/// @brief The TerminalID enum class represents different types of terminal
/// emulators that FTXUI can detect. It is used to identify the terminal
/// emulator in use, which can affect how FTXUI renders its output and handles
/// input events.
/// @ingroup component
enum class TerminalID {
Unknown,
// --
Konsole,
LinuxVC,
Urxvt,
Vte,
Xterm,
};
std::ostream& operator<<(
std::ostream& os,
TerminalID terminal_id);
} // namespace ftxui
#endif /* end of include guard: FTXUI_COMPONENT_TERMINAL_ID_HPP */

View File

@@ -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, const char*> event_to_string = {

View File

@@ -1,6 +1,7 @@
// 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 <iostream>
#include "ftxui/component/loop.hpp"
#include <utility> // for move

View File

@@ -321,6 +321,8 @@ std::string DeviceStatusReport(DSRMode ps) {
return CSI + std::to_string(int(ps)) + "n";
}
const std::string TerminalIdReport = "\x1b[0c";
class CapturedMouseImpl : public CapturedMouseInterface {
public:
explicit CapturedMouseImpl(std::function<void(void)> callback)
@@ -724,6 +726,9 @@ void ScreenInteractive::Install() {
enable({DECMode::kMouseSgrExtMode});
}
// Report the Terminal ID.
std::cout << TerminalIdReport;
// After installing the new configuration, flush it to the terminal to
// ensure it is fully applied:
Flush();
@@ -794,6 +799,17 @@ void ScreenInteractive::HandleTask(Component component, Task& task) {
return;
}
if (arg.is_terminal_id()) {
m_terminal_id = arg.terminal_id();
for(auto & callback : terminal_id_callback_) {
if (callback) {
callback(m_terminal_id);
}
}
return;
}
if (arg.is_mouse()) {
arg.mouse().x -= cursor_x_;
arg.mouse().y -= cursor_y_;
@@ -1084,4 +1100,13 @@ bool ScreenInteractive::SelectionData::operator!=(
return !(*this == other);
}
TerminalID ScreenInteractive::TerminalId() const {
return m_terminal_id;
}
void ScreenInteractive::OnTerminalIDUpdate(
const TerminalIDUpdateCallback& callback) {
terminal_id_callback_.push_back(callback);
}
} // namespace ftxui.

View File

@@ -207,6 +207,7 @@ TEST(ScreenInteractive, FixedSizeInitialFrame) {
"\x1B[?1003h" // Enable mouse motion tracking.
"\x1B[?1015h" // Enable mouse wheel tracking.
"\x1B[?1006h" // Enable SGR mouse tracking.
"\x1B[0c" // Query terminal ID.
"\0" // Flush stdout.
// Reset the screen.

View File

@@ -0,0 +1,41 @@
// 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::Urxvt: {
os << "Urxvt";
break;
}
case TerminalID::LinuxVC: {
os << "LinuxVC";
break;
}
case TerminalID::Konsole: {
os << "Konsole";
break;
}
case TerminalID::Vte: {
os << "Vte";
break;
}
case TerminalID::Xterm: {
os << "Xterm";
break;
}
}
return os;
}
} // namespace ftxui

View File

@@ -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,22 @@ TerminalInputParser::Output TerminalInputParser::ParseCursorPosition(
return output;
}
TerminalInputParser::Output TerminalInputParser::ParseTerminalID(
std::vector<int> arguments) {
if (arguments.empty()) {
return TerminalID::Unknown;
}
switch (arguments[0]) {
case 1:
return TerminalID::Xterm;
case 6:
return TerminalID::LinuxVC;
case 62:
return TerminalID::Konsole;
default:
return TerminalID::Unknown;
}
}
} // namespace ftxui

View File

@@ -8,6 +8,7 @@
#include <vector> // for vector
#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
@@ -33,6 +34,7 @@ class TerminalInputParser {
CURSOR_POSITION,
CURSOR_SHAPE,
SPECIAL,
TERMINAL_ID
};
struct CursorPosition {
@@ -46,10 +48,14 @@ class TerminalInputParser {
Mouse mouse;
CursorPosition cursor{};
int cursor_shape;
TerminalID terminal_id;
};
Output(Type t) // NOLINT
: type(t) {}
Output(TerminalID terminal_id) // NOLINT
: type(TERMINAL_ID), terminal_id(terminal_id) {}
};
void Send(Output output);
@@ -61,6 +67,7 @@ class TerminalInputParser {
Output ParseOSC();
Output ParseMouse(bool altered, bool pressed, std::vector<int> arguments);
Output ParseCursorPosition(std::vector<int> arguments);
Output ParseTerminalID(std::vector<int> arguments);
Sender<Task> out_;
int position_ = -1;

View File

@@ -510,5 +510,27 @@ TEST(Event, DeviceControlString) {
EXPECT_FALSE(event_receiver->Receive(&received));
}
TEST(Event, TerminalID) {
// Test terminal id for KDE konsole
auto event_receiver = MakeReceiver<Task>();
{
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<Event>(received).is_terminal_id());
EXPECT_EQ(TerminalID::Konsole, std::get<Event>(received).terminal_id());
EXPECT_FALSE(event_receiver->Receive(&received));
}
} // namespace ftxui
// NOLINTEND