diff --git a/cli/kdeconnect-cli.cpp b/cli/kdeconnect-cli.cpp index 40dd6c50..c3324cc9 100644 --- a/cli/kdeconnect-cli.cpp +++ b/cli/kdeconnect-cli.cpp @@ -1,317 +1,317 @@ /* * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "interfaces/devicesmodel.h" #include "interfaces/notificationsmodel.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/dbushelpers.h" #include "kdeconnect-version.h" #include int main(int argc, char** argv) { QCoreApplication app(argc, argv); KAboutData about(QStringLiteral("kdeconnect-cli"), QStringLiteral("kdeconnect-cli"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect CLI tool"), KAboutLicense::GPL, i18n("(C) 2015 Aleix Pol Gonzalez")); KAboutData::setApplicationData(about); about.addAuthor( i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org") ); about.addAuthor( i18n("Albert Vaca Cintora"), QString(), QStringLiteral("albertvaka@gmail.com") ); QCommandLineParser parser; parser.addOption(QCommandLineOption(QStringList(QStringLiteral("l")) << QStringLiteral("list-devices"), i18n("List all devices"))); parser.addOption(QCommandLineOption(QStringList(QStringLiteral("a")) << QStringLiteral("list-available"), i18n("List available (paired and reachable) devices"))); parser.addOption(QCommandLineOption(QStringLiteral("id-only"), i18n("Make --list-devices or --list-available print only the devices id, to ease scripting"))); parser.addOption(QCommandLineOption(QStringLiteral("name-only"), i18n("Make --list-devices or --list-available print only the devices name, to ease scripting"))); parser.addOption(QCommandLineOption(QStringLiteral("id-name-only"), i18n("Make --list-devices or --list-available print only the devices id and name, to ease scripting"))); parser.addOption(QCommandLineOption(QStringLiteral("refresh"), i18n("Search for devices in the network and re-establish connections"))); parser.addOption(QCommandLineOption(QStringLiteral("pair"), i18n("Request pairing to a said device"))); parser.addOption(QCommandLineOption(QStringLiteral("ring"), i18n("Find the said device by ringing it."))); 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("share"), i18n("Share a file to a said device"), QStringLiteral("path"))); 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"))); parser.addOption(QCommandLineOption(QStringLiteral("lock"), i18n("Lock the specified device"))); parser.addOption(QCommandLineOption(QStringLiteral("send-sms"), i18n("Sends an SMS. Requires destination"), i18n("message"))); parser.addOption(QCommandLineOption(QStringLiteral("destination"), i18n("Phone number to send the message"), i18n("phone number"))); parser.addOption(QCommandLineOption(QStringList(QStringLiteral("device")) << QStringLiteral("d"), i18n("Device ID"), QStringLiteral("dev"))); parser.addOption(QCommandLineOption(QStringList(QStringLiteral("name")) << QStringLiteral("n"), i18n("Device Name"), QStringLiteral("name"))); parser.addOption(QCommandLineOption(QStringLiteral("encryption-info"), i18n("Get encryption info about said device"))); parser.addOption(QCommandLineOption(QStringLiteral("list-commands"), i18n("Lists remote commands and their ids"))); parser.addOption(QCommandLineOption(QStringLiteral("execute-command"), i18n("Executes a remote command by id"), QStringLiteral("id"))); parser.addOption(QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("send-keys")}, i18n("Sends keys to a said device"), QStringLiteral("key"))); parser.addOption(QCommandLineOption(QStringLiteral("my-id"), i18n("Display this device's id and exit"))); parser.addOption(QCommandLineOption(QStringLiteral("photo"), i18n("Open the connected device's camera and transfer the photo"))); //Hidden because it's an implementation detail QCommandLineOption deviceAutocomplete(QStringLiteral("shell-device-autocompletion")); deviceAutocomplete.setFlags(QCommandLineOption::HiddenFromHelp); deviceAutocomplete.setDescription(QStringLiteral("Outputs all available devices id's with their name and paired status")); //Not visible, so no translation needed deviceAutocomplete.setValueName(QStringLiteral("shell")); parser.addOption(deviceAutocomplete); about.setupCommandLine(&parser); parser.addHelpOption(); parser.process(app); about.processCommandLine(&parser); const QString id = QStringLiteral("kdeconnect-cli-") + QString::number(QCoreApplication::applicationPid()); DaemonDbusInterface iface; if (parser.isSet(QStringLiteral("my-id"))) { QTextStream(stdout) << iface.selfId() << endl; } else if (parser.isSet(QStringLiteral("l")) || parser.isSet(QStringLiteral("a"))) { bool reachable = false; if (parser.isSet(QStringLiteral("a"))) { reachable = true; } else { blockOnReply(iface.acquireDiscoveryMode(id)); QThread::sleep(2); } const QStringList devices = blockOnReply(iface.devices(reachable, false)); bool displayCount = true; for (const QString& id : devices) { if (parser.isSet(QStringLiteral("id-only"))) { QTextStream(stdout) << id << endl; displayCount = false; } else if (parser.isSet(QStringLiteral("name-only"))) { DeviceDbusInterface deviceIface(id); QTextStream(stdout) << deviceIface.name() << endl; displayCount = false; } else if (parser.isSet(QStringLiteral("id-name-only"))) { DeviceDbusInterface deviceIface(id); QTextStream(stdout) << id << ' ' << deviceIface.name() << endl; displayCount = false; } else { DeviceDbusInterface deviceIface(id); QString statusInfo; const bool isReachable = deviceIface.isReachable(); const bool isTrusted = deviceIface.isTrusted(); if (isReachable && isTrusted) { statusInfo = i18n("(paired and reachable)"); } else if (isReachable) { statusInfo = i18n("(reachable)"); } else if (isTrusted) { statusInfo = i18n("(paired)"); } QTextStream(stdout) << "- " << deviceIface.name() << ": " << deviceIface.id() << ' ' << statusInfo << endl; } } if (displayCount) { QTextStream(stderr) << i18np("1 device found", "%1 devices found", devices.size()) << endl; } else if (devices.isEmpty()) { QTextStream(stderr) << i18n("No devices found") << endl; } blockOnReply(iface.releaseDiscoveryMode(id)); } else if (parser.isSet(QStringLiteral("shell-device-autocompletion"))) { //Outputs a list of reachable devices in zsh autocomplete format, with the name as description const QStringList devices = blockOnReply(iface.devices(true, false)); for (const QString &id : devices) { DeviceDbusInterface deviceIface(id); QString statusInfo; const bool isTrusted = deviceIface.isTrusted(); if (isTrusted) { statusInfo = i18n("(paired)"); } else { statusInfo = i18n("(unpaired)"); } //Description: "device name (paired/unpaired)" QString description = deviceIface.name() + QLatin1Char(' ') + statusInfo; //Replace characters description.replace(QLatin1Char('\\'), QStringLiteral("\\\\")); description.replace(QLatin1Char('['), QStringLiteral("\\[")); description.replace(QLatin1Char(']'), QStringLiteral("\\]")); description.replace(QLatin1Char('\''), QStringLiteral("\\'")); description.replace(QLatin1Char('\"'), QStringLiteral("\\\"")); description.replace(QLatin1Char('\n'), QLatin1Char(' ')); description.remove(QLatin1Char('\0')); //Output id and description QTextStream(stdout) << id << '[' << description << ']' << endl; } } else if(parser.isSet(QStringLiteral("refresh"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect"), QStringLiteral("org.kde.kdeconnect.daemon"), QStringLiteral("forceOnNetworkChange")); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } else { QString device = parser.value(QStringLiteral("device")); if (device.isEmpty() && parser.isSet(QStringLiteral("name"))) { device = blockOnReply(iface.deviceIdByName(parser.value(QStringLiteral("name")))); if (device.isEmpty()) { QTextStream(stderr) << "Couldn't find device: " << parser.value(QStringLiteral("name")) << endl; return 1; } } if(device.isEmpty()) { QTextStream(stderr) << i18n("No device specified") << endl; parser.showHelp(1); } if (parser.isSet(QStringLiteral("share"))) { QStringList urls; QUrl url = QUrl::fromUserInput(parser.value(QStringLiteral("share")), QDir::currentPath()); urls.append(url.toString()); // Check for more arguments const auto args = parser.positionalArguments(); for (const QString& input : args) { QUrl url = QUrl::fromUserInput(input, QDir::currentPath()); urls.append(url.toString()); } QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/share"), QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrls")); msg.setArguments(QVariantList() << QVariant(urls)); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); for (const QString& url : qAsConst(urls)) { QTextStream(stdout) << i18n("Shared %1", url) << endl; } } else if (parser.isSet(QStringLiteral("share-text"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/share"), QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareText")); msg.setArguments(QVariantList() << parser.value(QStringLiteral("share-text"))); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); QTextStream(stdout) << i18n("Shared text: %1", parser.value(QStringLiteral("share-text"))) << endl; } else if(parser.isSet(QStringLiteral("pair"))) { DeviceDbusInterface dev(device); if (!dev.isReachable()) { //Device doesn't exist, go into discovery mode and wait up to 30 seconds for the device to appear QEventLoop wait; QTextStream(stderr) << i18n("waiting for device...") << endl; blockOnReply(iface.acquireDiscoveryMode(id)); QObject::connect(&iface, &DaemonDbusInterface::deviceAdded, &iface, [&](const QString& deviceAddedId) { if (device == deviceAddedId) { wait.quit(); } }); QTimer::singleShot(30 * 1000, &wait, &QEventLoop::quit); wait.exec(); } if (!dev.isReachable()) { QTextStream(stderr) << i18n("Device not found") << endl; } else if(blockOnReply(dev.isTrusted())) { QTextStream(stderr) << i18n("Already paired") << endl; } else { QTextStream(stderr) << i18n("Pair requested") << endl; blockOnReply(dev.requestPair()); } blockOnReply(iface.releaseDiscoveryMode(id)); } else if(parser.isSet(QStringLiteral("unpair"))) { DeviceDbusInterface dev(device); if (!dev.isTrusted()) { QTextStream(stderr) << i18n("Already not paired") << endl; } else { QTextStream(stderr) << i18n("Unpaired") << endl; blockOnReply(dev.unpair()); } } 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"), QStringLiteral("org.kde.kdeconnect.device.ping"), QStringLiteral("sendPing")); if (parser.isSet(QStringLiteral("ping-msg"))) { QString message = parser.value(QStringLiteral("ping-msg")); msg.setArguments(QVariantList() << message); } - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } else if(parser.isSet(QStringLiteral("send-sms"))) { if (parser.isSet(QStringLiteral("destination"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/sms"), QStringLiteral("org.kde.kdeconnect.device.sms"), QStringLiteral("sendSms")); msg.setArguments({ parser.value(QStringLiteral("destination")), parser.value(QStringLiteral("send-sms"))}); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } else { QTextStream(stderr) << i18n("error: should specify the SMS's recipient by passing --destination "); return 1; } } else if(parser.isSet(QStringLiteral("ring"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/findmyphone"), QStringLiteral("org.kde.kdeconnect.device.findmyphone"), QStringLiteral("ring")); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } else if(parser.isSet(QStringLiteral("photo"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/photo"), QStringLiteral("org.kde.kdeconnect.device.photo"), QStringLiteral("requestPhoto")); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } else if(parser.isSet(QStringLiteral("send-keys"))) { QString seq = parser.value(QStringLiteral("send-keys")); QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/remotekeyboard"), QStringLiteral("org.kde.kdeconnect.device.remotekeyboard"), QStringLiteral("sendKeyPress")); if (seq.trimmed() == QLatin1String("-")) { // from stdin QFile in; if(in.open(stdin,QIODevice::ReadOnly | QIODevice::Unbuffered)) { while (!in.atEnd()) { QByteArray line = in.readLine(); // sanitize to ASCII-codes > 31? msg.setArguments({QString::fromLatin1(line), -1, false, false, false}); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } in.close(); } } else { msg.setArguments({seq, -1, false, false, false}); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); } } else if(parser.isSet(QStringLiteral("list-notifications"))) { NotificationsModel notifications; notifications.setDeviceId(device); for(int i=0, rows=notifications.rowCount(); itoObject(); QTextStream(stdout) << it.key() << ": " << cont.value(QStringLiteral("name")).toString() << ": " << cont.value(QStringLiteral("command")).toString() << endl; } } else if(parser.isSet(QStringLiteral("execute-command"))) { RemoteCommandsDbusInterface iface(device); blockOnReply(iface.triggerCommand(parser.value(QStringLiteral("execute-command")))); } else if(parser.isSet(QStringLiteral("encryption-info"))) { DeviceDbusInterface dev(device); QString info = blockOnReply(dev.encryptionInfo()); // QSsl::Der = 1 QTextStream(stdout) << info << endl; } else { QTextStream(stderr) << i18n("Nothing to be done") << endl; } } QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); return app.exec(); } diff --git a/core/daemon.cpp b/core/daemon.cpp index 1fdfe68a..11065e0b 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,328 +1,328 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "daemon.h" #include #include #include #include #include "core_debug.h" #include "kdeconnectconfig.h" #include "networkpacket.h" #include "dbushelper.h" #include "notificationserverinfo.h" #ifdef KDECONNECT_BLUETOOTH #include "backends/bluetooth/bluetoothlinkprovider.h" #endif #include "backends/lan/lanlinkprovider.h" #include "backends/loopback/loopbacklinkprovider.h" #include "device.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them QSet m_linkProviders; //Every known device QMap m_devices; QSet m_discoveryModeAcquisitions; bool m_testMode; }; Daemon* Daemon::instance() { Q_ASSERT(s_instance != nullptr); return s_instance; } Daemon::Daemon(QObject* parent, bool testMode) : QObject(parent) , d(new DaemonPrivate) { Q_ASSERT(!s_instance); s_instance = this; d->m_testMode = testMode; // HACK init may call pure virtual functions from this class so it can't be called directly from the ctor QTimer::singleShot(0, this, &Daemon::init); } void Daemon::init() { qCDebug(KDECONNECT_CORE) << "Daemon starting"; //Load backends if (d->m_testMode) d->m_linkProviders.insert(new LoopbackLinkProvider()); else { d->m_linkProviders.insert(new LanLinkProvider()); #ifdef KDECONNECT_BLUETOOTH d->m_linkProviders.insert(new BluetoothLinkProvider()); #endif #ifdef KDECONNECT_LOOPBACK d->m_linkProviders.insert(new LoopbackLinkProvider()); #endif } //Read remembered paired devices const QStringList& list = KdeConnectConfig::instance()->trustedDevices(); for (const QString& id : list) { addDevice(new Device(this, id)); } //Listen to new devices for (LinkProvider* a : qAsConst(d->m_linkProviders)) { connect(a, &LinkProvider::onConnectionReceived, this, &Daemon::onNewDeviceLink); a->onStart(); } //Register on DBus qDBusRegisterMetaType< QMap >(); - DbusHelper::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); - DbusHelper::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); + DBusHelper::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); + DBusHelper::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); NotificationServerInfo::instance().init(); qCDebug(KDECONNECT_CORE) << "Daemon started"; } void Daemon::acquireDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.insert(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { forceOnNetworkChange(); } } void Daemon::releaseDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.remove(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } void Daemon::removeDevice(Device* device) { d->m_devices.remove(device->id()); device->deleteLater(); Q_EMIT deviceRemoved(device->id()); Q_EMIT deviceListChanged(); } void Daemon::cleanDevices() { const auto devs = d->m_devices; for (Device* device : devs) { if (device->isTrusted()) { continue; } device->cleanUnneededLinks(); //If there are no links remaining if (!device->isReachable()) { removeDevice(device); } } } void Daemon::forceOnNetworkChange() { qCDebug(KDECONNECT_CORE) << "Sending onNetworkChange to" << d->m_linkProviders.size() << "LinkProviders"; for (LinkProvider* a : qAsConst(d->m_linkProviders)) { a->onNetworkChange(); } } Device*Daemon::getDevice(const QString& deviceId) { for (Device* device : qAsConst(d->m_devices)) { if (device->id() == deviceId) { return device; } } return nullptr; } const QSet& Daemon::getLinkProviders() const { return d->m_linkProviders; } QStringList Daemon::devices(bool onlyReachable, bool onlyTrusted) const { QStringList ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret.append(device->id()); } return ret; } QMap Daemon::deviceNames(bool onlyReachable, bool onlyTrusted) const { QMap ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret[device->id()] = device->name(); } return ret; } void Daemon::onNewDeviceLink(const NetworkPacket& identityPacket, DeviceLink* dl) { const QString& id = identityPacket.get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Device discovered" << id << "via" << dl->provider()->name(); if (d->m_devices.contains(id)) { qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = d->m_devices[id]; bool wasReachable = device->isReachable(); device->addLink(identityPacket, dl); if (!wasReachable) { Q_EMIT deviceVisibilityChanged(id, true); Q_EMIT deviceListChanged(); } } else { qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPacket, dl); //we discard the connections that we created but it's not paired. if (!isDiscoveringDevices() && !device->isTrusted() && !dl->linkShouldBeKeptAlive()) { device->deleteLater(); } else { addDevice(device); } } } void Daemon::onDeviceStatusChanged() { Device* device = (Device*)sender(); //qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); if (!device->isReachable() && !device->isTrusted()) { //qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); removeDevice(device); } else { Q_EMIT deviceVisibilityChanged(device->id(), device->isReachable()); Q_EMIT deviceListChanged(); } } void Daemon::setAnnouncedName(const QString& name) { qCDebug(KDECONNECT_CORE()) << "Announcing name"; KdeConnectConfig::instance()->setName(name); forceOnNetworkChange(); Q_EMIT announcedNameChanged(name); } QString Daemon::announcedName() { return KdeConnectConfig::instance()->name(); } QNetworkAccessManager* Daemon::networkAccessManager() { static QPointer manager; if (!manager) { manager = new QNetworkAccessManager(this); } return manager; } QList Daemon::devicesList() const { return d->m_devices.values(); } bool Daemon::isDiscoveringDevices() const { return !d->m_discoveryModeAcquisitions.isEmpty(); } QString Daemon::deviceIdByName(const QString& name) const { for (Device* device : qAsConst(d->m_devices)) { if (device->name() == name && device->isTrusted()) return device->id(); } return {}; } void Daemon::addDevice(Device* device) { const QString id = device->id(); connect(device, &Device::reachableChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::trustedChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::hasPairingRequestsChanged, this, &Daemon::pairingRequestsChanged); connect(device, &Device::hasPairingRequestsChanged, this, [this, device](bool hasPairingRequests) { if (hasPairingRequests) askPairingConfirmation(device); } ); d->m_devices[id] = device; Q_EMIT deviceAdded(id); Q_EMIT deviceListChanged(); } QStringList Daemon::pairingRequests() const { QStringList ret; for(Device* dev: qAsConst(d->m_devices)) { if (dev->hasPairingRequests()) ret += dev->id(); } return ret; } Daemon::~Daemon() { } QString Daemon::selfId() const { return KdeConnectConfig::instance()->deviceId(); } diff --git a/core/dbushelper.cpp b/core/dbushelper.cpp index 2415849d..97ecae9b 100644 --- a/core/dbushelper.cpp +++ b/core/dbushelper.cpp @@ -1,179 +1,179 @@ /** * Copyright 2014 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dbushelper.h" #include "core_debug.h" #include #include #include #include #include #include #include #include "kdeconnectconfig.h" #ifdef Q_OS_MAC #include #include #endif -namespace DbusHelper { +namespace DBusHelper { #ifdef USE_PRIVATE_DBUS class DBusInstancePrivate { public: DBusInstancePrivate(); ~DBusInstancePrivate(); void launchDBusDaemon(); void closeDBusDaemon(); private: QProcess *m_dbusProcess; }; static DBusInstancePrivate dbusInstance; #endif void filterNonExportableCharacters(QString& s) { static QRegExp regexp(QStringLiteral("[^A-Za-z0-9_]"), Qt::CaseSensitive, QRegExp::Wildcard); s.replace(regexp,QLatin1String("_")); } QDBusConnection sessionBus() { #ifdef USE_PRIVATE_DBUS return QDBusConnection::connectToBus(KdeConnectConfig::instance()->privateDBusAddress(), QStringLiteral(KDECONNECT_PRIVATE_DBUS_NAME)); #else return QDBusConnection::sessionBus(); #endif } #ifdef USE_PRIVATE_DBUS void launchDBusDaemon() { dbusInstance.launchDBusDaemon(); qAddPostRoutine(closeDBusDaemon); } void closeDBusDaemon() { dbusInstance.closeDBusDaemon(); } #ifdef Q_OS_MAC void macosUnsetLaunchctlEnv() { // Unset Launchd env QProcess unsetLaunchdDBusEnv; unsetLaunchdDBusEnv.setProgram(QStringLiteral("launchctl")); unsetLaunchdDBusEnv.setArguments({ QStringLiteral("unsetenv"), QStringLiteral(KDECONNECT_SESSION_DBUS_LAUNCHD_ENV) }); unsetLaunchdDBusEnv.start(); unsetLaunchdDBusEnv.waitForFinished(); } #endif void DBusInstancePrivate::launchDBusDaemon() { // Kill old dbus daemon if (m_dbusProcess != nullptr) closeDBusDaemon(); // Start dbus daemon m_dbusProcess = new QProcess(); QString kdeconnectDBusConfiguration; QString dbusDaemonExecutable = QStandardPaths::findExecutable(QStringLiteral("dbus-daemon"), { QCoreApplication::applicationDirPath() }); if (!dbusDaemonExecutable.isNull()) { kdeconnectDBusConfiguration = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("dbus-1/session.conf")); } else { // macOS Debug env dbusDaemonExecutable = QString::fromLatin1(qgetenv("craftRoot")) + QStringLiteral("/../bin/dbus-daemon"); kdeconnectDBusConfiguration = QString::fromLatin1(qgetenv("craftRoot")) + QStringLiteral("/../share/dbus-1/session.conf"); } m_dbusProcess->setProgram(dbusDaemonExecutable); m_dbusProcess->setArguments({QStringLiteral("--print-address"), QStringLiteral("--nofork"), QStringLiteral("--config-file=") + kdeconnectDBusConfiguration, QStringLiteral("--address=") + QStringLiteral(KDECONNECT_PRIVATE_DBUS_ADDR) }); m_dbusProcess->setWorkingDirectory(QCoreApplication::applicationDirPath()); m_dbusProcess->setStandardOutputFile(KdeConnectConfig::instance()->privateDBusAddressPath()); m_dbusProcess->setStandardErrorFile(QProcess::nullDevice()); m_dbusProcess->start(); #ifdef Q_OS_MAC // Set launchctl env QString privateDBusAddress = KdeConnectConfig::instance()->privateDBusAddress(); QRegularExpressionMatch path; if (privateDBusAddress.contains(QRegularExpression( QStringLiteral("path=(?/tmp/dbus-[A-Za-z0-9]+)") ), &path)) { qCDebug(KDECONNECT_CORE) << "DBus address: " << path.captured(QStringLiteral("path")); QProcess setLaunchdDBusEnv; setLaunchdDBusEnv.setProgram(QStringLiteral("launchctl")); setLaunchdDBusEnv.setArguments({ QStringLiteral("setenv"), QStringLiteral(KDECONNECT_SESSION_DBUS_LAUNCHD_ENV), path.captured(QStringLiteral("path")) }); setLaunchdDBusEnv.start(); setLaunchdDBusEnv.waitForFinished(); } else { qCDebug(KDECONNECT_CORE) << "Cannot get dbus address"; } #endif } void DBusInstancePrivate::closeDBusDaemon() { if (m_dbusProcess != nullptr) { m_dbusProcess->terminate(); m_dbusProcess->waitForFinished(); delete m_dbusProcess; m_dbusProcess = nullptr; QFile privateDBusAddressFile(KdeConnectConfig::instance()->privateDBusAddressPath()); if (privateDBusAddressFile.exists()) privateDBusAddressFile.resize(0); #ifdef Q_OS_MAC macosUnsetLaunchctlEnv(); #endif } } DBusInstancePrivate::DBusInstancePrivate() :m_dbusProcess(nullptr){} DBusInstancePrivate::~DBusInstancePrivate() { closeDBusDaemon(); } #endif } diff --git a/core/dbushelper.h.in b/core/dbushelper.h.in index afcf392b..2ae8f6ae 100644 --- a/core/dbushelper.h.in +++ b/core/dbushelper.h.in @@ -1,46 +1,46 @@ /** * Copyright 2014 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KDECONNECT_DBUSHELPER_H #define KDECONNECT_DBUSHELPER_H #include #include #include "kdeconnectcore_export.h" #define KDECONNECT_PRIVATE_DBUS_ADDR "${KDECONNECT_PRIVATE_DBUS_ADDR}" #define KDECONNECT_PRIVATE_DBUS_NAME "${KDECONNECT_PRIVATE_DBUS_NAME}" #define KDECONNECT_SESSION_DBUS_LAUNCHD_ENV "DBUS_LAUNCHD_SESSION_BUS_SOCKET" -namespace DbusHelper { +namespace DBusHelper { void KDECONNECTCORE_EXPORT filterNonExportableCharacters(QString& s); #ifdef USE_PRIVATE_DBUS void KDECONNECTCORE_EXPORT launchDBusDaemon(); void KDECONNECTCORE_EXPORT closeDBusDaemon(); #endif QDBusConnection KDECONNECTCORE_EXPORT sessionBus(); #ifdef Q_OS_MAC void KDECONNECTCORE_EXPORT macosUnsetLaunchctlEnv(); #endif } #endif diff --git a/core/device.cpp b/core/device.cpp index 5670f2fe..18e35ec9 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -1,568 +1,568 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "device.h" #include #include #include #include #include #include #include "core_debug.h" #include "kdeconnectplugin.h" #include "pluginloader.h" #include "backends/devicelink.h" #include "backends/lan/landevicelink.h" #include "backends/linkprovider.h" #include "networkpacket.h" #include "kdeconnectconfig.h" #include "daemon.h" #include "dbushelper.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" class Device::DevicePrivate { public: DevicePrivate(const QString &id) : m_deviceId(id) { } ~DevicePrivate() { qDeleteAll(m_deviceLinks); m_deviceLinks.clear(); } const QString m_deviceId; QString m_deviceName; DeviceType m_deviceType; int m_protocolVersion; QVector m_deviceLinks; QHash m_plugins; QMultiMap m_pluginsByIncomingCapability; QSet m_supportedPlugins; QSet m_allPlugins; QSet m_pairRequests; }; static void warn(const QString& info) { qWarning() << "Device pairing error" << info; } Device::Device(QObject* parent, const QString& id) : QObject(parent) , d(new Device::DevicePrivate(id)) { d->m_protocolVersion = NetworkPacket::s_protocolVersion; KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance()->getTrustedDevice(d->m_deviceId); d->m_deviceName = info.deviceName; d->m_deviceType = str2type(info.deviceType); //Register in bus - DbusHelper::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); + DBusHelper::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); //Assume every plugin is supported until addLink is called and we can get the actual list d->m_allPlugins = PluginLoader::instance()->getPluginList().toSet(); d->m_supportedPlugins = d->m_allPlugins; connect(this, &Device::pairingError, this, &warn); } Device::Device(QObject* parent, const NetworkPacket& identityPacket, DeviceLink* dl) : QObject(parent) , d(new Device::DevicePrivate(identityPacket.get(QStringLiteral("deviceId")))) { d->m_deviceName = identityPacket.get(QStringLiteral("deviceName")); d->m_allPlugins = PluginLoader::instance()->getPluginList().toSet(); addLink(identityPacket, dl); //Register in bus - DbusHelper::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); + DBusHelper::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); connect(this, &Device::pairingError, this, &warn); connect(this, &Device::reachableChanged, this, &Device::statusIconNameChanged); connect(this, &Device::trustedChanged, this, &Device::statusIconNameChanged); } Device::~Device() { delete d; } QString Device::id() const { return d->m_deviceId; } QString Device::name() const { return d->m_deviceName; } QString Device::type() const { return type2str(d->m_deviceType); } bool Device::isReachable() const { return !d->m_deviceLinks.isEmpty(); } int Device::protocolVersion() { return d->m_protocolVersion; } QStringList Device::supportedPlugins() const { return d->m_supportedPlugins.toList(); } bool Device::hasPlugin(const QString& name) const { return d->m_plugins.contains(name); } QStringList Device::loadedPlugins() const { return d->m_plugins.keys(); } void Device::reloadPlugins() { QHash newPluginMap, oldPluginMap = d->m_plugins; QMultiMap newPluginsByIncomingCapability; if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices PluginLoader* loader = PluginLoader::instance(); for (const QString& pluginName : qAsConst(d->m_supportedPlugins)) { const KPluginMetaData service = loader->getPluginInfo(pluginName); const bool pluginEnabled = isPluginEnabled(pluginName); const QSet incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPacketType")).toSet(); if (pluginEnabled) { KdeConnectPlugin* plugin = d->m_plugins.take(pluginName); if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } Q_ASSERT(plugin); for (const QString& interface : incomingCapabilities) { newPluginsByIncomingCapability.insert(interface, plugin); } newPluginMap[pluginName] = plugin; } } } const bool differentPlugins = oldPluginMap != newPluginMap; //Erase all left plugins in the original map (meaning that we don't want //them anymore, otherwise they would have been moved to the newPluginMap) qDeleteAll(d->m_plugins); d->m_plugins = newPluginMap; d->m_pluginsByIncomingCapability = newPluginsByIncomingCapability; - QDBusConnection bus = DbusHelper::sessionBus(); + QDBusConnection bus = DBusHelper::sessionBus(); for (KdeConnectPlugin* plugin : qAsConst(d->m_plugins)) { //TODO: see how it works in Android (only done once, when created) plugin->connected(); const QString dbusPath = plugin->dbusPath(); if (!dbusPath.isEmpty()) { bus.registerObject(dbusPath, plugin, QDBusConnection::ExportAllProperties | QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportScriptableSlots); } } if (differentPlugins) { Q_EMIT pluginsChanged(); } } QString Device::pluginsConfigFile() const { return KdeConnectConfig::instance()->deviceConfigDir(id()).absoluteFilePath(QStringLiteral("config")); } void Device::requestPair() { if (isTrusted()) { Q_EMIT pairingError(i18n("Already paired")); return; } if (!isReachable()) { Q_EMIT pairingError(i18n("Device not reachable")); return; } for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { dl->userRequestsPair(); } } void Device::unpair() { for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { dl->userRequestsUnpair(); } KdeConnectConfig::instance()->removeTrustedDevice(id()); Q_EMIT trustedChanged(false); } void Device::pairStatusChanged(DeviceLink::PairStatus status) { if (status == DeviceLink::NotPaired) { KdeConnectConfig::instance()->removeTrustedDevice(id()); for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { if (dl != sender()) { dl->setPairStatus(DeviceLink::NotPaired); } } } else { KdeConnectConfig::instance()->addTrustedDevice(id(), name(), type()); } reloadPlugins(); //Will load/unload plugins bool isTrusted = (status == DeviceLink::Paired); Q_EMIT trustedChanged(isTrusted); Q_ASSERT(isTrusted == this->isTrusted()); } static bool lessThan(DeviceLink* p1, DeviceLink* p2) { return p1->provider()->priority() > p2->provider()->priority(); } void Device::addLink(const NetworkPacket& identityPacket, DeviceLink* link) { //qCDebug(KDECONNECT_CORE) << "Adding link to" << id() << "via" << link->provider(); setName(identityPacket.get(QStringLiteral("deviceName"))); d->m_deviceType = str2type(identityPacket.get(QStringLiteral("deviceType"))); if (d->m_deviceLinks.contains(link)) { return; } d->m_protocolVersion = identityPacket.get(QStringLiteral("protocolVersion"), -1); if (d->m_protocolVersion != NetworkPacket::s_protocolVersion) { qCWarning(KDECONNECT_CORE) << d->m_deviceName << "- warning, device uses a different protocol version" << d->m_protocolVersion << "expected" << NetworkPacket::s_protocolVersion; } connect(link, &QObject::destroyed, this, &Device::linkDestroyed); d->m_deviceLinks.append(link); //Theoretically we will never add two links from the same provider (the provider should destroy //the old one before this is called), so we do not have to worry about destroying old links. //-- Actually, we should not destroy them or the provider will store an invalid ref! connect(link, &DeviceLink::receivedPacket, this, &Device::privateReceivedPacket); std::sort(d->m_deviceLinks.begin(), d->m_deviceLinks.end(), lessThan); const bool capabilitiesSupported = identityPacket.has(QStringLiteral("incomingCapabilities")) || identityPacket.has(QStringLiteral("outgoingCapabilities")); if (capabilitiesSupported) { const QSet outgoingCapabilities = identityPacket.get(QStringLiteral("outgoingCapabilities")).toSet() , incomingCapabilities = identityPacket.get(QStringLiteral("incomingCapabilities")).toSet(); d->m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities); //qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities; } else { d->m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); } reloadPlugins(); if (d->m_deviceLinks.size() == 1) { Q_EMIT reachableChanged(true); } connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged); connect(link, &DeviceLink::pairingRequest, this, &Device::addPairingRequest); connect(link, &DeviceLink::pairingRequestExpired, this, &Device::removePairingRequest); connect(link, &DeviceLink::pairingError, this, &Device::pairingError); } void Device::addPairingRequest(PairingHandler* handler) { const bool wasEmpty = d->m_pairRequests.isEmpty(); d->m_pairRequests.insert(handler); if (wasEmpty != d->m_pairRequests.isEmpty()) Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty()); } void Device::removePairingRequest(PairingHandler* handler) { const bool wasEmpty = d->m_pairRequests.isEmpty(); d->m_pairRequests.remove(handler); if (wasEmpty != d->m_pairRequests.isEmpty()) Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty()); } bool Device::hasPairingRequests() const { return !d->m_pairRequests.isEmpty(); } void Device::acceptPairing() { if (d->m_pairRequests.isEmpty()) qWarning() << "no pair requests to accept!"; //copying because the pairing handler will be removed upon accept const auto prCopy = d->m_pairRequests; for (auto ph: prCopy) ph->acceptPairing(); } void Device::rejectPairing() { if (d->m_pairRequests.isEmpty()) qWarning() << "no pair requests to reject!"; //copying because the pairing handler will be removed upon reject const auto prCopy = d->m_pairRequests; for (auto ph: prCopy) ph->rejectPairing(); } void Device::linkDestroyed(QObject* o) { removeLink(static_cast(o)); } void Device::removeLink(DeviceLink* link) { d->m_deviceLinks.removeAll(link); //qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining"; if (d->m_deviceLinks.isEmpty()) { reloadPlugins(); Q_EMIT reachableChanged(false); } } bool Device::sendPacket(NetworkPacket& np) { Q_ASSERT(np.type() != PACKET_TYPE_PAIR); Q_ASSERT(isTrusted()); //Maybe we could block here any packet that is not an identity or a pairing packet to prevent sending non encrypted data for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { if (dl->sendPacket(np)) return true; } return false; } void Device::privateReceivedPacket(const NetworkPacket& np) { Q_ASSERT(np.type() != PACKET_TYPE_PAIR); if (isTrusted()) { const QList plugins = d->m_pluginsByIncomingCapability.values(np.type()); if (plugins.isEmpty()) { qWarning() << "discarding unsupported packet" << np.type() << "for" << name(); } for (KdeConnectPlugin* plugin : plugins) { plugin->receivePacket(np); } } else { qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring packet" << np.type(); unpair(); } } bool Device::isTrusted() const { return KdeConnectConfig::instance()->trustedDevices().contains(id()); } QStringList Device::availableLinks() const { QStringList sl; sl.reserve(d->m_deviceLinks.size()); for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { sl.append(dl->provider()->name()); } return sl; } void Device::cleanUnneededLinks() { if (isTrusted()) { return; } for(int i = 0; i < d->m_deviceLinks.size(); ) { DeviceLink* dl = d->m_deviceLinks[i]; if (!dl->linkShouldBeKeptAlive()) { dl->deleteLater(); d->m_deviceLinks.remove(i); } else { i++; } } } QHostAddress Device::getLocalIpAddress() const { for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { LanDeviceLink* ldl = dynamic_cast(dl); if (ldl) { return ldl->hostAddress(); } } return QHostAddress::Null; } Device::DeviceType Device::str2type(const QString& deviceType) { if (deviceType == QLatin1String("desktop")) return Desktop; if (deviceType == QLatin1String("laptop")) return Laptop; if (deviceType == QLatin1String("smartphone") || deviceType == QLatin1String("phone")) return Phone; if (deviceType == QLatin1String("tablet")) return Tablet; if (deviceType == QLatin1String("tv")) return Tv; return Unknown; } QString Device::type2str(Device::DeviceType deviceType) { if (deviceType == Desktop) return QStringLiteral("desktop"); if (deviceType == Laptop) return QStringLiteral("laptop"); if (deviceType == Phone) return QStringLiteral("smartphone"); if (deviceType == Tablet) return QStringLiteral("tablet"); if (deviceType == Tv) return QStringLiteral("tv"); return QStringLiteral("unknown"); } QString Device::statusIconName() const { return iconForStatus(isReachable(), isTrusted()); } QString Device::iconName() const { return iconForStatus(true, false); } QString Device::iconForStatus(bool reachable, bool trusted) const { Device::DeviceType deviceType = d->m_deviceType; if (deviceType == Device::Unknown) { deviceType = Device::Phone; //Assume phone if we don't know the type } else if (deviceType == Device::Desktop) { deviceType = Device::Device::Laptop; // We don't have desktop icon yet } QString status = (reachable? (trusted? QStringLiteral("connected") : QStringLiteral("disconnected")) : QStringLiteral("trusted")); QString type = type2str(deviceType); return type+status; } void Device::setName(const QString& name) { if (d->m_deviceName != name) { d->m_deviceName = name; Q_EMIT nameChanged(name); } } KdeConnectPlugin* Device::plugin(const QString& pluginName) const { return d->m_plugins[pluginName]; } void Device::setPluginEnabled(const QString& pluginName, bool enabled) { if (!d->m_allPlugins.contains(pluginName)) { return; } KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); const QString enabledKey = pluginName + QStringLiteral("Enabled"); pluginStates.writeEntry(enabledKey, enabled); reloadPlugins(); } bool Device::isPluginEnabled(const QString& pluginName) const { const QString enabledKey = pluginName + QStringLiteral("Enabled"); KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); return (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false) : PluginLoader::instance()->getPluginInfo(pluginName).isEnabledByDefault()); } QString Device::encryptionInfo() const { QString result; QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha1; QString localSha1 = QString::fromLatin1(KdeConnectConfig::instance()->certificate().digest(digestAlgorithm).toHex()); for (int i = 2; igetDeviceProperty(id(), QStringLiteral("certificate")).toStdString(); QSslCertificate remoteCertificate = QSslCertificate(QByteArray(remotePem.c_str(), (int)remotePem.size())); QString remoteSha1 = QString::fromLatin1(remoteCertificate.digest(digestAlgorithm).toHex()); for (int i = 2; i < remoteSha1.size(); i += 3) { remoteSha1.insert(i, QStringLiteral(":")); // Improve readability } result += i18n("SHA1 fingerprint of remote device certificate is: %1\n", remoteSha1); return result; } QString Device::pluginIconName(const QString& pluginName) { if (hasPlugin(pluginName)) { return d->m_plugins[pluginName]->iconName(); } return QString(); } diff --git a/core/kdeconnectconfig.cpp b/core/kdeconnectconfig.cpp index 8c2bb5cc..b705a583 100644 --- a/core/kdeconnectconfig.cpp +++ b/core/kdeconnectconfig.cpp @@ -1,377 +1,377 @@ /** * Copyright 2015 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kdeconnectconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core_debug.h" #include "dbushelper.h" #include "daemon.h" const QFile::Permissions strictPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; struct KdeConnectConfigPrivate { // The Initializer object sets things up, and also does cleanup when it goes out of scope // Note it's not being used anywhere. That's intended QCA::Initializer m_qcaInitializer; QCA::PrivateKey m_privateKey; QSslCertificate m_certificate; // Use QSslCertificate instead of QCA::Certificate due to compatibility with QSslSocket QSettings* m_config; QSettings* m_trustedDevices; #ifdef USE_PRIVATE_DBUS QString m_privateDBusAddress; // Private DBus Address cache #endif }; KdeConnectConfig* KdeConnectConfig::instance() { static KdeConnectConfig* kcc = new KdeConnectConfig(); return kcc; } KdeConnectConfig::KdeConnectConfig() : d(new KdeConnectConfigPrivate) { //qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(","); if(!QCA::isSupported("rsa")) { qCritical() << "Could not find support for RSA in your QCA installation"; Daemon::instance()->reportError( i18n("KDE Connect failed to start"), i18n("Could not find support for RSA in your QCA installation. If your " "distribution provides separate packets for QCA-ossl and QCA-gnupg, " "make sure you have them installed and try again.")); } //Make sure base directory exists QDir().mkpath(baseConfigDir().path()); //.config/kdeconnect/config d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); loadPrivateKey(); loadCertificate(); } QString KdeConnectConfig::name() { QString username; #ifdef Q_OS_WIN username = QString::fromLatin1(qgetenv("USERNAME")); #else username = QString::fromLatin1(qgetenv("USER")); #endif QString defaultName = username + QStringLiteral("@") + QHostInfo::localHostName(); QString name = d->m_config->value(QStringLiteral("name"), defaultName).toString(); return name; } void KdeConnectConfig::setName(const QString& name) { d->m_config->setValue(QStringLiteral("name"), name); d->m_config->sync(); } QString KdeConnectConfig::deviceType() { return QStringLiteral("desktop"); // TODO } QString KdeConnectConfig::deviceId() { return d->m_certificate.subjectInfo(QSslCertificate::CommonName).constFirst(); } QString KdeConnectConfig::privateKeyPath() { return baseConfigDir().absoluteFilePath(QStringLiteral("privateKey.pem")); } QString KdeConnectConfig::certificatePath() { return baseConfigDir().absoluteFilePath(QStringLiteral("certificate.pem")); } QSslCertificate KdeConnectConfig::certificate() { return d->m_certificate; } QDir KdeConnectConfig::baseConfigDir() { QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QString kdeconnectConfigPath = QDir(configPath).absoluteFilePath(QStringLiteral("kdeconnect")); return QDir(kdeconnectConfigPath); } QStringList KdeConnectConfig::trustedDevices() { const QStringList& list = d->m_trustedDevices->childGroups(); return list; } void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) { d->m_trustedDevices->beginGroup(id); d->m_trustedDevices->setValue(QStringLiteral("name"), name); d->m_trustedDevices->setValue(QStringLiteral("type"), type); d->m_trustedDevices->endGroup(); d->m_trustedDevices->sync(); QDir().mkpath(deviceConfigDir(id).path()); } KdeConnectConfig::DeviceInfo KdeConnectConfig::getTrustedDevice(const QString& id) { d->m_trustedDevices->beginGroup(id); KdeConnectConfig::DeviceInfo info; info.deviceName = d->m_trustedDevices->value(QStringLiteral("name"), QLatin1String("unnamed")).toString(); info.deviceType = d->m_trustedDevices->value(QStringLiteral("type"), QLatin1String("unknown")).toString(); d->m_trustedDevices->endGroup(); return info; } void KdeConnectConfig::removeTrustedDevice(const QString& deviceId) { d->m_trustedDevices->remove(deviceId); d->m_trustedDevices->sync(); //We do not remove the config files. } // Utility functions to set and get a value void KdeConnectConfig::setDeviceProperty(const QString& deviceId, const QString& key, const QString& value) { d->m_trustedDevices->beginGroup(deviceId); d->m_trustedDevices->setValue(key, value); d->m_trustedDevices->endGroup(); d->m_trustedDevices->sync(); } QString KdeConnectConfig::getDeviceProperty(const QString& deviceId, const QString& key, const QString& defaultValue) { QString value; d->m_trustedDevices->beginGroup(deviceId); value = d->m_trustedDevices->value(key, defaultValue).toString(); d->m_trustedDevices->endGroup(); return value; } QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); return QDir(deviceConfigPath); } QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); return QDir(pluginConfigDir); } void KdeConnectConfig::loadPrivateKey() { QString keyPath = privateKeyPath(); QFile privKey(keyPath); bool needsToGenerateKey = false; if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) { QCA::ConvertResult result; d->m_privateKey = QCA::PrivateKey::fromPEM(QString::fromLatin1(privKey.readAll()), QCA::SecureArray(), &result); if (result != QCA::ConvertResult::ConvertGood) { qCWarning(KDECONNECT_CORE) << "Private key from" << keyPath << "is not valid"; needsToGenerateKey = true; } } else { needsToGenerateKey = true; } if (needsToGenerateKey) { generatePrivateKey(keyPath); } //Extra security check if (QFile::permissions(keyPath) != strictPermissions) { qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath; } } void KdeConnectConfig::loadCertificate() { QString certPath = certificatePath(); QFile cert(certPath); bool needsToGenerateCert = false; if (cert.exists() && cert.open(QIODevice::ReadOnly)) { auto loadedCerts = QSslCertificate::fromPath(certPath); if (loadedCerts.empty()) { qCWarning(KDECONNECT_CORE) << "Certificate from" << certPath << "is not valid"; needsToGenerateCert = true; } else { d->m_certificate = loadedCerts.at(0); } } else { needsToGenerateCert = true; } if (needsToGenerateCert) { generateCertificate(certPath); } //Extra security check if (QFile::permissions(certPath) != strictPermissions) { qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect certificate file has too open permissions " << certPath; } } void KdeConnectConfig::generatePrivateKey(const QString& keyPath) { qCDebug(KDECONNECT_CORE) << "Generating private key"; bool error = false; d->m_privateKey = QCA::KeyGenerator().createRSA(2048); QFile privKey(keyPath); if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) { error = true; } else { privKey.setPermissions(strictPermissions); int written = privKey.write(d->m_privateKey.toPEM().toLatin1()); if (written <= 0) { error = true; } } if (error) { Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); } } void KdeConnectConfig::generateCertificate(const QString& certPath) { qCDebug(KDECONNECT_CORE) << "Generating certificate"; bool error = false; QString uuid = QUuid::createUuid().toString(); - DbusHelper::filterNonExportableCharacters(uuid); + DBusHelper::filterNonExportableCharacters(uuid); qCDebug(KDECONNECT_CORE) << "My id:" << uuid; // FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely. // The same thing we are doing with QCA could be done invoking openssl (although it's potentially less portable): // openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout privateKey.pem -days 3650 -out certificate.pem -subj "/O=KDE/OU=KDE Connect/CN=_e6e29ad4_2b31_4b6d_8f7a_9872dbaa9095_" QCA::CertificateOptions certificateOptions = QCA::CertificateOptions(); QDateTime startTime = QDateTime::currentDateTime().addYears(-1); QDateTime endTime = startTime.addYears(10); QCA::CertificateInfo certificateInfo; certificateInfo.insert(QCA::CommonName, uuid); certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); certificateOptions.setInfo(certificateInfo); certificateOptions.setFormat(QCA::PKCS10); certificateOptions.setSerialNumber(QCA::BigInteger(10)); certificateOptions.setValidityPeriod(startTime, endTime); d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1()); QFile cert(certPath); if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) { error = true; } else { cert.setPermissions(strictPermissions); int written = cert.write(d->m_certificate.toPem()); if (written <= 0) { error = true; } } if (error) { Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath)); } } #ifdef USE_PRIVATE_DBUS QString KdeConnectConfig::privateDBusAddressPath() { return baseConfigDir().absoluteFilePath(QStringLiteral("private_dbus_address")); } QString KdeConnectConfig::privateDBusAddress() { if (d->m_privateDBusAddress.length() != 0) return d->m_privateDBusAddress; QString dbusAddressPath = privateDBusAddressPath(); QFile dbusAddressFile(dbusAddressPath); if (!dbusAddressFile.open(QFile::ReadOnly | QFile::Text)) { qCCritical(KDECONNECT_CORE) << "Private DBus enabled but error read private dbus address conf"; exit(1); } QTextStream in(&dbusAddressFile); qCDebug(KDECONNECT_CORE) << "Waiting for private dbus"; int retry = 0; QString addr = in.readLine(); while(addr.length() == 0 && retry < 5) { qCDebug(KDECONNECT_CORE) << "Retry reading private DBus address after 3s"; QThread::sleep(3); retry ++; addr = in.readLine(); // Read until first not empty line } if (addr.length() == 0) { qCCritical(KDECONNECT_CORE) << "Private DBus enabled but read private dbus address failed"; exit(1); } qCDebug(KDECONNECT_CORE) << "Private dbus address: " << addr; d->m_privateDBusAddress = addr; return addr; } #endif diff --git a/core/kdeconnectpluginconfig.cpp b/core/kdeconnectpluginconfig.cpp index a59975e5..7a560c16 100644 --- a/core/kdeconnectpluginconfig.cpp +++ b/core/kdeconnectpluginconfig.cpp @@ -1,100 +1,100 @@ /** * Copyright 2015 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kdeconnectpluginconfig.h" #include #include #include #include "kdeconnectconfig.h" #include "dbushelper.h" struct KdeConnectPluginConfigPrivate { QDir m_configDir; QSettings* m_config; QDBusMessage m_signal; }; KdeConnectPluginConfig::KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName) : d(new KdeConnectPluginConfigPrivate()) { d->m_configDir = KdeConnectConfig::instance()->pluginConfigDir(deviceId, pluginName); QDir().mkpath(d->m_configDir.path()); d->m_config = new QSettings(d->m_configDir.absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); d->m_signal = QDBusMessage::createSignal(QStringLiteral("/kdeconnect/") + deviceId + QStringLiteral("/") + pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged")); - DbusHelper::sessionBus().connect(QLatin1String(""), QStringLiteral("/kdeconnect/") + deviceId + QStringLiteral("/") + pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"), this, SLOT(slotConfigChanged())); + DBusHelper::sessionBus().connect(QLatin1String(""), QStringLiteral("/kdeconnect/") + deviceId + QStringLiteral("/") + pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"), this, SLOT(slotConfigChanged())); } KdeConnectPluginConfig::~KdeConnectPluginConfig() { delete d->m_config; } QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& defaultValue) { d->m_config->sync(); return d->m_config->value(key, defaultValue); } QVariantList KdeConnectPluginConfig::getList(const QString& key, const QVariantList& defaultValue) { QVariantList list; d->m_config->sync(); // note: need sync() to get recent changes signalled from other process int size = d->m_config->beginReadArray(key); if (size < 1) { d->m_config->endArray(); return defaultValue; } for (int i = 0; i < size; ++i) { d->m_config->setArrayIndex(i); list << d->m_config->value(QStringLiteral("value")); } d->m_config->endArray(); return list; } void KdeConnectPluginConfig::set(const QString& key, const QVariant& value) { d->m_config->setValue(key, value); d->m_config->sync(); - DbusHelper::sessionBus().send(d->m_signal); + DBusHelper::sessionBus().send(d->m_signal); } void KdeConnectPluginConfig::setList(const QString& key, const QVariantList& list) { d->m_config->beginWriteArray(key); for (int i = 0; i < list.size(); ++i) { d->m_config->setArrayIndex(i); d->m_config->setValue(QStringLiteral("value"), list.at(i)); } d->m_config->endArray(); d->m_config->sync(); - DbusHelper::sessionBus().send(d->m_signal); + DBusHelper::sessionBus().send(d->m_signal); } void KdeConnectPluginConfig::slotConfigChanged() { Q_EMIT configChanged(); } diff --git a/core/networkpacket.cpp b/core/networkpacket.cpp index ee9f8a36..98f38188 100644 --- a/core/networkpacket.cpp +++ b/core/networkpacket.cpp @@ -1,172 +1,172 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "networkpacket.h" #include "core_debug.h" #include #include #include #include #include #include #include #include "dbushelper.h" #include "filetransferjob.h" #include "pluginloader.h" #include "kdeconnectconfig.h" QDebug operator<<(QDebug s, const NetworkPacket& pkg) { s.nospace() << "NetworkPacket(" << pkg.type() << ':' << pkg.body(); if (pkg.hasPayload()) { s.nospace() << ":withpayload"; } s.nospace() << ')'; return s.space(); } const int NetworkPacket::s_protocolVersion = 7; NetworkPacket::NetworkPacket(const QString& type, const QVariantMap& body) : m_id(QString::number(QDateTime::currentMSecsSinceEpoch())) , m_type(type) , m_body(body) , m_payload() , m_payloadSize(0) { } void NetworkPacket::createIdentityPacket(NetworkPacket* np) { KdeConnectConfig* config = KdeConnectConfig::instance(); np->m_id = QString::number(QDateTime::currentMSecsSinceEpoch()); np->m_type = PACKET_TYPE_IDENTITY; np->m_payload = QSharedPointer(); np->m_payloadSize = 0; np->set(QStringLiteral("deviceId"), config->deviceId()); np->set(QStringLiteral("deviceName"), config->name()); np->set(QStringLiteral("deviceType"), config->deviceType()); np->set(QStringLiteral("protocolVersion"), NetworkPacket::s_protocolVersion); np->set(QStringLiteral("incomingCapabilities"), PluginLoader::instance()->incomingCapabilities()); np->set(QStringLiteral("outgoingCapabilities"), PluginLoader::instance()->outgoingCapabilities()); //qCDebug(KDECONNECT_CORE) << "createIdentityPacket" << np->serialize(); } template QVariantMap qobject2qvariant(const T* object) { QVariantMap map; auto metaObject = T::staticMetaObject; for(int i = metaObject.propertyOffset(); i < metaObject.propertyCount(); ++i) { QMetaProperty prop = metaObject.property(i); map.insert(QString::fromLatin1(prop.name()), prop.readOnGadget(object)); } return map; } QByteArray NetworkPacket::serialize() const { //Object -> QVariant //QVariantMap variant; //variant["id"] = mId; //variant["type"] = mType; //variant["body"] = mBody; QVariantMap variant = qobject2qvariant(this); if (hasPayload()) { //qCDebug(KDECONNECT_CORE) << "Serializing payloadTransferInfo"; variant[QStringLiteral("payloadSize")] = payloadSize(); variant[QStringLiteral("payloadTransferInfo")] = m_payloadTransferInfo; } //QVariant -> json auto jsonDocument = QJsonDocument::fromVariant(variant); QByteArray json = jsonDocument.toJson(QJsonDocument::Compact); if (json.isEmpty()) { qCDebug(KDECONNECT_CORE) << "Serialization error:"; } else { /*if (!isEncrypted()) { //qCDebug(KDECONNECT_CORE) << "Serialized packet:" << json; }*/ json.append('\n'); } return json; } template void qvariant2qobject(const QVariantMap& variant, T* object) { for ( QVariantMap::const_iterator iter = variant.begin(); iter != variant.end(); ++iter ) { const int propertyIndex = T::staticMetaObject.indexOfProperty(iter.key().toLatin1()); if (propertyIndex < 0) { qCWarning(KDECONNECT_CORE) << "missing property" << object << iter.key(); continue; } QMetaProperty property = T::staticMetaObject.property(propertyIndex); bool ret = property.writeOnGadget(object, *iter); if (!ret) { qCWarning(KDECONNECT_CORE) << "couldn't set" << object << "->" << property.name() << '=' << *iter; } } } bool NetworkPacket::unserialize(const QByteArray& a, NetworkPacket* np) { //Json -> QVariant QJsonParseError parseError; auto parser = QJsonDocument::fromJson(a, &parseError); if (parser.isNull()) { qCDebug(KDECONNECT_CORE) << "Unserialization error:" << parseError.errorString(); return false; } auto variant = parser.toVariant().toMap(); qvariant2qobject(variant, np); np->m_payloadSize = variant[QStringLiteral("payloadSize")].toInt(); //Will return 0 if was not present, which is ok if (np->m_payloadSize == -1) { np->m_payloadSize = np->get(QStringLiteral("size"), -1); } np->m_payloadTransferInfo = variant[QStringLiteral("payloadTransferInfo")].toMap(); //Will return an empty qvariantmap if was not present, which is ok //Ids containing characters that are not allowed as dbus paths would make app crash if (np->m_body.contains(QStringLiteral("deviceId"))) { QString deviceId = np->get(QStringLiteral("deviceId")); - DbusHelper::filterNonExportableCharacters(deviceId); + DBusHelper::filterNonExportableCharacters(deviceId); np->set(QStringLiteral("deviceId"), deviceId); } return true; } FileTransferJob* NetworkPacket::createPayloadTransferJob(const QUrl& destination) const { return new FileTransferJob(this, destination); } diff --git a/core/notificationserverinfo.cpp b/core/notificationserverinfo.cpp index 862ea82c..bddda138 100644 --- a/core/notificationserverinfo.cpp +++ b/core/notificationserverinfo.cpp @@ -1,65 +1,65 @@ /** * Copyright 2019 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationserverinfo.h" #include #include #include #include "dbushelper.h" #include "core_debug.h" NotificationServerInfo& NotificationServerInfo::instance() { static NotificationServerInfo instance; return instance; } void NotificationServerInfo::init() { QDBusMessage query = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("GetCapabilities")); - QDBusPendingReply reply = DbusHelper::sessionBus().asyncCall(query); + QDBusPendingReply reply = DBusHelper::sessionBus().asyncCall(query); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, reply, watcher] { watcher->deleteLater(); if (reply.isError()) { qCWarning(KDECONNECT_CORE) << "Could not query capabilities from notifications server"; return; } if (reply.value().contains(QLatin1String("x-kde-display-appname"))) { m_supportedHints |= X_KDE_DISPLAY_APPNAME; } if (reply.value().contains(QLatin1String("x-kde-origin-name"))) { m_supportedHints |= X_KDE_ORIGIN_NAME; } }); } NotificationServerInfo::Hints NotificationServerInfo::supportedHints() { return m_supportedHints; } diff --git a/daemon/kdeconnectd.cpp b/daemon/kdeconnectd.cpp index 69dca974..f106384f 100644 --- a/daemon/kdeconnectd.cpp +++ b/daemon/kdeconnectd.cpp @@ -1,174 +1,174 @@ /** * Copyright 2014 Yuri Samoilenko * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/backends/pairinghandler.h" #include "kdeconnect-version.h" Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_DAEMON) Q_LOGGING_CATEGORY(KDECONNECT_DAEMON, "kdeconnect.daemon") class DesktopDaemon : public Daemon { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") public: DesktopDaemon(QObject* parent = nullptr) : Daemon(parent) , m_nam(nullptr) {} void askPairingConfirmation(Device* device) override { KNotification* notification = new KNotification(QStringLiteral("pairingRequest")); notification->setIconName(QStringLiteral("dialog-information")); notification->setComponentName(QStringLiteral("kdeconnect")); notification->setText(i18n("Pairing request from %1", device->name().toHtmlEscaped())); notification->setActions(QStringList() << i18n("Accept") << i18n("Reject")); // notification->setTimeout(PairingHandler::pairingTimeoutMsec()); connect(notification, &KNotification::action1Activated, device, &Device::acceptPairing); connect(notification, &KNotification::action2Activated, device, &Device::rejectPairing); notification->sendEvent(); } void reportError(const QString & title, const QString & description) override { qCWarning(KDECONNECT_DAEMON) << title << ":" << description; KNotification::event(KNotification::Error, title, description); } QNetworkAccessManager* networkAccessManager() override { if (!m_nam) { m_nam = new KIO::AccessManager(this); } return m_nam; } Q_SCRIPTABLE void sendSimpleNotification(const QString &eventId, const QString &title, const QString &text, const QString &iconName) override { KNotification* notification = new KNotification(eventId); //KNotification::Persistent notification->setIconName(iconName); notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(title); notification->setText(text); notification->sendEvent(); } void quit() override { QApplication::quit(); } private: QNetworkAccessManager* m_nam; }; // Copied from plasma-workspace/libkworkspace/kworkspace.cpp static void detectPlatform(int argc, char **argv) { if (qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) { return; } for (int i = 0; i < argc; i++) { if (qstrcmp(argv[i], "-platform") == 0 || qstrcmp(argv[i], "--platform") == 0 || QByteArray(argv[i]).startsWith("-platform=") || QByteArray(argv[i]).startsWith("--platform=")) { return; } } const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE"); if (sessionType.isEmpty()) { return; } if (qstrcmp(sessionType, "wayland") == 0) { qputenv("QT_QPA_PLATFORM", "wayland"); } else if (qstrcmp(sessionType, "x11") == 0) { qputenv("QT_QPA_PLATFORM", "xcb"); } } int main(int argc, char* argv[]) { detectPlatform(argc, argv); QApplication app(argc, argv); KAboutData aboutData( QStringLiteral("kdeconnect.daemon"), i18n("KDE Connect Daemon"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Daemon"), KAboutLicense::GPL ); KAboutData::setApplicationData(aboutData); app.setQuitOnLastWindowClosed(false); #ifdef USE_PRIVATE_DBUS - DbusHelper::launchDBusDaemon(); + DBusHelper::launchDBusDaemon(); #endif QCommandLineParser parser; QCommandLineOption replaceOption({QStringLiteral("replace")}, i18n("Replace an existing instance")); parser.addOption(replaceOption); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); if (parser.isSet(replaceOption)) { auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"), QStringLiteral("quit")); - DbusHelper::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits + DBusHelper::sessionBus().call(message); //deliberately block until it's done, so we register the name after the app quits } KDBusService dbusService(KDBusService::Unique); DesktopDaemon daemon; // kdeconnectd is autostarted, so disable session management to speed up startup auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); return app.exec(); } #include "kdeconnectd.moc" diff --git a/fileitemactionplugin/sendfileitemaction.cpp b/fileitemactionplugin/sendfileitemaction.cpp index 61947597..6d6f4738 100644 --- a/fileitemactionplugin/sendfileitemaction.cpp +++ b/fileitemactionplugin/sendfileitemaction.cpp @@ -1,101 +1,101 @@ /* * Copyright (C) 2011 Alejandro Fiestas Olivares * Copyright (C) 2014 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sendfileitemaction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(SendFileItemActionFactory, registerPlugin();) Q_LOGGING_CATEGORY(KDECONNECT_FILEITEMACTION, "kdeconnect.fileitemaction") SendFileItemAction::SendFileItemAction(QObject* parent, const QVariantList& ): KAbstractFileItemActionPlugin(parent) { } QList SendFileItemAction::actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) { QList actions; DaemonDbusInterface iface; if (!iface.isValid()) { return actions; } QDBusPendingReply reply = iface.devices(true, true); reply.waitForFinished(); const QStringList devices = reply.value(); for (const QString& id : devices) { DeviceDbusInterface deviceIface(id); if (!deviceIface.isValid()) { continue; } if (!deviceIface.hasPlugin(QStringLiteral("kdeconnect_share"))) { continue; } QAction* action = new QAction(QIcon::fromTheme(deviceIface.iconName()), deviceIface.name(), parentWidget); action->setProperty("id", id); action->setProperty("urls", QVariant::fromValue(fileItemInfos.urlList())); action->setProperty("parentWidget", QVariant::fromValue(parentWidget)); connect(action, &QAction::triggered, this, &SendFileItemAction::sendFile); actions += action; } if (actions.count() > 1) { QAction* menuAction = new QAction(QIcon::fromTheme(QStringLiteral("kdeconnect")), i18n("Send via KDE Connect"), parentWidget); QMenu* menu = new QMenu(parentWidget); menu->addActions(actions); menuAction->setMenu(menu); return QList() << menuAction; } else { if(actions.count() == 1) { actions.first()->setText(i18n("Send to '%1' via KDE Connect", actions.first()->text())); } return actions; } } void SendFileItemAction::sendFile() { const QList urls = sender()->property("urls").value>(); QString id = sender()->property("id").toString(); for (const QUrl& url : urls) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/share"), QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); - DbusHelper::sessionBus().call(msg); + DBusHelper::sessionBus().call(msg); } } #include "sendfileitemaction.moc" diff --git a/indicator/deviceindicator.cpp b/indicator/deviceindicator.cpp index 8369f95d..20cf2292 100644 --- a/indicator/deviceindicator.cpp +++ b/indicator/deviceindicator.cpp @@ -1,150 +1,150 @@ /* * Copyright 2016 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "deviceindicator.h" #include #include #include "interfaces/dbusinterfaces.h" #include class BatteryAction : public QAction { Q_OBJECT public: BatteryAction(DeviceDbusInterface* device) : QAction(nullptr) , m_batteryIface(new DeviceBatteryDbusInterface(device->id(), this)) { setWhenAvailable(m_batteryIface->charge(), [this](int charge) { setCharge(charge); }, this); setWhenAvailable(m_batteryIface->isCharging(), [this](bool charging) { setCharging(charging); }, this); connect(m_batteryIface, SIGNAL(chargeChanged(int)), this, SLOT(setCharge(int))); connect(m_batteryIface, SIGNAL(stateChanged(bool)), this, SLOT(setCharging(bool))); setIcon(QIcon::fromTheme(QStringLiteral("battery"))); update(); } void update() { if (m_charge < 0) setText(i18n("No Battery")); else if (m_charging) setText(i18n("Battery: %1% (Charging)", m_charge)); else setText(i18n("Battery: %1%", m_charge)); } private Q_SLOTS: void setCharge(int charge) { m_charge = charge; update(); } void setCharging(bool charging) { m_charging = charging; update(); } private: DeviceBatteryDbusInterface* m_batteryIface; int m_charge = -1; bool m_charging = false; }; DeviceIndicator::DeviceIndicator(DeviceDbusInterface* device) : QMenu(device->name(), nullptr) , m_device(device) , m_remoteCommandsInterface(new RemoteCommandsDbusInterface(m_device->id())) { #ifdef Q_OS_WIN setIcon(QIcon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icons/hicolor/scalable/status/") + device->iconName() + QStringLiteral(".svg")))); #else setIcon(QIcon::fromTheme(device->iconName())); #endif connect(device, SIGNAL(nameChanged(QString)), this, SLOT(setText(QString))); auto battery = new BatteryAction(device); addAction(battery); setWhenAvailable(device->hasPlugin(QStringLiteral("kdeconnect_battery")), [battery](bool available) { battery->setVisible(available); } , this); auto browse = addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Browse device")); connect(browse, &QAction::triggered, device, [device](){ SftpDbusInterface* sftpIface = new SftpDbusInterface(device->id(), device); sftpIface->startBrowsing(); sftpIface->deleteLater(); }); setWhenAvailable(device->hasPlugin(QStringLiteral("kdeconnect_sftp")), [browse](bool available) { browse->setVisible(available); }, this); auto findDevice = addAction(QIcon::fromTheme(QStringLiteral("irc-voice")), i18n("Ring device")); connect(findDevice, &QAction::triggered, device, [device](){ FindMyPhoneDeviceDbusInterface* iface = new FindMyPhoneDeviceDbusInterface(device->id(), device); iface->ring(); iface->deleteLater(); }); setWhenAvailable(device->hasPlugin(QStringLiteral("kdeconnect_findmyphone")), [findDevice](bool available) { findDevice->setVisible(available); }, this); auto sendFile = addAction(QIcon::fromTheme(QStringLiteral("document-share")), i18n("Send file")); connect(sendFile, &QAction::triggered, device, [device, this](){ const QUrl url = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Select file to send to '%1'", device->name()), QUrl::fromLocalFile(QDir::homePath())); if (url.isEmpty()) return; QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device->id() + QStringLiteral("/share"), QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); - DbusHelper::sessionBus().call(msg); + DBusHelper::sessionBus().call(msg); }); setWhenAvailable(device->hasPlugin(QStringLiteral("kdeconnect_share")), [sendFile](bool available) { sendFile->setVisible(available); }, this); if (!QStandardPaths::findExecutable(QStringLiteral("kdeconnect-sms")).isEmpty()) { auto smsapp = addAction(QIcon::fromTheme(QStringLiteral("message-new")), i18n("SMS Messages...")); QObject::connect(smsapp, &QAction::triggered, device, [device] () { QProcess::startDetached(QLatin1String("kdeconnect-sms"), { QStringLiteral("--device"), device->id() }); }); setWhenAvailable(device->hasPlugin(QStringLiteral("kdeconnect_sms")), [smsapp](bool available) { smsapp->setVisible(available); }, this); } QMenu* remoteCommandsMenu = new QMenu(i18n("Run command"), this); QAction* menuAction = remoteCommandsMenu->menuAction(); QAction* addCommandAction = remoteCommandsMenu->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add commands")); connect(addCommandAction, &QAction::triggered, m_remoteCommandsInterface, &RemoteCommandsDbusInterface::editCommands); addAction(menuAction); setWhenAvailable(device->hasPlugin(QStringLiteral("kdeconnect_remotecommands")), [this, remoteCommandsMenu, menuAction](bool available) { menuAction->setVisible(available); if (!available) return; const auto cmds = QJsonDocument::fromJson(m_remoteCommandsInterface->commands()).object(); for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) { const QJsonObject cont = it->toObject(); QString key = it.key(); QAction* action = remoteCommandsMenu->addAction(cont.value(QStringLiteral("name")).toString()); connect(action, &QAction::triggered, [this, key] { m_remoteCommandsInterface->triggerCommand(key); }); } }, this); } #include "deviceindicator.moc" diff --git a/indicator/main.cpp b/indicator/main.cpp index f54fe7c9..1e7b54fa 100644 --- a/indicator/main.cpp +++ b/indicator/main.cpp @@ -1,217 +1,217 @@ /* * Copyright 2016 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #ifdef QSYSTRAY #include #else #include #endif #include #include #include #include #include "interfaces/devicesmodel.h" #include "interfaces/dbusinterfaces.h" #include "kdeconnect-version.h" #include "deviceindicator.h" #include int main(int argc, char** argv) { QApplication app(argc, argv); KAboutData about(QStringLiteral("kdeconnect-indicator"), i18n("KDE Connect Indicator"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Indicator tool"), KAboutLicense::GPL, i18n("(C) 2016 Aleix Pol Gonzalez")); KAboutData::setApplicationData(about); #ifdef Q_OS_WIN QProcess kdeconnectd; kdeconnectd.start(QStringLiteral("kdeconnectd.exe")); #endif #ifdef Q_OS_MAC // Unset launchctl env, avoid block - DbusHelper::macosUnsetLaunchctlEnv(); + DBusHelper::macosUnsetLaunchctlEnv(); // Start kdeconnectd QProcess kdeconnectdProcess; if (QFile::exists(QCoreApplication::applicationDirPath() + QStringLiteral("/kdeconnectd"))) { kdeconnectdProcess.startDetached(QCoreApplication::applicationDirPath() + QStringLiteral("/kdeconnectd")); } else if (QFile::exists(QString::fromLatin1(qgetenv("craftRoot")) + QStringLiteral("/../lib/libexec/kdeconnectd"))) { kdeconnectdProcess.startDetached(QString::fromLatin1(qgetenv("craftRoot")) + QStringLiteral("/../lib/libexec/kdeconnectd")); } else { QMessageBox::critical(nullptr, i18n("KDE Connect"), i18n("Cannot find kdeconnectd"), QMessageBox::Abort, QMessageBox::Abort); return -1; } // Wait for dbus daemon env QProcess getLaunchdDBusEnv; int retry = 0; do { getLaunchdDBusEnv.setProgram(QStringLiteral("launchctl")); getLaunchdDBusEnv.setArguments({ QStringLiteral("getenv"), QStringLiteral(KDECONNECT_SESSION_DBUS_LAUNCHD_ENV) }); getLaunchdDBusEnv.start(); getLaunchdDBusEnv.waitForFinished(); QString launchdDBusEnv = QString::fromLocal8Bit(getLaunchdDBusEnv.readAllStandardOutput()); if (launchdDBusEnv.length() > 0) { break; } else if (retry >= 10) { // Show a warning and exit qCritical() << "Fail to get launchctl" << KDECONNECT_SESSION_DBUS_LAUNCHD_ENV << "env"; QMessageBox::critical(nullptr, i18n("KDE Connect"), i18n("Cannot connect to DBus\n" "KDE Connect will quit"), QMessageBox::Abort, QMessageBox::Abort); return -1; } else { QThread::sleep(1); // Retry after 1s retry++; } } while(true); #endif KDBusService dbusService(KDBusService::Unique); DevicesModel model; model.setDisplayFilter(DevicesModel::Reachable | DevicesModel::Paired); QMenu* menu = new QMenu; DaemonDbusInterface iface; auto refreshMenu = [&iface, &model, &menu]() { menu->clear(); auto configure = menu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure...")); QObject::connect(configure, &QAction::triggered, configure, [](){ KCMultiDialog* dialog = new KCMultiDialog; dialog->addModule(QStringLiteral("kcm_kdeconnect")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); }); for (int i=0, count = model.rowCount(); iaddMenu(indicator); } const QStringList requests = iface.pairingRequests(); if (!requests.isEmpty()) { menu->addSection(i18n("Pairing requests")); for(const auto& req: requests) { DeviceDbusInterface* dev = new DeviceDbusInterface(req, menu); auto pairMenu = menu->addMenu(dev->name()); pairMenu->addAction(i18n("Pair"), dev, &DeviceDbusInterface::acceptPairing); pairMenu->addAction(i18n("Reject"), dev, &DeviceDbusInterface::rejectPairing); } } #if (defined Q_OS_MAC || defined Q_OS_WIN) // Add quit menu menu->addAction(i18n("Quit"), [](){ auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect.daemon"), QStringLiteral("/MainApplication"), QStringLiteral("org.qtproject.Qt.QCoreApplication"), QStringLiteral("quit")); - DbusHelper::sessionBus().call(message); + DBusHelper::sessionBus().call(message); QCoreApplication::quit(); // Close this application }); #endif }; QObject::connect(&iface, &DaemonDbusInterface::pairingRequestsChangedProxy, &model, refreshMenu); QObject::connect(&model, &DevicesModel::rowsInserted, &model, refreshMenu); QObject::connect(&model, &DevicesModel::rowsRemoved, &model, refreshMenu); #ifdef Q_OS_MAC const QString iconPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory); if (!iconPath.isNull()) { QStringList themeSearchPaths = QIcon::themeSearchPaths(); themeSearchPaths << iconPath; QIcon::setThemeSearchPaths(themeSearchPaths); } #endif #ifdef QSYSTRAY QSystemTrayIcon systray; #ifdef Q_OS_WIN systray.setIcon(QIcon(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("icons/hicolor/scalable/apps/kdeconnectindicatorwin.svg")))); #else systray.setIcon(QIcon::fromTheme(QStringLiteral("kdeconnectindicatordark"))); #endif systray.setVisible(true); systray.setToolTip(QStringLiteral("KDE Connect")); QObject::connect(&model, &DevicesModel::rowsChanged, &model, [&systray, &model]() { systray.setToolTip(i18np("%1 device connected", "%1 devices connected", model.rowCount())); }); systray.setContextMenu(menu); #else KStatusNotifierItem systray; #ifdef Q_OS_MAC if (!iconPath.isNull()) { systray.setIconByName(QStringLiteral("kdeconnectindicatordark")); } else { // We are in macOS dev env, just continue qWarning() << "Fail to find indicator icon, continue anyway"; } #else systray.setIconByName(QStringLiteral("kdeconnectindicatordark")); #endif systray.setToolTip(QStringLiteral("kdeconnect"), QStringLiteral("KDE Connect"), QStringLiteral("KDE Connect")); systray.setCategory(KStatusNotifierItem::Communications); systray.setStatus(KStatusNotifierItem::Passive); systray.setStandardActionsEnabled(false); QObject::connect(&model, &DevicesModel::rowsChanged, &model, [&systray, &model]() { const auto count = model.rowCount(); systray.setStatus(count == 0 ? KStatusNotifierItem::Passive : KStatusNotifierItem::Active); systray.setToolTip(QStringLiteral("kdeconnect"), QStringLiteral("KDE Connect"), i18np("%1 device connected", "%1 devices connected", count)); }); systray.setContextMenu(menu); #endif refreshMenu(); app.setQuitOnLastWindowClosed(false); return app.exec(); } diff --git a/interfaces/dbusinterfaces.cpp b/interfaces/dbusinterfaces.cpp index 1340bee5..678ac53f 100644 --- a/interfaces/dbusinterfaces.cpp +++ b/interfaces/dbusinterfaces.cpp @@ -1,197 +1,197 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dbusinterfaces.h" #include QString DaemonDbusInterface::activatedService() { static const QString service = QStringLiteral("org.kde.kdeconnect"); - auto reply = DbusHelper::sessionBus().interface()->startService(service); + auto reply = DBusHelper::sessionBus().interface()->startService(service); if (!reply.isValid()) { qWarning() << "error activating kdeconnectd:" << reply.error(); } return service; } DaemonDbusInterface::DaemonDbusInterface(QObject* parent) - : OrgKdeKdeconnectDaemonInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect"), DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDaemonInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect"), DBusHelper::sessionBus(), parent) { connect(this, &OrgKdeKdeconnectDaemonInterface::pairingRequestsChanged, this, &DaemonDbusInterface::pairingRequestsChangedProxy); } DaemonDbusInterface::~DaemonDbusInterface() { } DeviceDbusInterface::DeviceDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") +id, DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") +id, DBusHelper::sessionBus(), parent) , m_id(id) { connect(this, &OrgKdeKdeconnectDeviceInterface::trustedChanged, this, &DeviceDbusInterface::trustedChangedProxy); connect(this, &OrgKdeKdeconnectDeviceInterface::reachableChanged, this, &DeviceDbusInterface::reachableChangedProxy); connect(this, &OrgKdeKdeconnectDeviceInterface::nameChanged, this, &DeviceDbusInterface::nameChangedProxy); connect(this, &OrgKdeKdeconnectDeviceInterface::hasPairingRequestsChanged, this, &DeviceDbusInterface::hasPairingRequestsChangedProxy); } DeviceDbusInterface::~DeviceDbusInterface() { } QString DeviceDbusInterface::id() const { return m_id; } void DeviceDbusInterface::pluginCall(const QString& plugin, const QString& method) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") +id() + QStringLiteral("/") + plugin, QStringLiteral("org.kde.kdeconnect.device.") + plugin, method); - DbusHelper::sessionBus().asyncCall(msg); + DBusHelper::sessionBus().asyncCall(msg); } DeviceBatteryDbusInterface::DeviceBatteryDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceBatteryInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") +id, DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceBatteryInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") +id, DBusHelper::sessionBus(), parent) { } DeviceBatteryDbusInterface::~DeviceBatteryDbusInterface() { } DeviceNotificationsDbusInterface::DeviceNotificationsDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceNotificationsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") +id, DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceNotificationsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") +id, DBusHelper::sessionBus(), parent) { } DeviceNotificationsDbusInterface::~DeviceNotificationsDbusInterface() { } NotificationDbusInterface::NotificationDbusInterface(const QString& deviceId, const QString& notificationId, QObject* parent) - : OrgKdeKdeconnectDeviceNotificationsNotificationInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/notifications/") + notificationId, DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceNotificationsNotificationInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/notifications/") + notificationId, DBusHelper::sessionBus(), parent) , id(notificationId) { } NotificationDbusInterface::~NotificationDbusInterface() { } DeviceConversationsDbusInterface::DeviceConversationsDbusInterface(const QString& deviceId, QObject* parent) - : OrgKdeKdeconnectDeviceConversationsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId, DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceConversationsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId, DBusHelper::sessionBus(), parent) { } DeviceConversationsDbusInterface::~DeviceConversationsDbusInterface() { } SftpDbusInterface::SftpDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceSftpInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/sftp"), DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceSftpInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/sftp"), DBusHelper::sessionBus(), parent) { } SftpDbusInterface::~SftpDbusInterface() { } MprisDbusInterface::MprisDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceMprisremoteInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/mprisremote"), DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceMprisremoteInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/mprisremote"), DBusHelper::sessionBus(), parent) { connect(this, &OrgKdeKdeconnectDeviceMprisremoteInterface::propertiesChanged, this, &MprisDbusInterface::propertiesChangedProxy); } MprisDbusInterface::~MprisDbusInterface() { } RemoteControlDbusInterface::RemoteControlDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceRemotecontrolInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/remotecontrol"), DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceRemotecontrolInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/remotecontrol"), DBusHelper::sessionBus(), parent) { } RemoteControlDbusInterface::~RemoteControlDbusInterface() { } LockDeviceDbusInterface::LockDeviceDbusInterface(const QString& id, QObject* parent) - : OrgKdeKdeconnectDeviceLockdeviceInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/lockdevice"), DbusHelper::sessionBus(), parent) + : OrgKdeKdeconnectDeviceLockdeviceInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + id + QStringLiteral("/lockdevice"), DBusHelper::sessionBus(), parent) { connect(this, &OrgKdeKdeconnectDeviceLockdeviceInterface::lockedChanged, this, &LockDeviceDbusInterface::lockedChangedProxy); Q_ASSERT(isValid()); } LockDeviceDbusInterface::~LockDeviceDbusInterface() { } FindMyPhoneDeviceDbusInterface::FindMyPhoneDeviceDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceFindmyphoneInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/findmyphone"), DbusHelper::sessionBus(), parent) + OrgKdeKdeconnectDeviceFindmyphoneInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/findmyphone"), DBusHelper::sessionBus(), parent) { } FindMyPhoneDeviceDbusInterface::~FindMyPhoneDeviceDbusInterface() { } RemoteCommandsDbusInterface::RemoteCommandsDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceRemotecommandsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/remotecommands"), DbusHelper::sessionBus(), parent) + OrgKdeKdeconnectDeviceRemotecommandsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/remotecommands"), DBusHelper::sessionBus(), parent) { } RemoteCommandsDbusInterface::~RemoteCommandsDbusInterface() = default; RemoteKeyboardDbusInterface::RemoteKeyboardDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceRemotekeyboardInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/remotekeyboard"), DbusHelper::sessionBus(), parent) + OrgKdeKdeconnectDeviceRemotekeyboardInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/remotekeyboard"), DBusHelper::sessionBus(), parent) { connect(this, &OrgKdeKdeconnectDeviceRemotekeyboardInterface::remoteStateChanged, this, &RemoteKeyboardDbusInterface::remoteStateChanged); } RemoteKeyboardDbusInterface::~RemoteKeyboardDbusInterface() = default; SmsDbusInterface::SmsDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceSmsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/sms"), DbusHelper::sessionBus(), parent) + OrgKdeKdeconnectDeviceSmsInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/sms"), DBusHelper::sessionBus(), parent) { } SmsDbusInterface::~SmsDbusInterface() = default; ShareDbusInterface::ShareDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceShareInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/share"), DbusHelper::sessionBus(), parent) + OrgKdeKdeconnectDeviceShareInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/share"), DBusHelper::sessionBus(), parent) { } ShareDbusInterface::~ShareDbusInterface() = default; RemoteSystemVolumeDbusInterface::RemoteSystemVolumeDbusInterface(const QString& deviceId, QObject* parent): - OrgKdeKdeconnectDeviceRemotesystemvolumeInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/remotesystemvolume"), DbusHelper::sessionBus(), parent) + OrgKdeKdeconnectDeviceRemotesystemvolumeInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/remotesystemvolume"), DBusHelper::sessionBus(), parent) { } diff --git a/interfaces/devicesmodel.cpp b/interfaces/devicesmodel.cpp index 25f2bc49..1be6c92e 100644 --- a/interfaces/devicesmodel.cpp +++ b/interfaces/devicesmodel.cpp @@ -1,315 +1,315 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "devicesmodel.h" #include "interfaces_debug.h" #include #include #include #include #include #include #include "dbusinterfaces.h" #include // #include "modeltest.h" Q_LOGGING_CATEGORY(KDECONNECT_INTERFACES, "kdeconnect.interfaces"); static QString createId() { return QCoreApplication::instance()->applicationName()+QString::number(QCoreApplication::applicationPid()); } Q_GLOBAL_STATIC_WITH_ARGS(QString, s_keyId, (createId())); DevicesModel::DevicesModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(new DaemonDbusInterface(this)) , m_displayFilter(StatusFilterFlag::NoFilter) { //new ModelTest(this, this); connect(this, &QAbstractItemModel::rowsRemoved, this, &DevicesModel::rowsChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &DevicesModel::rowsChanged); connect(m_dbusInterface, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); connect(m_dbusInterface, &OrgKdeKdeconnectDaemonInterface::deviceVisibilityChanged, this, &DevicesModel::deviceUpdated); connect(m_dbusInterface, &OrgKdeKdeconnectDaemonInterface::deviceRemoved, this, &DevicesModel::deviceRemoved); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), - DbusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &DevicesModel::refreshDeviceList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &DevicesModel::clearDevices); //refresh the view, acquireDiscoveryMode if necessary setDisplayFilter(NoFilter); } QHash< int, QByteArray > DevicesModel::roleNames() const { QHash names = QAbstractItemModel::roleNames(); names.insert(IdModelRole, "deviceId"); names.insert(IconNameRole, "iconName"); names.insert(DeviceRole, "device"); names.insert(StatusModelRole, "status"); return names; } DevicesModel::~DevicesModel() { m_dbusInterface->releaseDiscoveryMode(*s_keyId); } int DevicesModel::rowForDevice(const QString& id) const { for (int i = 0, c=m_deviceList.size(); iid() == id) { return i; } } return -1; } void DevicesModel::deviceAdded(const QString& id) { if (rowForDevice(id) >= 0) { Q_ASSERT_X(false, "deviceAdded", "Trying to add a device twice"); return; } DeviceDbusInterface* dev = new DeviceDbusInterface(id, this); Q_ASSERT(dev->isValid()); if (! passesFilter(dev)) { delete dev; return; } beginInsertRows(QModelIndex(), m_deviceList.size(), m_deviceList.size()); appendDevice(dev); endInsertRows(); } void DevicesModel::deviceRemoved(const QString& id) { int row = rowForDevice(id); if (row>=0) { beginRemoveRows(QModelIndex(), row, row); delete m_deviceList.takeAt(row); endRemoveRows(); } } void DevicesModel::deviceUpdated(const QString& id, bool isVisible) { Q_UNUSED(isVisible); int row = rowForDevice(id); if (row < 0) { // FIXME: when m_dbusInterface is not valid refreshDeviceList() does // nothing and we can miss some devices. // Someone can reproduce this problem by restarting kdeconnectd while // kdeconnect's plasmoid is still running. // Another reason for this branch is that we removed the device previously // because of the filter settings. qCDebug(KDECONNECT_INTERFACES) << "Adding missing or previously removed device" << id; deviceAdded(id); } else { DeviceDbusInterface* dev = getDevice(row); if (! passesFilter(dev)) { beginRemoveRows(QModelIndex(), row, row); delete m_deviceList.takeAt(row); endRemoveRows(); qCDebug(KDECONNECT_INTERFACES) << "Removed changed device " << id; } else { const QModelIndex idx = index(row); Q_EMIT dataChanged(idx, idx); } } } int DevicesModel::displayFilter() const { return m_displayFilter; } void DevicesModel::setDisplayFilter(int flags) { m_displayFilter = (StatusFilterFlag)flags; const bool reachableNeeded = (m_displayFilter & StatusFilterFlag::Reachable); if (reachableNeeded) m_dbusInterface->acquireDiscoveryMode(*s_keyId); else m_dbusInterface->releaseDiscoveryMode(*s_keyId); refreshDeviceList(); } void DevicesModel::refreshDeviceList() { if (!m_dbusInterface->isValid()) { clearDevices(); qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } bool onlyPaired = (m_displayFilter & StatusFilterFlag::Paired); bool onlyReachable = (m_displayFilter & StatusFilterFlag::Reachable); QDBusPendingReply pendingDeviceIds = m_dbusInterface->devices(onlyReachable, onlyPaired); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingDeviceIds, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &DevicesModel::receivedDeviceList); } void DevicesModel::receivedDeviceList(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearDevices(); QDBusPendingReply pendingDeviceIds = *watcher; if (pendingDeviceIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << "error while refreshing device list" << pendingDeviceIds.error().message(); return; } Q_ASSERT(m_deviceList.isEmpty()); const QStringList deviceIds = pendingDeviceIds.value(); if (deviceIds.isEmpty()) return; beginInsertRows(QModelIndex(), 0, deviceIds.count()-1); for (const QString& id : deviceIds) { appendDevice(new DeviceDbusInterface(id, this)); } endInsertRows(); } void DevicesModel::appendDevice(DeviceDbusInterface* dev) { m_deviceList.append(dev); connect(dev, &OrgKdeKdeconnectDeviceInterface::nameChanged, this, &DevicesModel::nameChanged); } void DevicesModel::nameChanged(const QString& newName) { Q_UNUSED(newName); DeviceDbusInterface* device = static_cast(sender()); Q_ASSERT(rowForDevice(device->id()) >= 0); deviceUpdated(device->id(), true); } void DevicesModel::clearDevices() { if (!m_deviceList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_deviceList.size() - 1); qDeleteAll(m_deviceList); m_deviceList.clear(); endRemoveRows(); } } QVariant DevicesModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_deviceList.size()) { return QVariant(); } Q_ASSERT(m_dbusInterface->isValid()); DeviceDbusInterface* device = m_deviceList[index.row()]; Q_ASSERT(device->isValid()); //This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { case Qt::SizeHintRole: return QSize(0, 32); case IconModelRole: { QString icon = data(index, IconNameRole).toString(); return QIcon::fromTheme(icon); } case IdModelRole: return device->id(); case NameModelRole: return device->name(); case Qt::ToolTipRole: { bool trusted = device->isTrusted(); bool reachable = device->isReachable(); QString status = reachable? (trusted? i18n("Device trusted and connected") : i18n("Device not trusted")) : i18n("Device disconnected"); return status; } case StatusModelRole: { int status = StatusFilterFlag::NoFilter; if (device->isReachable()) { status |= StatusFilterFlag::Reachable; } if (device->isTrusted()) { status |= StatusFilterFlag::Paired; } return status; } case IconNameRole: return device->statusIconName(); case DeviceRole: return QVariant::fromValue(device); default: return QVariant(); } } DeviceDbusInterface* DevicesModel::getDevice(int row) const { if (row < 0 || row >= m_deviceList.size()) { return nullptr; } return m_deviceList[row]; } int DevicesModel::rowCount(const QModelIndex& parent) const { if(parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_deviceList.size(); } bool DevicesModel::passesFilter(DeviceDbusInterface* dev) const { bool onlyPaired = (m_displayFilter & StatusFilterFlag::Paired); bool onlyReachable = (m_displayFilter & StatusFilterFlag::Reachable); return !((onlyReachable && !dev->isReachable()) || (onlyPaired && !dev->isTrusted())); } diff --git a/interfaces/notificationsmodel.cpp b/interfaces/notificationsmodel.cpp index c690297e..99c0bb03 100644 --- a/interfaces/notificationsmodel.cpp +++ b/interfaces/notificationsmodel.cpp @@ -1,272 +1,272 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationsmodel.h" #include "interfaces_debug.h" #include #include #include #include //#include "modeltest.h" //In older Qt released, qAsConst isnt available #include "core/qtcompat_p.h" NotificationsModel::NotificationsModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { //new ModelTest(this, this); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::dataChanged, this, &NotificationsModel::anyDismissableChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::anyDismissableChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), - DbusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &NotificationsModel::refreshNotificationList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &NotificationsModel::clearNotifications); } QHash NotificationsModel::roleNames() const { //Role names for QML QHash names = QAbstractItemModel::roleNames(); names.insert(DbusInterfaceRole, "dbusInterface"); names.insert(AppNameModelRole, "appName"); names.insert(IdModelRole, "notificationId"); names.insert(DismissableModelRole, "dismissable"); names.insert(RepliableModelRole, "repliable"); names.insert(IconPathModelRole, "appIcon"); names.insert(TitleModelRole, "title"); names.insert(TextModelRole, "notitext"); return names; } NotificationsModel::~NotificationsModel() { } QString NotificationsModel::deviceId() const { return m_deviceId; } void NotificationsModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new DeviceNotificationsDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationPosted, this, &NotificationsModel::notificationAdded); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationRemoved, this, &NotificationsModel::notificationRemoved); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::allNotificationsRemoved, this, &NotificationsModel::clearNotifications); refreshNotificationList(); Q_EMIT deviceIdChanged(deviceId); } void NotificationsModel::notificationAdded(const QString& id) { beginInsertRows(QModelIndex(), 0, 0); NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, id, this); connect(dbusInterface, &NotificationDbusInterface::ready, this, &NotificationsModel::notificationUpdated); m_notificationList.prepend(dbusInterface); endInsertRows(); } void NotificationsModel::notificationRemoved(const QString& id) { for (int i = 0; i < m_notificationList.size(); ++i) { if (m_notificationList[i]->notificationId() == id) { beginRemoveRows(QModelIndex(), i, i); m_notificationList.removeAt(i); endRemoveRows(); return; } } qCWarning(KDECONNECT_INTERFACES) << "Attempted to remove unknown notification: " << id; } void NotificationsModel::refreshNotificationList() { if (!m_dbusInterface) { return; } clearNotifications(); if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } QDBusPendingReply pendingNotificationIds = m_dbusInterface->activeNotifications(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingNotificationIds, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &NotificationsModel::receivedNotifications); } void NotificationsModel::receivedNotifications(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearNotifications(); QDBusPendingReply pendingNotificationIds = *watcher; if (pendingNotificationIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << pendingNotificationIds.error(); return; } const QStringList notificationIds = pendingNotificationIds.value(); if (notificationIds.isEmpty()) { return; } beginInsertRows(QModelIndex(), 0, notificationIds.size() - 1); for (const QString& notificationId : notificationIds) { NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, notificationId, this); m_notificationList.append(dbusInterface); } endInsertRows(); } QVariant NotificationsModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_notificationList.count() || !m_notificationList[index.row()]->isValid()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } NotificationDbusInterface* notification = m_notificationList[index.row()]; //FIXME: This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { case IconModelRole: return QIcon::fromTheme(QStringLiteral("device-notifier")); case IdModelRole: return notification->internalId(); case NameModelRole: return notification->ticker(); case ContentModelRole: return QString(); //To implement in the Android side case AppNameModelRole: return notification->appName(); case DbusInterfaceRole: return QVariant::fromValue(notification); case DismissableModelRole: return notification->dismissable(); case RepliableModelRole: return !notification->replyId().isEmpty(); case IconPathModelRole: return notification->iconPath(); case TitleModelRole: return notification->title(); case TextModelRole: return notification->text(); default: return QVariant(); } } NotificationDbusInterface* NotificationsModel::getNotification(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } int row = index.row(); if (row < 0 || row >= m_notificationList.size()) { return nullptr; } return m_notificationList[row]; } int NotificationsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_notificationList.count(); } bool NotificationsModel::isAnyDimissable() const { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { return true; } } return false; } void NotificationsModel::dismissAll() { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { notification->dismiss(); } } } void NotificationsModel::clearNotifications() { if (!m_notificationList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_notificationList.size() - 1); qDeleteAll(m_notificationList); m_notificationList.clear(); endRemoveRows(); } } void NotificationsModel::notificationUpdated() { Q_EMIT dataChanged(index(0,0), index(m_notificationList.size() - 1, 0)); } diff --git a/interfaces/remotecommandsmodel.cpp b/interfaces/remotecommandsmodel.cpp index f5325247..bc7a7db0 100644 --- a/interfaces/remotecommandsmodel.cpp +++ b/interfaces/remotecommandsmodel.cpp @@ -1,155 +1,155 @@ /** * Copyright 2018 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "remotecommandsmodel.h" #include "interfaces_debug.h" #include #include #include RemoteCommandsModel::RemoteCommandsModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { connect(this, &QAbstractItemModel::rowsInserted, this, &RemoteCommandsModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &RemoteCommandsModel::rowsChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), - DbusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &RemoteCommandsModel::refreshCommandList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &RemoteCommandsModel::clearCommands); } QHash RemoteCommandsModel::roleNames() const { //Role names for QML QHash names = QAbstractItemModel::roleNames(); names.insert(KeyRole, "key"); names.insert(NameRole, "name"); names.insert(CommandRole, "command"); return names; } RemoteCommandsModel::~RemoteCommandsModel() { } QString RemoteCommandsModel::deviceId() const { return m_deviceId; } void RemoteCommandsModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new RemoteCommandsDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotecommandsInterface::commandsChanged, this, &RemoteCommandsModel::refreshCommandList); refreshCommandList(); Q_EMIT deviceIdChanged(deviceId); } void RemoteCommandsModel::refreshCommandList() { if (!m_dbusInterface) { return; } clearCommands(); if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } const auto cmds = QJsonDocument::fromJson(m_dbusInterface->commands()).object(); beginResetModel(); for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) { const QJsonObject cont = it->toObject(); Command command; command.key = it.key(); command.name = cont.value(QStringLiteral("name")).toString(); command.command = cont.value(QStringLiteral("command")).toString(); m_commandList.append(command); } endResetModel(); } QVariant RemoteCommandsModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_commandList.count()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } Command command = m_commandList[index.row()]; switch (role) { case KeyRole: return command.key; case NameRole: return command.name; case CommandRole: return command.command; default: return QVariant(); } } int RemoteCommandsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_commandList.count(); } void RemoteCommandsModel::clearCommands() { if (!m_commandList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_commandList.size() - 1); m_commandList.clear(); endRemoveRows(); } } diff --git a/interfaces/remotesinksmodel.cpp b/interfaces/remotesinksmodel.cpp index 8932ac65..4b986b31 100644 --- a/interfaces/remotesinksmodel.cpp +++ b/interfaces/remotesinksmodel.cpp @@ -1,175 +1,175 @@ /** * Copyright 2018 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "remotesinksmodel.h" #include "interfaces_debug.h" #include #include #include RemoteSinksModel::RemoteSinksModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { connect(this, &QAbstractItemModel::rowsInserted, this, &RemoteSinksModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &RemoteSinksModel::rowsChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), - DbusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &RemoteSinksModel::refreshSinkList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &RemoteSinksModel::refreshSinkList); } QHash RemoteSinksModel::roleNames() const { //Role names for QML QHash names = QAbstractItemModel::roleNames(); names.insert(NameRole, "name"); names.insert(DescriptionRole, "description"); names.insert(MaxVolumeRole, "maxVolume"); names.insert(VolumeRole, "volume"); names.insert(MutedRole, "muted"); return names; } RemoteSinksModel::~RemoteSinksModel() { } QString RemoteSinksModel::deviceId() const { return m_deviceId; } void RemoteSinksModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new RemoteSystemVolumeDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotesystemvolumeInterface::sinksChanged, this, &RemoteSinksModel::refreshSinkList); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotesystemvolumeInterface::volumeChanged, this, [this](const QString& name, int volume) { for (Sink* s: qAsConst(m_sinkList)) { if (s->name == name) { s->volume = volume; Q_EMIT dataChanged(index(0,0), index(m_sinkList.size() - 1, 0)); } } }); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotesystemvolumeInterface::mutedChanged, this, [this](const QString& name, bool muted) { for (Sink* s: qAsConst(m_sinkList)) { if (s->name == name) { s->muted = muted; Q_EMIT dataChanged(index(0,0), index(m_sinkList.size() - 1, 0)); } } }); refreshSinkList(); Q_EMIT deviceIdChanged(deviceId); } void RemoteSinksModel::refreshSinkList() { if (!m_dbusInterface) { return; } if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } const auto cmds = QJsonDocument::fromJson(m_dbusInterface->sinks()).array(); beginResetModel(); qDeleteAll(m_sinkList); m_sinkList.clear(); for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) { const QJsonObject cont = it->toObject(); Sink* sink = new Sink(); sink->name = cont.value(QStringLiteral("name")).toString(); sink->description = cont.value(QStringLiteral("description")).toString(); sink->maxVolume = cont.value(QStringLiteral("maxVolume")).toInt(); sink->volume = cont.value(QStringLiteral("volume")).toInt(); sink->muted = cont.value(QStringLiteral("muted")).toBool(); m_sinkList.append(sink); } endResetModel(); } QVariant RemoteSinksModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_sinkList.count()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } Sink* sink = m_sinkList[index.row()]; switch (role) { case NameRole: return sink->name; case DescriptionRole: return sink->description; case MaxVolumeRole: return sink->maxVolume; case VolumeRole: return sink->volume; case MutedRole: return sink->muted; default: return QVariant(); } } int RemoteSinksModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_sinkList.count(); } diff --git a/plugins/lockdevice/lockdeviceplugin.cpp b/plugins/lockdevice/lockdeviceplugin.cpp index 454f52e1..da694551 100644 --- a/plugins/lockdevice/lockdeviceplugin.cpp +++ b/plugins/lockdevice/lockdeviceplugin.cpp @@ -1,103 +1,103 @@ /** * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "lockdeviceplugin.h" #include #include #include #include #include "screensaverdbusinterface.h" #include #include K_PLUGIN_CLASS_WITH_JSON(LockDevicePlugin, "kdeconnect_lockdevice.json") Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_LOCKREMOTE, "kdeconnect.plugin.lock") LockDevicePlugin::LockDevicePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_remoteLocked(false) , m_iface(nullptr) { } LockDevicePlugin::~LockDevicePlugin() { delete m_iface; } bool LockDevicePlugin::isLocked() const { return m_remoteLocked; } void LockDevicePlugin::setLocked(bool locked) { NetworkPacket np(PACKET_TYPE_LOCK_REQUEST, {{QStringLiteral("setLocked"), locked}}); sendPacket(np); } bool LockDevicePlugin::receivePacket(const NetworkPacket & np) { if (np.has(QStringLiteral("isLocked"))) { bool locked = np.get(QStringLiteral("isLocked")); if (m_remoteLocked != locked) { m_remoteLocked = locked; Q_EMIT lockedChanged(locked); } } bool sendState = np.has(QStringLiteral("requestLocked")); if (np.has(QStringLiteral("setLocked"))) { iface()->SetActive(np.get(QStringLiteral("setLocked"))); sendState = true; } if (sendState) { NetworkPacket np(PACKET_TYPE_LOCK, QVariantMap {{QStringLiteral("isLocked"), QVariant::fromValue(iface()->GetActive())}}); sendPacket(np); } return true; } OrgFreedesktopScreenSaverInterface* LockDevicePlugin::iface() { if (!m_iface) { - m_iface = new OrgFreedesktopScreenSaverInterface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/org/freedesktop/ScreenSaver"), DbusHelper::sessionBus()); + m_iface = new OrgFreedesktopScreenSaverInterface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/org/freedesktop/ScreenSaver"), DBusHelper::sessionBus()); if(!m_iface->isValid()) qCWarning(KDECONNECT_PLUGIN_LOCKREMOTE) << "Couldn't connect to the ScreenSaver interface"; } return m_iface; } void LockDevicePlugin::connected() { NetworkPacket np(PACKET_TYPE_LOCK_REQUEST, {{QStringLiteral("requestLocked"), QVariant()}}); sendPacket(np); } QString LockDevicePlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/lockdevice"); } #include "lockdeviceplugin.moc" diff --git a/plugins/mpriscontrol/mpriscontrolplugin.cpp b/plugins/mpriscontrol/mpriscontrolplugin.cpp index 6bd58f1b..1fb8f75c 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin.cpp +++ b/plugins/mpriscontrol/mpriscontrolplugin.cpp @@ -1,382 +1,382 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mpriscontrolplugin.h" #include #include #include #include #include #include #include #include #include #include "mprisdbusinterface.h" #include "propertiesdbusinterface.h" K_PLUGIN_CLASS_WITH_JSON(MprisControlPlugin, "kdeconnect_mpriscontrol.json") Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRIS, "kdeconnect.plugin.mpris") MprisPlayer::MprisPlayer(const QString& serviceName, const QString& dbusObjectPath, const QDBusConnection& busConnection) : m_serviceName(serviceName) , m_propertiesInterface(new OrgFreedesktopDBusPropertiesInterface(serviceName, dbusObjectPath, busConnection)) , m_mediaPlayer2PlayerInterface(new OrgMprisMediaPlayer2PlayerInterface(serviceName, dbusObjectPath, busConnection)) { m_mediaPlayer2PlayerInterface->setTimeout(500); } MprisControlPlugin::MprisControlPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , prevVolume(-1) { - m_watcher = new QDBusServiceWatcher(QString(), DbusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + m_watcher = new QDBusServiceWatcher(QString(), DBusHelper::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); // TODO: QDBusConnectionInterface::serviceOwnerChanged is deprecated, maybe query org.freedesktop.DBus directly? - connect(DbusHelper::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &MprisControlPlugin::serviceOwnerChanged); + connect(DBusHelper::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &MprisControlPlugin::serviceOwnerChanged); //Add existing interfaces - const QStringList services = DbusHelper::sessionBus().interface()->registeredServiceNames().value(); + const QStringList services = DBusHelper::sessionBus().interface()->registeredServiceNames().value(); for (const QString& service : services) { // The string doesn't matter, it just needs to be empty/non-empty serviceOwnerChanged(service, QLatin1String(""), QStringLiteral("1")); } } // Copied from the mpris2 dataengine in the plasma-workspace repository void MprisControlPlugin::serviceOwnerChanged(const QString& serviceName, const QString& oldOwner, const QString& newOwner) { if (!serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) return; if (!oldOwner.isEmpty()) { qCDebug(KDECONNECT_PLUGIN_MPRIS) << "MPRIS service" << serviceName << "just went offline"; removePlayer(serviceName); } if (!newOwner.isEmpty()) { qCDebug(KDECONNECT_PLUGIN_MPRIS) << "MPRIS service" << serviceName << "just came online"; addPlayer(serviceName); } } void MprisControlPlugin::addPlayer(const QString& service) { const QString mediaPlayerObjectPath = QStringLiteral("/org/mpris/MediaPlayer2"); // estimate identifier string QDBusInterface mprisInterface(service, mediaPlayerObjectPath, QStringLiteral("org.mpris.MediaPlayer2")); //FIXME: This call hangs and returns an empty string if KDED is still starting! QString identity = mprisInterface.property("Identity").toString(); if (identity.isEmpty()) { identity = service.mid(sizeof("org.mpris.MediaPlayer2")); } QString uniqueName = identity; for (int i = 2; playerList.contains(uniqueName); ++i) { uniqueName = identity + QLatin1String(" [") + QString::number(i) + QLatin1Char(']'); } - MprisPlayer player(service, mediaPlayerObjectPath, DbusHelper::sessionBus()); + MprisPlayer player(service, mediaPlayerObjectPath, DBusHelper::sessionBus()); playerList.insert(uniqueName, player); connect(player.propertiesInterface(), &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &MprisControlPlugin::propertiesChanged); connect(player.mediaPlayer2PlayerInterface(), &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &MprisControlPlugin::seeked); qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris addPlayer" << service << "->" << uniqueName; sendPlayerList(); } void MprisControlPlugin::seeked(qlonglong position){ //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeked in player"; OrgMprisMediaPlayer2PlayerInterface* mediaPlayer2PlayerInterface = (OrgMprisMediaPlayer2PlayerInterface*)sender(); const auto end = playerList.constEnd(); const auto it = std::find_if(playerList.constBegin(), end, [mediaPlayer2PlayerInterface](const MprisPlayer& player) { return (player.mediaPlayer2PlayerInterface() == mediaPlayer2PlayerInterface); }); if (it == end) { qCWarning(KDECONNECT_PLUGIN_MPRIS) << "Seeked signal received for no longer tracked service" << mediaPlayer2PlayerInterface->service(); return; } const QString& playerName = it.key(); NetworkPacket np(PACKET_TYPE_MPRIS, { {QStringLiteral("pos"), position/1000}, //Send milis instead of nanos {QStringLiteral("player"), playerName} }); sendPacket(np); } void MprisControlPlugin::propertiesChanged(const QString& propertyInterface, const QVariantMap& properties) { Q_UNUSED(propertyInterface); OrgFreedesktopDBusPropertiesInterface* propertiesInterface = (OrgFreedesktopDBusPropertiesInterface*)sender(); const auto end = playerList.constEnd(); const auto it = std::find_if(playerList.constBegin(), end, [propertiesInterface](const MprisPlayer& player) { return (player.propertiesInterface() == propertiesInterface); }); if (it == end) { qCWarning(KDECONNECT_PLUGIN_MPRIS) << "PropertiesChanged signal received for no longer tracked service" << propertiesInterface->service(); return; } OrgMprisMediaPlayer2PlayerInterface* const mediaPlayer2PlayerInterface = it.value().mediaPlayer2PlayerInterface(); const QString& playerName = it.key(); NetworkPacket np(PACKET_TYPE_MPRIS); bool somethingToSend = false; if (properties.contains(QStringLiteral("Volume"))) { int volume = (int) (properties[QStringLiteral("Volume")].toDouble()*100); if (volume != prevVolume) { np.set(QStringLiteral("volume"),volume); prevVolume = volume; somethingToSend = true; } } if (properties.contains(QStringLiteral("Metadata"))) { QDBusArgument bullshit = qvariant_cast(properties[QStringLiteral("Metadata")]); QVariantMap nowPlayingMap; bullshit >> nowPlayingMap; mprisPlayerMetadataToNetworkPacket(np, nowPlayingMap); somethingToSend = true; } if (properties.contains(QStringLiteral("PlaybackStatus"))) { bool playing = (properties[QStringLiteral("PlaybackStatus")].toString() == QLatin1String("Playing")); np.set(QStringLiteral("isPlaying"), playing); somethingToSend = true; } if (properties.contains(QStringLiteral("CanPause"))) { np.set(QStringLiteral("canPause"), properties[QStringLiteral("CanPause")].toBool()); somethingToSend = true; } if (properties.contains(QStringLiteral("CanPlay"))) { np.set(QStringLiteral("canPlay"), properties[QStringLiteral("CanPlay")].toBool()); somethingToSend = true; } if (properties.contains(QStringLiteral("CanGoNext"))) { np.set(QStringLiteral("canGoNext"), properties[QStringLiteral("CanGoNext")].toBool()); somethingToSend = true; } if (properties.contains(QStringLiteral("CanGoPrevious"))) { np.set(QStringLiteral("canGoPrevious"), properties[QStringLiteral("CanGoPrevious")].toBool()); somethingToSend = true; } if (properties.contains(QStringLiteral("CanSeek"))) { np.set(QStringLiteral("canSeek"), properties[QStringLiteral("CanSeek")].toBool()); somethingToSend = true; } if (somethingToSend) { np.set(QStringLiteral("player"), playerName); // Always also update the position if (mediaPlayer2PlayerInterface->canSeek()) { long long pos = mediaPlayer2PlayerInterface->position(); np.set(QStringLiteral("pos"), pos/1000); //Send milis instead of nanos } sendPacket(np); } } void MprisControlPlugin::removePlayer(const QString& serviceName) { const auto end = playerList.end(); const auto it = std::find_if(playerList.begin(), end, [serviceName](const MprisPlayer& player) { return (player.serviceName() == serviceName); }); if (it == end) { qCWarning(KDECONNECT_PLUGIN_MPRIS) << "Could not find player for serviceName" << serviceName; return; } const QString& playerName = it.key(); qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris removePlayer" << serviceName << "->" << playerName; playerList.erase(it); sendPlayerList(); } bool MprisControlPlugin::sendAlbumArt(const NetworkPacket& np) { const QString player = np.get(QStringLiteral("player")); auto it = playerList.find(player); bool valid_player = (it != playerList.end()); if (!valid_player) { return false; } //Get mpris information auto& mprisInterface = *it.value().mediaPlayer2PlayerInterface(); QVariantMap nowPlayingMap = mprisInterface.metadata(); //Check if the supplied album art url indeed belongs to this mpris player QUrl playerAlbumArtUrl{nowPlayingMap[QStringLiteral("mpris:artUrl")].toString()}; QString requestedAlbumArtUrl = np.get(QStringLiteral("albumArtUrl")); if (!playerAlbumArtUrl.isValid() || playerAlbumArtUrl != QUrl(requestedAlbumArtUrl)) { return false; } //Only support sending local files if (playerAlbumArtUrl.scheme() != QStringLiteral("file")) { return false; } //Open the file to send QSharedPointer art{new QFile(playerAlbumArtUrl.toLocalFile())}; //Send the album art as payload NetworkPacket answer(PACKET_TYPE_MPRIS); answer.set(QStringLiteral("transferringAlbumArt"), true); answer.set(QStringLiteral("player"), player); answer.set(QStringLiteral("albumArtUrl"), requestedAlbumArtUrl); answer.setPayload(art, art->size()); sendPacket(answer); return true; } bool MprisControlPlugin::receivePacket (const NetworkPacket& np) { if (np.has(QStringLiteral("playerList"))) { return false; //Whoever sent this is an mpris client and not an mpris control! } if (np.has(QStringLiteral("albumArtUrl"))) { return sendAlbumArt(np); } //Send the player list const QString player = np.get(QStringLiteral("player")); auto it = playerList.find(player); bool valid_player = (it != playerList.end()); if (!valid_player || np.get(QStringLiteral("requestPlayerList"))) { sendPlayerList(); if (!valid_player) { return true; } } //Do something to the mpris interface const QString& serviceName = it.value().serviceName(); // turn from pointer to reference to keep the patch diff small, // actual patch would change all "mprisInterface." into "mprisInterface->" auto& mprisInterface = *it.value().mediaPlayer2PlayerInterface(); if (np.has(QStringLiteral("action"))) { const QString& action = np.get(QStringLiteral("action")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Calling action" << action << "in" << serviceName; //TODO: Check for valid actions, currently we trust anything the other end sends us mprisInterface.call(action); } if (np.has(QStringLiteral("setVolume"))) { double volume = np.get(QStringLiteral("setVolume"))/100.f; qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Setting volume" << volume << "to" << serviceName; mprisInterface.setVolume(volume); } if (np.has(QStringLiteral("Seek"))) { int offset = np.get(QStringLiteral("Seek")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeking" << offset << "to" << serviceName; mprisInterface.Seek(offset); } if (np.has(QStringLiteral("SetPosition"))){ qlonglong position = np.get(QStringLiteral("SetPosition"),0)*1000; qlonglong seek = position - mprisInterface.position(); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Setting position by seeking" << seek << "to" << serviceName; mprisInterface.Seek(seek); } //Send something read from the mpris interface NetworkPacket answer(PACKET_TYPE_MPRIS); bool somethingToSend = false; if (np.get(QStringLiteral("requestNowPlaying"))) { QVariantMap nowPlayingMap = mprisInterface.metadata(); mprisPlayerMetadataToNetworkPacket(answer, nowPlayingMap); qlonglong pos = mprisInterface.position(); answer.set(QStringLiteral("pos"), pos/1000); bool playing = (mprisInterface.playbackStatus() == QLatin1String("Playing")); answer.set(QStringLiteral("isPlaying"), playing); answer.set(QStringLiteral("canPause"), mprisInterface.canPause()); answer.set(QStringLiteral("canPlay"), mprisInterface.canPlay()); answer.set(QStringLiteral("canGoNext"), mprisInterface.canGoNext()); answer.set(QStringLiteral("canGoPrevious"), mprisInterface.canGoPrevious()); answer.set(QStringLiteral("canSeek"), mprisInterface.canSeek()); somethingToSend = true; } if (np.get(QStringLiteral("requestVolume"))) { int volume = (int)(mprisInterface.volume() * 100); answer.set(QStringLiteral("volume"),volume); somethingToSend = true; } if (somethingToSend) { answer.set(QStringLiteral("player"), player); sendPacket(answer); } return true; } void MprisControlPlugin::sendPlayerList() { NetworkPacket np(PACKET_TYPE_MPRIS); np.set(QStringLiteral("playerList"),playerList.keys()); np.set(QStringLiteral("supportAlbumArtPayload"), true); sendPacket(np); } void MprisControlPlugin::mprisPlayerMetadataToNetworkPacket(NetworkPacket& np, const QVariantMap& nowPlayingMap) const { QString title = nowPlayingMap[QStringLiteral("xesam:title")].toString(); QString artist = nowPlayingMap[QStringLiteral("xesam:artist")].toString(); QString album = nowPlayingMap[QStringLiteral("xesam:album")].toString(); QString albumArtUrl = nowPlayingMap[QStringLiteral("mpris:artUrl")].toString(); QString nowPlaying = title; if (!artist.isEmpty()) { nowPlaying = artist + QStringLiteral(" - ") + title; } np.set(QStringLiteral("title"), title); np.set(QStringLiteral("artist"), artist); np.set(QStringLiteral("album"), album); np.set(QStringLiteral("albumArtUrl"), albumArtUrl); np.set(QStringLiteral("nowPlaying"), nowPlaying); bool hasLength = false; long long length = nowPlayingMap[QStringLiteral("mpris:length")].toLongLong(&hasLength) / 1000; //nanoseconds to milliseconds if (!hasLength) { length = -1; } np.set(QStringLiteral("length"), length); } #include "mpriscontrolplugin.moc" diff --git a/plugins/notifications/notificationsdbusinterface.cpp b/plugins/notifications/notificationsdbusinterface.cpp index fa6450f5..4bcc2f4b 100644 --- a/plugins/notifications/notificationsdbusinterface.cpp +++ b/plugins/notifications/notificationsdbusinterface.cpp @@ -1,192 +1,192 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationsdbusinterface.h" #include "notification_debug.h" #include "notification.h" #include #include #include #include "notificationsplugin.h" #include "sendreplydialog.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , m_device(plugin->device()) , m_plugin(plugin) , m_lastId(0) { } NotificationsDbusInterface::~NotificationsDbusInterface() { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface"; } void NotificationsDbusInterface::clearNotifications() { qDeleteAll(m_notifications); m_notifications.clear(); Q_EMIT allNotificationsRemoved(); } QStringList NotificationsDbusInterface::activeNotifications() { return m_notifications.keys(); } void NotificationsDbusInterface::notificationReady() { Notification* noti = static_cast(sender()); disconnect(noti, &Notification::ready, this, &NotificationsDbusInterface::notificationReady); addNotification(noti); } void NotificationsDbusInterface::processPacket(const NetworkPacket& np) { if (np.get(QStringLiteral("isCancel"))) { QString id = np.get(QStringLiteral("id")); // cut off kdeconnect-android's prefix if there: if (id.startsWith(QLatin1String("org.kde.kdeconnect_tp::"))) id = id.mid(id.indexOf(QLatin1String("::")) + 2); removeNotification(id); return; } QString id = np.get(QStringLiteral("id")); Notification* noti = nullptr; if (!m_internalIdToPublicId.contains(id)) { noti = new Notification(np, m_plugin->device(), this); if (noti->isReady()) { addNotification(noti); } else { connect(noti, &Notification::ready, this, &NotificationsDbusInterface::notificationReady); } } else { QString pubId = m_internalIdToPublicId.value(id); noti = m_notifications.value(pubId); noti->update(np); } } void NotificationsDbusInterface::addNotification(Notification* noti) { const QString& internalId = noti->internalId(); if (m_internalIdToPublicId.contains(internalId)) { removeNotification(internalId); } //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "addNotification" << internalId; connect(noti, &Notification::dismissRequested, this, &NotificationsDbusInterface::dismissRequested); connect(noti, &Notification::replyRequested, this, [this,noti]{ replyRequested(noti); }); connect(noti, &Notification::actionTriggered, this, &NotificationsDbusInterface::sendAction); const QString& publicId = newId(); m_notifications[publicId] = noti; m_internalIdToPublicId[internalId] = publicId; - DbusHelper::sessionBus().registerObject(m_device->dbusPath() + QStringLiteral("/notifications/") + publicId, noti, QDBusConnection::ExportScriptableContents); + DBusHelper::sessionBus().registerObject(m_device->dbusPath() + QStringLiteral("/notifications/") + publicId, noti, QDBusConnection::ExportScriptableContents); Q_EMIT notificationPosted(publicId); } void NotificationsDbusInterface::removeNotification(const QString& internalId) { //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId; if (!m_internalIdToPublicId.contains(internalId)) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by internal Id: " << internalId; return; } QString publicId = m_internalIdToPublicId.take(internalId); Notification* noti = m_notifications.take(publicId); if (!noti) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by public Id: " << publicId; return; } //Deleting the notification will unregister it automatically - //DbusHelper::sessionBus().unregisterObject(mDevice->dbusPath()+"/notifications/"+publicId); + //DBusHelper::sessionBus().unregisterObject(mDevice->dbusPath()+"/notifications/"+publicId); noti->deleteLater(); Q_EMIT notificationRemoved(publicId); } void NotificationsDbusInterface::dismissRequested(const QString& internalId) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REQUEST); np.set(QStringLiteral("cancel"), internalId); m_plugin->sendPacket(np); //Workaround: we erase notifications without waiting a response from the //phone because we won't receive a response if we are out of sync and this //notification no longer exists. Ideally, each time we reach the phone //after some time disconnected we should re-sync all the notifications. removeNotification(internalId); } void NotificationsDbusInterface::replyRequested(Notification* noti) { QString replyId = noti->replyId(); QString appName = noti->appName(); QString originalMessage = noti->ticker(); SendReplyDialog* dialog = new SendReplyDialog(originalMessage, replyId, appName); connect(dialog, &SendReplyDialog::sendReply, this, &NotificationsDbusInterface::sendReply); dialog->show(); dialog->raise(); } void NotificationsDbusInterface::sendReply(const QString& replyId, const QString& message) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REPLY); np.set(QStringLiteral("requestReplyId"), replyId); np.set(QStringLiteral("message"), message); m_plugin->sendPacket(np); } void NotificationsDbusInterface::sendAction(const QString& key, const QString& action) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_ACTION); np.set(QStringLiteral("key"), key); np.set(QStringLiteral("action"), action); m_plugin->sendPacket(np); } QString NotificationsDbusInterface::newId() { return QString::number(++m_lastId); } diff --git a/plugins/pausemusic/pausemusicplugin.cpp b/plugins/pausemusic/pausemusicplugin.cpp index bd3fcd06..cf2ce0d1 100644 --- a/plugins/pausemusic/pausemusicplugin.cpp +++ b/plugins/pausemusic/pausemusicplugin.cpp @@ -1,128 +1,128 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "pausemusicplugin.h" #include #include #include #include #include #include #include #include //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" K_PLUGIN_CLASS_WITH_JSON(PauseMusicPlugin, "kdeconnect_pausemusic.json") Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PAUSEMUSIC, "kdeconnect.plugin.pausemusic") PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , mutedSinks() {} bool PauseMusicPlugin::receivePacket(const NetworkPacket& np) { bool pauseOnlyWhenTalking = config()->get(QStringLiteral("conditionTalking"), false); if (pauseOnlyWhenTalking) { if (np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } else { //Pause as soon as it rings if (np.get(QStringLiteral("event")) != QLatin1String("ringing") && np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } bool pauseConditionFulfilled = !np.get(QStringLiteral("isCancel")); bool pause = config()->get(QStringLiteral("actionPause"), true); bool mute = config()->get(QStringLiteral("actionMute"), false); if (pauseConditionFulfilled) { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Muting system volume"; const auto sinks = PulseAudioQt::Context::instance()->sinks(); for (const auto sink : sinks) { if (!sink->isMuted()) { sink->setMuted(true); mutedSinks.insert(sink->name()); } } } if (pause) { //Search for interfaces currently playing - const QStringList interfaces = DbusHelper::sessionBus().interface()->registeredServiceNames().value(); + const QStringList interfaces = DBusHelper::sessionBus().interface()->registeredServiceNames().value(); for (const QString& iface : interfaces) { if (iface.startsWith(QLatin1String("org.mpris.MediaPlayer2"))) { QDBusInterface mprisInterface(iface, QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2.Player")); QString status = mprisInterface.property("PlaybackStatus").toString(); if (status == QLatin1String("Playing")) { if (!pausedSources.contains(iface)) { pausedSources.insert(iface); if (mprisInterface.property("CanPause").toBool()) { mprisInterface.asyncCall(QStringLiteral("Pause")); } else { mprisInterface.asyncCall(QStringLiteral("Stop")); } } } } } } } else { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Unmuting system volume"; const auto sinks = PulseAudioQt::Context::instance()->sinks(); for (const auto sink : sinks) { if (mutedSinks.contains(sink->name())) { sink->setMuted(false); } } mutedSinks.clear(); } if (pause && !pausedSources.empty()) { for (const QString& iface : qAsConst(pausedSources)) { QDBusInterface mprisInterface(iface, QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2.Player")); mprisInterface.asyncCall(QStringLiteral("Play")); } pausedSources.clear(); } } return true; } #include "pausemusicplugin.moc" diff --git a/plugins/runcommand/runcommand_config.cpp b/plugins/runcommand/runcommand_config.cpp index 6e7df95e..7a7ed772 100644 --- a/plugins/runcommand/runcommand_config.cpp +++ b/plugins/runcommand/runcommand_config.cpp @@ -1,177 +1,177 @@ /** * Copyright 2015 David Edmundson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "runcommand_config.h" #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(ShareConfigFactory, registerPlugin();) RunCommandConfig::RunCommandConfig(QWidget* parent, const QVariantList& args) : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_runcommand_config")) { QMenu* defaultMenu = new QMenu(this); addSuggestedCommand(defaultMenu, i18n("Suspend"), QStringLiteral("systemctl suspend")); addSuggestedCommand(defaultMenu, i18n("Maximum Brightness"), QStringLiteral("qdbus org.kde.Solid.PowerManagement /org/kde/Solid/PowerManagement/Actions/BrightnessControl org.kde.Solid.PowerManagement.Actions.BrightnessControl.setBrightness `qdbus org.kde.Solid.PowerManagement /org/kde/Solid/PowerManagement/Actions/BrightnessControl org.kde.Solid.PowerManagement.Actions.BrightnessControl.brightnessMax`")); addSuggestedCommand(defaultMenu, i18n("Lock Screen"), QStringLiteral("loginctl lock-session")); addSuggestedCommand(defaultMenu, i18n("Unlock Screen"), QStringLiteral("loginctl unlock-session")); addSuggestedCommand(defaultMenu, i18n("Close All Vaults"), QStringLiteral("qdbus org.kde.kded5 /modules/plasmavault closeAllVaults")); addSuggestedCommand(defaultMenu, i18n("Forcefully Close All Vaults"), QStringLiteral("qdbus org.kde.kded5 /modules/plasmavault forceCloseAllVaults")); QTableView* table = new QTableView(this); table->horizontalHeader()->setStretchLastSection(true); table->verticalHeader()->setVisible(false); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(table); QPushButton* button = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Sample commands"), this); button->setMenu(defaultMenu); layout->addWidget(button); setLayout(layout); m_entriesModel = new QStandardItemModel(this); table->setModel(m_entriesModel); m_entriesModel->setHorizontalHeaderLabels(QStringList() << i18n("Name") << i18n("Command")); } RunCommandConfig::~RunCommandConfig() { } void RunCommandConfig::addSuggestedCommand(QMenu* menu, const QString &name, const QString &command) { auto action = new QAction(name); connect(action, &QAction::triggered, action, [this, name, command]() { insertRow(0, name, command); Q_EMIT changed(true); }); menu->addAction(action); } void RunCommandConfig::defaults() { KCModule::defaults(); m_entriesModel->removeRows(0,m_entriesModel->rowCount()); Q_EMIT changed(true); } void RunCommandConfig::load() { KCModule::load(); QJsonDocument jsonDocument = QJsonDocument::fromJson(config()->get(QStringLiteral("commands"), "{}")); QJsonObject jsonConfig = jsonDocument.object(); const QStringList keys = jsonConfig.keys(); for (const QString& key : keys) { const QJsonObject entry = jsonConfig[key].toObject(); const QString name = entry[QStringLiteral("name")].toString(); const QString command = entry[QStringLiteral("command")].toString(); QStandardItem* newName = new QStandardItem(name); newName->setEditable(true); newName->setData(key); QStandardItem* newCommand = new QStandardItem(command); newName->setEditable(true); m_entriesModel->appendRow(QList() << newName << newCommand); } m_entriesModel->sort(0); insertEmptyRow(); connect(m_entriesModel, &QAbstractItemModel::dataChanged, this, &RunCommandConfig::onDataChanged); Q_EMIT changed(false); } void RunCommandConfig::save() { QJsonObject jsonConfig; for (int i=0;i < m_entriesModel->rowCount(); i++) { QString key = m_entriesModel->item(i, 0)->data().toString(); const QString name = m_entriesModel->item(i, 0)->text(); const QString command = m_entriesModel->item(i, 1)->text(); if (name.isEmpty() || command.isEmpty()) { continue; } if (key.isEmpty()) { key = QUuid::createUuid().toString(); - DbusHelper::filterNonExportableCharacters(key); + DBusHelper::filterNonExportableCharacters(key); } QJsonObject entry; entry[QStringLiteral("name")] = name; entry[QStringLiteral("command")] = command; jsonConfig[key] = entry; } QJsonDocument document; document.setObject(jsonConfig); config()->set(QStringLiteral("commands"), document.toJson(QJsonDocument::Compact)); KCModule::save(); Q_EMIT changed(false); } void RunCommandConfig::insertEmptyRow() { insertRow(m_entriesModel->rowCount(), {}, {}); } void RunCommandConfig::insertRow(int i, const QString& name, const QString& command) { QStandardItem* newName = new QStandardItem(name); newName->setEditable(true); QStandardItem* newCommand = new QStandardItem(command); newName->setEditable(true); m_entriesModel->insertRow(i, QList() << newName << newCommand); } void RunCommandConfig::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_EMIT changed(true); Q_UNUSED(topLeft); if (bottomRight.row() == m_entriesModel->rowCount() - 1) { //TODO check both entries are still empty insertEmptyRow(); } } #include "runcommand_config.moc" diff --git a/plugins/sendnotifications/notificationslistener.cpp b/plugins/sendnotifications/notificationslistener.cpp index 76221535..bfbea38e 100644 --- a/plugins/sendnotifications/notificationslistener.cpp +++ b/plugins/sendnotifications/notificationslistener.cpp @@ -1,280 +1,280 @@ /** * Copyright 2015 Holger Kaelberer * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationslistener.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sendnotificationsplugin.h" #include "sendnotification_debug.h" #include "notifyingapplication.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin) : QDBusAbstractAdaptor(aPlugin), m_plugin(aPlugin) { qRegisterMetaTypeStreamOperators("NotifyingApplication"); - bool ret = DbusHelper::sessionBus() + bool ret = DBusHelper::sessionBus() .registerObject(QStringLiteral("/org/freedesktop/Notifications"), this, QDBusConnection::ExportScriptableContents); if (!ret) qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Error registering notifications listener for device" << m_plugin->device()->name() << ":" - << DbusHelper::sessionBus().lastError(); + << DBusHelper::sessionBus().lastError(); else qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Registered notifications listener for device" << m_plugin->device()->name(); QDBusInterface iface(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus")); iface.call(QStringLiteral("AddMatch"), QStringLiteral("interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'")); setTranslatedAppName(); loadApplications(); connect(m_plugin->config(), &KdeConnectPluginConfig::configChanged, this, &NotificationsListener::loadApplications); } NotificationsListener::~NotificationsListener() { qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Destroying NotificationsListener"; QDBusInterface iface(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus")); QDBusMessage res = iface.call(QStringLiteral("RemoveMatch"), QStringLiteral("interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'")); - DbusHelper::sessionBus().unregisterObject(QStringLiteral("/org/freedesktop/Notifications")); + DBusHelper::sessionBus().unregisterObject(QStringLiteral("/org/freedesktop/Notifications")); } void NotificationsListener::setTranslatedAppName() { QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/kdeconnect.notifyrc"), QStandardPaths::LocateFile); if (filePath.isEmpty()) { qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Couldn't find kdeconnect.notifyrc to hide kdeconnect notifications on the devices. Using default name."; m_translatedAppName = QStringLiteral("KDE Connect"); return; } KConfig config(filePath, KConfig::OpenFlag::SimpleConfig); KConfigGroup globalgroup(&config, QStringLiteral("Global")); m_translatedAppName = globalgroup.readEntry(QStringLiteral("Name"), QStringLiteral("KDE Connect")); } void NotificationsListener::loadApplications() { m_applications.clear(); const QVariantList list = m_plugin->config()->getList(QStringLiteral("applications")); for (const auto& a : list) { NotifyingApplication app = a.value(); if (!m_applications.contains(app.name)) { m_applications.insert(app.name, app); } } //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Loaded" << applications.size() << " applications"; } bool NotificationsListener::parseImageDataArgument(const QVariant& argument, int& width, int& height, int& rowStride, int& bitsPerSample, int& channels, bool& hasAlpha, QByteArray& imageData) const { if (!argument.canConvert()) return false; const QDBusArgument dbusArg = argument.value(); dbusArg.beginStructure(); dbusArg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> imageData; dbusArg.endStructure(); return true; } QSharedPointer NotificationsListener::iconForImageData(const QVariant& argument) const { int width, height, rowStride, bitsPerSample, channels; bool hasAlpha; QByteArray imageData; if (!parseImageDataArgument(argument, width, height, rowStride, bitsPerSample, channels, hasAlpha, imageData)) return QSharedPointer(); if (bitsPerSample != 8) { qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Unsupported image format:" << "width=" << width << "height=" << height << "rowStride=" << rowStride << "bitsPerSample=" << bitsPerSample << "channels=" << channels << "hasAlpha=" << hasAlpha; return QSharedPointer(); } QImage image(reinterpret_cast(imageData.data()), width, height, rowStride, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32); if (hasAlpha) image = image.rgbSwapped(); // RGBA --> ARGB QSharedPointer buffer = QSharedPointer(new QBuffer); if (!buffer || !buffer->open(QIODevice::WriteOnly) || !image.save(buffer.data(), "PNG")) { qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Could not initialize image buffer"; return QSharedPointer(); } return buffer; } QSharedPointer NotificationsListener::iconForIconName(const QString& iconName) const { int size = KIconLoader::SizeEnormous; // use big size to allow for good // quality on high-DPI mobile devices QString iconPath = KIconLoader::global()->iconPath(iconName, -size, true); if (!iconPath.isEmpty()) { if (!iconPath.endsWith(QLatin1String(".png")) && KIconLoader::global()->theme()->name() != QLatin1String("hicolor")) { // try falling back to hicolor theme: KIconTheme hicolor(QStringLiteral("hicolor")); if (hicolor.isValid()) { iconPath = hicolor.iconPath(iconName + QStringLiteral(".png"), size, KIconLoader::MatchBest); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Found non-png icon in default theme trying fallback to hicolor:" << iconPath; } } } if (iconPath.endsWith(QLatin1String(".png"))) return QSharedPointer(new QFile(iconPath)); return QSharedPointer(); } uint NotificationsListener::Notify(const QString& appName, uint replacesId, const QString& appIcon, const QString& summary, const QString& body, const QStringList& actions, const QVariantMap& hints, int timeout) { static int id = 0; Q_UNUSED(actions); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout; // skip our own notifications if (appName == m_translatedAppName) return 0; NotifyingApplication app; if (!m_applications.contains(appName)) { // new application -> add to config app.name = appName; app.icon = appIcon; app.active = true; app.blacklistExpression = QRegularExpression(); m_applications.insert(app.name, app); // update config: QVariantList list; for (const auto& a : qAsConst(m_applications)) list << QVariant::fromValue(a); m_plugin->config()->setList(QStringLiteral("applications"), list); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Added new application to config:" << app; } else { app = m_applications.value(appName); } if (!app.active) return 0; if (timeout > 0 && m_plugin->config()->get(QStringLiteral("generalPersistent"), false)) return 0; int urgency = -1; if (hints.contains(QStringLiteral("urgency"))) { bool ok; urgency = hints[QStringLiteral("urgency")].toInt(&ok); if (!ok) urgency = -1; } if (urgency > -1 && urgency < m_plugin->config()->get(QStringLiteral("generalUrgency"), 0)) return 0; QString ticker = summary; if (!body.isEmpty() && m_plugin->config()->get(QStringLiteral("generalIncludeBody"), true)) ticker += QStringLiteral(": ") + body; if (app.blacklistExpression.isValid() && !app.blacklistExpression.pattern().isEmpty() && app.blacklistExpression.match(ticker).hasMatch()) return 0; //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Sending notification from" << appName << ":" < 0 ? replacesId : ++id)}, {QStringLiteral("appName"), appName}, {QStringLiteral("ticker"), ticker}, {QStringLiteral("isClearable"), timeout == 0} }); // KNotifications are persistent if // timeout == 0, for other notifications // clearability is pointless // sync any icon data? if (m_plugin->config()->get(QStringLiteral("generalSynchronizeIcons"), true)) { QSharedPointer iconSource; // try different image sources according to priorities in notifications- // spec version 1.2: if (hints.contains(QStringLiteral("image-data"))) iconSource = iconForImageData(hints[QStringLiteral("image-data")]); else if (hints.contains(QStringLiteral("image_data"))) // 1.1 backward compatibility iconSource = iconForImageData(hints[QStringLiteral("image_data")]); else if (hints.contains(QStringLiteral("image-path"))) iconSource = iconForIconName(hints[QStringLiteral("image-path")].toString()); else if (hints.contains(QStringLiteral("image_path"))) // 1.1 backward compatibility iconSource = iconForIconName(hints[QStringLiteral("image_path")].toString()); else if (!appIcon.isEmpty()) iconSource = iconForIconName(appIcon); else if (hints.contains(QStringLiteral("icon_data"))) // < 1.1 backward compatibility iconSource = iconForImageData(hints[QStringLiteral("icon_data")]); if (iconSource) np.setPayload(iconSource, iconSource->size()); } m_plugin->sendPacket(np); return (replacesId > 0 ? replacesId : id); } diff --git a/tests/testprivatedbus.cpp b/tests/testprivatedbus.cpp index 2834b80e..28658866 100644 --- a/tests/testprivatedbus.cpp +++ b/tests/testprivatedbus.cpp @@ -1,109 +1,109 @@ /** * Copyright 2019 Weixuan XIAO * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dbushelper.h" #include #include #include /** * This class tests the working of private dbus in kdeconnect */ class PrivateDBusTest : public QObject { Q_OBJECT public: PrivateDBusTest() { - DbusHelper::launchDBusDaemon(); + DBusHelper::launchDBusDaemon(); } ~PrivateDBusTest() { - DbusHelper::closeDBusDaemon(); + DBusHelper::closeDBusDaemon(); } private Q_SLOTS: void testConnectionWithPrivateDBus(); void testServiceRegistrationWithPrivateDBus(); void testMethodCallWithPrivateDBus(); }; /** * Open private DBus normally and get connection info */ void PrivateDBusTest::testConnectionWithPrivateDBus() { - QDBusConnection conn = DbusHelper::sessionBus(); + QDBusConnection conn = DBusHelper::sessionBus(); QVERIFY2(conn.isConnected(), "Connection not established"); QVERIFY2(conn.name() == QStringLiteral(KDECONNECT_PRIVATE_DBUS_NAME), "DBus Connection is not the right one"); } /** * Open private DBus connection normally and register a service */ void PrivateDBusTest::testServiceRegistrationWithPrivateDBus() { - QDBusConnection conn = DbusHelper::sessionBus(); + QDBusConnection conn = DBusHelper::sessionBus(); QVERIFY2(conn.isConnected(), "DBus not connected"); QDBusConnectionInterface *bus = conn.interface(); QVERIFY2(bus != nullptr, "Failed to get DBus interface"); QVERIFY2(bus->registerService(QStringLiteral("privatedbus.test")) == QDBusConnectionInterface::ServiceRegistered, "Failed to register DBus Serice"); bus->unregisterService(QStringLiteral("privatedbus.test")); } /** * Open private DBus connection normally, call a method and get its reply */ void PrivateDBusTest::testMethodCallWithPrivateDBus() { - QDBusConnection conn = DbusHelper::sessionBus(); + QDBusConnection conn = DBusHelper::sessionBus(); QVERIFY2(conn.isConnected(), "DBus not connected"); /* dbus-send --session \ --dest=org.freedesktop.DBus \ --type=method_call \ --print-reply \ /org/freedesktop/DBus \ org.freedesktop.DBus.ListNames */ QDBusMessage msg = conn.call( QDBusMessage::createMethodCall( QStringLiteral("org.freedesktop.DBus"), // Service QStringLiteral("/org/freedesktop/DBus"), // Path QStringLiteral("org.freedesktop.DBus"), // Interface QStringLiteral("ListNames") // Method ) ); QVERIFY2(msg.type() == QDBusMessage::ReplyMessage, "Failed calling method on private DBus"); } QTEST_MAIN(PrivateDBusTest); #include "testprivatedbus.moc" diff --git a/urlhandler/kdeconnect-handler.cpp b/urlhandler/kdeconnect-handler.cpp index 21908265..facd6f5b 100644 --- a/urlhandler/kdeconnect-handler.cpp +++ b/urlhandler/kdeconnect-handler.cpp @@ -1,122 +1,122 @@ /* * Copyright 2017 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdeconnect-version.h" #include "ui_dialog.h" /** * Show only devices that can be shared to */ int main(int argc, char** argv) { QApplication app(argc, argv); const QString description = i18n("KDE Connect URL handler"); KAboutData about(QStringLiteral("kdeconnect-urlhandler"), description, QStringLiteral(KDECONNECT_VERSION_STRING), description, KAboutLicense::GPL, i18n("(C) 2017 Aleix Pol Gonzalez")); about.addAuthor( QStringLiteral("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org") ); KAboutData::setApplicationData(about); QUrl urlToShare; bool open; { QCommandLineParser parser; parser.addPositionalArgument(QStringLiteral("url"), i18n("URL to share")); parser.addOption(QCommandLineOption(QStringLiteral("open"), QStringLiteral("Open the file on the remote device"))); parser.addHelpOption(); about.setupCommandLine(&parser); parser.process(app); about.processCommandLine(&parser); if (parser.positionalArguments().count() != 1) { parser.showHelp(1); return 1; } urlToShare = QUrl::fromUserInput(parser.positionalArguments().constFirst(), QDir::currentPath(), QUrl::AssumeLocalFile); open = parser.isSet(QStringLiteral("open")); } DevicesModel model; model.setDisplayFilter(DevicesModel::Paired | DevicesModel::Reachable); DevicesPluginFilterProxyModel proxyModel; proxyModel.setPluginFilter(QStringLiteral("kdeconnect_share")); proxyModel.setSourceModel(&model); QDialog dialog; Ui::Dialog uidialog; uidialog.setupUi(&dialog); uidialog.devicePicker->setModel(&proxyModel); QString displayUrl; if (urlToShare.scheme() == QLatin1String("tel")) { displayUrl = urlToShare.toDisplayString(QUrl::RemoveScheme); uidialog.label->setText(i18n("Device to call %1 with:", displayUrl)); } else if (urlToShare.isLocalFile() && open) { displayUrl = urlToShare.toDisplayString(QUrl::PreferLocalFile); uidialog.label->setText(i18n("Device to open %1 on:", displayUrl)); } else { displayUrl = urlToShare.toDisplayString(QUrl::PreferLocalFile); uidialog.label->setText(i18n("Device to send %1 to:", displayUrl)); } dialog.setWindowTitle(displayUrl); if (dialog.exec() == QDialog::Accepted) { QUrl url = urlToShare; const int currentDeviceIndex = uidialog.devicePicker->currentIndex(); if(!url.isEmpty() && currentDeviceIndex >= 0) { const QString device = proxyModel.index(currentDeviceIndex, 0).data(DevicesModel::IdModelRole).toString(); const QString action = open && url.isLocalFile() ? QStringLiteral("openFile") : QStringLiteral("shareUrl"); QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect/devices/") + device + QStringLiteral("/share"), QStringLiteral("org.kde.kdeconnect.device.share"), action); msg.setArguments({ url.toString() }); - blockOnReply(DbusHelper::sessionBus().asyncCall(msg)); + blockOnReply(DBusHelper::sessionBus().asyncCall(msg)); return 0; } else { QTextStream(stderr) << (i18n("Couldn't share %1", url.toString())) << endl; return 1; } } else { return 1; } }