diff --git a/cli/kdeconnect-cli.cpp b/cli/kdeconnect-cli.cpp index bd1ebd94..0552d525 100644 --- a/cli/kdeconnect-cli.cpp +++ b/cli/kdeconnect-cli.cpp @@ -1,250 +1,250 @@ /* * 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 #include "interfaces/devicesmodel.h" #include "interfaces/notificationsmodel.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/dbushelpers.h" #include "kdeconnect-version.h" 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("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("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"))); parser.addOption(QCommandLineOption(QStringLiteral("my-id"), i18n("Display this device's id and exit"))); about.setupCommandLine(&parser); parser.addHelpOption(); parser.process(app); about.processCommandLine(&parser); const QString id = "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 paired = true, reachable = false; if (parser.isSet(QStringLiteral("a"))) { reachable = true; } else { blockOnReply(iface.acquireDiscoveryMode(id)); QThread::sleep(2); } const QStringList devices = blockOnReply(iface.devices(paired, reachable)); - Q_FOREACH (const QString& id, devices) { + for (const QString& id : devices) { if (parser.isSet(QStringLiteral("id-only"))) { QTextStream(stdout) << id << endl; } 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 (!parser.isSet(QStringLiteral("id-only"))) { QTextStream(stdout) << 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("refresh"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect"), QStringLiteral("org.kde.kdeconnect.daemon"), QStringLiteral("forceOnNetworkChange")); blockOnReply(QDBusConnection::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"))) { QUrl url = QUrl::fromUserInput(parser.value(QStringLiteral("share")), QDir::currentPath()); parser.clearPositionalArguments(); if(!url.isEmpty() && !device.isEmpty()) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/share", QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); } else { QTextStream(stderr) << (i18n("Couldn't share %1", url.toString())) << 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, [&](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.isReachable()) { QTextStream(stderr) << i18n("Device does not exist") << endl; } else 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"), "/modules/kdeconnect/devices/"+device+"/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(QDBusConnection::sessionBus().asyncCall(msg)); } else if(parser.isSet(QStringLiteral("send-sms"))) { if (parser.isSet(QStringLiteral("destination"))) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/telephony", QStringLiteral("org.kde.kdeconnect.device.telephony"), QStringLiteral("sendSms")); msg.setArguments({ parser.value("destination"), parser.value("send-sms") }); blockOnReply(QDBusConnection::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"), "/modules/kdeconnect/devices/"+device+"/findmyphone", QStringLiteral("org.kde.kdeconnect.device.findmyphone"), QStringLiteral("ring")); blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); } else if(parser.isSet("send-keys")) { QString seq = parser.value("send-keys"); QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/"+device+"/remotekeyboard", "org.kde.kdeconnect.device.remotekeyboard", "sendKeyPress"); if (seq.trimmed() == QLatin1String("-")) { // from file 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(line), -1, false, false, false}); blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); } in.close(); } } else { msg.setArguments({seq, -1, false, false, false}); blockOnReply(QDBusConnection::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/backends/bluetooth/bluetoothlinkprovider.cpp b/core/backends/bluetooth/bluetoothlinkprovider.cpp index 6a8855f6..7cc9eaf0 100644 --- a/core/backends/bluetooth/bluetoothlinkprovider.cpp +++ b/core/backends/bluetooth/bluetoothlinkprovider.cpp @@ -1,305 +1,305 @@ /** * Copyright 2016 Saikrishna Arcot * * 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 "bluetoothlinkprovider.h" #include "core_debug.h" #include #include #include #include #include #include #include #include "bluetoothdevicelink.h" BluetoothLinkProvider::BluetoothLinkProvider() { mServiceUuid = QBluetoothUuid(QString("185f3df4-3268-4e3f-9fca-d4d5059915bd")); connectTimer = new QTimer(this); connectTimer->setInterval(30000); connectTimer->setSingleShot(false); mServiceDiscoveryAgent = new QBluetoothServiceDiscoveryAgent(this); mServiceDiscoveryAgent->setUuidFilter(mServiceUuid); connect(mServiceDiscoveryAgent, SIGNAL(finished()), this, SLOT(serviceDiscoveryFinished())); connect(connectTimer, SIGNAL(timeout()), mServiceDiscoveryAgent, SLOT(start())); } void BluetoothLinkProvider::onStart() { QBluetoothLocalDevice localDevice; if (!localDevice.isValid()) { qCWarning(KDECONNECT_CORE) << "No local bluetooth adapter found"; return; } mBluetoothServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this); mBluetoothServer->setSecurityFlags(QBluetooth::Encryption | QBluetooth::Secure); connect(mBluetoothServer, SIGNAL(newConnection()), this, SLOT(serverNewConnection())); mServiceDiscoveryAgent->start(); connectTimer->start(); mKdeconnectService = mBluetoothServer->listen(mServiceUuid, "KDE Connect"); } void BluetoothLinkProvider::onStop() { if (!mBluetoothServer) { return; } connectTimer->stop(); mKdeconnectService.unregisterService(); mBluetoothServer->close(); mBluetoothServer->deleteLater(); } //I'm in a new network, let's be polite and introduce myself void BluetoothLinkProvider::onNetworkChange() { } void BluetoothLinkProvider::connectError() { QBluetoothSocket* socket = qobject_cast(sender()); if (!socket) return; qCWarning(KDECONNECT_CORE) << "Couldn't connect to socket:" << socket->errorString(); disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); mSockets.remove(socket->peerAddress()); socket->deleteLater(); } void BluetoothLinkProvider::addLink(BluetoothDeviceLink* deviceLink, const QString& deviceId) { QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(deviceId); if (oldLinkIterator != mLinks.end()) { DeviceLink* oldLink = oldLinkIterator.value(); disconnect(oldLink, SIGNAL(destroyed(QObject*)), this, SLOT(deviceLinkDestroyed(QObject*))); oldLink->deleteLater(); mLinks.erase(oldLinkIterator); } mLinks[deviceId] = deviceLink; } //I'm the new device and I found an existing device void BluetoothLinkProvider::serviceDiscoveryFinished() { - QList services = mServiceDiscoveryAgent->discoveredServices(); + const QList services = mServiceDiscoveryAgent->discoveredServices(); - Q_FOREACH (QBluetoothServiceInfo info, services) { + for (QBluetoothServiceInfo info : services) { if (mSockets.contains(info.device().address())) { continue; } QBluetoothSocket* socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); connect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); socket->connectToService(info); qCDebug(KDECONNECT_CORE()) << "Connecting to" << info.device().address(); if (socket->error() != QBluetoothSocket::NoSocketError) { qCWarning(KDECONNECT_CORE) << "Socket connection error:" << socket->errorString(); } } } //I'm the new device and I'm connected to the existing device. Time to get data. void BluetoothLinkProvider::clientConnected() { QBluetoothSocket* socket = qobject_cast(sender()); if (!socket) return; qCDebug(KDECONNECT_CORE()) << "Connected to" << socket->peerAddress(); if (mSockets.contains(socket->peerAddress())) { qCWarning(KDECONNECT_CORE()) << "Duplicate connection to" << socket->peerAddress(); socket->close(); socket->deleteLater(); return; } mSockets.insert(socket->peerAddress(), socket); disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); connect(socket, SIGNAL(readyRead()), this, SLOT(clientIdentityReceived())); connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); } //I'm the new device and the existing device sent me data. void BluetoothLinkProvider::clientIdentityReceived() { QBluetoothSocket* socket = qobject_cast(sender()); if (!socket) return; QByteArray identityArray; while (socket->bytesAvailable() > 0 || !identityArray.contains('\n')) { identityArray += socket->readAll(); } disconnect(socket, SIGNAL(readyRead()), this, SLOT(clientIdentityReceived())); NetworkPackage receivedPackage(""); bool success = NetworkPackage::unserialize(identityArray, &receivedPackage); if (!success || receivedPackage.type() != PACKAGE_TYPE_IDENTITY) { qCWarning(KDECONNECT_CORE) << "Not an identity package"; mSockets.remove(socket->peerAddress()); socket->close(); socket->deleteLater(); return; } qCDebug(KDECONNECT_CORE()) << "Received identity package from" << socket->peerAddress(); disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); const QString& deviceId = receivedPackage.get("deviceId"); BluetoothDeviceLink* deviceLink = new BluetoothDeviceLink(deviceId, this, socket); NetworkPackage np2(""); NetworkPackage::createIdentityPackage(&np2); success = deviceLink->sendPackage(np2); if (success) { qCDebug(KDECONNECT_CORE) << "Handshaking done (I'm the new device)"; connect(deviceLink, SIGNAL(destroyed(QObject*)), this, SLOT(deviceLinkDestroyed(QObject*))); Q_EMIT onConnectionReceived(receivedPackage, deviceLink); //We kill any possible link from this same device addLink(deviceLink, deviceId); } else { // Connection might be lost. Delete it. delete deviceLink; } //We don't delete the socket because now it's owned by the BluetoothDeviceLink } //I'm the existing device, a new device is kindly introducing itself. void BluetoothLinkProvider::serverNewConnection() { QBluetoothSocket* socket = mBluetoothServer->nextPendingConnection(); qCDebug(KDECONNECT_CORE()) << "Received connection from" << socket->peerAddress(); if (mSockets.contains(socket->peerAddress())) { qCDebug(KDECONNECT_CORE()) << "Duplicate connection from" << socket->peerAddress(); socket->close(); socket->deleteLater(); return; } connect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); NetworkPackage np2(""); NetworkPackage::createIdentityPackage(&np2); socket->write(np2.serialize()); qCDebug(KDECONNECT_CORE()) << "Sent identity package to" << socket->peerAddress(); mSockets.insert(socket->peerAddress(), socket); } //I'm the existing device and this is the answer to my identity package (data received) void BluetoothLinkProvider::serverDataReceived() { QBluetoothSocket* socket = qobject_cast(sender()); if (!socket) return; QByteArray identityArray; while (socket->bytesAvailable() > 0 || !identityArray.contains('\n')) { identityArray += socket->readAll(); } disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); NetworkPackage receivedPackage(""); bool success = NetworkPackage::unserialize(identityArray, &receivedPackage); if (!success || receivedPackage.type() != PACKAGE_TYPE_IDENTITY) { qCWarning(KDECONNECT_CORE) << "Not an identity package."; mSockets.remove(socket->peerAddress()); socket->close(); socket->deleteLater(); return; } qCDebug(KDECONNECT_CORE()) << "Received identity package from" << socket->peerAddress(); const QString& deviceId = receivedPackage.get("deviceId"); BluetoothDeviceLink* deviceLink = new BluetoothDeviceLink(deviceId, this, socket); connect(deviceLink, SIGNAL(destroyed(QObject*)), this, SLOT(deviceLinkDestroyed(QObject*))); Q_EMIT onConnectionReceived(receivedPackage, deviceLink); addLink(deviceLink, deviceId); } void BluetoothLinkProvider::deviceLinkDestroyed(QObject* destroyedDeviceLink) { const QString id = destroyedDeviceLink->property("deviceId").toString(); qCDebug(KDECONNECT_CORE()) << "Device disconnected:" << id; QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(id); if (oldLinkIterator != mLinks.end() && oldLinkIterator.value() == destroyedDeviceLink) { mLinks.erase(oldLinkIterator); } } void BluetoothLinkProvider::socketDisconnected() { QBluetoothSocket* socket = qobject_cast(sender()); if (!socket) return; disconnect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); mSockets.remove(mSockets.key(socket)); } BluetoothLinkProvider::~BluetoothLinkProvider() { } diff --git a/core/daemon.cpp b/core/daemon.cpp index 6fd4cb57..01aba12b 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,287 +1,287 @@ /** * 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 "networkpackage.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" static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them QSet mLinkProviders; //Every known device QMap mDevices; QSet mDiscoveryModeAcquisitions; }; 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; qCDebug(KDECONNECT_CORE) << "KdeConnect daemon starting"; //Load backends if (testMode) d->mLinkProviders.insert(new LoopbackLinkProvider()); else { d->mLinkProviders.insert(new LanLinkProvider()); #ifdef KDECONNECT_BLUETOOTH d->mLinkProviders.insert(new BluetoothLinkProvider()); #endif } //Read remebered paired devices const QStringList& list = KdeConnectConfig::instance()->trustedDevices(); - Q_FOREACH (const QString& id, list) { + for (const QString& id : list) { addDevice(new Device(this, id)); } //Listen to new devices - Q_FOREACH (LinkProvider* a, d->mLinkProviders) { + for (LinkProvider* a : qAsConst(d->mLinkProviders)) { connect(a, &LinkProvider::onConnectionReceived, this, &Daemon::onNewDeviceLink); a->onStart(); } //Register on DBus QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); qCDebug(KDECONNECT_CORE) << "KdeConnect daemon started"; } void Daemon::acquireDiscoveryMode(const QString &key) { bool oldState = d->mDiscoveryModeAcquisitions.isEmpty(); d->mDiscoveryModeAcquisitions.insert(key); if (oldState != d->mDiscoveryModeAcquisitions.isEmpty()) { forceOnNetworkChange(); } } void Daemon::releaseDiscoveryMode(const QString &key) { bool oldState = d->mDiscoveryModeAcquisitions.isEmpty(); d->mDiscoveryModeAcquisitions.remove(key); if (oldState != d->mDiscoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } void Daemon::removeDevice(Device* device) { d->mDevices.remove(device->id()); device->deleteLater(); Q_EMIT deviceRemoved(device->id()); } void Daemon::cleanDevices() { - Q_FOREACH (Device* device, d->mDevices) { + for (Device* device : qAsConst(d->mDevices)) { 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->mLinkProviders.size() << " LinkProviders"; - Q_FOREACH (LinkProvider* a, d->mLinkProviders) { + for (LinkProvider* a : qAsConst(d->mLinkProviders)) { a->onNetworkChange(); } } Device*Daemon::getDevice(const QString& deviceId) { - Q_FOREACH (Device* device, d->mDevices) { + for (Device* device : qAsConst(d->mDevices)) { if (device->id() == deviceId) { return device; } } return Q_NULLPTR; } QStringList Daemon::devices(bool onlyReachable, bool onlyTrusted) const { QStringList ret; - Q_FOREACH (Device* device, d->mDevices) { + for (Device* device : qAsConst(d->mDevices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret.append(device->id()); } return ret; } void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl) { const QString& id = identityPackage.get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Device discovered" << id << "via" << dl->provider()->name(); if (d->mDevices.contains(id)) { qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPackage.get(QStringLiteral("deviceName")); Device* device = d->mDevices[id]; bool wasReachable = device->isReachable(); device->addLink(identityPackage, dl); if (!wasReachable) { Q_EMIT deviceVisibilityChanged(id, true); } } else { qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPackage.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPackage, 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()); } } 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->mDevices.values(); } bool Daemon::isDiscoveringDevices() const { return !d->mDiscoveryModeAcquisitions.isEmpty(); } QString Daemon::deviceIdByName(const QString &name) const { - Q_FOREACH (Device* d, d->mDevices) { - if (d->name() == name && d->isTrusted()) - return d->id(); + for (Device* device : qAsConst(d->mDevices)) { + 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->mDevices[id] = device; Q_EMIT deviceAdded(id); } QStringList Daemon::pairingRequests() const { QStringList ret; for(Device* dev: d->mDevices) { if (dev->hasPairingRequests()) ret += dev->id(); } return ret; } Daemon::~Daemon() { } QString Daemon::selfId() const { return KdeConnectConfig::instance()->deviceId(); } diff --git a/core/device.cpp b/core/device.cpp index 413d8958..d0f8565f 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -1,477 +1,477 @@ /** * 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" #ifdef interface // MSVC language extension, QDBusConnection uses this as a variable name #undef interface #endif #include #include #include #include #include #include "core_debug.h" #include "kdeconnectplugin.h" #include "pluginloader.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" #include "networkpackage.h" #include "kdeconnectconfig.h" #include "daemon.h" static void warn(const QString &info) { qWarning() << "Device pairing error" << info; } Device::Device(QObject* parent, const QString& id) : QObject(parent) , m_deviceId(id) , m_protocolVersion(NetworkPackage::ProtocolVersion) //We don't know it yet { KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance()->getTrustedDevice(id); m_deviceName = info.deviceName; m_deviceType = str2type(info.deviceType); //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); //Assume every plugin is supported until addLink is called and we can get the actual list m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); connect(this, &Device::pairingError, this, &warn); } Device::Device(QObject* parent, const NetworkPackage& identityPackage, DeviceLink* dl) : QObject(parent) , m_deviceId(identityPackage.get(QStringLiteral("deviceId"))) , m_deviceName(identityPackage.get(QStringLiteral("deviceName"))) { addLink(identityPackage, dl); //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); connect(this, &Device::pairingError, this, &warn); } Device::~Device() { qDeleteAll(m_deviceLinks); m_deviceLinks.clear(); } bool Device::hasPlugin(const QString& name) const { return m_plugins.contains(name); } QStringList Device::loadedPlugins() const { return m_plugins.keys(); } void Device::reloadPlugins() { QHash newPluginMap, oldPluginMap = 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(); - Q_FOREACH (const QString& pluginName, m_supportedPlugins) { + for (const QString& pluginName : qAsConst(m_supportedPlugins)) { const KPluginMetaData service = loader->getPluginInfo(pluginName); const bool pluginEnabled = isPluginEnabled(pluginName); const QSet incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPackageType")).toSet(); if (pluginEnabled) { KdeConnectPlugin* plugin = m_plugins.take(pluginName); if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } Q_ASSERT(plugin); - Q_FOREACH (const QString& interface, incomingCapabilities) { + 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(m_plugins); m_plugins = newPluginMap; m_pluginsByIncomingCapability = newPluginsByIncomingCapability; QDBusConnection bus = QDBusConnection::sessionBus(); - Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) { + for (KdeConnectPlugin* plugin : qAsConst(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; } - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { dl->userRequestsPair(); } } void Device::unpair() { - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { + for (DeviceLink* dl : qAsConst(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()); - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { + for (DeviceLink* dl : qAsConst(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 NetworkPackage& identityPackage, DeviceLink* link) { //qCDebug(KDECONNECT_CORE) << "Adding link to" << id() << "via" << link->provider(); Q_ASSERT(!m_deviceLinks.contains(link)); m_protocolVersion = identityPackage.get(QStringLiteral("protocolVersion"), -1); if (m_protocolVersion != NetworkPackage::ProtocolVersion) { qCWarning(KDECONNECT_CORE) << m_deviceName << "- warning, device uses a different protocol version" << m_protocolVersion << "expected" << NetworkPackage::ProtocolVersion; } connect(link, &QObject::destroyed, this, &Device::linkDestroyed); m_deviceLinks.append(link); //re-read the device name from the identityPackage because it could have changed setName(identityPackage.get(QStringLiteral("deviceName"))); m_deviceType = str2type(identityPackage.get(QStringLiteral("deviceType"))); //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::receivedPackage, this, &Device::privateReceivedPackage); qSort(m_deviceLinks.begin(), m_deviceLinks.end(), lessThan); const bool capabilitiesSupported = identityPackage.has(QStringLiteral("incomingCapabilities")) || identityPackage.has(QStringLiteral("outgoingCapabilities")); if (capabilitiesSupported) { const QSet outgoingCapabilities = identityPackage.get(QStringLiteral("outgoingCapabilities")).toSet() , incomingCapabilities = identityPackage.get(QStringLiteral("incomingCapabilities")).toSet(); m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities); //qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities; } else { m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); } reloadPlugins(); if (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 = m_pairRequests.isEmpty(); m_pairRequests.insert(handler); if (wasEmpty != m_pairRequests.isEmpty()) Q_EMIT hasPairingRequestsChanged(!m_pairRequests.isEmpty()); } void Device::removePairingRequest(PairingHandler* handler) { const bool wasEmpty = m_pairRequests.isEmpty(); m_pairRequests.remove(handler); if (wasEmpty != m_pairRequests.isEmpty()) Q_EMIT hasPairingRequestsChanged(!m_pairRequests.isEmpty()); } bool Device::hasPairingRequests() const { return !m_pairRequests.isEmpty(); } void Device::acceptPairing() { if (m_pairRequests.isEmpty()) qWarning() << "no pair requests to accept!"; //copying because the pairing handler will be removed upon accept const auto prCopy = m_pairRequests; for (auto ph: prCopy) ph->acceptPairing(); } void Device::rejectPairing() { if (m_pairRequests.isEmpty()) qWarning() << "no pair requests to reject!"; //copying because the pairing handler will be removed upon reject const auto prCopy = m_pairRequests; for (auto ph: prCopy) ph->rejectPairing(); } void Device::linkDestroyed(QObject* o) { removeLink(static_cast(o)); } void Device::removeLink(DeviceLink* link) { m_deviceLinks.removeAll(link); //qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining"; if (m_deviceLinks.isEmpty()) { reloadPlugins(); Q_EMIT reachableChanged(false); } } bool Device::sendPackage(NetworkPackage& np) { Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR); Q_ASSERT(isTrusted()); //Maybe we could block here any package that is not an identity or a pairing package to prevent sending non encrypted data - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { if (dl->sendPackage(np)) return true; } return false; } void Device::privateReceivedPackage(const NetworkPackage& np) { Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR); if (isTrusted()) { const QList plugins = m_pluginsByIncomingCapability.values(np.type()); if (plugins.isEmpty()) { qWarning() << "discarding unsupported package" << np.type() << "for" << name(); } - Q_FOREACH (KdeConnectPlugin* plugin, plugins) { + for (KdeConnectPlugin* plugin : plugins) { plugin->receivePackage(np); } } else { qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring package" << np.type(); unpair(); } } bool Device::isTrusted() const { return KdeConnectConfig::instance()->trustedDevices().contains(id()); } QStringList Device::availableLinks() const { QStringList sl; sl.reserve(m_deviceLinks.size()); - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { sl.append(dl->provider()->name()); } return sl; } void Device::cleanUnneededLinks() { if (isTrusted()) { return; } for(int i = 0; i < m_deviceLinks.size(); ) { DeviceLink* dl = m_deviceLinks[i]; if (!dl->linkShouldBeKeptAlive()) { dl->deleteLater(); m_deviceLinks.remove(i); } else { i++; } } } 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; 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"); 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 = 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 (m_deviceName != name) { m_deviceName = name; Q_EMIT nameChanged(name); } } KdeConnectPlugin* Device::plugin(const QString& pluginName) const { return m_plugins[pluginName]; } void Device::setPluginEnabled(const QString& pluginName, bool enabled) { 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, ':'); // Improve readability } result += i18n("SHA1 fingerprint of remote device certificate is: %1\n", remoteSha1); return result; } diff --git a/core/pluginloader.cpp b/core/pluginloader.cpp index 05739c64..681e0c8a 100644 --- a/core/pluginloader.cpp +++ b/core/pluginloader.cpp @@ -1,131 +1,131 @@ /** * 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 "pluginloader.h" #include #include #include #include "core_debug.h" #include "device.h" #include "kdeconnectplugin.h" PluginLoader* PluginLoader::instance() { static PluginLoader* instance = new PluginLoader(); return instance; } PluginLoader::PluginLoader() { - QVector data = KPluginLoader::findPlugins(QStringLiteral("kdeconnect/")); - Q_FOREACH (const KPluginMetaData& metadata, data) { + const QVector data = KPluginLoader::findPlugins(QStringLiteral("kdeconnect/")); + for (const KPluginMetaData& metadata : data) { plugins[metadata.pluginId()] = metadata; } } QStringList PluginLoader::getPluginList() const { return plugins.keys(); } KPluginMetaData PluginLoader::getPluginInfo(const QString& name) const { return plugins.value(name); } KdeConnectPlugin* PluginLoader::instantiatePluginForDevice(const QString& pluginName, Device* device) const { KdeConnectPlugin* ret = Q_NULLPTR; KPluginMetaData service = plugins.value(pluginName); if (!service.isValid()) { qCDebug(KDECONNECT_CORE) << "Plugin unknown" << pluginName; return ret; } KPluginLoader loader(service.fileName()); KPluginFactory *factory = loader.factory(); if (!factory) { qCDebug(KDECONNECT_CORE) << "KPluginFactory could not load the plugin:" << service.pluginId() << loader.errorString(); return ret; } const QStringList outgoingInterfaces = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPackageType")); QVariant deviceVariant = QVariant::fromValue(device); ret = factory->create(device, QVariantList() << deviceVariant << pluginName << outgoingInterfaces); if (!ret) { qCDebug(KDECONNECT_CORE) << "Error loading plugin"; return ret; } //qCDebug(KDECONNECT_CORE) << "Loaded plugin:" << service.pluginId(); return ret; } QStringList PluginLoader::incomingCapabilities() const { QSet ret; - Q_FOREACH (const KPluginMetaData& service, plugins) { + for (const KPluginMetaData& service : qAsConst(plugins)) { ret += KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPackageType")).toSet(); } return ret.toList(); } QStringList PluginLoader::outgoingCapabilities() const { QSet ret; - Q_FOREACH (const KPluginMetaData& service, plugins) { + for (const KPluginMetaData& service : qAsConst(plugins)) { ret += KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPackageType")).toSet(); } return ret.toList(); } QSet PluginLoader::pluginsForCapabilities(const QSet& incoming, const QSet& outgoing) { QSet ret; - Q_FOREACH (const KPluginMetaData& service, plugins) { + for (const KPluginMetaData& service : qAsConst(plugins)) { const QSet pluginIncomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPackageType")).toSet(); const QSet pluginOutgoingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPackageType")).toSet(); bool capabilitiesEmpty = (pluginIncomingCapabilities.isEmpty() && pluginOutgoingCapabilities.isEmpty()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) bool capabilitiesIntersect = (outgoing.intersects(pluginIncomingCapabilities) || incoming.intersects(pluginOutgoingCapabilities)); #else QSet commonIncoming = incoming; commonIncoming.intersect(pluginOutgoingCapabilities); QSet commonOutgoing = outgoing; commonOutgoing.intersect(pluginIncomingCapabilities); bool capabilitiesIntersect = (!commonIncoming.isEmpty() || !commonOutgoing.isEmpty()); #endif if (capabilitiesIntersect || capabilitiesEmpty) { ret += service.pluginId(); } else { qCDebug(KDECONNECT_CORE) << "Not loading plugin" << service.pluginId() << "because device doesn't support it"; } } return ret; } diff --git a/fileitemactionplugin/sendfileitemaction.cpp b/fileitemactionplugin/sendfileitemaction.cpp index 1a35771c..3a999361 100644 --- a/fileitemactionplugin/sendfileitemaction.cpp +++ b/fileitemactionplugin/sendfileitemaction.cpp @@ -1,99 +1,99 @@ /* * 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 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(); - Q_FOREACH (const QString& id, devices) { + 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("preferences-system-network")), 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() { - QList urls = sender()->property("urls").value>(); + const QList urls = sender()->property("urls").value>(); QString id = sender()->property("id").toString(); - Q_FOREACH (const QUrl& url, urls) { + for (const QUrl& url : urls) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+id+"/share", QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); QDBusConnection::sessionBus().call(msg); } } #include "sendfileitemaction.moc" diff --git a/interfaces/devicesmodel.cpp b/interfaces/devicesmodel.cpp index a72d8da7..cb0da81a 100644 --- a/interfaces/devicesmodel.cpp +++ b/interfaces/devicesmodel.cpp @@ -1,314 +1,314 @@ /** * 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 "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(), QDBusConnection::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); - Q_FOREACH(const QString& id, deviceIds) { + 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 b22a4c4e..80df2792 100644 --- a/interfaces/notificationsmodel.cpp +++ b/interfaces/notificationsmodel.cpp @@ -1,257 +1,257 @@ /** * 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" 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(), QDBusConnection::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"); 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) { int currentSize = m_notificationList.size(); beginInsertRows(QModelIndex(), currentSize, currentSize); NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, id, this); m_notificationList.append(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); - Q_FOREACH (const QString& notificationId, notificationIds) { + 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 qVariantFromValue(notification); case DismissableModelRole: return notification->dismissable(); case RepliableModelRole: return !notification->replyId().isEmpty(); case IconPathModelRole: return notification->iconPath(); 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 { - Q_FOREACH (NotificationDbusInterface* notification, m_notificationList) { + for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { return true; } } return false; } void NotificationsModel::dismissAll() { - Q_FOREACH (NotificationDbusInterface* notification, m_notificationList) { + 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(); } } diff --git a/kio/kiokdeconnect.cpp b/kio/kiokdeconnect.cpp index 0bbb13fa..05cda9fe 100644 --- a/kio/kiokdeconnect.cpp +++ b/kio/kiokdeconnect.cpp @@ -1,234 +1,234 @@ /** * 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 "kiokdeconnect.h" #include #include #include #include Q_LOGGING_CATEGORY(KDECONNECT_KIO, "kdeconnect.kio") extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) { if (argc != 4) { fprintf(stderr, "Usage: kio_kdeconnect protocol pool app\n"); exit(-1); } KioKdeconnect slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } //Some useful error mapping KIO::Error toKioError(const QDBusError::ErrorType type) { switch (type) { case QDBusError::NoError: return KIO::Error(KJob::NoError); case QDBusError::NoMemory: return KIO::ERR_OUT_OF_MEMORY; case QDBusError::Timeout: return KIO::ERR_SERVER_TIMEOUT; case QDBusError::TimedOut: return KIO::ERR_SERVER_TIMEOUT; default: return KIO::ERR_INTERNAL; }; }; template bool handleDBusError(QDBusReply& reply, KIO::SlaveBase* slave) { if (!reply.isValid()) { qCDebug(KDECONNECT_KIO) << "Error in DBus request:" << reply.error(); slave->error(toKioError(reply.error().type()),reply.error().message()); return true; } return false; } KioKdeconnect::KioKdeconnect(const QByteArray &pool, const QByteArray &app) : SlaveBase("kdeconnect", pool, app), m_dbusInterface(new DaemonDbusInterface(this)) { } void KioKdeconnect::listAllDevices() { infoMessage(i18n("Listing devices...")); //TODO: Change to all devices and show different icons for connected and disconnected? - QStringList devices = m_dbusInterface->devices(true, true); + const QStringList devices = m_dbusInterface->devices(true, true); - Q_FOREACH(const QString& deviceId, devices) { + for (const QString& deviceId : devices) { DeviceDbusInterface interface(deviceId); if (!interface.hasPlugin(QStringLiteral("kdeconnect_sftp"))) continue; const QString path = QStringLiteral("kdeconnect://").append(deviceId).append("/"); const QString name = interface.name(); const QString icon = QStringLiteral("kdeconnect"); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, name); entry.insert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); entry.insert(KIO::UDSEntry::UDS_URL, path); listEntry(entry); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_SIZE, 0); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); listEntry(entry); infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDevice() { infoMessage(i18n("Accessing device...")); qCDebug(KDECONNECT_KIO) << "ListDevice" << m_currentDevice; SftpDbusInterface interface(m_currentDevice); QDBusReply mountreply = interface.mountAndWait(); if (handleDBusError(mountreply, this)) { return; } if (!mountreply.value()) { error(KIO::ERR_COULD_NOT_MOUNT, i18n("Could not mount device filesystem")); return; } QDBusReply< QVariantMap > urlreply = interface.getDirectories(); if (handleDBusError(urlreply, this)) { return; } QVariantMap urls = urlreply.value(); for (QVariantMap::iterator it = urls.begin(); it != urls.end(); ++it) { const QString path = it.key(); const QString name = it.value().toString(); const QString icon = QStringLiteral("folder"); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("files")); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, name); entry.insert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); entry.insert(KIO::UDSEntry::UDS_URL, QUrl::fromLocalFile(path).toString()); listEntry(entry); } // We also need a non-null and writable UDSentry for "." KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_SIZE, 0); entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); listEntry(entry); infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDir(const QUrl &url) { qCDebug(KDECONNECT_KIO) << "Listing..." << url; /// Url is not used here because all we could care about the url is the host, and that's already /// handled in @p setHost Q_UNUSED(url); if (!m_dbusInterface->isValid()) { infoMessage(i18n("Could not contact background service.")); finished(); return; } if (m_currentDevice.isEmpty()) { listAllDevices(); } else { listDevice(); } } void KioKdeconnect::stat(const QUrl &url) { qCDebug(KDECONNECT_KIO) << "Stat: " << url; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); statEntry(entry); finished(); } void KioKdeconnect::get(const QUrl &url) { qCDebug(KDECONNECT_KIO) << "Get: " << url; mimeType(QLatin1String("")); finished(); } void KioKdeconnect::setHost(const QString &hostName, quint16 port, const QString &user, const QString &pass) { //This is called before everything else to set the file we want to show qCDebug(KDECONNECT_KIO) << "Setting host: " << hostName; // In this kio only the hostname is used Q_UNUSED(port) Q_UNUSED(user) Q_UNUSED(pass) m_currentDevice = hostName; } diff --git a/plasmoid/declarativeplugin/responsewaiter.cpp b/plasmoid/declarativeplugin/responsewaiter.cpp index ed974e9b..6288e782 100644 --- a/plasmoid/declarativeplugin/responsewaiter.cpp +++ b/plasmoid/declarativeplugin/responsewaiter.cpp @@ -1,132 +1,132 @@ #include "responsewaiter.h" #include #include #include #include Q_DECLARE_METATYPE(QDBusPendingReply<>) Q_DECLARE_METATYPE(QDBusPendingReply) Q_DECLARE_METATYPE(QDBusPendingReply) Q_DECLARE_METATYPE(QDBusPendingReply) Q_DECLARE_METATYPE(QDBusPendingReply) DBusResponseWaiter* DBusResponseWaiter::m_instance = nullptr; DBusResponseWaiter* DBusResponseWaiter::instance() { if (!m_instance) { m_instance = new DBusResponseWaiter(); } return m_instance; } DBusResponseWaiter::DBusResponseWaiter() : QObject() { m_registered << qRegisterMetaType >("QDBusPendingReply<>") << qRegisterMetaType >("QDBusPendingReply") << qRegisterMetaType >("QDBusPendingReply") << qRegisterMetaType >("QDBusPendingReply") << qRegisterMetaType >("QDBusPendingReply") ; } QVariant DBusResponseWaiter::waitForReply(QVariant variant) const { if (QDBusPendingCall* call = const_cast(extractPendingCall(variant))) { call->waitForFinished(); if (call->isError()) { qWarning() << "error:" << call->error(); return QVariant("error"); } QDBusMessage reply = call->reply(); if (reply.arguments().count() > 0) { return reply.arguments().at(0); } } return QVariant(); } DBusAsyncResponse::DBusAsyncResponse(QObject* parent) : QObject(parent) , m_autodelete(false) { m_timeout.setSingleShot(true); m_timeout.setInterval(15000); connect(&m_timeout, &QTimer::timeout, this, &DBusAsyncResponse::onTimeout); } void DBusAsyncResponse::setPendingCall(QVariant variant) { if (QDBusPendingCall* call = const_cast(DBusResponseWaiter::instance()->extractPendingCall(variant))) { QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(*call); watcher->setProperty("pengingCallVariant", variant); connect(watcher, &QDBusPendingCallWatcher::finished, this, &DBusAsyncResponse::onCallFinished); connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); connect(&m_timeout, &QTimer::timeout, watcher, &QObject::deleteLater); m_timeout.start(); } } void DBusAsyncResponse::onCallFinished(QDBusPendingCallWatcher* watcher) { m_timeout.stop(); QVariant variant = watcher->property("pengingCallVariant"); if (QDBusPendingCall* call = const_cast(DBusResponseWaiter::instance()->extractPendingCall(variant))) { if (call->isError()) { Q_EMIT error(call->error().message()); } else { QDBusMessage reply = call->reply(); if (reply.arguments().count() > 0) { Q_EMIT success(reply.arguments().at(0)); } else { Q_EMIT success(QVariant()); } } } if (m_autodelete) { deleteLater(); } } void DBusAsyncResponse::onTimeout() { Q_EMIT error(QStringLiteral("timeout when waiting dbus response!")); } const QDBusPendingCall* DBusResponseWaiter::extractPendingCall(QVariant& variant) const { - Q_FOREACH(int type, m_registered) + for (int type : qAsConst(m_registered)) { if (variant.canConvert(QVariant::Type(type))) { return reinterpret_cast(variant.constData()); } } return nullptr; } diff --git a/plugins/mpriscontrol/mpriscontrolplugin.cpp b/plugins/mpriscontrol/mpriscontrolplugin.cpp index 6266b4a0..3a85a287 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin.cpp +++ b/plugins/mpriscontrol/mpriscontrolplugin.cpp @@ -1,285 +1,285 @@ /** * 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_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mpriscontrol.json", registerPlugin< MprisControlPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRIS, "kdeconnect.plugin.mpris") MprisControlPlugin::MprisControlPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , prevVolume(-1) { m_watcher = new QDBusServiceWatcher(QString(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); // TODO: QDBusConnectionInterface::serviceOwnerChanged is deprecated, maybe query org.freedesktop.DBus directly? connect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &MprisControlPlugin::serviceOwnerChanged); //Add existing interfaces - QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); - Q_FOREACH (const QString& service, services) { + const QStringList services = QDBusConnection::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) { QDBusInterface mprisInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), 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")); } playerList[identity] = service; qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris addPlayer" << service << "->" << identity; sendPlayerList(); OrgFreedesktopDBusPropertiesInterface* freedesktopInterface = new OrgFreedesktopDBusPropertiesInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this); connect(freedesktopInterface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &MprisControlPlugin::propertiesChanged); OrgMprisMediaPlayer2PlayerInterface* mprisInterface0 = new OrgMprisMediaPlayer2PlayerInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus()); connect(mprisInterface0, &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &MprisControlPlugin::seeked); } void MprisControlPlugin::seeked(qlonglong position){ //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeked in player"; OrgFreedesktopDBusPropertiesInterface* interface = (OrgFreedesktopDBusPropertiesInterface*)sender(); const QString& service = interface->service(); const QString& player = playerList.key(service); NetworkPackage np(PACKAGE_TYPE_MPRIS, { {"pos", position/1000}, //Send milis instead of nanos {"player", player} }); sendPackage(np); } void MprisControlPlugin::propertiesChanged(const QString& propertyInterface, const QVariantMap& properties) { Q_UNUSED(propertyInterface); NetworkPackage np(PACKAGE_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; if (nowPlayingMap.contains(QStringLiteral("xesam:title"))) { QString nowPlaying = nowPlayingMap[QStringLiteral("xesam:title")].toString(); if (nowPlayingMap.contains(QStringLiteral("xesam:artist"))) { nowPlaying = nowPlayingMap[QStringLiteral("xesam:artist")].toString() + " - " + nowPlaying; } np.set(QStringLiteral("nowPlaying"),nowPlaying); somethingToSend = true; } if (nowPlayingMap.contains(QStringLiteral("mpris:length"))) { if (nowPlayingMap.contains(QStringLiteral("mpris:length"))) { long long length = nowPlayingMap[QStringLiteral("mpris:length")].toLongLong(); np.set(QStringLiteral("length"),length/1000); //milis to nanos } 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) { OrgFreedesktopDBusPropertiesInterface* interface = (OrgFreedesktopDBusPropertiesInterface*)sender(); const QString& service = interface->service(); const QString& player = playerList.key(service); np.set(QStringLiteral("player"), player); // Always also update the position OrgMprisMediaPlayer2PlayerInterface mprisInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus()); if (mprisInterface.canSeek()) { long long pos = mprisInterface.position(); np.set(QStringLiteral("pos"), pos/1000); //Send milis instead of nanos } sendPackage(np); } } void MprisControlPlugin::removePlayer(const QString& ifaceName) { const QString identity = playerList.key(ifaceName); qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris removePlayer" << ifaceName << "->" << identity; playerList.remove(identity); sendPlayerList(); } bool MprisControlPlugin::receivePackage (const NetworkPackage& np) { if (np.has(QStringLiteral("playerList"))) { return false; //Whoever sent this is an mpris client and not an mpris control! } //Send the player list const QString player = np.get(QStringLiteral("player")); bool valid_player = playerList.contains(player); if (!valid_player || np.get(QStringLiteral("requestPlayerList"))) { sendPlayerList(); if (!valid_player) { return true; } } //Do something to the mpris interface OrgMprisMediaPlayer2PlayerInterface mprisInterface(playerList[player], QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus()); mprisInterface.setTimeout(500); if (np.has(QStringLiteral("action"))) { const QString& action = np.get(QStringLiteral("action")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Calling action" << action << "in" << playerList[player]; //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" << playerList[player]; mprisInterface.setVolume(volume); } if (np.has(QStringLiteral("Seek"))) { int offset = np.get(QStringLiteral("Seek")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeking" << offset << "to" << playerList[player]; 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" << playerList[player]; mprisInterface.Seek(seek); } //Send something read from the mpris interface NetworkPackage answer(PACKAGE_TYPE_MPRIS); bool somethingToSend = false; if (np.get(QStringLiteral("requestNowPlaying"))) { QVariantMap nowPlayingMap = mprisInterface.metadata(); QString nowPlaying = nowPlayingMap[QStringLiteral("xesam:title")].toString(); if (nowPlayingMap.contains(QStringLiteral("xesam:artist"))) { nowPlaying = nowPlayingMap[QStringLiteral("xesam:artist")].toString() + " - " + nowPlaying; } answer.set(QStringLiteral("nowPlaying"),nowPlaying); if (nowPlayingMap.contains(QStringLiteral("mpris:length"))) { qlonglong length = nowPlayingMap[QStringLiteral("mpris:length")].toLongLong(); answer.set(QStringLiteral("length"),length/1000); } 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); sendPackage(answer); } return true; } void MprisControlPlugin::sendPlayerList() { NetworkPackage np(PACKAGE_TYPE_MPRIS); np.set(QStringLiteral("playerList"),playerList.keys()); sendPackage(np); } #include "mpriscontrolplugin.moc" diff --git a/plugins/notifications/notificationsdbusinterface.cpp b/plugins/notifications/notificationsdbusinterface.cpp index 3654551c..d6cb3cfc 100644 --- a/plugins/notifications/notificationsdbusinterface.cpp +++ b/plugins/notifications/notificationsdbusinterface.cpp @@ -1,176 +1,176 @@ /** * 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" NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , mDevice(plugin->device()) , mPlugin(plugin) , mLastId(0) { } NotificationsDbusInterface::~NotificationsDbusInterface() { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface"; } void NotificationsDbusInterface::clearNotifications() { qDeleteAll(mNotifications); mNotifications.clear(); Q_EMIT allNotificationsRemoved(); } QStringList NotificationsDbusInterface::activeNotifications() { return mNotifications.keys(); } void NotificationsDbusInterface::processPackage(const NetworkPackage& 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); } else if (np.get(QStringLiteral("isRequest"))) { - Q_FOREACH (const auto& n, mNotifications) { + for (const auto& n : qAsConst(mNotifications)) { NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REQUEST, { {"id", n->internalId()}, {"appName", n->appName()}, {"ticker", n->ticker()}, {"isClearable", n->dismissable()}, {"requestAnswer", true} }); mPlugin->sendPackage(np); } } else if(np.get(QStringLiteral("requestAnswer"), false)) { } else { QString id = np.get(QStringLiteral("id")); if (!mInternalIdToPublicId.contains(id)) { Notification* noti = new Notification(np, this); addNotification(noti); } else { QString pubId = mInternalIdToPublicId[id]; mNotifications[pubId]->update(np); } } } void NotificationsDbusInterface::addNotification(Notification* noti) { const QString& internalId = noti->internalId(); if (mInternalIdToPublicId.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); }); const QString& publicId = newId(); mNotifications[publicId] = noti; mInternalIdToPublicId[internalId] = publicId; QDBusConnection::sessionBus().registerObject(mDevice->dbusPath()+"/notifications/"+publicId, noti, QDBusConnection::ExportScriptableContents); Q_EMIT notificationPosted(publicId); } void NotificationsDbusInterface::removeNotification(const QString& internalId) { //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId; if (!mInternalIdToPublicId.contains(internalId)) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by internal Id: " << internalId; return; } QString publicId = mInternalIdToPublicId.take(internalId); Notification* noti = mNotifications.take(publicId); if (!noti) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by public Id: " << publicId; return; } //Deleting the notification will unregister it automatically //QDBusConnection::sessionBus().unregisterObject(mDevice->dbusPath()+"/notifications/"+publicId); noti->deleteLater(); Q_EMIT notificationRemoved(publicId); } void NotificationsDbusInterface::dismissRequested(const QString& internalId) { NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REQUEST); np.set(QStringLiteral("cancel"), internalId); mPlugin->sendPackage(np); //Workaround: we erase notifications without waiting a repsonse 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(); } void NotificationsDbusInterface::sendReply(const QString& replyId, const QString& message) { NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REPLY); np.set(QStringLiteral("requestReplyId"), replyId); np.set(QStringLiteral("message"), message); mPlugin->sendPackage(np); } QString NotificationsDbusInterface::newId() { return QString::number(++mLastId); } diff --git a/plugins/pausemusic/pausemusicplugin.cpp b/plugins/pausemusic/pausemusicplugin.cpp index 4f1b9064..5b5bcd09 100644 --- a/plugins/pausemusic/pausemusicplugin.cpp +++ b/plugins/pausemusic/pausemusicplugin.cpp @@ -1,136 +1,136 @@ /** * 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 K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_pausemusic.json", registerPlugin< PauseMusicPlugin >(); ) //TODO: Port this away from KMix to use only Pulseaudio int PauseMusicPlugin::isKMixMuted() { QDBusInterface kmixInterface(QStringLiteral("org.kde.kmix"), QStringLiteral("/Mixers"), QStringLiteral("org.kde.KMix.MixSet")); QString mixer = kmixInterface.property("currentMasterMixer").toString(); QString control = kmixInterface.property("currentMasterControl").toString(); if (mixer.isEmpty() || control.isEmpty()) return -1; mixer.replace(':','_'); mixer.replace('.','_'); mixer.replace('-','_'); control.replace(':','_'); control.replace('.','_'); control.replace('-','_'); QDBusInterface mixerInterface(QStringLiteral("org.kde.kmix"), "/Mixers/"+mixer+"/"+control, QStringLiteral("org.kde.KMix.Control")); if (mixerInterface.property("mute").toBool()) return 1; return (mixerInterface.property("volume").toInt() == 0); } PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , muted(false) { QDBusInterface kmixInterface(QStringLiteral("org.kde.kmix"), QStringLiteral("/kmix/KMixWindow/actions/mute"), QStringLiteral("org.qtproject.Qt.QAction")); } bool PauseMusicPlugin::receivePackage(const NetworkPackage& 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) { QDBusInterface kmixInterface(QStringLiteral("org.kde.kmix"), QStringLiteral("/kmix/KMixWindow/actions/mute"), QStringLiteral("org.qtproject.Qt.QAction")); if (isKMixMuted() == 0) { muted = true; kmixInterface.call(QStringLiteral("trigger")); } } if (pause) { //Search for interfaces currently playing - QStringList interfaces = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); - Q_FOREACH (const QString& iface, interfaces) { + const QStringList interfaces = QDBusConnection::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 && muted) { QDBusInterface kmixInterface(QStringLiteral("org.kde.kmix"), QStringLiteral("/kmix/KMixWindow/actions/mute"), QStringLiteral("org.qtproject.Qt.QAction")); if (isKMixMuted() > 0) { kmixInterface.call(QStringLiteral("trigger")); } muted = false; } if (pause && !pausedSources.empty()) { - Q_FOREACH (const QString& iface, pausedSources) { + for (const QString& iface : qAsConst(pausedSources)) { QDBusInterface mprisInterface(iface, QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2.Player")); mprisInterface.asyncCall(QStringLiteral("PlayPause")); } pausedSources.clear(); } } return true; } #include "pausemusicplugin.moc" diff --git a/plugins/runcommand/runcommand_config.cpp b/plugins/runcommand/runcommand_config.cpp index 6686d241..cfb3bb2c 100644 --- a/plugins/runcommand/runcommand_config.cpp +++ b/plugins/runcommand/runcommand_config.cpp @@ -1,145 +1,146 @@ /** * 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 K_PLUGIN_FACTORY(ShareConfigFactory, registerPlugin();) RunCommandConfig::RunCommandConfig(QWidget *parent, const QVariantList& args) : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_runcommand_config")) { QTableView *table = new QTableView(this); table->horizontalHeader()->setStretchLastSection(true); table->verticalHeader()->setVisible(false); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(table); setLayout(layout); m_entriesModel = new QStandardItemModel(this); table->setModel(m_entriesModel); m_entriesModel->setHorizontalHeaderLabels(QStringList() << i18n("Name") << i18n("Command")); } RunCommandConfig::~RunCommandConfig() { } void RunCommandConfig::defaults() { KCModule::defaults(); m_entriesModel->clear(); Q_EMIT changed(true); } void RunCommandConfig::load() { KCModule::load(); QJsonDocument jsonDocument = QJsonDocument::fromJson(config()->get(QStringLiteral("commands"), "{}")); QJsonObject jsonConfig = jsonDocument.object(); - Q_FOREACH (const QString &key, jsonConfig.keys()) { + 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(); } 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() { QStandardItem *newName = new QStandardItem; newName->setEditable(true); QStandardItem *newCommand = new QStandardItem; newName->setEditable(true); m_entriesModel->appendRow(QList() << newName << newCommand); } void RunCommandConfig::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { 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 d047ca2e..816d78e1 100644 --- a/plugins/sendnotifications/notificationslistener.cpp +++ b/plugins/sendnotifications/notificationslistener.cpp @@ -1,274 +1,274 @@ /** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "notificationslistener.h" #include "sendnotificationsplugin.h" #include "sendnotification_debug.h" #include "notifyingapplication.h" NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin) : QDBusAbstractAdaptor(aPlugin), mPlugin(aPlugin) { qRegisterMetaTypeStreamOperators("NotifyingApplication"); bool ret = QDBusConnection::sessionBus() .registerObject(QStringLiteral("/org/freedesktop/Notifications"), this, QDBusConnection::ExportScriptableContents); if (!ret) qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Error registering notifications listener for device" << mPlugin->device()->name() << ":" << QDBusConnection::sessionBus().lastError(); else qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Registered notifications listener for device" << mPlugin->device()->name(); QDBusInterface iface(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus")); iface.call(QStringLiteral("AddMatch"), "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); setTranslatedAppName(); loadApplications(); connect(mPlugin->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"), "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); QDBusConnection::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."; mTranslatedAppName = QStringLiteral("KDE Connect"); return; } KConfig config(filePath, KConfig::OpenFlag::SimpleConfig); KConfigGroup globalgroup(&config, QStringLiteral("Global")); mTranslatedAppName = globalgroup.readEntry(QStringLiteral("Name"), QStringLiteral("KDE Connect")); } void NotificationsListener::loadApplications() { applications.clear(); - QVariantList list = mPlugin->config()->getList(QStringLiteral("applications")); - Q_FOREACH (const auto& a, list) { + const QVariantList list = mPlugin->config()->getList(QStringLiteral("applications")); + for (const auto& a : list) { NotifyingApplication app = a.value(); if (!applications.contains(app.name)) 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 + ".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 == mTranslatedAppName) return 0; NotifyingApplication app; if (!applications.contains(appName)) { // new application -> add to config app.name = appName; app.icon = appIcon; app.active = true; app.blacklistExpression = QRegularExpression(); applications.insert(app.name, app); // update config: QVariantList list; - Q_FOREACH (const auto& a, applications) + for (const auto& a : qAsConst(applications)) list << QVariant::fromValue(a); mPlugin->config()->setList(QStringLiteral("applications"), list); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Added new application to config:" << app; } else app = applications.value(appName); if (!app.active) return 0; if (timeout > 0 && mPlugin->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 < mPlugin->config()->get(QStringLiteral("generalUrgency"), 0)) return 0; QString ticker = summary; if (!body.isEmpty() && mPlugin->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)}, {"appName", appName}, {"ticker", ticker}, {"isClearable", timeout == 0} }); // KNotifications are persistent if // timeout == 0, for other notifications // clearability is pointless // sync any icon data? if (mPlugin->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()); } mPlugin->sendPackage(np); return (replacesId > 0 ? replacesId : id); } diff --git a/tests/pluginloadtest.cpp b/tests/pluginloadtest.cpp index b649f4b9..7e30bcff 100644 --- a/tests/pluginloadtest.cpp +++ b/tests/pluginloadtest.cpp @@ -1,84 +1,85 @@ /** * 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 #include "core/daemon.h" #include "core/device.h" #include "core/kdeconnectplugin.h" #include #include "kdeconnect-version.h" #include "testdaemon.h" class PluginLoadTest : public QObject { Q_OBJECT public: PluginLoadTest() { QStandardPaths::setTestModeEnabled(true); mDaemon = new TestDaemon; } private Q_SLOTS: void testPlugins() { Device* d = nullptr; mDaemon->acquireDiscoveryMode(QStringLiteral("plugintest")); - Q_FOREACH(Device* id, mDaemon->devicesList()) { + const QList devicesList = mDaemon->devicesList(); + for (Device* id : devicesList) { if (id->isReachable()) { if (!id->isTrusted()) id->requestPair(); d = id; break; } } mDaemon->releaseDiscoveryMode(QStringLiteral("plugintest")); if (!d->loadedPlugins().contains(QStringLiteral("kdeconnect_remotecontrol"))) { QSKIP("kdeconnect_remotecontrol is required for this test"); } QVERIFY(d); QVERIFY(d->isTrusted()); QVERIFY(d->isReachable()); d->setPluginEnabled(QStringLiteral("kdeconnect_mousepad"), false); QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), false); QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol")); d->setPluginEnabled(QStringLiteral("kdeconnect_mousepad"), true); QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), true); QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol")); } private: TestDaemon* mDaemon; }; QTEST_MAIN(PluginLoadTest); #include "pluginloadtest.moc" diff --git a/tests/sendfiletest.cpp b/tests/sendfiletest.cpp index c2c29a42..4d5063e7 100644 --- a/tests/sendfiletest.cpp +++ b/tests/sendfiletest.cpp @@ -1,148 +1,149 @@ /** * 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 #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/kdeconnectplugin.h" #include #include "kdeconnect-version.h" #include "testdaemon.h" class TestSendFile : public QObject { Q_OBJECT public: TestSendFile() { QStandardPaths::setTestModeEnabled(true); mDaemon = new TestDaemon; } private Q_SLOTS: void testSend() { mDaemon->acquireDiscoveryMode(QStringLiteral("test")); Device* d = nullptr; - Q_FOREACH(Device* id, mDaemon->devicesList()) { + const QList devicesList = mDaemon->devicesList(); + for (Device* id : devicesList) { if (id->isReachable()) { if (!id->isTrusted()) id->requestPair(); d = id; } } mDaemon->releaseDiscoveryMode(QStringLiteral("test")); QVERIFY(d); QCOMPARE(d->isReachable(), true); QCOMPARE(d->isTrusted(), true); QByteArray content("12312312312313213123213123"); QTemporaryFile temp; temp.open(); temp.write(content); temp.close(); KdeConnectPlugin* plugin = d->plugin(QStringLiteral("kdeconnect_share")); QVERIFY(plugin); plugin->metaObject()->invokeMethod(plugin, "shareUrl", Q_ARG(QString, QUrl::fromLocalFile(temp.fileName()).toString())); QSignalSpy spy(plugin, SIGNAL(shareReceived(QUrl))); QVERIFY(spy.wait(2000)); QVariantList args = spy.takeFirst(); QUrl sentFile(args.first().toUrl()); QFile file(sentFile.toLocalFile()); QCOMPARE(file.size(), content.size()); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(file.readAll(), content); } void testSslJobs() { const QString aFile = QFINDTESTDATA("sendfiletest.cpp"); const QString destFile = QDir::tempPath() + "/kdeconnect-test-sentfile"; QFile(destFile).remove(); const QString deviceId = KdeConnectConfig::instance()->deviceId() , deviceName = QStringLiteral("testdevice") , deviceType = KdeConnectConfig::instance()->deviceType(); KdeConnectConfig* kcc = KdeConnectConfig::instance(); kcc->addTrustedDevice(deviceId, deviceName, deviceType); kcc->setDeviceProperty(deviceId, QStringLiteral("certificate"), QString::fromLatin1(kcc->certificate().toPem())); // Using same certificate from kcc, instead of generating QSharedPointer f(new QFile(aFile)); UploadJob* uj = new UploadJob(f, deviceId); QSignalSpy spyUpload(uj, &KJob::result); uj->start(); auto info = uj->transferInfo(); info.insert(QStringLiteral("deviceId"), deviceId); info.insert(QStringLiteral("size"), aFile.size()); DownloadJob* dj = new DownloadJob(QHostAddress::LocalHost, info); QVERIFY(dj->getPayload()->open(QIODevice::ReadOnly)); FileTransferJob* ft = new FileTransferJob(dj->getPayload(), uj->transferInfo()[QStringLiteral("size")].toInt(), QUrl::fromLocalFile(destFile)); QSignalSpy spyDownload(dj, &KJob::result); QSignalSpy spyTransfer(ft, &KJob::result); ft->start(); dj->start(); QVERIFY(spyTransfer.count() || spyTransfer.wait(1000000000)); if (ft->error()) qWarning() << "fterror" << ft->errorString(); QCOMPARE(ft->error(), 0); QCOMPARE(spyDownload.count(), 1); QCOMPARE(spyUpload.count(), 1); QFile resultFile(destFile), originFile(aFile); QVERIFY(resultFile.open(QIODevice::ReadOnly)); QVERIFY(originFile.open(QIODevice::ReadOnly)); const QByteArray resultContents = resultFile.readAll(), originContents = originFile.readAll(); QCOMPARE(resultContents.size(), originContents.size()); QCOMPARE(resultFile.readAll(), originFile.readAll()); } private: TestDaemon* mDaemon; }; QTEST_MAIN(TestSendFile); #include "sendfiletest.moc" diff --git a/tests/testnotificationlistener.cpp b/tests/testnotificationlistener.cpp index e82bc4dd..8fa6e547 100644 --- a/tests/testnotificationlistener.cpp +++ b/tests/testnotificationlistener.cpp @@ -1,501 +1,502 @@ /** * 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 #include #include #include #include #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/kdeconnectplugin.h" #include "kdeconnect-version.h" #include "plugins/sendnotifications/sendnotificationsplugin.h" #include "plugins/sendnotifications/notificationslistener.h" #include "plugins/sendnotifications/notifyingapplication.h" // Tweaked NotificationsPlugin for testing class TestNotificationsPlugin : public SendNotificationsPlugin { Q_OBJECT public: explicit TestNotificationsPlugin(QObject *parent, const QVariantList &args) : SendNotificationsPlugin(parent, args) { } ~TestNotificationsPlugin() override = default; // allow to access notificationsListener for testing: NotificationsListener* getNotificationsListener() const { return notificationsListener; } void setNotificationsListener(NotificationsListener* value) { notificationsListener = value; } }; // Tweaked Device for testing: class TestDevice: public Device { Q_OBJECT private: int sentPackages; NetworkPackage *lastPackage; public: explicit TestDevice(QObject* parent, const QString& id) : Device (parent, id) , sentPackages(0) , lastPackage(nullptr) {} ~TestDevice() override { delete lastPackage; } int getSentPackages() const { return sentPackages; } NetworkPackage* getLastPackage() { return lastPackage; } void deleteLastPackage() { delete lastPackage; lastPackage = nullptr; } public Q_SLOTS: bool sendPackage(NetworkPackage& np) override { ++sentPackages; // copy package manually to allow for inspection (can't use copy-constructor) deleteLastPackage(); lastPackage = new NetworkPackage(np.type()); Q_ASSERT(lastPackage); for (QVariantMap::ConstIterator iter = np.body().constBegin(); iter != np.body().constEnd(); iter++) lastPackage->set(iter.key(), iter.value()); lastPackage->setPayload(np.payload(), np.payloadSize()); return true; } }; // Tweaked NotificationsListener for testing: class TestedNotificationsListener: public NotificationsListener { Q_OBJECT public: explicit TestedNotificationsListener(KdeConnectPlugin* aPlugin) : NotificationsListener(aPlugin) {} ~TestedNotificationsListener() override {} QHash& getApplications() { return applications; } void setApplications(const QHash& value) { applications = value; } protected: bool parseImageDataArgument(const QVariant& argument, int& width, int& height, int& rowStride, int& bitsPerSample, int& channels, bool& hasAlpha, QByteArray& imageData) const override { width = argument.toMap().value(QStringLiteral("width")).toInt(); height = argument.toMap().value(QStringLiteral("height")).toInt(); rowStride = argument.toMap().value(QStringLiteral("rowStride")).toInt(); bitsPerSample = argument.toMap().value(QStringLiteral("bitsPerSample")).toInt(); channels = argument.toMap().value(QStringLiteral("channels")).toInt(); hasAlpha = argument.toMap().value(QStringLiteral("hasAlpha")).toBool(); imageData = argument.toMap().value(QStringLiteral("imageData")).toByteArray(); return true; } }; class TestNotificationListener : public QObject { Q_OBJECT public: TestNotificationListener() : plugin(nullptr) { QStandardPaths::setTestModeEnabled(true); } private Q_SLOTS: void testNotify(); private: TestNotificationsPlugin* plugin; }; void TestNotificationListener::testNotify() { // // set things up: // QString dId(QStringLiteral("testid")); TestDevice *d = new TestDevice(nullptr, dId); int proxiedNotifications = 0; QCOMPARE(proxiedNotifications, d->getSentPackages()); plugin = new TestNotificationsPlugin(this, QVariantList({ QVariant::fromValue(d), "notifications_plugin", {"kdeconnect.notification"}})); QVERIFY(plugin->getNotificationsListener()); delete plugin->getNotificationsListener(); // inject our tweaked NotificationsListener: TestedNotificationsListener* listener = new TestedNotificationsListener(plugin); QVERIFY(listener); plugin->setNotificationsListener(listener); QCOMPARE(listener, plugin->getNotificationsListener()); // make sure config is default: plugin->config()->set(QStringLiteral("generalPersistent"), false); plugin->config()->set(QStringLiteral("generalIncludeBody"), true); plugin->config()->set(QStringLiteral("generalUrgency"), 0); QCOMPARE(plugin->config()->get("generalPersistent"), false); QCOMPARE(plugin->config()->get("generalIncludeBody"), true); QCOMPARE(plugin->config()->get("generalUrgency"), false); // applications are modified directly: listener->getApplications().clear(); QCOMPARE(listener->getApplications().count(), 0); // // Go !!! // uint replacesId = 99; uint retId; QString appName(QStringLiteral("some-appName")); QString body(QStringLiteral("some-body")); QString icon(QStringLiteral("some-icon")); QString summary(QStringLiteral("some-summary")); // regular Notify call that is synchronized ... retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); // ... should return replacesId, QCOMPARE(retId, replacesId); // ... have triggered sending a package QCOMPARE(++proxiedNotifications, d->getSentPackages()); // ... with our properties, QCOMPARE(d->getLastPackage()->get("id"), replacesId); QCOMPARE(d->getLastPackage()->get("appName"), appName); QCOMPARE(d->getLastPackage()->get("ticker"), summary + ": " + body); QCOMPARE(d->getLastPackage()->get("isClearable"), true); QCOMPARE(d->getLastPackage()->hasPayload(), false); // ... and create a new application internally that is initialized correctly: QCOMPARE(listener->getApplications().count(), 1); QVERIFY(listener->getApplications().contains(appName)); QVERIFY(listener->getApplications()[appName].active); QCOMPARE(listener->getApplications()[appName].name, appName); QVERIFY(listener->getApplications()[appName].blacklistExpression.pattern().isEmpty()); QCOMPARE(listener->getApplications()[appName].name, appName); QCOMPARE(listener->getApplications()[appName].icon, icon); // another one, with other timeout and urgency values: QString appName2(QStringLiteral("some-appName2")); QString body2(QStringLiteral("some-body2")); QString icon2(QStringLiteral("some-icon2")); QString summary2(QStringLiteral("some-summary2")); retId = listener->Notify(appName2, replacesId+1, icon2, summary2, body2, {}, {{"urgency", 2}}, 10); QCOMPARE(retId, replacesId+1); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QCOMPARE(d->getLastPackage()->get("id"), replacesId+1); QCOMPARE(d->getLastPackage()->get("appName"), appName2); QCOMPARE(d->getLastPackage()->get("ticker"), summary2 + ": " + body2); QCOMPARE(d->getLastPackage()->get("isClearable"), false); // timeout != 0 QCOMPARE(d->getLastPackage()->hasPayload(), false); QCOMPARE(listener->getApplications().count(), 2); QVERIFY(listener->getApplications().contains(appName2)); QVERIFY(listener->getApplications().contains(appName)); // if persistent-only is set, timeouts > 0 are not synced: plugin->config()->set(QStringLiteral("generalPersistent"), true); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 1); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); retId = listener->Notify(appName2, replacesId, icon2, summary2, body2, {}, {{}}, 3); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); // but timeout == 0 is retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); plugin->config()->set(QStringLiteral("generalPersistent"), false); // if min-urgency is set, lower urgency levels are not synced: plugin->config()->set(QStringLiteral("generalUrgency"), 1); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 0}}, 0); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); // equal urgency is retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 1}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // higher urgency as well retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 2}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); plugin->config()->set(QStringLiteral("generalUrgency"), 0); // notifications for a deactivated application are not synced: QVERIFY(listener->getApplications().contains(appName)); listener->getApplications()[appName].active = false; QVERIFY(!listener->getApplications()[appName].active); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 0}}, 0); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); // others are still: retId = listener->Notify(appName2, replacesId+1, icon2, summary2, body2, {}, {{}}, 0); QCOMPARE(retId, replacesId+1); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // back to normal: listener->getApplications()[appName].active = true; QVERIFY(listener->getApplications()[appName].active); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // notifications with blacklisted subjects are not synced: QVERIFY(listener->getApplications().contains(appName)); listener->getApplications()[appName].blacklistExpression.setPattern(QStringLiteral("black[12]|foo(bar|baz)")); retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary black1"), body, {}, {{}}, 0); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary foobar"), body, {}, {{}}, 0); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); // other subjects are synced: retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary foo"), body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary black3"), body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // also body is checked by blacklist if requested: plugin->config()->set(QStringLiteral("generalIncludeBody"), true); retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body black1"), {}, {{}}, 0); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body foobaz"), {}, {{}}, 0); QCOMPARE(retId, 0U); QCOMPARE(proxiedNotifications, d->getSentPackages()); // body does not matter if inclusion was not requested: plugin->config()->set(QStringLiteral("generalIncludeBody"), false); retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body black1"), {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // without body, also ticker value is different: QCOMPARE(d->getLastPackage()->get("ticker"), summary); retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body foobaz"), {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // back to normal: listener->getApplications()[appName].blacklistExpression.setPattern(QLatin1String("")); plugin->config()->set(QStringLiteral("generalIncludeBody"), true); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); retId = listener->Notify(appName2, replacesId, icon2, summary2, body2, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); // icon synchronization: QStringList iconPaths; // appIcon int count = 0; - Q_FOREACH (const auto& iconName, KIconLoader::global()->queryIcons(-KIconLoader::SizeEnormous, KIconLoader::Application)) { + const QStringList icons = KIconLoader::global()->queryIcons(-KIconLoader::SizeEnormous, KIconLoader::Application); + for (const auto& iconName : icons) { if (!iconName.endsWith(QLatin1String(".png"))) continue; if (count++ > 3) // max 3 iterations break; iconPaths.append(iconName); // memorize some paths for later // existing icons are sync-ed if requested plugin->config()->set(QStringLiteral("generalSynchronizeIcons"), true); QFileInfo fi(iconName); retId = listener->Notify(appName, replacesId, fi.baseName(), summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()->hasPayload()); QCOMPARE(d->getLastPackage()->payloadSize(), fi.size()); // works also with abolute paths retId = listener->Notify(appName, replacesId, iconName, summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()->hasPayload()); QCOMPARE(d->getLastPackage()->payloadSize(), fi.size()); // extensions other than png are not accepted: retId = listener->Notify(appName, replacesId, iconName + ".svg", summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(!d->getLastPackage()->hasPayload()); // if sync not requested no payload: plugin->config()->set(QStringLiteral("generalSynchronizeIcons"), false); retId = listener->Notify(appName, replacesId, fi.baseName(), summary, body, {}, {{}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(!d->getLastPackage()->hasPayload()); QCOMPARE(d->getLastPackage()->payloadSize(), 0); } plugin->config()->set(QStringLiteral("generalSynchronizeIcons"), true); // image-path in hints if (iconPaths.size() > 0) { retId = listener->Notify(appName, replacesId, iconPaths.size() > 1 ? iconPaths[1] : icon, summary, body, {}, {{"image-path", iconPaths[0]}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()->hasPayload()); QFileInfo hintsFi(iconPaths[0]); // image-path has priority over appIcon parameter: QCOMPARE(d->getLastPackage()->payloadSize(), hintsFi.size()); } // image_path in hints if (iconPaths.size() > 0) { retId = listener->Notify(appName, replacesId, iconPaths.size() > 1 ? iconPaths[1] : icon, summary, body, {}, {{"image_path", iconPaths[0]}}, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()->hasPayload()); QFileInfo hintsFi(iconPaths[0]); // image_path has priority over appIcon parameter: QCOMPARE(d->getLastPackage()->payloadSize(), hintsFi.size()); } // image-data in hints // set up: QBuffer *buffer; QImage image; int width = 2, height = 2, rowStride = 4*width, bitsPerSample = 8, channels = 4; bool hasAlpha = 1; char rawData[] = { 0x01, 0x02, 0x03, 0x04, // raw rgba data 0x11, 0x12, 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34 }; QVariantMap imageData = {{"width", width}, {"height", height}, {"rowStride", rowStride}, {"bitsPerSample", bitsPerSample}, {"channels", channels}, {"hasAlpha", hasAlpha}, {"imageData", QByteArray(rawData, sizeof(rawData))}}; QVariantMap hints; #define COMPARE_PIXEL(x, y) \ QCOMPARE(qRed(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 0]); \ QCOMPARE(qGreen(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 1]); \ QCOMPARE(qBlue(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 2]); \ QCOMPARE(qAlpha(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 3]); hints.insert(QStringLiteral("image-data"), imageData); if (iconPaths.size() > 0) hints.insert(QStringLiteral("image-path"), iconPaths[0]); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, hints, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()->hasPayload()); buffer = dynamic_cast(d->getLastPackage()->payload().data()); QCOMPARE(d->getLastPackage()->payloadSize(), buffer->size()); // image-data is attached as png data QVERIFY(image.loadFromData(reinterpret_cast(buffer->data().constData()), buffer->size(), "PNG")); // image-data has priority over image-path: QCOMPARE(image.byteCount(), rowStride*height); // rgba -> argb conversion was done correctly: COMPARE_PIXEL(0,0); COMPARE_PIXEL(1,0); COMPARE_PIXEL(0,1); COMPARE_PIXEL(1,1); // same for image_data in hints hints.clear(); hints.insert(QStringLiteral("image-data"), imageData); if (iconPaths.size() > 0) hints.insert(QStringLiteral("image_path"), iconPaths[0]); retId = listener->Notify(appName, replacesId, icon, summary, body, {}, hints, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()->hasPayload()); buffer = dynamic_cast(d->getLastPackage()->payload().data()); QCOMPARE(d->getLastPackage()->payloadSize(), buffer->size()); // image-data is attached as png data QVERIFY(image.loadFromData(reinterpret_cast(buffer->data().constData()), buffer->size(), "PNG")); // image_data has priority over image_path/image-path: QCOMPARE(image.byteCount(), rowStride*height); // rgba -> argb conversion was done correctly: COMPARE_PIXEL(0,0); COMPARE_PIXEL(1,0); COMPARE_PIXEL(0,1); COMPARE_PIXEL(1,1); // same for icon_data, which has lowest priority hints.clear(); hints.insert(QStringLiteral("icon_data"), imageData); retId = listener->Notify(appName, replacesId, QLatin1String(""), summary, body, {}, hints, 0); QCOMPARE(retId, replacesId); QCOMPARE(++proxiedNotifications, d->getSentPackages()); QVERIFY(d->getLastPackage()); QVERIFY(d->getLastPackage()->hasPayload()); buffer = dynamic_cast(d->getLastPackage()->payload().data()); // image-data is attached as png data QVERIFY(image.loadFromData(reinterpret_cast(buffer->data().constData()), buffer->size(), "PNG")); QCOMPARE(image.byteCount(), rowStride*height); // rgba -> argb conversion was done correctly: COMPARE_PIXEL(0,0); COMPARE_PIXEL(1,0); COMPARE_PIXEL(0,1); COMPARE_PIXEL(1,1); #undef COMPARE_PIXEL } QTEST_GUILESS_MAIN(TestNotificationListener); #include "testnotificationlistener.moc" diff --git a/tests/testsocketlinereader.cpp b/tests/testsocketlinereader.cpp index 02612a81..828e5691 100644 --- a/tests/testsocketlinereader.cpp +++ b/tests/testsocketlinereader.cpp @@ -1,122 +1,122 @@ /************************************************************************************* * Copyright (C) 2014 by Alejandro Fiestas Olivares * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "../core/backends/lan/socketlinereader.h" #include "../core/backends/lan/server.h" #include #include #include #include #include class TestSocketLineReader : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void newPackage(); private Q_SLOTS: void socketLineReader(); private: QTimer mTimer; QEventLoop mLoop; QList mPackages; Server *mServer; QSslSocket *mConn; SocketLineReader *mReader; }; void TestSocketLineReader::initTestCase() { mServer = new Server(this); QVERIFY2(mServer->listen(QHostAddress::LocalHost, 8694), "Failed to create local tcp server"); mTimer.setInterval(4000);//For second is more enough to send some data via local socket mTimer.setSingleShot(true); connect(&mTimer, &QTimer::timeout, &mLoop, &QEventLoop::quit); mConn = new QSslSocket(this); mConn->connectToHost(QHostAddress::LocalHost, 8694); connect(mConn, &QAbstractSocket::connected, &mLoop, &QEventLoop::quit); mTimer.start(); mLoop.exec(); QVERIFY2(mConn->isOpen(), "Could not connect to local tcp server"); } void TestSocketLineReader::socketLineReader() { QList dataToSend; dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n"; - Q_FOREACH(const QByteArray &line, dataToSend) { + for (const QByteArray& line : dataToSend) { mConn->write(line); } mConn->flush(); int maxAttemps = 5; while(!mServer->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket *sock = mServer->nextPendingConnection(); QVERIFY2(sock != nullptr, "Could not open a connection to the client"); mReader = new SocketLineReader(sock, this); connect(mReader, &SocketLineReader::readyRead, this, &TestSocketLineReader::newPackage); mTimer.start(); mLoop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); QCOMPARE(mPackages.count(), 5);//We expect 5 Packages for(int x = 0;x < 5; ++x) { QCOMPARE(mPackages[x], dataToSend[x]); } } void TestSocketLineReader::newPackage() { if (!mReader->bytesAvailable()) { return; } int maxLoops = 5; while(mReader->bytesAvailable() > 0 && maxLoops > 0) { --maxLoops; const QByteArray package = mReader->readLine(); if (!package.isEmpty()) { mPackages.append(package); } if (mPackages.count() == 5) { mLoop.exit(); } } } QTEST_GUILESS_MAIN(TestSocketLineReader) #include "testsocketlinereader.moc" diff --git a/tests/testsslsocketlinereader.cpp b/tests/testsslsocketlinereader.cpp index 09a53e1f..24a912cb 100644 --- a/tests/testsslsocketlinereader.cpp +++ b/tests/testsslsocketlinereader.cpp @@ -1,310 +1,310 @@ /** * Copyright 2015 Vineet Garg * * 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 "../core/backends/lan/server.h" #include "../core/backends/lan/socketlinereader.h" #include #include #include #include /* * This class tests the behaviour of socket line reader when the connection if over ssl. Since SSL takes part below application layer, * working of SocketLineReader should be same. */ class TestSslSocketLineReader : public QObject { Q_OBJECT public Q_SLOTS: void newPackage(); private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void cleanupTestCase(); void testTrustedDevice(); void testUntrustedDevice(); void testTrustedDeviceWithWrongCertificate(); private: const int PORT = 7894; QTimer mTimer; QCA::Initializer mQcaInitializer; QEventLoop mLoop; QList mPackages; Server *mServer; QSslSocket *mClientSocket; SocketLineReader *mReader; private: void setSocketAttributes(QSslSocket* socket, QString deviceName); }; void TestSslSocketLineReader::initTestCase() { mServer = new Server(this); QVERIFY2(mServer->listen(QHostAddress::LocalHost, PORT), "Failed to create local tcp server"); mTimer.setInterval(10 * 1000);//Ten second is more enough for this test, just used this so that to break mLoop if stuck mTimer.setSingleShot(true); connect(&mTimer, &QTimer::timeout, &mLoop, &QEventLoop::quit); mTimer.start(); } void TestSslSocketLineReader::init() { mClientSocket = new QSslSocket(this); mClientSocket->connectToHost(QHostAddress::LocalHost, PORT); connect(mClientSocket, &QAbstractSocket::connected, &mLoop, &QEventLoop::quit); mLoop.exec(); QVERIFY2(mClientSocket->isOpen(), "Could not connect to local tcp server"); } void TestSslSocketLineReader::cleanup() { mClientSocket->disconnectFromHost(); delete mClientSocket; } void TestSslSocketLineReader::cleanupTestCase() { delete mServer; } void TestSslSocketLineReader::testTrustedDevice() { int maxAttemps = 5; QCOMPARE(true, mServer->hasPendingConnections()); while(!mServer->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket *serverSocket = mServer->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Null socket returned by server"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); setSocketAttributes(serverSocket, QStringLiteral("Test Server")); setSocketAttributes(mClientSocket, QStringLiteral("Test Client")); serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); serverSocket->addCaCertificate(mClientSocket->localCertificate()); mClientSocket->setPeerVerifyName(QStringLiteral("Test Server")); mClientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); mClientSocket->addCaCertificate(serverSocket->localCertificate()); connect(mClientSocket, &QSslSocket::encrypted, &mLoop, &QEventLoop::quit); serverSocket->startServerEncryption(); mClientSocket->startClientEncryption(); mLoop.exec(); // Both client and server socket should be encrypted here and should have remote certificate because VerifyPeer is used QVERIFY2(mClientSocket->isOpen(), "Client socket already closed"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); QVERIFY2(mClientSocket->isEncrypted(), "Client is not encrypted"); QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted"); QVERIFY2(!mClientSocket->peerCertificate().isNull(), "Server certificate not received"); QVERIFY2(!serverSocket->peerCertificate().isNull(), "Client certificate not received"); QList dataToSend; dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n"; - Q_FOREACH(const QByteArray &line, dataToSend) { + for (const QByteArray &line : dataToSend) { mClientSocket->write(line); } mClientSocket->flush(); mPackages.clear(); mReader = new SocketLineReader(serverSocket, this); connect(mReader, &SocketLineReader::readyRead, this,&TestSslSocketLineReader::newPackage); mLoop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); QCOMPARE(mPackages.count(), 5);//We expect 5 Packages for(int x = 0;x < 5; ++x) { QCOMPARE(mPackages[x], dataToSend[x]); } delete mReader; } void TestSslSocketLineReader::testUntrustedDevice() { int maxAttemps = 5; QCOMPARE(true, mServer->hasPendingConnections()); while(!mServer->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket *serverSocket = mServer->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Null socket returned by server"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); setSocketAttributes(serverSocket, QStringLiteral("Test Server")); setSocketAttributes(mClientSocket, QStringLiteral("Test Client")); serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); serverSocket->setPeerVerifyMode(QSslSocket::QueryPeer); mClientSocket->setPeerVerifyName(QStringLiteral("Test Server")); mClientSocket->setPeerVerifyMode(QSslSocket::QueryPeer); connect(mClientSocket, &QSslSocket::encrypted, &mLoop, &QEventLoop::quit); serverSocket->startServerEncryption(); mClientSocket->startClientEncryption(); mLoop.exec(); QVERIFY2(mClientSocket->isOpen(), "Client socket already closed"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); QVERIFY2(mClientSocket->isEncrypted(), "Client is not encrypted"); QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted"); QVERIFY2(!mClientSocket->peerCertificate().isNull(), "Server certificate not received"); QVERIFY2(!serverSocket->peerCertificate().isNull(), "Client certificate not received"); QList dataToSend; dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n"; - Q_FOREACH(const QByteArray &line, dataToSend) { + for (const QByteArray &line : dataToSend) { mClientSocket->write(line); } mClientSocket->flush(); mPackages.clear(); mReader = new SocketLineReader(serverSocket, this); connect(mReader, &SocketLineReader::readyRead, this, &TestSslSocketLineReader::newPackage); mLoop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); QCOMPARE(mPackages.count(), 5);//We expect 5 Packages for(int x = 0;x < 5; ++x) { QCOMPARE(mPackages[x], dataToSend[x]); } delete mReader; } void TestSslSocketLineReader::testTrustedDeviceWithWrongCertificate() { int maxAttemps = 5; while(!mServer->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket *serverSocket = mServer->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Could not open a connection to the client"); setSocketAttributes(serverSocket, QStringLiteral("Test Server")); setSocketAttributes(mClientSocket, QStringLiteral("Test Client")); // Not adding other device certificate to list of CA certificate, and using VerifyPeer. This should lead to handshake failure serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); mClientSocket->setPeerVerifyName(QStringLiteral("Test Server")); mClientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); connect(serverSocket, &QSslSocket::encrypted, &mLoop, &QEventLoop::quit); // Encrypted signal should never be emitted connect(mClientSocket, &QSslSocket::encrypted, &mLoop, &QEventLoop::quit); // Encrypted signal should never be emitted connect(serverSocket, &QAbstractSocket::disconnected, &mLoop, &QEventLoop::quit); connect(mClientSocket, &QAbstractSocket::disconnected, &mLoop, &QEventLoop::quit); serverSocket->startServerEncryption(); mClientSocket->startClientEncryption(); mLoop.exec(); QVERIFY2(!serverSocket->isEncrypted(), "Server is encrypted, it should not"); QVERIFY2(!mClientSocket->isEncrypted(), "lient is encrypted, it should now"); if (serverSocket->state() != QAbstractSocket::UnconnectedState) mLoop.exec(); // Wait until serverSocket is disconnected, It should be in disconnected state if (mClientSocket->state() != QAbstractSocket::UnconnectedState) mLoop.exec(); // Wait until mClientSocket is disconnected, It should be in disconnected state QCOMPARE((int)mClientSocket->state(), 0); QCOMPARE((int)serverSocket->state(), 0); } void TestSslSocketLineReader::newPackage() { if (!mReader->bytesAvailable()) { return; } int maxLoops = 5; while(mReader->bytesAvailable() > 0 && maxLoops > 0) { --maxLoops; const QByteArray package = mReader->readLine(); if (!package.isEmpty()) { mPackages.append(package); } if (mPackages.count() == 5) { mLoop.exit(); } } } void TestSslSocketLineReader::setSocketAttributes(QSslSocket *socket, QString deviceName) { QDateTime startTime = QDateTime::currentDateTime(); QDateTime endTime = startTime.addYears(10); QCA::CertificateInfo certificateInfo; certificateInfo.insert(QCA::CommonName,deviceName); certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); QCA::CertificateOptions certificateOptions(QCA::PKCS10); certificateOptions.setSerialNumber(10); certificateOptions.setInfo(certificateInfo); certificateOptions.setValidityPeriod(startTime, endTime); certificateOptions.setFormat(QCA::PKCS10); QCA::PrivateKey privKey = QCA::KeyGenerator().createRSA(2048); QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privKey).toPEM().toLatin1()); socket->setPrivateKey(QSslKey(privKey.toPEM().toLatin1(), QSsl::Rsa)); socket->setLocalCertificate(certificate); } QTEST_GUILESS_MAIN(TestSslSocketLineReader) #include "testsslsocketlinereader.moc"