Improve terminal theming, cursor UX, and size negotiation
This commit is contained in:
@@ -43,6 +43,7 @@ public slots:
|
||||
virtual void reconnectSession(const SessionConnectOptions& options) = 0;
|
||||
virtual void sendInput(const QString& input) = 0;
|
||||
virtual void confirmHostKey(bool trustHost) = 0;
|
||||
virtual void updateTerminalSize(int columns, int rows) = 0;
|
||||
|
||||
signals:
|
||||
void stateChanged(SessionState state, const QString& message);
|
||||
|
||||
@@ -79,6 +79,11 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent)
|
||||
m_backend,
|
||||
&SessionBackend::confirmHostKey,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestTerminalSize,
|
||||
m_backend,
|
||||
&SessionBackend::updateTerminalSize,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_backend,
|
||||
&SessionBackend::stateChanged,
|
||||
@@ -172,6 +177,11 @@ void SessionTab::onCopyErrorClicked()
|
||||
void SessionTab::onClearTerminalClicked()
|
||||
{
|
||||
m_terminalOutput->clear();
|
||||
if (m_state == SessionState::Connected) {
|
||||
// Ask the remote shell to repaint a prompt after local clear.
|
||||
emit requestInput(QStringLiteral("\x0c"));
|
||||
}
|
||||
m_terminalOutput->setFocus();
|
||||
}
|
||||
|
||||
void SessionTab::onBackendStateChanged(SessionState state, const QString& message)
|
||||
@@ -339,6 +349,10 @@ void SessionTab::setupUi()
|
||||
&TerminalView::inputGenerated,
|
||||
this,
|
||||
[this](const QString& input) { emit requestInput(input); });
|
||||
connect(m_terminalOutput,
|
||||
&TerminalView::terminalSizeChanged,
|
||||
this,
|
||||
[this](int columns, int rows) { emit requestTerminalSize(columns, rows); });
|
||||
connect(m_themeSelector,
|
||||
&QComboBox::currentTextChanged,
|
||||
m_terminalOutput,
|
||||
@@ -493,10 +507,8 @@ void SessionTab::refreshActionButtons()
|
||||
m_copyErrorButton->setEnabled(!m_lastError.isEmpty());
|
||||
|
||||
m_terminalOutput->setEnabled(isConnected);
|
||||
if (isConnected) {
|
||||
m_terminalOutput->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void SessionTab::setPanelExpanded(QToolButton* button,
|
||||
QWidget* panel,
|
||||
|
||||
@@ -35,6 +35,7 @@ signals:
|
||||
void requestReconnect(const SessionConnectOptions& options);
|
||||
void requestInput(const QString& input);
|
||||
void requestHostKeyConfirmation(bool trustHost);
|
||||
void requestTerminalSize(int columns, int rows);
|
||||
|
||||
private slots:
|
||||
void onConnectClicked();
|
||||
|
||||
@@ -25,7 +25,9 @@ SshSessionBackend::SshSessionBackend(const Profile& profile, QObject* parent)
|
||||
m_reconnectPending(false),
|
||||
m_waitingForPasswordPrompt(false),
|
||||
m_waitingForHostKeyConfirmation(false),
|
||||
m_passwordSubmitted(false)
|
||||
m_passwordSubmitted(false),
|
||||
m_terminalColumns(0),
|
||||
m_terminalRows(0)
|
||||
{
|
||||
m_connectedProbeTimer->setSingleShot(true);
|
||||
|
||||
@@ -152,6 +154,16 @@ void SshSessionBackend::confirmHostKey(bool trustHost)
|
||||
: QStringLiteral("Host key rejected by user."));
|
||||
}
|
||||
|
||||
void SshSessionBackend::updateTerminalSize(int columns, int rows)
|
||||
{
|
||||
m_terminalColumns = columns;
|
||||
m_terminalRows = rows;
|
||||
|
||||
if (m_state == SessionState::Connected) {
|
||||
applyTerminalSizeIfAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void SshSessionBackend::onProcessStarted()
|
||||
{
|
||||
emit eventLogged(QStringLiteral("ssh process started."));
|
||||
@@ -284,6 +296,10 @@ void SshSessionBackend::setState(SessionState state, const QString& message)
|
||||
m_state = state;
|
||||
emit stateChanged(state, message);
|
||||
emit eventLogged(message);
|
||||
|
||||
if (m_state == SessionState::Connected) {
|
||||
applyTerminalSizeIfAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
bool SshSessionBackend::startSshProcess(const SessionConnectOptions& options)
|
||||
@@ -502,3 +518,21 @@ QString SshSessionBackend::knownHostsFileForNullDevice() const
|
||||
return QStringLiteral("/dev/null");
|
||||
#endif
|
||||
}
|
||||
|
||||
void SshSessionBackend::applyTerminalSizeIfAvailable()
|
||||
{
|
||||
if (m_process->state() != QProcess::Running) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_terminalColumns <= 0 || m_terminalRows <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString command = QStringLiteral("stty cols %1 rows %2\\n")
|
||||
.arg(m_terminalColumns)
|
||||
.arg(m_terminalRows);
|
||||
m_process->write(command.toUtf8());
|
||||
emit eventLogged(
|
||||
QStringLiteral("Applied terminal size: %1x%2").arg(m_terminalColumns).arg(m_terminalRows));
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public slots:
|
||||
void reconnectSession(const SessionConnectOptions& options) override;
|
||||
void sendInput(const QString& input) override;
|
||||
void confirmHostKey(bool trustHost) override;
|
||||
void updateTerminalSize(int columns, int rows) override;
|
||||
|
||||
private slots:
|
||||
void onProcessStarted();
|
||||
@@ -43,6 +44,8 @@ private:
|
||||
bool m_waitingForPasswordPrompt;
|
||||
bool m_waitingForHostKeyConfirmation;
|
||||
bool m_passwordSubmitted;
|
||||
int m_terminalColumns;
|
||||
int m_terminalRows;
|
||||
|
||||
void setState(SessionState state, const QString& message);
|
||||
bool startSshProcess(const SessionConnectOptions& options);
|
||||
@@ -52,6 +55,7 @@ private:
|
||||
void cleanupAskPassScript();
|
||||
QString mapSshError(const QString& rawError) const;
|
||||
QString knownHostsFileForNullDevice() const;
|
||||
void applyTerminalSizeIfAvailable();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QColor>
|
||||
#include <QFocusEvent>
|
||||
#include <QFontMetrics>
|
||||
#include <QKeyEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QTimer>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -21,13 +25,21 @@ TerminalView::TerminalView(QWidget* parent)
|
||||
m_hasFgColor(false),
|
||||
m_hasBgColor(false)
|
||||
{
|
||||
setReadOnly(true);
|
||||
setReadOnly(false);
|
||||
setUndoRedoEnabled(false);
|
||||
document()->setMaximumBlockCount(4000);
|
||||
setAcceptRichText(false);
|
||||
setLineWrapMode(QTextEdit::NoWrap);
|
||||
setContextMenuPolicy(Qt::NoContextMenu);
|
||||
setCursorWidth(2);
|
||||
document()->setMaximumBlockCount(4000);
|
||||
|
||||
applyThemePalette(paletteByName(QStringLiteral("Dark")));
|
||||
resetSgrState();
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
moveCursor(QTextCursor::End);
|
||||
emitTerminalSize();
|
||||
});
|
||||
}
|
||||
|
||||
QStringList TerminalView::themeNames()
|
||||
@@ -123,8 +135,19 @@ void TerminalView::keyPressEvent(QKeyEvent* event)
|
||||
return;
|
||||
}
|
||||
|
||||
moveCursor(QTextCursor::End);
|
||||
|
||||
const Qt::KeyboardModifiers modifiers = event->modifiers();
|
||||
|
||||
if (modifiers == (Qt::ControlModifier | Qt::ShiftModifier)
|
||||
&& event->key() == Qt::Key_C) {
|
||||
const QString selected = textCursor().selectedText();
|
||||
if (!selected.isEmpty()) {
|
||||
QApplication::clipboard()->setText(selected);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifiers == Qt::ControlModifier) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_C:
|
||||
@@ -180,8 +203,18 @@ void TerminalView::keyPressEvent(QKeyEvent* event)
|
||||
emit inputGenerated(text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QTextEdit::keyPressEvent(event);
|
||||
void TerminalView::focusInEvent(QFocusEvent* event)
|
||||
{
|
||||
QTextEdit::focusInEvent(event);
|
||||
moveCursor(QTextCursor::End);
|
||||
}
|
||||
|
||||
void TerminalView::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QTextEdit::resizeEvent(event);
|
||||
emitTerminalSize();
|
||||
}
|
||||
|
||||
TerminalView::ThemePalette TerminalView::paletteByName(const QString& themeName)
|
||||
@@ -190,23 +223,23 @@ TerminalView::ThemePalette TerminalView::paletteByName(const QString& themeName)
|
||||
|
||||
if (theme == QStringLiteral("light")) {
|
||||
return ThemePalette{QStringLiteral("Light"),
|
||||
QColor(QStringLiteral("#fafafa")),
|
||||
QColor(QStringLiteral("#202124")),
|
||||
QColor(QStringLiteral("#ececec")),
|
||||
QColor(QStringLiteral("#000000")),
|
||||
{QColor(QStringLiteral("#000000")),
|
||||
QColor(QStringLiteral("#a31515")),
|
||||
QColor(QStringLiteral("#aa0000")),
|
||||
QColor(QStringLiteral("#008000")),
|
||||
QColor(QStringLiteral("#795e26")),
|
||||
QColor(QStringLiteral("#0000ff")),
|
||||
QColor(QStringLiteral("#af00db")),
|
||||
QColor(QStringLiteral("#0451a5")),
|
||||
QColor(QStringLiteral("#666666"))},
|
||||
{QColor(QStringLiteral("#7f7f7f")),
|
||||
QColor(QStringLiteral("#cd3131")),
|
||||
QColor(QStringLiteral("#14a10e")),
|
||||
QColor(QStringLiteral("#b5ba00")),
|
||||
QColor(QStringLiteral("#0451a5")),
|
||||
QColor(QStringLiteral("#bc05bc")),
|
||||
QColor(QStringLiteral("#0598bc")),
|
||||
QColor(QStringLiteral("#7a5f00")),
|
||||
QColor(QStringLiteral("#0033cc")),
|
||||
QColor(QStringLiteral("#8a00a8")),
|
||||
QColor(QStringLiteral("#005f87")),
|
||||
QColor(QStringLiteral("#333333"))},
|
||||
{QColor(QStringLiteral("#5c5c5c")),
|
||||
QColor(QStringLiteral("#d30000")),
|
||||
QColor(QStringLiteral("#00a000")),
|
||||
QColor(QStringLiteral("#9a7700")),
|
||||
QColor(QStringLiteral("#0055ff")),
|
||||
QColor(QStringLiteral("#b300db")),
|
||||
QColor(QStringLiteral("#007ea7")),
|
||||
QColor(QStringLiteral("#111111"))}};
|
||||
}
|
||||
|
||||
@@ -338,7 +371,7 @@ void TerminalView::handleSgrSequence(const QString& params)
|
||||
for (int i = 0; i < parts.size(); ++i) {
|
||||
const QString part = parts.at(i).trimmed();
|
||||
bool ok = false;
|
||||
int code = part.isEmpty() ? 0 : part.toInt(&ok);
|
||||
const int code = part.isEmpty() ? 0 : part.toInt(&ok);
|
||||
if (!ok && !part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -399,7 +432,7 @@ void TerminalView::handleSgrSequence(const QString& params)
|
||||
if (mode == 5 && i + 2 < parts.size()) {
|
||||
const int index = parts.at(i + 2).toInt(&ok);
|
||||
if (ok) {
|
||||
QColor color = colorFrom256Index(index);
|
||||
const QColor color = colorFrom256Index(index);
|
||||
if (background) {
|
||||
m_bgColor = color;
|
||||
m_hasBgColor = true;
|
||||
@@ -466,3 +499,22 @@ QColor TerminalView::paletteColor(bool, int index, bool bright) const
|
||||
return bright ? m_palette.bright.at(static_cast<size_t>(safeIndex))
|
||||
: m_palette.normal.at(static_cast<size_t>(safeIndex));
|
||||
}
|
||||
|
||||
int TerminalView::terminalColumns() const
|
||||
{
|
||||
const QFontMetrics metrics(font());
|
||||
const int cellWidth = std::max(1, metrics.horizontalAdvance(QChar::fromLatin1('M')));
|
||||
return std::max(1, viewport()->width() / cellWidth);
|
||||
}
|
||||
|
||||
int TerminalView::terminalRows() const
|
||||
{
|
||||
const QFontMetrics metrics(font());
|
||||
const int cellHeight = std::max(1, metrics.lineSpacing());
|
||||
return std::max(1, viewport()->height() / cellHeight);
|
||||
}
|
||||
|
||||
void TerminalView::emitTerminalSize()
|
||||
{
|
||||
emit terminalSizeChanged(terminalColumns(), terminalRows());
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <array>
|
||||
|
||||
class QKeyEvent;
|
||||
class QFocusEvent;
|
||||
class QResizeEvent;
|
||||
|
||||
class TerminalView : public QTextEdit
|
||||
{
|
||||
@@ -20,9 +22,12 @@ public:
|
||||
|
||||
signals:
|
||||
void inputGenerated(const QString& input);
|
||||
void terminalSizeChanged(int columns, int rows);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void focusInEvent(QFocusEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
struct ThemePalette {
|
||||
@@ -51,6 +56,9 @@ private:
|
||||
void handleSgrSequence(const QString& params);
|
||||
void appendTextChunk(const QString& text);
|
||||
QColor paletteColor(bool background, int index, bool bright) const;
|
||||
int terminalColumns() const;
|
||||
int terminalRows() const;
|
||||
void emitTerminalSize();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,3 +33,7 @@ void UnsupportedSessionBackend::sendInput(const QString&)
|
||||
void UnsupportedSessionBackend::confirmHostKey(bool)
|
||||
{
|
||||
}
|
||||
|
||||
void UnsupportedSessionBackend::updateTerminalSize(int, int)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public slots:
|
||||
void reconnectSession(const SessionConnectOptions& options) override;
|
||||
void sendInput(const QString& input) override;
|
||||
void confirmHostKey(bool trustHost) override;
|
||||
void updateTerminalSize(int columns, int rows) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user