From ac239c194398a435b98024eea01b39456a1f2cee Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:00:46 +0200 Subject: [PATCH] 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. --- .gitignore | 1 + CMakeLists.txt | 56 ++++++++ PLAN.md | 27 ++++ src/CMakeLists.txt | 36 +++++ src/krdc_ssh.json | 16 +++ src/sshhostpreferences.cpp | 164 ++++++++++++++++++++++ src/sshhostpreferences.h | 51 +++++++ src/sshpreferences.ui | 174 +++++++++++++++++++++++ src/sshview.cpp | 277 +++++++++++++++++++++++++++++++++++++ src/sshview.h | 69 +++++++++ src/sshviewfactory.cpp | 64 +++++++++ src/sshviewfactory.h | 29 ++++ 12 files changed, 964 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 PLAN.md create mode 100644 src/CMakeLists.txt create mode 100644 src/krdc_ssh.json create mode 100644 src/sshhostpreferences.cpp create mode 100644 src/sshhostpreferences.h create mode 100644 src/sshpreferences.ui create mode 100644 src/sshview.cpp create mode 100644 src/sshview.h create mode 100644 src/sshviewfactory.cpp create mode 100644 src/sshviewfactory.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a5ba17d --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..5fc495d --- /dev/null +++ b/PLAN.md @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..6f016ad --- /dev/null +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/krdc_ssh.json b/src/krdc_ssh.json new file mode 100644 index 0000000..3737016 --- /dev/null +++ b/src/krdc_ssh.json @@ -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 +} diff --git a/src/sshhostpreferences.cpp b/src/sshhostpreferences.cpp new file mode 100644 index 0000000..fe2b0db --- /dev/null +++ b/src/sshhostpreferences.cpp @@ -0,0 +1,164 @@ +/* + SPDX-FileCopyrightText: 2024 KRDC SSH Plugin Authors + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "sshhostpreferences.h" + +#include + +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); +} diff --git a/src/sshhostpreferences.h b/src/sshhostpreferences.h new file mode 100644 index 0000000..60dc287 --- /dev/null +++ b/src/sshhostpreferences.h @@ -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 diff --git a/src/sshpreferences.ui b/src/sshpreferences.ui new file mode 100644 index 0000000..ac0709b --- /dev/null +++ b/src/sshpreferences.ui @@ -0,0 +1,174 @@ + + + SshPreferences + + + + 0 + 0 + 480 + 500 + + + + + + + Connection + + + + + + &Username: + + + usernameEdit + + + + + + + Leave empty to use current user + + + + + + + &Identity file: + + + identityFileEdit + + + + + + + Default (~/.ssh/id_*) + + + + + + + + + + Options + + + + + + Enable &X11 forwarding (-X) + + + + + + + Enable &agent forwarding (-A) + + + + + + + Enable &compression (-C) + + + + + + + + + + Port Forwarding + + + + + + + + + Local (-L) + + + + + Remote (-R) + + + + + Dynamic/SOCKS (-D) + + + + + + + + e.g. 8080:localhost:80 + + + + + + + Add + + + + + + + + + + 16777215 + 120 + + + + + + + + Remove Selected + + + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + KUrlRequester + QWidget +
kurlrequester.h
+
+
+ + +
diff --git a/src/sshview.cpp b/src/sshview.cpp new file mode 100644 index 0000000..ca89730 --- /dev/null +++ b/src/sshview.cpp @@ -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 +#include +#include +#include + +#include + +#include +#include +#include +#include + +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( + 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(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); +} diff --git a/src/sshview.h b/src/sshview.h new file mode 100644 index 0000000..4ff960d --- /dev/null +++ b/src/sshview.h @@ -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 +#include + +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 diff --git a/src/sshviewfactory.cpp b/src/sshviewfactory.cpp new file mode 100644 index 0000000..6b2f35d --- /dev/null +++ b/src/sshviewfactory.cpp @@ -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 +#include + +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( + "Enter the address here.
" + "Example: user@server (or server:port)"); +} + +#include "sshviewfactory.moc" diff --git a/src/sshviewfactory.h b/src/sshviewfactory.h new file mode 100644 index 0000000..ec8c96e --- /dev/null +++ b/src/sshviewfactory.h @@ -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