Integrate KodoTerm for SSH terminal sessions
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include "session_backend_factory.h"
|
||||
#include "terminal_view.h"
|
||||
|
||||
#include <KodoTerm/KodoTerm.hpp>
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
@@ -16,21 +18,44 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
TerminalTheme themeForName(const QString& themeName)
|
||||
{
|
||||
if (themeName.compare(QStringLiteral("Light"), Qt::CaseInsensitive) == 0) {
|
||||
return TerminalTheme::loadKonsoleTheme(
|
||||
QStringLiteral(":/KodoTermThemes/konsole/BlackOnWhite.colorscheme"));
|
||||
}
|
||||
|
||||
if (themeName.compare(QStringLiteral("Solarized Dark"), Qt::CaseInsensitive) == 0) {
|
||||
return TerminalTheme::loadKonsoleTheme(
|
||||
QStringLiteral(":/KodoTermThemes/konsole/Solarized.colorscheme"));
|
||||
}
|
||||
|
||||
return TerminalTheme::loadKonsoleTheme(
|
||||
QStringLiteral(":/KodoTermThemes/konsole/Breeze.colorscheme"));
|
||||
}
|
||||
}
|
||||
|
||||
SessionTab::SessionTab(const Profile& profile, QWidget* parent)
|
||||
: QWidget(parent),
|
||||
m_profile(profile),
|
||||
m_backendThread(new QThread(this)),
|
||||
m_backendThread(nullptr),
|
||||
m_backend(nullptr),
|
||||
m_useKodoTermForSsh(profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive)
|
||||
== 0),
|
||||
m_state(SessionState::Disconnected),
|
||||
m_statusLabel(nullptr),
|
||||
m_errorLabel(nullptr),
|
||||
m_sshTerminal(nullptr),
|
||||
m_terminalOutput(nullptr),
|
||||
m_eventLog(nullptr),
|
||||
m_connectButton(nullptr),
|
||||
@@ -49,81 +74,111 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent)
|
||||
|
||||
setupUi();
|
||||
|
||||
std::unique_ptr<SessionBackend> backend = createSessionBackend(m_profile);
|
||||
m_backend = backend.release();
|
||||
m_backend->moveToThread(m_backendThread);
|
||||
if (m_useKodoTermForSsh) {
|
||||
connect(m_sshTerminal,
|
||||
&KodoTerm::finished,
|
||||
this,
|
||||
[this](int exitCode, int) {
|
||||
if (m_state == SessionState::Disconnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_backendThread, &QThread::finished, m_backend, &QObject::deleteLater);
|
||||
connect(this,
|
||||
&SessionTab::requestConnect,
|
||||
m_backend,
|
||||
&SessionBackend::connectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestDisconnect,
|
||||
m_backend,
|
||||
&SessionBackend::disconnectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestReconnect,
|
||||
m_backend,
|
||||
&SessionBackend::reconnectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestInput,
|
||||
m_backend,
|
||||
&SessionBackend::sendInput,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestHostKeyConfirmation,
|
||||
m_backend,
|
||||
&SessionBackend::confirmHostKey,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestTerminalSize,
|
||||
m_backend,
|
||||
&SessionBackend::updateTerminalSize,
|
||||
Qt::QueuedConnection);
|
||||
if (exitCode == 0) {
|
||||
setState(SessionState::Disconnected,
|
||||
QStringLiteral("SSH session ended."));
|
||||
} else {
|
||||
m_lastError = QStringLiteral("ssh exited with code %1").arg(exitCode);
|
||||
m_errorLabel->setText(QStringLiteral("Last Error: %1").arg(m_lastError));
|
||||
setState(SessionState::Failed,
|
||||
QStringLiteral("SSH session exited unexpectedly."));
|
||||
}
|
||||
});
|
||||
connect(m_sshTerminal,
|
||||
&KodoTerm::cwdChanged,
|
||||
this,
|
||||
[this](const QString& cwd) {
|
||||
if (!cwd.trimmed().isEmpty()) {
|
||||
appendEvent(QStringLiteral("Remote cwd: %1").arg(cwd));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
m_backendThread = new QThread(this);
|
||||
|
||||
connect(m_backend,
|
||||
&SessionBackend::stateChanged,
|
||||
this,
|
||||
&SessionTab::onBackendStateChanged,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::eventLogged,
|
||||
this,
|
||||
&SessionTab::onBackendEventLogged,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::connectionError,
|
||||
this,
|
||||
&SessionTab::onBackendConnectionError,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::outputReceived,
|
||||
this,
|
||||
&SessionTab::onBackendOutputReceived,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::hostKeyConfirmationRequested,
|
||||
this,
|
||||
&SessionTab::onBackendHostKeyConfirmationRequested,
|
||||
Qt::QueuedConnection);
|
||||
std::unique_ptr<SessionBackend> backend = createSessionBackend(m_profile);
|
||||
m_backend = backend.release();
|
||||
m_backend->moveToThread(m_backendThread);
|
||||
|
||||
m_backendThread->start();
|
||||
connect(m_backendThread, &QThread::finished, m_backend, &QObject::deleteLater);
|
||||
connect(this,
|
||||
&SessionTab::requestConnect,
|
||||
m_backend,
|
||||
&SessionBackend::connectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestDisconnect,
|
||||
m_backend,
|
||||
&SessionBackend::disconnectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestReconnect,
|
||||
m_backend,
|
||||
&SessionBackend::reconnectSession,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestInput,
|
||||
m_backend,
|
||||
&SessionBackend::sendInput,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestHostKeyConfirmation,
|
||||
m_backend,
|
||||
&SessionBackend::confirmHostKey,
|
||||
Qt::QueuedConnection);
|
||||
connect(this,
|
||||
&SessionTab::requestTerminalSize,
|
||||
m_backend,
|
||||
&SessionBackend::updateTerminalSize,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_backend,
|
||||
&SessionBackend::stateChanged,
|
||||
this,
|
||||
&SessionTab::onBackendStateChanged,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::eventLogged,
|
||||
this,
|
||||
&SessionTab::onBackendEventLogged,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::connectionError,
|
||||
this,
|
||||
&SessionTab::onBackendConnectionError,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::outputReceived,
|
||||
this,
|
||||
&SessionTab::onBackendOutputReceived,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_backend,
|
||||
&SessionBackend::hostKeyConfirmationRequested,
|
||||
this,
|
||||
&SessionTab::onBackendHostKeyConfirmationRequested,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_backendThread->start();
|
||||
}
|
||||
|
||||
setState(SessionState::Disconnected, QStringLiteral("Ready to connect."));
|
||||
}
|
||||
|
||||
SessionTab::~SessionTab()
|
||||
{
|
||||
if (m_backend != nullptr && m_backendThread->isRunning()) {
|
||||
if (m_backend != nullptr && m_backendThread != nullptr && m_backendThread->isRunning()) {
|
||||
QMetaObject::invokeMethod(m_backend, "disconnectSession", Qt::BlockingQueuedConnection);
|
||||
m_backendThread->quit();
|
||||
m_backendThread->wait(2000);
|
||||
}
|
||||
|
||||
m_backendThread->quit();
|
||||
m_backendThread->wait(2000);
|
||||
}
|
||||
|
||||
QString SessionTab::tabTitle() const
|
||||
@@ -142,11 +197,28 @@ void SessionTab::onConnectClicked()
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastConnectOptions = options.value();
|
||||
|
||||
if (m_useKodoTermForSsh) {
|
||||
if (!startSshTerminal(options.value())) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestConnect(options.value());
|
||||
}
|
||||
|
||||
void SessionTab::onDisconnectClicked()
|
||||
{
|
||||
if (m_useKodoTermForSsh) {
|
||||
if (m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->kill();
|
||||
}
|
||||
setState(SessionState::Disconnected, QStringLiteral("Session disconnected."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestDisconnect();
|
||||
}
|
||||
|
||||
@@ -161,6 +233,18 @@ void SessionTab::onReconnectClicked()
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastConnectOptions = options.value();
|
||||
|
||||
if (m_useKodoTermForSsh) {
|
||||
if (m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->kill();
|
||||
}
|
||||
QTimer::singleShot(50,
|
||||
this,
|
||||
[this, options]() { startSshTerminal(options.value()); });
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestReconnect(options.value());
|
||||
}
|
||||
|
||||
@@ -176,12 +260,19 @@ 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"));
|
||||
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->clearScrollback();
|
||||
m_sshTerminal->setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_terminalOutput != nullptr) {
|
||||
m_terminalOutput->clear();
|
||||
if (m_state == SessionState::Connected) {
|
||||
emit requestInput(QStringLiteral("\x0c"));
|
||||
}
|
||||
m_terminalOutput->setFocus();
|
||||
}
|
||||
m_terminalOutput->setFocus();
|
||||
}
|
||||
|
||||
void SessionTab::onBackendStateChanged(SessionState state, const QString& message)
|
||||
@@ -203,7 +294,7 @@ void SessionTab::onBackendConnectionError(const QString& displayMessage, const Q
|
||||
|
||||
void SessionTab::onBackendOutputReceived(const QString& text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
if (text.isEmpty() || m_terminalOutput == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -276,7 +367,9 @@ void SessionTab::setupUi()
|
||||
auto* terminalHeader = new QHBoxLayout();
|
||||
auto* terminalLabel = new QLabel(QStringLiteral("SSH Terminal"), this);
|
||||
m_themeSelector = new QComboBox(this);
|
||||
m_themeSelector->addItems(TerminalView::themeNames());
|
||||
m_themeSelector->addItems({QStringLiteral("Dark"),
|
||||
QStringLiteral("Light"),
|
||||
QStringLiteral("Solarized Dark")});
|
||||
m_themeSelector->setCurrentText(QStringLiteral("Dark"));
|
||||
m_clearTerminalButton = new QPushButton(QStringLiteral("Clear"), this);
|
||||
terminalHeader->addWidget(terminalLabel);
|
||||
@@ -285,13 +378,36 @@ void SessionTab::setupUi()
|
||||
terminalHeader->addWidget(m_themeSelector);
|
||||
terminalHeader->addWidget(m_clearTerminalButton);
|
||||
|
||||
m_terminalOutput = new TerminalView(this);
|
||||
QFont terminalFont(QStringLiteral("Monospace"));
|
||||
terminalFont.setStyleHint(QFont::TypeWriter);
|
||||
m_terminalOutput->setFont(terminalFont);
|
||||
m_terminalOutput->setMinimumHeight(260);
|
||||
m_terminalOutput->setPlaceholderText(
|
||||
QStringLiteral("Connect, then type directly here to interact with the SSH session."));
|
||||
if (m_useKodoTermForSsh) {
|
||||
m_sshTerminal = new KodoTerm(this);
|
||||
QFont terminalFont(QStringLiteral("Monospace"));
|
||||
terminalFont.setStyleHint(QFont::TypeWriter);
|
||||
|
||||
KodoTermConfig config = m_sshTerminal->getConfig();
|
||||
config.font = terminalFont;
|
||||
config.maxScrollback = 12000;
|
||||
m_sshTerminal->setConfig(config);
|
||||
|
||||
applyTerminalTheme(m_themeSelector->currentText());
|
||||
|
||||
rootLayout->addLayout(detailsHeader);
|
||||
rootLayout->addWidget(m_detailsPanel);
|
||||
rootLayout->addLayout(terminalHeader);
|
||||
rootLayout->addWidget(m_sshTerminal, 1);
|
||||
} else {
|
||||
m_terminalOutput = new TerminalView(this);
|
||||
QFont terminalFont(QStringLiteral("Monospace"));
|
||||
terminalFont.setStyleHint(QFont::TypeWriter);
|
||||
m_terminalOutput->setFont(terminalFont);
|
||||
m_terminalOutput->setMinimumHeight(260);
|
||||
m_terminalOutput->setPlaceholderText(
|
||||
QStringLiteral("Connect, then type directly here to interact with the SSH session."));
|
||||
|
||||
rootLayout->addLayout(detailsHeader);
|
||||
rootLayout->addWidget(m_detailsPanel);
|
||||
rootLayout->addLayout(terminalHeader);
|
||||
rootLayout->addWidget(m_terminalOutput, 1);
|
||||
}
|
||||
|
||||
auto* eventsHeader = new QHBoxLayout();
|
||||
m_toggleEventsButton = new QToolButton(this);
|
||||
@@ -312,10 +428,6 @@ void SessionTab::setupUi()
|
||||
eventsLayout->addWidget(eventTitle);
|
||||
eventsLayout->addWidget(m_eventLog);
|
||||
|
||||
rootLayout->addLayout(detailsHeader);
|
||||
rootLayout->addWidget(m_detailsPanel);
|
||||
rootLayout->addLayout(terminalHeader);
|
||||
rootLayout->addWidget(m_terminalOutput, 1);
|
||||
rootLayout->addLayout(eventsHeader);
|
||||
rootLayout->addWidget(m_eventsPanel);
|
||||
|
||||
@@ -345,18 +457,22 @@ void SessionTab::setupUi()
|
||||
&QPushButton::clicked,
|
||||
this,
|
||||
&SessionTab::onClearTerminalClicked);
|
||||
connect(m_terminalOutput,
|
||||
&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); });
|
||||
|
||||
if (m_terminalOutput != nullptr) {
|
||||
connect(m_terminalOutput,
|
||||
&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,
|
||||
&TerminalView::setThemeName);
|
||||
this,
|
||||
[this](const QString& themeName) { applyTerminalTheme(themeName); });
|
||||
}
|
||||
|
||||
std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
|
||||
@@ -368,6 +484,12 @@ std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
|
||||
return options;
|
||||
}
|
||||
|
||||
if (m_useKodoTermForSsh
|
||||
&& m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
||||
// Password is entered directly in terminal prompt.
|
||||
return options;
|
||||
}
|
||||
|
||||
if (m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
||||
bool accepted = false;
|
||||
const QString password = QInputDialog::getText(this,
|
||||
@@ -506,8 +628,16 @@ void SessionTab::refreshActionButtons()
|
||||
|| m_state == SessionState::Disconnected);
|
||||
m_copyErrorButton->setEnabled(!m_lastError.isEmpty());
|
||||
|
||||
m_terminalOutput->setEnabled(isConnected);
|
||||
m_terminalOutput->setFocus();
|
||||
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->setEnabled(true);
|
||||
m_sshTerminal->setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_terminalOutput != nullptr) {
|
||||
m_terminalOutput->setEnabled(isConnected);
|
||||
m_terminalOutput->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void SessionTab::setPanelExpanded(QToolButton* button,
|
||||
@@ -527,3 +657,97 @@ void SessionTab::setPanelExpanded(QToolButton* button,
|
||||
button->setText(expanded ? QStringLiteral("Hide %1").arg(name)
|
||||
: QStringLiteral("Show %1").arg(name));
|
||||
}
|
||||
|
||||
bool SessionTab::startSshTerminal(const SessionConnectOptions& options)
|
||||
{
|
||||
if (m_sshTerminal == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList args;
|
||||
args << QStringLiteral("-tt") << QStringLiteral("-p") << QString::number(m_profile.port)
|
||||
<< QStringLiteral("-o") << QStringLiteral("ConnectTimeout=12") << QStringLiteral("-o")
|
||||
<< QStringLiteral("ServerAliveInterval=20") << QStringLiteral("-o")
|
||||
<< QStringLiteral("ServerAliveCountMax=2");
|
||||
|
||||
const QString policy = options.knownHostsPolicy.trimmed().isEmpty()
|
||||
? m_profile.knownHostsPolicy.trimmed()
|
||||
: options.knownHostsPolicy.trimmed();
|
||||
|
||||
if (policy.compare(QStringLiteral("Ignore"), Qt::CaseInsensitive) == 0) {
|
||||
#ifdef Q_OS_WIN
|
||||
const QString knownHostsNullDevice = QStringLiteral("NUL");
|
||||
#else
|
||||
const QString knownHostsNullDevice = QStringLiteral("/dev/null");
|
||||
#endif
|
||||
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no")
|
||||
<< QStringLiteral("-o")
|
||||
<< QStringLiteral("UserKnownHostsFile=%1").arg(knownHostsNullDevice);
|
||||
} else if (policy.compare(QStringLiteral("Accept New"), Qt::CaseInsensitive) == 0) {
|
||||
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=accept-new");
|
||||
} else if (policy.compare(QStringLiteral("Ask"), Qt::CaseInsensitive) == 0) {
|
||||
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=ask");
|
||||
} else {
|
||||
args << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=yes");
|
||||
}
|
||||
|
||||
if (m_profile.authMode.compare(QStringLiteral("Private Key"), Qt::CaseInsensitive) == 0) {
|
||||
QString keyPath = options.privateKeyPath.trimmed();
|
||||
if (keyPath.isEmpty()) {
|
||||
keyPath = m_profile.privateKeyPath.trimmed();
|
||||
}
|
||||
|
||||
if (keyPath.isEmpty()) {
|
||||
m_lastError = QStringLiteral("Private key path is required.");
|
||||
m_errorLabel->setText(QStringLiteral("Last Error: %1").arg(m_lastError));
|
||||
setState(SessionState::Failed, m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
args << QStringLiteral("-i") << keyPath;
|
||||
}
|
||||
|
||||
const QString target = m_profile.username.trimmed().isEmpty()
|
||||
? m_profile.host.trimmed()
|
||||
: QStringLiteral("%1@%2").arg(m_profile.username.trimmed(), m_profile.host.trimmed());
|
||||
args << target;
|
||||
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
if (!env.contains(QStringLiteral("TERM"))) {
|
||||
env.insert(QStringLiteral("TERM"), QStringLiteral("xterm-256color"));
|
||||
}
|
||||
if (!env.contains(QStringLiteral("COLORTERM"))) {
|
||||
env.insert(QStringLiteral("COLORTERM"), QStringLiteral("truecolor"));
|
||||
}
|
||||
|
||||
m_sshTerminal->setProgram(QStringLiteral("ssh"));
|
||||
m_sshTerminal->setArguments(args);
|
||||
m_sshTerminal->setProcessEnvironment(env);
|
||||
|
||||
appendEvent(QStringLiteral("Launching SSH terminal session."));
|
||||
setState(SessionState::Connecting, QStringLiteral("Starting SSH terminal..."));
|
||||
|
||||
if (!m_sshTerminal->start()) {
|
||||
m_lastError = QStringLiteral("Failed to start embedded SSH terminal process.");
|
||||
m_errorLabel->setText(QStringLiteral("Last Error: %1").arg(m_lastError));
|
||||
setState(SessionState::Failed, QStringLiteral("Failed to start SSH terminal."));
|
||||
return false;
|
||||
}
|
||||
|
||||
setState(SessionState::Connected, QStringLiteral("SSH session established."));
|
||||
return true;
|
||||
}
|
||||
|
||||
void SessionTab::applyTerminalTheme(const QString& themeName)
|
||||
{
|
||||
if (m_useKodoTermForSsh) {
|
||||
if (m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->setTheme(themeForName(themeName));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_terminalOutput != nullptr) {
|
||||
m_terminalOutput->setThemeName(themeName);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user