Terminal: Add support for Cursor shape and blink

Change-Id: I7f71578a714d375bcd4ef8ae431f4127cbc57a55
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Marcus Tillmanns
2023-03-05 23:55:37 +01:00
parent 2ac2c5c07d
commit f95f815e98
13 changed files with 122 additions and 10 deletions

View File

@@ -71,6 +71,11 @@ TerminalSettings::TerminalSettings()
fontSize.setDefaultValue(defaultFontSize()); fontSize.setDefaultValue(defaultFontSize());
fontSize.setRange(1, 100); fontSize.setRange(1, 100);
allowBlinkingCursor.setSettingsKey("AllowBlinkingCursor");
allowBlinkingCursor.setLabelText(Tr::tr("Allow blinking cursor"));
allowBlinkingCursor.setToolTip(Tr::tr("Allow the cursor to blink."));
allowBlinkingCursor.setDefaultValue(false);
shell.setSettingsKey("ShellPath"); shell.setSettingsKey("ShellPath");
shell.setLabelText(Tr::tr("Shell path:")); shell.setLabelText(Tr::tr("Shell path:"));
shell.setExpectedKind(PathChooser::ExistingCommand); shell.setExpectedKind(PathChooser::ExistingCommand);
@@ -110,6 +115,7 @@ TerminalSettings::TerminalSettings()
registerAspect(&font); registerAspect(&font);
registerAspect(&fontSize); registerAspect(&fontSize);
registerAspect(&shell); registerAspect(&shell);
registerAspect(&allowBlinkingCursor);
registerAspect(&foregroundColor); registerAspect(&foregroundColor);
registerAspect(&backgroundColor); registerAspect(&backgroundColor);

View File

@@ -23,6 +23,8 @@ public:
Utils::ColorAspect selectionColor; Utils::ColorAspect selectionColor;
Utils::ColorAspect colors[16]; Utils::ColorAspect colors[16];
Utils::BoolAspect allowBlinkingCursor;
}; };
} // namespace Terminal } // namespace Terminal

View File

@@ -54,6 +54,12 @@ QWidget *TerminalSettingsPage::widget()
settings.fontSize, st, settings.fontSize, st,
}, },
}, },
Group {
title(Tr::tr("Cursor")),
Row {
settings.allowBlinkingCursor, st,
},
},
Group { Group {
title(Tr::tr("Colors")), title(Tr::tr("Colors")),
Column { Column {

View File

@@ -28,6 +28,9 @@ struct TerminalSurfacePrivate
, m_vtermScreen(vterm_obtain_screen(m_vterm.get())) , m_vtermScreen(vterm_obtain_screen(m_vterm.get()))
, m_scrollback(std::make_unique<Internal::Scrollback>(5000)) , m_scrollback(std::make_unique<Internal::Scrollback>(5000))
, q(surface) , q(surface)
{}
void init()
{ {
vterm_set_utf8(m_vterm.get(), true); vterm_set_utf8(m_vterm.get(), true);
@@ -193,15 +196,24 @@ struct TerminalSurfacePrivate
int setTerminalProperties(VTermProp prop, VTermValue *val) int setTerminalProperties(VTermProp prop, VTermValue *val)
{ {
switch (prop) { switch (prop) {
case VTERM_PROP_CURSORVISIBLE: case VTERM_PROP_CURSORVISIBLE: {
Cursor old = q->cursor();
m_cursor.visible = val->boolean; m_cursor.visible = val->boolean;
q->cursorChanged(old, q->cursor());
break; break;
case VTERM_PROP_CURSORBLINK: }
qCDebug(log) << "Ignoring VTERM_PROP_CURSORBLINK" << val->boolean; case VTERM_PROP_CURSORBLINK: {
Cursor old = q->cursor();
m_cursor.blink = val->boolean;
emit q->cursorChanged(old, q->cursor());
break; break;
case VTERM_PROP_CURSORSHAPE: }
qCDebug(log) << "Ignoring VTERM_PROP_CURSORSHAPE" << val->number; case VTERM_PROP_CURSORSHAPE: {
Cursor old = q->cursor();
m_cursor.shape = (Cursor::Shape) val->number;
emit q->cursorChanged(old, q->cursor());
break; break;
}
case VTERM_PROP_ICONNAME: case VTERM_PROP_ICONNAME:
//emit iconTextChanged(val->string); //emit iconTextChanged(val->string);
break; break;
@@ -227,7 +239,8 @@ struct TerminalSurfacePrivate
{ {
Q_UNUSED(oldpos); Q_UNUSED(oldpos);
Cursor oldCursor = q->cursor(); Cursor oldCursor = q->cursor();
m_cursor = {{pos.col, pos.row}, visible > 0}; m_cursor.position = {pos.col, pos.row};
m_cursor.visible = visible > 0;
q->cursorChanged(oldCursor, q->cursor()); q->cursorChanged(oldCursor, q->cursor());
return 1; return 1;
} }
@@ -272,7 +285,9 @@ struct TerminalSurfacePrivate
TerminalSurface::TerminalSurface(QSize initialGridSize) TerminalSurface::TerminalSurface(QSize initialGridSize)
: d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize)) : d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize))
{} {
d->init();
}
TerminalSurface::~TerminalSurface() = default; TerminalSurface::~TerminalSurface() = default;
@@ -310,7 +325,8 @@ std::u32string::value_type TerminalSurface::fetchCharAt(int x, int y) const
TerminalCell TerminalSurface::fetchCell(int x, int y) const TerminalCell TerminalSurface::fetchCell(int x, int y) const
{ {
static TerminalCell emptyCell{1, {}, {}}; static TerminalCell
emptyCell{1, {}, {}, false, {}, std::nullopt, QTextCharFormat::NoUnderline, false};
QTC_ASSERT(y >= 0, return emptyCell); QTC_ASSERT(y >= 0, return emptyCell);
QTC_ASSERT(y < d->liveSize().height() + d->m_scrollback->size(), return emptyCell); QTC_ASSERT(y < d->liveSize().height() + d->m_scrollback->size(), return emptyCell);

View File

@@ -31,8 +31,15 @@ struct TerminalCell
struct Cursor struct Cursor
{ {
enum class Shape {
Block = 1,
Underline,
LeftBar,
};
QPoint position; QPoint position;
bool visible; bool visible;
Shape shape;
bool blink{false};
}; };
class TerminalSurface : public QObject class TerminalSurface : public QObject

View File

@@ -67,6 +67,17 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
setupColors(); setupColors();
setupActions(); setupActions();
m_cursorBlinkTimer.setInterval(750);
m_cursorBlinkTimer.setSingleShot(false);
connect(&m_cursorBlinkTimer, &QTimer::timeout, this, [this]() {
if (hasFocus())
m_cursorBlinkState = !m_cursorBlinkState;
else
m_cursorBlinkState = true;
updateViewport(gridToViewport(QRect{m_cursor.position, m_cursor.position}));
});
setAttribute(Qt::WA_InputMethodEnabled); setAttribute(Qt::WA_InputMethodEnabled);
setAttribute(Qt::WA_MouseTracking); setAttribute(Qt::WA_MouseTracking);
@@ -88,6 +99,7 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
// Setup colors first, as setupFont will redraw the screen. // Setup colors first, as setupFont will redraw the screen.
setupColors(); setupColors();
setupFont(); setupFont();
configBlinkTimer();
}); });
} }
@@ -261,7 +273,10 @@ void TerminalWidget::setupSurface()
if (startY > endY) if (startY > endY)
std::swap(startY, endY); std::swap(startY, endY);
m_cursor = newCursor;
updateViewport(gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}})); updateViewport(gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}}));
configBlinkTimer();
}); });
connect(m_surface.get(), &Internal::TerminalSurface::altscreenChanged, this, [this] { connect(m_surface.get(), &Internal::TerminalSurface::altscreenChanged, this, [this] {
setSelection(std::nullopt); setSelection(std::nullopt);
@@ -271,6 +286,18 @@ void TerminalWidget::setupSurface()
}); });
} }
void TerminalWidget::configBlinkTimer()
{
bool shouldRun = m_cursor.visible && m_cursor.blink && hasFocus()
&& TerminalSettings::instance().allowBlinkingCursor.value();
if (shouldRun != m_cursorBlinkTimer.isActive()) {
if (shouldRun)
m_cursorBlinkTimer.start();
else
m_cursorBlinkTimer.stop();
}
}
void TerminalWidget::setFont(const QFont &font) void TerminalWidget::setFont(const QFont &font)
{ {
m_font = font; m_font = font;
@@ -658,17 +685,33 @@ void TerminalWidget::paintCursor(QPainter &p) const
{ {
auto cursor = m_surface->cursor(); auto cursor = m_surface->cursor();
if (cursor.visible) { const bool blinkState = !cursor.blink || m_cursorBlinkState
|| !TerminalSettings::instance().allowBlinkingCursor.value();
if (cursor.visible && blinkState) {
const int cursorCellWidth = m_surface->cellWidthAt(cursor.position.x(), cursor.position.y()); const int cursorCellWidth = m_surface->cellWidthAt(cursor.position.x(), cursor.position.y());
QRectF cursorRect = QRectF(gridToGlobal(cursor.position), QRectF cursorRect = QRectF(gridToGlobal(cursor.position),
gridToGlobal({cursor.position.x() + cursorCellWidth, gridToGlobal({cursor.position.x() + cursorCellWidth,
cursor.position.y()}, cursor.position.y()},
true)); true));
cursorRect.adjust(0, 0, 0, -1);
if (hasFocus()) { if (hasFocus()) {
QPainter::CompositionMode oldMode = p.compositionMode(); QPainter::CompositionMode oldMode = p.compositionMode();
p.setCompositionMode(QPainter::RasterOp_NotDestination); p.setCompositionMode(QPainter::RasterOp_NotDestination);
switch (cursor.shape) {
case Internal::Cursor::Shape::Block:
p.fillRect(cursorRect, p.pen().brush()); p.fillRect(cursorRect, p.pen().brush());
break;
case Internal::Cursor::Shape::Underline:
p.drawLine(cursorRect.bottomLeft(), cursorRect.bottomRight());
break;
case Internal::Cursor::Shape::LeftBar:
p.drawLine(cursorRect.topLeft(), cursorRect.bottomLeft());
break;
}
p.setCompositionMode(oldMode); p.setCompositionMode(oldMode);
} else { } else {
p.drawRect(cursorRect); p.drawRect(cursorRect);
@@ -809,6 +852,12 @@ void TerminalWidget::paintEvent(QPaintEvent *event)
void TerminalWidget::keyPressEvent(QKeyEvent *event) void TerminalWidget::keyPressEvent(QKeyEvent *event)
{ {
// Don't blink during typing
if (m_cursorBlinkTimer.isActive()) {
m_cursorBlinkTimer.start();
m_cursorBlinkState = true;
}
bool actionTriggered = false; bool actionTriggered = false;
for (const auto &action : actions()) { for (const auto &action : actions()) {
if (!action->isEnabled()) if (!action->isEnabled())
@@ -918,10 +967,12 @@ void TerminalWidget::wheelEvent(QWheelEvent *event)
void TerminalWidget::focusInEvent(QFocusEvent *) void TerminalWidget::focusInEvent(QFocusEvent *)
{ {
updateViewport(); updateViewport();
configBlinkTimer();
} }
void TerminalWidget::focusOutEvent(QFocusEvent *) void TerminalWidget::focusOutEvent(QFocusEvent *)
{ {
updateViewport(); updateViewport();
configBlinkTimer();
} }
void TerminalWidget::inputMethodEvent(QInputMethodEvent *event) void TerminalWidget::inputMethodEvent(QInputMethodEvent *event)

View File

@@ -149,6 +149,8 @@ protected:
void setSelection(const std::optional<Selection> &selection); void setSelection(const std::optional<Selection> &selection);
void configBlinkTimer();
private: private:
std::unique_ptr<Utils::QtcProcess> m_process; std::unique_ptr<Utils::QtcProcess> m_process;
std::unique_ptr<Internal::TerminalSurface> m_surface; std::unique_ptr<Internal::TerminalSurface> m_surface;
@@ -188,6 +190,10 @@ private:
std::chrono::system_clock::time_point m_lastFlush; std::chrono::system_clock::time_point m_lastFlush;
std::chrono::system_clock::time_point m_lastDoubleClick; std::chrono::system_clock::time_point m_lastDoubleClick;
bool m_selectLineMode{false}; bool m_selectLineMode{false};
Internal::Cursor m_cursor;
QTimer m_cursorBlinkTimer;
bool m_cursorBlinkState{true};
}; };
} // namespace Terminal } // namespace Terminal

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo -e -n "\x1b[\x36 q" # Steady bar

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo -e -n "\x1b[\x35 q" # Blinking bar

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo -e -n "\x1b[\x31 q" # Blinking block (default)

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo -e -n "\x1b[\x33 q" # Blinking underline

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo -e -n "\x1b[\x32 q" # Steady block

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo -e -n "\x1b[\x34 q" # Steady underline