Initial implementation of KRDC SSH plugin

Adds an SSH terminal plugin for KRDC that embeds KonsolePart
(Konsole's KPart terminal emulator) inside a RemoteView subclass.
The plugin registers the ssh:// URL scheme and launches ssh via
the TerminalInterface::startProgram() API.

Components:
- SshViewFactory: plugin factory handling ssh:// URLs
- SshView: RemoteView subclass embedding KonsolePart
- SshHostPreferences: configuration for username, identity file,
  X11/agent forwarding, compression, and port forwarding tunnels

Key implementation details:
- KonsolePart loaded via KPluginFactory from kf6/parts/konsolepart
- ssh binary resolved via QStandardPaths::findExecutable()
- scaleResize() unconditionally calls resize() (KRDC's scroll area
  does not use setWidgetResizable, relies on this slot)
- deleteLater() for KonsolePart teardown to avoid use-after-free

Build: standalone CMake project linking against krdccore and KF6::Parts.
This commit is contained in:
Levi Neely 2026-07-11 17:00:46 +02:00
commit ac239c1943
12 changed files with 964 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

56
CMakeLists.txt Normal file
View File

@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.16)
project(krdc-ssh VERSION 0.1.0)
set(QT_MIN_VERSION "6.6.0")
set(KF_MIN_VERSION "6.0.0")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
include(FeatureSummary)
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Gui Widgets)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
Config
CoreAddons
I18n
KIO
Parts
WidgetsAddons
)
# Find krdccore headers and library
find_path(KRDCCORE_INCLUDE_DIR remoteviewfactory.h
PATH_SUFFIXES krdc
)
find_library(KRDCCORE_LIBRARY NAMES krdccore)
if(NOT KRDCCORE_INCLUDE_DIR OR NOT KRDCCORE_LIBRARY)
message(FATAL_ERROR "krdccore not found. Please install KRDC development files.")
endif()
message(STATUS "Found krdccore: ${KRDCCORE_LIBRARY}")
message(STATUS "Found krdccore headers: ${KRDCCORE_INCLUDE_DIR}")
set(CMAKE_CXX_STANDARD 20)
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_NO_CAST_TO_ASCII
-DQT_NO_CAST_FROM_ASCII
-DQT_STRICT_ITERATORS
-DQT_NO_URL_CAST_FROM_STRING
-DQT_NO_CAST_FROM_BYTEARRAY
-DQT_NO_KEYWORDS
-DQT_NO_FOREACH
)
add_subdirectory(src)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)

27
PLAN.md Normal file
View File

@ -0,0 +1,27 @@
# KRDC SSH Plugin
## Architecture
KRDC plugin that provides SSH terminal sessions. Embeds KonsolePart (KDE's terminal
KPart) inside a RemoteView subclass. KonsolePart runs `ssh user@host -p port` via
startProgram() — handles terminal emulation, rendering, and input natively.
## Key Discovery
KRDC's RemoteViewScrollArea does NOT use setWidgetResizable(true). Instead it emits
`resized(w, h)` connected to `scaleResize()`. The view MUST call `resize(w, h)` on
itself in scaleResize to get a non-zero size. The `m_connected` guard in resizeEvent
prevents the re-entrant crash during tab insertion show sequence.
## Checklist
- [x] Core plugin implementation
- [x] Configuration UI (username, identity, tunneling, options)
- [x] Fix crash: scaleResize during show (m_connected guard in resizeEvent)
- [x] Fix crash: disconnect (deleteLater instead of delete)
- [x] Fix blue screen: startProgram needs full path (QStandardPaths)
- [x] Fix blue screen: scaleResize isVisible() guard was blocking initial resize
- [x] Reverted to startProgram (not showShellInDir) for proper lifecycle
- [ ] Verify: no crash, no blue screen, ssh works on first connection
- [ ] Clean up dead code / unnecessary deferred resize in resizeEvent
- [ ] Squash/tidy commits if desired

36
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,36 @@
add_library(krdc_sshplugin MODULE)
target_sources(krdc_sshplugin PRIVATE
sshview.cpp
sshviewfactory.cpp
sshhostpreferences.cpp
)
ki18n_wrap_ui(krdc_sshplugin
sshpreferences.ui
)
ecm_qt_declare_logging_category(krdc_sshplugin
HEADER krdc_ssh_debug.h
IDENTIFIER KRDC_SSH
CATEGORY_NAME KRDC.SSH
)
target_include_directories(krdc_sshplugin PRIVATE
${KRDCCORE_INCLUDE_DIR}
)
target_link_libraries(krdc_sshplugin
Qt::Core
Qt::Gui
Qt::Widgets
KF6::ConfigCore
KF6::CoreAddons
KF6::I18n
KF6::KIOWidgets
KF6::Parts
KF6::WidgetsAddons
${KRDCCORE_LIBRARY}
)
install(TARGETS krdc_sshplugin DESTINATION ${KDE_INSTALL_PLUGINDIR}/krdc)

16
src/krdc_ssh.json Normal file
View File

@ -0,0 +1,16 @@
{
"KPlugin": {
"Authors": [
{
"Name": "KRDC SSH Plugin Authors"
}
],
"Category": "Service",
"Description": "Allows managing SSH terminal sessions through KRDC",
"EnabledByDefault": true,
"Icon": "utilities-terminal",
"License": "GPL",
"Name": "SSH"
},
"X-KDE-KRDC-Sorting": 30
}

164
src/sshhostpreferences.cpp Normal file
View File

@ -0,0 +1,164 @@
/*
SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "sshhostpreferences.h"
#include <KLocalizedString>
static const char username_config_key[] = "username";
static const char identity_file_config_key[] = "identityFile";
static const char x11_forwarding_config_key[] = "x11Forwarding";
static const char agent_forwarding_config_key[] = "agentForwarding";
static const char compression_config_key[] = "compression";
static const char tunnels_config_key[] = "tunnels";
SshHostPreferences::SshHostPreferences(KConfigGroup configGroup, QObject *parent)
: HostPreferences(configGroup, parent)
{
}
SshHostPreferences::~SshHostPreferences()
{
}
QWidget *SshHostPreferences::createProtocolSpecificConfigPage(QWidget *sshTunnelWidget)
{
Q_UNUSED(sshTunnelWidget);
auto *page = new QWidget();
sshUi.setupUi(page);
// Populate from config
sshUi.usernameEdit->setText(userName());
sshUi.identityFileEdit->setText(identityFile());
sshUi.x11ForwardingCheck->setChecked(x11Forwarding());
sshUi.agentForwardingCheck->setChecked(agentForwarding());
sshUi.compressionCheck->setChecked(compression());
// Populate tunnels list
const QStringList existingTunnels = tunnels();
for (const QString &tunnel : existingTunnels) {
sshUi.tunnelList->addItem(tunnel);
}
connect(sshUi.addTunnelButton, &QPushButton::clicked, this, &SshHostPreferences::addTunnel);
connect(sshUi.removeTunnelButton, &QPushButton::clicked, this, &SshHostPreferences::removeTunnel);
return page;
}
void SshHostPreferences::acceptConfig()
{
HostPreferences::acceptConfig();
setUserName(sshUi.usernameEdit->text().trimmed());
setIdentityFile(sshUi.identityFileEdit->text().trimmed());
setX11Forwarding(sshUi.x11ForwardingCheck->isChecked());
setAgentForwarding(sshUi.agentForwardingCheck->isChecked());
setCompression(sshUi.compressionCheck->isChecked());
// Collect tunnels from list widget
QStringList tunnelList;
for (int i = 0; i < sshUi.tunnelList->count(); ++i) {
tunnelList << sshUi.tunnelList->item(i)->text();
}
setTunnels(tunnelList);
}
void SshHostPreferences::addTunnel()
{
const QString spec = sshUi.tunnelSpecEdit->text().trimmed();
if (spec.isEmpty()) {
return;
}
QString flag;
switch (sshUi.tunnelTypeCombo->currentIndex()) {
case 0:
flag = QStringLiteral("-L");
break;
case 1:
flag = QStringLiteral("-R");
break;
case 2:
flag = QStringLiteral("-D");
break;
}
const QString entry = flag + QLatin1Char(' ') + spec;
sshUi.tunnelList->addItem(entry);
sshUi.tunnelSpecEdit->clear();
}
void SshHostPreferences::removeTunnel()
{
const auto selected = sshUi.tunnelList->selectedItems();
for (auto *item : selected) {
delete item;
}
}
// --- Config accessors ---
QString SshHostPreferences::userName() const
{
return m_configGroup.readEntry(username_config_key, QString());
}
void SshHostPreferences::setUserName(const QString &userName)
{
m_configGroup.writeEntry(username_config_key, userName);
}
QString SshHostPreferences::identityFile() const
{
return m_configGroup.readEntry(identity_file_config_key, QString());
}
void SshHostPreferences::setIdentityFile(const QString &path)
{
m_configGroup.writeEntry(identity_file_config_key, path);
}
bool SshHostPreferences::x11Forwarding() const
{
return m_configGroup.readEntry(x11_forwarding_config_key, false);
}
void SshHostPreferences::setX11Forwarding(bool enabled)
{
m_configGroup.writeEntry(x11_forwarding_config_key, enabled);
}
bool SshHostPreferences::agentForwarding() const
{
return m_configGroup.readEntry(agent_forwarding_config_key, false);
}
void SshHostPreferences::setAgentForwarding(bool enabled)
{
m_configGroup.writeEntry(agent_forwarding_config_key, enabled);
}
bool SshHostPreferences::compression() const
{
return m_configGroup.readEntry(compression_config_key, false);
}
void SshHostPreferences::setCompression(bool enabled)
{
m_configGroup.writeEntry(compression_config_key, enabled);
}
QStringList SshHostPreferences::tunnels() const
{
return m_configGroup.readEntry(tunnels_config_key, QStringList());
}
void SshHostPreferences::setTunnels(const QStringList &tunnels)
{
m_configGroup.writeEntry(tunnels_config_key, tunnels);
}

51
src/sshhostpreferences.h Normal file
View File

@ -0,0 +1,51 @@
/*
SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SSHHOSTPREFERENCES_H
#define SSHHOSTPREFERENCES_H
#include "hostpreferences.h"
#include "ui_sshpreferences.h"
class SshHostPreferences : public HostPreferences
{
Q_OBJECT
public:
explicit SshHostPreferences(KConfigGroup configGroup, QObject *parent = nullptr);
~SshHostPreferences() override;
QString userName() const;
void setUserName(const QString &userName);
QString identityFile() const;
void setIdentityFile(const QString &path);
bool x11Forwarding() const;
void setX11Forwarding(bool enabled);
bool agentForwarding() const;
void setAgentForwarding(bool enabled);
bool compression() const;
void setCompression(bool enabled);
QStringList tunnels() const;
void setTunnels(const QStringList &tunnels);
protected:
QWidget *createProtocolSpecificConfigPage(QWidget *sshTunnelWidget) override;
void acceptConfig() override;
private Q_SLOTS:
void addTunnel();
void removeTunnel();
private:
Ui::SshPreferences sshUi;
};
#endif // SSHHOSTPREFERENCES_H

174
src/sshpreferences.ui Normal file
View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SshPreferences</class>
<widget class="QWidget" name="SshPreferences">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>500</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="connectionGroupBox">
<property name="title">
<string>Connection</string>
</property>
<layout class="QFormLayout" name="connectionFormLayout">
<item row="0" column="0">
<widget class="QLabel" name="usernameLabel">
<property name="text">
<string>&amp;Username:</string>
</property>
<property name="buddy">
<cstring>usernameEdit</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="usernameEdit">
<property name="placeholderText">
<string>Leave empty to use current user</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="identityFileLabel">
<property name="text">
<string>&amp;Identity file:</string>
</property>
<property name="buddy">
<cstring>identityFileEdit</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KUrlRequester" name="identityFileEdit">
<property name="placeholderText">
<string>Default (~/.ssh/id_*)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="optionsGroupBox">
<property name="title">
<string>Options</string>
</property>
<layout class="QVBoxLayout" name="optionsLayout">
<item>
<widget class="QCheckBox" name="x11ForwardingCheck">
<property name="text">
<string>Enable &amp;X11 forwarding (-X)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="agentForwardingCheck">
<property name="text">
<string>Enable &amp;agent forwarding (-A)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="compressionCheck">
<property name="text">
<string>Enable &amp;compression (-C)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="tunnelingGroupBox">
<property name="title">
<string>Port Forwarding</string>
</property>
<layout class="QVBoxLayout" name="tunnelingLayout">
<item>
<layout class="QHBoxLayout" name="tunnelTypeLayout">
<item>
<widget class="QComboBox" name="tunnelTypeCombo">
<item>
<property name="text">
<string>Local (-L)</string>
</property>
</item>
<item>
<property name="text">
<string>Remote (-R)</string>
</property>
</item>
<item>
<property name="text">
<string>Dynamic/SOCKS (-D)</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tunnelSpecEdit">
<property name="placeholderText">
<string>e.g. 8080:localhost:80</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addTunnelButton">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="tunnelList">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>120</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeTunnelButton">
<property name="text">
<string>Remove Selected</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KUrlRequester</class>
<extends>QWidget</extends>
<header>kurlrequester.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

277
src/sshview.cpp Normal file
View File

@ -0,0 +1,277 @@
/*
SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "sshview.h"
#include "krdc_ssh_debug.h"
#include "sshhostpreferences.h"
#include <QKeyEvent>
#include <QMimeData>
#include <QPainter>
#include <QVBoxLayout>
#include <QStandardPaths>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KPluginMetaData>
#include <kde_terminal_interface.h>
SshView::SshView(QWidget *parent, const QUrl &url, KConfigGroup configGroup)
: RemoteView(parent)
, m_framebufferSize(800, 600)
{
m_url = url;
m_host = url.host();
m_port = url.port();
if (m_port <= 0) {
m_port = 22;
}
m_hostPreferences = new SshHostPreferences(configGroup, this);
}
SshView::~SshView()
{
if (!m_quitting) {
startQuitting();
}
}
bool SshView::loadKonsolePart()
{
const auto result = KPluginFactory::instantiatePlugin<KParts::ReadOnlyPart>(
KPluginMetaData(QStringLiteral("kf6/parts/konsolepart")));
if (!result) {
qCCritical(KRDC_SSH) << "Failed to load KonsolePart:" << result.errorString;
return false;
}
m_terminalPart = result.plugin;
m_terminalPart->setParent(this);
// Embed the terminal widget into our layout
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_terminalPart->widget());
m_terminalPart->widget()->setFocusPolicy(Qt::WheelFocus);
setFocusProxy(m_terminalPart->widget());
return true;
}
QStringList SshView::buildSshCommand() const
{
QStringList args;
args << QStringLiteral("ssh");
// Add port if non-default
if (m_port != 22) {
args << QStringLiteral("-p") << QString::number(m_port);
}
// Identity file
const QString identityFile = m_hostPreferences->identityFile();
if (!identityFile.isEmpty()) {
args << QStringLiteral("-i") << identityFile;
}
// Options
if (m_hostPreferences->x11Forwarding()) {
args << QStringLiteral("-X");
}
if (m_hostPreferences->agentForwarding()) {
args << QStringLiteral("-A");
}
if (m_hostPreferences->compression()) {
args << QStringLiteral("-C");
}
// Port forwarding tunnels
const QStringList tunnelSpecs = m_hostPreferences->tunnels();
for (const QString &tunnel : tunnelSpecs) {
// Each tunnel is stored as "-L spec" or "-R spec" or "-D spec"
const QStringList parts = tunnel.split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (parts.size() == 2) {
args << parts.at(0) << parts.at(1);
}
}
// Build user@host
// Prefer username from preferences, fall back to URL username
QString user = m_hostPreferences->userName();
if (user.isEmpty()) {
user = m_url.userName();
}
QString target;
if (!user.isEmpty()) {
target = user + QLatin1Char('@') + m_host;
} else {
target = m_host;
}
args << target;
return args;
}
bool SshView::startConnection()
{
setStatus(Connecting);
if (!loadKonsolePart()) {
Q_EMIT errorMessage(i18n("SSH Error"),
i18n("Could not load the terminal emulator component (KonsolePart). "
"Please ensure Konsole is installed."));
return false;
}
auto *terminalInterface = qobject_cast<TerminalInterface *>(m_terminalPart);
if (!terminalInterface) {
qCCritical(KRDC_SSH) << "KonsolePart does not implement TerminalInterface";
Q_EMIT errorMessage(i18n("SSH Error"),
i18n("Terminal component does not support the required interface."));
return false;
}
// Build the SSH command
const QStringList sshCommand = buildSshCommand();
qCDebug(KRDC_SSH) << "Starting SSH with command:" << sshCommand;
const QString sshPath = QStandardPaths::findExecutable(QStringLiteral("ssh"));
if (sshPath.isEmpty()) {
Q_EMIT errorMessage(i18n("SSH Error"),
i18n("Could not find the ssh executable in PATH."));
return false;
}
// Connect to the terminal's finished signal to detect disconnection
connect(m_terminalPart, &KParts::ReadOnlyPart::destroyed, this, [this]() {
if (!m_quitting) {
setStatus(Disconnected);
Q_EMIT disconnected();
}
});
// Start SSH directly via startProgram for clean process lifecycle.
terminalInterface->startProgram(sshPath, sshCommand);
m_connected = true;
setStatus(Connected);
Q_EMIT connected();
Q_EMIT framebufferSizeChanged(m_framebufferSize.width(), m_framebufferSize.height());
return true;
}
void SshView::startQuittingConnection()
{
m_quitting = true;
if (m_terminalPart) {
// Disconnect signals to avoid callbacks during teardown
m_terminalPart->disconnect(this);
// Use deleteLater to let Qt process pending events for KonsolePart
// before destruction. Direct delete causes use-after-free in deferred
// child deletions.
m_terminalPart->deleteLater();
m_terminalPart = nullptr;
}
setStatus(Disconnected);
}
QSize SshView::framebufferSize()
{
if (m_terminalPart && m_terminalPart->widget()) {
return m_terminalPart->widget()->size();
}
return m_framebufferSize;
}
QSize SshView::sizeHint() const
{
return m_framebufferSize;
}
QSize SshView::minimumSizeHint() const
{
return QSize(320, 240);
}
bool SshView::isQuitting()
{
return m_quitting;
}
bool SshView::supportsScaling() const
{
return false;
}
bool SshView::supportsLocalCursor() const
{
return false;
}
bool SshView::supportsViewOnly() const
{
return false;
}
HostPreferences *SshView::hostPreferences()
{
return m_hostPreferences;
}
void SshView::scaleResize(int w, int h)
{
m_framebufferSize = QSize(w, h);
// Resize ourselves to fill the scroll area. The m_connected guard in
// resizeEvent prevents re-entrant crashes during the show sequence.
resize(w, h);
}
void SshView::paintEvent(QPaintEvent *event)
{
// KonsolePart handles its own painting via its embedded widget.
// We just let the base class handle the background if needed.
RemoteView::paintEvent(event);
}
void SshView::resizeEvent(QResizeEvent *event)
{
RemoteView::resizeEvent(event);
// Layout handles KonsolePart widget sizing.
}
void SshView::handleKeyEvent(QKeyEvent *event)
{
// Input is handled directly by KonsolePart's widget via focus proxy.
Q_UNUSED(event);
}
void SshView::handleWheelEvent(QWheelEvent *event)
{
// Scrollback handled by KonsolePart directly.
Q_UNUSED(event);
}
void SshView::handleMouseEvent(QMouseEvent *event)
{
// Mouse events handled by KonsolePart directly.
Q_UNUSED(event);
}
void SshView::handleLocalClipboardChanged(const QMimeData *data)
{
// KonsolePart handles clipboard internally.
Q_UNUSED(data);
}

69
src/sshview.h Normal file
View File

@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SSHVIEW_H
#define SSHVIEW_H
#include "remoteview.h"
#include <KConfigGroup>
#include <KParts/ReadOnlyPart>
class SshHostPreferences;
namespace KParts
{
class ReadOnlyPart;
}
/**
* SSH terminal view. Embeds KonsolePart to provide a full terminal emulator
* connected to a remote host via SSH.
*/
class SshView : public RemoteView
{
Q_OBJECT
public:
explicit SshView(QWidget *parent = nullptr, const QUrl &url = QUrl(), KConfigGroup configGroup = KConfigGroup());
~SshView() override;
QSize framebufferSize() override;
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
bool isQuitting() override;
bool supportsScaling() const override;
bool supportsLocalCursor() const override;
bool supportsViewOnly() const override;
HostPreferences *hostPreferences() override;
public Q_SLOTS:
void scaleResize(int w, int h) override;
protected:
bool startConnection() override;
void startQuittingConnection() override;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void handleKeyEvent(QKeyEvent *event) override;
void handleWheelEvent(QWheelEvent *event) override;
void handleMouseEvent(QMouseEvent *event) override;
void handleLocalClipboardChanged(const QMimeData *data) override;
private:
bool loadKonsolePart();
QStringList buildSshCommand() const;
KParts::ReadOnlyPart *m_terminalPart = nullptr;
SshHostPreferences *m_hostPreferences = nullptr;
bool m_quitting = false;
bool m_connected = false;
QSize m_framebufferSize;
};
#endif // SSHVIEW_H

64
src/sshviewfactory.cpp Normal file
View File

@ -0,0 +1,64 @@
/*
SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "sshviewfactory.h"
#include "sshhostpreferences.h"
#include "sshview.h"
#include <KLocalizedString>
#include <KPluginFactory>
K_PLUGIN_CLASS_WITH_JSON(SshViewFactory, "krdc_ssh.json")
SshViewFactory::SshViewFactory(QObject *parent, const QVariantList &args)
: RemoteViewFactory(parent)
{
Q_UNUSED(args);
KLocalizedString::setApplicationDomain("krdc");
}
SshViewFactory::~SshViewFactory()
{
}
bool SshViewFactory::supportsUrl(const QUrl &url) const
{
return (url.scheme().compare(QLatin1String("ssh"), Qt::CaseInsensitive) == 0);
}
RemoteView *SshViewFactory::createView(QWidget *parent, const QUrl &url, KConfigGroup configGroup)
{
return new SshView(parent, url, configGroup);
}
HostPreferences *SshViewFactory::createHostPreferences(KConfigGroup configGroup, QWidget *parent)
{
return new SshHostPreferences(configGroup, parent);
}
QString SshViewFactory::scheme() const
{
return QLatin1String("ssh");
}
QString SshViewFactory::connectActionText() const
{
return i18n("New SSH Connection\u2026");
}
QString SshViewFactory::connectButtonText() const
{
return i18n("Connect to an SSH Terminal");
}
QString SshViewFactory::connectToolTipText() const
{
return i18n(
"<html>Enter the address here.<br />"
"<i>Example: user@server (or server:port)</i></html>");
}
#include "sshviewfactory.moc"

29
src/sshviewfactory.h Normal file
View File

@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef SSHVIEWFACTORY_H
#define SSHVIEWFACTORY_H
#include "remoteviewfactory.h"
class SshViewFactory : public RemoteViewFactory
{
Q_OBJECT
public:
explicit SshViewFactory(QObject *parent, const QVariantList &args);
~SshViewFactory() override;
bool supportsUrl(const QUrl &url) const override;
RemoteView *createView(QWidget *parent, const QUrl &url, KConfigGroup configGroup) override;
HostPreferences *createHostPreferences(KConfigGroup configGroup, QWidget *parent) override;
QString scheme() const override;
QString connectActionText() const override;
QString connectButtonText() const override;
QString connectToolTipText() const override;
};
#endif // SSHVIEWFACTORY_H