diff --git a/CMakeLists.txt b/CMakeLists.txt index 80196d5f..638cfd9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,65 +1,67 @@ project(kdeconnect) set(KDECONNECT_VERSION_MAJOR 1) set(KDECONNECT_VERSION_MINOR 1) set(KDECONNECT_VERSION_PATCH 0) set(KDECONNECT_VERSION "${KDECONNECT_VERSION_MAJOR}.${KDECONNECT_VERSION_MINOR}.${KDECONNECT_VERSION_PATCH}") cmake_minimum_required(VERSION 2.8.12) find_package(ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DIR}/cmake) -find_package(Qt5 5.2 REQUIRED COMPONENTS Quick) +find_package(Qt5 5.2 REQUIRED COMPONENTS Quick Bluetooth) + find_package(KF5 REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons) find_package(KF5DocTools) + find_package(Qca-qt5 2.1.0 REQUIRED) 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 WIN32) add_subdirectory(kio) 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(fileitemactionplugin) if(KF5DocTools_FOUND) add_subdirectory(doc) endif() if(BUILD_TESTING) add_subdirectory(tests) endif() install(FILES org.kde.kdeconnect.kcm.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 0b160963..e73155a3 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,55 +1,58 @@ project(KDEConnectCore) add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-core\") include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) add_subdirectory(backends/lan) add_subdirectory(backends/loopback) +add_subdirectory(backends/bluetooth) find_package(KF5Notifications 5.9 REQUIRED) set(kdeconnectcore_SRCS ${backends_kdeconnect_SRCS} backends/linkprovider.cpp backends/devicelink.cpp backends/pairinghandler.cpp + backends/devicelinereader.cpp kdeconnectplugin.cpp kdeconnectpluginconfig.cpp pluginloader.cpp kdeconnectconfig.cpp dbushelper.cpp networkpackage.cpp filetransferjob.cpp daemon.cpp device.cpp core_debug.cpp ) add_library(kdeconnectcore ${kdeconnectcore_SRCS}) target_link_libraries(kdeconnectcore PUBLIC Qt5::Network KF5::CoreAddons qca-qt5 + Qt5::Bluetooth PRIVATE Qt5::DBus Qt5::Gui KF5::I18n KF5::ConfigCore ) set_target_properties(kdeconnectcore PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) target_include_directories(kdeconnectcore PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) generate_export_header(kdeconnectcore EXPORT_FILE_NAME kdeconnectcore_export.h BASE_NAME KDEConnectCore) install(TARGETS kdeconnectcore EXPORT kdeconnectLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) diff --git a/core/backends/bluetooth/CMakeLists.txt b/core/backends/bluetooth/CMakeLists.txt new file mode 100644 index 00000000..125fa87a --- /dev/null +++ b/core/backends/bluetooth/CMakeLists.txt @@ -0,0 +1,9 @@ + +set(backends_kdeconnect_SRCS + ${backends_kdeconnect_SRCS} + + backends/bluetooth/bluetoothlinkprovider.cpp + backends/bluetooth/bluetoothdevicelink.cpp + + PARENT_SCOPE +) diff --git a/core/backends/bluetooth/bluetoothdevicelink.cpp b/core/backends/bluetooth/bluetoothdevicelink.cpp new file mode 100644 index 00000000..e3c1e333 --- /dev/null +++ b/core/backends/bluetooth/bluetoothdevicelink.cpp @@ -0,0 +1,110 @@ +/** + * Copyright 2015 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "bluetoothdevicelink.h" + +#include "../linkprovider.h" + +BluetoothDeviceLink::BluetoothDeviceLink(const QString& deviceId, LinkProvider* parent, QBluetoothSocket* socket) + : DeviceLink(deviceId, parent) + , mBluetoothSocket(new DeviceLineReader(socket)) +{ + connect(mBluetoothSocket, SIGNAL(readyRead()), + this, SLOT(dataReceived())); + + //We take ownership of the socket. + //When the link provider distroys us, + //the socket (and the reader) will be + //destroyed as well + connect(mBluetoothSocket, SIGNAL(disconnected()), + this, SLOT(deleteLater())); + mBluetoothSocket->setParent(this); +} + +/*void BluetoothDeviceLink::sendPayload(const QSharedPointer& mInput) +{ + while ( mInput->bytesAvailable() > 0 ) + { + qint64 bytes = qMin(mInput->bytesAvailable(), (qint64)4096); + int w = mBluetoothSocket->write(mInput->read(bytes)); + if (w<0) { + qWarning() << "error when writing data to upload" << bytes << mInput->bytesAvailable(); + break; + } + else + { + while ( mBluetoothSocket->flush() ); + } + } + + mInput->close(); +}*/ + +bool BluetoothDeviceLink::sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& np) +{ + np.encrypt(key); + return sendPackage(np); +} + +bool BluetoothDeviceLink::sendPackage(NetworkPackage& np) +{ + int written = mBluetoothSocket->write(np.serialize()); + if (np.hasPayload()) { + qWarning() << "Bluetooth backend does not support payloads."; + } + return (written != -1); +} + +void BluetoothDeviceLink::dataReceived() +{ + + if (mBluetoothSocket->bytesAvailable() == 0) return; + + const QByteArray package = mBluetoothSocket->readLine(); + + //kDebug(debugArea()) << "BluetoothDeviceLink dataReceived" << package; + + NetworkPackage unserialized(QString::null); + NetworkPackage::unserialize(package, &unserialized); + if (unserialized.isEncrypted()) { + //mPrivateKey should always be set when device link is added to device, no null-checking done here + NetworkPackage decrypted(QString::null); + unserialized.decrypt(mPrivateKey, &decrypted); + + if (decrypted.hasPayloadTransferInfo()) { + qWarning() << "Bluetooth backend does not support payloads."; + } + + Q_EMIT receivedPackage(decrypted); + + } else { + if (unserialized.hasPayloadTransferInfo()) { + qWarning() << "Ignoring unencrypted payload"; + } + + Q_EMIT receivedPackage(unserialized); + + } + + if (mBluetoothSocket->bytesAvailable() > 0) { + QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); + } + +} diff --git a/core/backends/bluetooth/bluetoothdevicelink.h b/core/backends/bluetooth/bluetoothdevicelink.h new file mode 100644 index 00000000..199d9ee4 --- /dev/null +++ b/core/backends/bluetooth/bluetoothdevicelink.h @@ -0,0 +1,53 @@ +/** + * Copyright 2015 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLUETOOTHDEVICELINK_H +#define BLUETOOTHDEVICELINK_H + +#include +#include +#include + +#include "../devicelink.h" +#include "../devicelinereader.h" + +class BluetoothDeviceLink + : public DeviceLink +{ + Q_OBJECT + +public: + BluetoothDeviceLink(const QString& deviceId, LinkProvider* parent, QBluetoothSocket* socket); + + bool sendPackage(NetworkPackage& np); + bool sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& np); + +private Q_SLOTS: + void dataReceived(); + +private: + DeviceLineReader* mBluetoothSocket; + + //void sendPayload(const QSharedPointer& mInput); + void sendMessage(const QString mMessage); + +}; + +#endif diff --git a/core/backends/bluetooth/bluetoothlinkprovider.cpp b/core/backends/bluetooth/bluetoothlinkprovider.cpp new file mode 100644 index 00000000..2b78171b --- /dev/null +++ b/core/backends/bluetooth/bluetoothlinkprovider.cpp @@ -0,0 +1,367 @@ +/** + * Copyright 2015 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "bluetoothlinkprovider.h" +#include "core_debug.h" + +#include +#include + +#include +#include +#include +#include + +#include "bluetoothdevicelink.h" + +BluetoothLinkProvider::BluetoothLinkProvider() +{ + if (!mDevice.isValid()) { + qCWarning(KDECONNECT_CORE) << "No local device found"; + mBluetoothServer = NULL; + return; + } + + mBluetoothServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this); + mBluetoothServer->setSecurityFlags(QBluetooth::Encryption | QBluetooth::Secure); + connect(mBluetoothServer, SIGNAL(newConnection()), this, SLOT(serverNewConnection())); + + mServiceUuid = QBluetoothUuid(QString("576bf9a0-98c9-11e4-bc89-0002a5d5c51b")); + + connectTimer = new QTimer(this); + connectTimer->setInterval(30000); + connectTimer->setSingleShot(false); +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) + connect(connectTimer, SIGNAL(timeout()), this, SLOT(connectToPairedDevices())); +#else + mServiceDiscoveryAgent = new QBluetoothServiceDiscoveryAgent(this); + mServiceDiscoveryAgent->setUuidFilter(mServiceUuid); + connect(mServiceDiscoveryAgent, SIGNAL(finished()), this, SLOT(serviceDiscoveryFinished())); + connect(connectTimer, SIGNAL(timeout()), mServiceDiscoveryAgent, SLOT(start())); +#endif +} + +void BluetoothLinkProvider::onStart() +{ + if (!mBluetoothServer) { + return; + } + +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) + connectToPairedDevices(); +#else + mServiceDiscoveryAgent->start(); +#endif + + connectTimer->start(); + mKdeconnectService = mBluetoothServer->listen(mServiceUuid, "KDE Connect"); + + onNetworkChange(QNetworkSession::Connected); +} + +void BluetoothLinkProvider::onStop() +{ + if (!mBluetoothServer) { + return; + } + + connectTimer->stop(); + + mKdeconnectService.unregisterService(); + mBluetoothServer->close(); +} + +//I'm in a new network, let's be polite and introduce myself +void BluetoothLinkProvider::onNetworkChange(QNetworkSession::State state) +{ + Q_UNUSED(state); +} + +QList BluetoothLinkProvider::getPairedDevices() { + QDBusConnection bus = QDBusConnection::systemBus(); + QDBusInterface manager_iface("org.bluez", "/","org.bluez.Manager", bus); + + QDBusReply devices = manager_iface.call("DefaultAdapter"); + + if (!devices.isValid()) { + qCWarning(KDECONNECT_CORE) << "Couldn't get default adapter:" << devices.error(); + return QList(); + } + + QDBusObjectPath defaultAdapter = devices.value(); + QString defaultAdapterPath = defaultAdapter.path(); + + QDBusInterface devices_iface("org.bluez", defaultAdapterPath, "org.bluez.Adapter", bus); + QDBusMessage pairedDevices = devices_iface.call("ListDevices"); + + QDBusArgument pairedDevicesArg = pairedDevices.arguments().at(0).value(); + pairedDevicesArg.beginArray(); + QList pairedDevicesList; + + while (!pairedDevicesArg.atEnd()) { + QVariant variant = pairedDevicesArg.asVariant(); + QDBusObjectPath pairedDevice = qvariant_cast(variant); + QString pairedDevicePath = pairedDevice.path(); + QString pairedDeviceMac = pairedDevicePath.split(QChar('/')).last().remove("dev_").replace("_", ":"); + pairedDevicesList.append(QBluetoothAddress(pairedDeviceMac)); + } + + return pairedDevicesList; +} + +void BluetoothLinkProvider::connectToPairedDevices() { + QList pairedDevices = getPairedDevices(); + for (int i = 0; i < pairedDevices.size(); i++) { + QBluetoothAddress pairedDevice = pairedDevices.at(i); + + if (mSockets.contains(pairedDevice)) { + continue; + } + + QBluetoothSocket* socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this); + connect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/onStart: Connecting to " << pairedDevice.toString(); + socket->connectToService(pairedDevice, mServiceUuid); + } +} + +void BluetoothLinkProvider::connectError() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + qCWarning(KDECONNECT_CORE) << "connectError: Couldn't connect to socket:" << socket->errorString(); + + disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); + disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + mSockets.remove(socket->peerAddress()); + socket->deleteLater(); +} + +void BluetoothLinkProvider::addLink(BluetoothDeviceLink* deviceLink, const QString& deviceId) +{ + QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(deviceId); + if (oldLinkIterator != mLinks.end()) { + DeviceLink* oldLink = oldLinkIterator.value(); + disconnect(oldLink, SIGNAL(destroyed(QObject*)), + this, SLOT(deviceLinkDestroyed(QObject*))); + oldLink->deleteLater(); + mLinks.erase(oldLinkIterator); + } + + mLinks[deviceId] = deviceLink; +} + +//I'm the new device and I found an existing device +void BluetoothLinkProvider::serviceDiscoveryFinished() +{ + QList services = mServiceDiscoveryAgent->discoveredServices(); + + foreach (QBluetoothServiceInfo info, services) { + if (mSockets.contains(info.device().address())) { + continue; + } + + QBluetoothSocket* socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); + + connect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + socket->connectToService(info); + + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/serviceDiscoveryFinished: Connecting to " << socket->peerAddress().toString(); + + if (socket->error() != QBluetoothSocket::NoSocketError) { + qCWarning(KDECONNECT_CORE) << "BluetoothLinkProvider/serviceDiscoveryFinished: Socket connection error: " << socket->errorString(); + } + } +} + +//I'm the new device and I'm connected to the existing device. Time to get data. +void BluetoothLinkProvider::clientConnected() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/clientConnected: Connected to " << socket->peerAddress().toString(); + + if (mSockets.contains(socket->peerAddress())) { + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/clientConnected: Duplicate connection to " << socket->peerAddress().toString(); + socket->close(); + socket->deleteLater(); + return; + } + + mSockets.insert(socket->peerAddress(), socket); + + disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + + connect(socket, SIGNAL(readyRead()), this, SLOT(clientIdentityReceived())); + connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); +} + +//I'm the new device and the existing device sent me data. +void BluetoothLinkProvider::clientIdentityReceived() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + QByteArray identityArray; + while (socket->bytesAvailable() > 0 || !identityArray.contains('\n')) { + identityArray += socket->readAll(); + } + + disconnect(socket, SIGNAL(readyRead()), this, SLOT(clientIdentityReceived())); + + NetworkPackage receivedPackage(""); + bool success = NetworkPackage::unserialize(identityArray, &receivedPackage); + + if (!success || receivedPackage.type() != PACKAGE_TYPE_IDENTITY) { + qCDebug(KDECONNECT_CORE) << "BluetoothLinkProvider/clientIdentityReceived: Not an identification package (wuh?)"; + mSockets.remove(socket->peerAddress()); + socket->close(); + socket->deleteLater(); + return; + } + + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/clientIdentityReceived: Received identity package from " << socket->peerAddress().toString(); + + disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + const QString& deviceId = receivedPackage.get("deviceId"); + BluetoothDeviceLink* deviceLink = new BluetoothDeviceLink(deviceId, this, socket); + + NetworkPackage np2(""); + NetworkPackage::createIdentityPackage(&np2); + success = deviceLink->sendPackage(np2); + + if (success) { + + qCDebug(KDECONNECT_CORE) << "Handshaking done (I'm the new device)"; + + connect(deviceLink, SIGNAL(destroyed(QObject*)), + this, SLOT(deviceLinkDestroyed(QObject*))); + + Q_EMIT onConnectionReceived(receivedPackage, deviceLink); + + //We kill any possible link from this same device + addLink(deviceLink, deviceId); + + } else { + // Connection might be lost. Delete it. + delete deviceLink; + } + + //We don't delete the socket because now it's owned by the BluetoothDeviceLink +} + +//I'm the existing device, a new device is kindly introducing itself. +void BluetoothLinkProvider::serverNewConnection() +{ + QBluetoothSocket* socket = mBluetoothServer->nextPendingConnection(); + + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/serverNewConnection:" << "Received connection from " << socket->peerAddress().toString(); + + if (mSockets.contains(socket->peerAddress())) { + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/serverNewConnection: Duplicate connection from " << socket->peerAddress().toString(); + socket->close(); + socket->deleteLater(); + return; + } + + connect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); + connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + NetworkPackage np2(""); + NetworkPackage::createIdentityPackage(&np2); + socket->write(np2.serialize()); + + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/serverNewConnection: Sent identity package to " << socket->peerAddress().toString(); + + mSockets.insert(socket->peerAddress(), socket); +} + +//I'm the existing device and this is the answer to my identity package (data received) +void BluetoothLinkProvider::serverDataReceived() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + QByteArray identityArray; + while (socket->bytesAvailable() > 0 || !identityArray.contains('\n')) { + identityArray += socket->readAll(); + } + + disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); + disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + NetworkPackage receivedPackage(""); + bool success = NetworkPackage::unserialize(identityArray, &receivedPackage); + + if (!success || receivedPackage.type() != PACKAGE_TYPE_IDENTITY) { + qCDebug(KDECONNECT_CORE) << "BluetoothLinkProvider/serverDataReceived: Not an identity package."; + mSockets.remove(socket->peerAddress()); + socket->close(); + socket->deleteLater(); + return; + } + + qCDebug(KDECONNECT_CORE()) << "BluetoothLinkProvider/serverDataReceived: Received identity package from " << socket->peerAddress().toString(); + + const QString& deviceId = receivedPackage.get("deviceId"); + BluetoothDeviceLink* deviceLink = new BluetoothDeviceLink(deviceId, this, socket); + + connect(deviceLink, SIGNAL(destroyed(QObject*)), + this, SLOT(deviceLinkDestroyed(QObject*))); + + Q_EMIT onConnectionReceived(receivedPackage, deviceLink); + + addLink(deviceLink, deviceId); +} + +void BluetoothLinkProvider::deviceLinkDestroyed(QObject* destroyedDeviceLink) +{ + //kDebug(debugArea()) << "deviceLinkDestroyed"; + const QString id = destroyedDeviceLink->property("deviceId").toString(); + qCDebug(KDECONNECT_CORE()) << "Device disconnected:" << id; + QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(id); + if (oldLinkIterator != mLinks.end() && oldLinkIterator.value() == destroyedDeviceLink) { + mLinks.erase(oldLinkIterator); + } +} + +void BluetoothLinkProvider::socketDisconnected() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + disconnect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + mSockets.remove(mSockets.key(socket)); +} + +BluetoothLinkProvider::~BluetoothLinkProvider() +{ + +} diff --git a/core/backends/bluetooth/bluetoothlinkprovider.h b/core/backends/bluetooth/bluetoothlinkprovider.h new file mode 100644 index 00000000..ee418886 --- /dev/null +++ b/core/backends/bluetooth/bluetoothlinkprovider.h @@ -0,0 +1,82 @@ +/** + * Copyright 2015 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLUETOOTHLINKPROVIDER_H +#define BLUETOOTHLINKPROVIDER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../linkprovider.h" + +class BluetoothDeviceLink; + +class BluetoothLinkProvider + : public LinkProvider +{ + Q_OBJECT + +public: + BluetoothLinkProvider(); + ~BluetoothLinkProvider(); + + QString name() { return "BluetoothLinkProvider"; } + int priority() { return PRIORITY_MEDIUM; } +public Q_SLOTS: + virtual void onNetworkChange(QNetworkSession::State state); + virtual void onStart(); + virtual void onStop(); + void connectError(); + void serviceDiscoveryFinished(); + +private Q_SLOTS: + void deviceLinkDestroyed(QObject* destroyedDeviceLink); + void socketDisconnected(); + void connectToPairedDevices(); + void serverNewConnection(); + void serverDataReceived(); + void clientConnected(); + void clientIdentityReceived(); + +private: + void addLink(BluetoothDeviceLink* deviceLink, const QString& deviceId); + QList getPairedDevices(); + + QBluetoothLocalDevice mDevice; + QBluetoothUuid mServiceUuid; + QBluetoothServer* mBluetoothServer; + QBluetoothServiceInfo mKdeconnectService; + QBluetoothServiceDiscoveryAgent* mServiceDiscoveryAgent; + QTimer* connectTimer; + + QMap mLinks; + + QMap mSockets; + +}; + +#endif diff --git a/core/backends/devicelinereader.cpp b/core/backends/devicelinereader.cpp new file mode 100644 index 00000000..bba0bdaa --- /dev/null +++ b/core/backends/devicelinereader.cpp @@ -0,0 +1,55 @@ +/** + * Copyright 2013 Albert Vaca + * Copyright 2014 Alejandro Fiestas Olivares + * + * 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 "devicelinereader.h" + +DeviceLineReader::DeviceLineReader(QIODevice* device, QObject* parent) + : QObject(parent) + , mDevice(device) +{ + connect(mDevice, SIGNAL(readyRead()), + this, SLOT(dataReceived())); + connect(mDevice, SIGNAL(disconnected()), + this, SIGNAL(disconnected())); +} + +void DeviceLineReader::dataReceived() +{ + while(mDevice->canReadLine()) { + const QByteArray line = mDevice->readLine(); + if (line.length() > 1) { + mPackages.enqueue(line);//we don't want single \n + } + } + + //If we still have things to read from the device, call dataReceived again + //We do this manually because we do not trust readyRead to be emitted again + //So we call this method again just in case. + if (mDevice->bytesAvailable() > 0) { + QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); + return; + } + + //If we have any packages, tell it to the world. + if (!mPackages.isEmpty()) { + Q_EMIT readyRead(); + } +} diff --git a/core/backends/devicelinereader.h b/core/backends/devicelinereader.h new file mode 100644 index 00000000..a5255c77 --- /dev/null +++ b/core/backends/devicelinereader.h @@ -0,0 +1,59 @@ +/** + * 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 DEVICELINEREADER_H +#define DEVICELINEREADER_H + +#include +#include +#include +#include + +/* + * Encapsulates a QIODevice and implements the same methods of its API that are + * used by LanDeviceLink, but readyRead is emitted only when a newline is found. + */ +class DeviceLineReader + : public QObject +{ + Q_OBJECT + +public: + DeviceLineReader(QIODevice* device, QObject* parent = 0); + + QByteArray readLine() { return mPackages.dequeue(); } + qint64 write(const QByteArray& data) { return mDevice->write(data); } + qint64 bytesAvailable() { return mPackages.size(); } + +Q_SIGNALS: + void readyRead(); + void disconnected(); + +private Q_SLOTS: + void dataReceived(); + +private: + QByteArray lastChunk; + QIODevice* mDevice; + QQueue mPackages; + +}; + +#endif diff --git a/core/daemon.cpp b/core/daemon.cpp index 3f4028cc..aab240e3 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,278 +1,284 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "daemon.h" #include #include #include #include #include "core_debug.h" #include "kdeconnectconfig.h" #include "networkpackage.h" +#include "backends/bluetooth/bluetoothlinkprovider.h" #include "backends/lan/lanlinkprovider.h" #include "backends/loopback/loopbacklinkprovider.h" #include "device.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them QSet mLinkProviders; //Every known device QMap mDevices; QSet mDiscoveryModeAcquisitions; }; Daemon* Daemon::instance() { Q_ASSERT(s_instance != nullptr); return s_instance; } Daemon::Daemon(QObject *parent, bool testMode) : QObject(parent) , d(new DaemonPrivate) { Q_ASSERT(!s_instance); s_instance = this; qCDebug(KDECONNECT_CORE) << "KdeConnect daemon starting"; //Load backends if (testMode) d->mLinkProviders.insert(new LoopbackLinkProvider()); else d->mLinkProviders.insert(new LanLinkProvider()); //Read remebered paired devices const QStringList& list = KdeConnectConfig::instance()->trustedDevices(); Q_FOREACH (const QString& id, list) { addDevice(new Device(this, id)); } //Listen to new devices Q_FOREACH (LinkProvider* a, d->mLinkProviders) { connect(a, &LinkProvider::onConnectionReceived, this, &Daemon::onNewDeviceLink); a->onStart(); } //Register on DBus QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); qCDebug(KDECONNECT_CORE) << "KdeConnect daemon started"; } void Daemon::acquireDiscoveryMode(const QString &key) { bool oldState = d->mDiscoveryModeAcquisitions.isEmpty(); d->mDiscoveryModeAcquisitions.insert(key); if (oldState != d->mDiscoveryModeAcquisitions.isEmpty()) { forceOnNetworkChange(); } } void Daemon::releaseDiscoveryMode(const QString &key) { bool oldState = d->mDiscoveryModeAcquisitions.isEmpty(); d->mDiscoveryModeAcquisitions.remove(key); + //Load backends + //d->mLinkProviders.insert(new LoopbackLinkProvider()); + d->mLinkProviders.insert(new LanLinkProvider()); + d->mLinkProviders.insert(new BluetoothLinkProvider()); + if (oldState != d->mDiscoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } void Daemon::removeDevice(Device* device) { d->mDevices.remove(device->id()); device->deleteLater(); Q_EMIT deviceRemoved(device->id()); } void Daemon::cleanDevices() { Q_FOREACH (Device* device, d->mDevices) { if (device->isTrusted()) { continue; } device->cleanUnneededLinks(); //If there are no links remaining if (!device->isReachable()) { removeDevice(device); } } } void Daemon::forceOnNetworkChange() { qCDebug(KDECONNECT_CORE) << "Sending onNetworkChange to " << d->mLinkProviders.size() << " LinkProviders"; Q_FOREACH (LinkProvider* a, d->mLinkProviders) { a->onNetworkChange(); } } Device*Daemon::getDevice(const QString& deviceId) { Q_FOREACH (Device* device, d->mDevices) { if (device->id() == deviceId) { return device; } } return Q_NULLPTR; } QStringList Daemon::devices(bool onlyReachable, bool onlyTrusted) const { QStringList ret; Q_FOREACH (Device* device, d->mDevices) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret.append(device->id()); } return ret; } void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl) { const QString& id = identityPackage.get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Device discovered" << id << "via" << dl->provider()->name(); if (d->mDevices.contains(id)) { qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPackage.get(QStringLiteral("deviceName")); Device* device = d->mDevices[id]; bool wasReachable = device->isReachable(); device->addLink(identityPackage, dl); if (!wasReachable) { Q_EMIT deviceVisibilityChanged(id, true); } } else { qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPackage.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPackage, dl); //we discard the connections that we created but it's not paired. if (!isDiscoveringDevices() && !device->isTrusted() && !dl->linkShouldBeKeptAlive()) { device->deleteLater(); } else { addDevice(device); } } } void Daemon::onDeviceStatusChanged() { Device* device = (Device*)sender(); //qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); if (!device->isReachable() && !device->isTrusted()) { //qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); removeDevice(device); } else { Q_EMIT deviceVisibilityChanged(device->id(), device->isReachable()); } } void Daemon::setAnnouncedName(const QString &name) { qCDebug(KDECONNECT_CORE()) << "Announcing name"; KdeConnectConfig::instance()->setName(name); forceOnNetworkChange(); Q_EMIT announcedNameChanged(name); } QString Daemon::announcedName() { return KdeConnectConfig::instance()->name(); } QNetworkAccessManager* Daemon::networkAccessManager() { static QPointer manager; if (!manager) { manager = new QNetworkAccessManager(this); } return manager; } QList Daemon::devicesList() const { return d->mDevices.values(); } bool Daemon::isDiscoveringDevices() const { return !d->mDiscoveryModeAcquisitions.isEmpty(); } QString Daemon::deviceIdByName(const QString &name) const { Q_FOREACH (Device* d, d->mDevices) { if (d->name() == name && d->isTrusted()) return d->id(); } return {}; } void Daemon::addDevice(Device* device) { const QString id = device->id(); connect(device, &Device::reachableChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::trustedChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::hasPairingRequestsChanged, this, &Daemon::pairingRequestsChanged); connect(device, &Device::hasPairingRequestsChanged, this, [this, device](bool hasPairingRequests) { if (hasPairingRequests) askPairingConfirmation(device); } ); d->mDevices[id] = device; Q_EMIT deviceAdded(id); } QStringList Daemon::pairingRequests() const { QStringList ret; for(Device* dev: d->mDevices) { if (dev->hasPairingRequests()) ret += dev->id(); } return ret; } Daemon::~Daemon() { } QString Daemon::selfId() const { return KdeConnectConfig::instance()->deviceId(); } diff --git a/core/kdeconnectplugin_config.cpp b/core/kdeconnectplugin_config.cpp new file mode 100644 index 00000000..c59cd553 --- /dev/null +++ b/core/kdeconnectplugin_config.cpp @@ -0,0 +1,49 @@ +/** + * 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 "kdeconnectplugin_config.h" +#include + +KdeConnectPluginConfig::KdeConnectPluginConfig(QObject* parent, const QVariantList& args) + : KCModule(SftpConfigFactory::componentData(), parent) + , mGlobalConfig(KSharedConfig::openConfig("kdeconnect/plugins/pausemusic")) +{ + KdeConnectKcm* kcm = 0; + QObject* kcmCandidate = parent->parentWidget(); + while (kcmCandidate) { + //qDebug() << kcmCandidate; + if (kcmCandidate->objectName() == "KdeConnectKcm") { + kcm = qobject_cast(kcmCandidate); + break; + } + kcmCandidate = kcmCandidate->parent(); + } + if (kcm) { + kcm->selectedDevice()->id(); + } + +} + +KdeConnectPluginConfig::~KdeConnectPluginConfig() +{ +} + + + diff --git a/core/kdeconnectplugin_config.h b/core/kdeconnectplugin_config.h new file mode 100644 index 00000000..e96a800c --- /dev/null +++ b/core/kdeconnectplugin_config.h @@ -0,0 +1,49 @@ +/** + * 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 KDECONNECTPLUGINCONFIG_H +#define KDECONNECTPLUGINCONFIG_H + +#include "kdeconnectcore_export.h" +#include + +struct DeviceDbusInterface; + +class KDECONNECTCORE_EXPORT KdeConnectPluginConfig + : public KCModule +{ + Q_OBJECT + +public: + KdeConnectPluginConfig(QObject* parent, const QVariantList& args); + virtual ~KdeConnectPluginConfig(); + QString deviceId() { return mDeviceId; } + + //TODO: Add these two to the Plugin as well + KSharedConfigPtr deviceConfig() { return mDeviceConfig; } + KSharedConfigPtr globalConfig() { return mGlobalConfig; } + +private: + QString mDeviceId; + KSharedConfigPtr mGlobalConfig; + KSharedConfigPtr mDeviceConfig; +}; + +#endif