diff --git a/CMakeLists.txt b/CMakeLists.txt index d1adf10e..dab0317b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,84 +1,115 @@ cmake_minimum_required(VERSION 3.0) project(kdeconnect) set(KDECONNECT_VERSION_MAJOR 1) set(KDECONNECT_VERSION_MINOR 3) set(KDECONNECT_VERSION_PATCH 0) set(KDECONNECT_VERSION "${KDECONNECT_VERSION_MAJOR}.${KDECONNECT_VERSION_MINOR}.${KDECONNECT_VERSION_PATCH}") -set(QT_MIN_VERSION "5.7.0") -set(KF5_MIN_VERSION "5.42.0") +find_package(PkgConfig) + +if (SAILFISHOS) + set(KF5_MIN_VERSION "5.31.0") + set(QT_MIN_VERSION "5.6.0") + set(KF5_REQUIRED_COMPONENTS I18n DBusAddons CoreAddons IconThemes Config) + set(KF5_OPTIONAL_COMPONENTS) + set(QCA_MIN_VERSION 2.0.0) + pkg_search_module(SFOS REQUIRED sailfishapp) + pkg_check_modules(QCA2 qca2-qt5>=${QCA_MIN_VERSION} REQUIRED) + add_definitions(-DSAILFISHOS) + include_directories(${QCA2_INCLUDEDIR}) + add_definitions(-DQT_NO_URL_CAST_FROM_STRING) +else() + set(KF5_MIN_VERSION "5.42.0") + set(QT_MIN_VERSION "5.7.0") + set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils) + set(KF5_OPTIONAL_COMPONENTS DocTools PulseAudioQt) + set(QCA_MIN_VERSION "2.1.0") + find_package(Qca-qt5 ${QCA_MIN_VERSION} REQUIRED) + add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_KEYWORDS) +endif() find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DIR}/cmake) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Quick Network) +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ${KF5_REQUIRED_COMPONENTS}) +if (KF5_OPTIONAL_COMPONENTS) +find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS ${KF5_OPTIONAL_COMPONENTS}) +endif() -find_package(KF5 ${KF5_MIN_VERSION} - REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils - OPTIONAL_COMPONENTS DocTools PulseAudioQt -) -find_package(Qca-qt5 2.1.0 REQUIRED) find_package(Phonon4Qt5 4.9.0 NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" TYPE OPTIONAL PURPOSE "Required for Find My Device plugin" ) include_directories(${CMAKE_SOURCE_DIR}) configure_file(kdeconnect-version.h.in ${CMAKE_CURRENT_BINARY_DIR}/kdeconnect-version.h) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMAddTests) include(ECMSetupVersion) include(ECMInstallIcons) include(FeatureSummary) include(KDEConnectMacros.cmake) -add_definitions(-DQT_NO_URL_CAST_FROM_STRING -DQT_NO_KEYWORDS) - include(GenerateExportHeader) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(core) -add_subdirectory(kcm) -add_subdirectory(kcmplugin) +if(NOT SAILFISHOS) + add_subdirectory(kcm) + add_subdirectory(kcmplugin) + add_subdirectory(daemon) +endif() + if(NOT WIN32) - add_subdirectory(kio) + if (NOT SAILFISHOS) + add_subdirectory(kio) + endif() add_subdirectory(plasmoid) endif() + add_subdirectory(icon) add_subdirectory(interfaces) option(EXPERIMENTALAPP_ENABLED OFF) if(EXPERIMENTALAPP_ENABLED) add_subdirectory(app) endif() -add_subdirectory(daemon) add_subdirectory(plugins) add_subdirectory(cli) -add_subdirectory(indicator) -add_subdirectory(urlhandler) -add_subdirectory(nautilus-extension) + +if (NOT SAILFISHOS) + add_subdirectory(indicator) + add_subdirectory(urlhandler) + add_subdirectory(nautilus-extension) +else() + add_subdirectory(sfos) +endif() option(SMSAPP_ENABLED OFF) if(SMSAPP_ENABLED) find_package(KF5People REQUIRED) add_subdirectory(smsapp) endif() + if(KF5DocTools_FOUND) add_subdirectory(doc) endif() -if(BUILD_TESTING) +if(BUILD_TESTING AND NOT SAILFISHOS) add_subdirectory(tests) endif() -install(FILES org.kde.kdeconnect.kcm.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) +if(NOT SAILFISHOS) + install(FILES org.kde.kdeconnect.kcm.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) +endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/core/backends/devicelink.h b/core/backends/devicelink.h index a941d650..752d797a 100644 --- a/core/backends/devicelink.h +++ b/core/backends/devicelink.h @@ -1,79 +1,80 @@ /** * 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 . */ #ifndef DEVICELINK_H #define DEVICELINK_H #include +#include //Fix build on older QCA #include #include "core/networkpacket.h" class PairingHandler; class NetworkPacket; class LinkProvider; class Device; class DeviceLink : public QObject { Q_OBJECT public: enum PairStatus { NotPaired, Paired }; DeviceLink(const QString& deviceId, LinkProvider* parent); virtual ~DeviceLink() = default; virtual QString name() = 0; const QString& deviceId() const { return m_deviceId; } LinkProvider* provider() { return m_linkProvider; } virtual bool sendPacket(NetworkPacket& np) = 0; //user actions virtual void userRequestsPair() = 0; virtual void userRequestsUnpair() = 0; PairStatus pairStatus() const { return m_pairStatus; } virtual void setPairStatus(PairStatus status); //The daemon will periodically destroy unpaired links if this returns false virtual bool linkShouldBeKeptAlive() { return false; } Q_SIGNALS: void pairingRequest(PairingHandler* handler); void pairingRequestExpired(PairingHandler* handler); void pairStatusChanged(DeviceLink::PairStatus status); void pairingError(const QString& error); void receivedPacket(const NetworkPacket& np); protected: QCA::PrivateKey m_privateKey; private: const QString m_deviceId; LinkProvider* m_linkProvider; PairStatus m_pairStatus; }; #endif diff --git a/core/daemon.cpp b/core/daemon.cpp index de98076c..c123e47a 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,304 +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 remebered 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) { 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 05754de7..5cd095d9 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -1,546 +1,549 @@ /** * 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) { 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; } diff --git a/core/pluginloader.cpp b/core/pluginloader.cpp index 41fd09be..f29ca730 100644 --- a/core/pluginloader.cpp +++ b/core/pluginloader.cpp @@ -1,131 +1,134 @@ /** * 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" +//In older Qt released, qAsConst isnt available +#include "qtcompat_p.h" + PluginLoader* PluginLoader::instance() { static PluginLoader* instance = new PluginLoader(); return instance; } PluginLoader::PluginLoader() { 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-OutgoingPacketType")); 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; for (const KPluginMetaData& service : qAsConst(plugins)) { ret += KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPacketType")).toSet(); } return ret.toList(); } QStringList PluginLoader::outgoingCapabilities() const { QSet ret; for (const KPluginMetaData& service : qAsConst(plugins)) { ret += KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPacketType")).toSet(); } return ret.toList(); } QSet PluginLoader::pluginsForCapabilities(const QSet& incoming, const QSet& outgoing) { QSet ret; for (const KPluginMetaData& service : qAsConst(plugins)) { const QSet pluginIncomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPacketType")).toSet(); const QSet pluginOutgoingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPacketType")).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/core/qtcompat_p.h b/core/qtcompat_p.h new file mode 100644 index 00000000..c71bbcef --- /dev/null +++ b/core/qtcompat_p.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2017 Kevin Funk + +#include + +#if QT_VERSION < QT_VERSION_CHECK(5,7,0) +namespace QtPrivate +{ +template struct QAddConst { + typedef const T Type; +}; +} + +// this adds const to non-const objects (like std::as_const) +template +Q_DECL_CONSTEXPR typename QtPrivate::QAddConst::Type &qAsConst(T &t) Q_DECL_NOTHROW { return t; } +// prevent rvalue arguments: +template +void qAsConst(const T &&) Q_DECL_EQ_DELETE; +#endif + +// compat for Q_FALLTHROUGH +#if QT_VERSION < QT_VERSION_CHECK(5,8,0) + +#if defined(__has_cpp_attribute) +# if __has_cpp_attribute(fallthrough) +# define Q_FALLTHROUGH() [[fallthrough]] +# elif __has_cpp_attribute(clang::fallthrough) +# define Q_FALLTHROUGH() [[clang::fallthrough]] +# elif __has_cpp_attribute(gnu::fallthrough) +# define Q_FALLTHROUGH() [[gnu::fallthrough]] +# endif +#endif + +#ifndef Q_FALLTHROUGH +# if defined(__GNUC__) && !defined(__INTEL_COMPILER) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 700) +# define Q_FALLTHROUGH() __attribute__((fallthrough)) +# else +# define Q_FALLTHROUGH() (void)0 +# endif +#endif + +#endif + +namespace QtCompat { +// TODO: Just use QDir::listSeparator once we depend on Qt 5.6 +Q_DECL_CONSTEXPR inline QChar listSeparator() Q_DECL_NOTHROW +{ +#if QT_VERSION < QT_VERSION_CHECK(5,6,0) +#ifdef Q_OS_WIN + return QLatin1Char(';'); +#else + return QLatin1Char(':'); +#endif +#else + return QDir::listSeparator(); +#endif +} +} + +#endif diff --git a/interfaces/notificationsmodel.cpp b/interfaces/notificationsmodel.cpp index 5b884c56..16fbcd3b 100644 --- a/interfaces/notificationsmodel.cpp +++ b/interfaces/notificationsmodel.cpp @@ -1,271 +1,274 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationsmodel.h" #include "interfaces_debug.h" #include #include #include #include //#include "modeltest.h" +//In older Qt released, qAsConst isnt available +#include "core/qtcompat_p.h" + NotificationsModel::NotificationsModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { //new ModelTest(this, this); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::dataChanged, this, &NotificationsModel::anyDismissableChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::anyDismissableChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), 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"); names.insert(TitleModelRole, "title"); names.insert(TextModelRole, "notitext"); return names; } NotificationsModel::~NotificationsModel() { } QString NotificationsModel::deviceId() const { return m_deviceId; } void NotificationsModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new DeviceNotificationsDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationPosted, this, &NotificationsModel::notificationAdded); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationRemoved, this, &NotificationsModel::notificationRemoved); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::allNotificationsRemoved, this, &NotificationsModel::clearNotifications); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationUpdated, this, &NotificationsModel::notificationUpdated); refreshNotificationList(); Q_EMIT deviceIdChanged(deviceId); } void NotificationsModel::notificationAdded(const QString& id) { beginInsertRows(QModelIndex(), 0, 0); NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, id, this); m_notificationList.prepend(dbusInterface); endInsertRows(); } void NotificationsModel::notificationRemoved(const QString& id) { for (int i = 0; i < m_notificationList.size(); ++i) { if (m_notificationList[i]->notificationId() == id) { beginRemoveRows(QModelIndex(), i, i); m_notificationList.removeAt(i); endRemoveRows(); return; } } qCWarning(KDECONNECT_INTERFACES) << "Attempted to remove unknown notification: " << id; } void NotificationsModel::refreshNotificationList() { if (!m_dbusInterface) { return; } clearNotifications(); if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } QDBusPendingReply pendingNotificationIds = m_dbusInterface->activeNotifications(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingNotificationIds, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &NotificationsModel::receivedNotifications); } void NotificationsModel::receivedNotifications(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearNotifications(); QDBusPendingReply pendingNotificationIds = *watcher; if (pendingNotificationIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << pendingNotificationIds.error(); return; } const QStringList notificationIds = pendingNotificationIds.value(); if (notificationIds.isEmpty()) { return; } beginInsertRows(QModelIndex(), 0, notificationIds.size() - 1); for (const QString& notificationId : notificationIds) { NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, notificationId, this); m_notificationList.append(dbusInterface); } endInsertRows(); } QVariant NotificationsModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_notificationList.count() || !m_notificationList[index.row()]->isValid()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } NotificationDbusInterface* notification = m_notificationList[index.row()]; //FIXME: This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { case IconModelRole: return QIcon::fromTheme(QStringLiteral("device-notifier")); case IdModelRole: return notification->internalId(); case NameModelRole: return notification->ticker(); case ContentModelRole: return QString(); //To implement in the Android side case AppNameModelRole: return notification->appName(); case DbusInterfaceRole: return qVariantFromValue(notification); case DismissableModelRole: return notification->dismissable(); case RepliableModelRole: return !notification->replyId().isEmpty(); case IconPathModelRole: return notification->iconPath(); case TitleModelRole: return notification->title(); case TextModelRole: return notification->text(); default: return QVariant(); } } NotificationDbusInterface* NotificationsModel::getNotification(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } int row = index.row(); if (row < 0 || row >= m_notificationList.size()) { return nullptr; } return m_notificationList[row]; } int NotificationsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_notificationList.count(); } bool NotificationsModel::isAnyDimissable() const { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { return true; } } return false; } void NotificationsModel::dismissAll() { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { notification->dismiss(); } } } void NotificationsModel::clearNotifications() { if (!m_notificationList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_notificationList.size() - 1); qDeleteAll(m_notificationList); m_notificationList.clear(); endRemoveRows(); } } void NotificationsModel::notificationUpdated(const QString& id) { //TODO only emit the affected indices Q_UNUSED(id); Q_EMIT dataChanged(index(0,0), index(m_notificationList.size() - 1, 0)); } diff --git a/plasmoid/CMakeLists.txt b/plasmoid/CMakeLists.txt index 51ea5ffa..6442fb4c 100644 --- a/plasmoid/CMakeLists.txt +++ b/plasmoid/CMakeLists.txt @@ -1,9 +1,11 @@ add_subdirectory(declarativeplugin) +if (NOT SAILFISHOS) install(DIRECTORY package/ DESTINATION ${DATA_INSTALL_DIR}/plasma/plasmoids/org.kde.kdeconnect) install(FILES package/metadata.desktop DESTINATION ${SERVICES_INSTALL_DIR} RENAME plasma-kdeconnect.desktop) +endif() diff --git a/plasmoid/declarativeplugin/responsewaiter.cpp b/plasmoid/declarativeplugin/responsewaiter.cpp index 6288e782..dca838dc 100644 --- a/plasmoid/declarativeplugin/responsewaiter.cpp +++ b/plasmoid/declarativeplugin/responsewaiter.cpp @@ -1,132 +1,135 @@ #include "responsewaiter.h" #include #include #include #include +//In older Qt released, qAsConst isnt available +#include "core/qtcompat_p.h" + 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") << 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 { for (int type : qAsConst(m_registered)) { if (variant.canConvert(QVariant::Type(type))) { - return reinterpret_cast(variant.constData()); + return reinterpret_cast(variant.constData()); } } - + return nullptr; } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 0ecc7858..b778946e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,39 +1,47 @@ include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/core) add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-plugins\") install(FILES kdeconnect_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) add_subdirectory(ping) -add_subdirectory(clipboard) -add_subdirectory(contacts) -add_subdirectory(telephony) -add_subdirectory(share) -add_subdirectory(notifications) add_subdirectory(battery) -add_subdirectory(findmyphone) -if(Phonon4Qt5_FOUND) - add_subdirectory(findthisdevice) -endif() -add_subdirectory(remotekeyboard) -add_subdirectory(mousepad) -if(NOT WIN32) - add_subdirectory(runcommand) - add_subdirectory(sendnotifications) - add_subdirectory(pausemusic) - add_subdirectory(mpriscontrol) - add_subdirectory(screensaver-inhibit) - add_subdirectory(sftp) +add_subdirectory(sendnotifications) +add_subdirectory(clipboard) + +if(NOT SAILFISHOS) + add_subdirectory(contacts) + add_subdirectory(share) + add_subdirectory(remotekeyboard) + add_subdirectory(notifications) + add_subdirectory(findmyphone) + add_subdirectory(telephony) + + if(WIN32) + add_subdirectory(mousepad_windows) + else() + add_subdirectory(runcommand) + add_subdirectory(pausemusic) + add_subdirectory(mpriscontrol) + add_subdirectory(mousepad) + add_subdirectory(screensaver-inhibit) + add_subdirectory(sftp) + endif() + + if(Phonon4Qt5_FOUND) + add_subdirectory(findthisdevice) + endif() endif() -if(EXPERIMENTALAPP_ENABLED) + +if(SAILFISHOS OR EXPERIMENTALAPP_ENABLED) add_subdirectory(remotecommands) add_subdirectory(mprisremote) add_subdirectory(remotecontrol) add_subdirectory(lockdevice) endif() if(KF5PulseAudioQt_FOUND) add_subdirectory(systemvolume) endif() #FIXME: If we split notifications in several files, they won't appear in the same group in the Notifications KCM install(FILES kdeconnect.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) diff --git a/plugins/battery/batteryplugin.cpp b/plugins/battery/batteryplugin.cpp index 3bf8b243..9727a376 100644 --- a/plugins/battery/batteryplugin.cpp +++ b/plugins/battery/batteryplugin.cpp @@ -1,83 +1,83 @@ /** * 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 "batteryplugin.h" #include + #include #include #include #include "batterydbusinterface.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_battery.json", registerPlugin< BatteryPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_BATTERY, "kdeconnect.plugin.battery") BatteryPlugin::BatteryPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , batteryDbusInterface(new BatteryDbusInterface(device())) { //TODO: Add battery reporting, could be based on: // http://kde-apps.org/content/show.php/battery+plasmoid+with+remaining+time?content=120309 } void BatteryPlugin::connected() { NetworkPacket np(PACKET_TYPE_BATTERY_REQUEST, {{"request",true}}); sendPacket(np); } BatteryPlugin::~BatteryPlugin() { //FIXME: Qt dbus does not allow to remove an adaptor! (it causes a crash in // the next dbus access to its parent). The implication of not deleting this // is that disabling the plugin does not remove the interface (that will // return outdated values) and that enabling it again instantiates a second // adaptor. This is also a memory leak until the entire device is destroyed. //batteryDbusInterface->deleteLater(); } bool BatteryPlugin::receivePacket(const NetworkPacket& np) { bool isCharging = np.get(QStringLiteral("isCharging"), false); int currentCharge = np.get(QStringLiteral("currentCharge"), -1); int thresholdEvent = np.get(QStringLiteral("thresholdEvent"), (int)ThresholdNone); if (batteryDbusInterface->charge() != currentCharge || batteryDbusInterface->isCharging() != isCharging ) { batteryDbusInterface->updateValues(isCharging, currentCharge); } if ( thresholdEvent == ThresholdBatteryLow && !isCharging ) { Daemon::instance()->sendSimpleNotification(QStringLiteral("batteryLow"), i18nc("device name: low battery", "%1: Low Battery", device()->name()), i18n("Battery at %1%", currentCharge), QStringLiteral("battery-040")); - } return true; } #include "batteryplugin.moc" diff --git a/plugins/notifications/notificationsdbusinterface.cpp b/plugins/notifications/notificationsdbusinterface.cpp index ec61a53b..a8aa7faa 100644 --- a/plugins/notifications/notificationsdbusinterface.cpp +++ b/plugins/notifications/notificationsdbusinterface.cpp @@ -1,189 +1,192 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationsdbusinterface.h" #include "notification_debug.h" #include "notification.h" #include #include #include #include "notificationsplugin.h" #include "sendreplydialog.h" +//In older Qt released, qAsConst isnt available +#include "qtcompat_p.h" + NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , m_device(plugin->device()) , m_plugin(plugin) , m_lastId(0) { } NotificationsDbusInterface::~NotificationsDbusInterface() { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface"; } void NotificationsDbusInterface::clearNotifications() { qDeleteAll(m_notifications); m_notifications.clear(); Q_EMIT allNotificationsRemoved(); } QStringList NotificationsDbusInterface::activeNotifications() { return m_notifications.keys(); } void NotificationsDbusInterface::notificationReady() { Notification* noti = static_cast(sender()); disconnect(noti, &Notification::ready, this, &NotificationsDbusInterface::notificationReady); addNotification(noti); } void NotificationsDbusInterface::processPacket(const NetworkPacket& np) { if (np.get(QStringLiteral("isCancel"))) { QString id = np.get(QStringLiteral("id")); // cut off kdeconnect-android's prefix if there: if (id.startsWith(QLatin1String("org.kde.kdeconnect_tp::"))) id = id.mid(id.indexOf(QLatin1String("::")) + 2); removeNotification(id); } else { QString id = np.get(QStringLiteral("id")); if (!m_internalIdToPublicId.contains(id)) { Notification* noti = new Notification(np, this); if (noti->isReady()) { addNotification(noti); } else { connect(noti, &Notification::ready, this, &NotificationsDbusInterface::notificationReady); } } else { QString pubId = m_internalIdToPublicId.value(id); Notification* noti = m_notifications.value(pubId); if (!noti) return; noti->update(np); if (noti->isReady()) { Q_EMIT notificationUpdated(pubId); } else { connect(noti, &Notification::ready, this, [this, pubId]{ Q_EMIT notificationUpdated(pubId); }); } } } } void NotificationsDbusInterface::addNotification(Notification* noti) { const QString& internalId = noti->internalId(); if (m_internalIdToPublicId.contains(internalId)) { removeNotification(internalId); } //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "addNotification" << internalId; connect(noti, &Notification::dismissRequested, this, &NotificationsDbusInterface::dismissRequested); - - connect(noti, &Notification::replyRequested, this, [this,noti]{ - replyRequested(noti); + + connect(noti, &Notification::replyRequested, this, [this,noti]{ + replyRequested(noti); }); const QString& publicId = newId(); m_notifications[publicId] = noti; m_internalIdToPublicId[internalId] = publicId; QDBusConnection::sessionBus().registerObject(m_device->dbusPath()+"/notifications/"+publicId, noti, QDBusConnection::ExportScriptableContents); Q_EMIT notificationPosted(publicId); } void NotificationsDbusInterface::removeNotification(const QString& internalId) { //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId; if (!m_internalIdToPublicId.contains(internalId)) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by internal Id: " << internalId; return; } QString publicId = m_internalIdToPublicId.take(internalId); Notification* noti = m_notifications.take(publicId); if (!noti) { qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by public Id: " << publicId; return; } //Deleting the notification will unregister it automatically //QDBusConnection::sessionBus().unregisterObject(mDevice->dbusPath()+"/notifications/"+publicId); noti->deleteLater(); Q_EMIT notificationRemoved(publicId); } void NotificationsDbusInterface::dismissRequested(const QString& internalId) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REQUEST); np.set(QStringLiteral("cancel"), internalId); m_plugin->sendPacket(np); //Workaround: we erase notifications without waiting a 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(); dialog->raise(); } void NotificationsDbusInterface::sendReply(const QString& replyId, const QString& message) { NetworkPacket np(PACKET_TYPE_NOTIFICATION_REPLY); np.set(QStringLiteral("requestReplyId"), replyId); np.set(QStringLiteral("message"), message); m_plugin->sendPacket(np); } QString NotificationsDbusInterface::newId() { return QString::number(++m_lastId); } diff --git a/plugins/pausemusic/pausemusicplugin.cpp b/plugins/pausemusic/pausemusicplugin.cpp index 3e68bcf0..ece11317 100644 --- a/plugins/pausemusic/pausemusicplugin.cpp +++ b/plugins/pausemusic/pausemusicplugin.cpp @@ -1,114 +1,117 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "pausemusicplugin.h" #include #include #include #include #include #include #include #include +//In older Qt released, qAsConst isnt available +#include "qtcompat_p.h" + K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_pausemusic.json", registerPlugin< PauseMusicPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PAUSEMUSIC, "kdeconnect.plugin.pausemusic") PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , muted(false) {} bool PauseMusicPlugin::receivePacket(const NetworkPacket& np) { bool pauseOnlyWhenTalking = config()->get(QStringLiteral("conditionTalking"), false); if (pauseOnlyWhenTalking) { if (np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } else { //Pause as soon as it rings if (np.get(QStringLiteral("event")) != QLatin1String("ringing") && np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } bool pauseConditionFulfilled = !np.get(QStringLiteral("isCancel")); bool pause = config()->get(QStringLiteral("actionPause"), true); bool mute = config()->get(QStringLiteral("actionMute"), false); if (pauseConditionFulfilled) { if (mute) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Muting system volume"; QProcess::startDetached("pactl set-sink-mute @DEFAULT_SINK@ 1"); muted = true; } if (pause) { //Search for interfaces currently playing 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) { qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Unmuting system volume"; QProcess::startDetached("pactl set-sink-mute @DEFAULT_SINK@ 0"); muted = false; } if (pause && !pausedSources.empty()) { for (const QString& iface : qAsConst(pausedSources)) { QDBusInterface mprisInterface(iface, QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2.Player")); mprisInterface.asyncCall(QStringLiteral("PlayPause")); } pausedSources.clear(); } } return true; } #include "pausemusicplugin.moc" diff --git a/plugins/sendnotifications/CMakeLists.txt b/plugins/sendnotifications/CMakeLists.txt index a6d972d0..b8858ed4 100644 --- a/plugins/sendnotifications/CMakeLists.txt +++ b/plugins/sendnotifications/CMakeLists.txt @@ -1,37 +1,48 @@ +if (SAILFISHOS) + set(SN_PLUGIN_ENABLED_BY_DEFAULT "true") +else() + set(SN_PLUGIN_ENABLED_BY_DEFAULT "false") +endif() + +configure_file(kdeconnect_sendnotifications.json.in kdeconnect_sendnotifications.json) + set(kdeconnect_sendnotifications_SRCS sendnotificationsplugin.cpp notificationslistener.cpp notifyingapplication.cpp + kdeconnect_sendnotifications.json ) -kdeconnect_add_plugin(kdeconnect_sendnotifications JSON kdeconnect_sendnotifications.json SOURCES ${kdeconnect_sendnotifications_SRCS}) +kdeconnect_add_plugin(kdeconnect_sendnotifications JSON "${CMAKE_CURRENT_BINARY_DIR}/kdeconnect_sendnotifications.json" SOURCES ${kdeconnect_sendnotifications_SRCS}) target_link_libraries(kdeconnect_sendnotifications kdeconnectcore Qt5::DBus - KF5::Notifications + ${NOTIFICATION_LIB} KF5::I18n + Qt5::Gui KF5::IconThemes KF5::ConfigCore ) ####################################### # Config - +if (NOT SAILFISHOS) set( kdeconnect_sendnotifications_config_SRCS sendnotifications_config.cpp notifyingapplication.cpp notifyingapplicationmodel.cpp ) ki18n_wrap_ui( kdeconnect_sendnotifications_config_SRCS sendnotifications_config.ui ) add_library(kdeconnect_sendnotifications_config MODULE ${kdeconnect_sendnotifications_config_SRCS} ) target_link_libraries( kdeconnect_sendnotifications_config kdeconnectcore kdeconnectpluginkcm KF5::I18n KF5::KCMUtils ) install( TARGETS kdeconnect_sendnotifications_config DESTINATION ${PLUGIN_INSTALL_DIR} ) install( FILES kdeconnect_sendnotifications_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +endif() diff --git a/plugins/sendnotifications/kdeconnect_sendnotifications.json b/plugins/sendnotifications/kdeconnect_sendnotifications.json.in similarity index 99% rename from plugins/sendnotifications/kdeconnect_sendnotifications.json rename to plugins/sendnotifications/kdeconnect_sendnotifications.json.in index 7c01f178..cbbe3118 100644 --- a/plugins/sendnotifications/kdeconnect_sendnotifications.json +++ b/plugins/sendnotifications/kdeconnect_sendnotifications.json.in @@ -1,97 +1,97 @@ { "KPlugin": { "Authors": [ { "Email": "holger.k@elberer.de", "Name": "Holger Kaelberer", "Name[sr@ijekavian]": "Холгер Келберер", "Name[sr@ijekavianlatin]": "Holger Kelberer", "Name[sr@latin]": "Holger Kelberer", "Name[sr]": "Холгер Келберер", "Name[x-test]": "xxHolger Kaelbererxx" } ], "Description": "Broadcast this computer's notifications, so they can be shown on other devices.", "Description[ar]": "بُثّ إخطارات هذا الحاسوب، لتظهر على الأجهزة الأخرى.", "Description[ca@valencia]": "Transmet les notificacions d'aquest ordinador, perquè puguen mostrar-se en altres dispositius.", "Description[ca]": "Transmet les notificacions d'aquest ordinador, perquè puguin mostrar-se en altres dispositius.", "Description[cs]": "Posílat upozornění tohoto počítače, takže mohou být zobrazena na jiných zařízeních.", "Description[da]": "Udsend denne computers bekendtgørelser, så de kan vises på andre enheder.", "Description[de]": "Die Benachrichtigungen dieses Rechners aussenden, sodass sie auf anderen Geräten angezeigt werden können.", "Description[el]": "Μεταδώστε την ειδοποίηση αυτού του υπολογιστή, ώστε να εμφανιστεί σε άλλες συσκευές.", "Description[es]": "Difundir las notificaciones de este equipo, para que puedan mostrarse en otros dispositivos.", "Description[et]": "Arvuti märguannete levitamine, et neid oleks näha ka teistes seadmetes", "Description[eu]": "Hedatu ordenagailu honen jakinarazpenak, beste gailuetan erakutsi ahal izateko", "Description[fi]": "Lähetä tämän tietokoneen ilmoitukset, jotta ne voidaan näyttää muilla laitteilla.", "Description[fr]": "Diffuser les notifications de cet ordinateur pour qu'elles puissent être vues par d'autres périphériques.", "Description[gl]": "Emitir as notificacións deste computador para que se mostren noutros dispositivos.", "Description[id]": "Siarkan pemberitahuan komputer ini, sehingga ia tampil pada perangkat lain.", "Description[it]": "Trasmetti le notifiche di questo computer, affinché possano essere mostrate su altri dispositivi.", "Description[ko]": "이 컴퓨터의 알림을 다른 장치에도 표시합니다.", "Description[nl]": "De meldingen van deze computer rondsturen, zodat ze op andere apparaten getoond kunnen worden.", "Description[nn]": "Vidaresend varslingar frå datamaskina til andre einingar.", "Description[pl]": "Rozgłaszaj powiadomienia tego komputera, tak aby można je było wyświetlać na innych urządzeniach.", "Description[pt]": "Difunde as notificações deste computador, para que possam ser apresentadas noutros dispositivos.", "Description[pt_BR]": "Transmite as notificações deste computador, para que possam ser exibidas em outros dispositivos.", "Description[ru]": "Трансляция уведомления с этого компьютера, чтобы они могли быть видны на других устройствах", "Description[sk]": "Vysielať upozornenia tohto počítača, aby sa zobrazili na druhom zariadení.", "Description[sr@ijekavian]": "Одашиље обавештења са овог рачунара, да се приказују на осталим уређајима.", "Description[sr@ijekavianlatin]": "Odašilje obaveštenja sa ovog računara, da se prikazuju na ostalim uređajima.", "Description[sr@latin]": "Odašilje obaveštenja sa ovog računara, da se prikazuju na ostalim uređajima.", "Description[sr]": "Одашиље обавештења са овог рачунара, да се приказују на осталим уређајима.", "Description[sv]": "Sänd ut datorns underrättelser så att de kan visas på andra enheter.", "Description[tr]": "Bu bilgisayarın bildirimlerini yayınlayın, böylece diğer cihazlarda gösterilebilirler.", "Description[uk]": "Транслювати сповіщення з цього комп’ютера так, щоб їх було показано на інших пристроях.", "Description[x-test]": "xxBroadcast this computer's notifications, so they can be shown on other devices.xx", "Description[zh_CN]": "广播此计算机的通知,以被其他设备显示。", "Description[zh_TW]": "廣播此電腦的通知,這樣可以顯示在其他裝置上。", - "EnabledByDefault": false, + "EnabledByDefault": ${SN_PLUGIN_ENABLED_BY_DEFAULT}, "Icon": "preferences-desktop-notification", "Id": "kdeconnect_sendnotifications", "License": "GPL", "Name": "Send notifications", "Name[ar]": "أرسل إخطارات", "Name[ca@valencia]": "Envia les notificacions", "Name[ca]": "Envia les notificacions", "Name[cs]": "Posílat oznamování", "Name[da]": "Send bekendtgørelser", "Name[de]": "Benachrichtigungen senden", "Name[el]": "Αποστολή ειδοποιήσεων", "Name[es]": "Enviar notificaciones", "Name[et]": "Märguannete saatmine", "Name[eu]": "Bidali jakinarazpenak", "Name[fi]": "Lähetä ilmoituksia", "Name[fr]": "Envoyer les notifications", "Name[gl]": "Enviar notificacións", "Name[it]": "Invia notifiche", "Name[ko]": "알림 보내기", "Name[nl]": "Stuur meldingen", "Name[nn]": "Send varslingar", "Name[pl]": "Wysyłaj powiadomienia", "Name[pt]": "Enviar as notificações", "Name[pt_BR]": "Enviar notificações", "Name[ru]": "Отправка уведомлений", "Name[sk]": "Posielať upozornenia", "Name[sr@ijekavian]": "Шаљи обавештења", "Name[sr@ijekavianlatin]": "Šalji obaveštenja", "Name[sr@latin]": "Šalji obaveštenja", "Name[sr]": "Шаљи обавештења", "Name[sv]": "Skicka underrättelser", "Name[tr]": "Bildirim gönder", "Name[uk]": "Надіслати сповіщення", "Name[x-test]": "xxSend notificationsxx", "Name[zh_CN]": "发送通知", "Name[zh_TW]": "傳送通知", "ServiceTypes": [ "KdeConnect/Plugin" ], "Version": "0.1", "Website": "http://albertvaka.wordpress.com" }, "X-KdeConnect-OutgoingPacketType": [ "kdeconnect.notification" ], "X-KdeConnect-SupportedPacketType": [ "kdeconnect.notification.request" ] } diff --git a/plugins/sendnotifications/notificationslistener.cpp b/plugins/sendnotifications/notificationslistener.cpp index c69c20ca..235a5d0a 100644 --- a/plugins/sendnotifications/notificationslistener.cpp +++ b/plugins/sendnotifications/notificationslistener.cpp @@ -1,274 +1,277 @@ /** * 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" +//In older Qt released, qAsConst isnt available +#include "qtcompat_p.h" + NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin) : QDBusAbstractAdaptor(aPlugin), m_plugin(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" << m_plugin->device()->name() << ":" << QDBusConnection::sessionBus().lastError(); else qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Registered notifications listener for device" << m_plugin->device()->name(); QDBusInterface iface(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus")); iface.call(QStringLiteral("AddMatch"), "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'"); setTranslatedAppName(); loadApplications(); connect(m_plugin->config(), &KdeConnectPluginConfig::configChanged, this, &NotificationsListener::loadApplications); } NotificationsListener::~NotificationsListener() { qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Destroying NotificationsListener"; QDBusInterface iface(QStringLiteral("org.freedesktop.DBus"), QStringLiteral("/org/freedesktop/DBus"), QStringLiteral("org.freedesktop.DBus")); QDBusMessage res = iface.call(QStringLiteral("RemoveMatch"), "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."; m_translatedAppName = QStringLiteral("KDE Connect"); return; } KConfig config(filePath, KConfig::OpenFlag::SimpleConfig); KConfigGroup globalgroup(&config, QStringLiteral("Global")); m_translatedAppName = globalgroup.readEntry(QStringLiteral("Name"), QStringLiteral("KDE Connect")); } void NotificationsListener::loadApplications() { m_applications.clear(); const QVariantList list = m_plugin->config()->getList(QStringLiteral("applications")); for (const auto& a : list) { NotifyingApplication app = a.value(); if (!m_applications.contains(app.name)) m_applications.insert(app.name, app); } //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Loaded" << applications.size() << " applications"; } bool NotificationsListener::parseImageDataArgument(const QVariant& argument, int& width, int& height, int& rowStride, int& bitsPerSample, int& channels, bool& hasAlpha, QByteArray& imageData) const { if (!argument.canConvert()) return false; const QDBusArgument dbusArg = argument.value(); dbusArg.beginStructure(); dbusArg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> imageData; dbusArg.endStructure(); return true; } QSharedPointer NotificationsListener::iconForImageData(const QVariant& argument) const { int width, height, rowStride, bitsPerSample, channels; bool hasAlpha; QByteArray imageData; if (!parseImageDataArgument(argument, width, height, rowStride, bitsPerSample, channels, hasAlpha, imageData)) return QSharedPointer(); if (bitsPerSample != 8) { qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Unsupported image format:" << "width=" << width << "height=" << height << "rowStride=" << rowStride << "bitsPerSample=" << bitsPerSample << "channels=" << channels << "hasAlpha=" << hasAlpha; return QSharedPointer(); } QImage image(reinterpret_cast(imageData.data()), width, height, rowStride, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32); if (hasAlpha) image = image.rgbSwapped(); // RGBA --> ARGB QSharedPointer buffer = QSharedPointer(new QBuffer); if (!buffer || !buffer->open(QIODevice::WriteOnly) || !image.save(buffer.data(), "PNG")) { qCWarning(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Could not initialize image buffer"; return QSharedPointer(); } return buffer; } QSharedPointer NotificationsListener::iconForIconName(const QString& iconName) const { int size = KIconLoader::SizeEnormous; // use big size to allow for good // quality on high-DPI mobile devices QString iconPath = KIconLoader::global()->iconPath(iconName, -size, true); if (!iconPath.isEmpty()) { if (!iconPath.endsWith(QLatin1String(".png")) && KIconLoader::global()->theme()->name() != QLatin1String("hicolor")) { // try falling back to hicolor theme: KIconTheme hicolor(QStringLiteral("hicolor")); if (hicolor.isValid()) { iconPath = hicolor.iconPath(iconName + ".png", size, KIconLoader::MatchBest); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Found non-png icon in default theme trying fallback to hicolor:" << iconPath; } } } if (iconPath.endsWith(QLatin1String(".png"))) return QSharedPointer(new QFile(iconPath)); return QSharedPointer(); } + uint NotificationsListener::Notify(const QString& appName, uint replacesId, const QString& appIcon, const QString& summary, const QString& body, const QStringList& actions, const QVariantMap& hints, int timeout) { static int id = 0; Q_UNUSED(actions); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Got notification appName=" << appName << "replacesId=" << replacesId << "appIcon=" << appIcon << "summary=" << summary << "body=" << body << "actions=" << actions << "hints=" << hints << "timeout=" << timeout; // skip our own notifications if (appName == m_translatedAppName) return 0; NotifyingApplication app; if (!m_applications.contains(appName)) { // new application -> add to config app.name = appName; app.icon = appIcon; app.active = true; app.blacklistExpression = QRegularExpression(); m_applications.insert(app.name, app); // update config: QVariantList list; for (const auto& a : qAsConst(m_applications)) list << QVariant::fromValue(a); m_plugin->config()->setList(QStringLiteral("applications"), list); //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Added new application to config:" << app; } else app = m_applications.value(appName); if (!app.active) return 0; if (timeout > 0 && m_plugin->config()->get(QStringLiteral("generalPersistent"), false)) return 0; int urgency = -1; if (hints.contains(QStringLiteral("urgency"))) { bool ok; urgency = hints[QStringLiteral("urgency")].toInt(&ok); if (!ok) urgency = -1; } if (urgency > -1 && urgency < m_plugin->config()->get(QStringLiteral("generalUrgency"), 0)) return 0; QString ticker = summary; if (!body.isEmpty() && m_plugin->config()->get(QStringLiteral("generalIncludeBody"), true)) ticker += QStringLiteral(": ") + body; if (app.blacklistExpression.isValid() && !app.blacklistExpression.pattern().isEmpty() && app.blacklistExpression.match(ticker).hasMatch()) return 0; //qCDebug(KDECONNECT_PLUGIN_SENDNOTIFICATION) << "Sending notification from" << appName << ":" < 0 ? replacesId : ++id)}, {"appName", appName}, {"ticker", ticker}, {"isClearable", timeout == 0} }); // KNotifications are persistent if // timeout == 0, for other notifications // clearability is pointless // sync any icon data? if (m_plugin->config()->get(QStringLiteral("generalSynchronizeIcons"), true)) { QSharedPointer iconSource; // try different image sources according to priorities in notifications- // spec version 1.2: if (hints.contains(QStringLiteral("image-data"))) iconSource = iconForImageData(hints[QStringLiteral("image-data")]); else if (hints.contains(QStringLiteral("image_data"))) // 1.1 backward compatibility iconSource = iconForImageData(hints[QStringLiteral("image_data")]); else if (hints.contains(QStringLiteral("image-path"))) iconSource = iconForIconName(hints[QStringLiteral("image-path")].toString()); else if (hints.contains(QStringLiteral("image_path"))) // 1.1 backward compatibility iconSource = iconForIconName(hints[QStringLiteral("image_path")].toString()); else if (!appIcon.isEmpty()) iconSource = iconForIconName(appIcon); else if (hints.contains(QStringLiteral("icon_data"))) // < 1.1 backward compatibility iconSource = iconForImageData(hints[QStringLiteral("icon_data")]); if (iconSource) np.setPayload(iconSource, iconSource->size()); } m_plugin->sendPacket(np); return (replacesId > 0 ? replacesId : id); } diff --git a/sfos/CMakeLists.txt b/sfos/CMakeLists.txt new file mode 100644 index 00000000..9ab670da --- /dev/null +++ b/sfos/CMakeLists.txt @@ -0,0 +1,26 @@ +find_package(Qt5 5.2 REQUIRED COMPONENTS DBus) + +pkg_check_modules(NNQT5 REQUIRED nemonotifications-qt5) +include_directories(${NNQT5_INCLUDE_DIRS}) + +set(kdeconnectsfos_SRCS + kdeconnect-sfos.cpp +) + +add_executable(kdeconnect-sfos ${kdeconnectsfos_SRCS}) +target_link_libraries(kdeconnect-sfos Qt5::Quick sailfishapp) + +install(TARGETS kdeconnect-sfos ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(PROGRAMS kdeconnect-sfos.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +INSTALL( DIRECTORY qml DESTINATION ${SHARE_INSTALL_PREFIX}/kdeconnect-sfos/ ) + +#Daemon +add_executable(kdeconnectd sailfishdaemon.cpp) +target_link_libraries(kdeconnectd kdeconnectcore KF5::DBusAddons ${NNQT5_LIBRARIES} KF5::I18n) + +configure_file(kdeconnectd.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectd.desktop) +configure_file(org.kde.kdeconnect.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectd.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kdeconnect.service DESTINATION ${DBUS_SERVICES_INSTALL_DIR}) +install(TARGETS kdeconnectd DESTINATION ${LIBEXEC_INSTALL_DIR}) diff --git a/sfos/kdeconnect-sfos.cpp b/sfos/kdeconnect-sfos.cpp new file mode 100644 index 00000000..5e4e9de0 --- /dev/null +++ b/sfos/kdeconnect-sfos.cpp @@ -0,0 +1,60 @@ +/* + Copyright (C) 2013 Jolla Ltd. + Contact: Thomas Perl + All rights reserved. + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Jolla Ltd nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef QT_QML_DEBUG +#include +#endif + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + // SailfishApp::main() will display "qml/kdeconnect-sfos.qml", if you need more + // control over initialization, you can use: + // + // - SailfishApp::application(int, char *[]) to get the QGuiApplication * + // - SailfishApp::createView() to get a new QQuickView * instance + // - SailfishApp::pathTo(QString) to get a QUrl to a resource file + // - SailfishApp::pathToMainQml() to get a QUrl to the main QML file + // + // To display the view, call "show()" (will show fullscreen on device). + + //return SailfishApp::main(argc, argv); + QGuiApplication *app = SailfishApp::application(argc, argv); + QScopedPointer view(SailfishApp::createView()); + + view->setSource(SailfishApp::pathTo("qml/kdeconnect-sfos.qml")); + view->showFullScreen(); + + return app->exec(); +} + diff --git a/sfos/kdeconnect-sfos.desktop b/sfos/kdeconnect-sfos.desktop new file mode 100644 index 00000000..7ba1133d --- /dev/null +++ b/sfos/kdeconnect-sfos.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +X-Nemo-Application-Type=generic +Icon=kdeconnect +Exec=kdeconnect-sfos +Name=KDE Connect +# translation example: +# your app name in German locale (de) +# +# Remember to comment out the following line, if you do not want to use +# a different app name in German locale (de). +#Name[de]=kdeconnect-sfos diff --git a/sfos/kdeconnectd.desktop.cmake b/sfos/kdeconnectd.desktop.cmake new file mode 100644 index 00000000..ab93439c --- /dev/null +++ b/sfos/kdeconnectd.desktop.cmake @@ -0,0 +1,47 @@ +[Desktop Entry] +Type=Application +Exec=${KDE_INSTALL_FULL_LIBEXECDIR}/kdeconnectd +X-KDE-StartupNotify=false +X-KDE-autostart-phase=1 +X-GNOME-Autostart-enabled=true +OnlyShowIn=KDE;GNOME;Unity;XFCE; +NoDisplay=true + +Name=KDEConnect daemon +Name[ar]=عفريت KDEConnect +Name[ast]=Degorriu KDEConnect +Name[bg]=Услуга KDE Connect +Name[ca]=Dimoni del KDEConnect +Name[ca@valencia]=Dimoni del KDEConnect +Name[cs]=Démon KDE Connect +Name[da]=KDEConnect-dæmon +Name[de]=KDE-Connect-Dienst +Name[el]=Δαίμονας του KDEConnect +Name[en_GB]=KDEConnect daemon +Name[es]=Demonio de KDE Connect +Name[et]=KDEConnecti deemon +Name[eu]=KDEConnect daimona +Name[fi]=KDEConnect-taustapalvelu +Name[fr]=Démon KDE Connect +Name[gl]=Servizo de KDE Connect +Name[he]=דמון KDEConnect +Name[hu]=KDEConnect szolgáltatás +Name[it]=Demone KDE Connect +Name[ko]=KDE Connect 데몬 +Name[nl]=KDEConnect-daemon +Name[nn]=KDEConnect-teneste +Name[pl]=Usługa KDEConnect +Name[pt]=Serviço do KDE Connect +Name[pt_BR]=Serviço do KDE Connect +Name[ru]=Служба KDE Connect +Name[sk]=KDEConnect démon +Name[sr]=КДЕ‑конекцијин демон +Name[sr@ijekavian]=КДЕ‑конекцијин демон +Name[sr@ijekavianlatin]=KDE‑konekcijin demon +Name[sr@latin]=KDE‑konekcijin demon +Name[sv]=Demon för KDE-anslut +Name[tr]=KDEConnect süreci +Name[uk]=Фонова служба KDEConnect +Name[x-test]=xxKDEConnect daemonxx +Name[zh_CN]=KDEConnect 守护进程 +Name[zh_TW]=KDE連線作業 diff --git a/sfos/org.kde.kdeconnect.service.in b/sfos/org.kde.kdeconnect.service.in new file mode 100644 index 00000000..4f747186 --- /dev/null +++ b/sfos/org.kde.kdeconnect.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.kde.kdeconnect +Exec=@KDE_INSTALL_FULL_LIBEXECDIR@/kdeconnectd diff --git a/sfos/qml/cover/CoverPage.qml b/sfos/qml/cover/CoverPage.qml new file mode 100644 index 00000000..a252225f --- /dev/null +++ b/sfos/qml/cover/CoverPage.qml @@ -0,0 +1,74 @@ +/* + Copyright (C) 2013 Jolla Ltd. + Contact: Thomas Perl + All rights reserved. + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Jolla Ltd nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 + +CoverBackground { + Label { + id: label + anchors.top: parent.top + anchors.topMargin: Theme.paddingMedium + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("KDE Connect") + } + + SilicaListView { + id: devices + anchors.top: label.bottom + anchors.margins: Theme.paddingSmall + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + visible: count > 0 + + model: DevicesSortProxyModel { + sourceModel: DevicesModel { displayFilter: DevicesModel.Reachable | DevicesModel.Paired } + } + + width: parent.width + spacing: Theme.paddingLarge + + delegate: ListItem { + width: ListView.view.width + height: Theme.itemSizeMedium + + Label { text: display } + } + } + Label { + text: qsTr("No paired \ndevices in range") + anchors.centerIn: parent + visible: devices.count == 0 + } + +} + diff --git a/sfos/qml/kdeconnect-sfos.qml b/sfos/qml/kdeconnect-sfos.qml new file mode 100644 index 00000000..b8a1cf7c --- /dev/null +++ b/sfos/qml/kdeconnect-sfos.qml @@ -0,0 +1,48 @@ +/* + Copyright (C) 2013 Jolla Ltd. + Contact: Thomas Perl + All rights reserved. + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Jolla Ltd nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import "pages" + +ApplicationWindow +{ + initialPage: Component { FirstPage { } } + cover: Qt.resolvedUrl("cover/CoverPage.qml") + allowedOrientations: defaultAllowedOrientations + + Keys.onPressed: { + if (event.key == Qt.Key_Back) { + console.log("back"); + pageStack.pop(); + } + } +} + diff --git a/sfos/qml/pages/DevicePage.qml b/sfos/qml/pages/DevicePage.qml new file mode 100644 index 00000000..4e5d4c98 --- /dev/null +++ b/sfos/qml/pages/DevicePage.qml @@ -0,0 +1,105 @@ +/* + Copyright (C) 2013 Jolla Ltd. + Contact: Thomas Perl + All rights reserved. + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Jolla Ltd nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 + +Page { + id: deviceView + property QtObject currentDevice + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.Portrait + + Column { + id: column + anchors.fill: parent + spacing: Theme.paddingMedium + + PageHeader { + title: currentDevice.name + } + + Label { + anchors.horizontalCenter: parent.horizontalCenter + text: "Device is " + (currentDevice.isTrusted ? "trusted" : "not trusted") + } + + Button { + id: text + anchors.horizontalCenter: parent.horizontalCenter + text: currentDevice.isTrusted ? "Un-Pair" : "Pair" + + onClicked: { + if (currentDevice.isTrusted) { + currentDevice.unpair() + } else { + currentDevice.requestPair() + } + } + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + visible: currentDevice.isTrusted + text: qsTr("Ping") + onClicked: { + currentDevice.pluginCall("ping", "sendPing"); + } + } + + PluginItem { + anchors.horizontalCenter: parent.horizontalCenter + text: ("Multimedia control") + interfaceFactory: MprisDbusInterfaceFactory + component: "mpris.qml" + pluginName: "mprisremote" + } + PluginItem { + anchors.horizontalCenter: parent.horizontalCenter + text: ("Remote input") + interfaceFactory: RemoteControlDbusInterfaceFactory + component: "mousepad.qml" + pluginName: "remotecontrol" + } + PluginItem { + anchors.horizontalCenter: parent.horizontalCenter + readonly property var lockIface: LockDeviceDbusInterfaceFactory.create(deviceView.currentDevice.id()) + pluginName: "lockdevice" + text: lockIface.isLocked ? ("Unlock") : ("Lock") + onClicked: { + lockIface.isLocked = !lockIface.isLocked; + } + } + } +} + + diff --git a/sfos/qml/pages/FindDevices.qml b/sfos/qml/pages/FindDevices.qml new file mode 100644 index 00000000..acceb8c4 --- /dev/null +++ b/sfos/qml/pages/FindDevices.qml @@ -0,0 +1,77 @@ +/* + Copyright (C) 2013 Jolla Ltd. + Contact: Thomas Perl + All rights reserved. + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Jolla Ltd nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 + +Page { + id: page + + // The effective value will be restricted by ApplicationWindow.allowedOrientations + allowedOrientations: Orientation.Portrait + + // To enable PullDownMenu, place our content in a SilicaFlickable + SilicaListView { + anchors.fill: parent + + header: PageHeader { + title: qsTr("Devices") + } + + // Place our content in a Column. The PageHeader is always placed at the top + // of the page, followed by our content. + + id: devices + model: DevicesModel { + id: devicesModel + } + + width: page.width + spacing: Theme.paddingLarge + + + delegate: ListItem { + width: ListView.view.width + height: Theme.itemSizeMedium + + Label { text: display + "\n" + toolTip } + + onClicked: { + var devicePage = pageStack.push(Qt.resolvedUrl("DevicePage.qml"), + {currentDevice: device} + ); + } + + + } + } +} + diff --git a/sfos/qml/pages/FirstPage.qml b/sfos/qml/pages/FirstPage.qml new file mode 100644 index 00000000..fb0624e0 --- /dev/null +++ b/sfos/qml/pages/FirstPage.qml @@ -0,0 +1,61 @@ +/* +Copyright (C) 2013 Jolla Ltd. +Contact: Thomas Perl +All rights reserved. + +You may use this file under the terms of BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Jolla Ltd nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 +import Nemo.Notifications 1.0 + +Page { + allowedOrientations: Orientation.Portrait + + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + + // PullDownMenu and PushUpMenu must be declared in SilicaFlickable, SilicaListView or SilicaGridView + PullDownMenu { + MenuItem { + text: qsTr("Find Devices") + onClicked: pageStack.push(Qt.resolvedUrl("FindDevices.qml")) + } + } + + Column { + id: column + width: parent.width + spacing: 20 + + PageHeader { title: "KDE Connect" } + + } + } +} + diff --git a/sfos/qml/pages/PluginItem.qml b/sfos/qml/pages/PluginItem.qml new file mode 100644 index 00000000..fcee5fd1 --- /dev/null +++ b/sfos/qml/pages/PluginItem.qml @@ -0,0 +1,47 @@ +/* + * 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 . + */ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 + +Button +{ + property alias pluginName: checker.pluginName + property var interfaceFactory + property string component + + readonly property var checker: PluginChecker { + id: checker + device: deviceView.currentDevice + } + visible: checker.available + onClicked: { + if (component === "") + return; + + var obj = interfaceFactory.create(deviceView.currentDevice.id()); + var page = pageStack.push( + component, + { pluginInterface: obj } + ); + obj.parent = page + } +} diff --git a/sfos/qml/pages/mousepad.qml b/sfos/qml/pages/mousepad.qml new file mode 100644 index 00000000..f6c43dd4 --- /dev/null +++ b/sfos/qml/pages/mousepad.qml @@ -0,0 +1,104 @@ +/* + * 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 . + */ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 + +Page +{ + id: mousepad + property QtObject pluginInterface + backNavigation: false + + Column + { + anchors.fill: parent + PageHeader { + id: header + title: "Remote Control" + } + MouseArea { + id: area + width: parent.width + height: parent.height - buttons.height - header.height - 20 + property var lastPos: Qt.point(-1, -1) + + //onClicked: mousepad.pluginInterface.sendCommand("singleclick", true); + + onPositionChanged: { + if (lastPos.x > -1) { + //console.log("move", mouse.x, mouse.y, lastPos) + var delta = Qt.point(mouse.x-lastPos.x, mouse.y-lastPos.y); + + pluginInterface.moveCursor(delta); + } + lastPos = Qt.point(mouse.x, mouse.y); + } + onReleased: { + lastPos = Qt.point(-1, -1) + } + } + Row { + id: buttons + height: childrenRect.height + width: parent.width + + Button { + width: parent.width / 3 + text: "Single" + onClicked: mousepad.pluginInterface.sendCommand("singleclick", true); + } + Button { + width: parent.width / 3 + text: "Middle" + onClicked: mousepad.pluginInterface.sendCommand("middleclick", true); + } + Button { + width: parent.width / 3 + text: "Right" + onClicked: mousepad.pluginInterface.sendCommand("rightclick", true); + } + } + } + + function myPop() { + pageStack._pageStackIndicator._backPageIndicator().data[0].clicked.disconnect(myPop) + pageStack.pop() + } + + onStatusChanged: { + if (status == PageStatus.Active) { + pageStack._createPageIndicator() + pageStack._pageStackIndicator.clickablePageIndicators = true + pageStack._pageStackIndicator._backPageIndicator().backNavigation = true + pageStack._pageStackIndicator._backPageIndicator().data[0].clicked.connect(myPop) + } else if (status == PageStatus.Deactivating) { + pageStack._pageStackIndicator.clickablePageIndicators = Qt.binding(function() { + return pageStack.currentPage ? pageStack.currentPage._clickablePageIndicators : true + }) + pageStack._pageStackIndicator._backPageIndicator().backNavigation = Qt.binding(function() { + return pageStack._currentContainer && pageStack._currentContainer.page + && pageStack._currentContainer.page.backNavigation && pageStack._currentContainer.pageStackIndex !== 0 + }) + } +} + +} diff --git a/sfos/qml/pages/mpris.qml b/sfos/qml/pages/mpris.qml new file mode 100644 index 00000000..1b0c7268 --- /dev/null +++ b/sfos/qml/pages/mpris.qml @@ -0,0 +1,82 @@ +/* + * 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 . + */ + +import QtQuick 2.0 +import Sailfish.Silica 1.0 +import org.kde.kdeconnect 1.0 + +Page +{ + id: root + property QtObject pluginInterface + + Column + { + anchors.fill: parent + PageHeader { title: "Multimedia Controls" } + + Component.onCompleted: { + pluginInterface.requestPlayerList(); + } + + Item { height: parent.height } + ComboBox { + label: "Player" + width: parent.width + onCurrentIndexChanged: root.pluginInterface.player = value + + menu: ContextMenu { + Repeater { + model: root.pluginInterface.playerList + MenuItem { text: modelData } + } + } + } + Label { + width: parent.width + text: root.pluginInterface.nowPlaying + } + Row { + width: parent.width + IconButton { + icon.source: "image://theme/icon-m-previous" + onClicked: root.pluginInterface.sendAction("Previous") + } + IconButton { + icon.source: root.pluginInterface.isPlaying ? "icon-m-image://theme/pause" : "image://theme/icon-m-play" + onClicked: root.pluginInterface.sendAction("PlayPause"); + } + IconButton { + icon.source: "image://theme/icon-m-next" + onClicked: root.pluginInterface.sendAction("Next") + } + } + Row { + width: parent.width + Label { text: ("Volume:") } + Slider { + value: root.pluginInterface.volume + maximumValue: 100 + width: parent.width + } + } + Item { height: parent.height } + } +} diff --git a/sfos/rpm/kdeconnect-sfos.changes.in b/sfos/rpm/kdeconnect-sfos.changes.in new file mode 100644 index 00000000..60e402b1 --- /dev/null +++ b/sfos/rpm/kdeconnect-sfos.changes.in @@ -0,0 +1,18 @@ +# Rename this file as kdeconnect-sfos.changes to include changelog +# entries in your RPM file. +# +# Add new changelog entries following the format below. +# Add newest entries to the top of the list. +# Separate entries from eachother with a blank line. +# +# Alternatively, if your changelog is automatically generated (e.g. with +# the git-change-log command provided with Sailfish OS SDK), create a +# kdeconnect-sfos.changes.run script to let mb2 run the required commands for you. + +# * date Author's Name version-release +# - Summary of changes + +* Sun Apr 13 2014 Jack Tar 0.0.1-1 +- Scrubbed the deck +- Hoisted the sails + diff --git a/sfos/rpm/kdeconnect-sfos.changes.run.in b/sfos/rpm/kdeconnect-sfos.changes.run.in new file mode 100644 index 00000000..90c6facd --- /dev/null +++ b/sfos/rpm/kdeconnect-sfos.changes.run.in @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Rename this file as kdeconnect-sfos.changes.run to let mb2 automatically +# generate changelog from well formatted Git commit messages and tag +# annotations. + +git-change-log + +# Here are some basic examples how to change from the default behavior. Run +# git-change-log --help inside the Sailfish OS SDK chroot or build engine to +# learn all the options git-change-log accepts. + +# Use a subset of tags +#git-change-log --tags refs/tags/my-prefix/* + +# Group entries by minor revision, suppress headlines for patch-level revisions +#git-change-log --dense '/[0-9]\+\.[0-9\+$' + +# Trim very old changes +#git-change-log --since 2014-04-01 +#echo '[ Some changelog entries trimmed for brevity ]' + +# Use the subjects (first lines) of tag annotations when no entry would be +# included for a revision otherwise +#git-change-log --auto-add-annotations diff --git a/sfos/rpm/kdeconnect-sfos.spec b/sfos/rpm/kdeconnect-sfos.spec new file mode 100644 index 00000000..e8a880c0 --- /dev/null +++ b/sfos/rpm/kdeconnect-sfos.spec @@ -0,0 +1,85 @@ +# +# Do NOT Edit the Auto-generated Part! +# Generated by: spectacle version 0.27 +# + +Name: kdeconnect-sfos + +# >> macros +# << macros + +Summary: KDEConnect client for Sailfish +Version: 0.1 +Release: 1 +Group: Qt/Qt +License: LICENSE +URL: http://example.org/ +Source0: %{name}-%{version}.tar.bz2 +Source100: kdeconnect-sfos.yaml +Requires: sailfishsilica-qt5 >= 0.10.9 +BuildRequires: pkgconfig(sailfishapp) >= 1.0.2 +BuildRequires: pkgconfig(Qt5Core) +BuildRequires: pkgconfig(Qt5Qml) +BuildRequires: pkgconfig(Qt5Quick) +BuildRequires: pkgconfig(nemonotifications-qt5) +BuildRequires: pkgconfig(qca2-qt5) >= 2.0.0 +BuildRequires: desktop-file-utils +BuildRequires: cmake >= 3.0 +BuildRequires: extra-cmake-modules >= 5.31.0 +BuildRequires: kcoreaddons-devel >= 5.31.0 +BuildRequires: kdbusaddons-devel >= 5.31.0 +BuildRequires: ki18n-devel >= 5.31.0 +BuildRequires: kconfig-devel >= 5.31.0 +BuildRequires: kiconthemes-devel >= 5.31.0 + +%description +Short description of my Sailfish OS Application + + +%prep +%setup -q + +# >> setup +# << setup + +%build +# >> build pre +# << build pre +mkdir -p build +cd build +%cmake .. -DSAILFISHOS=YES +make %{?jobs:-j%jobs} + +# >> build post +# << build post + +%install +rm -rf %{buildroot} +# >> install pre +# << install pre +pushd build +%make_install +popd + + +# >> install post +# << install post + +desktop-file-install --delete-original \ + --dir %{buildroot}%{_datadir}/applications \ + %{buildroot}%{_datadir}/applications/*.desktop + +%files +%defattr(-,root,root,-) +%{_bindir} +%{_libdir} +%{_datadir}/%{name} +%{_datadir}/applications/%{name}.desktop +/etc/xdg/autostart/kdeconnectd.desktop +/usr/share/dbus-1/services/org.kde.kdeconnect.service +/usr/share/knotifications5/kdeconnect.notifyrc +/usr/share/kservicetypes5/kdeconnect_plugin.desktop +#%{_datadir}/icons/hicolor/*/apps/%{name}.png +/usr/share/icons/ +# >> files +# << files diff --git a/sfos/sailfishdaemon.cpp b/sfos/sailfishdaemon.cpp new file mode 100644 index 00000000..da25896c --- /dev/null +++ b/sfos/sailfishdaemon.cpp @@ -0,0 +1,116 @@ +/** + * Copyright 2018 Adam Pigg + * + * 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 "core/daemon.h" +#include "core/device.h" +#include "core/backends/pairinghandler.h" +#include "kdeconnect-version.h" + +#include + +class SailfishDaemon : public Daemon +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") +public: + SailfishDaemon(QObject* parent = Q_NULLPTR) + : Daemon(parent) + , m_nam(Q_NULLPTR) + {} + + void askPairingConfirmation(Device* device) override + { + qDebug() << "Pairing request from " << device->name().toHtmlEscaped(); + + Notification *notification = new Notification(this); + + notification->setAppName(QCoreApplication::applicationName()); + notification->setPreviewSummary(i18n("Pairing request from %1", device->name().toHtmlEscaped())); + notification->setPreviewBody(i18n("Click here to pair")); + notification->setIcon("icon-s-sync"); + notification->setExpireTimeout(10000); + + connect(notification, &Notification::closed, + [=]( uint reason ) { + qDebug() << "Notification closed" << reason; + if (reason == 2) { //clicked + device->acceptPairing(); + } else { + device->rejectPairing(); + } + }); + + notification->publish(); + } + + void reportError(const QString & title, const QString & description) override + { + qDebug() << "Error: " << title << ":" << description; + } + + QNetworkAccessManager* networkAccessManager() override + { + if (!m_nam) { + m_nam = new QNetworkAccessManager(this); + } + return m_nam; + } + + void sendSimpleNotification(const QString &eventId, const QString &title, const QString &text, const QString &iconName) override + { + Q_UNUSED(eventId); + Notification *notification = new Notification(this); + + notification->setAppName(QCoreApplication::applicationName()); + notification->setPreviewSummary(title); + notification->setPreviewBody(text); + notification->setIcon(iconName); + notification->publish(); + } + + +private: + QNetworkAccessManager* m_nam; +}; + +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + + app.setApplicationName(QStringLiteral("kdeconnectd")); + app.setApplicationVersion(QStringLiteral(KDECONNECT_VERSION_STRING)); + app.setOrganizationDomain(QStringLiteral("kde.org")); + + KDBusService dbusService(KDBusService::Unique); + + Daemon* daemon = new SailfishDaemon; + + QObject::connect(daemon, SIGNAL(destroyed(QObject*)), &app, SLOT(quit())); + + return app.exec(); +} + +#include "sailfishdaemon.moc"