From 144a60b58a8bc8aceffbae2a74ecef57205afc8b Mon Sep 17 00:00:00 2001 From: Albert Vaca Cintora Date: Wed, 7 Jun 2023 19:48:25 +0000 Subject: [PATCH] Allow disabling clipboard auto-share and add option to share manually Continues the work started in !396 by rebasing it onto latest master and making the "send clipboard" button from the plasmoid invisible when automatic syncing is enabled. I didn't find a way to do the same in kdeconnect-indicator and kdeconnect-app (why do we have 3 UIs???), so in those we always show the option for now. --- app/qml/DevicePage.qml | 7 +++ cli/kdeconnect-cli.cpp | 7 +++ .../kdeconnectdeclarativeplugin.cpp | 2 + indicator/deviceindicator.cpp | 14 +++++ interfaces/CMakeLists.txt | 1 + interfaces/dbusinterfaces.cpp | 14 +++++ interfaces/dbusinterfaces.h | 14 ++++- plasmoid/package/contents/ui/Clipboard.qml | 39 ++++++++++++++ .../package/contents/ui/DeviceDelegate.qml | 18 +++++++ plugins/clipboard/CMakeLists.txt | 1 + plugins/clipboard/clipboard_config.cpp | 20 +++++--- plugins/clipboard/clipboard_config.h | 1 + plugins/clipboard/clipboard_config.ui | 10 ++-- plugins/clipboard/clipboardplugin.cpp | 51 +++++++++++++++---- plugins/clipboard/clipboardplugin.h | 19 ++++++- .../clipboard/kdeconnect_clipboard_config.qml | 15 +++--- 16 files changed, 200 insertions(+), 33 deletions(-) create mode 100644 plasmoid/package/contents/ui/Clipboard.qml diff --git a/app/qml/DevicePage.qml b/app/qml/DevicePage.qml index 4a8fb0c6f..f7e796852 100644 --- a/app/qml/DevicePage.qml +++ b/app/qml/DevicePage.qml @@ -98,6 +98,13 @@ Kirigami.ScrollablePage { pluginName: "remotecommands" device: root.currentDevice }, + PluginItem { + readonly property var clipboardIface: ClipboardDbusInterfaceFactory.create(root.currentDevice.id()) + pluginName: "clipboard" + name: i18nd("kdeconnect-app", "Send Clipboard") + onClick: () => clipboardIface.sendClipboard() + device: root.currentDevice + }, PluginItem { pluginName: "share" name: i18nd("kdeconnect-app", "Share File") diff --git a/cli/kdeconnect-cli.cpp b/cli/kdeconnect-cli.cpp index 180752a34..2093400e8 100644 --- a/cli/kdeconnect-cli.cpp +++ b/cli/kdeconnect-cli.cpp @@ -51,6 +51,7 @@ int main(int argc, char **argv) parser.addOption(QCommandLineOption(QStringLiteral("unpair"), i18n("Stop pairing to a said device"))); parser.addOption(QCommandLineOption(QStringLiteral("ping"), i18n("Sends a ping to said device"))); parser.addOption(QCommandLineOption(QStringLiteral("ping-msg"), i18n("Same as ping but you can set the message to display"), i18n("message"))); + parser.addOption(QCommandLineOption(QStringLiteral("send-clipboard"), i18n("Sends the current clipboard to said device"))); parser.addOption(QCommandLineOption(QStringLiteral("share"), i18n("Share a file/URL to a said device"), QStringLiteral("path or URL"))); parser.addOption(QCommandLineOption(QStringLiteral("share-text"), i18n("Share text to a said device"), QStringLiteral("text"))); parser.addOption(QCommandLineOption(QStringLiteral("list-notifications"), i18n("Display the notifications on a said device"))); @@ -264,6 +265,12 @@ int main(int argc, char **argv) QTextStream(stderr) << i18n("Unpaired") << Qt::endl; blockOnReply(dev.unpair()); } + } else if (parser.isSet(QStringLiteral("send-clipboard"))) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), + QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/clipboard"), + QStringLiteral("org.kde.kdeconnect.device.clipboard"), + QStringLiteral("sendClipboard")); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); } else if (parser.isSet(QStringLiteral("ping")) || parser.isSet(QStringLiteral("ping-msg"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/ping"), diff --git a/declarativeplugin/kdeconnectdeclarativeplugin.cpp b/declarativeplugin/kdeconnectdeclarativeplugin.cpp index ecfa016cc..60fa1e7c1 100644 --- a/declarativeplugin/kdeconnectdeclarativeplugin.cpp +++ b/declarativeplugin/kdeconnectdeclarativeplugin.cpp @@ -64,6 +64,7 @@ void KdeConnectDeclarativePlugin::registerTypes(const char *uri) 0, "FindMyPhoneDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); + qmlRegisterUncreatableType(uri, 1, 0, "ClipboardDbusInterface", QStringLiteral("You're not supposed to instantiate interfaces")); qmlRegisterUncreatableType(uri, 1, 0, @@ -113,6 +114,7 @@ void KdeConnectDeclarativePlugin::registerTypes(const char *uri) registerFactory(uri, "FindMyPhoneDbusInterfaceFactory"); registerFactory(uri, "SftpDbusInterfaceFactory"); registerFactory(uri, "RemoteKeyboardDbusInterfaceFactory"); + registerFactory(uri, "ClipboardDbusInterfaceFactory"); registerFactory(uri, "MprisDbusInterfaceFactory"); registerFactory(uri, "RemoteControlDbusInterfaceFactory"); registerFactory(uri, "LockDeviceDbusInterfaceFactory"); diff --git a/indicator/deviceindicator.cpp b/indicator/deviceindicator.cpp index 5fa99cb5f..ecec3f536 100644 --- a/indicator/deviceindicator.cpp +++ b/indicator/deviceindicator.cpp @@ -63,6 +63,20 @@ DeviceIndicator::DeviceIndicator(DeviceDbusInterface *device) }, this); + // Clipboard + auto clipboard = addAction(QIcon::fromTheme(QStringLiteral("klipper")), i18n("Send clipboard")); + connect(clipboard, &QAction::triggered, device, [device]() { + ClipboardDbusInterface *clipboardIface = new ClipboardDbusInterface(device->id(), device); + clipboardIface->sendClipboard(); + clipboardIface->deleteLater(); + }); + setWhenAvailable( + device->hasPlugin(QStringLiteral("kdeconnect_clipboard")), + [clipboard](bool available) { + clipboard->setVisible(available); + }, + this); + // Find device auto findDevice = addAction(QIcon::fromTheme(QStringLiteral("irc-voice")), i18n("Ring device")); connect(findDevice, &QAction::triggered, device, [device]() { diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt index 311256dce..c4ca2b6e5 100644 --- a/interfaces/CMakeLists.txt +++ b/interfaces/CMakeLists.txt @@ -58,6 +58,7 @@ geninterface(${PROJECT_SOURCE_DIR}/plugins/remotesystemvolume/remotesystemvolume geninterface(${PROJECT_SOURCE_DIR}/plugins/bigscreen/bigscreenplugin.h bigscreeninterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/virtualmonitor/virtualmonitorplugin.h virtualmonitorinterface) geninterface(${PROJECT_SOURCE_DIR}/plugins/photo/photoplugin.h photointerface) +geninterface(${PROJECT_SOURCE_DIR}/plugins/clipboard/clipboardplugin.h deviceclipboardinterface) add_library(kdeconnectinterfaces ${libkdeconnect_SRC}) set_target_properties(kdeconnectinterfaces PROPERTIES diff --git a/interfaces/dbusinterfaces.cpp b/interfaces/dbusinterfaces.cpp index 5c1058f5e..de94216ec 100644 --- a/interfaces/dbusinterfaces.cpp +++ b/interfaces/dbusinterfaces.cpp @@ -261,3 +261,17 @@ VirtualmonitorDbusInterface::VirtualmonitorDbusInterface(const QString &deviceId VirtualmonitorDbusInterface::~VirtualmonitorDbusInterface() { } + +#include +ClipboardDbusInterface::ClipboardDbusInterface(const QString &deviceId, QObject *parent) + : OrgKdeKdeconnectDeviceClipboardInterface(DaemonDbusInterface::activatedService(), + QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/clipboard"), + QDBusConnection::sessionBus(), + parent) +{ + connect(this, &OrgKdeKdeconnectDeviceClipboardInterface::autoShareDisabledChanged, this, &ClipboardDbusInterface::autoShareDisabledChangedProxy); +} + +ClipboardDbusInterface::~ClipboardDbusInterface() +{ +} diff --git a/interfaces/dbusinterfaces.h b/interfaces/dbusinterfaces.h index b135b257b..4e6daf140 100644 --- a/interfaces/dbusinterfaces.h +++ b/interfaces/dbusinterfaces.h @@ -14,6 +14,7 @@ #include "connectivityinterface.h" #include "conversationsinterface.h" #include "daemoninterface.h" +#include "deviceclipboardinterface.h" #include "devicefindmyphoneinterface.h" #include "deviceinterface.h" #include "devicenotificationsinterface.h" @@ -21,6 +22,7 @@ #include "lockdeviceinterface.h" #include "mprisremoteinterface.h" #include "notificationinterface.h" +#include "photointerface.h" #include "remotecommandsinterface.h" #include "remotecontrolinterface.h" #include "remotekeyboardinterface.h" @@ -28,7 +30,6 @@ #include "shareinterface.h" #include "smsinterface.h" #include "virtualmonitorinterface.h" -#include "photointerface.h" /** * Using these "proxy" classes just in case we need to rename the @@ -276,4 +277,15 @@ static void setWhenAvailable(const QDBusPendingReply &pending, W func, QObjec }); } +class KDECONNECTINTERFACES_EXPORT ClipboardDbusInterface : public OrgKdeKdeconnectDeviceClipboardInterface +{ + Q_OBJECT + Q_PROPERTY(bool isAutoShareDisabled READ isAutoShareDisabled NOTIFY autoShareDisabledChangedProxy) +public: + explicit ClipboardDbusInterface(const QString &deviceId, QObject *parent = nullptr); + ~ClipboardDbusInterface() override; +Q_SIGNALS: + void autoShareDisabledChangedProxy(bool b); +}; + #endif diff --git a/plasmoid/package/contents/ui/Clipboard.qml b/plasmoid/package/contents/ui/Clipboard.qml new file mode 100644 index 000000000..6f48a68c1 --- /dev/null +++ b/plasmoid/package/contents/ui/Clipboard.qml @@ -0,0 +1,39 @@ +/** + * SPDX-FileCopyrightText: 2021 Yaman Qalieh + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +import QtQuick 2.1 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.kdeconnect 1.0 + +QtObject { + + id: root + + property alias device: checker.device + readonly property alias available: checker.available + + readonly property PluginChecker pluginChecker: PluginChecker { + id: checker + pluginName: "clipboard" + } + + property variant clipboard: null + + function sendClipboard() { + if (clipboard) { + clipboard.sendClipboard(); + } + } + + onAvailableChanged: { + if (available) { + clipboard = ClipboardDbusInterfaceFactory.create(device.id()) + } else { + clipboard = null + } + } +} diff --git a/plasmoid/package/contents/ui/DeviceDelegate.qml b/plasmoid/package/contents/ui/DeviceDelegate.qml index 9cf829edc..06b64aa5f 100644 --- a/plasmoid/package/contents/ui/DeviceDelegate.qml +++ b/plasmoid/package/contents/ui/DeviceDelegate.qml @@ -170,6 +170,24 @@ PlasmaComponents.ListItem onClicked: fileDialog.open() } + //Clipboard + PlasmaComponents.MenuItem + { + Clipboard { + id: clipboard + device: root.device + } + + id: sendclipboard + icon: "klipper" + visible: clipboard.available && clipboard.clipboard.isAutoShareDisabled + text: i18n("Send Clipboard") + + onClicked: { + clipboard.sendClipboard() + } + } + //Photo PlasmaComponents.MenuItem { diff --git a/plugins/clipboard/CMakeLists.txt b/plugins/clipboard/CMakeLists.txt index 5a118fc21..d610ee8f0 100644 --- a/plugins/clipboard/CMakeLists.txt +++ b/plugins/clipboard/CMakeLists.txt @@ -16,6 +16,7 @@ kdeconnect_add_plugin(kdeconnect_clipboard SOURCES ${kdeconnect_clipboard_SRCS}) target_link_libraries(kdeconnect_clipboard kdeconnectcore KF5::GuiAddons + Qt${QT_MAJOR_VERSION}::DBus ${kdeconnect_clipboard_WL_LINK_LIBS} ) diff --git a/plugins/clipboard/clipboard_config.cpp b/plugins/clipboard/clipboard_config.cpp index b07073873..080a067b6 100644 --- a/plugins/clipboard/clipboard_config.cpp +++ b/plugins/clipboard/clipboard_config.cpp @@ -17,7 +17,7 @@ ClipboardConfig::ClipboardConfig(QWidget* parent, const QVariantList &args) { m_ui->setupUi(this); - connect(m_ui->check_unknown, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(m_ui->check_autoshare, SIGNAL(toggled(bool)), this, SLOT(autoShareChanged())); connect(m_ui->check_password, SIGNAL(toggled(bool)), this, SLOT(changed())); } @@ -26,10 +26,16 @@ ClipboardConfig::~ClipboardConfig() delete m_ui; } +void ClipboardConfig::autoShareChanged() +{ + m_ui->check_password->setEnabled(m_ui->check_autoshare->isChecked()); + Q_EMIT changed(); +} + void ClipboardConfig::defaults() { KCModule::defaults(); - m_ui->check_unknown->setChecked(true); + m_ui->check_autoshare->setChecked(true); m_ui->check_password->setChecked(true); Q_EMIT changed(true); } @@ -37,17 +43,17 @@ void ClipboardConfig::defaults() void ClipboardConfig::load() { KCModule::load(); - bool unknown = config()->getBool(QStringLiteral("sendUnknown"), true); + // "sendUnknown" is the legacy name for this setting + bool autoShare = config()->getBool(QStringLiteral("autoShare"), config()->getBool(QStringLiteral("sendUnknown"), true)); bool password = config()->getBool(QStringLiteral("sendPassword"), true); - m_ui->check_unknown->setChecked(unknown); + m_ui->check_autoshare->setChecked(autoShare); m_ui->check_password->setChecked(password); - - Q_EMIT changed(false); + autoShareChanged(); } void ClipboardConfig::save() { - config()->set(QStringLiteral("sendUnknown"), m_ui->check_unknown->isChecked()); + config()->set(QStringLiteral("autoShare"), m_ui->check_autoshare->isChecked()); config()->set(QStringLiteral("sendPassword"), m_ui->check_password->isChecked()); KCModule::save(); Q_EMIT changed(false); diff --git a/plugins/clipboard/clipboard_config.h b/plugins/clipboard/clipboard_config.h index c30e23f29..34179600a 100644 --- a/plugins/clipboard/clipboard_config.h +++ b/plugins/clipboard/clipboard_config.h @@ -25,6 +25,7 @@ public Q_SLOTS: void save() override; void load() override; void defaults() override; + void autoShareChanged(); private: Ui::ClipboardConfigUi *m_ui; diff --git a/plugins/clipboard/clipboard_config.ui b/plugins/clipboard/clipboard_config.ui index 25398b350..acf9bd14a 100644 --- a/plugins/clipboard/clipboard_config.ui +++ b/plugins/clipboard/clipboard_config.ui @@ -29,20 +29,20 @@ - Contents shared to other devices + Automatic synchronization - + - Passwords (as marked by password managers) + Automatically share the clipboard from this device - + - Anything else + Including passwords (as marked by password managers) diff --git a/plugins/clipboard/clipboardplugin.cpp b/plugins/clipboard/clipboardplugin.cpp index f20119cca..771cc9c6e 100644 --- a/plugins/clipboard/clipboardplugin.cpp +++ b/plugins/clipboard/clipboardplugin.cpp @@ -16,7 +16,9 @@ K_PLUGIN_CLASS_WITH_JSON(ClipboardPlugin, "kdeconnect_clipboard.json") ClipboardPlugin::ClipboardPlugin(QObject *parent, const QVariantList &args) : KdeConnectPlugin(parent, args) { - connect(ClipboardListener::instance(), &ClipboardListener::clipboardChanged, this, &ClipboardPlugin::propagateClipboard); + connect(ClipboardListener::instance(), &ClipboardListener::clipboardChanged, this, &ClipboardPlugin::clipboardChanged); + connect(config(), &KdeConnectPluginConfig::configChanged, this, &ClipboardPlugin::configChanged); + configChanged(); } void ClipboardPlugin::connected() @@ -24,20 +26,47 @@ void ClipboardPlugin::connected() sendConnectPacket(); } -void ClipboardPlugin::propagateClipboard(const QString &content, ClipboardListener::ClipboardContentType contentType) +QString ClipboardPlugin::dbusPath() const { - if (contentType == ClipboardListener::ClipboardContentTypeUnknown) { - if (!config()->getBool(QStringLiteral("sendUnknown"), true)) { - return; - } - } else if (contentType == ClipboardListener::ClipboardContentTypePassword) { - if (!config()->getBool(QStringLiteral("sendPassword"), true)) { - return; - } - } else { + return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/clipboard"); +} + +void ClipboardPlugin::clipboardChanged(const QString &content, ClipboardListener::ClipboardContentType contentType) +{ + if (!autoShare) { return; } + if (contentType == ClipboardListener::ClipboardContentTypePassword) { + if (!sharePasswords) { + return; + } + } + + sendClipboard(content); +} + +void ClipboardPlugin::configChanged() +{ + autoShare = config()->getBool(QStringLiteral("autoShare"), config()->getBool(QStringLiteral("sendUnknown"), true)); + sharePasswords = config()->getBool(QStringLiteral("sendPassword"), true); + Q_EMIT autoShareDisabledChanged(isAutoShareDisabled()); +} + +bool ClipboardPlugin::isAutoShareDisabled() +{ + // Return true also if autoShare is enabled but disabled for passwords + return !autoShare || !sharePasswords; +} + +void ClipboardPlugin::sendClipboard() +{ + QString content = ClipboardListener::instance()->currentContent(); + sendClipboard(content); +} + +void ClipboardPlugin::sendClipboard(const QString &content) +{ NetworkPacket np(PACKET_TYPE_CLIPBOARD, {{QStringLiteral("content"), content}}); sendPacket(np); } diff --git a/plugins/clipboard/clipboardplugin.h b/plugins/clipboard/clipboardplugin.h index 24c38604a..4da26b566 100644 --- a/plugins/clipboard/clipboardplugin.h +++ b/plugins/clipboard/clipboardplugin.h @@ -41,15 +41,30 @@ class ClipboardPlugin : public KdeConnectPlugin { Q_OBJECT - + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.clipboard") + Q_PROPERTY(bool isAutoShareDisabled READ isAutoShareDisabled NOTIFY autoShareDisabledChanged) public: explicit ClipboardPlugin(QObject *parent, const QVariantList &args); + Q_SCRIPTABLE void sendClipboard(); + Q_SCRIPTABLE void sendClipboard(const QString &content); + QString dbusPath() const override; + bool receivePacket(const NetworkPacket &np) override; void connected() override; + bool isAutoShareDisabled(); + +Q_SIGNALS: + Q_SCRIPTABLE void autoShareDisabledChanged(bool b); + private Q_SLOTS: - void propagateClipboard(const QString &content, ClipboardListener::ClipboardContentType contentType); + void clipboardChanged(const QString &content, ClipboardListener::ClipboardContentType contentType); void sendConnectPacket(); + void configChanged(); + +private: + bool autoShare; + bool sharePasswords; }; #endif diff --git a/plugins/clipboard/kdeconnect_clipboard_config.qml b/plugins/clipboard/kdeconnect_clipboard_config.qml index fa2b937fd..dfd83ebb3 100644 --- a/plugins/clipboard/kdeconnect_clipboard_config.qml +++ b/plugins/clipboard/kdeconnect_clipboard_config.qml @@ -21,19 +21,20 @@ Kirigami.FormLayout { } Component.onCompleted: { - unknown.checked = config.getBool("sendUnknown", true) + autoShare.checked = config.getBool("autoShare", config.getBool("sendUnknown", true)) password.checked = config.getBool("sendPassword", true) } QQC2.CheckBox { - id: password - text: i18n("Passwords (as marked by password managers)") - onClicked: config.set("sendPassword", checked) + id: autoShare + text: i18n("Automatically share the clipboard from this device") + onClicked: config.set("autoShare", checked) } QQC2.CheckBox { - id: unknown - text: i18n("Anything else") - onClicked: config.set("sendUnknown", checked) + id: password + text: i18n("Including passwords (as marked by password managers)") + onClicked: config.set("sendPassword", checked) } + }