diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 28ccc91bb..9ae8f2747 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -3,6 +3,7 @@ set(kded_kdeconnect_SRCS linkproviders/linkprovider.cpp linkproviders/loopbacklinkprovider.cpp linkproviders/avahitcplinkprovider.cpp + linkproviders/broadcasttcplinkprovider.cpp devicelinks/devicelink.cpp devicelinks/echodevicelink.cpp diff --git a/daemon/daemon.cpp b/daemon/daemon.cpp index d8fa935c7..31308a2b8 100644 --- a/daemon/daemon.cpp +++ b/daemon/daemon.cpp @@ -29,27 +29,25 @@ #include "packageinterfaces/batterypackageinterface.h" #include "packageinterfaces/mpriscontrolpackageinterface.h" +#include "linkproviders/broadcasttcplinkprovider.h" #include "linkproviders/avahitcplinkprovider.h" #include "linkproviders/loopbacklinkprovider.h" -#include -#include -#include +#include #include +#include +#include #include #include -#include -#include -#include - K_PLUGIN_FACTORY(KdeConnectFactory, registerPlugin();) K_EXPORT_PLUGIN(KdeConnectFactory("kdeconnect", "kdeconnect")) Daemon::Daemon(QObject *parent, const QList&) : KDEDModule(parent) { + KSharedConfigPtr config = KSharedConfig::openConfig("kdeconnectrc"); if (!config->group("myself").hasKey("id")) { @@ -70,7 +68,8 @@ Daemon::Daemon(QObject *parent, const QList&) //TODO: Do not hardcode the load of the device locators //use: https://techbase.kde.org/Development/Tutorials/Services/Plugins - mLinkProviders.insert(new AvahiTcpLinkProvider()); + mLinkProviders.insert(new BroadcastTcpLinkProvider()); + //mLinkProviders.insert(new AvahiTcpLinkProvider()); //mLinkProviders.insert(new LoopbackLinkProvider()); //Read remebered paired devices @@ -90,8 +89,12 @@ Daemon::Daemon(QObject *parent, const QList&) } } + QNetworkSession* network = new QNetworkSession(QNetworkConfigurationManager().defaultConfiguration()); + //Listen to incomming connections Q_FOREACH (LinkProvider* a, mLinkProviders) { + connect(network, SIGNAL(stateChanged(QNetworkSession::State)), + a, SLOT(onNetworkChange(QNetworkSession::State))); connect(a,SIGNAL(onNewDeviceLink(NetworkPackage,DeviceLink*)), this,SLOT(onNewDeviceLink(NetworkPackage,DeviceLink*))); } @@ -105,10 +108,19 @@ void Daemon::setDiscoveryEnabled(bool b) { //Listen to incomming connections Q_FOREACH (LinkProvider* a, mLinkProviders) { - a->setDiscoverable(b); + if (b) + a->onStart(); + else + a->onStop(); } } +void Daemon::forceOnNetworkChange() +{ + Q_FOREACH (LinkProvider* a, mLinkProviders) { + a->onNetworkChange(QNetworkSession::Connected); + } +} QStringList Daemon::devices() { diff --git a/daemon/daemon.h b/daemon/daemon.h index 9228e2e51..1077ccfcb 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -60,6 +60,8 @@ public Q_SLOTS: //After calling this, signal deviceDiscovered will be triggered for each device Q_SCRIPTABLE void setDiscoveryEnabled(bool b); + Q_SCRIPTABLE void forceOnNetworkChange(); + //Returns a list of ids. The respective devices can be manipulated using the dbus path: "/modules/kdeconnect/Devices/"+id Q_SCRIPTABLE QStringList devices(); diff --git a/daemon/devicelinks/tcpdevicelink.cpp b/daemon/devicelinks/tcpdevicelink.cpp index 16a558e5e..29d07595d 100644 --- a/daemon/devicelinks/tcpdevicelink.cpp +++ b/daemon/devicelinks/tcpdevicelink.cpp @@ -19,9 +19,9 @@ */ #include "tcpdevicelink.h" -#include "linkproviders/avahitcplinkprovider.h" +#include "linkproviders/linkprovider.h" -TcpDeviceLink::TcpDeviceLink(const QString& d, AvahiTcpLinkProvider* a, QTcpSocket* socket) +TcpDeviceLink::TcpDeviceLink(const QString& d, LinkProvider* a, QTcpSocket* socket) : DeviceLink(d, a) { mSocket = socket; diff --git a/daemon/devicelinks/tcpdevicelink.h b/daemon/devicelinks/tcpdevicelink.h index 801dc2df9..f39b9de2a 100644 --- a/daemon/devicelinks/tcpdevicelink.h +++ b/daemon/devicelinks/tcpdevicelink.h @@ -36,7 +36,7 @@ class TcpDeviceLink Q_OBJECT public: - TcpDeviceLink(const QString& d, AvahiTcpLinkProvider* a, QTcpSocket* socket); + TcpDeviceLink(const QString& d, LinkProvider* a, QTcpSocket* socket); bool sendPackage(const NetworkPackage& np); diff --git a/daemon/linkproviders/avahitcplinkprovider.cpp b/daemon/linkproviders/avahitcplinkprovider.cpp index d7b77a399..4cc58540f 100644 --- a/daemon/linkproviders/avahitcplinkprovider.cpp +++ b/daemon/linkproviders/avahitcplinkprovider.cpp @@ -28,7 +28,6 @@ AvahiTcpLinkProvider::AvahiTcpLinkProvider() { QString serviceType = "_kdeconnect._tcp"; - quint16 port = 10602; //http://api.kde.org/4.x-api/kdelibs-apidocs/dnssd/html/index.html @@ -36,10 +35,27 @@ AvahiTcpLinkProvider::AvahiTcpLinkProvider() mServer = new QTcpServer(this); connect(mServer,SIGNAL(newConnection()),this, SLOT(newConnection())); - mServer->listen(QHostAddress::Any, port); } +void AvahiTcpLinkProvider::onStart() +{ + + mServer->listen(QHostAddress::Any, port); + service->publishAsync(); +} + +void AvahiTcpLinkProvider::onStop() +{ + mServer->close(); + service->stop(); + +} +void AvahiTcpLinkProvider::onNetworkChange(QNetworkSession::State state) +{ + //Nothing to do, Avahi will handle it +} + void AvahiTcpLinkProvider::newConnection() { qDebug() << "AvahiTcpLinkProvider newConnection"; @@ -104,9 +120,3 @@ AvahiTcpLinkProvider::~AvahiTcpLinkProvider() delete service; } -void AvahiTcpLinkProvider::setDiscoverable(bool b) -{ - qDebug() << "AvahiTcpLinkProvider discoverable:" << b; - if (b) service->publishAsync(); -} - diff --git a/daemon/linkproviders/avahitcplinkprovider.h b/daemon/linkproviders/avahitcplinkprovider.h index 4c47dade0..6df0570ca 100644 --- a/daemon/linkproviders/avahitcplinkprovider.h +++ b/daemon/linkproviders/avahitcplinkprovider.h @@ -42,7 +42,10 @@ public: QString name() { return "AvahiTcpLinkProvider"; } int priority() { return PRIORITY_HIGH + 1; } - void setDiscoverable(bool b); +public Q_SLOTS: + virtual void onNetworkChange(QNetworkSession::State state); + virtual void onStart(); + virtual void onStop(); private Q_SLOTS: void newConnection(); @@ -53,6 +56,7 @@ private: DNSSD::PublicService* service; QTcpServer* mServer; + static const quint16 port = 10602; QMap links; }; diff --git a/daemon/linkproviders/broadcasttcplinkprovider.cpp b/daemon/linkproviders/broadcasttcplinkprovider.cpp new file mode 100644 index 000000000..1b5295900 --- /dev/null +++ b/daemon/linkproviders/broadcasttcplinkprovider.cpp @@ -0,0 +1,193 @@ +/** + * 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 "broadcasttcplinkprovider.h" + +#include "devicelinks/tcpdevicelink.h" + +#include +#include +#include + +BroadcastTcpLinkProvider::BroadcastTcpLinkProvider() +{ + + mUdpServer = new QUdpSocket(this); + connect(mUdpServer, SIGNAL(readyRead()), this, SLOT(newUdpConnection())); + + mTcpServer = new QTcpServer(this); + connect(mTcpServer,SIGNAL(newConnection()),this, SLOT(newConnection())); + +} + +void BroadcastTcpLinkProvider::onStart() +{ + mUdpServer->bind(QHostAddress::Broadcast, port, QUdpSocket::ShareAddress); + mTcpServer->listen(QHostAddress::Any, port); + + onNetworkChange(QNetworkSession::Connected); +} + +void BroadcastTcpLinkProvider::onStop() +{ + mUdpServer->close(); + mTcpServer->close(); +} + +//I'm in a new network, let's be polite and introduce myself +void BroadcastTcpLinkProvider::onNetworkChange(QNetworkSession::State state) { + qDebug() << "onNetworkChange" << state; + NetworkPackage np(""); + NetworkPackage::createIdentityPackage(&np); + QUdpSocket().writeDatagram(np.serialize(),QHostAddress("255.255.255.255"), port); +} + +//I'm the existing device, a new device is kindly introducing itself (I will create a TcpSocket) +void BroadcastTcpLinkProvider::newUdpConnection() +{ + while (mUdpServer->hasPendingDatagrams()) { + QByteArray datagram; + datagram.resize(mUdpServer->pendingDatagramSize()); + QHostAddress sender; + quint16 senderPort; + + mUdpServer->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + + NetworkPackage np(""); + NetworkPackage::unserialize(datagram,&np); + + if (np.version() > 0 && np.type() == PACKAGE_TYPE_IDENTITY) { + + NetworkPackage np2(""); + NetworkPackage::createIdentityPackage(&np2); + + if (np.get("deviceId") == np2.get("deviceId")) { + qDebug() << "I can't fuck myself!"; + return; + } + + const QString& id = np.get("deviceId"); + if (links.contains(id)) { + //Delete old link if we already know it, probably it is down if this happens. + qDebug() << "Destroying old link"; + delete links[id]; + links.remove(id); + } + + QTcpSocket* socket = new QTcpSocket(this); + + qDebug() << "Received Udp presentation from" << sender << "asking for a tcp connection..."; + socket->connectToHost(sender, port); + socket->waitForConnected(); + qDebug() << "Connected" << socket->isWritable(); + + TcpDeviceLink* dl = new TcpDeviceLink(id, this, socket); + + connect(dl,SIGNAL(destroyed(QObject*)),this,SLOT(deviceLinkDestroyed(QObject*))); + + links[id] = dl; + bool success = dl->sendPackage(np2); + if (!success) { //FIXME: Why is this happening? + qDebug() << "Fallback, try reverse connection"; + QUdpSocket().writeDatagram(np2.serialize(),sender, port); + } + + qDebug() << "Handshaking done (i'm the existing device)"; + + emit onNewDeviceLink(np, dl); + + } + } + +} + +//I'm the new device and this is the answer to my UDP introduction (no data received yet) +void BroadcastTcpLinkProvider::newConnection() +{ + qDebug() << "BroadcastTcpLinkProvider newConnection"; + + QTcpSocket* socket = mTcpServer->nextPendingConnection(); + socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1); + + connect(socket,SIGNAL(readyRead()),this,SLOT(dataReceived())); + +/* + NetworkPackage np(PACKAGE_TYPE_IDENTITY); + NetworkPackage::createIdentityPackage(&np); + int written = socket->write(np.serialize()); + + qDebug() << "BroadcastTcpLinkProvider sent package." << written << " bytes written, waiting for reply"; +*/ +} + +//I'm the new device and this is the answer to my UDP introduction (data received) +void BroadcastTcpLinkProvider::dataReceived() +{ + QTcpSocket* socket = (QTcpSocket*) QObject::sender(); + + QByteArray data = socket->readLine(); + + qDebug() << "BroadcastTcpLinkProvider received reply:" << data; + + NetworkPackage np(""); + NetworkPackage::unserialize(data,&np); + + if (np.version() > 0 && np.type() == PACKAGE_TYPE_IDENTITY) { + + const QString& id = np.get("deviceId"); + TcpDeviceLink* dl = new TcpDeviceLink(id, this, socket); + + connect(dl,SIGNAL(destroyed(QObject*)),this,SLOT(deviceLinkDestroyed(QObject*))); + + if (links.contains(id)) { + //Delete old link if we already know it, probably it is down if this happens. + qDebug() << "Destroying old link"; + delete links[id]; + } + links[id] = dl; + + //qDebug() << "BroadcastTcpLinkProvider creating link to device" << id << "(" << socket->peerAddress() << ")"; + + qDebug() << "Handshaking done (i'm the new device)"; + + emit onNewDeviceLink(np, dl); + + disconnect(socket,SIGNAL(readyRead()),this,SLOT(dataReceived())); + + } else { + qDebug() << "BroadcastTcpLinkProvider/newConnection: Not an identification package (wuh?)"; + } + +} + +void BroadcastTcpLinkProvider::deviceLinkDestroyed(QObject* deviceLink) +{ + const QString& id = ((DeviceLink*)deviceLink)->deviceId(); + qDebug() << "deviceLinkDestroyed"; + if (links.contains(id)) { + qDebug() << "removing link from link list"; + links.remove(id); + } +} + +BroadcastTcpLinkProvider::~BroadcastTcpLinkProvider() +{ + +} diff --git a/daemon/linkproviders/broadcasttcplinkprovider.h b/daemon/linkproviders/broadcasttcplinkprovider.h new file mode 100644 index 000000000..b4580fe02 --- /dev/null +++ b/daemon/linkproviders/broadcasttcplinkprovider.h @@ -0,0 +1,64 @@ +/** + * 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 BROADCASTTCPLINKPROVIDER_H +#define BROADCASTTCPLINKPROVIDER_H + +#include +#include +#include + +#include "linkprovider.h" +#include "netaddress.h" + + +class BroadcastTcpLinkProvider + : public LinkProvider +{ + Q_OBJECT + +public: + BroadcastTcpLinkProvider(); + ~BroadcastTcpLinkProvider(); + + QString name() { return "BroadcastTcpLinkProvider"; } + int priority() { return PRIORITY_HIGH + 5; } + +public Q_SLOTS: + virtual void onNetworkChange(QNetworkSession::State state); + virtual void onStart(); + virtual void onStop(); + +private Q_SLOTS: + void newUdpConnection(); + void newConnection(); + void dataReceived(); + void deviceLinkDestroyed(QObject*); + +private: + QTcpServer* mTcpServer; + QUdpSocket* mUdpServer; + const static quint16 port = 1714; + + QMap links; + +}; + +#endif diff --git a/daemon/linkproviders/linkprovider.h b/daemon/linkproviders/linkprovider.h index cef0f641e..54ae50f29 100644 --- a/daemon/linkproviders/linkprovider.h +++ b/daemon/linkproviders/linkprovider.h @@ -21,8 +21,9 @@ #ifndef LINKPROVIDER_H #define LINKPROVIDER_H -#include #include +#include +#include #include "devicelinks/devicelink.h" #include "device.h" @@ -46,7 +47,10 @@ public: virtual QString name() = 0; virtual int priority() = 0; - virtual void setDiscoverable(bool b) = 0; +public Q_SLOTS: + virtual void onStart() = 0; + virtual void onStop() = 0; + virtual void onNetworkChange(QNetworkSession::State state) = 0; Q_SIGNALS: //NOTE: The provider will to destroy the DeviceLink when it's no longer accessible, diff --git a/daemon/packageinterfaces/notificationpackageinterface.cpp b/daemon/packageinterfaces/notificationpackageinterface.cpp index 83f54e0c3..48695bd5d 100644 --- a/daemon/packageinterfaces/notificationpackageinterface.cpp +++ b/daemon/packageinterfaces/notificationpackageinterface.cpp @@ -23,23 +23,40 @@ #include #include +NotificationPackageInterface::NotificationPackageInterface(QObject* parent) + : PackageInterface(parent) +{ + //TODO: Split in EventNotificationInterface and NotificationDrawerSyncInterface + + trayIcon = new KStatusNotifierItem(parent); + trayIcon->setIconByName("pda"); + trayIcon->setTitle("KdeConnect"); + connect(trayIcon,SIGNAL(activateRequested(bool,QPoint)),this,SLOT(showPendingNotifications())); +} + KNotification* NotificationPackageInterface::createNotification(const QString& deviceName, const NetworkPackage& np) { + QString id = QString::number(np.id()); + QString npType = np.get("notificationType"); QString title, content, type, icon; + bool transient; title = deviceName; + if (npType == "ringing") { type = "callReceived"; icon = "call-start"; content = "Incoming call from " + np.get("phoneNumber","unknown number"); + transient = false; } else if (npType == "missedCall") { type = "missedCall"; icon = "call-start"; content = "Missed call from " + np.get("phoneNumber","unknown number"); + transient = true; } else if (npType == "sms") { type = "smsReceived"; icon = "mail-receive"; @@ -47,33 +64,76 @@ KNotification* NotificationPackageInterface::createNotification(const QString& d + np.get("phoneNumber","unknown number") + ":\n" + np.get("messageBody",""); + transient = true; } else if (npType == "battery") { type = "battery100"; icon = "battery-100"; content = "Battery at " + np.get("batteryLevel") + "%"; + transient = false; } else if (npType == "notification") { type = "pingReceived"; icon = "dialog-ok"; content = np.get("notificationContent"); + transient = false; } else { //TODO: return NULL if !debug type = "unknownEvent"; icon = "pda"; content = "Unknown notification type: " + npType; + transient = false; } qDebug() << "Creating notification with type:" << type; + + if (transient) { + trayIcon->setStatus(KStatusNotifierItem::Active); + + KNotification* notification = new KNotification(type); //KNotification::Persistent + notification->setPixmap(KIcon(icon).pixmap(48, 48)); + notification->setComponentData(KComponentData("kdeconnect", "kdeconnect")); + notification->setTitle(title); + notification->setText(content); + + pendingNotifications.insert(id, notification); + } + KNotification* notification = new KNotification(type); //KNotification::Persistent notification->setPixmap(KIcon(icon).pixmap(48, 48)); notification->setComponentData(KComponentData("kdeconnect", "kdeconnect")); notification->setTitle(title); notification->setText(content); + notification->setProperty("id",id); + + connect(notification,SIGNAL(activated()),this,SLOT(notificationAttended())); + connect(notification,SIGNAL(closed()),this,SLOT(notificationAttended())); return notification; } +void NotificationPackageInterface::notificationAttended() +{ + KNotification* normalNotification = (KNotification*)sender(); + QString id = normalNotification->property("id").toString(); + if (pendingNotifications.contains(id)) { + delete pendingNotifications[id]; + pendingNotifications.remove(id); + if (pendingNotifications.isEmpty()) { + trayIcon->setStatus(KStatusNotifierItem::Passive); + } + } +} + +void NotificationPackageInterface::showPendingNotifications() +{ + trayIcon->setStatus(KStatusNotifierItem::Passive); + Q_FOREACH (KNotification* notification, pendingNotifications) { + notification->sendEvent(); + } + pendingNotifications.clear(); +} + bool NotificationPackageInterface::receivePackage(const Device& device, const NetworkPackage& np) { diff --git a/daemon/packageinterfaces/notificationpackageinterface.h b/daemon/packageinterfaces/notificationpackageinterface.h index dae87497c..b898bcfbe 100644 --- a/daemon/packageinterfaces/notificationpackageinterface.h +++ b/daemon/packageinterfaces/notificationpackageinterface.h @@ -25,17 +25,26 @@ #include "packageinterface.h" +#include + class NotificationPackageInterface : public PackageInterface { + Q_OBJECT public: + NotificationPackageInterface(QObject* parent = 0); + virtual bool receivePackage(const Device&, const NetworkPackage& np); private: - static KNotification* createNotification(const QString& deviceName,const NetworkPackage& np); - + KNotification* createNotification(const QString& deviceName,const NetworkPackage& np); + KStatusNotifierItem* trayIcon; + QHash pendingNotifications; +public slots: + void showPendingNotifications(); + void notificationAttended(); }; #endif