diff --git a/cli/kdeconnect-cli.cpp b/cli/kdeconnect-cli.cpp index a42a1cac..2b5e4de2 100644 --- a/cli/kdeconnect-cli.cpp +++ b/cli/kdeconnect-cli.cpp @@ -1,267 +1,268 @@ /* * 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("name-only"), i18n("Make --list-devices or --list-available print only the devices name, to ease scripting"))); parser.addOption(QCommandLineOption(QStringLiteral("id-name-only"), i18n("Make --list-devices or --list-available print only the devices id and name, to ease scripting"))); parser.addOption(QCommandLineOption(QStringLiteral("refresh"), i18n("Search for devices in the network and re-establish connections"))); parser.addOption(QCommandLineOption(QStringLiteral("pair"), i18n("Request pairing to a said device"))); parser.addOption(QCommandLineOption(QStringLiteral("ring"), i18n("Find the said device by ringing it."))); parser.addOption(QCommandLineOption(QStringLiteral("unpair"), i18n("Stop pairing to a said device"))); parser.addOption(QCommandLineOption(QStringLiteral("ping"), i18n("Sends a ping to said device"))); parser.addOption(QCommandLineOption(QStringLiteral("ping-msg"), i18n("Same as ping but you can set the message to display"), i18n("message"))); parser.addOption(QCommandLineOption(QStringLiteral("share"), i18n("Share a file to a said device"), QStringLiteral("path"))); parser.addOption(QCommandLineOption(QStringLiteral("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(reachable, paired)); bool displayCount = true; for (const QString& id : devices) { if (parser.isSet(QStringLiteral("id-only"))) { QTextStream(stdout) << id << endl; displayCount = false; } else if (parser.isSet(QStringLiteral("name-only"))) { DeviceDbusInterface deviceIface(id); QTextStream(stdout) << deviceIface.name() << endl; displayCount = false; } else if (parser.isSet(QStringLiteral("id-name-only"))) { DeviceDbusInterface deviceIface(id); QTextStream(stdout) << id << ' ' << deviceIface.name() << endl; displayCount = false; } else { DeviceDbusInterface deviceIface(id); QString statusInfo; const bool isReachable = deviceIface.isReachable(); const bool isTrusted = deviceIface.isTrusted(); if (isReachable && isTrusted) { statusInfo = i18n("(paired and reachable)"); } else if (isReachable) { statusInfo = i18n("(reachable)"); } else if (isTrusted) { statusInfo = i18n("(paired)"); } QTextStream(stdout) << "- " << deviceIface.name() << ": " << deviceIface.id() << ' ' << statusInfo << endl; } } if (displayCount) { QTextStream(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"))) { QList urls; QUrl url = QUrl::fromUserInput(parser.value(QStringLiteral("share")), QDir::currentPath()); urls.append(url); // Check for more arguments - for (const QString& input : parser.positionalArguments()) { + const auto args = parser.positionalArguments(); + for (const QString& input : args) { QUrl url = QUrl::fromUserInput(input, QDir::currentPath()); urls.append(url); } for (const QUrl& url : urls) { 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)); QTextStream(stdout) << i18n("Shared %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.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+"/sms", QStringLiteral("org.kde.kdeconnect.device.sms"), 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 stdin QFile in; if(in.open(stdin,QIODevice::ReadOnly | QIODevice::Unbuffered)) { while (!in.atEnd()) { QByteArray line = in.readLine(); // sanitize to ASCII-codes > 31? msg.setArguments({QString(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/daemon.cpp b/core/daemon.cpp index e5233546..51acc923 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,307 +1,307 @@ /** * 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 #include "core_debug.h" #include "kdeconnectconfig.h" #include "networkpacket.h" #ifdef KDECONNECT_BLUETOOTH #include "backends/bluetooth/bluetoothlinkprovider.h" #endif #include "backends/lan/lanlinkprovider.h" #include "backends/loopback/loopbacklinkprovider.h" #include "device.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them QSet m_linkProviders; //Every known device QMap m_devices; QSet m_discoveryModeAcquisitions; }; 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->m_linkProviders.insert(new LoopbackLinkProvider()); else { d->m_linkProviders.insert(new LanLinkProvider()); #ifdef KDECONNECT_BLUETOOTH d->m_linkProviders.insert(new BluetoothLinkProvider()); #endif } //Read remembered paired devices const QStringList& list = KdeConnectConfig::instance()->trustedDevices(); for (const QString& id : list) { addDevice(new Device(this, id)); } //Listen to new devices for (LinkProvider* a : qAsConst(d->m_linkProviders)) { connect(a, &LinkProvider::onConnectionReceived, this, &Daemon::onNewDeviceLink); a->onStart(); } //Register on DBus qDBusRegisterMetaType< QMap >(); 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->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.insert(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { forceOnNetworkChange(); } } void Daemon::releaseDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.remove(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } void Daemon::removeDevice(Device* device) { d->m_devices.remove(device->id()); device->deleteLater(); Q_EMIT deviceRemoved(device->id()); Q_EMIT deviceListChanged(); } void Daemon::cleanDevices() { for (Device* device : qAsConst(d->m_devices)) { if (device->isTrusted()) { continue; } device->cleanUnneededLinks(); //If there are no links remaining if (!device->isReachable()) { removeDevice(device); } } } void Daemon::forceOnNetworkChange() { qCDebug(KDECONNECT_CORE) << "Sending onNetworkChange to " << d->m_linkProviders.size() << " LinkProviders"; for (LinkProvider* a : qAsConst(d->m_linkProviders)) { a->onNetworkChange(); } } Device*Daemon::getDevice(const QString& deviceId) { for (Device* device : qAsConst(d->m_devices)) { if (device->id() == deviceId) { return device; } } return Q_NULLPTR; } QStringList Daemon::devices(bool onlyReachable, bool onlyTrusted) const { QStringList ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret.append(device->id()); } return ret; } QMap Daemon::deviceNames(bool onlyReachable, bool onlyTrusted) const { QMap ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret[device->id()] = device->name(); } return ret; } void Daemon::onNewDeviceLink(const NetworkPacket& identityPacket, DeviceLink* dl) { const QString& id = identityPacket.get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Device discovered" << id << "via" << dl->provider()->name(); if (d->m_devices.contains(id)) { qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = d->m_devices[id]; bool wasReachable = device->isReachable(); device->addLink(identityPacket, dl); if (!wasReachable) { Q_EMIT deviceVisibilityChanged(id, true); Q_EMIT deviceListChanged(); } } else { qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPacket, dl); //we discard the connections that we created but it's not paired. if (!isDiscoveringDevices() && !device->isTrusted() && !dl->linkShouldBeKeptAlive()) { device->deleteLater(); } else { addDevice(device); } } } void Daemon::onDeviceStatusChanged() { Device* device = (Device*)sender(); //qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); if (!device->isReachable() && !device->isTrusted()) { //qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); removeDevice(device); } else { Q_EMIT deviceVisibilityChanged(device->id(), device->isReachable()); Q_EMIT deviceListChanged(); } } void Daemon::setAnnouncedName(const QString& name) { qCDebug(KDECONNECT_CORE()) << "Announcing name"; KdeConnectConfig::instance()->setName(name); forceOnNetworkChange(); Q_EMIT announcedNameChanged(name); } QString Daemon::announcedName() { return KdeConnectConfig::instance()->name(); } QNetworkAccessManager* Daemon::networkAccessManager() { static QPointer manager; if (!manager) { manager = new QNetworkAccessManager(this); } return manager; } QList Daemon::devicesList() const { return d->m_devices.values(); } bool Daemon::isDiscoveringDevices() const { return !d->m_discoveryModeAcquisitions.isEmpty(); } QString Daemon::deviceIdByName(const QString& name) const { for (Device* device : qAsConst(d->m_devices)) { if (device->name() == name && device->isTrusted()) return device->id(); } return {}; } void Daemon::addDevice(Device* device) { const QString id = device->id(); connect(device, &Device::reachableChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::trustedChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::hasPairingRequestsChanged, this, &Daemon::pairingRequestsChanged); connect(device, &Device::hasPairingRequestsChanged, this, [this, device](bool hasPairingRequests) { if (hasPairingRequests) askPairingConfirmation(device); } ); d->m_devices[id] = device; Q_EMIT deviceAdded(id); Q_EMIT deviceListChanged(); } QStringList Daemon::pairingRequests() const { QStringList ret; - for(Device* dev: d->m_devices) { + for(Device* dev: qAsConst(d->m_devices)) { if (dev->hasPairingRequests()) ret += dev->id(); } return ret; } Daemon::~Daemon() { } QString Daemon::selfId() const { return KdeConnectConfig::instance()->deviceId(); } diff --git a/core/device.cpp b/core/device.cpp index 8f5805e8..1dd0f59b 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -1,556 +1,556 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "device.h" #include #include #include #include #include #include #include #include "core_debug.h" #include "kdeconnectplugin.h" #include "pluginloader.h" #include "backends/devicelink.h" #include "backends/lan/landevicelink.h" #include "backends/linkprovider.h" #include "networkpacket.h" #include "kdeconnectconfig.h" #include "daemon.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" class Device::DevicePrivate { public: DevicePrivate(const QString &id) : m_deviceId(id) { } ~DevicePrivate() { qDeleteAll(m_deviceLinks); m_deviceLinks.clear(); } const QString m_deviceId; QString m_deviceName; DeviceType m_deviceType; int m_protocolVersion; QVector m_deviceLinks; QHash m_plugins; QMultiMap m_pluginsByIncomingCapability; QSet m_supportedPlugins; QSet m_pairRequests; }; static void warn(const QString& info) { qWarning() << "Device pairing error" << info; } Device::Device(QObject* parent, const QString& id) : QObject(parent) , d(new Device::DevicePrivate(id)) { d->m_protocolVersion = NetworkPacket::s_protocolVersion; KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance()->getTrustedDevice(d->m_deviceId); d->m_deviceName = info.deviceName; d->m_deviceType = str2type(info.deviceType); //Register in bus 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 d->m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); connect(this, &Device::pairingError, this, &warn); } Device::Device(QObject* parent, const NetworkPacket& identityPacket, DeviceLink* dl) : QObject(parent) , d(new Device::DevicePrivate(identityPacket.get(QStringLiteral("deviceId")))) { d->m_deviceName = identityPacket.get(QStringLiteral("deviceName")); addLink(identityPacket, dl); //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); connect(this, &Device::pairingError, this, &warn); } Device::~Device() { delete d; } QString Device::id() const { return d->m_deviceId; } QString Device::name() const { return d->m_deviceName; } QString Device::type() const { return type2str(d->m_deviceType); } bool Device::isReachable() const { return !d->m_deviceLinks.isEmpty(); } int Device::protocolVersion() { return d->m_protocolVersion; } QStringList Device::supportedPlugins() const { return d->m_supportedPlugins.toList(); } bool Device::hasPlugin(const QString& name) const { return d->m_plugins.contains(name); } QStringList Device::loadedPlugins() const { return d->m_plugins.keys(); } void Device::reloadPlugins() { QHash newPluginMap, oldPluginMap = d->m_plugins; QMultiMap newPluginsByIncomingCapability; if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices PluginLoader* loader = PluginLoader::instance(); for (const QString& pluginName : qAsConst(d->m_supportedPlugins)) { const KPluginMetaData service = loader->getPluginInfo(pluginName); const bool pluginEnabled = isPluginEnabled(pluginName); const QSet incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPacketType")).toSet(); if (pluginEnabled) { KdeConnectPlugin* plugin = d->m_plugins.take(pluginName); if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } Q_ASSERT(plugin); for (const QString& interface : incomingCapabilities) { newPluginsByIncomingCapability.insert(interface, plugin); } newPluginMap[pluginName] = plugin; } } } const bool differentPlugins = oldPluginMap != newPluginMap; //Erase all left plugins in the original map (meaning that we don't want //them anymore, otherwise they would have been moved to the newPluginMap) qDeleteAll(d->m_plugins); d->m_plugins = newPluginMap; d->m_pluginsByIncomingCapability = newPluginsByIncomingCapability; QDBusConnection bus = QDBusConnection::sessionBus(); for (KdeConnectPlugin* plugin : qAsConst(d->m_plugins)) { //TODO: see how it works in Android (only done once, when created) plugin->connected(); const QString dbusPath = plugin->dbusPath(); if (!dbusPath.isEmpty()) { bus.registerObject(dbusPath, plugin, QDBusConnection::ExportAllProperties | QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportScriptableSlots); } } if (differentPlugins) { Q_EMIT pluginsChanged(); } } QString Device::pluginsConfigFile() const { return KdeConnectConfig::instance()->deviceConfigDir(id()).absoluteFilePath(QStringLiteral("config")); } void Device::requestPair() { if (isTrusted()) { Q_EMIT pairingError(i18n("Already paired")); return; } if (!isReachable()) { Q_EMIT pairingError(i18n("Device not reachable")); return; } for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { dl->userRequestsPair(); } } void Device::unpair() { for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { dl->userRequestsUnpair(); } KdeConnectConfig::instance()->removeTrustedDevice(id()); Q_EMIT trustedChanged(false); } void Device::pairStatusChanged(DeviceLink::PairStatus status) { if (status == DeviceLink::NotPaired) { KdeConnectConfig::instance()->removeTrustedDevice(id()); for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { if (dl != sender()) { dl->setPairStatus(DeviceLink::NotPaired); } } } else { KdeConnectConfig::instance()->addTrustedDevice(id(), name(), type()); } reloadPlugins(); //Will load/unload plugins bool isTrusted = (status == DeviceLink::Paired); Q_EMIT trustedChanged(isTrusted); Q_ASSERT(isTrusted == this->isTrusted()); } static bool lessThan(DeviceLink* p1, DeviceLink* p2) { return p1->provider()->priority() > p2->provider()->priority(); } void Device::addLink(const NetworkPacket& identityPacket, DeviceLink* link) { //qCDebug(KDECONNECT_CORE) << "Adding link to" << id() << "via" << link->provider(); setName(identityPacket.get(QStringLiteral("deviceName"))); d->m_deviceType = str2type(identityPacket.get(QStringLiteral("deviceType"))); if (d->m_deviceLinks.contains(link)) return; d->m_protocolVersion = identityPacket.get(QStringLiteral("protocolVersion"), -1); if (d->m_protocolVersion != NetworkPacket::s_protocolVersion) { qCWarning(KDECONNECT_CORE) << d->m_deviceName << "- warning, device uses a different protocol version" << d->m_protocolVersion << "expected" << NetworkPacket::s_protocolVersion; } connect(link, &QObject::destroyed, this, &Device::linkDestroyed); d->m_deviceLinks.append(link); //Theoretically we will never add two links from the same provider (the provider should destroy //the old one before this is called), so we do not have to worry about destroying old links. //-- Actually, we should not destroy them or the provider will store an invalid ref! connect(link, &DeviceLink::receivedPacket, this, &Device::privateReceivedPacket); std::sort(d->m_deviceLinks.begin(), d->m_deviceLinks.end(), lessThan); const bool capabilitiesSupported = identityPacket.has(QStringLiteral("incomingCapabilities")) || identityPacket.has(QStringLiteral("outgoingCapabilities")); if (capabilitiesSupported) { const QSet outgoingCapabilities = identityPacket.get(QStringLiteral("outgoingCapabilities")).toSet() , incomingCapabilities = identityPacket.get(QStringLiteral("incomingCapabilities")).toSet(); d->m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities); //qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities; } else { d->m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); } reloadPlugins(); if (d->m_deviceLinks.size() == 1) { Q_EMIT reachableChanged(true); } connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged); connect(link, &DeviceLink::pairingRequest, this, &Device::addPairingRequest); connect(link, &DeviceLink::pairingRequestExpired, this, &Device::removePairingRequest); connect(link, &DeviceLink::pairingError, this, &Device::pairingError); } void Device::addPairingRequest(PairingHandler* handler) { const bool wasEmpty = d->m_pairRequests.isEmpty(); d->m_pairRequests.insert(handler); if (wasEmpty != d->m_pairRequests.isEmpty()) Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty()); } void Device::removePairingRequest(PairingHandler* handler) { const bool wasEmpty = d->m_pairRequests.isEmpty(); d->m_pairRequests.remove(handler); if (wasEmpty != d->m_pairRequests.isEmpty()) Q_EMIT hasPairingRequestsChanged(!d->m_pairRequests.isEmpty()); } bool Device::hasPairingRequests() const { return !d->m_pairRequests.isEmpty(); } void Device::acceptPairing() { if (d->m_pairRequests.isEmpty()) qWarning() << "no pair requests to accept!"; //copying because the pairing handler will be removed upon accept const auto prCopy = d->m_pairRequests; for (auto ph: prCopy) ph->acceptPairing(); } void Device::rejectPairing() { if (d->m_pairRequests.isEmpty()) qWarning() << "no pair requests to reject!"; //copying because the pairing handler will be removed upon reject const auto prCopy = d->m_pairRequests; for (auto ph: prCopy) ph->rejectPairing(); } void Device::linkDestroyed(QObject* o) { removeLink(static_cast(o)); } void Device::removeLink(DeviceLink* link) { d->m_deviceLinks.removeAll(link); //qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining"; if (d->m_deviceLinks.isEmpty()) { reloadPlugins(); Q_EMIT reachableChanged(false); } } bool Device::sendPacket(NetworkPacket& np) { Q_ASSERT(np.type() != PACKET_TYPE_PAIR); Q_ASSERT(isTrusted()); //Maybe we could block here any packet that is not an identity or a pairing packet to prevent sending non encrypted data for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { if (dl->sendPacket(np)) return true; } return false; } void Device::privateReceivedPacket(const NetworkPacket& np) { Q_ASSERT(np.type() != PACKET_TYPE_PAIR); if (isTrusted()) { const QList plugins = d->m_pluginsByIncomingCapability.values(np.type()); if (plugins.isEmpty()) { qWarning() << "discarding unsupported packet" << np.type() << "for" << name(); } for (KdeConnectPlugin* plugin : plugins) { plugin->receivePacket(np); } } else { qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring packet" << np.type(); unpair(); } } bool Device::isTrusted() const { return KdeConnectConfig::instance()->trustedDevices().contains(id()); } QStringList Device::availableLinks() const { QStringList sl; sl.reserve(d->m_deviceLinks.size()); for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { sl.append(dl->provider()->name()); } return sl; } void Device::cleanUnneededLinks() { if (isTrusted()) { return; } for(int i = 0; i < d->m_deviceLinks.size(); ) { DeviceLink* dl = d->m_deviceLinks[i]; if (!dl->linkShouldBeKeptAlive()) { dl->deleteLater(); d->m_deviceLinks.remove(i); } else { i++; } } } QHostAddress Device::getLocalIpAddress() const { - for (DeviceLink* dl : d->m_deviceLinks) { + for (DeviceLink* dl : qAsConst(d->m_deviceLinks)) { LanDeviceLink* ldl = dynamic_cast(dl); if (ldl) { return ldl->hostAddress(); } } return QHostAddress::Null; } Device::DeviceType Device::str2type(const QString& deviceType) { if (deviceType == QLatin1String("desktop")) return Desktop; if (deviceType == QLatin1String("laptop")) return Laptop; if (deviceType == QLatin1String("smartphone") || deviceType == QLatin1String("phone")) return Phone; if (deviceType == QLatin1String("tablet")) return Tablet; if (deviceType == QLatin1String("tv")) return Tv; return Unknown; } QString Device::type2str(Device::DeviceType deviceType) { if (deviceType == Desktop) return QStringLiteral("desktop"); if (deviceType == Laptop) return QStringLiteral("laptop"); if (deviceType == Phone) return QStringLiteral("smartphone"); if (deviceType == Tablet) return QStringLiteral("tablet"); if (deviceType == Tv) return QStringLiteral("tv"); return QStringLiteral("unknown"); } QString Device::statusIconName() const { return iconForStatus(isReachable(), isTrusted()); } QString Device::iconName() const { return iconForStatus(true, false); } QString Device::iconForStatus(bool reachable, bool trusted) const { Device::DeviceType deviceType = d->m_deviceType; if (deviceType == Device::Unknown) { deviceType = Device::Phone; //Assume phone if we don't know the type } else if (deviceType == Device::Desktop) { deviceType = Device::Device::Laptop; // We don't have desktop icon yet } QString status = (reachable? (trusted? QStringLiteral("connected") : QStringLiteral("disconnected")) : QStringLiteral("trusted")); QString type = type2str(deviceType); return type+status; } void Device::setName(const QString& name) { if (d->m_deviceName != name) { d->m_deviceName = name; Q_EMIT nameChanged(name); } } KdeConnectPlugin* Device::plugin(const QString& pluginName) const { return d->m_plugins[pluginName]; } void Device::setPluginEnabled(const QString& pluginName, bool enabled) { 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; } QString Device::pluginIconName(const QString& pluginName) { if (hasPlugin(pluginName)) { return d->m_plugins[pluginName]->iconName(); } return QString(); } diff --git a/interfaces/remotesinksmodel.cpp b/interfaces/remotesinksmodel.cpp index 8ef9f163..48adc96b 100644 --- a/interfaces/remotesinksmodel.cpp +++ b/interfaces/remotesinksmodel.cpp @@ -1,173 +1,173 @@ /** * Copyright 2018 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "remotesinksmodel.h" #include "interfaces_debug.h" #include #include RemoteSinksModel::RemoteSinksModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { connect(this, &QAbstractItemModel::rowsInserted, this, &RemoteSinksModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &RemoteSinksModel::rowsChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &RemoteSinksModel::refreshSinkList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &RemoteSinksModel::refreshSinkList); } QHash RemoteSinksModel::roleNames() const { //Role names for QML QHash names = QAbstractItemModel::roleNames(); names.insert(NameRole, "name"); names.insert(DescriptionRole, "description"); names.insert(MaxVolumeRole, "maxVolume"); names.insert(VolumeRole, "volume"); names.insert(MutedRole, "muted"); return names; } RemoteSinksModel::~RemoteSinksModel() { } QString RemoteSinksModel::deviceId() const { return m_deviceId; } void RemoteSinksModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new RemoteSystemVolumeDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotesystemvolumeInterface::sinksChanged, this, &RemoteSinksModel::refreshSinkList); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotesystemvolumeInterface::volumeChanged, this, [this](const QString& name, int volume) { - for (Sink* s: m_sinkList) { + for (Sink* s: qAsConst(m_sinkList)) { if (s->name == name) { s->volume = volume; Q_EMIT dataChanged(index(0,0), index(m_sinkList.size() - 1, 0)); } } }); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceRemotesystemvolumeInterface::mutedChanged, this, [this](const QString& name, bool muted) { - for (Sink* s: m_sinkList) { + for (Sink* s: qAsConst(m_sinkList)) { if (s->name == name) { s->muted = muted; Q_EMIT dataChanged(index(0,0), index(m_sinkList.size() - 1, 0)); } } }); refreshSinkList(); Q_EMIT deviceIdChanged(deviceId); } void RemoteSinksModel::refreshSinkList() { if (!m_dbusInterface) { return; } if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } const auto cmds = QJsonDocument::fromJson(m_dbusInterface->sinks()).array(); beginResetModel(); qDeleteAll(m_sinkList); m_sinkList.clear(); for (auto it = cmds.constBegin(), itEnd = cmds.constEnd(); it!=itEnd; ++it) { const QJsonObject cont = it->toObject(); Sink* sink = new Sink(); sink->name = cont.value(QStringLiteral("name")).toString(); sink->description = cont.value(QStringLiteral("description")).toString(); sink->maxVolume = cont.value(QStringLiteral("maxVolume")).toInt(); sink->volume = cont.value(QStringLiteral("volume")).toInt(); sink->muted = cont.value(QStringLiteral("muted")).toBool(); m_sinkList.append(sink); } endResetModel(); } QVariant RemoteSinksModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_sinkList.count()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } Sink* sink = m_sinkList[index.row()]; switch (role) { case NameRole: return sink->name; case DescriptionRole: return sink->description; case MaxVolumeRole: return sink->maxVolume; case VolumeRole: return sink->volume; case MutedRole: return sink->muted; default: return QVariant(); } } int RemoteSinksModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_sinkList.count(); } diff --git a/plugins/sendnotifications/sendnotifications_config.cpp b/plugins/sendnotifications/sendnotifications_config.cpp index 55645bbe..d6126b5d 100644 --- a/plugins/sendnotifications/sendnotifications_config.cpp +++ b/plugins/sendnotifications/sendnotifications_config.cpp @@ -1,121 +1,122 @@ /** * 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 "sendnotifications_config.h" #include "ui_sendnotifications_config.h" #include "notifyingapplicationmodel.h" #include #include K_PLUGIN_FACTORY(SendNotificationsConfigFactory, registerPlugin();) SendNotificationsConfig::SendNotificationsConfig(QWidget* parent, const QVariantList& args) : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_sendnotifications_config")) , m_ui(new Ui::SendNotificationsConfigUi()) , appModel(new NotifyingApplicationModel) { qRegisterMetaTypeStreamOperators("NotifyingApplication"); m_ui->setupUi(this); m_ui->appList->setIconSize(QSize(32,32)); m_ui->appList->setModel(appModel); m_ui->appList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::QHeaderView::Fixed); m_ui->appList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::QHeaderView::Stretch); m_ui->appList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::QHeaderView::Stretch); for (int i = 0; i < 3; i++) m_ui->appList->resizeColumnToContents(i); connect(m_ui->appList->horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), m_ui->appList, SLOT(sortByColumn(int))); connect(m_ui->check_persistent, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_ui->spin_urgency, SIGNAL(editingFinished()), this, SLOT(changed())); connect(m_ui->check_body, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_ui->check_icons, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(appModel, SIGNAL(applicationsChanged()), this, SLOT(changed())); connect(config(), &KdeConnectPluginConfig::configChanged, this, &SendNotificationsConfig::loadApplications); } SendNotificationsConfig::~SendNotificationsConfig() { delete m_ui; } void SendNotificationsConfig::defaults() { KCModule::defaults(); m_ui->check_persistent->setChecked(false); m_ui->spin_urgency->setValue(0); m_ui->check_body->setChecked(true); m_ui->check_icons->setChecked(true); Q_EMIT changed(true); } void SendNotificationsConfig::loadApplications() { appModel->clearApplications(); QVariantList list = config()->getList(QStringLiteral("applications")); for (const auto& a: list) { NotifyingApplication app = a.value(); if (!appModel->containsApp(app.name)) { appModel->appendApp(app); } } } void SendNotificationsConfig::load() { KCModule::load(); bool persistent = config()->get(QStringLiteral("generalPersistent"), false); m_ui->check_persistent->setChecked(persistent); bool body = config()->get(QStringLiteral("generalIncludeBody"), true); m_ui->check_body->setChecked(body); bool icons = config()->get(QStringLiteral("generalSynchronizeIcons"), true); m_ui->check_icons->setChecked(icons); int urgency = config()->get(QStringLiteral("generalUrgency"), 0); m_ui->spin_urgency->setValue(urgency); loadApplications(); Q_EMIT changed(false); } void SendNotificationsConfig::save() { config()->set(QStringLiteral("generalPersistent"), m_ui->check_persistent->isChecked()); config()->set(QStringLiteral("generalIncludeBody"), m_ui->check_body->isChecked()); config()->set(QStringLiteral("generalSynchronizeIcons"), m_ui->check_icons->isChecked()); config()->set(QStringLiteral("generalUrgency"), m_ui->spin_urgency->value()); QVariantList list; - list.reserve(appModel->apps().size()); - for (const auto& a: appModel->apps()) { + const auto apps = appModel->apps(); + list.reserve(apps.size()); + for (const auto& a: apps) { list.append(QVariant::fromValue(a)); } config()->setList(QStringLiteral("applications"), list); KCModule::save(); Q_EMIT changed(false); } #include "sendnotifications_config.moc" diff --git a/plugins/systemvolume/systemvolumeplugin-pulse.cpp b/plugins/systemvolume/systemvolumeplugin-pulse.cpp index 3b37d776..772a0295 100644 --- a/plugins/systemvolume/systemvolumeplugin-pulse.cpp +++ b/plugins/systemvolume/systemvolumeplugin-pulse.cpp @@ -1,127 +1,129 @@ /** * Copyright 2017 Nicolas Fella * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "systemvolumeplugin-pulse.h" #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_systemvolume.json", registerPlugin< SystemvolumePlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SYSTEMVOLUME, "kdeconnect.plugin.systemvolume") SystemvolumePlugin::SystemvolumePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , sinksMap() {} bool SystemvolumePlugin::receivePacket(const NetworkPacket& np) { if (!PulseAudioQt::Context::instance()->isValid()) return false; if (np.has(QStringLiteral("requestSinks"))) { sendSinkList(); } else { QString name = np.get(QStringLiteral("name")); if (sinksMap.contains(name)) { if (np.has(QStringLiteral("volume"))) { sinksMap[name]->setVolume(np.get(QStringLiteral("volume"))); } if (np.has(QStringLiteral("muted"))) { sinksMap[name]->setMuted(np.get(QStringLiteral("muted"))); } } } return true; } void SystemvolumePlugin::sendSinkList() { QJsonDocument document; QJsonArray array; sinksMap.clear(); - for (PulseAudioQt::Sink* sink : PulseAudioQt::Context::instance()->sinks()) { + const auto sinks = PulseAudioQt::Context::instance()->sinks(); + for (PulseAudioQt::Sink* sink : sinks) { sinksMap.insert(sink->name(), sink); connect(sink, &PulseAudioQt::Sink::volumeChanged, this, [this, sink] { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("volume"), sink->volume()); np.set(QStringLiteral("name"), sink->name()); sendPacket(np); }); connect(sink, &PulseAudioQt::Sink::mutedChanged, this, [this, sink] { NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("muted"), sink->isMuted()); np.set(QStringLiteral("name"), sink->name()); sendPacket(np); }); QJsonObject sinkObject; sinkObject.insert("name", sink->name()); sinkObject.insert("description", sink->description()); sinkObject.insert("muted", sink->isMuted()); sinkObject.insert("volume", sink->volume()); sinkObject.insert("maxVolume", PulseAudioQt::normalVolume()); array.append(sinkObject); } document.setArray(array); NetworkPacket np(PACKET_TYPE_SYSTEMVOLUME); np.set(QStringLiteral("sinkList"), document); sendPacket(np); } void SystemvolumePlugin::connected() { connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkAdded, this, [this] { sendSinkList(); }); connect(PulseAudioQt::Context::instance(), &PulseAudioQt::Context::sinkRemoved, this, [this] { sendSinkList(); }); - for (PulseAudioQt::Sink* sink : PulseAudioQt::Context::instance()->sinks()) { + const auto sinks = PulseAudioQt::Context::instance()->sinks(); + for (PulseAudioQt::Sink* sink : sinks) { sinksMap.insert(sink->name(), sink); } } #include "systemvolumeplugin-pulse.moc" diff --git a/tests/testsocketlinereader.cpp b/tests/testsocketlinereader.cpp index 1c42f0af..09ae12d7 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 newPacket(); private Q_SLOTS: void socketLineReader(); private: QTimer m_timer; QEventLoop m_loop; QList m_packets; Server* m_server; QSslSocket* m_conn; SocketLineReader* m_reader; }; void TestSocketLineReader::initTestCase() { m_server = new Server(this); QVERIFY2(m_server->listen(QHostAddress::LocalHost, 8694), "Failed to create local tcp server"); m_timer.setInterval(4000);//For second is more enough to send some data via local socket m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, &m_loop, &QEventLoop::quit); m_conn = new QSslSocket(this); m_conn->connectToHost(QHostAddress::LocalHost, 8694); connect(m_conn, &QAbstractSocket::connected, &m_loop, &QEventLoop::quit); m_timer.start(); m_loop.exec(); QVERIFY2(m_conn->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"; - for (const QByteArray& line : dataToSend) { + for (const QByteArray& line : qAsConst(dataToSend)) { m_conn->write(line); } m_conn->flush(); int maxAttemps = 5; while(!m_server->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket* sock = m_server->nextPendingConnection(); QVERIFY2(sock != nullptr, "Could not open a connection to the client"); m_reader = new SocketLineReader(sock, this); connect(m_reader, &SocketLineReader::readyRead, this, &TestSocketLineReader::newPacket); m_timer.start(); m_loop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); QCOMPARE(m_packets.count(), 5);//We expect 5 Packets for(int x = 0;x < 5; ++x) { QCOMPARE(m_packets[x], dataToSend[x]); } } void TestSocketLineReader::newPacket() { if (!m_reader->bytesAvailable()) { return; } int maxLoops = 5; while(m_reader->bytesAvailable() > 0 && maxLoops > 0) { --maxLoops; const QByteArray packet = m_reader->readLine(); if (!packet.isEmpty()) { m_packets.append(packet); } if (m_packets.count() == 5) { m_loop.exit(); } } } QTEST_GUILESS_MAIN(TestSocketLineReader) #include "testsocketlinereader.moc" diff --git a/tests/testsslsocketlinereader.cpp b/tests/testsslsocketlinereader.cpp index fe90edf3..10e5a1ae 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 newPacket(); private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void cleanupTestCase(); void testTrustedDevice(); void testUntrustedDevice(); void testTrustedDeviceWithWrongCertificate(); private: const int PORT = 7894; QTimer m_timer; QCA::Initializer m_qcaInitializer; QEventLoop m_loop; QList m_packets; Server* m_server; QSslSocket* m_clientSocket; SocketLineReader* m_reader; private: void setSocketAttributes(QSslSocket* socket, QString deviceName); }; void TestSslSocketLineReader::initTestCase() { m_server = new Server(this); QVERIFY2(m_server->listen(QHostAddress::LocalHost, PORT), "Failed to create local tcp server"); m_timer.setInterval(10 * 1000);//Ten second is more enough for this test, just used this so that to break mLoop if stuck m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, &m_loop, &QEventLoop::quit); m_timer.start(); } void TestSslSocketLineReader::init() { m_clientSocket = new QSslSocket(this); m_clientSocket->connectToHost(QHostAddress::LocalHost, PORT); connect(m_clientSocket, &QAbstractSocket::connected, &m_loop, &QEventLoop::quit); m_loop.exec(); QVERIFY2(m_clientSocket->isOpen(), "Could not connect to local tcp server"); } void TestSslSocketLineReader::cleanup() { m_clientSocket->disconnectFromHost(); delete m_clientSocket; } void TestSslSocketLineReader::cleanupTestCase() { delete m_server; } void TestSslSocketLineReader::testTrustedDevice() { int maxAttemps = 5; QCOMPARE(true, m_server->hasPendingConnections()); while(!m_server->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket* serverSocket = m_server->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Null socket returned by server"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); setSocketAttributes(serverSocket, QStringLiteral("Test Server")); setSocketAttributes(m_clientSocket, QStringLiteral("Test Client")); serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); serverSocket->addCaCertificate(m_clientSocket->localCertificate()); m_clientSocket->setPeerVerifyName(QStringLiteral("Test Server")); m_clientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); m_clientSocket->addCaCertificate(serverSocket->localCertificate()); connect(m_clientSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); serverSocket->startServerEncryption(); m_clientSocket->startClientEncryption(); m_loop.exec(); // Both client and server socket should be encrypted here and should have remote certificate because VerifyPeer is used QVERIFY2(m_clientSocket->isOpen(), "Client socket already closed"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); QVERIFY2(m_clientSocket->isEncrypted(), "Client is not encrypted"); QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted"); QVERIFY2(!m_clientSocket->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"; - for (const QByteArray& line : dataToSend) { + for (const QByteArray& line : qAsConst(dataToSend)) { m_clientSocket->write(line); } m_clientSocket->flush(); m_packets.clear(); m_reader = new SocketLineReader(serverSocket, this); connect(m_reader, &SocketLineReader::readyRead, this,&TestSslSocketLineReader::newPacket); m_loop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); QCOMPARE(m_packets.count(), 5);//We expect 5 Packets for(int x = 0;x < 5; ++x) { QCOMPARE(m_packets[x], dataToSend[x]); } delete m_reader; } void TestSslSocketLineReader::testUntrustedDevice() { int maxAttemps = 5; QCOMPARE(true, m_server->hasPendingConnections()); while(!m_server->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket* serverSocket = m_server->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Null socket returned by server"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); setSocketAttributes(serverSocket, QStringLiteral("Test Server")); setSocketAttributes(m_clientSocket, QStringLiteral("Test Client")); serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); serverSocket->setPeerVerifyMode(QSslSocket::QueryPeer); m_clientSocket->setPeerVerifyName(QStringLiteral("Test Server")); m_clientSocket->setPeerVerifyMode(QSslSocket::QueryPeer); connect(m_clientSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); serverSocket->startServerEncryption(); m_clientSocket->startClientEncryption(); m_loop.exec(); QVERIFY2(m_clientSocket->isOpen(), "Client socket already closed"); QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); QVERIFY2(m_clientSocket->isEncrypted(), "Client is not encrypted"); QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted"); QVERIFY2(!m_clientSocket->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"; - for (const QByteArray& line : dataToSend) { + for (const QByteArray& line : qAsConst(dataToSend)) { m_clientSocket->write(line); } m_clientSocket->flush(); m_packets.clear(); m_reader = new SocketLineReader(serverSocket, this); connect(m_reader, &SocketLineReader::readyRead, this, &TestSslSocketLineReader::newPacket); m_loop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); QCOMPARE(m_packets.count(), 5);//We expect 5 Packets for(int x = 0;x < 5; ++x) { QCOMPARE(m_packets[x], dataToSend[x]); } delete m_reader; } void TestSslSocketLineReader::testTrustedDeviceWithWrongCertificate() { int maxAttemps = 5; while(!m_server->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } QSslSocket* serverSocket = m_server->nextPendingConnection(); QVERIFY2(serverSocket != 0, "Could not open a connection to the client"); setSocketAttributes(serverSocket, QStringLiteral("Test Server")); setSocketAttributes(m_clientSocket, 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); m_clientSocket->setPeerVerifyName(QStringLiteral("Test Server")); m_clientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); connect(serverSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); // Encrypted signal should never be emitted connect(m_clientSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); // Encrypted signal should never be emitted connect(serverSocket, &QAbstractSocket::disconnected, &m_loop, &QEventLoop::quit); connect(m_clientSocket, &QAbstractSocket::disconnected, &m_loop, &QEventLoop::quit); serverSocket->startServerEncryption(); m_clientSocket->startClientEncryption(); m_loop.exec(); QVERIFY2(!serverSocket->isEncrypted(), "Server is encrypted, it should not"); QVERIFY2(!m_clientSocket->isEncrypted(), "lient is encrypted, it should now"); if (serverSocket->state() != QAbstractSocket::UnconnectedState) m_loop.exec(); // Wait until serverSocket is disconnected, It should be in disconnected state if (m_clientSocket->state() != QAbstractSocket::UnconnectedState) m_loop.exec(); // Wait until mClientSocket is disconnected, It should be in disconnected state QCOMPARE((int)m_clientSocket->state(), 0); QCOMPARE((int)serverSocket->state(), 0); } void TestSslSocketLineReader::newPacket() { if (!m_reader->bytesAvailable()) { return; } int maxLoops = 5; while(m_reader->bytesAvailable() > 0 && maxLoops > 0) { --maxLoops; const QByteArray packet = m_reader->readLine(); if (!packet.isEmpty()) { m_packets.append(packet); } if (m_packets.count() == 5) { m_loop.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"