File indexing completed on 2024-04-28 16:51:32

0001 /*
0002     SPDX-FileCopyrightText: 2017 Kai Uwe Broulik <kde@privat.broulik.de>
0003     SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org>
0004 
0005     SPDX-License-Identifier: MIT
0006 */
0007 
0008 #include "kdeconnectplugin.h"
0009 
0010 #include "connection.h"
0011 #include <QDBusConnection>
0012 #include <QDBusMessage>
0013 #include <QDBusPendingReply>
0014 
0015 static const QString s_kdeConnectServiceName = QStringLiteral("org.kde.kdeconnect");
0016 static const QString s_kdeConnectObjectPath = QStringLiteral("/modules/kdeconnect");
0017 
0018 static const QString s_kdeConnectDaemon = QStringLiteral("org.kde.kdeconnect.daemon");
0019 
0020 KDEConnectPlugin::KDEConnectPlugin(QObject *parent)
0021     : AbstractBrowserPlugin(QStringLiteral("kdeconnect"), 1, parent)
0022 {
0023 }
0024 
0025 bool KDEConnectPlugin::onLoad()
0026 {
0027     QDBusConnection bus = QDBusConnection::sessionBus();
0028 
0029     bus.connect(s_kdeConnectServiceName, s_kdeConnectObjectPath, s_kdeConnectDaemon, QStringLiteral("deviceAdded"), this, SLOT(onDeviceAdded(QString)));
0030     bus.connect(s_kdeConnectServiceName, s_kdeConnectObjectPath, s_kdeConnectDaemon, QStringLiteral("deviceRemoved"), this, SLOT(onDeviceRemoved(QString)));
0031     bus.connect(s_kdeConnectServiceName,
0032                 s_kdeConnectObjectPath,
0033                 s_kdeConnectDaemon,
0034                 QStringLiteral("deviceVisibilityChanged"),
0035                 this,
0036                 SLOT(onDeviceVisibilityChanged(QString, bool)));
0037 
0038     QDBusMessage msg = QDBusMessage::createMethodCall(s_kdeConnectServiceName, s_kdeConnectObjectPath, s_kdeConnectDaemon, QStringLiteral("devices"));
0039     msg.setArguments({true /* only reachable */, true /* only paired */});
0040     QDBusPendingReply<QStringList> reply = bus.asyncCall(msg);
0041     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
0042     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0043         QDBusPendingReply<QStringList> reply = *watcher;
0044         watcher->deleteLater();
0045         if (reply.isError()) {
0046             debug() << "kdeconnect discovery failed:" << reply.error().name();
0047             return;
0048         }
0049 
0050         const QStringList devices = reply.value();
0051         for (const QString &deviceId : devices) {
0052             onDeviceAdded(deviceId);
0053         }
0054     });
0055     return true;
0056 }
0057 
0058 bool KDEConnectPlugin::onUnload()
0059 {
0060     QDBusConnection bus = QDBusConnection::sessionBus();
0061 
0062     bus.disconnect(s_kdeConnectServiceName, s_kdeConnectObjectPath, s_kdeConnectDaemon, QStringLiteral("deviceAdded"), this, SLOT(onDeviceAdded(QString)));
0063     bus.disconnect(s_kdeConnectServiceName, s_kdeConnectObjectPath, s_kdeConnectDaemon, QStringLiteral("deviceRemoved"), this, SLOT(onDeviceRemoved(QString)));
0064     bus.disconnect(s_kdeConnectServiceName,
0065                    s_kdeConnectObjectPath,
0066                    s_kdeConnectDaemon,
0067                    QStringLiteral("deviceVisibilityChanged"),
0068                    this,
0069                    SLOT(onDeviceVisibilityChanged(QString, bool)));
0070 
0071     const QStringList devices = m_devices;
0072     for (const QString &deviceId : devices) {
0073         onDeviceRemoved(deviceId);
0074     }
0075     return true;
0076 }
0077 
0078 void KDEConnectPlugin::onDeviceAdded(const QString &deviceId)
0079 {
0080     if (m_devices.contains(deviceId)) {
0081         return;
0082     }
0083 
0084     QDBusMessage msg = QDBusMessage::createMethodCall(s_kdeConnectServiceName,
0085                                                       QStringLiteral("/modules/kdeconnect/devices/") + deviceId,
0086                                                       QStringLiteral("org.freedesktop.DBus.Properties"),
0087                                                       QStringLiteral("GetAll"));
0088     msg.setArguments({QStringLiteral("org.kde.kdeconnect.device")});
0089     QDBusPendingReply<QVariantMap> reply = QDBusConnection::sessionBus().asyncCall(msg);
0090     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
0091 
0092     QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, deviceId](QDBusPendingCallWatcher *watcher) {
0093         QDBusPendingReply<QVariantMap> reply = *watcher;
0094         watcher->deleteLater();
0095 
0096         if (reply.isError()) {
0097             debug() << "getting device for properties for" << deviceId << "failed:" << reply.error().message();
0098             return;
0099         }
0100 
0101         // We might have gotten a second deviceAdded signal, check again.
0102         if (m_devices.contains(deviceId)) {
0103             return;
0104         }
0105 
0106         QVariantMap props = reply.value();
0107 
0108         // Also check isTrusted for compatibility with older KDEConnect
0109         const bool paired = props.value(QStringLiteral("isPaired")).toBool() || props.value(QStringLiteral("isTrusted")).toBool();
0110         if (!props.value(QStringLiteral("isReachable")).toBool() || !paired) {
0111             return;
0112         }
0113 
0114         props.insert(QStringLiteral("id"), deviceId);
0115 
0116         m_devices.append(deviceId);
0117         sendData(QStringLiteral("deviceAdded"), QJsonObject::fromVariantMap(props));
0118     });
0119 }
0120 
0121 void KDEConnectPlugin::onDeviceRemoved(const QString &deviceId)
0122 {
0123     if (m_devices.removeOne(deviceId)) {
0124         sendData(QStringLiteral("deviceRemoved"), {{QStringLiteral("id"), deviceId}});
0125     }
0126 }
0127 
0128 void KDEConnectPlugin::onDeviceVisibilityChanged(const QString &deviceId, bool visible)
0129 {
0130     if (visible) {
0131         onDeviceAdded(deviceId);
0132     } else {
0133         onDeviceRemoved(deviceId);
0134     }
0135 }
0136 
0137 void KDEConnectPlugin::handleData(const QString &event, const QJsonObject &json)
0138 {
0139     if (event == QLatin1String("shareUrl")) {
0140         const QString deviceId = json.value(QStringLiteral("deviceId")).toString();
0141         const QString url = json.value(QStringLiteral("url")).toString();
0142 
0143         debug() << "sending kde connect url" << url << "to device" << deviceId;
0144 
0145         QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"),
0146                                                           QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/share"),
0147                                                           QStringLiteral("org.kde.kdeconnect.device.share"),
0148                                                           QStringLiteral("shareUrl"));
0149         msg.setArguments({url});
0150         QDBusConnection::sessionBus().call(msg, QDBus::NoBlock);
0151     }
0152 }