LibVT+Kernel: Add support for setting cursor styles

This commit introduces support for 3 new escape sequences:
1. Stop blinking cursor mode
2. `DECTCEM` mode (enable/disable cursor)
3. `DECSCUSR` (set cursor style)

`TerminalWidget` now supports the following cursor types: block,
underline and vertical bar. Each of these can blink or be steady.
`VirtualConsole` ignores these (just as we were doing before).
This commit is contained in:
Daniel Bertalan 2021-05-24 09:36:41 +02:00 committed by Andreas Kling
parent 7dfc804d7d
commit 875a2cbb71
Notes: sideshowbarker 2024-07-18 17:28:13 +09:00
6 changed files with 162 additions and 14 deletions

View file

@ -387,6 +387,11 @@ void VirtualConsole::emit(const u8* data, size_t size)
TTY::emit(data[i], true);
}
void VirtualConsole::set_cursor_style(VT::CursorStyle)
{
// Do nothing
}
String VirtualConsole::device_name() const
{
return String::formatted("tty{}", minor());

View file

@ -106,6 +106,7 @@ private:
virtual void terminal_did_resize(u16 columns, u16 rows) override;
virtual void terminal_history_changed() override;
virtual void emit(const u8*, size_t) override;
virtual void set_cursor_style(VT::CursorStyle) override;
// ^CharacterDevice
virtual const char* class_name() const override { return "VirtualConsole"; }

View file

@ -46,24 +46,64 @@ void Terminal::clear_including_history()
void Terminal::alter_mode(bool should_set, Parameters params, Intermediates intermediates)
{
auto steady_cursor_to_blinking = [](CursorStyle style) {
switch (style) {
case SteadyBar:
return BlinkingBar;
case SteadyBlock:
return BlinkingBlock;
case SteadyUnderline:
return BlinkingUnderline;
default:
return style;
}
};
auto blinking_cursor_to_steady = [](CursorStyle style) {
switch (style) {
case BlinkingBar:
return SteadyBar;
case BlinkingBlock:
return SteadyBlock;
case BlinkingUnderline:
return SteadyUnderline;
default:
return style;
}
};
if (intermediates.size() > 0 && intermediates[0] == '?') {
for (auto mode : params) {
switch (mode) {
case 3: {
// 80/132-column mode (DECCOLM)
unsigned new_columns = should_set ? 80 : 132;
unsigned new_columns = should_set ? 132 : 80;
dbgln_if(TERMINAL_DEBUG, "Setting {}-column mode", new_columns);
set_size(new_columns, rows());
clear();
break;
}
case 12:
if (should_set) {
// Start blinking cursor
m_cursor_style = steady_cursor_to_blinking(m_cursor_style);
} else {
// Stop blinking cursor
m_cursor_style = blinking_cursor_to_steady(m_cursor_style);
}
m_client.set_cursor_style(m_cursor_style);
break;
case 25:
// Hide cursor command, but doesn't need to be run (for now, because
// we don't do inverse control codes anyways)
if (should_set)
dbgln("Terminal: Hide Cursor escapecode received. Not needed: ignored.");
else
dbgln("Terminal: Show Cursor escapecode received. Not needed: ignored.");
if (should_set) {
// Show cursor
m_cursor_style = m_saved_cursor_style;
m_client.set_cursor_style(m_cursor_style);
} else {
// Hide cursor
m_saved_cursor_style = m_cursor_style;
m_cursor_style = None;
m_client.set_cursor_style(None);
}
break;
default:
dbgln("Terminal::alter_mode: Unimplemented private mode {} (should_set={})", mode, should_set);
@ -84,12 +124,12 @@ void Terminal::alter_mode(bool should_set, Parameters params, Intermediates inte
void Terminal::RM(Parameters params, Intermediates intermediates)
{
alter_mode(true, params, intermediates);
alter_mode(false, params, intermediates);
}
void Terminal::SM(Parameters params, Intermediates intermediates)
{
alter_mode(false, params, intermediates);
alter_mode(true, params, intermediates);
}
void Terminal::SGR(Parameters params)
@ -454,6 +494,35 @@ void Terminal::SD(Parameters params)
scroll_down();
}
void Terminal::DECSCUSR(Parameters params)
{
unsigned style = 1;
if (params.size() >= 1 && params[0] != 0)
style = params[0];
switch (style) {
case 1:
m_client.set_cursor_style(BlinkingBlock);
break;
case 2:
m_client.set_cursor_style(SteadyBlock);
break;
case 3:
m_client.set_cursor_style(BlinkingUnderline);
break;
case 4:
m_client.set_cursor_style(SteadyUnderline);
break;
case 5:
m_client.set_cursor_style(BlinkingBar);
break;
case 6:
m_client.set_cursor_style(SteadyBar);
break;
default:
dbgln("Unknown cursor style {}", style);
}
}
#ifndef KERNEL
void Terminal::IL(Parameters params)
{
@ -833,6 +902,12 @@ void Terminal::execute_csi_sequence(Parameters parameters, Intermediates interme
case 'n':
DSR(parameters);
break;
case 'q':
if (intermediates.size() >= 1 && intermediates[0] == ' ')
DECSCUSR(parameters);
else
unimplemented_csi_sequence(parameters, intermediates, last_byte);
break;
default:
unimplemented_csi_sequence(parameters, intermediates, last_byte);
}

View file

@ -26,6 +26,16 @@ class VirtualConsole;
namespace VT {
enum CursorStyle {
None,
BlinkingBlock,
SteadyBlock,
BlinkingUnderline,
SteadyUnderline,
BlinkingBar,
SteadyBar
};
class TerminalClient {
public:
virtual ~TerminalClient() { }
@ -36,6 +46,7 @@ public:
virtual void terminal_did_resize(u16 columns, u16 rows) = 0;
virtual void terminal_history_changed() = 0;
virtual void emit(const u8*, size_t) = 0;
virtual void set_cursor_style(CursorStyle) = 0;
};
class Terminal : public EscapeSequenceExecutor {
@ -238,6 +249,9 @@ protected:
// DSR - Device Status Reports
void DSR(Parameters);
// DECSCUSR - Set Cursor Style
void DECSCUSR(Parameters);
#ifndef KERNEL
// ICH - Insert Character
void ICH(Parameters);
@ -319,6 +333,9 @@ protected:
bool m_swallow_current { false };
bool m_stomp { false };
CursorStyle m_cursor_style { BlinkingBlock };
CursorStyle m_saved_cursor_style { BlinkingBlock };
Attribute m_current_attribute;
Attribute m_saved_attribute;

View file

@ -326,6 +326,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
for (size_t column = 0; column < line.length(); ++column) {
bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
&& (m_cursor_style == VT::CursorStyle::SteadyBlock || m_cursor_style == VT::CursorStyle::BlinkingBlock)
&& m_has_logical_focus
&& visual_row == row_with_cursor
&& column == m_terminal.cursor_column();
@ -392,6 +393,7 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
for (size_t column = 0; column < line.length(); ++column) {
auto attribute = line.attribute_at(column);
bool should_reverse_fill_for_cursor_or_selection = m_cursor_blink_state
&& (m_cursor_style == VT::CursorStyle::SteadyBlock || m_cursor_style == VT::CursorStyle::BlinkingBlock)
&& m_has_logical_focus
&& visual_row == row_with_cursor
&& column == m_terminal.cursor_column();
@ -417,11 +419,31 @@ void TerminalWidget::paint_event(GUI::PaintEvent& event)
}
// Draw cursor.
if (!m_has_logical_focus && row_with_cursor < m_terminal.rows()) {
if (m_cursor_blink_state && row_with_cursor < m_terminal.rows()) {
auto& cursor_line = m_terminal.line(first_row_from_history + row_with_cursor);
if (m_terminal.cursor_row() < (m_terminal.rows() - rows_from_history)) {
auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing);
painter.draw_rect(cell_rect, color_from_rgb(cursor_line.attribute_at(m_terminal.cursor_column()).effective_foreground_color()));
if (m_terminal.cursor_row() >= (m_terminal.rows() - rows_from_history))
return;
if (m_has_logical_focus && (m_cursor_style == VT::CursorStyle::BlinkingBlock || m_cursor_style == VT::CursorStyle::SteadyBlock))
return; // This has already been handled by inverting the cell colors
auto cursor_color = color_from_rgb(cursor_line.attribute_at(m_terminal.cursor_column()).effective_foreground_color());
auto cell_rect = glyph_rect(row_with_cursor, m_terminal.cursor_column()).inflated(0, m_line_spacing);
if (m_cursor_style == VT::CursorStyle::BlinkingUnderline || m_cursor_style == VT::CursorStyle::SteadyUnderline) {
auto x1 = cell_rect.bottom_left().x();
auto x2 = cell_rect.bottom_right().x();
auto y = cell_rect.bottom_left().y();
for (auto x = x1; x <= x2; ++x)
painter.set_pixel({ x, y }, cursor_color);
} else if (m_cursor_style == VT::CursorStyle::BlinkingBar || m_cursor_style == VT::CursorStyle::SteadyBar) {
auto x = cell_rect.bottom_left().x();
auto y1 = cell_rect.top_left().y();
auto y2 = cell_rect.bottom_left().y();
for (auto y = y1; y <= y2; ++y)
painter.set_pixel({ x, y }, cursor_color);
} else {
// We fall back to a block if we don't support the selected cursor type.
painter.draw_rect(cell_rect, cursor_color);
}
}
}
@ -986,6 +1008,32 @@ void TerminalWidget::emit(const u8* data, size_t size)
}
}
void TerminalWidget::set_cursor_style(CursorStyle style)
{
switch (style) {
case None:
m_cursor_blink_timer->stop();
m_cursor_blink_state = false;
break;
case SteadyBlock:
case SteadyUnderline:
case SteadyBar:
m_cursor_blink_timer->stop();
m_cursor_blink_state = true;
break;
case BlinkingBlock:
case BlinkingUnderline:
case BlinkingBar:
m_cursor_blink_state = true;
m_cursor_blink_timer->restart();
break;
default:
dbgln("Cursor style not implemented");
}
m_cursor_style = style;
invalidate_cursor();
}
void TerminalWidget::context_menu_event(GUI::ContextMenuEvent& event)
{
if (m_hovered_href_id.is_null()) {
@ -1107,5 +1155,4 @@ void TerminalWidget::set_font_and_resize_to_fit(const Gfx::Font& font)
set_font(font);
resize(widget_size_for_font(font));
}
}

View file

@ -114,6 +114,7 @@ private:
virtual void terminal_did_resize(u16 columns, u16 rows) override;
virtual void terminal_history_changed() override;
virtual void emit(const u8*, size_t) override;
virtual void set_cursor_style(CursorStyle) override;
void set_logical_focus(bool);
@ -173,6 +174,8 @@ private:
bool m_cursor_blink_state { true };
bool m_automatic_size_policy { false };
VT::CursorStyle m_cursor_style { BlinkingBlock };
enum class AutoScrollDirection {
None,
Up,