Files
qt-creator/src/plugins/terminal/terminalwidget.cpp

962 lines
29 KiB
C++
Raw Normal View History

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "terminalwidget.h"
#include "celllayout.h"
#include "keys.h"
#include "terminalsettings.h"
#include "terminaltr.h"
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/processinterface.h>
#include <utils/stringutils.h>
#include <vterm.h>
#include <QApplication>
#include <QClipboard>
#include <QElapsedTimer>
#include <QGlyphRun>
#include <QLoggingCategory>
#include <QPaintEvent>
#include <QPainter>
#include <QRawFont>
#include <QRegularExpression>
#include <QScrollBar>
#include <QTextLayout>
#include <chrono>
Q_LOGGING_CATEGORY(terminalLog, "qtc.terminal", QtWarningMsg)
using namespace Utils;
using namespace Utils::Terminal;
namespace Terminal {
using namespace std::chrono_literals;
// Minimum time between two refreshes.
static const auto minRefreshInterval = 16ms;
TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &openParameters)
: QAbstractScrollArea(parent)
, m_vterm(vterm_new(size().height(), size().width()), vterm_free)
, m_vtermScreen(vterm_obtain_screen(m_vterm.get()))
, m_scrollback(std::make_unique<Internal::Scrollback>(5000))
, m_copyAction(Tr::tr("Copy"))
, m_pasteAction(Tr::tr("Paste"))
, m_clearSelectionAction(Tr::tr("Clear Selection"))
, m_zoomInAction(Tr::tr("Zoom In"))
, m_zoomOutAction(Tr::tr("Zoom Out"))
, m_openParameters(openParameters)
, m_lastFlush(QDateTime::currentDateTime())
{
setupVTerm();
setupFont();
setupColors();
setAttribute(Qt::WA_InputMethodEnabled);
setAttribute(Qt::WA_MouseTracking);
setCursor(Qt::IBeamCursor);
setViewportMargins(1, 1, 1, 1);
m_textLayout.setCacheEnabled(true);
setFocus();
setFocusPolicy(Qt::StrongFocus);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_flushDelayTimer.setSingleShot(true);
m_flushDelayTimer.setInterval(minRefreshInterval);
connect(&m_flushDelayTimer, &QTimer::timeout, this, [this]() { flushVTerm(true); });
connect(&m_copyAction, &QAction::triggered, this, &TerminalWidget::copyToClipboard);
connect(&m_pasteAction, &QAction::triggered, this, &TerminalWidget::pasteFromClipboard);
connect(&m_clearSelectionAction, &QAction::triggered, this, &TerminalWidget::clearSelection);
connect(&m_zoomInAction, &QAction::triggered, this, &TerminalWidget::zoomIn);
connect(&m_zoomOutAction, &QAction::triggered, this, &TerminalWidget::zoomOut);
connect(&TerminalSettings::instance(), &AspectContainer::applied, this, [this] {
m_layoutVersion++;
// Setup colors first, as setupFont will redraw the screen.
setupColors();
setupFont();
});
}
void TerminalWidget::setupPty()
{
m_process = std::make_unique<QtcProcess>();
Environment env = m_openParameters.environment.value_or(Environment::systemEnvironment());
CommandLine shellCommand = m_openParameters.shellCommand.value_or(
CommandLine{TerminalSettings::instance().shell.filePath(), {}});
// For git bash on Windows
env.prependOrSetPath(shellCommand.executable().parentDir());
if (env.hasKey("CLINK_NOAUTORUN"))
env.unset("CLINK_NOAUTORUN");
m_process->setProcessMode(ProcessMode::Writer);
m_process->setTerminalMode(TerminalMode::Pty);
m_process->setCommand(shellCommand);
if (m_openParameters.workingDirectory.has_value())
m_process->setWorkingDirectory(*m_openParameters.workingDirectory);
m_process->setEnvironment(env);
connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this]() {
onReadyRead(false);
});
connect(m_process.get(), &QtcProcess::done, this, [this] {
m_cursor.visible = false;
if (m_process) {
if (m_process->exitCode() != 0) {
QByteArray msg = QString("\r\n\033[31mProcess exited with code: %1")
.arg(m_process->exitCode())
.toUtf8();
if (!m_process->errorString().isEmpty())
msg += QString(" (%1)").arg(m_process->errorString()).toUtf8();
vterm_input_write(m_vterm.get(), msg.constData(), msg.size());
vterm_screen_flush_damage(m_vtermScreen);
return;
}
}
if (m_openParameters.m_exitBehavior == ExitBehavior::Restart) {
QMetaObject::invokeMethod(
this,
[this] {
m_process.reset();
setupPty();
},
Qt::QueuedConnection);
}
if (m_openParameters.m_exitBehavior == ExitBehavior::Close)
deleteLater();
if (m_openParameters.m_exitBehavior == ExitBehavior::Keep) {
QByteArray msg = QString("\r\nProcess exited with code: %1")
.arg(m_process ? m_process->exitCode() : -1)
.toUtf8();
vterm_input_write(m_vterm.get(), msg.constData(), msg.size());
vterm_screen_flush_damage(m_vtermScreen);
}
});
connect(m_process.get(), &QtcProcess::started, this, [this] {
applySizeChange();
emit started(m_process->processId());
});
m_process->start();
}
void TerminalWidget::setupFont()
{
QFont f;
f.setFixedPitch(true);
f.setFamily(TerminalSettings::instance().font.value());
f.setPointSize(TerminalSettings::instance().fontSize.value());
setFont(f);
}
void TerminalWidget::setupColors()
{
// Check if the colors have changed.
std::array<QColor, 18> newColors;
for (int i = 0; i < 16; ++i) {
newColors[i] = TerminalSettings::instance().colors[i].value();
}
newColors[16] = TerminalSettings::instance().foregroundColor.value();
newColors[17] = TerminalSettings::instance().backgroundColor.value();
if (m_currentColors == newColors)
return;
m_currentColors = newColors;
VTermState *vts = vterm_obtain_state(m_vterm.get());
auto setColor = [vts](int index, uint8_t r, uint8_t g, uint8_t b) {
VTermColor col;
vterm_color_rgb(&col, r, g, b);
vterm_state_set_palette_color(vts, index, &col);
};
for (int i = 0; i < 16; ++i) {
QColor c = TerminalSettings::instance().colors[i].value();
setColor(i, c.red(), c.green(), c.blue());
}
VTermColor fg;
VTermColor bg;
vterm_color_rgb(&fg,
TerminalSettings::instance().foregroundColor.value().red(),
TerminalSettings::instance().foregroundColor.value().green(),
TerminalSettings::instance().foregroundColor.value().blue());
vterm_color_rgb(&bg,
TerminalSettings::instance().backgroundColor.value().red(),
TerminalSettings::instance().backgroundColor.value().green(),
TerminalSettings::instance().backgroundColor.value().blue());
vterm_state_set_default_colors(vts, &fg, &bg);
clearContents();
}
void TerminalWidget::writeToPty(const QByteArray &data)
{
if (m_process)
m_process->writeRaw(data);
}
void TerminalWidget::setupVTerm()
{
vterm_set_utf8(m_vterm.get(), true);
static auto writeToPty = [](const char *s, size_t len, void *user) {
auto p = static_cast<TerminalWidget *>(user);
p->writeToPty(QByteArray(s, static_cast<int>(len)));
};
vterm_output_set_callback(m_vterm.get(), writeToPty, this);
memset(&m_vtermScreenCallbacks, 0, sizeof(m_vtermScreenCallbacks));
m_vtermScreenCallbacks.damage = [](VTermRect rect, void *user) {
auto p = static_cast<TerminalWidget *>(user);
p->invalidate(rect);
return 1;
};
m_vtermScreenCallbacks.sb_pushline = [](int cols, const VTermScreenCell *cells, void *user) {
auto p = static_cast<TerminalWidget *>(user);
return p->sb_pushline(cols, cells);
};
m_vtermScreenCallbacks.sb_popline = [](int cols, VTermScreenCell *cells, void *user) {
auto p = static_cast<TerminalWidget *>(user);
return p->sb_popline(cols, cells);
};
m_vtermScreenCallbacks.settermprop = [](VTermProp prop, VTermValue *val, void *user) {
auto p = static_cast<TerminalWidget *>(user);
return p->setTerminalProperties(prop, val);
};
m_vtermScreenCallbacks.movecursor = [](VTermPos pos, VTermPos oldpos, int visible, void *user) {
auto p = static_cast<TerminalWidget *>(user);
return p->movecursor(pos, oldpos, visible);
};
m_vtermScreenCallbacks.sb_clear = [](void *user) {
auto p = static_cast<TerminalWidget *>(user);
return p->sb_clear();
};
vterm_screen_set_callbacks(m_vtermScreen, &m_vtermScreenCallbacks, this);
vterm_screen_set_damage_merge(m_vtermScreen, VTERM_DAMAGE_SCROLL);
vterm_screen_enable_altscreen(m_vtermScreen, true);
VTermState *vts = vterm_obtain_state(m_vterm.get());
vterm_state_set_bold_highbright(vts, true);
vterm_screen_reset(m_vtermScreen, 1);
}
void TerminalWidget::setFont(const QFont &font)
{
m_font = font;
//QRawFont rawFont = QRawFont::fromFont(m_font);
m_textLayout.setFont(m_font);
QFontMetricsF qfm{m_font};
const auto w = [qfm]() -> qreal {
if (HostOsInfo::isMacHost())
return qfm.maxWidth();
return qfm.averageCharWidth();
}();
qCInfo(terminalLog) << font.family() << font.pointSize() << w << viewport()->size();
m_cellSize = {w, qfm.height()};
m_cellBaseline = qfm.ascent();
m_lineSpacing = qfm.height();
QAbstractScrollArea::setFont(m_font);
if (m_process) {
applySizeChange();
}
}
QAction &TerminalWidget::copyAction()
{
return m_copyAction;
}
QAction &TerminalWidget::pasteAction()
{
return m_pasteAction;
}
QAction &TerminalWidget::clearSelectionAction()
{
return m_clearSelectionAction;
}
QAction &TerminalWidget::zoomInAction()
{
return m_zoomInAction;
}
QAction &TerminalWidget::zoomOutAction()
{
return m_zoomOutAction;
}
void TerminalWidget::copyToClipboard() const
{
if (m_selection) {
const size_t startLine = qFloor(m_selection->start.y() / m_lineSpacing);
const size_t endLine = qFloor(m_selection->end.y() / m_lineSpacing);
QString selectedText;
size_t row = startLine;
for (; row < m_scrollback->size(); row++) {
const Internal::Scrollback::Line &line = m_scrollback->line((m_scrollback->size() - 1)
- row);
if (row > endLine)
break;
const QTextLayout &layout = line.layout(m_layoutVersion, m_font, m_lineSpacing);
const std::optional<QTextLayout::FormatRange> range
= selectionToFormatRange(*m_selection, layout, row);
if (range)
selectedText.append(line.layout(m_layoutVersion, m_font, m_lineSpacing)
.text()
.mid(range->start, range->length)
.trimmed());
if (endLine > row)
selectedText.append(QChar::LineFeed);
}
if (row <= endLine) {
const std::optional<QTextLayout::FormatRange> range
= selectionToFormatRange(*m_selection, m_textLayout, m_scrollback->size());
if (range)
selectedText.append(m_textLayout.text()
.mid(range->start, range->length)
.replace(QChar::LineSeparator, QChar::LineFeed)
.trimmed());
}
setClipboardAndSelection(selectedText);
}
}
void TerminalWidget::pasteFromClipboard()
{
QClipboard *clipboard = QApplication::clipboard();
const QString clipboardText = clipboard->text(QClipboard::Clipboard);
if (clipboardText.isEmpty())
return;
vterm_keyboard_start_paste(m_vterm.get());
for (unsigned int ch : clipboardText.toUcs4())
vterm_keyboard_unichar(m_vterm.get(), ch, VTERM_MOD_NONE);
vterm_keyboard_end_paste(m_vterm.get());
if (!m_altscreen && m_scrollback->offset()) {
m_scrollback->unscroll();
viewport()->update();
}
}
void TerminalWidget::clearSelection()
{
m_selection.reset();
update();
}
void TerminalWidget::zoomIn()
{
m_layoutVersion++;
m_font.setPointSize(m_font.pointSize() + 1);
setFont(m_font);
}
void TerminalWidget::zoomOut()
{
m_layoutVersion++;
m_font.setPointSize(qMax(m_font.pointSize() - 1, 1));
setFont(m_font);
}
void TerminalWidget::clearContents()
{
// Fake a scrollback clearing
QByteArray data{"\x1b[3J"};
vterm_input_write(m_vterm.get(), data.constData(), data.size());
// Send Ctrl+L which will clear the screen
writeToPty(QByteArray("\f"));
}
void TerminalWidget::onReadyRead(bool forceFlush)
{
QByteArray data = m_process->readAllRawStandardOutput();
vterm_input_write(m_vterm.get(), data.constData(), data.size());
flushVTerm(forceFlush);
}
void TerminalWidget::flushVTerm(bool force)
{
if (force || QDateTime::currentDateTime() - m_lastFlush > minRefreshInterval) {
m_lastFlush = QDateTime::currentDateTime();
vterm_screen_flush_damage(m_vtermScreen);
return;
}
if (!m_flushDelayTimer.isActive())
m_flushDelayTimer.start();
}
const VTermScreenCell *TerminalWidget::fetchCell(int x, int y) const
{
QTC_ASSERT(y >= 0, return nullptr);
QTC_ASSERT(y < m_vtermSize.height(), return nullptr);
static VTermScreenCell refCell{};
VTermPos vtp{y, x};
vterm_screen_get_cell(m_vtermScreen, vtp, &refCell);
vterm_screen_convert_color_to_rgb(m_vtermScreen, &refCell.fg);
vterm_screen_convert_color_to_rgb(m_vtermScreen, &refCell.bg);
return &refCell;
};
QPoint TerminalWidget::viewportToGlobal(QPoint p) const
{
int y = p.y() - topMargin();
const double offset = (m_scrollback->size() - m_scrollback->offset()) * m_lineSpacing;
y += offset;
return {p.x(), y};
}
void TerminalWidget::createTextLayout()
{
QElapsedTimer t;
t.start();
VTermColor defaultBg;
if (!m_altscreen) {
VTermColor defaultFg;
vterm_state_get_default_colors(vterm_obtain_state(m_vterm.get()), &defaultFg, &defaultBg);
// We want to compare the cell bg against this later and cells don't
// set DEFAULT_BG
defaultBg.type = VTERM_COLOR_RGB;
} else {
// This is a slightly better guess when in an altscreen
const VTermScreenCell *cell = fetchCell(0, 0);
defaultBg = cell->bg;
}
m_textLayout.clearLayout();
QString allText;
Internal::createTextLayout(m_textLayout,
allText,
defaultBg,
QRect({0, 0}, m_vtermSize),
m_lineSpacing,
[this](int x, int y) { return fetchCell(x, y); });
qCInfo(terminalLog) << "createTextLayout took:" << t.elapsed() << "ms";
}
qreal TerminalWidget::topMargin() const
{
return (qreal) viewport()->size().height() - ((qreal) m_vtermSize.height() * m_lineSpacing);
}
std::optional<QTextLayout::FormatRange> TerminalWidget::selectionToFormatRange(
TerminalWidget::Selection selection, const QTextLayout &layout, int rowOffset) const
{
int selectionStartLine = qFloor(selection.start.y() / m_lineSpacing) - rowOffset;
int selectionEndLine = qFloor(selection.end.y() / m_lineSpacing) - rowOffset;
int nRows = layout.lineCount();
if (selectionStartLine < nRows && selectionEndLine >= 0) {
QTextLine lStart = layout.lineAt(qMax(0, qMin(selectionStartLine, nRows)));
QTextLine lEnd = layout.lineAt(qMin(nRows - 1, qMax(0, selectionEndLine)));
int startPos = 0;
int endPos = lEnd.textLength();
if (selectionStartLine >= 0)
startPos = lStart.xToCursor(selection.start.x());
if (selectionEndLine < (nRows))
endPos = lEnd.xToCursor(selection.end.x());
QTextLayout::FormatRange range;
range.start = startPos;
range.length = endPos - startPos;
range.format.setBackground(TerminalSettings::instance().selectionColor.value());
return range;
}
return {};
}
void TerminalWidget::paintEvent(QPaintEvent *event)
{
event->accept();
QPainter p(viewport());
p.setCompositionMode(QPainter::CompositionMode_Source);
VTermColor defaultBg;
if (!m_altscreen) {
VTermColor defaultFg;
vterm_state_get_default_colors(vterm_obtain_state(m_vterm.get()), &defaultFg, &defaultBg);
// We want to compare the cell bg against this later and cells don't
// set DEFAULT_BG
defaultBg.type = VTERM_COLOR_RGB;
} else {
// This is a slightly better guess when in an altscreen
const VTermScreenCell *cell = fetchCell(0, 0);
defaultBg = cell->bg;
}
p.fillRect(event->rect(), Internal::toQColor(defaultBg));
unsigned long off = m_scrollback->size() - m_scrollback->offset();
// transform painter according to scroll offsets
QPointF offset{0, -(off * m_lineSpacing)};
qreal margin = topMargin();
qreal y = offset.y() + margin;
size_t row = qFloor((offset.y() * -1) / m_lineSpacing);
y += row * m_lineSpacing;
for (; row < m_scrollback->size(); row++) {
if (y >= 0 && y < viewport()->height()) {
const Internal::Scrollback::Line &line = m_scrollback->line((m_scrollback->size() - 1)
- row);
QList<QTextLayout::FormatRange> selections;
if (m_selection) {
const std::optional<QTextLayout::FormatRange> range
= selectionToFormatRange(m_selection.value(),
line.layout(m_layoutVersion, m_font, m_lineSpacing),
row);
if (range) {
selections.append(range.value());
}
}
line.layout(m_layoutVersion, m_font, m_lineSpacing).draw(&p, {0.0, y}, selections);
}
y += m_lineSpacing;
}
// Draw the live part
if (y < m_vtermSize.height() * m_lineSpacing) {
QList<QTextLayout::FormatRange> selections;
if (m_selection) {
const std::optional<QTextLayout::FormatRange> range
= selectionToFormatRange(m_selection.value(), m_textLayout, row);
if (range) {
selections.append(range.value());
}
}
m_textLayout.draw(&p, {0.0, y}, selections);
if (m_cursor.visible && m_preEditString.isEmpty()) {
p.setPen(QColor::fromRgb(0xFF, 0xFF, 0xFF));
if (m_textLayout.lineCount() > m_cursor.row) {
QTextLine cursorLine = m_textLayout.lineAt(m_cursor.row);
if (cursorLine.isValid()) {
QFontMetricsF fm(m_font);
const QString text = m_textLayout.text();
const QList<uint> asUcs4 = text.toUcs4();
const int textStart = cursorLine.textStart();
const int cPos = textStart + m_cursor.col;
if (cPos >= 0 && cPos < asUcs4.size()) {
const unsigned int ch = asUcs4.at(cPos);
const qreal br = fm.horizontalAdvance(QString::fromUcs4(&ch, 1));
const qreal xCursor = cursorLine.cursorToX(cPos);
const double yCursor = cursorLine.y() + y;
const QRectF cursorRect
= QRectF{xCursor, yCursor + 1, br, m_lineSpacing - 2};
if (hasFocus()) {
QPainter::CompositionMode oldMode = p.compositionMode();
p.setCompositionMode(QPainter::RasterOp_NotDestination);
p.fillRect(cursorRect, p.pen().brush());
p.setCompositionMode(oldMode);
} else {
p.drawRect(cursorRect);
}
}
}
}
}
if (!m_preEditString.isEmpty()) {
QTextLine cursorLine = m_textLayout.lineAt(m_cursor.row);
if (cursorLine.isValid()) {
int pos = cursorLine.textStart() + m_cursor.col;
QPointF displayPos = QPointF{cursorLine.cursorToX(pos), cursorLine.y() + y};
p.fillRect(QRectF{displayPos.toPoint(), m_cellSize}, QColor::fromRgb(0, 0, 0));
p.setPen(Qt::white);
displayPos.setY(displayPos.y() + m_cellBaseline);
p.drawText(displayPos, m_preEditString);
}
}
}
p.fillRect(QRectF{{0, 0}, QSizeF{(qreal) width(), margin}}, Internal::toQColor(defaultBg));
}
void TerminalWidget::keyPressEvent(QKeyEvent *event)
{
event->accept();
if (event->modifiers() == Qt::NoModifier && event->key() == Qt::Key_Escape && m_selection) {
clearSelectionAction().trigger();
return;
}
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Plus) {
zoomInAction().trigger();
return;
}
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Minus) {
zoomOutAction().trigger();
return;
}
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) {
copyAction().trigger();
return;
}
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_V) {
pasteAction().trigger();
return;
}
bool keypad = event->modifiers() & Qt::KeypadModifier;
VTermModifier mod = Internal::qtModifierToVTerm(event->modifiers());
VTermKey key = Internal::qtKeyToVTerm(Qt::Key(event->key()), keypad);
if (key != VTERM_KEY_NONE) {
if (mod == VTERM_MOD_SHIFT && (key == VTERM_KEY_ESCAPE || key == VTERM_KEY_BACKSPACE))
mod = VTERM_MOD_NONE;
vterm_keyboard_key(m_vterm.get(), key, mod);
} else if (event->text().length() == 1) {
// This maps to delete word and is way to easy to mistakenly type
// if (event->key() == Qt::Key_Space && mod == VTERM_MOD_SHIFT)
// mod = VTERM_MOD_NONE;
// Per https://github.com/justinmk/neovim/commit/317d5ca7b0f92ef42de989b3556ca9503f0a3bf6
// libvterm prefers we send the full keycode rather than sending the
// ctrl modifier. This helps with ncurses applications which otherwise
// do not recognize ctrl+<key> and in the shell for getting common control characters
// like ctrl+i for tab or ctrl+j for newline.
// Workaround for "ALT+SHIFT+/" (\ on german mac keyboards)
if (mod == (VTERM_MOD_SHIFT | VTERM_MOD_ALT) && event->key() == Qt::Key_Slash) {
mod = VTERM_MOD_NONE;
}
vterm_keyboard_unichar(m_vterm.get(),
event->text().toUcs4()[0],
static_cast<VTermModifier>(mod & ~VTERM_MOD_CTRL));
} else if (mod != VTERM_MOD_NONE && event->key() == Qt::Key_C) {
vterm_keyboard_unichar(m_vterm.get(), 'c', mod);
}
}
void TerminalWidget::applySizeChange()
{
m_vtermSize = {
qFloor((qreal) (viewport()->size().width()) / (qreal) m_cellSize.width()),
qFloor((qreal) (viewport()->size().height()) / m_lineSpacing),
};
if (m_vtermSize.height() <= 0)
m_vtermSize.setHeight(1);
if (m_vtermSize.width() <= 0)
m_vtermSize.setWidth(1);
if (m_process)
m_process->ptyData().resize(m_vtermSize);
vterm_set_size(m_vterm.get(), m_vtermSize.height(), m_vtermSize.width());
flushVTerm(true);
}
void TerminalWidget::updateScrollBars()
{
verticalScrollBar()->setRange(0, static_cast<int>(m_scrollback->size()));
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void TerminalWidget::resizeEvent(QResizeEvent *event)
{
event->accept();
// If increasing in size, we'll trigger libvterm to call sb_popline in
// order to pull lines out of the history. This will cause the scrollback
// to decrease in size which reduces the size of the verticalScrollBar.
// That will trigger a scroll offset increase which we want to ignore.
m_ignoreScroll = true;
applySizeChange();
m_selection.reset();
m_ignoreScroll = false;
}
void TerminalWidget::invalidate(VTermRect rect)
{
Q_UNUSED(rect);
createTextLayout();
viewport()->update();
}
int TerminalWidget::sb_pushline(int cols, const VTermScreenCell *cells)
{
m_scrollback->emplace(cols, cells, vterm_obtain_state(m_vterm.get()));
updateScrollBars();
return 1;
}
int TerminalWidget::sb_popline(int cols, VTermScreenCell *cells)
{
if (m_scrollback->size() == 0)
return 0;
m_scrollback->popto(cols, cells);
updateScrollBars();
return 1;
}
int TerminalWidget::sb_clear()
{
m_scrollback->clear();
updateScrollBars();
return 1;
}
void TerminalWidget::wheelEvent(QWheelEvent *event)
{
verticalScrollBar()->event(event);
}
void TerminalWidget::focusInEvent(QFocusEvent *)
{
viewport()->update();
}
void TerminalWidget::focusOutEvent(QFocusEvent *)
{
viewport()->update();
}
void TerminalWidget::inputMethodEvent(QInputMethodEvent *event)
{
m_preEditString = event->preeditString();
if (event->commitString().isEmpty()) {
viewport()->update();
return;
}
for (const unsigned int ch : event->commitString().toUcs4()) {
vterm_keyboard_unichar(m_vterm.get(), ch, VTERM_MOD_NONE);
}
}
void TerminalWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_selectionStartPos = event->pos();
QPoint pos = viewportToGlobal(event->pos());
m_selection = Selection{pos, pos};
viewport()->update();
}
}
void TerminalWidget::mouseMoveEvent(QMouseEvent *event)
{
if (m_selection && event->buttons() & Qt::LeftButton) {
QPoint start = viewportToGlobal(m_selectionStartPos);
QPoint newEnd = viewportToGlobal(event->pos());
if (start.y() > newEnd.y() || (start.y() == newEnd.y() && start.x() > newEnd.x()))
std::swap(start, newEnd);
m_selection->start = start;
m_selection->end = newEnd;
viewport()->update();
}
}
void TerminalWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (m_selection && event->button() != Qt::LeftButton) {
if ((m_selectionStartPos - event->pos()).manhattanLength() < 2) {
m_selection.reset();
viewport()->update();
}
}
}
void TerminalWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
// TODO :(
Q_UNUSED(event);
viewport()->update();
}
void TerminalWidget::scrollContentsBy(int dx, int dy)
{
Q_UNUSED(dx);
if (m_ignoreScroll)
return;
if (m_altscreen)
return;
size_t orig = m_scrollback->offset();
size_t offset = m_scrollback->scroll(dy);
if (orig == offset)
return;
m_cursor.visible = (offset == 0);
viewport()->update();
}
void TerminalWidget::showEvent(QShowEvent *event)
{
Q_UNUSED(event);
if (!m_process)
setupPty();
QAbstractScrollArea::showEvent(event);
}
bool TerminalWidget::event(QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride) {
if (hasFocus()) {
event->accept();
return true;
}
}
if (event->type() == QEvent::KeyPress) {
QKeyEvent *k = (QKeyEvent *) event;
keyPressEvent(k);
return true;
}
if (event->type() == QEvent::KeyRelease) {
QKeyEvent *k = (QKeyEvent *) event;
keyReleaseEvent(k);
return true;
}
if (event->type() == QEvent::Paint) {
QPainter p(this);
p.fillRect(QRect(QPoint(0, 0), size()),
TerminalSettings::instance().backgroundColor.value());
return true;
}
return QAbstractScrollArea::event(event);
}
int TerminalWidget::setTerminalProperties(VTermProp prop, VTermValue *val)
{
switch (prop) {
case VTERM_PROP_CURSORVISIBLE:
m_cursor.visible = val->boolean;
break;
case VTERM_PROP_CURSORBLINK:
qCDebug(terminalLog) << "Ignoring VTERM_PROP_CURSORBLINK" << val->boolean;
break;
case VTERM_PROP_CURSORSHAPE:
qCDebug(terminalLog) << "Ignoring VTERM_PROP_CURSORSHAPE" << val->number;
break;
case VTERM_PROP_ICONNAME:
//emit iconTextChanged(val->string);
break;
case VTERM_PROP_TITLE:
//emit titleChanged(val->string);
setWindowTitle(QString::fromUtf8(val->string.str, val->string.len));
break;
case VTERM_PROP_ALTSCREEN:
m_altscreen = val->boolean;
m_selection.reset();
break;
case VTERM_PROP_MOUSE:
qCDebug(terminalLog) << "Ignoring VTERM_PROP_MOUSE" << val->number;
break;
case VTERM_PROP_REVERSE:
qCDebug(terminalLog) << "Ignoring VTERM_PROP_REVERSE" << val->boolean;
break;
case VTERM_N_PROPS:
break;
}
return 1;
}
int TerminalWidget::movecursor(VTermPos pos, VTermPos oldpos, int visible)
{
Q_UNUSED(oldpos);
viewport()->update();
m_cursor.row = pos.row;
m_cursor.col = pos.col;
m_cursor.visible = visible;
return 1;
}
} // namespace Terminal