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) } + }