Browse Source

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).
Daniel Bertalan 4 năm trước cách đây
mục cha
commit
875a2cbb71

+ 5 - 0
Kernel/TTY/VirtualConsole.cpp

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

+ 1 - 0
Kernel/TTY/VirtualConsole.h

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

+ 84 - 9
Userland/Libraries/LibVT/Terminal.cpp

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

+ 17 - 0
Userland/Libraries/LibVT/Terminal.h

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

+ 52 - 5
Userland/Libraries/LibVT/TerminalWidget.cpp

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

+ 3 - 0
Userland/Libraries/LibVT/TerminalWidget.h

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