forked from qt-creator/qt-creator
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:
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
p.fillRect(cursorRect, p.pen().brush());
|
switch (cursor.shape) {
|
||||||
|
case Internal::Cursor::Shape::Block:
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
3
src/plugins/terminal/tests/cursor/bar
Executable file
3
src/plugins/terminal/tests/cursor/bar
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e -n "\x1b[\x36 q" # Steady bar
|
||||||
3
src/plugins/terminal/tests/cursor/blinkbar
Executable file
3
src/plugins/terminal/tests/cursor/blinkbar
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e -n "\x1b[\x35 q" # Blinking bar
|
||||||
3
src/plugins/terminal/tests/cursor/blinkblock
Executable file
3
src/plugins/terminal/tests/cursor/blinkblock
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e -n "\x1b[\x31 q" # Blinking block (default)
|
||||||
3
src/plugins/terminal/tests/cursor/blinkunderline
Executable file
3
src/plugins/terminal/tests/cursor/blinkunderline
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e -n "\x1b[\x33 q" # Blinking underline
|
||||||
3
src/plugins/terminal/tests/cursor/block
Executable file
3
src/plugins/terminal/tests/cursor/block
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e -n "\x1b[\x32 q" # Steady block
|
||||||
3
src/plugins/terminal/tests/cursor/underline
Executable file
3
src/plugins/terminal/tests/cursor/underline
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e -n "\x1b[\x34 q" # Steady underline
|
||||||
Reference in New Issue
Block a user