FTXUI/src/ftxui/component/screen_interactive.cpp

755 lines
21 KiB
C++
Raw Normal View History

#include <algorithm> // for copy, max, min
2022-03-31 08:17:43 +08:00
#include <array> // for array
#include <chrono> // for operator-, milliseconds, duration, operator>=, time_point, common_type<>::type
#include <csignal> // for signal, raise, SIGTSTP, SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGWINCH
2022-03-31 08:17:43 +08:00
#include <cstdio> // for fileno, size_t, stdin
2022-06-12 23:08:22 +08:00
#include <ftxui/component/task.hpp> // for Task, Closure, AnimationTask
#include <ftxui/screen/screen.hpp> // for Pixel, Screen::Cursor, Screen
#include <functional> // for function
#include <initializer_list> // for initializer_list
2021-05-10 02:32:27 +08:00
#include <iostream> // for cout, ostream, basic_ostream, operator<<, endl, flush
#include <stack> // for stack
2022-03-14 01:51:46 +08:00
#include <thread> // for thread, sleep_for
2022-09-06 02:56:41 +08:00
#include <tuple>
#include <type_traits> // for decay_t
2022-03-31 08:17:43 +08:00
#include <utility> // for move, swap
#include <variant> // for visit
#include <vector> // for vector
2021-05-10 02:32:27 +08:00
2022-03-14 01:51:46 +08:00
#include "ftxui/component/animation.hpp" // for TimePoint, Clock, Duration, Params, RequestAnimationFrame
2021-05-10 02:32:27 +08:00
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse, CapturedMouseInterface
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/loop.hpp" // for Loop
2022-06-12 23:08:22 +08:00
#include "ftxui/component/receiver.hpp" // for Sender, ReceiverImpl, MakeReceiver, SenderImpl, Receiver
2021-05-10 02:32:27 +08:00
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/component/terminal_input_parser.hpp" // for TerminalInputParser
2021-05-02 02:40:35 +08:00
#include "ftxui/dom/node.hpp" // for Node, Render
#include "ftxui/dom/requirement.hpp" // for Requirement
#include "ftxui/screen/terminal.hpp" // for Size, Dimensions
#if defined(_WIN32)
#define DEFINE_CONSOLEV2_PROPERTIES
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#ifndef UNICODE
#error Must be compiled in UNICODE mode
#endif
#else
2021-05-10 02:32:27 +08:00
#include <sys/select.h> // for select, FD_ISSET, FD_SET, FD_ZERO, fd_set
#include <termios.h> // for tcsetattr, termios, tcgetattr, TCSANOW, cc_t, ECHO, ICANON, VMIN, VTIME
#include <unistd.h> // for STDIN_FILENO, read
#endif
// Quick exit is missing in standard CLang headers
#if defined(__clang__) && defined(__APPLE__)
#define quick_exit(a) exit(a)
#endif
namespace ftxui {
2022-03-14 01:51:46 +08:00
namespace animation {
void RequestAnimationFrame() {
auto* screen = ScreenInteractive::Active();
2022-03-31 08:17:43 +08:00
if (screen) {
2022-03-14 01:51:46 +08:00
screen->RequestAnimationFrame();
2022-03-31 08:17:43 +08:00
}
2022-03-14 01:51:46 +08:00
}
} // namespace animation
2020-08-09 20:53:56 +08:00
namespace {
2020-03-25 08:15:46 +08:00
2022-03-31 08:17:43 +08:00
ScreenInteractive* g_active_screen = nullptr; // NOLINT
2021-03-22 05:54:39 +08:00
void Flush() {
// Emscripten doesn't implement flush. We interpret zero as flush.
std::cout << '\0' << std::flush;
2021-03-22 05:54:39 +08:00
}
constexpr int timeout_milliseconds = 20;
constexpr int timeout_microseconds = timeout_milliseconds * 1000;
#if defined(_WIN32)
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto console = GetStdHandle(STD_INPUT_HANDLE);
auto parser = TerminalInputParser(out->Clone());
while (!*quit) {
// Throttle ReadConsoleInput by waiting 250ms, this wait function will
// return if there is input in the console.
auto wait_result = WaitForSingleObject(console, timeout_milliseconds);
if (wait_result == WAIT_TIMEOUT) {
parser.Timeout(timeout_milliseconds);
continue;
}
DWORD number_of_events = 0;
if (!GetNumberOfConsoleInputEvents(console, &number_of_events))
continue;
if (number_of_events <= 0)
continue;
std::vector<INPUT_RECORD> records{number_of_events};
DWORD number_of_events_read = 0;
2021-05-02 02:40:35 +08:00
ReadConsoleInput(console, records.data(), (DWORD)records.size(),
&number_of_events_read);
records.resize(number_of_events_read);
for (const auto& r : records) {
switch (r.EventType) {
case KEY_EVENT: {
auto key_event = r.Event.KeyEvent;
// ignore UP key events
if (key_event.bKeyDown == FALSE)
continue;
parser.Add((char)key_event.uChar.UnicodeChar);
} break;
case WINDOW_BUFFER_SIZE_EVENT:
out->Send(Event::Special({0}));
break;
case MENU_EVENT:
case FOCUS_EVENT:
case MOUSE_EVENT:
// TODO(mauve): Implement later.
break;
}
}
}
}
2021-03-22 05:54:39 +08:00
#elif defined(__EMSCRIPTEN__)
#include <emscripten.h>
// Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
2021-03-22 05:54:39 +08:00
(void)timeout_microseconds;
auto parser = TerminalInputParser(std::move(out));
2021-03-22 05:54:39 +08:00
char c;
while (!*quit) {
2021-05-10 02:32:27 +08:00
while (read(STDIN_FILENO, &c, 1), c)
2021-03-22 05:54:39 +08:00
parser.Add(c);
emscripten_sleep(1);
parser.Timeout(1);
}
}
extern "C" {
EMSCRIPTEN_KEEPALIVE
void ftxui_on_resize(int columns, int rows) {
Terminal::SetFallbackSize({
columns,
rows,
});
std::raise(SIGWINCH);
}
}
2021-03-22 05:54:39 +08:00
#else
2021-05-02 02:40:35 +08:00
#include <sys/time.h> // for timeval
2021-03-21 05:45:21 +08:00
2020-05-02 08:02:04 +08:00
int CheckStdinReady(int usec_timeout) {
timeval tv = {0, usec_timeout};
fd_set fds;
2022-03-31 08:17:43 +08:00
FD_ZERO(&fds); // NOLINT
FD_SET(STDIN_FILENO, &fds); // NOLINT
select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); // NOLINT
return FD_ISSET(STDIN_FILENO, &fds); // NOLINT
2020-05-02 08:02:04 +08:00
}
2020-03-25 08:15:46 +08:00
// Read char from the terminal.
void EventListener(std::atomic<bool>* quit, Sender<Task> out) {
auto parser = TerminalInputParser(std::move(out));
while (!*quit) {
if (!CheckStdinReady(timeout_microseconds)) {
parser.Timeout(timeout_milliseconds);
continue;
}
2022-03-31 08:17:43 +08:00
const size_t buffer_size = 100;
std::array<char, buffer_size> buffer; // NOLINT;
int l = read(fileno(stdin), buffer.data(), buffer_size); // NOLINT
for (int i = 0; i < l; ++i) {
parser.Add(buffer[i]); // NOLINT
}
}
2020-03-25 08:15:46 +08:00
}
#endif
2022-03-31 08:17:43 +08:00
const std::string CSI = "\x1b["; // NOLINT
2021-04-25 21:22:38 +08:00
// DEC: Digital Equipment Corporation
enum class DECMode {
kLineWrap = 7,
kMouseX10 = 9,
kCursor = 25,
kMouseVt200 = 1000,
kMouseAnyEvent = 1003,
kMouseUtf8 = 1005,
kMouseSgrExtMode = 1006,
kMouseUrxvtMode = 1015,
kMouseSgrPixelsMode = 1016,
kAlternateScreen = 1049,
};
// Device Status Report (DSR) {
enum class DSRMode {
kCursor = 6,
};
2022-03-31 08:17:43 +08:00
std::string Serialize(const std::vector<DECMode>& parameters) {
2021-04-25 21:22:38 +08:00
bool first = true;
std::string out;
for (DECMode parameter : parameters) {
2022-03-31 08:17:43 +08:00
if (!first) {
2021-04-25 21:22:38 +08:00
out += ";";
2022-03-31 08:17:43 +08:00
}
2021-04-25 21:22:38 +08:00
out += std::to_string(int(parameter));
first = false;
}
return out;
}
2021-04-25 21:22:38 +08:00
// DEC Private Mode Set (DECSET)
2022-03-31 08:17:43 +08:00
std::string Set(const std::vector<DECMode>& parameters) {
2021-04-25 21:22:38 +08:00
return CSI + "?" + Serialize(parameters) + "h";
}
2021-04-25 21:22:38 +08:00
// DEC Private Mode Reset (DECRST)
2022-03-31 08:17:43 +08:00
std::string Reset(const std::vector<DECMode>& parameters) {
2021-04-25 21:22:38 +08:00
return CSI + "?" + Serialize(parameters) + "l";
}
2021-04-25 21:22:38 +08:00
// Device Status Report (DSR)
2022-03-31 08:17:43 +08:00
std::string DeviceStatusReport(DSRMode ps) {
2021-04-25 21:22:38 +08:00
return CSI + std::to_string(int(ps)) + "n";
}
2020-03-23 16:23:57 +08:00
using SignalHandler = void(int);
2022-03-31 08:17:43 +08:00
std::stack<Closure> on_exit_functions; // NOLINT
void OnExit(int signal) {
(void)signal;
while (!on_exit_functions.empty()) {
on_exit_functions.top()();
on_exit_functions.pop();
}
}
2022-03-31 08:17:43 +08:00
const auto install_signal_handler = [](int sig, SignalHandler handler) {
2020-03-23 16:23:57 +08:00
auto old_signal_handler = std::signal(sig, handler);
2022-09-06 02:56:41 +08:00
on_exit_functions.push(
[=] { std::ignore = std::signal(sig, old_signal_handler); });
2020-03-23 16:23:57 +08:00
};
2022-03-31 08:17:43 +08:00
Closure g_on_resize = [] {}; // NOLINT
2019-07-01 05:59:27 +08:00
void OnResize(int /* signal */) {
2022-03-31 08:17:43 +08:00
g_on_resize();
}
void OnSigStop(int /*signal*/) {
ScreenInteractive::Private::SigStop(*g_active_screen);
}
class CapturedMouseImpl : public CapturedMouseInterface {
public:
2022-03-31 08:17:43 +08:00
explicit CapturedMouseImpl(std::function<void(void)> callback)
: callback_(std::move(callback)) {}
~CapturedMouseImpl() override { callback_(); }
2022-03-31 08:17:43 +08:00
CapturedMouseImpl(const CapturedMouseImpl&) = delete;
CapturedMouseImpl(CapturedMouseImpl&&) = delete;
CapturedMouseImpl& operator=(const CapturedMouseImpl&) = delete;
CapturedMouseImpl& operator=(CapturedMouseImpl&&) = delete;
private:
std::function<void(void)> callback_;
};
2022-03-14 01:51:46 +08:00
void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
2022-03-31 08:17:43 +08:00
// Animation at around 60fps.
const auto time_delta = std::chrono::milliseconds(15);
2022-03-14 01:51:46 +08:00
while (!*quit) {
out->Send(AnimationTask());
2022-03-31 08:17:43 +08:00
std::this_thread::sleep_for(time_delta);
2022-03-14 01:51:46 +08:00
}
}
2020-08-09 20:53:56 +08:00
} // namespace
ScreenInteractive::ScreenInteractive(int dimx,
int dimy,
Dimension dimension,
bool use_alternative_screen)
: Screen(dimx, dimy),
dimension_(dimension),
use_alternative_screen_(use_alternative_screen) {
task_receiver_ = MakeReceiver<Task>();
}
// static
2019-01-27 04:52:55 +08:00
ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) {
2022-09-06 02:56:41 +08:00
return {
dimx,
dimy,
Dimension::Fixed,
false,
};
}
// static
ScreenInteractive ScreenInteractive::Fullscreen() {
2022-09-06 02:56:41 +08:00
return {
0,
0,
Dimension::Fullscreen,
true,
};
}
// static
ScreenInteractive ScreenInteractive::TerminalOutput() {
2022-09-06 02:56:41 +08:00
return {
0,
0,
Dimension::TerminalOutput,
false,
};
}
2019-01-19 07:20:29 +08:00
// static
ScreenInteractive ScreenInteractive::FitComponent() {
2022-09-06 02:56:41 +08:00
return {
0,
0,
Dimension::FitComponent,
false,
};
2019-01-19 07:20:29 +08:00
}
void ScreenInteractive::Post(Task task) {
// Task/Events sent toward inactive screen or screen waiting to become
// inactive are dropped.
if (!task_sender_) {
return;
}
task_sender_->Send(std::move(task));
}
void ScreenInteractive::PostEvent(Event event) {
Post(event);
2019-01-27 09:33:06 +08:00
}
2022-03-14 01:51:46 +08:00
void ScreenInteractive::RequestAnimationFrame() {
2022-03-31 08:17:43 +08:00
if (animation_requested_) {
2022-03-14 01:51:46 +08:00
return;
2022-03-31 08:17:43 +08:00
}
2022-03-14 01:51:46 +08:00
animation_requested_ = true;
auto now = animation::Clock::now();
2022-03-31 08:17:43 +08:00
const auto time_histeresis = std::chrono::milliseconds(33);
if (now - previous_animation_time_ >= time_histeresis) {
previous_animation_time_ = now;
2022-03-31 08:17:43 +08:00
}
2022-03-14 01:51:46 +08:00
}
CapturedMouse ScreenInteractive::CaptureMouse() {
2022-03-31 08:17:43 +08:00
if (mouse_captured) {
return nullptr;
2022-03-31 08:17:43 +08:00
}
mouse_captured = true;
return std::make_unique<CapturedMouseImpl>(
[this] { mouse_captured = false; });
}
2022-03-31 08:17:43 +08:00
void ScreenInteractive::Loop(Component component) { // NOLINT
class Loop loop(this, component);
loop.Run();
}
bool ScreenInteractive::HasQuitted() {
return task_receiver_->HasQuitted();
}
void ScreenInteractive::PreMain() {
2021-09-01 23:47:48 +08:00
// Suspend previously active screen:
if (g_active_screen) {
std::swap(suspended_screen_, g_active_screen);
std::cout << suspended_screen_->reset_cursor_position
<< suspended_screen_->ResetPosition(/*clear=*/true);
suspended_screen_->dimx_ = 0;
suspended_screen_->dimy_ = 0;
suspended_screen_->Uninstall();
}
// This screen is now active:
g_active_screen = this;
g_active_screen->Install();
previous_animation_time_ = animation::Clock::now();
}
void ScreenInteractive::PostMain() {
2021-09-01 23:47:48 +08:00
g_active_screen->Uninstall();
g_active_screen = nullptr;
// Put cursor position at the end of the drawing.
std::cout << reset_cursor_position;
// Restore suspended screen.
if (suspended_screen_) {
std::cout << ResetPosition(/*clear=*/true);
dimx_ = 0;
dimy_ = 0;
std::swap(g_active_screen, suspended_screen_);
g_active_screen->Install();
} else {
// On final exit, keep the current drawing and reset cursor position one
// line after it.
std::cout << std::endl;
}
}
/// @brief Decorate a function. It executes the same way, but with the currently
/// active screen terminal hooks temporarilly uninstalled during its execution.
/// @param fn The function to decorate.
2022-03-31 08:17:43 +08:00
Closure ScreenInteractive::WithRestoredIO(Closure fn) { // NOLINT
return [this, fn] {
Uninstall();
fn();
Install();
};
}
2022-03-14 01:51:46 +08:00
// static
ScreenInteractive* ScreenInteractive::Active() {
return g_active_screen;
}
2021-09-01 23:47:48 +08:00
void ScreenInteractive::Install() {
// After uninstalling the new configuration, flush it to the terminal to
// ensure it is fully applied:
on_exit_functions.push([] { Flush(); });
on_exit_functions.push([this] { ExitLoopClosure()(); });
// Install signal handlers to restore the terminal state on exit. The default
// signal handlers are restored on exit.
2022-03-31 08:17:43 +08:00
for (int signal : {SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE}) {
install_signal_handler(signal, OnExit);
2022-03-31 08:17:43 +08:00
}
2022-03-31 08:17:43 +08:00
// Save the old terminal configuration and restore it on exit.
#if defined(_WIN32)
// Enable VT processing on stdout and stdin
auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
DWORD out_mode = 0;
DWORD in_mode = 0;
GetConsoleMode(stdout_handle, &out_mode);
GetConsoleMode(stdin_handle, &in_mode);
on_exit_functions.push([=] { SetConsoleMode(stdout_handle, out_mode); });
on_exit_functions.push([=] { SetConsoleMode(stdin_handle, in_mode); });
2020-07-17 02:58:58 +08:00
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
const int enable_virtual_terminal_processing = 0x0004;
const int disable_newline_auto_return = 0x0008;
out_mode |= enable_virtual_terminal_processing;
out_mode |= disable_newline_auto_return;
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
const int enable_line_input = 0x0002;
const int enable_echo_input = 0x0004;
const int enable_virtual_terminal_input = 0x0200;
const int enable_window_input = 0x0008;
in_mode &= ~enable_echo_input;
in_mode &= ~enable_line_input;
in_mode |= enable_virtual_terminal_input;
in_mode |= enable_window_input;
SetConsoleMode(stdin_handle, in_mode);
SetConsoleMode(stdout_handle, out_mode);
#else
2022-03-31 08:17:43 +08:00
struct termios terminal; // NOLINT
tcgetattr(STDIN_FILENO, &terminal);
on_exit_functions.push([=] { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); });
2022-03-31 08:17:43 +08:00
terminal.c_lflag &= ~ICANON; // NOLINT Non canonique terminal.
terminal.c_lflag &= ~ECHO; // NOLINT Do not print after a key press.
2020-05-02 08:02:04 +08:00
terminal.c_cc[VMIN] = 0;
terminal.c_cc[VTIME] = 0;
2020-05-21 02:51:07 +08:00
// auto oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
// fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
// on_exit_functions.push([=] { fcntl(STDIN_FILENO, F_GETFL, oldf); });
2020-05-02 08:02:04 +08:00
tcsetattr(STDIN_FILENO, TCSANOW, &terminal);
// Handle resize.
2022-03-31 08:17:43 +08:00
g_on_resize = [&] { task_sender_->Send(Event::Special({0})); };
install_signal_handler(SIGWINCH, OnResize);
// Handle SIGTSTP/SIGCONT.
install_signal_handler(SIGTSTP, OnSigStop);
#endif
2022-03-31 08:17:43 +08:00
auto enable = [&](const std::vector<DECMode>& parameters) {
2021-04-25 21:22:38 +08:00
std::cout << Set(parameters);
on_exit_functions.push([=] { std::cout << Reset(parameters); });
};
2022-03-31 08:17:43 +08:00
auto disable = [&](const std::vector<DECMode>& parameters) {
2021-04-25 21:22:38 +08:00
std::cout << Reset(parameters);
on_exit_functions.push([=] { std::cout << Set(parameters); });
};
if (use_alternative_screen_) {
2021-04-25 21:22:38 +08:00
enable({
DECMode::kAlternateScreen,
});
}
on_exit_functions.push([=] {
std::cout << "\033[?25h"; // Enable cursor.
std::cout << "\033[?1 q"; // Cursor block blinking.
});
2021-04-25 21:22:38 +08:00
disable({
//DECMode::kCursor,
2021-04-25 21:22:38 +08:00
DECMode::kLineWrap,
});
enable({
2021-05-02 02:40:35 +08:00
// DECMode::kMouseVt200,
2021-04-25 21:22:38 +08:00
DECMode::kMouseAnyEvent,
DECMode::kMouseUtf8,
DECMode::kMouseSgrExtMode,
});
// After installing the new configuration, flush it to the terminal to ensure
// it is fully applied:
Flush();
2021-09-01 23:47:48 +08:00
quit_ = false;
task_sender_ = task_receiver_->MakeSender();
2021-09-01 23:47:48 +08:00
event_listener_ =
std::thread(&EventListener, &quit_, task_receiver_->MakeSender());
2022-03-14 01:51:46 +08:00
animation_listener_ =
std::thread(&AnimationListener, &quit_, task_receiver_->MakeSender());
2021-09-01 23:47:48 +08:00
}
void ScreenInteractive::Uninstall() {
ExitLoopClosure()();
event_listener_.join();
2022-03-14 01:51:46 +08:00
animation_listener_.join();
2019-01-27 09:33:06 +08:00
2021-09-01 23:47:48 +08:00
OnExit(0);
}
2022-03-31 08:17:43 +08:00
// NOLINTNEXTLINE
void ScreenInteractive::RunOnceBlocking(Component component) {
Task task;
if (task_receiver_->Receive(&task)) {
HandleTask(component, task);
}
RunOnce(component);
}
void ScreenInteractive::RunOnce(Component component) {
Task task;
while (task_receiver_->ReceiveNonBlocking(&task)) {
HandleTask(component, task);
}
Draw(component);
}
void ScreenInteractive::HandleTask(Component component, Task& task) {
// clang-format off
std::visit([&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
// Handle Event.
if constexpr (std::is_same_v<T, Event>) {
if (arg.is_cursor_reporting()) {
cursor_x_ = arg.cursor_x();
cursor_y_ = arg.cursor_y();
return;
}
if (arg.is_mouse()) {
arg.mouse().x -= cursor_x_;
arg.mouse().y -= cursor_y_;
}
arg.screen_ = this;
component->OnEvent(arg);
frame_valid_ = false;
return;
}
// Handle callback
if constexpr (std::is_same_v<T, Closure>) {
arg();
return;
}
// Handle Animation
if constexpr (std::is_same_v<T, AnimationTask>) {
if (!animation_requested_) {
return;
}
animation_requested_ = false;
animation::TimePoint now = animation::Clock::now();
animation::Duration delta = now - previous_animation_time_;
previous_animation_time_ = now;
animation::Params params(delta);
component->OnAnimation(params);
frame_valid_ = false;
return;
}
},
task);
// clang-format on
}
2022-03-31 08:17:43 +08:00
// NOLINTNEXTLINE
2021-05-10 02:32:27 +08:00
void ScreenInteractive::Draw(Component component) {
if (frame_valid_)
return;
2019-01-13 01:24:46 +08:00
auto document = component->Render();
int dimx = 0;
int dimy = 0;
switch (dimension_) {
case Dimension::Fixed:
2019-01-13 01:24:46 +08:00
dimx = dimx_;
dimy = dimy_;
break;
case Dimension::TerminalOutput:
document->ComputeRequirement();
dimx = Terminal::Size().dimx;
2020-06-01 22:13:29 +08:00
dimy = document->requirement().min_y;
break;
case Dimension::Fullscreen:
dimx = Terminal::Size().dimx;
dimy = Terminal::Size().dimy;
break;
2019-01-19 07:20:29 +08:00
case Dimension::FitComponent:
auto terminal = Terminal::Size();
2019-01-19 07:20:29 +08:00
document->ComputeRequirement();
2020-06-01 22:13:29 +08:00
dimx = std::min(document->requirement().min_x, terminal.dimx);
dimy = std::min(document->requirement().min_y, terminal.dimy);
2019-01-19 07:20:29 +08:00
break;
}
2021-05-17 06:44:37 +08:00
bool resized = (dimx != dimx_) || (dimy != dimy_);
std::cout << reset_cursor_position << ResetPosition(/*clear=*/resized);
// Resize the screen if needed.
2021-05-17 06:44:37 +08:00
if (resized) {
dimx_ = dimx;
dimy_ = dimy;
pixels_ = std::vector<std::vector<Pixel>>(dimy, std::vector<Pixel>(dimx));
cursor_.x = dimx_ - 1;
cursor_.y = dimy_ - 1;
}
Implement Fallback for microsoft's terminals. (#138) I finally got access to a computer using the Microsoft's Windows OS. That's the opportunity to find and mitigate all the problems encountered. This patch: 1. Introduce an option and a C++ definition to enable fallback for Microsoft's terminal emulators. This allows me to see/test the Microsoft output from Linux. This also allows Windows users to remove the fallback and target non Microsoft terminals on Windows if needed. 2. Microsoft's terminal suffer from a race condition bug when reporting the cursor position: https://github.com/microsoft/terminal/pull/7583. The mitigation is not to ask for the cursor position in fullscreen mode where it isn't really needed and request it less often. This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/136 3. Microsoft's terminal do not handle properly hidding the cursor. Instead the character under the cursor is hidden, which is a big problem. As a result, we don't enable setting the cursor to the best position for [input method editors](https://en.wikipedia.org/wiki/Input_method), It will be displayed at the bottom right corner. See: - https://github.com/microsoft/terminal/issues/1203 - https://github.com/microsoft/terminal/issues/3093 4. Microsoft's terminals do not provide a way to query if they support colors. As a fallback, assume true colors is supported. See issue: - https://github.com/microsoft/terminal/issues/1040 This mitigates: - https://github.com/ArthurSonzogni/FTXUI/issues/135 5. The "cmd" on Windows do not properly report its dimension. Powershell works correctly. As a fallback, use a 80x80 size instead of 0x0. 6. There are several dom elements and component displayed incorrectly, because the font used is missing several unicode glyph. Use alternatives or less detailled one as a fallback.
2021-07-04 23:38:31 +08:00
// Periodically request the terminal emulator the frame position relative to
// the screen. This is useful for converting mouse position reported in
// screen's coordinates to frame's coordinates.
#if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK)
// Microsoft's terminal suffers from a [bug]. When reporting the cursor
// position, several output sequences are mixed together into garbage.
// This causes FTXUI user to see some "1;1;R" sequences into the Input
// component. See [issue]. Solution is to request cursor position less
// often. [bug]: https://github.com/microsoft/terminal/pull/7583 [issue]:
// https://github.com/ArthurSonzogni/FTXUI/issues/136
static int i = -3;
++i;
2022-03-31 08:17:43 +08:00
if (!use_alternative_screen_ && (i % 150 == 0)) { // NOLINT
std::cout << DeviceStatusReport(DSRMode::kCursor);
2022-03-31 08:17:43 +08:00
}
Implement Fallback for microsoft's terminals. (#138) I finally got access to a computer using the Microsoft's Windows OS. That's the opportunity to find and mitigate all the problems encountered. This patch: 1. Introduce an option and a C++ definition to enable fallback for Microsoft's terminal emulators. This allows me to see/test the Microsoft output from Linux. This also allows Windows users to remove the fallback and target non Microsoft terminals on Windows if needed. 2. Microsoft's terminal suffer from a race condition bug when reporting the cursor position: https://github.com/microsoft/terminal/pull/7583. The mitigation is not to ask for the cursor position in fullscreen mode where it isn't really needed and request it less often. This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/136 3. Microsoft's terminal do not handle properly hidding the cursor. Instead the character under the cursor is hidden, which is a big problem. As a result, we don't enable setting the cursor to the best position for [input method editors](https://en.wikipedia.org/wiki/Input_method), It will be displayed at the bottom right corner. See: - https://github.com/microsoft/terminal/issues/1203 - https://github.com/microsoft/terminal/issues/3093 4. Microsoft's terminals do not provide a way to query if they support colors. As a fallback, assume true colors is supported. See issue: - https://github.com/microsoft/terminal/issues/1040 This mitigates: - https://github.com/ArthurSonzogni/FTXUI/issues/135 5. The "cmd" on Windows do not properly report its dimension. Powershell works correctly. As a fallback, use a 80x80 size instead of 0x0. 6. There are several dom elements and component displayed incorrectly, because the font used is missing several unicode glyph. Use alternatives or less detailled one as a fallback.
2021-07-04 23:38:31 +08:00
#else
static int i = -3;
2021-05-17 06:44:37 +08:00
++i;
2022-03-31 08:17:43 +08:00
if (!use_alternative_screen_ &&
(previous_frame_resized_ || i % 40 == 0)) { // NOLINT
Implement Fallback for microsoft's terminals. (#138) I finally got access to a computer using the Microsoft's Windows OS. That's the opportunity to find and mitigate all the problems encountered. This patch: 1. Introduce an option and a C++ definition to enable fallback for Microsoft's terminal emulators. This allows me to see/test the Microsoft output from Linux. This also allows Windows users to remove the fallback and target non Microsoft terminals on Windows if needed. 2. Microsoft's terminal suffer from a race condition bug when reporting the cursor position: https://github.com/microsoft/terminal/pull/7583. The mitigation is not to ask for the cursor position in fullscreen mode where it isn't really needed and request it less often. This fixes: https://github.com/ArthurSonzogni/FTXUI/issues/136 3. Microsoft's terminal do not handle properly hidding the cursor. Instead the character under the cursor is hidden, which is a big problem. As a result, we don't enable setting the cursor to the best position for [input method editors](https://en.wikipedia.org/wiki/Input_method), It will be displayed at the bottom right corner. See: - https://github.com/microsoft/terminal/issues/1203 - https://github.com/microsoft/terminal/issues/3093 4. Microsoft's terminals do not provide a way to query if they support colors. As a fallback, assume true colors is supported. See issue: - https://github.com/microsoft/terminal/issues/1040 This mitigates: - https://github.com/ArthurSonzogni/FTXUI/issues/135 5. The "cmd" on Windows do not properly report its dimension. Powershell works correctly. As a fallback, use a 80x80 size instead of 0x0. 6. There are several dom elements and component displayed incorrectly, because the font used is missing several unicode glyph. Use alternatives or less detailled one as a fallback.
2021-07-04 23:38:31 +08:00
std::cout << DeviceStatusReport(DSRMode::kCursor);
2022-03-31 08:17:43 +08:00
}
#endif
previous_frame_resized_ = resized;
2021-05-17 06:44:37 +08:00
Render(*this, document);
// Set cursor position for user using tools to insert CJK characters.
set_cursor_position = "";
reset_cursor_position = "";
{
int dx = dimx_ - 1 - cursor_.x;
int dy = dimy_ - 1 - cursor_.y;
if (dy != 0) {
set_cursor_position += "\x1B[" + std::to_string(dy) + "A";
reset_cursor_position += "\x1B[" + std::to_string(dy) + "B";
}
if (dx != 0) {
set_cursor_position += "\x1B[" + std::to_string(dx) + "D";
reset_cursor_position += "\x1B[" + std::to_string(dx) + "C";
}
if (cursor_.shape == Cursor::Hidden) {
set_cursor_position += "\033[?25l";
} else {
set_cursor_position += "\033[?25h";
set_cursor_position += "\033[" + std::to_string(int(cursor_.shape)) + " q";
}
}
std::cout << ToString() << set_cursor_position;
Flush();
Clear();
frame_valid_ = true;
}
Closure ScreenInteractive::ExitLoopClosure() {
return [this] { Exit(); };
}
void ScreenInteractive::Exit() {
Post([this] {
2020-03-25 08:15:46 +08:00
quit_ = true;
task_sender_.reset();
});
}
void ScreenInteractive::SigStop() {
#if defined(_WIN32)
// Windows do no support SIGTSTP.
#else
Post([&] {
Uninstall();
std::cout << reset_cursor_position;
reset_cursor_position = "";
std::cout << ResetPosition(/*clear=*/true);
dimx_ = 0;
dimy_ = 0;
Flush();
2022-09-06 02:56:41 +08:00
std::ignore = std::raise(SIGTSTP);
Install();
});
#endif
}
} // namespace ftxui.
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.