forked from qt-creator/qt-creator
Terminal: Create Terminal solution
Change-Id: If271fd23a84c49bbc25fcc3b9bc0939c7237d095 Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
@@ -1,12 +1,8 @@
|
||||
|
||||
add_qtc_plugin(Terminal
|
||||
PLUGIN_DEPENDS Core ProjectExplorer
|
||||
DEPENDS libvterm ptyqt
|
||||
DEPENDS TerminalLib
|
||||
SOURCES
|
||||
celliterator.cpp celliterator.h
|
||||
glyphcache.cpp glyphcache.h
|
||||
keys.cpp keys.h
|
||||
scrollback.cpp scrollback.h
|
||||
shellintegration.cpp shellintegration.h
|
||||
shellmodel.cpp shellmodel.h
|
||||
shortcutmap.cpp shortcutmap.h
|
||||
@@ -18,7 +14,6 @@ add_qtc_plugin(Terminal
|
||||
terminalprocessimpl.cpp terminalprocessimpl.h
|
||||
terminalsearch.cpp terminalsearch.h
|
||||
terminalsettings.cpp terminalsettings.h
|
||||
terminalsurface.cpp terminalsurface.h
|
||||
terminaltr.h
|
||||
terminalwidget.cpp terminalwidget.h
|
||||
)
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
// 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 "celliterator.h"
|
||||
|
||||
#include "terminalsurface.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
CellIterator::CellIterator(const TerminalSurface *surface, QPoint pos)
|
||||
: CellIterator(surface, pos.x() + (pos.y() * surface->liveSize().width()))
|
||||
{}
|
||||
|
||||
CellIterator::CellIterator(const TerminalSurface *surface, int pos)
|
||||
: m_state(State::INSIDE)
|
||||
, m_surface(surface)
|
||||
{
|
||||
m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
|
||||
m_pos = qMax(0, qMin(m_maxpos + 1, pos));
|
||||
|
||||
if (m_pos == 0) {
|
||||
m_state = State::BEGIN;
|
||||
} else if (m_pos == m_maxpos + 1) {
|
||||
m_state = State::END;
|
||||
}
|
||||
|
||||
if (m_state != State::END)
|
||||
updateChar();
|
||||
}
|
||||
|
||||
CellIterator::CellIterator(const TerminalSurface *surface)
|
||||
: m_state(State::END)
|
||||
, m_surface(surface)
|
||||
{
|
||||
m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
|
||||
m_pos = m_maxpos + 1;
|
||||
}
|
||||
|
||||
QPoint CellIterator::gridPos() const
|
||||
{
|
||||
return m_surface->posToGrid(m_pos);
|
||||
}
|
||||
|
||||
bool CellIterator::updateChar()
|
||||
{
|
||||
QPoint cell = m_surface->posToGrid(m_pos);
|
||||
m_char = m_surface->fetchCharAt(cell.x(), cell.y());
|
||||
return m_char != 0;
|
||||
}
|
||||
|
||||
CellIterator &CellIterator::operator-=(int n)
|
||||
{
|
||||
if (n == 0)
|
||||
return *this;
|
||||
|
||||
if (m_pos - n < 0)
|
||||
throw new std::runtime_error("-= n too big!");
|
||||
|
||||
m_pos -= n;
|
||||
|
||||
while (!updateChar() && m_pos > 0 && m_skipZeros)
|
||||
m_pos--;
|
||||
|
||||
m_state = State::INSIDE;
|
||||
|
||||
if (m_pos == 0) {
|
||||
m_state = State::BEGIN;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CellIterator &CellIterator::operator+=(int n)
|
||||
{
|
||||
if (n == 0)
|
||||
return *this;
|
||||
|
||||
if (m_pos + n < m_maxpos + 1) {
|
||||
m_state = State::INSIDE;
|
||||
m_pos += n;
|
||||
while (!updateChar() && m_pos < (m_maxpos + 1) && m_skipZeros)
|
||||
m_pos++;
|
||||
|
||||
if (m_pos == m_maxpos + 1)
|
||||
m_state = State::END;
|
||||
} else {
|
||||
*this = m_surface->end();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,97 +0,0 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QPoint>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
class TerminalSurface;
|
||||
|
||||
class CellIterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = std::u32string::value_type;
|
||||
using pointer = std::u32string::value_type *;
|
||||
// We need to return copies for std::reverse_iterator to work
|
||||
using reference = std::u32string::value_type;
|
||||
|
||||
enum class State { BEGIN, INSIDE, END } m_state{};
|
||||
|
||||
public:
|
||||
CellIterator(const TerminalSurface *surface, QPoint pos);
|
||||
CellIterator(const TerminalSurface *surface, int pos);
|
||||
CellIterator(const TerminalSurface *surface);
|
||||
|
||||
public:
|
||||
QPoint gridPos() const;
|
||||
|
||||
public:
|
||||
CellIterator &operator-=(int n);
|
||||
CellIterator &operator+=(int n);
|
||||
|
||||
reference operator*() const { return m_char; }
|
||||
pointer operator->() { return &m_char; }
|
||||
|
||||
CellIterator &operator++() { return *this += 1; }
|
||||
CellIterator operator++(int)
|
||||
{
|
||||
CellIterator tmp = *this;
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
CellIterator &operator--() { return *this -= 1; }
|
||||
CellIterator operator--(int)
|
||||
{
|
||||
CellIterator tmp = *this;
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator!=(const CellIterator &other) const
|
||||
{
|
||||
if (other.m_state != m_state)
|
||||
return true;
|
||||
|
||||
if (other.m_pos != m_pos)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator==(const CellIterator &other) const { return !operator!=(other); }
|
||||
|
||||
CellIterator operator-(int n) const
|
||||
{
|
||||
CellIterator result = *this;
|
||||
result -= n;
|
||||
return result;
|
||||
}
|
||||
|
||||
CellIterator operator+(int n) const
|
||||
{
|
||||
CellIterator result = *this;
|
||||
result += n;
|
||||
return result;
|
||||
}
|
||||
|
||||
int position() const { return m_pos; }
|
||||
void setSkipZeros(bool skipZeros) { m_skipZeros = skipZeros; }
|
||||
|
||||
private:
|
||||
bool updateChar();
|
||||
|
||||
const TerminalSurface *m_surface{nullptr};
|
||||
int m_pos{-1};
|
||||
int m_maxpos{-1};
|
||||
bool m_skipZeros{false};
|
||||
mutable std::u32string::value_type m_char;
|
||||
};
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,48 +0,0 @@
|
||||
// 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 "glyphcache.h"
|
||||
|
||||
#include <QTextLayout>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
size_t qHash(const GlyphCacheKey &key, size_t seed = 0)
|
||||
{
|
||||
return qHash(key.font, seed) ^ qHash(key.text, seed);
|
||||
}
|
||||
|
||||
GlyphCache &GlyphCache::instance()
|
||||
{
|
||||
static GlyphCache cache(5000);
|
||||
return cache;
|
||||
}
|
||||
|
||||
const QGlyphRun *GlyphCache::get(const QFont &font, const QString &text)
|
||||
{
|
||||
GlyphCacheKey key{font, text};
|
||||
if (auto *run = object(key))
|
||||
return run;
|
||||
|
||||
QTextLayout layout;
|
||||
|
||||
layout.setText(text);
|
||||
layout.setFont(font);
|
||||
|
||||
layout.beginLayout();
|
||||
layout.createLine().setNumColumns(std::numeric_limits<int>::max());
|
||||
layout.endLayout();
|
||||
|
||||
if (layout.lineCount() > 0) {
|
||||
const auto &line = layout.lineAt(0);
|
||||
const auto runs = line.glyphRuns();
|
||||
if (!runs.isEmpty()) {
|
||||
QGlyphRun *run = new QGlyphRun(layout.lineAt(0).glyphRuns().first());
|
||||
insert(key, run);
|
||||
return run;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,34 +0,0 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QFont>
|
||||
#include <QGlyphRun>
|
||||
#include <QString>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
struct GlyphCacheKey
|
||||
{
|
||||
QFont font;
|
||||
QString text;
|
||||
|
||||
bool operator==(const GlyphCacheKey &other) const
|
||||
{
|
||||
return font == other.font && text == other.text;
|
||||
}
|
||||
};
|
||||
|
||||
class GlyphCache : public QCache<GlyphCacheKey, QGlyphRun>
|
||||
{
|
||||
public:
|
||||
using QCache::QCache;
|
||||
|
||||
static GlyphCache &instance();
|
||||
|
||||
const QGlyphRun *get(const QFont &font, const QString &text);
|
||||
};
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,91 +0,0 @@
|
||||
// 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 <utils/hostosinfo.h>
|
||||
|
||||
#include "keys.h"
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
VTermModifier qtModifierToVTerm(Qt::KeyboardModifiers mod)
|
||||
{
|
||||
int ret = VTERM_MOD_NONE;
|
||||
|
||||
if (mod & Qt::ShiftModifier)
|
||||
ret |= VTERM_MOD_SHIFT;
|
||||
|
||||
if (mod & Qt::AltModifier)
|
||||
ret |= VTERM_MOD_ALT;
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
if (mod & Qt::MetaModifier)
|
||||
ret |= VTERM_MOD_CTRL;
|
||||
#else
|
||||
if (mod & Qt::ControlModifier)
|
||||
ret |= VTERM_MOD_CTRL;
|
||||
#endif
|
||||
|
||||
return static_cast<VTermModifier>(ret);
|
||||
}
|
||||
|
||||
VTermKey qtKeyToVTerm(Qt::Key key, bool keypad)
|
||||
{
|
||||
if (key >= Qt::Key_F1 && key <= Qt::Key_F35)
|
||||
return static_cast<VTermKey>(VTERM_KEY_FUNCTION_0 + key - Qt::Key_F1 + 1);
|
||||
|
||||
switch (key) {
|
||||
case Qt::Key_Return:
|
||||
return VTERM_KEY_ENTER;
|
||||
case Qt::Key_Tab:
|
||||
return VTERM_KEY_TAB;
|
||||
case Qt::Key_Backspace:
|
||||
return VTERM_KEY_BACKSPACE;
|
||||
case Qt::Key_Escape:
|
||||
return VTERM_KEY_ESCAPE;
|
||||
case Qt::Key_Up:
|
||||
return VTERM_KEY_UP;
|
||||
case Qt::Key_Down:
|
||||
return VTERM_KEY_DOWN;
|
||||
case Qt::Key_Left:
|
||||
return VTERM_KEY_LEFT;
|
||||
case Qt::Key_Right:
|
||||
return VTERM_KEY_RIGHT;
|
||||
case Qt::Key_Insert:
|
||||
return VTERM_KEY_INS;
|
||||
case Qt::Key_Delete:
|
||||
return VTERM_KEY_DEL;
|
||||
case Qt::Key_Home:
|
||||
return VTERM_KEY_HOME;
|
||||
case Qt::Key_End:
|
||||
return VTERM_KEY_END;
|
||||
case Qt::Key_PageUp:
|
||||
return VTERM_KEY_PAGEUP;
|
||||
case Qt::Key_PageDown:
|
||||
return VTERM_KEY_PAGEDOWN;
|
||||
case Qt::Key_multiply:
|
||||
return keypad ? VTERM_KEY_KP_MULT : VTERM_KEY_NONE;
|
||||
case Qt::Key_Plus:
|
||||
return keypad ? VTERM_KEY_KP_PLUS : VTERM_KEY_NONE;
|
||||
case Qt::Key_Comma:
|
||||
return keypad ? VTERM_KEY_KP_COMMA : VTERM_KEY_NONE;
|
||||
case Qt::Key_Minus:
|
||||
return keypad ? VTERM_KEY_KP_MINUS : VTERM_KEY_NONE;
|
||||
case Qt::Key_Period:
|
||||
return keypad ? VTERM_KEY_KP_PERIOD : VTERM_KEY_NONE;
|
||||
case Qt::Key_Slash:
|
||||
return keypad ? VTERM_KEY_KP_DIVIDE : VTERM_KEY_NONE;
|
||||
case Qt::Key_Enter: {
|
||||
VTermKey enterKey = VTERM_KEY_KP_ENTER;
|
||||
|
||||
if (Utils::HostOsInfo::isWindowsHost())
|
||||
enterKey = VTERM_KEY_ENTER;
|
||||
|
||||
return keypad ? enterKey : VTERM_KEY_NONE;
|
||||
}
|
||||
case Qt::Key_Equal:
|
||||
return keypad ? VTERM_KEY_KP_EQUAL : VTERM_KEY_NONE;
|
||||
default:
|
||||
return VTERM_KEY_NONE;
|
||||
}
|
||||
}
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,15 +0,0 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vterm_keycodes.h>
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
VTermKey qtKeyToVTerm(Qt::Key key, bool keypad);
|
||||
VTermModifier qtModifierToVTerm(Qt::KeyboardModifiers mod);
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2020, Justin Bronder
|
||||
// Copied and modified from: https://github.com/jsbronder/sff
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include "scrollback.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <future>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
Scrollback::Line::Line(int cols, const VTermScreenCell *cells)
|
||||
: m_cols(cols)
|
||||
, m_cells(std::make_unique<VTermScreenCell[]>(cols))
|
||||
{
|
||||
memcpy(m_cells.get(), cells, cols * sizeof(cells[0]));
|
||||
}
|
||||
|
||||
const VTermScreenCell *Scrollback::Line::cell(int i) const
|
||||
{
|
||||
assert(i >= 0 && i < m_cols);
|
||||
return &m_cells[i];
|
||||
}
|
||||
|
||||
Scrollback::Scrollback(size_t capacity)
|
||||
: m_capacity(capacity)
|
||||
{}
|
||||
|
||||
void Scrollback::emplace(int cols, const VTermScreenCell *cells)
|
||||
{
|
||||
m_deque.emplace_front(cols, cells);
|
||||
while (m_deque.size() > m_capacity) {
|
||||
m_deque.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void Scrollback::popto(int cols, VTermScreenCell *cells)
|
||||
{
|
||||
const Line &sbl = m_deque.front();
|
||||
|
||||
int ncells = cols;
|
||||
if (ncells > sbl.cols())
|
||||
ncells = sbl.cols();
|
||||
|
||||
memcpy(cells, sbl.cells(), sizeof(cells[0]) * ncells);
|
||||
for (size_t i = ncells; i < static_cast<size_t>(cols); ++i) {
|
||||
cells[i].chars[0] = '\0';
|
||||
cells[i].width = 1;
|
||||
cells[i].bg = cells[ncells - 1].bg;
|
||||
}
|
||||
|
||||
m_deque.pop_front();
|
||||
}
|
||||
|
||||
void Scrollback::clear()
|
||||
{
|
||||
m_deque.clear();
|
||||
}
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright (c) 2020, Justin Bronder
|
||||
// Copied and modified from: https://github.com/jsbronder/sff
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vterm.h>
|
||||
|
||||
#include <deque>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
#include <QFont>
|
||||
#include <QTextLayout>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
class Scrollback
|
||||
{
|
||||
public:
|
||||
class Line
|
||||
{
|
||||
public:
|
||||
Line(int cols, const VTermScreenCell *cells);
|
||||
Line(Line &&other) = default;
|
||||
Line() = delete;
|
||||
|
||||
int cols() const { return m_cols; };
|
||||
const VTermScreenCell *cell(int i) const;
|
||||
const VTermScreenCell *cells() const { return &m_cells[0]; };
|
||||
|
||||
private:
|
||||
int m_cols;
|
||||
std::unique_ptr<VTermScreenCell[]> m_cells;
|
||||
};
|
||||
|
||||
public:
|
||||
Scrollback(size_t capacity);
|
||||
Scrollback() = delete;
|
||||
|
||||
int capacity() const { return static_cast<int>(m_capacity); };
|
||||
int size() const { return static_cast<int>(m_deque.size()); };
|
||||
|
||||
const Line &line(size_t index) const { return m_deque.at(index); };
|
||||
const std::deque<Line> &lines() const { return m_deque; };
|
||||
|
||||
void emplace(int cols, const VTermScreenCell *cells);
|
||||
void popto(int cols, VTermScreenCell *cells);
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
size_t m_capacity;
|
||||
std::deque<Line> m_deque;
|
||||
};
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
#include "shellintegration.h"
|
||||
|
||||
#include "terminalsettings.h"
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/stringutils.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_LOGGING_CATEGORY(integrationLog, "qtc.terminal.shellintegration", QtWarningMsg)
|
||||
@@ -74,9 +77,12 @@ bool ShellIntegration::canIntegrate(const Utils::CommandLine &cmdLine)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShellIntegration::onOsc(int cmd, const VTermStringFragment &fragment)
|
||||
void ShellIntegration::onOsc(int cmd, std::string_view str, bool initial, bool final)
|
||||
{
|
||||
QString d = QString::fromLocal8Bit(fragment.str, fragment.len);
|
||||
Q_UNUSED(initial);
|
||||
Q_UNUSED(final);
|
||||
|
||||
QString d = QString::fromLocal8Bit(str);
|
||||
const auto [command, data] = Utils::splitAtFirst(d, ';');
|
||||
|
||||
if (cmd == 1337) {
|
||||
@@ -103,6 +109,17 @@ void ShellIntegration::onOsc(int cmd, const VTermStringFragment &fragment)
|
||||
}
|
||||
}
|
||||
|
||||
void ShellIntegration::onBell()
|
||||
{
|
||||
if (settings().audibleBell.value())
|
||||
QApplication::beep();
|
||||
}
|
||||
|
||||
void ShellIntegration::onTitle(const QString &title)
|
||||
{
|
||||
emit titleChanged(title);
|
||||
}
|
||||
|
||||
void ShellIntegration::prepareProcess(Utils::Process &process)
|
||||
{
|
||||
Environment env = process.environment().hasChanges() ? process.environment()
|
||||
|
||||
@@ -7,25 +7,28 @@
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/process.h>
|
||||
|
||||
#include <vterm.h>
|
||||
#include <solutions/terminal/surfaceintegration.h>
|
||||
|
||||
#include <QTemporaryDir>
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
class ShellIntegration : public QObject
|
||||
class ShellIntegration : public QObject, public TerminalSolution::SurfaceIntegration
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static bool canIntegrate(const Utils::CommandLine &cmdLine);
|
||||
|
||||
void onOsc(int cmd, const VTermStringFragment &fragment);
|
||||
void onOsc(int cmd, std::string_view str, bool initial, bool final) override;
|
||||
void onBell() override;
|
||||
void onTitle(const QString &title) override;
|
||||
|
||||
void prepareProcess(Utils::Process &process);
|
||||
|
||||
signals:
|
||||
void commandChanged(const Utils::CommandLine &command);
|
||||
void currentDirChanged(const QString &dir);
|
||||
void titleChanged(const QString &title);
|
||||
|
||||
private:
|
||||
QTemporaryDir m_tempDir;
|
||||
|
||||
@@ -80,7 +80,8 @@ TerminalPane::TerminalPane(QObject *parent)
|
||||
m_escSettingButton->setToolTip(Tr::tr("Sends Esc to terminal instead of Qt Creator."));
|
||||
} else {
|
||||
m_escSettingButton->setText(shiftEsc);
|
||||
m_escSettingButton->setToolTip(Tr::tr("Press %1 to send Esc to terminal.").arg(shiftEsc));
|
||||
m_escSettingButton->setToolTip(
|
||||
Tr::tr("Press %1 to send Esc to terminal.").arg(shiftEsc));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,8 +162,8 @@ void TerminalPane::openTerminal(const OpenTerminalParameters ¶meters)
|
||||
if (icon.isNull()) {
|
||||
QFileIconProvider iconProvider;
|
||||
const FilePath command = parametersCopy.shellCommand
|
||||
? parametersCopy.shellCommand->executable()
|
||||
: settings().shell();
|
||||
? parametersCopy.shellCommand->executable()
|
||||
: settings().shell();
|
||||
icon = iconProvider.icon(command.toFileInfo());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,18 @@ using namespace std::chrono_literals;
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
using namespace Terminal::Internal;
|
||||
|
||||
constexpr std::chrono::milliseconds debounceInterval = 100ms;
|
||||
|
||||
TerminalSearch::TerminalSearch(TerminalSurface *surface)
|
||||
TerminalSearch::TerminalSearch(TerminalSolution::TerminalSurface *surface)
|
||||
: m_surface(surface)
|
||||
{
|
||||
m_debounceTimer.setInterval(debounceInterval);
|
||||
m_debounceTimer.setSingleShot(true);
|
||||
|
||||
connect(surface, &TerminalSurface::invalidated, this, &TerminalSearch::updateHits);
|
||||
connect(surface,
|
||||
&TerminalSolution::TerminalSurface::invalidated,
|
||||
this,
|
||||
&TerminalSearch::updateHits);
|
||||
connect(&m_debounceTimer, &QTimer::timeout, this, &TerminalSearch::debouncedUpdateHits);
|
||||
}
|
||||
|
||||
@@ -85,9 +86,9 @@ bool isSpace(char32_t a, char32_t b)
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<SearchHit> TerminalSearch::search()
|
||||
QList<TerminalSolution::SearchHit> TerminalSearch::search()
|
||||
{
|
||||
QList<SearchHit> hits;
|
||||
QList<TerminalSolution::SearchHit> hits;
|
||||
|
||||
std::function<bool(char32_t, char32_t)> compare;
|
||||
|
||||
@@ -108,12 +109,12 @@ QList<SearchHit> TerminalSearch::search()
|
||||
searchString.insert(searchString.begin(), std::numeric_limits<char32_t>::max());
|
||||
}
|
||||
|
||||
Internal::CellIterator it = m_surface->begin();
|
||||
TerminalSolution::CellIterator it = m_surface->begin();
|
||||
while (it != m_surface->end()) {
|
||||
it = std::search(it, m_surface->end(), searchString.begin(), searchString.end(), compare);
|
||||
|
||||
if (it != m_surface->end()) {
|
||||
auto hit = SearchHit{it.position(),
|
||||
auto hit = TerminalSolution::SearchHit{it.position(),
|
||||
static_cast<int>(it.position() + searchString.size())};
|
||||
if (m_findFlags.testFlag(FindFlag::FindWholeWords)) {
|
||||
hit.start++;
|
||||
@@ -127,9 +128,9 @@ QList<SearchHit> TerminalSearch::search()
|
||||
return hits;
|
||||
}
|
||||
|
||||
QList<SearchHit> TerminalSearch::searchRegex()
|
||||
QList<TerminalSolution::SearchHit> TerminalSearch::searchRegex()
|
||||
{
|
||||
QList<SearchHit> hits;
|
||||
QList<TerminalSolution::SearchHit> hits;
|
||||
|
||||
QString allText;
|
||||
allText.reserve(1000);
|
||||
@@ -170,7 +171,7 @@ QList<SearchHit> TerminalSearch::searchRegex()
|
||||
}
|
||||
e -= adjust;
|
||||
}
|
||||
hits << SearchHit{s, e};
|
||||
hits << TerminalSolution::SearchHit{s, e};
|
||||
}
|
||||
|
||||
return hits;
|
||||
@@ -185,7 +186,7 @@ void TerminalSearch::debouncedUpdateHits()
|
||||
|
||||
const bool regex = m_findFlags.testFlag(FindFlag::FindRegularExpression);
|
||||
|
||||
QList<SearchHit> hits = regex ? searchRegex() : search();
|
||||
QList<TerminalSolution::SearchHit> hits = regex ? searchRegex() : search();
|
||||
|
||||
if (hits != m_hits) {
|
||||
m_currentHit = -1;
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "terminalsurface.h"
|
||||
#include <terminal/terminalsurface.h>
|
||||
|
||||
#include <solutions/terminal/terminalview.h>
|
||||
|
||||
#include <coreplugin/find/ifindsupport.h>
|
||||
#include <coreplugin/find/textfindconstants.h>
|
||||
@@ -12,19 +14,7 @@
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
struct SearchHit
|
||||
{
|
||||
int start{-1};
|
||||
int end{-1};
|
||||
|
||||
bool operator!=(const SearchHit &other) const
|
||||
{
|
||||
return start != other.start || end != other.end;
|
||||
}
|
||||
bool operator==(const SearchHit &other) const { return !operator!=(other); }
|
||||
};
|
||||
|
||||
struct SearchHitWithText : SearchHit
|
||||
struct SearchHitWithText : TerminalSolution::SearchHit
|
||||
{
|
||||
QString text;
|
||||
};
|
||||
@@ -33,17 +23,17 @@ class TerminalSearch : public Core::IFindSupport
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TerminalSearch(Internal::TerminalSurface *surface);
|
||||
TerminalSearch(TerminalSolution::TerminalSurface *surface);
|
||||
|
||||
void setCurrentSelection(std::optional<SearchHitWithText> selection);
|
||||
void setSearchString(const QString &searchString, Utils::FindFlags findFlags);
|
||||
void nextHit();
|
||||
void previousHit();
|
||||
|
||||
const QList<SearchHit> &hits() const { return m_hits; }
|
||||
SearchHit currentHit() const
|
||||
const QList<TerminalSolution::SearchHit> &hits() const { return m_hits; }
|
||||
TerminalSolution::SearchHit currentHit() const
|
||||
{
|
||||
return m_currentHit >= 0 ? m_hits.at(m_currentHit) : SearchHit{};
|
||||
return m_currentHit >= 0 ? m_hits.at(m_currentHit) : TerminalSolution::SearchHit{};
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -65,17 +55,17 @@ signals:
|
||||
protected:
|
||||
void updateHits();
|
||||
void debouncedUpdateHits();
|
||||
QList<SearchHit> search();
|
||||
QList<SearchHit> searchRegex();
|
||||
QList<TerminalSolution::SearchHit> search();
|
||||
QList<TerminalSolution::SearchHit> searchRegex();
|
||||
|
||||
private:
|
||||
std::optional<SearchHitWithText> m_currentSelection;
|
||||
QString m_currentSearchString;
|
||||
Utils::FindFlags m_findFlags;
|
||||
Internal::TerminalSurface *m_surface;
|
||||
TerminalSolution::TerminalSurface *m_surface;
|
||||
|
||||
int m_currentHit{-1};
|
||||
QList<SearchHit> m_hits;
|
||||
QList<TerminalSolution::SearchHit> m_hits;
|
||||
QTimer m_debounceTimer;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
|
||||
#include <libptyqt/ptyqt.h>
|
||||
|
||||
#include <utils/dropsupport.h>
|
||||
#include <utils/environment.h>
|
||||
#include <utils/expected.h>
|
||||
@@ -72,7 +70,6 @@ void setupColor(TerminalSettings *settings,
|
||||
color.setSettingsKey(label);
|
||||
color.setDefaultValue(defaultColor);
|
||||
color.setToolTip(Tr::tr("The color used for %1.").arg(label));
|
||||
|
||||
settings->registerAspect(&color);
|
||||
}
|
||||
|
||||
@@ -479,7 +476,7 @@ TerminalSettings::TerminalSettings()
|
||||
fontComboBox->setCurrentFont(font());
|
||||
|
||||
connect(fontComboBox, &QFontComboBox::currentFontChanged, this, [this](const QFont &f) {
|
||||
font.setValue(f.family());
|
||||
font.setVolatileValue(f.family());
|
||||
});
|
||||
|
||||
auto loadThemeButton = new QPushButton(Tr::tr("Load Theme..."));
|
||||
|
||||
@@ -1,571 +0,0 @@
|
||||
// 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 "terminalsurface.h"
|
||||
|
||||
#include "keys.h"
|
||||
#include "scrollback.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <vterm.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
Q_LOGGING_CATEGORY(log, "qtc.terminal.surface", QtWarningMsg);
|
||||
|
||||
QColor toQColor(const VTermColor &c)
|
||||
{
|
||||
return QColor(qRgb(c.rgb.red, c.rgb.green, c.rgb.blue));
|
||||
};
|
||||
|
||||
struct TerminalSurfacePrivate
|
||||
{
|
||||
TerminalSurfacePrivate(TerminalSurface *surface,
|
||||
const QSize &initialGridSize,
|
||||
ShellIntegration *shellIntegration)
|
||||
: m_vterm(vterm_new(initialGridSize.height(), initialGridSize.width()), vterm_free)
|
||||
, m_vtermScreen(vterm_obtain_screen(m_vterm.get()))
|
||||
, m_scrollback(std::make_unique<Internal::Scrollback>(5000))
|
||||
, m_shellIntegration(shellIntegration)
|
||||
, q(surface)
|
||||
{}
|
||||
|
||||
void init()
|
||||
{
|
||||
vterm_set_utf8(m_vterm.get(), true);
|
||||
|
||||
static auto writeToPty = [](const char *s, size_t len, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
emit p->q->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<TerminalSurfacePrivate *>(user);
|
||||
p->invalidate(rect);
|
||||
return 1;
|
||||
};
|
||||
m_vtermScreenCallbacks.sb_pushline = [](int cols, const VTermScreenCell *cells, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->sb_pushline(cols, cells);
|
||||
};
|
||||
m_vtermScreenCallbacks.sb_popline = [](int cols, VTermScreenCell *cells, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->sb_popline(cols, cells);
|
||||
};
|
||||
m_vtermScreenCallbacks.settermprop = [](VTermProp prop, VTermValue *val, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->setTerminalProperties(prop, val);
|
||||
};
|
||||
m_vtermScreenCallbacks.movecursor =
|
||||
[](VTermPos pos, VTermPos oldpos, int visible, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->movecursor(pos, oldpos, visible);
|
||||
};
|
||||
m_vtermScreenCallbacks.sb_clear = [](void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->sb_clear();
|
||||
};
|
||||
m_vtermScreenCallbacks.bell = [](void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
emit p->q->bell();
|
||||
return 1;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
memset(&m_vtermStateFallbacks, 0, sizeof(m_vtermStateFallbacks));
|
||||
|
||||
m_vtermStateFallbacks.osc = [](int cmd, VTermStringFragment fragment, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->osc(cmd, fragment);
|
||||
};
|
||||
|
||||
VTermState *vts = vterm_obtain_state(m_vterm.get());
|
||||
vterm_state_set_unrecognised_fallbacks(vts, &m_vtermStateFallbacks, this);
|
||||
vterm_state_set_bold_highbright(vts, true);
|
||||
|
||||
VTermColor fg;
|
||||
VTermColor bg;
|
||||
vterm_color_indexed(&fg, ColorIndex::Foreground);
|
||||
vterm_color_indexed(&bg, ColorIndex::Background);
|
||||
vterm_state_set_default_colors(vts, &fg, &bg);
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
VTermColor col;
|
||||
vterm_color_indexed(&col, i);
|
||||
vterm_state_set_palette_color(vts, i, &col);
|
||||
}
|
||||
|
||||
vterm_screen_reset(m_vtermScreen, 1);
|
||||
}
|
||||
|
||||
QSize liveSize() const
|
||||
{
|
||||
int rows;
|
||||
int cols;
|
||||
vterm_get_size(m_vterm.get(), &rows, &cols);
|
||||
|
||||
return QSize(cols, rows);
|
||||
}
|
||||
|
||||
std::variant<int, QColor> toVariantColor(const VTermColor &color)
|
||||
{
|
||||
if (color.type & VTERM_COLOR_DEFAULT_BG)
|
||||
return ColorIndex::Background;
|
||||
else if (color.type & VTERM_COLOR_DEFAULT_FG)
|
||||
return ColorIndex::Foreground;
|
||||
else if (color.type & VTERM_COLOR_INDEXED) {
|
||||
if (color.indexed.idx >= 16) {
|
||||
VTermColor c = color;
|
||||
vterm_state_convert_color_to_rgb(vterm_obtain_state(m_vterm.get()), &c);
|
||||
return toQColor(c);
|
||||
}
|
||||
return color.indexed.idx;
|
||||
} else if (color.type == VTERM_COLOR_RGB)
|
||||
return toQColor(color);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
TerminalCell toCell(const VTermScreenCell &cell)
|
||||
{
|
||||
TerminalCell result;
|
||||
result.width = cell.width;
|
||||
result.text = QString::fromUcs4(cell.chars);
|
||||
|
||||
const VTermColor *bg = &cell.bg;
|
||||
const VTermColor *fg = &cell.fg;
|
||||
|
||||
if (static_cast<bool>(cell.attrs.reverse))
|
||||
std::swap(fg, bg);
|
||||
|
||||
result.backgroundColor = toVariantColor(*bg);
|
||||
result.foregroundColor = toVariantColor(*fg);
|
||||
|
||||
result.bold = cell.attrs.bold;
|
||||
result.strikeOut = cell.attrs.strike;
|
||||
|
||||
if (cell.attrs.underline > 0) {
|
||||
result.underlineStyle = QTextCharFormat::NoUnderline;
|
||||
switch (cell.attrs.underline) {
|
||||
case VTERM_UNDERLINE_SINGLE:
|
||||
result.underlineStyle = QTextCharFormat::SingleUnderline;
|
||||
break;
|
||||
case VTERM_UNDERLINE_DOUBLE:
|
||||
// TODO: Double underline
|
||||
result.underlineStyle = QTextCharFormat::SingleUnderline;
|
||||
break;
|
||||
case VTERM_UNDERLINE_CURLY:
|
||||
result.underlineStyle = QTextCharFormat::WaveUnderline;
|
||||
break;
|
||||
case VTERM_UNDERLINE_DASHED:
|
||||
result.underlineStyle = QTextCharFormat::DashUnderline;
|
||||
break;
|
||||
case VTERM_UNDERLINE_DOTTED:
|
||||
result.underlineStyle = QTextCharFormat::DotLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.strikeOut = cell.attrs.strike;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Callbacks from vterm
|
||||
void invalidate(VTermRect rect)
|
||||
{
|
||||
if (!m_altscreen) {
|
||||
rect.start_row += m_scrollback->size();
|
||||
rect.end_row += m_scrollback->size();
|
||||
}
|
||||
|
||||
emit q->invalidated(
|
||||
QRect{QPoint{rect.start_col, rect.start_row}, QPoint{rect.end_col, rect.end_row - 1}});
|
||||
}
|
||||
|
||||
int sb_pushline(int cols, const VTermScreenCell *cells)
|
||||
{
|
||||
m_scrollback->emplace(cols, cells);
|
||||
emit q->fullSizeChanged(q->fullSize());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sb_popline(int cols, VTermScreenCell *cells)
|
||||
{
|
||||
if (m_scrollback->size() == 0)
|
||||
return 0;
|
||||
|
||||
m_scrollback->popto(cols, cells);
|
||||
emit q->fullSizeChanged(q->fullSize());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sb_clear()
|
||||
{
|
||||
m_scrollback->clear();
|
||||
emit q->fullSizeChanged(q->fullSize());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int osc(int cmd, const VTermStringFragment &fragment)
|
||||
{
|
||||
if (m_shellIntegration)
|
||||
m_shellIntegration->onOsc(cmd, fragment);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setTerminalProperties(VTermProp prop, VTermValue *val)
|
||||
{
|
||||
switch (prop) {
|
||||
case VTERM_PROP_CURSORVISIBLE: {
|
||||
Cursor old = q->cursor();
|
||||
m_cursor.visible = val->boolean;
|
||||
q->cursorChanged(old, q->cursor());
|
||||
break;
|
||||
}
|
||||
case VTERM_PROP_CURSORBLINK: {
|
||||
Cursor old = q->cursor();
|
||||
m_cursor.blink = val->boolean;
|
||||
emit q->cursorChanged(old, q->cursor());
|
||||
break;
|
||||
}
|
||||
case VTERM_PROP_CURSORSHAPE: {
|
||||
Cursor old = q->cursor();
|
||||
m_cursor.shape = (Cursor::Shape) val->number;
|
||||
emit q->cursorChanged(old, q->cursor());
|
||||
break;
|
||||
}
|
||||
case VTERM_PROP_ICONNAME:
|
||||
break;
|
||||
case VTERM_PROP_TITLE:
|
||||
emit q->titleChanged(QString::fromUtf8(val->string.str, val->string.len));
|
||||
break;
|
||||
case VTERM_PROP_ALTSCREEN:
|
||||
m_altscreen = val->boolean;
|
||||
emit q->altscreenChanged(m_altscreen);
|
||||
break;
|
||||
case VTERM_PROP_MOUSE:
|
||||
qCDebug(log) << "Ignoring VTERM_PROP_MOUSE" << val->number;
|
||||
break;
|
||||
case VTERM_PROP_REVERSE:
|
||||
qCDebug(log) << "Ignoring VTERM_PROP_REVERSE" << val->boolean;
|
||||
break;
|
||||
case VTERM_N_PROPS:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
int movecursor(VTermPos pos, VTermPos oldpos, int visible)
|
||||
{
|
||||
Q_UNUSED(oldpos);
|
||||
Cursor oldCursor = q->cursor();
|
||||
m_cursor.position = {pos.col, pos.row};
|
||||
m_cursor.visible = visible > 0;
|
||||
q->cursorChanged(oldCursor, q->cursor());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const VTermScreenCell *cellAt(int x, int y)
|
||||
{
|
||||
QTC_ASSERT(y >= 0 && x >= 0, return nullptr);
|
||||
QTC_ASSERT(y < q->fullSize().height() && x < liveSize().width(), return nullptr);
|
||||
|
||||
if (!m_altscreen && y < m_scrollback->size()) {
|
||||
const auto &sbl = m_scrollback->line((m_scrollback->size() - 1) - y);
|
||||
if (x < sbl.cols()) {
|
||||
return sbl.cell(x);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_altscreen)
|
||||
y -= m_scrollback->size();
|
||||
|
||||
static VTermScreenCell refCell{};
|
||||
VTermPos vtp{y, x};
|
||||
vterm_screen_get_cell(m_vtermScreen, vtp, &refCell);
|
||||
|
||||
return &refCell;
|
||||
}
|
||||
|
||||
std::unique_ptr<VTerm, void (*)(VTerm *)> m_vterm;
|
||||
VTermScreen *m_vtermScreen;
|
||||
VTermScreenCallbacks m_vtermScreenCallbacks;
|
||||
VTermStateFallbacks m_vtermStateFallbacks;
|
||||
|
||||
Cursor m_cursor;
|
||||
QString m_currentCommand;
|
||||
|
||||
bool m_altscreen{false};
|
||||
|
||||
std::unique_ptr<Internal::Scrollback> m_scrollback;
|
||||
|
||||
ShellIntegration *m_shellIntegration{nullptr};
|
||||
|
||||
TerminalSurface *q;
|
||||
};
|
||||
|
||||
TerminalSurface::TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration)
|
||||
: d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize, shellIntegration))
|
||||
{
|
||||
d->init();
|
||||
}
|
||||
|
||||
TerminalSurface::~TerminalSurface() = default;
|
||||
|
||||
int TerminalSurface::cellWidthAt(int x, int y) const
|
||||
{
|
||||
const VTermScreenCell *cell = d->cellAt(x, y);
|
||||
if (!cell)
|
||||
return 0;
|
||||
return cell->width;
|
||||
}
|
||||
|
||||
QSize TerminalSurface::liveSize() const
|
||||
{
|
||||
return d->liveSize();
|
||||
}
|
||||
|
||||
QSize TerminalSurface::fullSize() const
|
||||
{
|
||||
if (d->m_altscreen)
|
||||
return liveSize();
|
||||
return QSize{d->liveSize().width(), d->liveSize().height() + d->m_scrollback->size()};
|
||||
}
|
||||
|
||||
std::u32string::value_type TerminalSurface::fetchCharAt(int x, int y) const
|
||||
{
|
||||
const VTermScreenCell *cell = d->cellAt(x, y);
|
||||
if (!cell)
|
||||
return 0;
|
||||
|
||||
if (cell->width == 0)
|
||||
return 0;
|
||||
|
||||
QString s = QString::fromUcs4(cell->chars, 6).normalized(QString::NormalizationForm_C);
|
||||
const QList<uint> ucs4 = s.toUcs4();
|
||||
return std::u32string(ucs4.begin(), ucs4.end()).front();
|
||||
}
|
||||
|
||||
TerminalCell TerminalSurface::fetchCell(int x, int y) const
|
||||
{
|
||||
static TerminalCell emptyCell{1,
|
||||
{},
|
||||
{},
|
||||
false,
|
||||
ColorIndex::Foreground,
|
||||
ColorIndex::Background,
|
||||
QTextCharFormat::NoUnderline,
|
||||
false};
|
||||
|
||||
QTC_ASSERT(y >= 0, return emptyCell);
|
||||
QTC_ASSERT(y < fullSize().height() && x < fullSize().width(), return emptyCell);
|
||||
|
||||
const VTermScreenCell *refCell = d->cellAt(x, y);
|
||||
if (!refCell)
|
||||
return emptyCell;
|
||||
|
||||
return d->toCell(*refCell);
|
||||
}
|
||||
|
||||
void TerminalSurface::clearAll()
|
||||
{
|
||||
// Fake a scrollback clearing
|
||||
QByteArray data{"\x1b[3J"};
|
||||
vterm_input_write(d->m_vterm.get(), data.constData(), data.size());
|
||||
|
||||
// Send Ctrl+L which will clear the screen
|
||||
emit writeToPty(QByteArray("\f"));
|
||||
}
|
||||
|
||||
void TerminalSurface::resize(QSize newSize)
|
||||
{
|
||||
vterm_set_size(d->m_vterm.get(), newSize.height(), newSize.width());
|
||||
}
|
||||
|
||||
QPoint TerminalSurface::posToGrid(int pos) const
|
||||
{
|
||||
return {pos % d->liveSize().width(), pos / d->liveSize().width()};
|
||||
}
|
||||
int TerminalSurface::gridToPos(QPoint gridPos) const
|
||||
{
|
||||
return gridPos.y() * d->liveSize().width() + gridPos.x();
|
||||
}
|
||||
|
||||
void TerminalSurface::dataFromPty(const QByteArray &data)
|
||||
{
|
||||
vterm_input_write(d->m_vterm.get(), data.constData(), data.size());
|
||||
vterm_screen_flush_damage(d->m_vtermScreen);
|
||||
}
|
||||
|
||||
void TerminalSurface::flush()
|
||||
{
|
||||
vterm_screen_flush_damage(d->m_vtermScreen);
|
||||
}
|
||||
|
||||
void TerminalSurface::pasteFromClipboard(const QString &clipboardText)
|
||||
{
|
||||
if (clipboardText.isEmpty())
|
||||
return;
|
||||
|
||||
vterm_keyboard_start_paste(d->m_vterm.get());
|
||||
for (unsigned int ch : clipboardText.toUcs4()) {
|
||||
// Workaround for weird nano behavior to correctly paste newlines
|
||||
// see: http://savannah.gnu.org/bugs/?49176
|
||||
// and: https://github.com/kovidgoyal/kitty/issues/994
|
||||
if (ch == '\n')
|
||||
ch = '\r';
|
||||
vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE);
|
||||
}
|
||||
vterm_keyboard_end_paste(d->m_vterm.get());
|
||||
|
||||
if (!d->m_altscreen) {
|
||||
emit unscroll();
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSurface::sendKey(Qt::Key key)
|
||||
{
|
||||
if (key == Qt::Key_Escape)
|
||||
vterm_keyboard_key(d->m_vterm.get(), VTERM_KEY_ESCAPE, VTERM_MOD_NONE);
|
||||
}
|
||||
|
||||
void TerminalSurface::sendKey(const QString &text)
|
||||
{
|
||||
for (const unsigned int ch : text.toUcs4())
|
||||
vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE);
|
||||
}
|
||||
|
||||
void TerminalSurface::sendKey(QKeyEvent *event)
|
||||
{
|
||||
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(d->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(d->m_vterm.get(), event->text().toUcs4()[0], VTERM_MOD_NONE);
|
||||
} else if (mod == VTERM_MOD_CTRL && event->key() >= Qt::Key_A && event->key() < Qt::Key_Z) {
|
||||
vterm_keyboard_unichar(d->m_vterm.get(), 'a' + (event->key() - Qt::Key_A), mod);
|
||||
}
|
||||
}
|
||||
|
||||
Cursor TerminalSurface::cursor() const
|
||||
{
|
||||
Cursor cursor = d->m_cursor;
|
||||
if (!d->m_altscreen)
|
||||
cursor.position.setY(cursor.position.y() + d->m_scrollback->size());
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
ShellIntegration *TerminalSurface::shellIntegration() const
|
||||
{
|
||||
return d->m_shellIntegration;
|
||||
}
|
||||
|
||||
void TerminalSurface::mouseMove(QPoint pos, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
vterm_mouse_move(d->m_vterm.get(), pos.y(), pos.x(), Internal::qtModifierToVTerm(modifiers));
|
||||
}
|
||||
|
||||
void TerminalSurface::mouseButton(Qt::MouseButton button,
|
||||
bool pressed,
|
||||
Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
int btnIdx = 0;
|
||||
switch (button) {
|
||||
case Qt::LeftButton:
|
||||
btnIdx = 1;
|
||||
break;
|
||||
case Qt::RightButton:
|
||||
btnIdx = 3;
|
||||
break;
|
||||
case Qt::MiddleButton:
|
||||
btnIdx = 2;
|
||||
break;
|
||||
case Qt::ExtraButton1:
|
||||
btnIdx = 4;
|
||||
break;
|
||||
case Qt::ExtraButton2:
|
||||
btnIdx = 5;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
vterm_mouse_button(d->m_vterm.get(), btnIdx, pressed, Internal::qtModifierToVTerm(modifiers));
|
||||
}
|
||||
|
||||
CellIterator TerminalSurface::begin() const
|
||||
{
|
||||
auto res = CellIterator(this, {0, 0});
|
||||
res.m_state = CellIterator::State::BEGIN;
|
||||
return res;
|
||||
}
|
||||
|
||||
CellIterator TerminalSurface::end() const
|
||||
{
|
||||
return CellIterator(this);
|
||||
}
|
||||
|
||||
std::reverse_iterator<CellIterator> TerminalSurface::rbegin() const
|
||||
{
|
||||
return std::make_reverse_iterator(end());
|
||||
}
|
||||
|
||||
std::reverse_iterator<CellIterator> TerminalSurface::rend() const
|
||||
{
|
||||
return std::make_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
CellIterator TerminalSurface::iteratorAt(QPoint pos) const
|
||||
{
|
||||
return CellIterator(this, pos);
|
||||
}
|
||||
CellIterator TerminalSurface::iteratorAt(int pos) const
|
||||
{
|
||||
return CellIterator(this, pos);
|
||||
}
|
||||
|
||||
std::reverse_iterator<CellIterator> TerminalSurface::rIteratorAt(QPoint pos) const
|
||||
{
|
||||
return std::make_reverse_iterator(iteratorAt(pos));
|
||||
}
|
||||
|
||||
std::reverse_iterator<CellIterator> TerminalSurface::rIteratorAt(int pos) const
|
||||
{
|
||||
return std::make_reverse_iterator(iteratorAt(pos));
|
||||
}
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
@@ -1,114 +0,0 @@
|
||||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "celliterator.h"
|
||||
#include "shellintegration.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QSize>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
class Scrollback;
|
||||
|
||||
struct TerminalSurfacePrivate;
|
||||
|
||||
enum ColorIndex { Foreground = 16, Background = 17 };
|
||||
|
||||
struct TerminalCell
|
||||
{
|
||||
int width;
|
||||
QString text;
|
||||
bool bold{false};
|
||||
bool italic{false};
|
||||
std::variant<int, QColor> foregroundColor;
|
||||
std::variant<int, QColor> backgroundColor;
|
||||
QTextCharFormat::UnderlineStyle underlineStyle{QTextCharFormat::NoUnderline};
|
||||
bool strikeOut{false};
|
||||
};
|
||||
|
||||
struct Cursor
|
||||
{
|
||||
enum class Shape {
|
||||
Block = 1,
|
||||
Underline,
|
||||
LeftBar,
|
||||
};
|
||||
QPoint position;
|
||||
bool visible;
|
||||
Shape shape;
|
||||
bool blink{false};
|
||||
};
|
||||
|
||||
class TerminalSurface : public QObject
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration);
|
||||
~TerminalSurface();
|
||||
|
||||
public:
|
||||
CellIterator begin() const;
|
||||
CellIterator end() const;
|
||||
std::reverse_iterator<CellIterator> rbegin() const;
|
||||
std::reverse_iterator<CellIterator> rend() const;
|
||||
|
||||
CellIterator iteratorAt(QPoint pos) const;
|
||||
CellIterator iteratorAt(int pos) const;
|
||||
|
||||
std::reverse_iterator<CellIterator> rIteratorAt(QPoint pos) const;
|
||||
std::reverse_iterator<CellIterator> rIteratorAt(int pos) const;
|
||||
|
||||
public:
|
||||
void clearAll();
|
||||
|
||||
void resize(QSize newSize);
|
||||
|
||||
TerminalCell fetchCell(int x, int y) const;
|
||||
std::u32string::value_type fetchCharAt(int x, int y) const;
|
||||
int cellWidthAt(int x, int y) const;
|
||||
|
||||
QSize liveSize() const;
|
||||
QSize fullSize() const;
|
||||
|
||||
QPoint posToGrid(int pos) const;
|
||||
int gridToPos(QPoint gridPos) const;
|
||||
|
||||
void dataFromPty(const QByteArray &data);
|
||||
void flush();
|
||||
|
||||
void pasteFromClipboard(const QString &text);
|
||||
|
||||
void sendKey(Qt::Key key);
|
||||
void sendKey(QKeyEvent *event);
|
||||
void sendKey(const QString &text);
|
||||
|
||||
int invertedScrollOffset() const;
|
||||
|
||||
Cursor cursor() const;
|
||||
|
||||
ShellIntegration *shellIntegration() const;
|
||||
|
||||
void mouseMove(QPoint pos, Qt::KeyboardModifiers modifiers);
|
||||
void mouseButton(Qt::MouseButton button, bool pressed, Qt::KeyboardModifiers modifiers);
|
||||
signals:
|
||||
void writeToPty(const QByteArray &data);
|
||||
void invalidated(QRect grid);
|
||||
void fullSizeChanged(QSize newSize);
|
||||
void cursorChanged(Cursor oldCursor, Cursor newCursor);
|
||||
void altscreenChanged(bool altScreen);
|
||||
void unscroll();
|
||||
void bell();
|
||||
void titleChanged(const QString &title);
|
||||
|
||||
private:
|
||||
std::unique_ptr<TerminalSurfacePrivate> d;
|
||||
};
|
||||
|
||||
} // namespace Terminal::Internal
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,83 +3,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shellintegration.h"
|
||||
#include "shortcutmap.h"
|
||||
#include "terminalsearch.h"
|
||||
#include "terminalsurface.h"
|
||||
|
||||
#include <solutions/terminal/terminalview.h>
|
||||
|
||||
#include <aggregation/aggregate.h>
|
||||
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
|
||||
#include <utils/link.h>
|
||||
#include <utils/process.h>
|
||||
#include <utils/terminalhooks.h>
|
||||
|
||||
#include <QAbstractScrollArea>
|
||||
#include <QAction>
|
||||
#include <QTextLayout>
|
||||
#include <QTimer>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
using RegisteredAction = std::unique_ptr<QAction, std::function<void(QAction *)>>;
|
||||
|
||||
class TerminalWidget : public QAbstractScrollArea
|
||||
class TerminalWidget : public TerminalSolution::TerminalView
|
||||
{
|
||||
friend class CellIterator;
|
||||
Q_OBJECT
|
||||
public:
|
||||
TerminalWidget(QWidget *parent = nullptr,
|
||||
const Utils::Terminal::OpenTerminalParameters &openParameters = {});
|
||||
|
||||
void setFont(const QFont &font);
|
||||
|
||||
void copyToClipboard();
|
||||
void pasteFromClipboard();
|
||||
void copyLinkToClipboard();
|
||||
|
||||
void clearSelection();
|
||||
|
||||
void zoomIn();
|
||||
void zoomOut();
|
||||
|
||||
void moveCursorWordLeft();
|
||||
void moveCursorWordRight();
|
||||
|
||||
void clearContents();
|
||||
|
||||
void closeTerminal();
|
||||
|
||||
TerminalSearch *search() { return m_search.get(); }
|
||||
|
||||
struct Selection
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
bool final{false};
|
||||
|
||||
bool operator!=(const Selection &other) const
|
||||
{
|
||||
return start != other.start || end != other.end || final != other.final;
|
||||
}
|
||||
|
||||
bool operator==(const Selection &other) const { return !operator!=(other); }
|
||||
};
|
||||
|
||||
struct LinkSelection : public Selection
|
||||
{
|
||||
Utils::Link link;
|
||||
|
||||
bool operator!=(const LinkSelection &other) const
|
||||
{
|
||||
return link != other.link || Selection::operator!=(other);
|
||||
}
|
||||
};
|
||||
|
||||
void setShellName(const QString &shellName);
|
||||
QString shellName() const;
|
||||
QString title() const;
|
||||
@@ -102,142 +55,52 @@ signals:
|
||||
void titleChanged();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void keyReleaseEvent(QKeyEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
void focusInEvent(QFocusEvent *event) override;
|
||||
void focusOutEvent(QFocusEvent *event) override;
|
||||
void inputMethodEvent(QInputMethodEvent *event) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
void focusInEvent(QFocusEvent *event) override;
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
protected:
|
||||
void onReadyRead(bool forceFlush);
|
||||
void setupSurface();
|
||||
void setupFont();
|
||||
void setupPty();
|
||||
void setupColors();
|
||||
void setupActions();
|
||||
|
||||
void writeToPty(const QByteArray &data);
|
||||
void handleEscKey(QKeyEvent *event);
|
||||
|
||||
int paintCell(QPainter &p,
|
||||
const QRectF &cellRect,
|
||||
QPoint gridPos,
|
||||
const Internal::TerminalCell &cell,
|
||||
QFont &f,
|
||||
QList<SearchHit>::const_iterator &searchIt) const;
|
||||
void paintCells(QPainter &painter, QPaintEvent *event) const;
|
||||
void paintCursor(QPainter &painter) const;
|
||||
void paintPreedit(QPainter &painter) const;
|
||||
bool paintFindMatches(QPainter &painter,
|
||||
QList<SearchHit>::const_iterator &searchIt,
|
||||
const QRectF &cellRect,
|
||||
const QPoint gridPos) const;
|
||||
bool paintSelection(QPainter &painter, const QRectF &cellRect, const QPoint gridPos) const;
|
||||
void paintDebugSelection(QPainter &painter, const Selection &selection) const;
|
||||
void surfaceChanged() override;
|
||||
|
||||
qreal topMargin() const;
|
||||
void selectionChanged(const std::optional<Selection> &newSelection) override;
|
||||
void linkActivated(const Link &link) override;
|
||||
void contextMenuRequested(const QPoint &pos) override;
|
||||
|
||||
QPoint viewportToGlobal(QPoint p) const;
|
||||
QPoint globalToViewport(QPoint p) const;
|
||||
QPoint globalToGrid(QPointF p) const;
|
||||
QPointF gridToGlobal(QPoint p, bool bottom = false, bool right = false) const;
|
||||
QRect gridToViewport(QRect rect) const;
|
||||
QPoint toGridPos(QMouseEvent *event) const;
|
||||
void writeToPty(const QByteArray &data) override;
|
||||
void resizePty(QSize newSize) override;
|
||||
void setClipboard(const QString &text) override;
|
||||
std::optional<TerminalView::Link> toLink(const QString &text) override;
|
||||
|
||||
void updateViewport();
|
||||
void updateViewportRect(const QRect &rect);
|
||||
|
||||
int textLineFromPixel(int y) const;
|
||||
std::optional<int> textPosFromPoint(const QTextLayout &textLayout, QPoint p) const;
|
||||
|
||||
std::optional<QTextLayout::FormatRange> selectionToFormatRange(
|
||||
TerminalWidget::Selection selection, const QTextLayout &layout, int rowOffset) const;
|
||||
|
||||
bool checkLinkAt(const QPoint &pos);
|
||||
|
||||
struct TextAndOffsets
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
std::u32string text;
|
||||
};
|
||||
|
||||
TextAndOffsets textAt(const QPoint &pos) const;
|
||||
|
||||
void applySizeChange();
|
||||
void updateScrollBars();
|
||||
|
||||
void flushVTerm(bool force);
|
||||
|
||||
bool setSelection(const std::optional<Selection> &selection, bool scroll = true);
|
||||
QString textFromSelection() const;
|
||||
|
||||
void configBlinkTimer();
|
||||
|
||||
QColor toQColor(std::variant<int, QColor> color) const;
|
||||
|
||||
void updateCopyState();
|
||||
const QList<TerminalSolution::SearchHit> &searchHits() const override;
|
||||
|
||||
RegisteredAction registerAction(Utils::Id commandId, const Core::Context &context);
|
||||
void registerShortcut(Core::Command *command);
|
||||
|
||||
void updateCopyState();
|
||||
|
||||
private:
|
||||
Core::Context m_context;
|
||||
std::unique_ptr<Utils::Process> m_process;
|
||||
std::unique_ptr<Internal::TerminalSurface> m_surface;
|
||||
std::unique_ptr<ShellIntegration> m_shellIntegration;
|
||||
|
||||
QString m_shellName;
|
||||
Utils::Id m_identifier;
|
||||
|
||||
QFont m_font;
|
||||
QSizeF m_cellSize;
|
||||
|
||||
bool m_ignoreScroll{false};
|
||||
|
||||
QString m_preEditString;
|
||||
QString m_title;
|
||||
|
||||
std::optional<Selection> m_selection;
|
||||
std::optional<LinkSelection> m_linkSelection;
|
||||
TerminalSolution::SearchHit m_lastSelectedHit{};
|
||||
|
||||
struct
|
||||
{
|
||||
QPoint start;
|
||||
QPoint end;
|
||||
} m_activeMouseSelect;
|
||||
|
||||
QTimer m_flushDelayTimer;
|
||||
|
||||
QTimer m_scrollTimer;
|
||||
int m_scrollDirection{0};
|
||||
|
||||
std::array<QColor, 20> m_currentColors;
|
||||
Utils::Id m_identifier;
|
||||
|
||||
Utils::Terminal::OpenTerminalParameters m_openParameters;
|
||||
|
||||
std::chrono::system_clock::time_point m_lastFlush;
|
||||
std::chrono::system_clock::time_point m_lastDoubleClick;
|
||||
bool m_selectLineMode{false};
|
||||
|
||||
Internal::Cursor m_cursor;
|
||||
QTimer m_cursorBlinkTimer;
|
||||
bool m_cursorBlinkState{true};
|
||||
|
||||
Utils::FilePath m_cwd;
|
||||
Utils::CommandLine m_currentCommand;
|
||||
|
||||
@@ -245,7 +108,6 @@ private:
|
||||
TerminalSearchPtr m_search;
|
||||
|
||||
Aggregation::Aggregate *m_aggregate{nullptr};
|
||||
SearchHit m_lastSelectedHit{};
|
||||
|
||||
RegisteredAction m_copy;
|
||||
RegisteredAction m_paste;
|
||||
|
||||
67
src/plugins/terminal/tests/mouse
Executable file
67
src/plugins/terminal/tests/mouse
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
|
||||
|
||||
function cleanup {
|
||||
stty -echo
|
||||
printf "\e[?1006;1000l"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
stty -echo
|
||||
|
||||
# Enable SGR protocol and button press and release events
|
||||
printf "\e[?1006;1000h"
|
||||
|
||||
while read -n 1 line
|
||||
do
|
||||
printf -v ch "%d" \'$line
|
||||
if [ "27" != "$ch" ]; then
|
||||
continue
|
||||
fi
|
||||
read -n 1 line
|
||||
if [ "[" != "$line" ]; then
|
||||
continue
|
||||
fi
|
||||
read -n 1 line
|
||||
if [ "<" != "$line" ]; then
|
||||
continue
|
||||
fi
|
||||
# Read button state
|
||||
modifier=
|
||||
while read -n 1 line
|
||||
do
|
||||
if [ ";" = "$line" ]; then
|
||||
# End
|
||||
break
|
||||
fi
|
||||
printf -v modifier "$modifier$line"
|
||||
done
|
||||
# Read column
|
||||
col=
|
||||
while read -n 1 line
|
||||
do
|
||||
if [ ";" = "$line" ]; then
|
||||
# End
|
||||
break
|
||||
fi
|
||||
printf -v col "$col$line"
|
||||
done
|
||||
# Read row
|
||||
row=
|
||||
while read -n 1 line
|
||||
do
|
||||
if [ "M" = "$line" ] || [ "m" = "$line" ]; then
|
||||
# End
|
||||
btn=$line
|
||||
break
|
||||
fi
|
||||
printf -v row "$row$line"
|
||||
done
|
||||
if [ "M" = "$btn" ]; then
|
||||
echo "You pressed at $col x $row (mods: $modifier)"
|
||||
else
|
||||
echo "You released at $col x $row (mods: $modifier)"
|
||||
fi
|
||||
done < "${1:-/dev/stdin}"
|
||||
|
||||
Reference in New Issue
Block a user