Internal
Public Access
Session UX: inline password prompt and terminal font size controls
Replace the modal RDP/SSH password dialog with an inline prompt bar embedded in the session tab instead of a separate popup window. Add per-tab terminal font size controls (increase/decrease/reset/set exact point size) via the tab context menu, with the chosen size persisted across sessions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+246
-81
@@ -13,7 +13,6 @@
|
||||
#include <QFont>
|
||||
#include <QFontDatabase>
|
||||
#include <QHBoxLayout>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
@@ -22,6 +21,7 @@
|
||||
#include <QClipboard>
|
||||
#include <QComboBox>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
@@ -71,6 +71,9 @@ SessionTab::SessionTab(const Profile& profile,
|
||||
m_terminalThemeName(preferences.terminalThemeName.trimmed().isEmpty()
|
||||
? QStringLiteral("Dark")
|
||||
: preferences.terminalThemeName.trimmed()),
|
||||
m_terminalFontPointSize(preferences.terminalFontPointSize > 0
|
||||
? preferences.terminalFontPointSize
|
||||
: 0),
|
||||
m_sshTerminal(nullptr),
|
||||
m_rdpDisplay(nullptr),
|
||||
m_terminalOutput(nullptr),
|
||||
@@ -81,6 +84,11 @@ SessionTab::SessionTab(const Profile& profile,
|
||||
m_clearEventsButton(nullptr),
|
||||
m_exportEventsButton(nullptr),
|
||||
m_eventsPanel(nullptr),
|
||||
m_passwordPromptBar(nullptr),
|
||||
m_passwordPromptLabel(nullptr),
|
||||
m_passwordPromptInput(nullptr),
|
||||
m_passwordPromptConnectButton(nullptr),
|
||||
m_passwordPromptCancelButton(nullptr),
|
||||
m_eventSeverityFilter(EventSeverity::Info),
|
||||
m_eventsPanelExpanded(preferences.eventsPanelExpanded)
|
||||
{
|
||||
@@ -265,21 +273,20 @@ void SessionTab::connectSession()
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<SessionConnectOptions> options = buildConnectOptions();
|
||||
if (!options.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastConnectOptions = options.value();
|
||||
|
||||
if (m_useKodoTermForSsh) {
|
||||
if (!startSshTerminal(options.value())) {
|
||||
requestConnectOptions([this](std::optional<SessionConnectOptions> options) {
|
||||
if (!options.has_value()) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestConnect(options.value());
|
||||
m_lastConnectOptions = options.value();
|
||||
|
||||
if (m_useKodoTermForSsh) {
|
||||
startSshTerminal(options.value());
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestConnect(options.value());
|
||||
});
|
||||
}
|
||||
|
||||
void SessionTab::disconnectSession()
|
||||
@@ -305,24 +312,25 @@ void SessionTab::reconnectSession()
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<SessionConnectOptions> options = buildConnectOptions();
|
||||
if (!options.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastConnectOptions = options.value();
|
||||
|
||||
if (m_useKodoTermForSsh) {
|
||||
if (m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->kill();
|
||||
requestConnectOptions([this](std::optional<SessionConnectOptions> options) {
|
||||
if (!options.has_value()) {
|
||||
return;
|
||||
}
|
||||
QTimer::singleShot(50,
|
||||
this,
|
||||
[this, options]() { startSshTerminal(options.value()); });
|
||||
return;
|
||||
}
|
||||
|
||||
emit requestReconnect(options.value());
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
void SessionTab::clearTerminal()
|
||||
@@ -380,6 +388,84 @@ bool SessionTab::supportsClearAction() const
|
||||
return m_useKodoTermForSsh || m_terminalOutput != nullptr;
|
||||
}
|
||||
|
||||
bool SessionTab::supportsZoom() const
|
||||
{
|
||||
return m_useKodoTermForSsh || m_terminalOutput != nullptr;
|
||||
}
|
||||
|
||||
void SessionTab::zoomIn()
|
||||
{
|
||||
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->zoomIn();
|
||||
m_terminalFontPointSize = m_sshTerminal->getConfig().font.pointSize();
|
||||
} else if (m_terminalOutput != nullptr) {
|
||||
m_terminalFontPointSize = m_terminalOutput->font().pointSize() + 1;
|
||||
m_terminalOutput->setFontPointSize(m_terminalFontPointSize);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
emit terminalFontSizeChanged(m_terminalFontPointSize);
|
||||
}
|
||||
|
||||
void SessionTab::zoomOut()
|
||||
{
|
||||
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->zoomOut();
|
||||
m_terminalFontPointSize = m_sshTerminal->getConfig().font.pointSize();
|
||||
} else if (m_terminalOutput != nullptr) {
|
||||
const int newSize = m_terminalOutput->font().pointSize() - 1;
|
||||
if (newSize < 6) {
|
||||
return;
|
||||
}
|
||||
m_terminalFontPointSize = newSize;
|
||||
m_terminalOutput->setFontPointSize(m_terminalFontPointSize);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
emit terminalFontSizeChanged(m_terminalFontPointSize);
|
||||
}
|
||||
|
||||
void SessionTab::resetZoom()
|
||||
{
|
||||
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
|
||||
m_sshTerminal->resetZoom();
|
||||
m_terminalFontPointSize = m_sshTerminal->getConfig().font.pointSize();
|
||||
} else if (m_terminalOutput != nullptr) {
|
||||
m_terminalFontPointSize = defaultTerminalFont().pointSize();
|
||||
m_terminalOutput->setFontPointSize(m_terminalFontPointSize);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
emit terminalFontSizeChanged(m_terminalFontPointSize);
|
||||
}
|
||||
|
||||
void SessionTab::setTerminalFontPointSize(int pointSize)
|
||||
{
|
||||
const int clamped = qBound(6, pointSize, 72);
|
||||
|
||||
if (m_useKodoTermForSsh && m_sshTerminal != nullptr) {
|
||||
KodoTermConfig config = m_sshTerminal->getConfig();
|
||||
config.font.setPointSize(clamped);
|
||||
m_sshTerminal->setConfig(config);
|
||||
m_terminalFontPointSize = clamped;
|
||||
} else if (m_terminalOutput != nullptr) {
|
||||
m_terminalFontPointSize = clamped;
|
||||
m_terminalOutput->setFontPointSize(clamped);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
emit terminalFontSizeChanged(m_terminalFontPointSize);
|
||||
}
|
||||
|
||||
int SessionTab::terminalFontPointSize() const
|
||||
{
|
||||
return m_terminalFontPointSize;
|
||||
}
|
||||
|
||||
bool SessionTab::isEventsPanelExpanded() const
|
||||
{
|
||||
return m_eventsPanelExpanded;
|
||||
@@ -535,7 +621,10 @@ void SessionTab::setupUi()
|
||||
|
||||
if (m_useKodoTermForSsh) {
|
||||
m_sshTerminal = new KodoTerm(this);
|
||||
const QFont terminalFont = defaultTerminalFont();
|
||||
QFont terminalFont = defaultTerminalFont();
|
||||
if (m_terminalFontPointSize > 0) {
|
||||
terminalFont.setPointSize(m_terminalFontPointSize);
|
||||
}
|
||||
|
||||
KodoTermConfig config = m_sshTerminal->getConfig();
|
||||
config.font = terminalFont;
|
||||
@@ -548,7 +637,11 @@ void SessionTab::setupUi()
|
||||
rootLayout->addWidget(m_rdpDisplay, 1);
|
||||
} else {
|
||||
m_terminalOutput = new TerminalView(this);
|
||||
m_terminalOutput->setFont(defaultTerminalFont());
|
||||
QFont fallbackFont = defaultTerminalFont();
|
||||
if (m_terminalFontPointSize > 0) {
|
||||
fallbackFont.setPointSize(m_terminalFontPointSize);
|
||||
}
|
||||
m_terminalOutput->setFont(fallbackFont);
|
||||
m_terminalOutput->setMinimumHeight(260);
|
||||
m_terminalOutput->setReadOnly(true);
|
||||
if (m_profile.protocol.compare(QStringLiteral("VNC"), Qt::CaseInsensitive) == 0) {
|
||||
@@ -563,6 +656,45 @@ void SessionTab::setupUi()
|
||||
|
||||
applyTerminalTheme(m_terminalThemeName);
|
||||
|
||||
auto* passwordPromptLayout = new QHBoxLayout();
|
||||
m_passwordPromptLabel = new QLabel(this);
|
||||
m_passwordPromptInput = new QLineEdit(this);
|
||||
m_passwordPromptInput->setEchoMode(QLineEdit::Password);
|
||||
m_passwordPromptConnectButton = new QPushButton(QStringLiteral("Connect"), this);
|
||||
m_passwordPromptCancelButton = new QPushButton(QStringLiteral("Cancel"), this);
|
||||
passwordPromptLayout->addWidget(m_passwordPromptLabel);
|
||||
passwordPromptLayout->addWidget(m_passwordPromptInput, 1);
|
||||
passwordPromptLayout->addWidget(m_passwordPromptConnectButton);
|
||||
passwordPromptLayout->addWidget(m_passwordPromptCancelButton);
|
||||
|
||||
m_passwordPromptBar = new QWidget(this);
|
||||
m_passwordPromptBar->setLayout(passwordPromptLayout);
|
||||
m_passwordPromptBar->setAutoFillBackground(true);
|
||||
m_passwordPromptBar->setVisible(false);
|
||||
rootLayout->addWidget(m_passwordPromptBar);
|
||||
|
||||
connect(m_passwordPromptConnectButton, &QPushButton::clicked, this, [this]() {
|
||||
if (!m_passwordPromptCallback) {
|
||||
return;
|
||||
}
|
||||
const QString password = m_passwordPromptInput->text();
|
||||
const auto callback = m_passwordPromptCallback;
|
||||
hidePasswordPrompt();
|
||||
callback(password);
|
||||
});
|
||||
connect(m_passwordPromptCancelButton, &QPushButton::clicked, this, [this]() {
|
||||
if (!m_passwordPromptCallback) {
|
||||
return;
|
||||
}
|
||||
const auto callback = m_passwordPromptCallback;
|
||||
hidePasswordPrompt();
|
||||
callback(std::nullopt);
|
||||
});
|
||||
connect(m_passwordPromptInput,
|
||||
&QLineEdit::returnPressed,
|
||||
m_passwordPromptConnectButton,
|
||||
&QPushButton::click);
|
||||
|
||||
auto* eventsHeader = new QHBoxLayout();
|
||||
m_toggleEventsButton = new QToolButton(this);
|
||||
m_toggleEventsButton->setCheckable(true);
|
||||
@@ -676,77 +808,85 @@ void SessionTab::setupUi()
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
|
||||
void SessionTab::requestConnectOptions(
|
||||
std::function<void(std::optional<SessionConnectOptions>)> callback)
|
||||
{
|
||||
SessionConnectOptions options;
|
||||
options.knownHostsPolicy = m_profile.knownHostsPolicy;
|
||||
SessionConnectOptions baseOptions;
|
||||
baseOptions.knownHostsPolicy = m_profile.knownHostsPolicy;
|
||||
|
||||
const bool isSsh = m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0;
|
||||
const bool isRdp = m_profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0;
|
||||
|
||||
if (!isSsh && !isRdp) {
|
||||
return options;
|
||||
callback(baseOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRdp) {
|
||||
if (m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) != 0) {
|
||||
return options;
|
||||
callback(baseOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
bool accepted = false;
|
||||
const QString password = QInputDialog::getText(
|
||||
this,
|
||||
QStringLiteral("RDP Password"),
|
||||
QStringLiteral("Password for %1:")
|
||||
.arg(m_profile.username.trimmed().isEmpty()
|
||||
? m_profile.host
|
||||
: QStringLiteral("%1@%2").arg(m_profile.username, m_profile.host)),
|
||||
QLineEdit::Password,
|
||||
QString(),
|
||||
&accepted);
|
||||
if (!accepted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const QString label = QStringLiteral("RDP password for %1:")
|
||||
.arg(m_profile.username.trimmed().isEmpty()
|
||||
? m_profile.host
|
||||
: QStringLiteral("%1@%2").arg(m_profile.username, m_profile.host));
|
||||
|
||||
if (password.isEmpty()) {
|
||||
QMessageBox::warning(this,
|
||||
QStringLiteral("Connect"),
|
||||
QStringLiteral("Password is required for password authentication."));
|
||||
return std::nullopt;
|
||||
}
|
||||
showPasswordPrompt(
|
||||
label,
|
||||
[this, baseOptions, callback](std::optional<QString> password) {
|
||||
if (!password.has_value()) {
|
||||
callback(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
options.password = password;
|
||||
return options;
|
||||
if (password->isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
QStringLiteral("Connect"),
|
||||
QStringLiteral("Password is required for password authentication."));
|
||||
callback(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
SessionConnectOptions options = baseOptions;
|
||||
options.password = password.value();
|
||||
callback(options);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_useKodoTermForSsh
|
||||
&& m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
||||
// Password is entered directly in terminal prompt.
|
||||
return options;
|
||||
callback(baseOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) == 0) {
|
||||
bool accepted = false;
|
||||
const QString password = QInputDialog::getText(this,
|
||||
QStringLiteral("SSH Password"),
|
||||
QStringLiteral("Password for %1@%2:")
|
||||
.arg(m_profile.username, m_profile.host),
|
||||
QLineEdit::Password,
|
||||
QString(),
|
||||
&accepted);
|
||||
if (!accepted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
showPasswordPrompt(
|
||||
QStringLiteral("SSH password for %1@%2:").arg(m_profile.username, m_profile.host),
|
||||
[this, baseOptions, callback](std::optional<QString> password) {
|
||||
if (!password.has_value()) {
|
||||
callback(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.isEmpty()) {
|
||||
QMessageBox::warning(this,
|
||||
QStringLiteral("Connect"),
|
||||
QStringLiteral("Password is required for password authentication."));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (password->isEmpty()) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
QStringLiteral("Connect"),
|
||||
QStringLiteral("Password is required for password authentication."));
|
||||
callback(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
options.password = password;
|
||||
return options;
|
||||
SessionConnectOptions options = baseOptions;
|
||||
options.password = password.value();
|
||||
callback(options);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
QString keyPath = m_profile.privateKeyPath.trimmed();
|
||||
@@ -756,7 +896,8 @@ std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
|
||||
QString(),
|
||||
QStringLiteral("All Files (*)"));
|
||||
if (keyPath.isEmpty()) {
|
||||
return std::nullopt;
|
||||
callback(std::nullopt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,11 +905,35 @@ std::optional<SessionConnectOptions> SessionTab::buildConnectOptions()
|
||||
QMessageBox::warning(this,
|
||||
QStringLiteral("Connect"),
|
||||
QStringLiteral("Private key file not found: %1").arg(keyPath));
|
||||
return std::nullopt;
|
||||
callback(std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
SessionConnectOptions options = baseOptions;
|
||||
options.privateKeyPath = keyPath;
|
||||
return options;
|
||||
callback(options);
|
||||
}
|
||||
|
||||
void SessionTab::showPasswordPrompt(const QString& labelText,
|
||||
std::function<void(std::optional<QString>)> callback)
|
||||
{
|
||||
if (m_passwordPromptCallback) {
|
||||
const auto previousCallback = m_passwordPromptCallback;
|
||||
m_passwordPromptCallback = nullptr;
|
||||
previousCallback(std::nullopt);
|
||||
}
|
||||
|
||||
m_passwordPromptCallback = std::move(callback);
|
||||
m_passwordPromptLabel->setText(labelText);
|
||||
m_passwordPromptInput->clear();
|
||||
m_passwordPromptBar->setVisible(true);
|
||||
m_passwordPromptInput->setFocus();
|
||||
}
|
||||
|
||||
void SessionTab::hidePasswordPrompt()
|
||||
{
|
||||
m_passwordPromptBar->setVisible(false);
|
||||
m_passwordPromptCallback = nullptr;
|
||||
}
|
||||
|
||||
bool SessionTab::validateProfileForConnect()
|
||||
|
||||
Reference in New Issue
Block a user