diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 00000000..e21d11f2 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,3 @@ +{ + "phabricator.uri" : "https://phabricator.kde.org/project/profile/159/" +} diff --git a/.gitignore b/.gitignore index d7f2d606..fc688c25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ build clang *.kdev4 Makefile lib src/Makefile src/*_automoc.cpp* src/moc_*.cpp CMakeTmp CMakeFiles +KDEConnect.config +KDEConnect.creator +KDEConnect.files +KDEConnect.includes +.directory diff --git a/.reviewboardrc b/.reviewboardrc deleted file mode 100644 index 064c03e0..00000000 --- a/.reviewboardrc +++ /dev/null @@ -1,3 +0,0 @@ -REVIEWBOARD_URL = "https://git.reviewboard.kde.org" -REPOSITORY = 'git://anongit.kde.org/kdeconnect-kde' -TARGET_GROUPS = 'kdeconnect' diff --git a/CMakeLists.txt b/CMakeLists.txt index df7dd500..c87920d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,55 +1,67 @@ project(kdeconnect) -set(KDECONNECT_VERSION_MAJOR 0) -set(KDECONNECT_VERSION_MINOR 8) -set(KDECONNECT_VERSION_PATCH 0) +set(KDECONNECT_VERSION_MAJOR 1) +set(KDECONNECT_VERSION_MINOR 2) +set(KDECONNECT_VERSION_PATCH 1) 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 Test) -find_package(KF5 REQUIRED COMPONENTS I18n ConfigWidgets DBusAddons IconThemes) +find_package(Qt5 5.2 REQUIRED COMPONENTS Quick) + +find_package(KF5 5.42.0 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) +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) +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) -add_subdirectory(kio) +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(plasmoid) add_subdirectory(cli) +add_subdirectory(indicator) add_subdirectory(fileitemactionplugin) +add_subdirectory(urlhandler) +if(KF5DocTools_FOUND) + add_subdirectory(doc) +endif() -add_subdirectory(tests) +if(BUILD_TESTING) + add_subdirectory(tests) +endif() -install(PROGRAMS kdeconnect-non-plasma.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) +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/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..81b32747 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +This information is provided for Github users who may wonder why issues are disabled. + +To file bugs, use the [kde bugtracker](https://bugs.kde.org/describecomponents.cgi?product=kdeconnect). + +To request features, join the discussion on the [mailing list](https://mail.kde.org/mailman/listinfo/kdeconnect). diff --git a/KDEConnectMacros.cmake b/KDEConnectMacros.cmake index e2075cd1..bf052ff6 100644 --- a/KDEConnectMacros.cmake +++ b/KDEConnectMacros.cmake @@ -1,35 +1,8 @@ # Copyright 2015 Aleix Pol Gonzalez # Redistribution and use is allowed according to the terms of the BSD license. # Thoroughly inspired in kdevplatform_add_plugin -function(kdeconnect_add_plugin plugin) - set(options ) - set(oneValueArgs JSON) - set(multiValueArgs SOURCES) - cmake_parse_arguments(KC_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - get_filename_component(json "${KC_ADD_PLUGIN_JSON}" REALPATH) - - # ensure we recompile the corresponding object files when the json file changes - set(dependent_sources ) - foreach(source ${KC_ADD_PLUGIN_SOURCES}) - get_filename_component(source "${source}" REALPATH) - if(EXISTS "${source}") - file(STRINGS "${source}" match REGEX "K_PLUGIN_FACTORY_WITH_JSON") - if(match) - list(APPEND dependent_sources "${source}") - endif() - endif() - endforeach() - if(NOT dependent_sources) - # fallback to all sources - better safe than sorry... - set(dependent_sources ${KC_ADD_PLUGIN_SOURCES}) - endif() - set_property(SOURCE ${dependent_sources} APPEND PROPERTY OBJECT_DEPENDS ${json}) - - add_library(${plugin} MODULE ${KC_ADD_PLUGIN_SOURCES}) - set_property(TARGET ${plugin} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${json}) - - install(TARGETS ${plugin} DESTINATION ${PLUGIN_INSTALL_DIR}/kdeconnect) +function(kdeconnect_add_plugin) + kcoreaddons_add_plugin(${ARGN} INSTALL_NAMESPACE kdeconnect) endfunction() diff --git a/README.md b/README.md index eeaec404..bd4510db 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,65 @@ # KDE Connect - desktop app KDE Connect is a multi-platform app that allows your devices to communicate (eg: your phone and your computer). ## (Some) Features - **Shared clipboard**: copy and paste between your phone and your computer (or any other device). -- **Notification sync**: Read your Android notifications from the desktop. +- **Notification sync**: Read and reply to your Android notifications from the desktop. - **Share files and URLs** instantly from one device to another. - **Multimedia remote control**: Use your phone as a remote for Linux media players. -- **Virtual touchpad**: Use your phone screen as your computer's touchpad. +- **Virtual touchpad**: Use your phone screen as your computer's touchpad and keyboard. -All this without wires, over the already existing WiFi network, and using a secure, encrypted protocol. +All this without wires, over the already existing WiFi network, and using TLS encryption. ## Supported platforms -- Computers running Plasma 5, KDE4, Unity (Ubuntu), Gnome 3, Elementary OS... +- Computers running Linux with Plasma 5, Gnome 3, Elementary OS... - Android, by installing the [KDE Connect Android app](https://play.google.com/store/apps/details?id=org.kde.kdeconnect_tp) (also available on [F-Droid](https://f-droid.org/repository/browse/?fdid=org.kde.kdeconnect_tp)). -There is also source code for an unmaintained iOS port, waiting for somebody to give it some love :) - ## How to install This explains how to install KDE Connect on your computer. You will also need to install it in your phone and pair both devices in the app if you want it to be any useful. ### On Linux Look in your distribution repo for a package called `kdeconnect-kde`, `kdeconnect-plasma`, or just `kdeconnect`. If it's not there and you know how to build software from sources, you just found the repo :) -If you are not using Plasma 5 or KDE4, you will also need to install [indicator-kdeconnect](https://github.com/vikoadi/indicator-kdeconnect) (available as an [Ubuntu PPA](https://code.launchpad.net/~vikoadi/+archive/ubuntu/ppa/])) for integration with other desktops using appindicator. +If you are not using Plasma 5, you might want to install a user interface for your platform. The [indicator-kdeconnect](https://github.com/bajoja/indicator-kdeconnect) project provides an appindicator icon plus integration with Nautilus. ### On Mac or Windows -There is no support for Mac or Windows yet. The last time I checked it was compiling on Windows, so it's only lacking a user interface. Same for Mac. Contributions welcome! +Platforms other than Linux are not officially supported, but it should be possible to run KDE Connect there. You can compile KDE Connect for Windows using [Craft](https://community.kde.org/Craft) and it works except for some plugins. I haven't tried on Mac. Contributions welcome! ### On BSD -It should work, but no promises. +It should work, but no promises :) ## How does it work? KDE Connect consists of an UI-agnostic "core" library which exposes a series of DBus interfaces, and several UI components that consume these DBus interfaces. This way, new UI components can be added to integrate better with specific platforms or desktops, without having to reimplement the protocol or any of the internals. The core KDE Connect library is also divided in 4 big blocks: - **LinkProviders**: Are in charge of discovering other KDE Connect-enabled devices in the network and establishing a Link to them. - **Devices**: Represent a remote device, abstracting the specific Link that is being used to reach it. - **NetworkPackets**: JSON-serializable and self-contained pieces of information to be sent by the plugins between devices. - **Plugins**: Independent pieces of code which implement a specific feature. Plugins will use NetworkPackets to exchange information through the network with other Plugins on a remote Device. -The basic structure of a NetworkPacket (before encryption) is the following: +The basic structure of a NetworkPacket is the following: ``` { "id": 123456789, "type": "com.example.myplugin", "body": { }, "version": 5 } ``` -The content of the `"body"` section is defined by each Plugin. Hence, only the only the emisor and receiver plugins of a given packet type need agree on the contents of the body. +The content of the `"body"` section is defined by each Plugin. Hence, only the emisor and receiver plugins of a given packet type need agree on the contents of the body. NetworkPackets can also have binary data attached that can't be serialized to JSON. In this case, two new fields will be added: `"payloadSize"`: The size of the file, or -1 if it is a stream without known size. `"payloadTransferInfo"`: Another JSON object where the specific Link can add information so the Link in the remote end can establish a connection and receive the payload (eg: IP and port in a local network). It's up to the Link implementation to decide how to use this field. +## Contributing + +To contribute patches, use [KDE Connect's Phabricator](https://phabricator.kde.org/project/profile/159/). There you can also find a task list with stuff to do, and links to other relevant resources. It is a good idea to also subscribe to the [KDE Connect mailing list](https://mail.kde.org/mailman/listinfo/kdeconnect). + ## License [GNU GPL v2](https://www.gnu.org/licenses/gpl-2.0.html) and [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html) -If you are reading this from Github, you should know that this is just a mirror of the [KDE Project repo](https://projects.kde.org/projects/playground/base/kdeconnect-kde/repository/). - -[![Build Status](https://build.kde.org/buildStatus/icon?job=kdeconnect-kde master kf5-qt5)](https://build.kde.org/job/kdeconnect-kde%20master%20kf5-qt5/) +If you are reading this from Github, you should know that this is just a mirror of the [KDE Project repo](https://cgit.kde.org/kdeconnect-kde.git). diff --git a/app/main.cpp b/app/main.cpp index b23bf705..28a0ee14 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,49 +1,51 @@ /* * Copyright (C) 2014 Aleix Pol Gonzalez * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { QApplication app(argc, argv); - KAboutData aboutData("kdeconnect-kde", i18n("Awesome App"), "1.0", i18n("KDE Connect App"), KAboutLicense::GPL, i18n("(c) 2015, Aleix Pol Gonzalez")); - aboutData.addAuthor(i18n("Aleix Pol Gonzalez"), i18n("Maintainer"), "aleixpol@kde.org"); + KAboutData aboutData(QStringLiteral("kdeconnect.app"), i18n("KDE Connect App"), QStringLiteral("1.0"), i18n("KDE Connect App"), KAboutLicense::GPL, i18n("(c) 2015, Aleix Pol Gonzalez")); + aboutData.addAuthor(i18n("Aleix Pol Gonzalez"), i18n("Maintainer"), QStringLiteral("aleixpol@kde.org")); KAboutData::setApplicationData(aboutData); { QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addVersionOption(); parser.addHelpOption(); parser.process(app); aboutData.processCommandLine(&parser); } - QQmlApplicationEngine engine(QUrl("qrc:/qml/main.qml")); + QQmlApplicationEngine engine; KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(&engine); kdeclarative.setupBindings(); + engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + return app.exec(); } diff --git a/app/org.kde.kdeconnect.app.desktop b/app/org.kde.kdeconnect.app.desktop index 838f8ff7..9d46a146 100644 --- a/app/org.kde.kdeconnect.app.desktop +++ b/app/org.kde.kdeconnect.app.desktop @@ -1,57 +1,118 @@ [Desktop Entry] Name=KDE Connect Application +Name[ar]=تطبيق كدي المتّصل +Name[ast]=Aplicación KDE Connect Name[ca]=Aplicació KDE Connect +Name[ca@valencia]=Aplicació KDE Connect Name[cs]=Aplikace KDE Connect +Name[da]=Programmet KDE Connect Name[de]=KDE-Connect-Anwendung +Name[el]=Εφαρμογή KDE Connect +Name[en_GB]=KDE Connect Application Name[es]=Aplicación KDE Connect +Name[et]=KDE Connecti rakendus +Name[eu]=KDE Connect aplikazioa Name[fi]=KDE Connect -sovellus -Name[gl]=Programa de KDE Connect +Name[fr]=Application KDE Connect +Name[gl]=Aplicativo de KDE Connect +Name[he]=היישום KDE Connect +Name[hu]=KDE Connect alkalmazás Name[it]=Applicazione KDE Connect +Name[ko]=KDE Connect 프로그램 Name[nl]=KDE Connect-toepassing +Name[nn]=KDE Connect-program Name[pl]=Program KDE Connect Name[pt]=Aplicação do KDE Connect Name[pt_BR]=Aplicativo KDE Connect +Name[ru]=Приложение KDE Connect Name[sk]=KDE Connect aplikácia +Name[sr]=КДЕ‑конекцијин програм +Name[sr@ijekavian]=КДЕ‑конекцијин програм +Name[sr@ijekavianlatin]=KDE‑konekcijin program +Name[sr@latin]=KDE‑konekcijin program Name[sv]=Programmet KDE-anslut -Name[tr]=KDE Bağlantı Uygulaması +Name[tr]=KDE Connect Uygulaması Name[uk]=Програма KDE Connect Name[x-test]=xxKDE Connect Applicationxx +Name[zh_CN]=KDE Connect 应用程序 +Name[zh_TW]=KDE 連線應用程式 GenericName=Device Synchronization +GenericName[ar]=مزامنة الأجهزة +GenericName[ast]=Sincronización de preseos GenericName[ca]=Sincronització de dispositius +GenericName[ca@valencia]=Sincronització de dispositius GenericName[cs]=Synchronizace zařízení +GenericName[da]=Enhedssynkronisering GenericName[de]=Geräteabgleich +GenericName[el]=Συγχρονισμός συσκευών +GenericName[en_GB]=Device Synchronisation GenericName[es]=Sincronización de dispositivos +GenericName[et]=Seadmete sünkroonimine +GenericName[eu]=Galuak sinkronizatzea GenericName[fi]=Laitteiden synkronointi +GenericName[fr]=Synchronisation de périphériques GenericName[gl]=Sincronización de dispositivos +GenericName[he]=סנכרון התקן +GenericName[hu]=Eszközszinkronizáció GenericName[it]=Sincronizzazione dispositivo +GenericName[ko]=장치 동기화 +GenericName[lt]=Įrenginių sinchronizavimas GenericName[nl]=Synchronisatie van apparaat +GenericName[nn]=Einingssynkronisering GenericName[pl]=Synchronizacja urządzenia GenericName[pt]=Sincronização de Dispositivos GenericName[pt_BR]=Sincronização de dispositivo +GenericName[ru]=Синхронизация с мобильным устройством GenericName[sk]=Synchronizácia zariadení +GenericName[sr]=Синхронизација уређаја +GenericName[sr@ijekavian]=Синхронизација уређаја +GenericName[sr@ijekavianlatin]=Sinhronizacija uređaja +GenericName[sr@latin]=Sinhronizacija uređaja GenericName[sv]=Enhetssynkronisering GenericName[tr]=Aygıt Eşzamanlaması GenericName[uk]=Синхронізація із пристроями GenericName[x-test]=xxDevice Synchronizationxx +GenericName[zh_CN]=设备同步 +GenericName[zh_TW]=設備同步 Comment=Make all your devices one +Comment[ar]=اجعل أجهزتك كلّها واحدًا +Comment[ast]=Fai tolos tos preseos ún Comment[ca]=Fa que tots els vostres dispositius siguin un +Comment[ca@valencia]=Fa que tots els vostres dispositius siguen un Comment[cs]=Sjednoťte svá zařízení +Comment[da]=Gør alle dine enheder til en Comment[de]=Gleichen Sie alle Ihre Geräte ab +Comment[el]=Όλες οι συσκευές σας σε μία +Comment[en_GB]=Make all your devices one Comment[es]=Convertir todos sus dispositivos en uno +Comment[et]=Kõigi seadmete ühendamine +Comment[eu]=Bat egin zure gailu guztiak Comment[fi]=Yhdistä kaikki laitteesi toisiinsa +Comment[fr]=Unifiez vos périphériques Comment[gl]=Unifique os seus dispositivos. +Comment[he]=הפוך את כך ההתקנים שלך לאחד +Comment[hu]=Egyesítse eszközeit Comment[it]=Fai di tutti i tuoi dispositivi un solo dispositivo +Comment[ko]=모든 장치를 하나로 동기화 Comment[nl]=Al uw apparaten een maken +Comment[nn]=Slå alle einingane dine saman Comment[pl]=Uczyń wszystkie swoje urządzenia jednym Comment[pt]=Unifique todos os seus dispositivos Comment[pt_BR]=Torne todos os seus dispositivos em um +Comment[ru]=Объедините все ваши устройства Comment[sk]=Urobiť z vašich zariadení jedno +Comment[sr]=Обједините своје уређаје +Comment[sr@ijekavian]=Обједините своје уређаје +Comment[sr@ijekavianlatin]=Objedinite svoje uređaje +Comment[sr@latin]=Objedinite svoje uređaje Comment[sv]=Gör alla enheter till en Comment[tr]=Tüm aygıtlarınızı birleştirin Comment[uk]=Поєднайте усі ваші пристрої Comment[x-test]=xxMake all your devices onexx +Comment[zh_CN]=连接您所有设备 +Comment[zh_TW]=讓您的設備內容全面同步 Exec=kcapp Icon=kdeconnect Type=Application Terminal=false Categories=Qt;KDE;Network diff --git a/app/qml/DevicePage.qml b/app/qml/DevicePage.qml new file mode 100644 index 00000000..23c9fce5 --- /dev/null +++ b/app/qml/DevicePage.qml @@ -0,0 +1,107 @@ +/* + * 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.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import org.kde.kirigami 2.0 as Kirigami +import org.kde.kdeconnect 1.0 + +Kirigami.Page +{ + id: deviceView + property QtObject currentDevice + title: currentDevice.name + + actions.contextualActions: deviceLoader.item.actions + + Loader { + id: deviceLoader + anchors.fill: parent + Layout.fillHeight: true + Layout.fillWidth: true + + sourceComponent: deviceView.currentDevice.isTrusted ? trustedDevice : untrustedDevice + Component { + id: trustedDevice + ColumnLayout { + property list actions: [ + Kirigami.Action { + onTriggered: deviceView.currentDevice.unpair() + text: i18n("Unpair") + }, + Kirigami.Action { + text: i18n("Send Ping") + onTriggered: { + deviceView.currentDevice.pluginCall("ping", "sendPing"); + } + } + ] + + id: trustedView + Layout.fillHeight: true + Layout.fillWidth: true + + PluginItem { + label: i18n("Multimedia control") + interfaceFactory: MprisDbusInterfaceFactory + component: "qrc:/qml/mpris.qml" + pluginName: "mprisremote" + } + PluginItem { + label: i18n("Remote input") + interfaceFactory: RemoteControlDbusInterfaceFactory + component: "qrc:/qml/mousepad.qml" + pluginName: "remotecontrol" + } + PluginItem { + readonly property var lockIface: LockDeviceDbusInterfaceFactory.create(deviceView.currentDevice.id()) + pluginName: "lockdevice" + label: lockIface.isLocked ? i18n("Unlock") : i18n("Lock") + onClicked: { + lockIface.isLocked = !lockIface.isLocked; + } + } + PluginItem { + readonly property var findmyphoneIface: FindMyPhoneDbusInterfaceFactory.create(deviceView.currentDevice.id()) + pluginName: "findmyphone" + label: i18n("Find Device") + onClicked: { + findmyphoneIface.ring() + } + } + + Item { Layout.fillHeight: true } + } + } + Component { + id: untrustedDevice + Item { + readonly property var actions: [] + Button { + anchors.centerIn: parent + + text: i18n("Pair") + onClicked: deviceView.currentDevice.requestPair() + } + } + } + } +} diff --git a/app/qml/FindDevicesPage.qml b/app/qml/FindDevicesPage.qml new file mode 100644 index 00000000..a2d8284b --- /dev/null +++ b/app/qml/FindDevicesPage.qml @@ -0,0 +1,68 @@ +/* + * Copyright 2016 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.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import org.kde.kirigami 2.0 as Kirigami +import org.kde.kdeconnect 1.0 + +Kirigami.Page +{ + objectName: "FindDevices" + title: i18n("Pair") + ScrollView { + anchors.fill: parent + ListView { + section { + property: "status" + delegate: Label { + text: switch (parseInt(section)) + { + case DevicesModel.Paired: + return i18n("Paired") + case DevicesModel.Reachable: + return i18n("Reachable") + case (DevicesModel.Reachable | DevicesModel.Paired): + return i18n("Paired & Reachable") + } + + } + } + + model: DevicesSortProxyModel { + sourceModel: DevicesModel { displayFilter: DevicesModel.Reachable } + } + delegate: Kirigami.BasicListItem { + width: ListView.view.width + icon: iconName + label: display + "\n" + toolTip + enabled: !(status & DevicesModel.Paired) + onClicked: { + pageStack.clear() + pageStack.push( + "qrc:/qml/DevicePage.qml", + {currentDevice: device} + ); + } + } + } + } +} diff --git a/app/qml/DeviceDelegate.qml b/app/qml/PluginItem.qml similarity index 58% copy from app/qml/DeviceDelegate.qml copy to app/qml/PluginItem.qml index 2b528ca0..b0d55ddd 100644 --- a/app/qml/DeviceDelegate.qml +++ b/app/qml/PluginItem.qml @@ -1,56 +1,49 @@ /* * 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.2 -import QtQuick.Controls 1.2 +import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 -import org.kde.kquickcontrolsaddons 2.0 +import org.kde.kirigami 2.0 as Kirigami import org.kde.kdeconnect 1.0 -Item +Kirigami.BasicListItem { - height: info.height - signal clicked - QIconItem { - id: icon - width: 40 - height: parent.height - icon: iconName - anchors.verticalCenter: parent.verticalCenter - } - MouseArea { - anchors.fill: parent - onClicked: parent.clicked() + property alias pluginName: checker.pluginName + property var interfaceFactory + property string component + + readonly property var checker: PluginChecker { + id: checker + device: deviceView.currentDevice } - ColumnLayout { - id: info - anchors { - left: icon.right - top: parent.top - right: parent.right - } - property bool expand: false - Label { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: display + "\n" + toolTip - } + visible: checker.available + onClicked: { + if (component === "") + return; + + var obj = interfaceFactory.create(checker.deviceId); + var page = pageStack.push( + component, + { pluginInterface: obj } + ); + obj.parent = page } } diff --git a/app/qml/main.qml b/app/qml/main.qml index 441e21ed..3957c9d3 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -1,160 +1,119 @@ /* * 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.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 +import org.kde.kirigami 2.0 as Kirigami import org.kde.kdeconnect 1.0 -ApplicationWindow +Kirigami.ApplicationWindow { id: root visible: true - width: 400 + width: 900 height: 500 - title: i18n("KDE Connect") - toolBar: RowLayout { - Button { - iconName: "go-previous" - enabled: stack.depth>1 - onClicked: stack.pop() - } - Label { - Layout.fillWidth: true - text: "KDE Connect" - font.pointSize: 20 - } + Component { + id: findDevicesComp + FindDevicesPage {} } - StackView { - id: stack - anchors { - fill: parent - margins: 5 - } - initialItem: ScrollView { - Layout.fillHeight: true - ListView { - id: devicesView - section { - property: "status" - delegate: Label { - text: switch (parseInt(section)) - { - case DevicesModel.Paired: - return i18n("Paired") - case DevicesModel.Reachable: - return i18n("Reachable") - case (DevicesModel.Reachable | DevicesModel.Paired): - return i18n("Paired & Reachable") - } + Component { + id: deviceComp + DevicePage {} + } - } - } + Kirigami.Action { + id: findDevicesAction + text: i18n ("Find devices...") + iconName: "list-add" + checked: pageStack.currentItem && pageStack.currentItem.objectName == "FindDevices" - spacing: 5 - model: DevicesSortProxyModel { - sourceModel: DevicesModel { - id: connectDeviceModel - displayFilter: DevicesModel.Reachable - } - } - delegate: DeviceDelegate { - width: parent.width - onClicked: { - var data = { - item: deviceViewComponent, - properties: {currentDevice: device} - }; - stack.push(data); - } - } - } + onTriggered: { + root.pageStack.clear() + root.pageStack.push(findDevicesComp); } } - Component { - id: deviceViewComponent - ColumnLayout { - id: deviceView - property QtObject currentDevice - Loader { - Layout.fillHeight: true - Layout.fillWidth: true + globalDrawer: Kirigami.GlobalDrawer { + id: drawer + title: i18n("KDE Connect") + titleIcon: "kdeconnect" +// bannerImageSource: "/home/apol/devel/kde5/share/wallpapers/Next/contents/images/1024x768.png" - sourceComponent: currentDevice.isPaired ? trustedDevice : untrustedDevice - Component { - id: trustedDevice - ColumnLayout { - id: trustedView - Layout.fillHeight: true - Layout.fillWidth: true + topContent: [ + TextField { + Layout.fillWidth: true - Button { - text: i18n("Un-Pair") - onClicked: deviceView.currentDevice.unpair() - } - Button { - text: i18n("Send Ping") - onClicked: deviceView.currentDevice.pluginCall("ping", "sendPing"); - } - Button { - text: i18n("Open Multimedia Remote Control") - onClicked: stack.push( { - item: "qrc:/qml/mpris.qml", - properties: { mprisInterface: MprisDbusInterfaceFactory.create(deviceView.currentDevice.id()) } - } ); - } - Button { - text: i18n("Mouse Pad") - onClicked: stack.push( { - item: "qrc:/qml/mousepad.qml", - properties: { remoteControlInterface: RemoteControlDbusInterfaceFactory.create(deviceView.currentDevice.id()) } - } ); - } - Button { - property var lockIface: LockDeviceDbusInterfaceFactory.create(deviceView.currentDevice.id()) - text: lockIface.isLocked ? i18n("Unlock") : i18n("Lock") - onClicked: { - lockIface.isLocked = !lockIface.isLocked; - } - } + DBusProperty { + id: announcedNameProperty + object: DaemonDbusInterface + read: "announcedName" + defaultValue: "" + } - Item { Layout.fillHeight: true } - } + text: announcedNameProperty.value + onAccepted: { + DaemonDbusInterface.setAnnouncedName(text) + text = Qt.binding(function() {return announcedNameProperty.value}) + } + } + ] + property var objects: [findDevicesAction] + Instantiator { + model: DevicesSortProxyModel { + sourceModel: DevicesModel { displayFilter: DevicesModel.Paired } + } + delegate: Kirigami.Action { + iconName: model.iconName + text: display + "\n" + toolTip + enabled: status & DevicesModel.Reachable + checked: pageStack.currentItem && pageStack.currentItem.currentDevice == device + onTriggered: { + root.pageStack.clear() + root.pageStack.push( + deviceComp, + {currentDevice: device} + ); } - Component { - id: untrustedDevice - ColumnLayout { - id: untrustedView - Layout.fillHeight: true - Layout.fillWidth: true + } - Button { - text: i18n("Pair") - onClicked: deviceView.currentDevice.requestPair() - } - } + onObjectAdded: { + drawer.objects.push(object) + drawer.objects = drawer.objects + } + onObjectRemoved: { + var idx = drawer.objects.indexOf(object); + if (idx>=0) { + var removed = drawer.objects.splice(idx, 1) + drawer.objects = drawer.objects } } } + actions: objects } + + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + } + + pageStack.initialPage: findDevicesComp } diff --git a/app/qml/mousepad.qml b/app/qml/mousepad.qml index 6586f78a..3e10c89e 100644 --- a/app/qml/mousepad.qml +++ b/app/qml/mousepad.qml @@ -1,64 +1,74 @@ /* * 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.2 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 +import org.kde.kirigami 2.0 as Kirigami -ColumnLayout +Kirigami.Page { id: mousepad - property QtObject remoteControlInterface + title: i18n("Remote Control") + property QtObject pluginInterface - MouseArea { - id: area - Layout.fillWidth: true - Layout.fillHeight: true - property var lastPos: Qt.point(-1, -1) + ColumnLayout + { + anchors.fill: parent - onClicked: mousepad.remoteControlInterface.sendCommand("singleclick", true); + MouseArea { + id: area + Layout.fillWidth: true + Layout.fillHeight: true + property var lastPos: Qt.point(-1, -1) - 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); + onClicked: mousepad.pluginInterface.sendCommand("singleclick", true); - remoteControlInterface.moveCursor(delta); - } - lastPos = Qt.point(mouse.x, mouse.y); - } - } - RowLayout { - Layout.fillWidth: 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); - Button { - Layout.fillWidth: true - onClicked: mousepad.remoteControlInterface.sendCommand("singleclick", true); - } - Button { - Layout.fillWidth: true - onClicked: mousepad.remoteControlInterface.sendCommand("middleclick", true); + pluginInterface.moveCursor(delta); + } + lastPos = Qt.point(mouse.x, mouse.y); + } + onReleased: { + lastPos = Qt.point(-1, -1) + } } - Button { + RowLayout { Layout.fillWidth: true - onClicked: mousepad.remoteControlInterface.sendCommand("rightclick", true); + + Button { + Layout.fillWidth: true + onClicked: mousepad.pluginInterface.sendCommand("singleclick", true); + } + Button { + Layout.fillWidth: true + onClicked: mousepad.pluginInterface.sendCommand("middleclick", true); + } + Button { + Layout.fillWidth: true + onClicked: mousepad.pluginInterface.sendCommand("rightclick", true); + } } } } diff --git a/app/qml/mpris.qml b/app/qml/mpris.qml index 0dd66f2c..fe2c2ed7 100644 --- a/app/qml/mpris.qml +++ b/app/qml/mpris.qml @@ -1,72 +1,79 @@ /* * 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.2 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 +import org.kde.kirigami 2.0 as Kirigami -ColumnLayout +Kirigami.Page { id: root - property QtObject mprisInterface + property QtObject pluginInterface + title: i18n("Multimedia Controls") - Component.onCompleted: { - mprisInterface.requestPlayerList(); - } + ColumnLayout + { + anchors.fill: parent - Item { Layout.fillHeight: true } - ComboBox { - Layout.fillWidth: true - model: root.mprisInterface.playerList - onCurrentTextChanged: root.mprisInterface.player = currentText - } - Label { - Layout.fillWidth: true - text: root.mprisInterface.nowPlaying - } - RowLayout { - Layout.fillWidth: true - Button { + Component.onCompleted: { + pluginInterface.requestPlayerList(); + } + + Item { Layout.fillHeight: true } + ComboBox { Layout.fillWidth: true - iconName: "media-skip-backward" - onClicked: root.mprisInterface.sendAction("Previous") + model: root.pluginInterface.playerList + onCurrentTextChanged: root.pluginInterface.player = currentText } - Button { + Label { Layout.fillWidth: true - iconName: root.mprisInterface.isPlaying ? "media-playback-pause" : "media-playback-start" - onClicked: root.mprisInterface.sendAction("PlayPause"); + text: root.pluginInterface.nowPlaying } - Button { + RowLayout { Layout.fillWidth: true - iconName: "media-skip-forward" - onClicked: root.mprisInterface.sendAction("Next") + Button { + Layout.fillWidth: true + iconName: "media-skip-backward" + onClicked: root.pluginInterface.sendAction("Previous") + } + Button { + Layout.fillWidth: true + iconName: root.pluginInterface.isPlaying ? "media-playback-pause" : "media-playback-start" + onClicked: root.pluginInterface.sendAction("PlayPause"); + } + Button { + Layout.fillWidth: true + iconName: "media-skip-forward" + onClicked: root.pluginInterface.sendAction("Next") + } } - } - RowLayout { - Layout.fillWidth: true - Label { text: i18n("Volume:") } - Slider { - value: root.mprisInterface.volume - maximumValue: 100 + RowLayout { Layout.fillWidth: true + Label { text: i18n("Volume:") } + Slider { + value: root.pluginInterface.volume + maximumValue: 100 + Layout.fillWidth: true + } } + Item { Layout.fillHeight: true } } - Item { Layout.fillHeight: true } } diff --git a/app/resources.qrc b/app/resources.qrc index 225f8513..924e2582 100644 --- a/app/resources.qrc +++ b/app/resources.qrc @@ -1,8 +1,10 @@ qml/main.qml - qml/DeviceDelegate.qml qml/mpris.qml qml/mousepad.qml + qml/PluginItem.qml + qml/DevicePage.qml + qml/FindDevicesPage.qml diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 7ddf635c..482a98a4 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -1,6 +1,12 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-cli\") add_executable(kdeconnect-cli kdeconnect-cli.cpp) +ecm_mark_nongui_executable(kdeconnect-cli) -target_link_libraries(kdeconnect-cli kdeconnectinterfaces KF5::CoreAddons KF5::I18n) +target_link_libraries(kdeconnect-cli + kdeconnectinterfaces + KF5::CoreAddons + KF5::I18n +) install(TARGETS kdeconnect-cli ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/cli/kdeconnect-cli.cpp b/cli/kdeconnect-cli.cpp index 536c7549..c3960352 100644 --- a/cli/kdeconnect-cli.cpp +++ b/cli/kdeconnect-cli.cpp @@ -1,156 +1,250 @@ /* * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include #include #include #include #include +#include #include -#include #include "interfaces/devicesmodel.h" #include "interfaces/notificationsmodel.h" #include "interfaces/dbusinterfaces.h" +#include "interfaces/dbushelpers.h" #include "kdeconnect-version.h" int main(int argc, char** argv) { QCoreApplication app(argc, argv); - KAboutData about("kdeconnect-cli", - "kdeconnect-cli", - QLatin1String(KDECONNECT_VERSION_STRING), + KAboutData about(QStringLiteral("kdeconnect-cli"), + QStringLiteral("kdeconnect-cli"), + QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect CLI tool"), KAboutLicense::GPL, i18n("(C) 2015 Aleix Pol Gonzalez")); KAboutData::setApplicationData(about); - about.addAuthor( i18n("Aleix Pol Gonzalez"), QString(), "aleixpol@kde.org" ); - about.addAuthor( i18n("Albert Vaca Cintora"), QString(), "albertvaka@gmail.com" ); + about.addAuthor( i18n("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org") ); + about.addAuthor( i18n("Albert Vaca Cintora"), QString(), QStringLiteral("albertvaka@gmail.com") ); QCommandLineParser parser; - parser.addOption(QCommandLineOption(QStringList("l") << "list-devices", i18n("List all devices"))); - parser.addOption(QCommandLineOption(QStringList("a") << "list-available", i18n("List available (paired and reachable) devices"))); - parser.addOption(QCommandLineOption("id-only", i18n("Make --list-devices or --list-available print only the devices id, to ease scripting"))); - parser.addOption(QCommandLineOption("refresh", i18n("Search for devices in the network and re-establish connections"))); - parser.addOption(QCommandLineOption("pair", i18n("Request pairing to a said device"))); - parser.addOption(QCommandLineOption("unpair", i18n("Stop pairing to a said device"))); - parser.addOption(QCommandLineOption("ping", i18n("Sends a ping to said device"))); - parser.addOption(QCommandLineOption("ping-msg", i18n("Same as ping but you can set the message to display"), i18n("message"))); - parser.addOption(QCommandLineOption("share", i18n("Share a file to a said device"), "path")); - parser.addOption(QCommandLineOption("list-notifications", i18n("Display the notifications on a said device"))); - parser.addOption(QCommandLineOption("lock", i18n("Lock the specified device"))); - parser.addOption(QCommandLineOption(QStringList("device") << "d", i18n("Device ID"), "dev")); + parser.addOption(QCommandLineOption(QStringList(QStringLiteral("l")) << QStringLiteral("list-devices"), i18n("List all devices"))); + parser.addOption(QCommandLineOption(QStringList(QStringLiteral("a")) << QStringLiteral("list-available"), i18n("List available (paired and reachable) devices"))); + parser.addOption(QCommandLineOption(QStringLiteral("id-only"), i18n("Make --list-devices or --list-available print only the devices id, to ease scripting"))); + parser.addOption(QCommandLineOption(QStringLiteral("refresh"), i18n("Search for devices in the network and re-establish connections"))); + parser.addOption(QCommandLineOption(QStringLiteral("pair"), i18n("Request pairing to a said device"))); + parser.addOption(QCommandLineOption(QStringLiteral("ring"), i18n("Find the said device by ringing it."))); + parser.addOption(QCommandLineOption(QStringLiteral("unpair"), i18n("Stop pairing to a said device"))); + parser.addOption(QCommandLineOption(QStringLiteral("ping"), i18n("Sends a ping to said device"))); + parser.addOption(QCommandLineOption(QStringLiteral("ping-msg"), i18n("Same as ping but you can set the message to display"), i18n("message"))); + parser.addOption(QCommandLineOption(QStringLiteral("share"), i18n("Share a file to a said device"), QStringLiteral("path"))); + parser.addOption(QCommandLineOption(QStringLiteral("list-notifications"), i18n("Display the notifications on a said device"))); + parser.addOption(QCommandLineOption(QStringLiteral("lock"), i18n("Lock the specified device"))); + parser.addOption(QCommandLineOption(QStringLiteral("send-sms"), i18n("Sends an SMS. Requires destination"), i18n("message"))); + parser.addOption(QCommandLineOption(QStringLiteral("destination"), i18n("Phone number to send the message"), i18n("phone number"))); + parser.addOption(QCommandLineOption(QStringList(QStringLiteral("device")) << QStringLiteral("d"), i18n("Device ID"), QStringLiteral("dev"))); + parser.addOption(QCommandLineOption(QStringList(QStringLiteral("name")) << QStringLiteral("n"), i18n("Device Name"), QStringLiteral("name"))); + parser.addOption(QCommandLineOption(QStringLiteral("encryption-info"), i18n("Get encryption info about said device"))); + parser.addOption(QCommandLineOption(QStringLiteral("list-commands"), i18n("Lists remote commands and their ids"))); + parser.addOption(QCommandLineOption(QStringLiteral("execute-command"), i18n("Executes a remote command by id"), QStringLiteral("id"))); + parser.addOption(QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("send-keys")}, i18n("Sends keys to a said device"))); + parser.addOption(QCommandLineOption(QStringLiteral("my-id"), i18n("Display this device's id and exit"))); about.setupCommandLine(&parser); parser.addHelpOption(); parser.process(app); about.processCommandLine(&parser); - if(parser.isSet("l") || parser.isSet("a")) { - DaemonDbusInterface iface; + const QString id = "kdeconnect-cli-"+QString::number(QCoreApplication::applicationPid()); + DaemonDbusInterface iface; + + if (parser.isSet(QStringLiteral("my-id"))) { + QTextStream(stdout) << iface.selfId() << endl; + } else if (parser.isSet(QStringLiteral("l")) || parser.isSet(QStringLiteral("a"))) { bool paired = true, reachable = false; - if (parser.isSet("a")) { + if (parser.isSet(QStringLiteral("a"))) { reachable = true; + } else { + blockOnReply(iface.acquireDiscoveryMode(id)); + QThread::sleep(2); } - QDBusPendingReply reply = iface.devices(paired, reachable); - reply.waitForFinished(); + const QStringList devices = blockOnReply(iface.devices(reachable, paired)); - const QStringList devices = reply.value(); - foreach (const QString& id, devices) { - if (parser.isSet("id-only")) { + for (const QString& id : devices) { + if (parser.isSet(QStringLiteral("id-only"))) { QTextStream(stdout) << id << endl; } else { DeviceDbusInterface deviceIface(id); QString statusInfo; - const bool isReachable = deviceIface.isReachable(), isPaired = deviceIface.isPaired(); - if (isReachable && isPaired) { + const bool isReachable = deviceIface.isReachable(); + const bool isTrusted = deviceIface.isTrusted(); + if (isReachable && isTrusted) { statusInfo = i18n("(paired and reachable)"); } else if (isReachable) { statusInfo = i18n("(reachable)"); - } else if (isPaired) + } else if (isTrusted) { statusInfo = i18n("(paired)"); + } QTextStream(stdout) << "- " << deviceIface.name() << ": " << deviceIface.id() << ' ' << statusInfo << endl; } } - if (!parser.isSet("id-only")) { + if (!parser.isSet(QStringLiteral("id-only"))) { QTextStream(stdout) << i18np("1 device found", "%1 devices found", devices.size()) << endl; } else if (devices.isEmpty()) { QTextStream(stderr) << i18n("No devices found") << endl; } - } else if(parser.isSet("refresh")) { - QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect", "org.kde.kdeconnect.daemon", "forceOnNetworkChange"); - QDBusConnection::sessionBus().call(msg); + + blockOnReply(iface.releaseDiscoveryMode(id)); + } else if(parser.isSet(QStringLiteral("refresh"))) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect"), QStringLiteral("org.kde.kdeconnect.daemon"), QStringLiteral("forceOnNetworkChange")); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); } else { - QString device; - if(!parser.isSet("device")) { + + QString device = parser.value(QStringLiteral("device")); + if (device.isEmpty() && parser.isSet(QStringLiteral("name"))) { + device = blockOnReply(iface.deviceIdByName(parser.value(QStringLiteral("name")))); + if (device.isEmpty()) { + QTextStream(stderr) << "Couldn't find device: " << parser.value(QStringLiteral("name")) << endl; + return 1; + } + } + if(device.isEmpty()) { QTextStream(stderr) << i18n("No device specified") << endl; + parser.showHelp(1); } - device = parser.value("device"); - QUrl url; - if(parser.isSet("share")) { - url = QUrl::fromUserInput(parser.value("share"), QDir::currentPath()); + + if(parser.isSet(QStringLiteral("share"))) { + QUrl url = QUrl::fromUserInput(parser.value(QStringLiteral("share")), QDir::currentPath()); parser.clearPositionalArguments(); if(!url.isEmpty() && !device.isEmpty()) { - QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/"+device+"/share", "org.kde.kdeconnect.device.share", "shareUrl"); + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/share", QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); - QDBusConnection::sessionBus().call(msg); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); } else { QTextStream(stderr) << (i18n("Couldn't share %1", url.toString())) << endl; } - } else if(parser.isSet("pair")) { + } else if(parser.isSet(QStringLiteral("pair"))) { DeviceDbusInterface dev(device); - if(dev.isPaired()) + if (!dev.isReachable()) { + //Device doesn't exist, go into discovery mode and wait up to 30 seconds for the device to appear + QEventLoop wait; + QTextStream(stderr) << i18n("waiting for device...") << endl; + blockOnReply(iface.acquireDiscoveryMode(id)); + + QObject::connect(&iface, &DaemonDbusInterface::deviceAdded, [&](const QString& deviceAddedId) { + if (device == deviceAddedId) { + wait.quit(); + } + }); + QTimer::singleShot(30 * 1000, &wait, &QEventLoop::quit); + + wait.exec(); + } + + if (!dev.isReachable()) { + QTextStream(stderr) << i18n("Device not found") << endl; + } else if(blockOnReply(dev.isTrusted())) { QTextStream(stderr) << i18n("Already paired") << endl; - else { - QDBusPendingReply req = dev.requestPair(); - req.waitForFinished(); + } else { + QTextStream(stderr) << i18n("Pair requested") << endl; + blockOnReply(dev.requestPair()); } - } else if(parser.isSet("unpair")) { + blockOnReply(iface.releaseDiscoveryMode(id)); + } else if(parser.isSet(QStringLiteral("unpair"))) { DeviceDbusInterface dev(device); - if(!dev.isPaired()) + if (!dev.isReachable()) { + QTextStream(stderr) << i18n("Device does not exist") << endl; + } else if(!dev.isTrusted()) { QTextStream(stderr) << i18n("Already not paired") << endl; - else { - QDBusPendingReply req = dev.unpair(); - req.waitForFinished(); + } else { + QTextStream(stderr) << i18n("Unpaired") << endl; + blockOnReply(dev.unpair()); } - } else if(parser.isSet("ping") || parser.isSet("ping-msg")) { - QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/"+device+"/ping", "org.kde.kdeconnect.device.ping", "sendPing"); - if (parser.isSet("ping-msg")) { - QString message = parser.value("ping-msg"); + } else if(parser.isSet(QStringLiteral("ping")) || parser.isSet(QStringLiteral("ping-msg"))) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/ping", QStringLiteral("org.kde.kdeconnect.device.ping"), QStringLiteral("sendPing")); + if (parser.isSet(QStringLiteral("ping-msg"))) { + QString message = parser.value(QStringLiteral("ping-msg")); msg.setArguments(QVariantList() << message); } - QDBusConnection::sessionBus().call(msg); - } else if(parser.isSet("list-notifications")) { + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); + } else if(parser.isSet(QStringLiteral("send-sms"))) { + if (parser.isSet(QStringLiteral("destination"))) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/telephony", QStringLiteral("org.kde.kdeconnect.device.telephony"), QStringLiteral("sendSms")); + msg.setArguments({ parser.value("destination"), parser.value("send-sms") }); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); + } else { + QTextStream(stderr) << i18n("error: should specify the SMS's recipient by passing --destination "); + return 1; + } + } else if(parser.isSet(QStringLiteral("ring"))) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/findmyphone", QStringLiteral("org.kde.kdeconnect.device.findmyphone"), QStringLiteral("ring")); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); + } else if(parser.isSet("send-keys")) { + QString seq = parser.value("send-keys"); + QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/"+device+"/remotekeyboard", "org.kde.kdeconnect.device.remotekeyboard", "sendKeyPress"); + if (seq.trimmed() == QLatin1String("-")) { + // from file + QFile in; + if(in.open(stdin,QIODevice::ReadOnly | QIODevice::Unbuffered)) { + while (!in.atEnd()) { + QByteArray line = in.readLine(); // sanitize to ASCII-codes > 31? + msg.setArguments({QString(line), -1, false, false, false}); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); + } + in.close(); + } + } else { + msg.setArguments({seq, -1, false, false, false}); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); + } + } else if(parser.isSet(QStringLiteral("list-notifications"))) { NotificationsModel notifications; notifications.setDeviceId(device); for(int i=0, rows=notifications.rowCount(); itoObject(); + QTextStream(stdout) << it.key() << ": " << cont.value(QStringLiteral("name")).toString() << ": " << cont.value(QStringLiteral("command")).toString() << endl; + } + } else if(parser.isSet(QStringLiteral("execute-command"))) { + RemoteCommandsDbusInterface iface(device); + blockOnReply(iface.triggerCommand(parser.value(QStringLiteral("execute-command")))); + } else if(parser.isSet(QStringLiteral("encryption-info"))) { + DeviceDbusInterface dev(device); + QString info = blockOnReply(dev.encryptionInfo()); // QSsl::Der = 1 + QTextStream(stdout) << info << endl; } else { QTextStream(stderr) << i18n("Nothing to be done") << endl; } } QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); return app.exec(); } diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index dd8fedbb..6dcfcc81 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,53 +1,67 @@ project(KDEConnectCore) add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-core\") include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) add_subdirectory(backends/lan) add_subdirectory(backends/loopback) +option(BLUETOOTH_ENABLED "Bluetooth support for kdeconnect" OFF) +if(BLUETOOTH_ENABLED) + find_package(Qt5 REQUIRED COMPONENTS Bluetooth) + add_subdirectory(backends/bluetooth) +endif() + 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 PRIVATE Qt5::DBus Qt5::Gui KF5::I18n KF5::ConfigCore ) +if (BLUETOOTH_ENABLED) + target_compile_definitions(kdeconnectcore PRIVATE -DKDECONNECT_BLUETOOTH) + target_link_libraries(kdeconnectcore PRIVATE Qt5::Bluetooth) +endif() + 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 ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectcore_export.h BASE_NAME KDEConnectCore) +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..9933f332 --- /dev/null +++ b/core/backends/bluetooth/CMakeLists.txt @@ -0,0 +1,12 @@ + +set(backends_kdeconnect_SRCS + ${backends_kdeconnect_SRCS} + + backends/bluetooth/bluetoothlinkprovider.cpp + backends/bluetooth/bluetoothdevicelink.cpp + backends/bluetooth/bluetoothpairinghandler.cpp + backends/bluetooth/bluetoothdownloadjob.cpp + backends/bluetooth/bluetoothuploadjob.cpp + + PARENT_SCOPE +) diff --git a/core/backends/bluetooth/bluetoothdevicelink.cpp b/core/backends/bluetooth/bluetoothdevicelink.cpp new file mode 100644 index 00000000..43a14dcd --- /dev/null +++ b/core/backends/bluetooth/bluetoothdevicelink.cpp @@ -0,0 +1,106 @@ +/** + * Copyright 2016 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "bluetoothdevicelink.h" + +#include "../linkprovider.h" +#include "bluetoothlinkprovider.h" +#include "bluetoothuploadjob.h" +#include "bluetoothdownloadjob.h" +#include "core_debug.h" + +BluetoothDeviceLink::BluetoothDeviceLink(const QString& deviceId, LinkProvider* parent, QBluetoothSocket* socket) + : DeviceLink(deviceId, parent) + , mSocketReader(new DeviceLineReader(socket, this)) + , mBluetoothSocket(socket) + , mPairingHandler(new BluetoothPairingHandler(this)) +{ + connect(mSocketReader, SIGNAL(readyRead()), + this, SLOT(dataReceived())); + + //We take ownership of the socket. + //When the link provider destroys us, + //the socket (and the reader) will be + //destroyed as well + mBluetoothSocket->setParent(this); + connect(mBluetoothSocket, SIGNAL(disconnected()), this, SLOT(deleteLater())); +} + +QString BluetoothDeviceLink::name() +{ + return "BluetoothLink"; // Should be same in both android and kde version +} + +bool BluetoothDeviceLink::sendPackage(NetworkPackage& np) +{ + if (np.hasPayload()) { + qCWarning(KDECONNECT_CORE) << "Sending packages with payload over bluetooth not yet supported"; + /* + BluetoothUploadJob* uploadJob = new BluetoothUploadJob(np.payload(), mBluetoothSocket->peerAddress(), this); + np.setPayloadTransferInfo(uploadJob->transferInfo()); + uploadJob->start(); + */ + } + int written = mSocketReader->write(np.serialize()); + return (written != -1); +} + +void BluetoothDeviceLink::userRequestsPair() { + mPairingHandler->requestPairing(); +} + +void BluetoothDeviceLink::userRequestsUnpair() { + mPairingHandler->unpair(); +} + +bool BluetoothDeviceLink::linkShouldBeKeptAlive() { + return pairStatus() == Paired; +} + +void BluetoothDeviceLink::dataReceived() +{ + if (mSocketReader->bytesAvailable() == 0) return; + + const QByteArray serializedPackage = mSocketReader->readLine(); + + //qCDebug(KDECONNECT_CORE) << "BluetoothDeviceLink dataReceived" << package; + + NetworkPackage package(QString::null); + NetworkPackage::unserialize(serializedPackage, &package); + + if (package.type() == PACKAGE_TYPE_PAIR) { + //TODO: Handle pair/unpair requests and forward them (to the pairing handler?) + mPairingHandler->packageReceived(package); + return; + } + + if (package.hasPayloadTransferInfo()) { + BluetoothDownloadJob* downloadJob = new BluetoothDownloadJob(mBluetoothSocket->peerAddress(), + package.payloadTransferInfo(), this); + downloadJob->start(); + package.setPayload(downloadJob->payload(), package.payloadSize()); + } + + Q_EMIT receivedPackage(package); + + if (mSocketReader->bytesAvailable() > 0) { + QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); + } +} diff --git a/core/backends/lan/landevicelink.h b/core/backends/bluetooth/bluetoothdevicelink.h similarity index 57% copy from core/backends/lan/landevicelink.h copy to core/backends/bluetooth/bluetoothdevicelink.h index 7d31881f..64e851e2 100644 --- a/core/backends/lan/landevicelink.h +++ b/core/backends/bluetooth/bluetoothdevicelink.h @@ -1,51 +1,60 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2016 Saikrishna Arcot * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef LANDEVICELINK_H -#define LANDEVICELINK_H +#ifndef BLUETOOTHDEVICELINK_H +#define BLUETOOTHDEVICELINK_H #include #include -#include +#include #include "../devicelink.h" +#include "../devicelinereader.h" +#include "bluetoothpairinghandler.h" -class SocketLineReader; - -class LanDeviceLink +class KDECONNECTCORE_EXPORT BluetoothDeviceLink : public DeviceLink { Q_OBJECT public: - LanDeviceLink(const QString& deviceId, LinkProvider* parent, QTcpSocket* socket); + BluetoothDeviceLink(const QString& deviceId, LinkProvider* parent, QBluetoothSocket* socket); + + virtual QString name() override; + bool sendPackage(NetworkPackage& np) override; - bool sendPackage(NetworkPackage& np); - bool sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& np); + virtual void userRequestsPair() override; + virtual void userRequestsUnpair() override; + + virtual bool linkShouldBeKeptAlive() override; private Q_SLOTS: void dataReceived(); private: - SocketLineReader* mSocketLineReader; + DeviceLineReader* mSocketReader; + QBluetoothSocket* mBluetoothSocket; + BluetoothPairingHandler* mPairingHandler; + + void sendMessage(const QString mMessage); }; #endif diff --git a/plugins/notifications/notification.cpp b/core/backends/bluetooth/bluetoothdownloadjob.cpp similarity index 55% copy from plugins/notifications/notification.cpp copy to core/backends/bluetooth/bluetoothdownloadjob.cpp index b44b5f85..1557e13d 100644 --- a/plugins/notifications/notification.cpp +++ b/core/backends/bluetooth/bluetoothdownloadjob.cpp @@ -1,46 +1,40 @@ -/** - * Copyright 2013 Albert Vaca +/* + * Copyright 2016 Saikrishna Arcot * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "notification.h" +#include "bluetoothdownloadjob.h" -#include - -Notification::Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent) +BluetoothDownloadJob::BluetoothDownloadJob(const QBluetoothAddress& remoteAddress, const QVariantMap& transferInfo, QObject* parent) : QObject(parent) + , mRemoteAddress(remoteAddress) + , mTransferUuid(QBluetoothUuid(transferInfo.value("uuid").toString())) + , mSocket(new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol)) { - mId = np.get("id"); - mAppName = np.get("appName"); - mTicker = np.get("ticker"); - mDismissable = np.get("isClearable"); - mIconPath = iconPath; } -Notification::~Notification() +QSharedPointer BluetoothDownloadJob::payload() const { - + return mSocket.staticCast(); } -void Notification::dismiss() +void BluetoothDownloadJob::start() { - if (mDismissable) { - Q_EMIT dismissRequested(this); - } + connect(mSocket.data(), SIGNAL(disconnected()), this, SLOT(deleteLater())); + mSocket->connectToService(mRemoteAddress, mTransferUuid, QIODevice::ReadOnly); } - diff --git a/core/backends/lan/uploadjob.h b/core/backends/bluetooth/bluetoothdownloadjob.h similarity index 61% copy from core/backends/lan/uploadjob.h copy to core/backends/bluetooth/bluetoothdownloadjob.h index 12b6f7e6..6206d0f7 100644 --- a/core/backends/lan/uploadjob.h +++ b/core/backends/bluetooth/bluetoothdownloadjob.h @@ -1,53 +1,47 @@ /* - * Copyright 2013 Albert Vaca + * Copyright 2016 Saikrishna Arcot * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef UPLOADJOB_H -#define UPLOADJOB_H - -#include +#ifndef BLUETOOTHDOWNLOADJOB_H +#define BLUETOOTHDOWNLOADJOB_H #include +#include #include -#include -#include #include +#include +#include +#include -class UploadJob - : public KJob +class BluetoothDownloadJob + : public QObject { Q_OBJECT public: - UploadJob(const QSharedPointer& source); - virtual void start(); - QVariantMap getTransferInfo(); + explicit BluetoothDownloadJob(const QBluetoothAddress& remoteAddress, const QVariantMap& transferInfo, QObject* parent = 0); + QSharedPointer payload() const; + void start(); private: - QSharedPointer mInput; - QTcpServer* mServer; - QTcpSocket* mSocket; - quint16 mPort; - -private Q_SLOTS: - void readyRead(); - void newConnection(); - void aboutToClose(); + QBluetoothAddress mRemoteAddress; + QBluetoothUuid mTransferUuid; + QSharedPointer mSocket; }; -#endif // UPLOADJOB_H +#endif // BLUETOOTHDOWNLOADJOB_H diff --git a/core/backends/bluetooth/bluetoothlinkprovider.cpp b/core/backends/bluetooth/bluetoothlinkprovider.cpp new file mode 100644 index 00000000..7cc9eaf0 --- /dev/null +++ b/core/backends/bluetooth/bluetoothlinkprovider.cpp @@ -0,0 +1,305 @@ +/** + * Copyright 2016 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "bluetoothlinkprovider.h" +#include "core_debug.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "bluetoothdevicelink.h" + +BluetoothLinkProvider::BluetoothLinkProvider() +{ + mServiceUuid = QBluetoothUuid(QString("185f3df4-3268-4e3f-9fca-d4d5059915bd")); + + connectTimer = new QTimer(this); + connectTimer->setInterval(30000); + connectTimer->setSingleShot(false); + + mServiceDiscoveryAgent = new QBluetoothServiceDiscoveryAgent(this); + mServiceDiscoveryAgent->setUuidFilter(mServiceUuid); + connect(mServiceDiscoveryAgent, SIGNAL(finished()), this, SLOT(serviceDiscoveryFinished())); + connect(connectTimer, SIGNAL(timeout()), mServiceDiscoveryAgent, SLOT(start())); +} + +void BluetoothLinkProvider::onStart() +{ + QBluetoothLocalDevice localDevice; + if (!localDevice.isValid()) { + qCWarning(KDECONNECT_CORE) << "No local bluetooth adapter found"; + return; + } + + mBluetoothServer = new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this); + mBluetoothServer->setSecurityFlags(QBluetooth::Encryption | QBluetooth::Secure); + connect(mBluetoothServer, SIGNAL(newConnection()), this, SLOT(serverNewConnection())); + + mServiceDiscoveryAgent->start(); + + connectTimer->start(); + mKdeconnectService = mBluetoothServer->listen(mServiceUuid, "KDE Connect"); +} + +void BluetoothLinkProvider::onStop() +{ + if (!mBluetoothServer) { + return; + } + + connectTimer->stop(); + + mKdeconnectService.unregisterService(); + mBluetoothServer->close(); + mBluetoothServer->deleteLater(); +} + +//I'm in a new network, let's be polite and introduce myself +void BluetoothLinkProvider::onNetworkChange() +{ +} + +void BluetoothLinkProvider::connectError() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + qCWarning(KDECONNECT_CORE) << "Couldn't connect to socket:" << socket->errorString(); + + disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); + disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + mSockets.remove(socket->peerAddress()); + socket->deleteLater(); +} + +void BluetoothLinkProvider::addLink(BluetoothDeviceLink* deviceLink, const QString& deviceId) +{ + QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(deviceId); + if (oldLinkIterator != mLinks.end()) { + DeviceLink* oldLink = oldLinkIterator.value(); + disconnect(oldLink, SIGNAL(destroyed(QObject*)), + this, SLOT(deviceLinkDestroyed(QObject*))); + oldLink->deleteLater(); + mLinks.erase(oldLinkIterator); + } + + mLinks[deviceId] = deviceLink; +} + +//I'm the new device and I found an existing device +void BluetoothLinkProvider::serviceDiscoveryFinished() +{ + const QList services = mServiceDiscoveryAgent->discoveredServices(); + + for (QBluetoothServiceInfo info : services) { + if (mSockets.contains(info.device().address())) { + continue; + } + + QBluetoothSocket* socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); + + connect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + socket->connectToService(info); + + qCDebug(KDECONNECT_CORE()) << "Connecting to" << info.device().address(); + + if (socket->error() != QBluetoothSocket::NoSocketError) { + qCWarning(KDECONNECT_CORE) << "Socket connection error:" << socket->errorString(); + } + } +} + +//I'm the new device and I'm connected to the existing device. Time to get data. +void BluetoothLinkProvider::clientConnected() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + qCDebug(KDECONNECT_CORE()) << "Connected to" << socket->peerAddress(); + + if (mSockets.contains(socket->peerAddress())) { + qCWarning(KDECONNECT_CORE()) << "Duplicate connection to" << socket->peerAddress(); + socket->close(); + socket->deleteLater(); + return; + } + + mSockets.insert(socket->peerAddress(), socket); + + disconnect(socket, SIGNAL(connected()), this, SLOT(clientConnected())); + + connect(socket, SIGNAL(readyRead()), this, SLOT(clientIdentityReceived())); + connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); +} + +//I'm the new device and the existing device sent me data. +void BluetoothLinkProvider::clientIdentityReceived() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + QByteArray identityArray; + while (socket->bytesAvailable() > 0 || !identityArray.contains('\n')) { + identityArray += socket->readAll(); + } + + disconnect(socket, SIGNAL(readyRead()), this, SLOT(clientIdentityReceived())); + + NetworkPackage receivedPackage(""); + bool success = NetworkPackage::unserialize(identityArray, &receivedPackage); + + if (!success || receivedPackage.type() != PACKAGE_TYPE_IDENTITY) { + qCWarning(KDECONNECT_CORE) << "Not an identity package"; + mSockets.remove(socket->peerAddress()); + socket->close(); + socket->deleteLater(); + return; + } + + qCDebug(KDECONNECT_CORE()) << "Received identity package from" << socket->peerAddress(); + + disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + const QString& deviceId = receivedPackage.get("deviceId"); + BluetoothDeviceLink* deviceLink = new BluetoothDeviceLink(deviceId, this, socket); + + NetworkPackage np2(""); + NetworkPackage::createIdentityPackage(&np2); + success = deviceLink->sendPackage(np2); + + if (success) { + + qCDebug(KDECONNECT_CORE) << "Handshaking done (I'm the new device)"; + + connect(deviceLink, SIGNAL(destroyed(QObject*)), + this, SLOT(deviceLinkDestroyed(QObject*))); + + Q_EMIT onConnectionReceived(receivedPackage, deviceLink); + + //We kill any possible link from this same device + addLink(deviceLink, deviceId); + + } else { + // Connection might be lost. Delete it. + delete deviceLink; + } + + //We don't delete the socket because now it's owned by the BluetoothDeviceLink +} + +//I'm the existing device, a new device is kindly introducing itself. +void BluetoothLinkProvider::serverNewConnection() +{ + QBluetoothSocket* socket = mBluetoothServer->nextPendingConnection(); + + qCDebug(KDECONNECT_CORE()) << "Received connection from" << socket->peerAddress(); + + if (mSockets.contains(socket->peerAddress())) { + qCDebug(KDECONNECT_CORE()) << "Duplicate connection from" << socket->peerAddress(); + socket->close(); + socket->deleteLater(); + return; + } + + connect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); + connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + NetworkPackage np2(""); + NetworkPackage::createIdentityPackage(&np2); + socket->write(np2.serialize()); + + qCDebug(KDECONNECT_CORE()) << "Sent identity package to" << socket->peerAddress(); + + mSockets.insert(socket->peerAddress(), socket); +} + +//I'm the existing device and this is the answer to my identity package (data received) +void BluetoothLinkProvider::serverDataReceived() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + QByteArray identityArray; + while (socket->bytesAvailable() > 0 || !identityArray.contains('\n')) { + identityArray += socket->readAll(); + } + + disconnect(socket, SIGNAL(readyRead()), this, SLOT(serverDataReceived())); + disconnect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), this, SLOT(connectError())); + + NetworkPackage receivedPackage(""); + bool success = NetworkPackage::unserialize(identityArray, &receivedPackage); + + if (!success || receivedPackage.type() != PACKAGE_TYPE_IDENTITY) { + qCWarning(KDECONNECT_CORE) << "Not an identity package."; + mSockets.remove(socket->peerAddress()); + socket->close(); + socket->deleteLater(); + return; + } + + qCDebug(KDECONNECT_CORE()) << "Received identity package from" << socket->peerAddress(); + + const QString& deviceId = receivedPackage.get("deviceId"); + BluetoothDeviceLink* deviceLink = new BluetoothDeviceLink(deviceId, this, socket); + + connect(deviceLink, SIGNAL(destroyed(QObject*)), + this, SLOT(deviceLinkDestroyed(QObject*))); + + Q_EMIT onConnectionReceived(receivedPackage, deviceLink); + + addLink(deviceLink, deviceId); +} + +void BluetoothLinkProvider::deviceLinkDestroyed(QObject* destroyedDeviceLink) +{ + const QString id = destroyedDeviceLink->property("deviceId").toString(); + qCDebug(KDECONNECT_CORE()) << "Device disconnected:" << id; + QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(id); + if (oldLinkIterator != mLinks.end() && oldLinkIterator.value() == destroyedDeviceLink) { + mLinks.erase(oldLinkIterator); + } +} + +void BluetoothLinkProvider::socketDisconnected() +{ + QBluetoothSocket* socket = qobject_cast(sender()); + if (!socket) return; + + disconnect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + mSockets.remove(mSockets.key(socket)); +} + +BluetoothLinkProvider::~BluetoothLinkProvider() +{ + +} diff --git a/core/backends/bluetooth/bluetoothlinkprovider.h b/core/backends/bluetooth/bluetoothlinkprovider.h new file mode 100644 index 00000000..ccdc6a35 --- /dev/null +++ b/core/backends/bluetooth/bluetoothlinkprovider.h @@ -0,0 +1,83 @@ +/** + * Copyright 2016 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLUETOOTHLINKPROVIDER_H +#define BLUETOOTHLINKPROVIDER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../linkprovider.h" + +class BluetoothDeviceLink; + +class KDECONNECTCORE_EXPORT BluetoothLinkProvider + : public LinkProvider +{ + Q_OBJECT + +public: + BluetoothLinkProvider(); + virtual ~BluetoothLinkProvider(); + + QString name() override { return "BluetoothLinkProvider"; } + int priority() override { return PRIORITY_MEDIUM; } + +public Q_SLOTS: + virtual void onNetworkChange() override; + virtual void onStart() override; + virtual void onStop() override; + void connectError(); + void serviceDiscoveryFinished(); + +private Q_SLOTS: + void deviceLinkDestroyed(QObject* destroyedDeviceLink); + void socketDisconnected(); + + void serverNewConnection(); + void serverDataReceived(); + void clientConnected(); + void clientIdentityReceived(); + +private: + void addLink(BluetoothDeviceLink* deviceLink, const QString& deviceId); + QList getPairedDevices(); + + QBluetoothUuid mServiceUuid; + QPointer mBluetoothServer; + QBluetoothServiceInfo mKdeconnectService; + QBluetoothServiceDiscoveryAgent* mServiceDiscoveryAgent; + QTimer* connectTimer; + + QMap mLinks; + + QMap mSockets; + +}; + +#endif diff --git a/core/backends/bluetooth/bluetoothpairinghandler.cpp b/core/backends/bluetooth/bluetoothpairinghandler.cpp new file mode 100644 index 00000000..c30bedb0 --- /dev/null +++ b/core/backends/bluetooth/bluetoothpairinghandler.cpp @@ -0,0 +1,151 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "core_debug.h" +#include "daemon.h" +#include "kdeconnectconfig.h" +#include "bluetoothpairinghandler.h" +#include "networkpackagetypes.h" + +BluetoothPairingHandler::BluetoothPairingHandler(DeviceLink* deviceLink) + : PairingHandler(deviceLink) + , m_status(NotPaired) +{ + m_pairingTimeout.setSingleShot(true); + m_pairingTimeout.setInterval(30 * 1000); //30 seconds of timeout + connect(&m_pairingTimeout, &QTimer::timeout, this, &BluetoothPairingHandler::pairingTimeout); +} + +void BluetoothPairingHandler::packageReceived(const NetworkPackage& np) +{ + qCDebug(KDECONNECT_CORE) << "Pairing package received!" << np.serialize(); + + m_pairingTimeout.stop(); + + bool wantsPair = np.get("pair"); + + if (wantsPair) { + + if (isPairRequested()) { //We started pairing + + qCDebug(KDECONNECT_CORE) << "Pair answer"; + setInternalPairStatus(Paired); + + } else { + qCDebug(KDECONNECT_CORE) << "Pair request"; + + if (isPaired()) { //I'm already paired, but they think I'm not + acceptPairing(); + return; + } + + setInternalPairStatus(RequestedByPeer); + } + + } else { //wantsPair == false + + qCDebug(KDECONNECT_CORE) << "Unpair request"; + + setInternalPairStatus(NotPaired); + if (isPairRequested()) { + Q_EMIT pairingError(i18n("Canceled by other peer")); + } + } +} + +bool BluetoothPairingHandler::requestPairing() +{ + switch (m_status) { + case Paired: + Q_EMIT pairingError(i18n("%1: Already paired", deviceLink()->name())); + return false; + case Requested: + Q_EMIT pairingError(i18n("%1: Pairing already requested for this device", deviceLink()->name())); + return false; + case RequestedByPeer: + qCDebug(KDECONNECT_CORE) << deviceLink()->name() << " : Pairing already started by the other end, accepting their request."; + acceptPairing(); + return false; + case NotPaired: + ; + } + + NetworkPackage np(PACKAGE_TYPE_PAIR); + np.set("pair", true); + bool success; + success = deviceLink()->sendPackage(np); + if (success) { + setInternalPairStatus(Requested); + m_pairingTimeout.start(); + } + return success; +} + +bool BluetoothPairingHandler::acceptPairing() +{ + qCDebug(KDECONNECT_CORE) << "User accepts pairing"; + m_pairingTimeout.stop(); // Just in case it is started + NetworkPackage np(PACKAGE_TYPE_PAIR); + np.set("pair", true); + bool success = deviceLink()->sendPackage(np); + if (success) { + setInternalPairStatus(Paired); + } + return success; +} + +void BluetoothPairingHandler::rejectPairing() +{ + qCDebug(KDECONNECT_CORE) << "User rejects pairing"; + NetworkPackage np(PACKAGE_TYPE_PAIR); + np.set("pair", false); + deviceLink()->sendPackage(np); + setInternalPairStatus(NotPaired); +} + +void BluetoothPairingHandler::unpair() { + NetworkPackage np(PACKAGE_TYPE_PAIR); + np.set("pair", false); + deviceLink()->sendPackage(np); + setInternalPairStatus(NotPaired); +} + +void BluetoothPairingHandler::pairingTimeout() +{ + NetworkPackage np(PACKAGE_TYPE_PAIR); + np.set("pair", false); + deviceLink()->sendPackage(np); + setInternalPairStatus(NotPaired); //Will emit the change as well + Q_EMIT pairingError(i18n("Timed out")); +} + +void BluetoothPairingHandler::setInternalPairStatus(BluetoothPairingHandler::InternalPairStatus status) +{ + m_status = status; + if (status == Paired) { + deviceLink()->setPairStatus(DeviceLink::Paired); + } else if (status == NotPaired){ + deviceLink()->setPairStatus(DeviceLink::NotPaired); + } else if (status == RequestedByPeer) { + Q_EMIT deviceLink()->pairingRequest(this); + } +} diff --git a/core/backends/bluetooth/bluetoothpairinghandler.h b/core/backends/bluetooth/bluetoothpairinghandler.h new file mode 100644 index 00000000..1695cf95 --- /dev/null +++ b/core/backends/bluetooth/bluetoothpairinghandler.h @@ -0,0 +1,67 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KDECONNECT_BLUETOOTHPAIRINGHANDLER_H +#define KDECONNECT_BLUETOOTHPAIRINGHANDLER_H + +#include "device.h" +#include "../devicelink.h" +#include "../pairinghandler.h" + +#include + +// This class is used pairing related stuff. It has direct access to links and can directly send packages +class BluetoothPairingHandler + : public PairingHandler +{ +public: + + enum InternalPairStatus { + NotPaired, + Requested, + RequestedByPeer, + Paired, + }; + + BluetoothPairingHandler(DeviceLink* deviceLink); + virtual ~BluetoothPairingHandler() { } + + virtual void packageReceived(const NetworkPackage& np) Q_DECL_OVERRIDE; + virtual bool requestPairing() Q_DECL_OVERRIDE; + virtual bool acceptPairing() Q_DECL_OVERRIDE; + virtual void rejectPairing() Q_DECL_OVERRIDE; + virtual void unpair() Q_DECL_OVERRIDE; + + bool isPairRequested() const { return m_status == Requested; } + bool isPaired() const { return m_status == Paired; } + +private Q_SLOTS: + void pairingTimeout(); + +protected: + void setInternalPairStatus(InternalPairStatus status); + + QTimer m_pairingTimeout; + + InternalPairStatus m_status; +}; + + +#endif //KDECONNECT_BLUETOOTHPAIRINGHANDLER_H diff --git a/core/backends/bluetooth/bluetoothuploadjob.cpp b/core/backends/bluetooth/bluetoothuploadjob.cpp new file mode 100644 index 00000000..1137b1a3 --- /dev/null +++ b/core/backends/bluetooth/bluetoothuploadjob.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Saikrishna Arcot + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "bluetoothuploadjob.h" + +#include +#include "core_debug.h" +#include + +BluetoothUploadJob::BluetoothUploadJob(const QSharedPointer& data, const QBluetoothAddress& remoteAddress, QObject* parent) + : QObject(parent) + , mData(data) + , mRemoteAddress(remoteAddress) + , mTransferUuid(QBluetoothUuid::createUuid()) + , mServer(new QBluetoothServer(QBluetoothServiceInfo::RfcommProtocol, this)) +{ + mServer->setSecurityFlags(QBluetooth::Encryption | QBluetooth::Secure); +} + +QVariantMap BluetoothUploadJob::transferInfo() const +{ + QVariantMap ret; + ret["uuid"] = mTransferUuid.toString().mid(1, 36); + return ret; +} + +void BluetoothUploadJob::start() +{ + connect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + mServiceInfo = mServer->listen(mTransferUuid, "KDE Connect Transfer Job"); + Q_ASSERT(mServiceInfo.isValid()); +} + +void BluetoothUploadJob::newConnection() +{ + QBluetoothSocket* socket = mServer->nextPendingConnection(); + connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); + + if (socket->peerAddress() != mRemoteAddress) { + socket->close(); + } else { + mServer->close(); + disconnect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + mServiceInfo.unregisterService(); + + if (!mData->open(QIODevice::ReadOnly)) { + qCWarning(KDECONNECT_CORE) << "error when opening the input to upload"; + socket->close(); + deleteLater(); + return; + } + } + + while (mData->bytesAvailable() > 0 && socket->isWritable()) { + qint64 bytes = qMin(mData->bytesAvailable(), (qint64)4096); + int w = socket->write(mData->read(bytes)); + if (w < 0) { + qCWarning(KDECONNECT_CORE()) << "error when writing data to upload" << bytes << mData->bytesAvailable(); + break; + } else { + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 2000); + } + } + + mData->close(); + socket->close(); + deleteLater(); +} diff --git a/core/backends/lan/uploadjob.h b/core/backends/bluetooth/bluetoothuploadjob.h similarity index 60% copy from core/backends/lan/uploadjob.h copy to core/backends/bluetooth/bluetoothuploadjob.h index 12b6f7e6..a2bd2280 100644 --- a/core/backends/lan/uploadjob.h +++ b/core/backends/bluetooth/bluetoothuploadjob.h @@ -1,53 +1,53 @@ /* - * Copyright 2013 Albert Vaca + * Copyright 2016 Saikrishna Arcot * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef UPLOADJOB_H -#define UPLOADJOB_H - -#include +#ifndef BLUETOOTHUPLOADJOB_H +#define BLUETOOTHUPLOADJOB_H #include +#include #include -#include -#include #include +#include +#include +#include -class UploadJob - : public KJob +class BluetoothUploadJob + : public QObject { Q_OBJECT public: - UploadJob(const QSharedPointer& source); - virtual void start(); - QVariantMap getTransferInfo(); + explicit BluetoothUploadJob(const QSharedPointer& data, const QBluetoothAddress& remoteAddress, QObject* parent = 0); + + QVariantMap transferInfo() const; + void start(); private: - QSharedPointer mInput; - QTcpServer* mServer; - QTcpSocket* mSocket; - quint16 mPort; + QSharedPointer mData; + QBluetoothAddress mRemoteAddress; + QBluetoothUuid mTransferUuid; + QBluetoothServer* mServer; + QBluetoothServiceInfo mServiceInfo; private Q_SLOTS: - void readyRead(); void newConnection(); - void aboutToClose(); }; -#endif // UPLOADJOB_H +#endif // BLUETOOTHUPLOADJOB_H diff --git a/core/backends/lan/socketlinereader.cpp b/core/backends/devicelinereader.cpp similarity index 70% copy from core/backends/lan/socketlinereader.cpp copy to core/backends/devicelinereader.cpp index a6bd85ee..c95fe74f 100644 --- a/core/backends/lan/socketlinereader.cpp +++ b/core/backends/devicelinereader.cpp @@ -1,53 +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 "socketlinereader.h" +#include "devicelinereader.h" -SocketLineReader::SocketLineReader(QTcpSocket* socket, QObject* parent) +DeviceLineReader::DeviceLineReader(QIODevice* device, QObject* parent) : QObject(parent) - , mSocket(socket) + , m_device(device) { - connect(mSocket, SIGNAL(readyRead()), + connect(m_device, SIGNAL(readyRead()), this, SLOT(dataReceived())); + connect(m_device, SIGNAL(disconnected()), + this, SIGNAL(disconnected())); } -void SocketLineReader::dataReceived() +void DeviceLineReader::dataReceived() { - while (mSocket->canReadLine()) { - const QByteArray line = mSocket->readLine(); + while(m_device->canReadLine()) { + const QByteArray line = m_device->readLine(); if (line.length() > 1) { - mPackages.enqueue(line);//we don't want single \n + m_packages.enqueue(line);//we don't want single \n } } - //If we still have things to read from the socket, call dataReceived again + //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 (mSocket->bytesAvailable() > 0) { + if (m_device->bytesAvailable() > 0) { QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); return; } //If we have any packages, tell it to the world. - if (!mPackages.isEmpty()) { + if (!m_packages.isEmpty()) { Q_EMIT readyRead(); } } diff --git a/core/backends/lan/socketlinereader.h b/core/backends/devicelinereader.h similarity index 61% copy from core/backends/lan/socketlinereader.h copy to core/backends/devicelinereader.h index b3be55a7..f4bd987a 100644 --- a/core/backends/lan/socketlinereader.h +++ b/core/backends/devicelinereader.h @@ -1,60 +1,60 @@ /** * 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 SOCKETLINEREADER_H -#define SOCKETLINEREADER_H +#ifndef DEVICELINEREADER_H +#define DEVICELINEREADER_H #include #include #include -#include -#include +#include /* - * Encapsulates a QTcpSocket and implements the same methods of its API that are - * used by LanDeviceLink, but readyRead is emitted only when a newline is found. + * Encapsulates a QIODevice and implements the same methods of its API that are + * used by LanDeviceLink and BluetoothDeviceLink, but readyRead is emitted only + * when a newline is found. */ -class SocketLineReader +class DeviceLineReader : public QObject { Q_OBJECT public: - SocketLineReader(QTcpSocket* socket, QObject* parent = 0); + DeviceLineReader(QIODevice* device, QObject* parent = 0); - QByteArray readLine() { return mPackages.dequeue(); } - qint64 write(const QByteArray& data) { return mSocket->write(data); } - QHostAddress peerAddress() const { return mSocket->peerAddress(); } - qint64 bytesAvailable() const { return mPackages.size(); } + QByteArray readLine() { return m_packages.dequeue(); } + qint64 write(const QByteArray& data) { return m_device->write(data); } + qint64 bytesAvailable() const { return m_packages.size(); } Q_SIGNALS: void readyRead(); + void disconnected(); private Q_SLOTS: void dataReceived(); private: - QByteArray lastChunk; - QTcpSocket* mSocket; - QQueue mPackages; + QByteArray m_lastChunk; + QIODevice* m_device; + QQueue m_packages; }; #endif diff --git a/core/backends/devicelink.cpp b/core/backends/devicelink.cpp index e4a13ca1..f59312cf 100644 --- a/core/backends/devicelink.cpp +++ b/core/backends/devicelink.cpp @@ -1,36 +1,44 @@ /** * 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 "devicelink.h" #include "kdeconnectconfig.h" #include "linkprovider.h" DeviceLink::DeviceLink(const QString& deviceId, LinkProvider* parent) : QObject(parent) - , mDeviceId(deviceId) - , mLinkProvider(parent) + , m_privateKey(KdeConnectConfig::instance()->privateKey()) + , m_deviceId(deviceId) + , m_linkProvider(parent) + , m_pairStatus(NotPaired) { Q_ASSERT(!deviceId.isEmpty()); setProperty("deviceId", deviceId); +} - mPrivateKey = KdeConnectConfig::instance()->privateKey(); +void DeviceLink::setPairStatus(DeviceLink::PairStatus status) +{ + if (m_pairStatus != status) { + m_pairStatus = status; + Q_EMIT pairStatusChanged(status); + } } diff --git a/core/backends/devicelink.h b/core/backends/devicelink.h index 0ae6c009..deb53293 100644 --- a/core/backends/devicelink.h +++ b/core/backends/devicelink.h @@ -1,59 +1,79 @@ /** * 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 #include "core/networkpackage.h" +class PairingHandler; class NetworkPackage; class LinkProvider; +class Device; class DeviceLink : public QObject { Q_OBJECT public: + enum PairStatus { NotPaired, Paired }; + DeviceLink(const QString& deviceId, LinkProvider* parent); - virtual ~DeviceLink() { }; + virtual ~DeviceLink() = default; + + virtual QString name() = 0; - const QString& deviceId() { return mDeviceId; } - LinkProvider* provider() { return mLinkProvider; } + const QString& deviceId() const { return m_deviceId; } + LinkProvider* provider() { return m_linkProvider; } virtual bool sendPackage(NetworkPackage& np) = 0; - virtual bool sendPackageEncrypted(QCA::PublicKey& publicKey, NetworkPackage& 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 receivedPackage(const NetworkPackage& np); protected: - QCA::PrivateKey mPrivateKey; + QCA::PrivateKey m_privateKey; private: - QString mDeviceId; - LinkProvider* mLinkProvider; + const QString m_deviceId; + LinkProvider* m_linkProvider; + PairStatus m_pairStatus; }; #endif diff --git a/core/backends/lan/CMakeLists.txt b/core/backends/lan/CMakeLists.txt index 7c5be388..0b01d662 100644 --- a/core/backends/lan/CMakeLists.txt +++ b/core/backends/lan/CMakeLists.txt @@ -1,12 +1,14 @@ set(backends_kdeconnect_SRCS ${backends_kdeconnect_SRCS} + backends/lan/server.cpp backends/lan/lanlinkprovider.cpp backends/lan/landevicelink.cpp + backends/lan/lanpairinghandler.cpp backends/lan/uploadjob.cpp backends/lan/downloadjob.cpp backends/lan/socketlinereader.cpp PARENT_SCOPE ) diff --git a/core/backends/lan/downloadjob.cpp b/core/backends/lan/downloadjob.cpp index bba2e988..9b341f31 100644 --- a/core/backends/lan/downloadjob.cpp +++ b/core/backends/lan/downloadjob.cpp @@ -1,49 +1,77 @@ /* * 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 "downloadjob.h" -DownloadJob::DownloadJob(QHostAddress address, QVariantMap transferInfo): KJob() +#ifndef Q_OS_WIN +#include +#include +#include +#include +#endif + +#include "kdeconnectconfig.h" +#include "lanlinkprovider.h" +#include "core/core_debug.h" + +DownloadJob::DownloadJob(const QHostAddress& address, const QVariantMap& transferInfo) + : KJob() + , m_address(address) + , m_port(transferInfo[QStringLiteral("port")].toInt()) + , m_socket(new QSslSocket) { - mAddress = address; - mPort = transferInfo["port"].toInt(); - mSocket = QSharedPointer(new QTcpSocket); + LanLinkProvider::configureSslSocket(m_socket.data(), transferInfo.value(QStringLiteral("deviceId")).toString(), true); + + connect(m_socket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketFailed(QAbstractSocket::SocketError))); + connect(m_socket.data(), &QAbstractSocket::connected, this, &DownloadJob::socketConnected); + // emit readChannelFinished when the socket gets disconnected. This seems to be a bug in upstream QSslSocket. + // Needs investigation and upstreaming of the fix. QTBUG-62257 + connect(m_socket.data(), &QAbstractSocket::disconnected, m_socket.data(), &QAbstractSocket::readChannelFinished); +} + +DownloadJob::~DownloadJob() +{ + } void DownloadJob::start() { - //kDebug(kdeconnect_kded()) << "DownloadJob Start"; - mSocket->connectToHost(mAddress, mPort, QIODevice::ReadOnly); - connect(mSocket.data(), SIGNAL(disconnected()), - this, SLOT(disconnected())); - //TODO: Implement payload encryption somehow (create an intermediate iodevice to encrypt the payload here?) + //TODO: Timeout? + // Cannot use read only, might be due to ssl handshake, getting QIODevice::ReadOnly error and no connection + m_socket->connectToHostEncrypted(m_address.toString(), m_port, QIODevice::ReadWrite); } -void DownloadJob::disconnected() +void DownloadJob::socketFailed(QAbstractSocket::SocketError error) { - //kDebug(kdeconnect_kded()) << "DownloadJob End"; + qWarning() << error << m_socket->errorString(); + setError(error + 1); + setErrorText(m_socket->errorString()); emitResult(); } QSharedPointer DownloadJob::getPayload() { - //kDebug(kdeconnect_kded()) << "getPayload"; - return mSocket.staticCast(); + return m_socket.staticCast(); +} + +void DownloadJob::socketConnected() +{ + emitResult(); } diff --git a/core/backends/lan/downloadjob.h b/core/backends/lan/downloadjob.h index eeeab07a..6bcf2120 100644 --- a/core/backends/lan/downloadjob.h +++ b/core/backends/lan/downloadjob.h @@ -1,52 +1,56 @@ /* * 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 DOWNLOADJOB_H #define DOWNLOADJOB_H #include #include #include #include -#include #include +#include +#include -class DownloadJob +#include "kdeconnectcore_export.h" + + +class KDECONNECTCORE_EXPORT DownloadJob : public KJob { Q_OBJECT public: - DownloadJob(QHostAddress address, QVariantMap transferInfo); - virtual void start(); + DownloadJob(const QHostAddress& address, const QVariantMap& transferInfo); + ~DownloadJob() override; + void start() override; QSharedPointer getPayload(); private: - QHostAddress mAddress; - qint16 mPort; - QSharedPointer mSocket; - + QHostAddress m_address; + qint16 m_port; + QSharedPointer m_socket; private Q_SLOTS: - void disconnected(); - + void socketFailed(QAbstractSocket::SocketError error); + void socketConnected(); }; #endif // UPLOADJOB_H diff --git a/core/backends/lan/landevicelink.cpp b/core/backends/lan/landevicelink.cpp index 09870579..8df7d97a 100644 --- a/core/backends/lan/landevicelink.cpp +++ b/core/backends/lan/landevicelink.cpp @@ -1,120 +1,178 @@ /** * 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 + #include "landevicelink.h" #include "core_debug.h" - -#include -#include -#include -#include - -#include "../linkprovider.h" +#include "kdeconnectconfig.h" +#include "backends/linkprovider.h" #include "uploadjob.h" #include "downloadjob.h" #include "socketlinereader.h" +#include "lanlinkprovider.h" -LanDeviceLink::LanDeviceLink(const QString& deviceId, LinkProvider* parent, QTcpSocket* socket) +LanDeviceLink::LanDeviceLink(const QString& deviceId, LinkProvider* parent, QSslSocket* socket, ConnectionStarted connectionSource) : DeviceLink(deviceId, parent) - , mSocketLineReader(new SocketLineReader(socket)) + , m_socketLineReader(nullptr) +{ + reset(socket, connectionSource); +} + +void LanDeviceLink::reset(QSslSocket* socket, ConnectionStarted connectionSource) { - connect(mSocketLineReader, SIGNAL(readyRead()), - this, SLOT(dataReceived())); + if (m_socketLineReader) { + disconnect(m_socketLineReader->m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater); + delete m_socketLineReader; + } + + m_socketLineReader = new SocketLineReader(socket, this); + + connect(socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater); + connect(m_socketLineReader, &SocketLineReader::readyRead, this, &LanDeviceLink::dataReceived); //We take ownership of the socket. - //When the link provider distroys us, + //When the link provider destroys us, //the socket (and the reader) will be //destroyed as well - connect(socket, SIGNAL(disconnected()), - this, SLOT(deleteLater())); - mSocketLineReader->setParent(this); - socket->setParent(this); + socket->setParent(m_socketLineReader); + + m_connectionSource = connectionSource; + + QString certString = KdeConnectConfig::instance()->getDeviceProperty(deviceId(), QStringLiteral("certificate")); + DeviceLink::setPairStatus(certString.isEmpty()? PairStatus::NotPaired : PairStatus::Paired); } +QHostAddress LanDeviceLink::hostAddress() const +{ + if (!m_socketLineReader) { + return QHostAddress::Null; + } + QHostAddress addr = m_socketLineReader->m_socket->peerAddress(); + if (addr.protocol() == QAbstractSocket::IPv6Protocol) { + bool success; + QHostAddress convertedAddr = QHostAddress(addr.toIPv4Address(&success)); + if (success) { + qCDebug(KDECONNECT_CORE) << "Converting IPv6" << addr << "to IPv4" << convertedAddr; + addr = convertedAddr; + } + } + return addr; +} -bool LanDeviceLink::sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& np) +QString LanDeviceLink::name() +{ + return QStringLiteral("LanLink"); // Should be same in both android and kde version +} + +bool LanDeviceLink::sendPackage(NetworkPackage& np) { if (np.hasPayload()) { - UploadJob* job = new UploadJob(np.payload()); - job->start(); - np.setPayloadTransferInfo(job->getTransferInfo()); + np.setPayloadTransferInfo(sendPayload(np)->transferInfo()); } - np.encrypt(key); - - int written = mSocketLineReader->write(np.serialize()); + int written = m_socketLineReader->write(np.serialize()); - //TODO: Actually detect if a package is received or not, now we keep TCP + //Actually we can't detect if a package is received or not. We keep TCP //"ESTABLISHED" connections that look legit (return true when we use them), //but that are actually broken (until keepalive detects that they are down). return (written != -1); } -bool LanDeviceLink::sendPackage(NetworkPackage& np) +UploadJob* LanDeviceLink::sendPayload(const NetworkPackage& np) { - if (np.hasPayload()) { - UploadJob* job = new UploadJob(np.payload()); - job->start(); - np.setPayloadTransferInfo(job->getTransferInfo()); - } - - int written = mSocketLineReader->write(np.serialize()); - return (written != -1); + UploadJob* job = new UploadJob(np.payload(), deviceId()); + job->start(); + return job; } void LanDeviceLink::dataReceived() { + if (m_socketLineReader->bytesAvailable() == 0) return; - if (mSocketLineReader->bytesAvailable() == 0) return; + const QByteArray serializedPackage = m_socketLineReader->readLine(); + NetworkPackage package(QString::null); + NetworkPackage::unserialize(serializedPackage, &package); - const QByteArray package = mSocketLineReader->readLine(); + //qCDebug(KDECONNECT_CORE) << "LanDeviceLink dataReceived" << serializedPackage; - //qCDebug(KDECONNECT_CORE) << "LanDeviceLink dataReceived" << package; + if (package.type() == PACKAGE_TYPE_PAIR) { + //TODO: Handle pair/unpair requests and forward them (to the pairing handler?) + qobject_cast(provider())->incomingPairPackage(this, package); + return; + } - 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 (package.hasPayloadTransferInfo()) { + //qCDebug(KDECONNECT_CORE) << "HasPayloadTransferInfo"; + QVariantMap transferInfo = package.payloadTransferInfo(); + //FIXME: The next two lines shouldn't be needed! Why are they here? + transferInfo.insert(QStringLiteral("useSsl"), true); + transferInfo.insert(QStringLiteral("deviceId"), deviceId()); + DownloadJob* job = new DownloadJob(m_socketLineReader->peerAddress(), transferInfo); + job->start(); + package.setPayload(job->getPayload(), package.payloadSize()); + } - if (decrypted.hasPayloadTransferInfo()) { - qCDebug(KDECONNECT_CORE) << "HasPayloadTransferInfo"; - DownloadJob* job = new DownloadJob(mSocketLineReader->peerAddress(), decrypted.payloadTransferInfo()); - job->start(); - decrypted.setPayload(job->getPayload(), decrypted.payloadSize()); - } + Q_EMIT receivedPackage(package); - Q_EMIT receivedPackage(decrypted); + if (m_socketLineReader->bytesAvailable() > 0) { + QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); + } +} + +void LanDeviceLink::userRequestsPair() +{ + if (m_socketLineReader->peerCertificate().isNull()) { + Q_EMIT pairingError(i18n("This device cannot be paired because it is running an old version of KDE Connect.")); } else { - if (unserialized.hasPayloadTransferInfo()) { - qWarning() << "Ignoring unencrypted payload"; - } + qobject_cast(provider())->userRequestsPair(deviceId()); + } +} - Q_EMIT receivedPackage(unserialized); +void LanDeviceLink::userRequestsUnpair() +{ + qobject_cast(provider())->userRequestsUnpair(deviceId()); +} +void LanDeviceLink::setPairStatus(PairStatus status) +{ + if (status == Paired && m_socketLineReader->peerCertificate().isNull()) { + Q_EMIT pairingError(i18n("This device cannot be paired because it is running an old version of KDE Connect.")); + return; } - if (mSocketLineReader->bytesAvailable() > 0) { - QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); + DeviceLink::setPairStatus(status); + if (status == Paired) { + Q_ASSERT(KdeConnectConfig::instance()->trustedDevices().contains(deviceId())); + Q_ASSERT(!m_socketLineReader->peerCertificate().isNull()); + KdeConnectConfig::instance()->setDeviceProperty(deviceId(), QStringLiteral("certificate"), m_socketLineReader->peerCertificate().toPem()); } +} + +bool LanDeviceLink::linkShouldBeKeptAlive() { + + return true; //FIXME: Current implementation is broken, so for now we will keep links always established + + //We keep the remotely initiated connections, since the remotes require them if they want to request + //pairing to us, or connections that are already paired. TODO: Keep connections in the process of pairing + //return (mConnectionSource == ConnectionStarted::Remotely || pairStatus() == Paired); } diff --git a/core/backends/lan/landevicelink.h b/core/backends/lan/landevicelink.h index 7d31881f..adb2a4b5 100644 --- a/core/backends/lan/landevicelink.h +++ b/core/backends/lan/landevicelink.h @@ -1,51 +1,68 @@ /** * 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 LANDEVICELINK_H #define LANDEVICELINK_H #include #include -#include +#include +#include -#include "../devicelink.h" +#include +#include "backends/devicelink.h" +#include "uploadjob.h" class SocketLineReader; -class LanDeviceLink +class KDECONNECTCORE_EXPORT LanDeviceLink : public DeviceLink { Q_OBJECT public: - LanDeviceLink(const QString& deviceId, LinkProvider* parent, QTcpSocket* socket); + enum ConnectionStarted : bool { Locally, Remotely }; - bool sendPackage(NetworkPackage& np); - bool sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& np); + LanDeviceLink(const QString& deviceId, LinkProvider* parent, QSslSocket* socket, ConnectionStarted connectionSource); + void reset(QSslSocket* socket, ConnectionStarted connectionSource); + + QString name() override; + bool sendPackage(NetworkPackage& np) override; + UploadJob* sendPayload(const NetworkPackage& np); + + void userRequestsPair() override; + void userRequestsUnpair() override; + + void setPairStatus(PairStatus status) override; + + bool linkShouldBeKeptAlive() override; + + QHostAddress hostAddress() const; private Q_SLOTS: void dataReceived(); private: - SocketLineReader* mSocketLineReader; - + SocketLineReader* m_socketLineReader; + ConnectionStarted m_connectionSource; + QHostAddress m_hostAddress; }; #endif diff --git a/core/backends/lan/lanlinkprovider.cpp b/core/backends/lan/lanlinkprovider.cpp index f5d2b22d..4b990e9e 100644 --- a/core/backends/lan/lanlinkprovider.cpp +++ b/core/backends/lan/lanlinkprovider.cpp @@ -1,317 +1,538 @@ /** * 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 "lanlinkprovider.h" #include "core_debug.h" +#ifndef Q_OS_WIN #include #include #include #include +#endif #include #include +#include #include -#include #include #include +#include +#include +#include "daemon.h" #include "landevicelink.h" -#include +#include "lanpairinghandler.h" +#include "kdeconnectconfig.h" -LanLinkProvider::LanLinkProvider() +#define MIN_VERSION_WITH_SSL_SUPPORT 6 + +LanLinkProvider::LanLinkProvider(bool testMode) + : m_testMode(testMode) { - mTcpPort = 0; + m_tcpPort = 0; + + m_combineBroadcastsTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough + m_combineBroadcastsTimer.setSingleShot(true); + connect(&m_combineBroadcastsTimer, &QTimer::timeout, this, &LanLinkProvider::broadcastToNetwork); + + connect(&m_udpSocket, &QIODevice::readyRead, this, &LanLinkProvider::newUdpConnection); - mUdpServer = new QUdpSocket(this); - connect(mUdpServer, SIGNAL(readyRead()), this, SLOT(newUdpConnection())); + m_server = new Server(this); + m_server->setProxy(QNetworkProxy::NoProxy); + connect(m_server,&QTcpServer::newConnection,this, &LanLinkProvider::newConnection); - mTcpServer = new QTcpServer(this); - connect(mTcpServer,SIGNAL(newConnection()),this, SLOT(newConnection())); + m_udpSocket.setProxy(QNetworkProxy::NoProxy); //Detect when a network interface changes status, so we announce ourelves in the new network - QNetworkConfigurationManager* networkManager; - networkManager = new QNetworkConfigurationManager(this); - connect(networkManager, &QNetworkConfigurationManager::configurationChanged, [this, networkManager](QNetworkConfiguration config) { - Q_UNUSED(config); - //qCDebug(KDECONNECT_CORE) << config.name() << " state changed to " << config.state(); - //qCDebug(KDECONNECT_CORE) << "Online status: " << (networkManager->isOnline()? "online":"offline"); + QNetworkConfigurationManager* networkManager = new QNetworkConfigurationManager(this); + connect(networkManager, &QNetworkConfigurationManager::configurationChanged, this, &LanLinkProvider::onNetworkConfigurationChanged); + +} + +void LanLinkProvider::onNetworkConfigurationChanged(const QNetworkConfiguration& config) +{ + if (m_lastConfig != config && config.state() == QNetworkConfiguration::Active) { + m_lastConfig = config; onNetworkChange(); - }); + } } LanLinkProvider::~LanLinkProvider() { - } void LanLinkProvider::onStart() { - mUdpServer->bind(QHostAddress::Any, port, QUdpSocket::ShareAddress); - - mTcpPort = port; - while (!mTcpServer->listen(QHostAddress::Any, mTcpPort)) { - mTcpPort++; - if (mTcpPort > 1764) { //No ports available? - qCritical(KDECONNECT_CORE) << "Error opening a port in range 1714-1764"; - mTcpPort = 0; + const QHostAddress bindAddress = m_testMode? QHostAddress::LocalHost : QHostAddress::Any; + + bool success = m_udpSocket.bind(bindAddress, UDP_PORT, QUdpSocket::ShareAddress); + Q_ASSERT(success); + + qCDebug(KDECONNECT_CORE) << "onStart"; + + m_tcpPort = MIN_TCP_PORT; + while (!m_server->listen(bindAddress, m_tcpPort)) { + m_tcpPort++; + if (m_tcpPort > MAX_TCP_PORT) { //No ports available? + qCritical(KDECONNECT_CORE) << "Error opening a port in range" << MIN_TCP_PORT << "-" << MAX_TCP_PORT; + m_tcpPort = 0; return; } } onNetworkChange(); } void LanLinkProvider::onStop() { - mUdpServer->close(); - mTcpServer->close(); + qCDebug(KDECONNECT_CORE) << "onStop"; + m_udpSocket.close(); + m_server->close(); } -//I'm in a new network, let's be polite and introduce myself void LanLinkProvider::onNetworkChange() { - if (!mTcpServer->isListening()) { + if (m_combineBroadcastsTimer.isActive()) { + qCDebug(KDECONNECT_CORE()) << "Preventing duplicate broadcasts"; + return; + } + m_combineBroadcastsTimer.start(); +} + +//I'm in a new network, let's be polite and introduce myself +void LanLinkProvider::broadcastToNetwork() +{ + + if (!m_server->isListening()) { //Not started return; } - Q_ASSERT(mTcpPort != 0); + Q_ASSERT(m_tcpPort != 0); qCDebug(KDECONNECT_CORE()) << "Broadcasting identity packet"; - NetworkPackage np(""); + + QHostAddress destAddress = m_testMode? QHostAddress::LocalHost : QHostAddress(QStringLiteral("255.255.255.255")); + + NetworkPackage np(QLatin1String("")); NetworkPackage::createIdentityPackage(&np); - np.set("tcpPort", mTcpPort); - mUdpSocket.writeDatagram(np.serialize(), QHostAddress("255.255.255.255"), port); + np.set(QStringLiteral("tcpPort"), m_tcpPort); + +#ifdef Q_OS_WIN + //On Windows we need to broadcast from every local IP address to reach all networks + QUdpSocket sendSocket; + sendSocket.setProxy(QNetworkProxy::NoProxy); + for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) { + if ( (iface.flags() & QNetworkInterface::IsUp) + && (iface.flags() & QNetworkInterface::IsRunning) + && (iface.flags() & QNetworkInterface::CanBroadcast)) { + for (const QNetworkAddressEntry& ifaceAddress : iface.addressEntries()) { + QHostAddress sourceAddress = ifaceAddress.ip(); + if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) { + qCDebug(KDECONNECT_CORE()) << "Broadcasting as" << sourceAddress; + sendSocket.bind(sourceAddress, UDP_PORT); + sendSocket.writeDatagram(np.serialize(), destAddress, UDP_PORT); + sendSocket.close(); + } + } + } + } +#else + m_udpSocket.writeDatagram(np.serialize(), destAddress, UDP_PORT); +#endif + } //I'm the existing device, a new device is kindly introducing itself. //I will create a TcpSocket and try to connect. This can result in either connected() or connectError(). -void LanLinkProvider::newUdpConnection() +void LanLinkProvider::newUdpConnection() //udpBroadcastReceived { - while (mUdpServer->hasPendingDatagrams()) { + while (m_udpSocket.hasPendingDatagrams()) { + QByteArray datagram; - datagram.resize(mUdpServer->pendingDatagramSize()); + datagram.resize(m_udpSocket.pendingDatagramSize()); QHostAddress sender; - mUdpServer->readDatagram(datagram.data(), datagram.size(), &sender); + m_udpSocket.readDatagram(datagram.data(), datagram.size(), &sender); + + if (sender.isLoopback() && !m_testMode) + continue; - NetworkPackage* receivedPackage = new NetworkPackage(""); + NetworkPackage* receivedPackage = new NetworkPackage(QLatin1String("")); bool success = NetworkPackage::unserialize(datagram, receivedPackage); + //qCDebug(KDECONNECT_CORE) << "udp connection from " << receivedPackage->; + + //qCDebug(KDECONNECT_CORE) << "Datagram " << datagram.data() ; + if (!success || receivedPackage->type() != PACKAGE_TYPE_IDENTITY) { delete receivedPackage; continue; } - if (receivedPackage->get("deviceId") == KdeConnectConfig::instance()->deviceId()) { + if (receivedPackage->get(QStringLiteral("deviceId")) == KdeConnectConfig::instance()->deviceId()) { //qCDebug(KDECONNECT_CORE) << "Ignoring my own broadcast"; delete receivedPackage; continue; } - int tcpPort = receivedPackage->get("tcpPort", port); + int tcpPort = receivedPackage->get(QStringLiteral("tcpPort")); //qCDebug(KDECONNECT_CORE) << "Received Udp identity package from" << sender << " asking for a tcp connection on port " << tcpPort; - QTcpSocket* socket = new QTcpSocket(this); - receivedIdentityPackages[socket].np = receivedPackage; - receivedIdentityPackages[socket].sender = sender; - connect(socket, SIGNAL(connected()), this, SLOT(connected())); + QSslSocket* socket = new QSslSocket(this); + socket->setProxy(QNetworkProxy::NoProxy); + m_receivedIdentityPackages[socket].np = receivedPackage; + m_receivedIdentityPackages[socket].sender = sender; + connect(socket, &QAbstractSocket::connected, this, &LanLinkProvider::connected); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectError())); socket->connectToHost(sender, tcpPort); } } void LanLinkProvider::connectError() { - QTcpSocket* socket = qobject_cast(sender()); + QSslSocket* socket = qobject_cast(sender()); if (!socket) return; - disconnect(socket, SIGNAL(connected()), this, SLOT(connected())); + disconnect(socket, &QAbstractSocket::connected, this, &LanLinkProvider::connected); disconnect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectError())); - qCDebug(KDECONNECT_CORE) << "Fallback (1), try reverse connection (send udp packet)"; - NetworkPackage np(""); + qCDebug(KDECONNECT_CORE) << "Fallback (1), try reverse connection (send udp packet)" << socket->errorString(); + NetworkPackage np(QLatin1String("")); NetworkPackage::createIdentityPackage(&np); - np.set("tcpPort", mTcpPort); - mUdpSocket.writeDatagram(np.serialize(), receivedIdentityPackages[socket].sender, port); + np.set(QStringLiteral("tcpPort"), m_tcpPort); + m_udpSocket.writeDatagram(np.serialize(), m_receivedIdentityPackages[socket].sender, UDP_PORT); //The socket we created didn't work, and we didn't manage //to create a LanDeviceLink from it, deleting everything. - delete receivedIdentityPackages[socket].np; - receivedIdentityPackages.remove(socket); + delete m_receivedIdentityPackages.take(socket).np; delete socket; } +//We received a UDP package and answered by connecting to them by TCP. This gets called on a succesful connection. void LanLinkProvider::connected() { - QTcpSocket* socket = qobject_cast(sender()); + QSslSocket* socket = qobject_cast(sender()); + if (!socket) return; - disconnect(socket, SIGNAL(connected()), this, SLOT(connected())); + disconnect(socket, &QAbstractSocket::connected, this, &LanLinkProvider::connected); disconnect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectError())); configureSocket(socket); - NetworkPackage* receivedPackage = receivedIdentityPackages[socket].np; - const QString& deviceId = receivedPackage->get("deviceId"); - //qCDebug(KDECONNECT_CORE) << "Connected" << socket->isWritable(); + // If socket disconnects due to any reason after connection, link on ssl faliure + connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); - LanDeviceLink* deviceLink = new LanDeviceLink(deviceId, this, socket); + NetworkPackage* receivedPackage = m_receivedIdentityPackages[socket].np; + const QString& deviceId = receivedPackage->get(QStringLiteral("deviceId")); + //qCDebug(KDECONNECT_CORE) << "Connected" << socket->isWritable(); - NetworkPackage np2(""); + // If network is on ssl, do not believe when they are connected, believe when handshake is completed + NetworkPackage np2(QLatin1String("")); NetworkPackage::createIdentityPackage(&np2); - bool success = deviceLink->sendPackage(np2); + socket->write(np2.serialize()); + bool success = socket->waitForBytesWritten(); if (success) { - //qCDebug(KDECONNECT_CORE) << "Handshaking done (i'm the existing device)"; + qCDebug(KDECONNECT_CORE) << "TCP connection done (i'm the existing device)"; - connect(deviceLink, SIGNAL(destroyed(QObject*)), - this, SLOT(deviceLinkDestroyed(QObject*))); + // if ssl supported + if (receivedPackage->get(QStringLiteral("protocolVersion")) >= MIN_VERSION_WITH_SSL_SUPPORT) { - Q_EMIT onConnectionReceived(*receivedPackage, deviceLink); + bool isDeviceTrusted = KdeConnectConfig::instance()->trustedDevices().contains(deviceId); + configureSslSocket(socket, deviceId, isDeviceTrusted); - //We kill any possible link from this same device - 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); - } + qCDebug(KDECONNECT_CORE) << "Starting server ssl (I'm the client TCP socket)"; + + connect(socket, &QSslSocket::encrypted, this, &LanLinkProvider::encrypted); + + if (isDeviceTrusted) { + connect(socket, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + } - mLinks[deviceId] = deviceLink; + socket->startServerEncryption(); + + return; // Return statement prevents from deleting received package, needed in slot "encrypted" + } else { + qWarning() << receivedPackage->get(QStringLiteral("deviceName")) << "uses an old protocol version, this won't work"; + //addLink(deviceId, socket, receivedPackage, LanDeviceLink::Remotely); + } } else { //I think this will never happen, but if it happens the deviceLink //(or the socket that is now inside it) might not be valid. Delete them. - delete deviceLink; qCDebug(KDECONNECT_CORE) << "Fallback (2), try reverse connection (send udp packet)"; - mUdpSocket.writeDatagram(np2.serialize(), receivedIdentityPackages[socket].sender, port); + m_udpSocket.writeDatagram(np2.serialize(), m_receivedIdentityPackages[socket].sender, UDP_PORT); } - delete receivedPackage; - receivedIdentityPackages.remove(socket); + delete m_receivedIdentityPackages.take(socket).np; //We don't delete the socket because now it's owned by the LanDeviceLink } -//I'm the new device and this is the answer to my UDP identity package (no data received yet) +void LanLinkProvider::encrypted() +{ + qCDebug(KDECONNECT_CORE) << "Socket succesfully stablished an SSL connection"; + + QSslSocket* socket = qobject_cast(sender()); + if (!socket) return; + disconnect(socket, &QSslSocket::encrypted, this, &LanLinkProvider::encrypted); + disconnect(socket, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + + Q_ASSERT(socket->mode() != QSslSocket::UnencryptedMode); + LanDeviceLink::ConnectionStarted connectionOrigin = (socket->mode() == QSslSocket::SslClientMode)? LanDeviceLink::Locally : LanDeviceLink::Remotely; + + NetworkPackage* receivedPackage = m_receivedIdentityPackages[socket].np; + const QString& deviceId = receivedPackage->get(QStringLiteral("deviceId")); + + addLink(deviceId, socket, receivedPackage, connectionOrigin); + + // Copied from connected slot, now delete received package + delete m_receivedIdentityPackages.take(socket).np; +} + +void LanLinkProvider::sslErrors(const QList& errors) +{ + QSslSocket* socket = qobject_cast(sender()); + if (!socket) return; + + disconnect(socket, &QSslSocket::encrypted, this, &LanLinkProvider::encrypted); + disconnect(socket, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + + qCDebug(KDECONNECT_CORE) << "Failing due to " << errors; + Device* device = Daemon::instance()->getDevice(socket->peerVerifyName()); + if (device) { + device->unpair(); + } + + delete m_receivedIdentityPackages.take(socket).np; + // Socket disconnects itself on ssl error and will be deleted by deleteLater slot, no need to delete manually +} + +//I'm the new device and this is the answer to my UDP identity package (no data received yet). They are connecting to us through TCP, and they should send an identity. void LanLinkProvider::newConnection() { //qCDebug(KDECONNECT_CORE) << "LanLinkProvider newConnection"; - while (mTcpServer->hasPendingConnections()) { - QTcpSocket* socket = mTcpServer->nextPendingConnection(); + while (m_server->hasPendingConnections()) { + QSslSocket* socket = m_server->nextPendingConnection(); configureSocket(socket); //This socket is still managed by us (and child of the QTcpServer), if //it disconnects before we manage to pass it to a LanDeviceLink, it's - //our responsability to delete it. We do so with this connection. - connect(socket, SIGNAL(disconnected()), - socket, SLOT(deleteLater())); - connect(socket, SIGNAL(readyRead()), - this, SLOT(dataReceived())); + //our responsibility to delete it. We do so with this connection. + connect(socket, &QAbstractSocket::disconnected, + socket, &QObject::deleteLater); + connect(socket, &QIODevice::readyRead, + this, &LanLinkProvider::dataReceived); } - } //I'm the new device and this is the answer to my UDP identity package (data received) void LanLinkProvider::dataReceived() { - QTcpSocket* socket = qobject_cast(sender()); + QSslSocket* socket = qobject_cast(sender()); const QByteArray data = socket->readLine(); //qCDebug(KDECONNECT_CORE) << "LanLinkProvider received reply:" << data; - NetworkPackage np(""); - bool success = NetworkPackage::unserialize(data, &np); - //qCDebug(KDECONNECT_CORE) << "LanLinkProvider received reply:" << data; + NetworkPackage* np = new NetworkPackage(QLatin1String("")); + bool success = NetworkPackage::unserialize(data, np); + + if (!success) { + delete np; + return; + } - if (!success || np.type() != PACKAGE_TYPE_IDENTITY) { - qCDebug(KDECONNECT_CORE) << "LanLinkProvider/newConnection: Not an identification package (wuh?)"; + if (np->type() != PACKAGE_TYPE_IDENTITY) { + qCWarning(KDECONNECT_CORE) << "LanLinkProvider/newConnection: Expected identity, received " << np->type(); + delete np; return; } + // Needed in "encrypted" if ssl is used, similar to "connected" + m_receivedIdentityPackages[socket].np = np; + + const QString& deviceId = np->get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Handshaking done (i'm the new device)"; - //This socket will now be owned by the LanDeviceLink, forget about it - disconnect(socket, SIGNAL(readyRead()), - this, SLOT(dataReceived())); - disconnect(socket, SIGNAL(disconnected()), - socket, SLOT(deleteLater())); - - const QString& deviceId = np.get("deviceId"); - LanDeviceLink* deviceLink = new LanDeviceLink(deviceId, this, socket); - connect(deviceLink, SIGNAL(destroyed(QObject*)), - this, SLOT(deviceLinkDestroyed(QObject*))); - - Q_EMIT onConnectionReceived(np, deviceLink); - - 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); - } + //This socket will now be owned by the LanDeviceLink or we don't want more data to be received, forget about it + disconnect(socket, &QIODevice::readyRead, this, &LanLinkProvider::dataReceived); + + if (np->get(QStringLiteral("protocolVersion")) >= MIN_VERSION_WITH_SSL_SUPPORT) { + + bool isDeviceTrusted = KdeConnectConfig::instance()->trustedDevices().contains(deviceId); + configureSslSocket(socket, deviceId, isDeviceTrusted); + + qCDebug(KDECONNECT_CORE) << "Starting client ssl (but I'm the server TCP socket)"; + + connect(socket, &QSslSocket::encrypted, this, &LanLinkProvider::encrypted); - mLinks[deviceId] = deviceLink; + if (isDeviceTrusted) { + connect(socket, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + } + + socket->startClientEncryption(); + } else { + qWarning() << np->get(QStringLiteral("deviceName")) << "uses an old protocol version, this won't work"; + //addLink(deviceId, socket, np, LanDeviceLink::Locally); + delete m_receivedIdentityPackages.take(socket).np; + } } void LanLinkProvider::deviceLinkDestroyed(QObject* destroyedDeviceLink) { - //qCDebug(KDECONNECT_CORE) << "deviceLinkDestroyed"; const QString id = destroyedDeviceLink->property("deviceId").toString(); - QMap< QString, DeviceLink* >::iterator oldLinkIterator = mLinks.find(id); - if (oldLinkIterator != mLinks.end() && oldLinkIterator.value() == destroyedDeviceLink) { - mLinks.erase(oldLinkIterator); + //qCDebug(KDECONNECT_CORE) << "deviceLinkDestroyed" << id; + Q_ASSERT(m_links.key(static_cast(destroyedDeviceLink)) == id); + QMap< QString, LanDeviceLink* >::iterator linkIterator = m_links.find(id); + if (linkIterator != m_links.end()) { + Q_ASSERT(linkIterator.value() == destroyedDeviceLink); + m_links.erase(linkIterator); + m_pairingHandlers.take(id)->deleteLater(); } } -void LanLinkProvider::configureSocket(QTcpSocket* socket) +void LanLinkProvider::configureSslSocket(QSslSocket* socket, const QString& deviceId, bool isDeviceTrusted) { - int fd = socket->socketDescriptor(); + // Setting supported ciphers manually + // Top 3 ciphers are for new Android devices, botton two are for old Android devices + // FIXME : These cipher suites should be checked whether they are supported or not on device + QList socketCiphers; + socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-ECDSA-AES256-GCM-SHA384"))); + socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-ECDSA-AES128-GCM-SHA256"))); + socketCiphers.append(QSslCipher(QStringLiteral("ECDHE-RSA-AES128-SHA"))); + socketCiphers.append(QSslCipher(QStringLiteral("RC4-SHA"))); + socketCiphers.append(QSslCipher(QStringLiteral("RC4-MD5"))); + socketCiphers.append(QSslCipher(QStringLiteral("DHE-RSA-AES256-SHA"))); + + // Configure for ssl + QSslConfiguration sslConfig; + sslConfig.setCiphers(socketCiphers); + sslConfig.setProtocol(QSsl::TlsV1_0); + + socket->setSslConfiguration(sslConfig); + socket->setLocalCertificate(KdeConnectConfig::instance()->certificate()); + socket->setPrivateKey(KdeConnectConfig::instance()->privateKeyPath()); + socket->setPeerVerifyName(deviceId); + + if (isDeviceTrusted) { + QString certString = KdeConnectConfig::instance()->getDeviceProperty(deviceId, QStringLiteral("certificate"), QString()); + socket->addCaCertificate(QSslCertificate(certString.toLatin1())); + socket->setPeerVerifyMode(QSslSocket::VerifyPeer); + } else { + socket->setPeerVerifyMode(QSslSocket::QueryPeer); + } + + //Usually SSL errors are only bad for trusted devices. Uncomment this section to log errors in any case, for debugging. + //QObject::connect(socket, static_cast&)>(&QSslSocket::sslErrors), [](const QList& errors) + //{ + // Q_FOREACH (const QSslError& error, errors) { + // qCDebug(KDECONNECT_CORE) << "SSL Error:" << error.errorString(); + // } + //}); +} + +void LanLinkProvider::configureSocket(QSslSocket* socket) { + + socket->setProxy(QNetworkProxy::NoProxy); socket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1)); #ifdef TCP_KEEPIDLE // time to start sending keepalive packets (seconds) int maxIdle = 10; - setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &maxIdle, sizeof(maxIdle)); + setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPIDLE, &maxIdle, sizeof(maxIdle)); #endif #ifdef TCP_KEEPINTVL // interval between keepalive packets after the initial period (seconds) int interval = 5; - setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); + setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); #endif #ifdef TCP_KEEPCNT // number of missed keepalive packets before disconnecting int count = 3; - setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); + setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); #endif } + +void LanLinkProvider::addLink(const QString& deviceId, QSslSocket* socket, NetworkPackage* receivedPackage, LanDeviceLink::ConnectionStarted connectionOrigin) +{ + // Socket disconnection will now be handled by LanDeviceLink + disconnect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); + + LanDeviceLink* deviceLink; + //Do we have a link for this device already? + QMap< QString, LanDeviceLink* >::iterator linkIterator = m_links.find(deviceId); + if (linkIterator != m_links.end()) { + //qCDebug(KDECONNECT_CORE) << "Reusing link to" << deviceId; + deviceLink = linkIterator.value(); + deviceLink->reset(socket, connectionOrigin); + } else { + deviceLink = new LanDeviceLink(deviceId, this, socket, connectionOrigin); + connect(deviceLink, &QObject::destroyed, this, &LanLinkProvider::deviceLinkDestroyed); + m_links[deviceId] = deviceLink; + if (m_pairingHandlers.contains(deviceId)) { + //We shouldn't have a pairinghandler if we didn't have a link. + //Crash if debug, recover if release (by setting the new devicelink to the old pairinghandler) + Q_ASSERT(m_pairingHandlers.contains(deviceId)); + m_pairingHandlers[deviceId]->setDeviceLink(deviceLink); + } + } + Q_EMIT onConnectionReceived(*receivedPackage, deviceLink); +} + +LanPairingHandler* LanLinkProvider::createPairingHandler(DeviceLink* link) +{ + LanPairingHandler* ph = m_pairingHandlers.value(link->deviceId()); + if (!ph) { + ph = new LanPairingHandler(link); + qCDebug(KDECONNECT_CORE) << "creating pairing handler for" << link->deviceId(); + connect (ph, &LanPairingHandler::pairingError, link, &DeviceLink::pairingError); + m_pairingHandlers[link->deviceId()] = ph; + } + return ph; +} + +void LanLinkProvider::userRequestsPair(const QString& deviceId) +{ + LanPairingHandler* ph = createPairingHandler(m_links.value(deviceId)); + ph->requestPairing(); +} + +void LanLinkProvider::userRequestsUnpair(const QString& deviceId) +{ + LanPairingHandler* ph = createPairingHandler(m_links.value(deviceId)); + ph->unpair(); +} + +void LanLinkProvider::incomingPairPackage(DeviceLink* deviceLink, const NetworkPackage& np) +{ + LanPairingHandler* ph = createPairingHandler(deviceLink); + ph->packageReceived(np); +} + diff --git a/core/backends/lan/lanlinkprovider.h b/core/backends/lan/lanlinkprovider.h index b379434b..983fe758 100644 --- a/core/backends/lan/lanlinkprovider.h +++ b/core/backends/lan/lanlinkprovider.h @@ -1,75 +1,100 @@ /** * 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 LANLINKPROVIDER_H #define LANLINKPROVIDER_H #include #include +#include #include +#include +#include +#include -#include "../linkprovider.h" -#include "netaddress.h" +#include "kdeconnectcore_export.h" +#include "backends/linkprovider.h" +#include "server.h" +#include "landevicelink.h" -class LanLinkProvider +class LanPairingHandler; +class KDECONNECTCORE_EXPORT LanLinkProvider : public LinkProvider { Q_OBJECT public: - LanLinkProvider(); - ~LanLinkProvider(); + LanLinkProvider(bool testMode = false); + ~LanLinkProvider() override; - QString name() { return "LanLinkProvider"; } - int priority() { return PRIORITY_HIGH; } + QString name() override { return QStringLiteral("LanLinkProvider"); } + int priority() override { return PRIORITY_HIGH; } + + void userRequestsPair(const QString& deviceId); + void userRequestsUnpair(const QString& deviceId); + void incomingPairPackage(DeviceLink* device, const NetworkPackage& np); + + static void configureSslSocket(QSslSocket* socket, const QString& deviceId, bool isDeviceTrusted); + static void configureSocket(QSslSocket* socket); + + const static quint16 UDP_PORT = 1716; + const static quint16 MIN_TCP_PORT = 1716; + const static quint16 MAX_TCP_PORT = 1764; public Q_SLOTS: - virtual void onNetworkChange(); - virtual void onStart(); - virtual void onStop(); + void onNetworkChange() override; + void onStart() override; + void onStop() override; void connected(); + void encrypted(); void connectError(); private Q_SLOTS: void newUdpConnection(); void newConnection(); void dataReceived(); void deviceLinkDestroyed(QObject* destroyedDeviceLink); + void sslErrors(const QList& errors); + void broadcastToNetwork(); private: - static void configureSocket(QTcpSocket* socket); + LanPairingHandler* createPairingHandler(DeviceLink* link); - QTcpServer* mTcpServer; - QUdpSocket* mUdpServer; - QUdpSocket mUdpSocket; - const static quint16 port = 1714; - quint16 mTcpPort; + void onNetworkConfigurationChanged(const QNetworkConfiguration& config); + void addLink(const QString& deviceId, QSslSocket* socket, NetworkPackage* receivedPackage, LanDeviceLink::ConnectionStarted connectionOrigin); - QMap mLinks; + Server* m_server; + QUdpSocket m_udpSocket; + quint16 m_tcpPort; + + QMap m_links; + QMap m_pairingHandlers; struct PendingConnect { NetworkPackage* np; QHostAddress sender; }; - QMap receivedIdentityPackages; - + QMap m_receivedIdentityPackages; + QNetworkConfiguration m_lastConfig; + const bool m_testMode; + QTimer m_combineBroadcastsTimer; }; #endif diff --git a/core/backends/lan/lanpairinghandler.cpp b/core/backends/lan/lanpairinghandler.cpp new file mode 100644 index 00000000..de247533 --- /dev/null +++ b/core/backends/lan/lanpairinghandler.cpp @@ -0,0 +1,142 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "core_debug.h" +#include "daemon.h" +#include "kdeconnectconfig.h" +#include "landevicelink.h" +#include "lanpairinghandler.h" +#include "networkpackagetypes.h" + +LanPairingHandler::LanPairingHandler(DeviceLink* deviceLink) + : PairingHandler(deviceLink) + , m_status(NotPaired) +{ + m_pairingTimeout.setSingleShot(true); + m_pairingTimeout.setInterval(pairingTimeoutMsec()); + connect(&m_pairingTimeout, &QTimer::timeout, this, &LanPairingHandler::pairingTimeout); +} + +void LanPairingHandler::packageReceived(const NetworkPackage& np) +{ + bool wantsPair = np.get(QStringLiteral("pair")); + + if (wantsPair) { + + if (isPairRequested()) { //We started pairing + + qCDebug(KDECONNECT_CORE) << "Pair answer"; + setInternalPairStatus(Paired); + + } else { + qCDebug(KDECONNECT_CORE) << "Pair request"; + + if (isPaired()) { //I'm already paired, but they think I'm not + acceptPairing(); + return; + } + + setInternalPairStatus(RequestedByPeer); + } + + } else { //wantsPair == false + + qCDebug(KDECONNECT_CORE) << "Unpair request"; + + if (isPairRequested()) { + Q_EMIT pairingError(i18n("Canceled by other peer")); + } + setInternalPairStatus(NotPaired); + } +} + +bool LanPairingHandler::requestPairing() +{ + if (m_status == Paired) { + Q_EMIT pairingError(i18n("%1: Already paired", deviceLink()->name())); + return false; + } + if (m_status == RequestedByPeer) { + qCDebug(KDECONNECT_CORE) << deviceLink()->name() << ": Pairing already started by the other end, accepting their request."; + return acceptPairing(); + } + + NetworkPackage np(PACKAGE_TYPE_PAIR, {{"pair", true}}); + const bool success = deviceLink()->sendPackage(np); + if (success) { + setInternalPairStatus(Requested); + } + return success; +} + +bool LanPairingHandler::acceptPairing() +{ + NetworkPackage np(PACKAGE_TYPE_PAIR, {{"pair", true}}); + bool success = deviceLink()->sendPackage(np); + if (success) { + setInternalPairStatus(Paired); + } + return success; +} + +void LanPairingHandler::rejectPairing() +{ + NetworkPackage np(PACKAGE_TYPE_PAIR, {{"pair", false}}); + deviceLink()->sendPackage(np); + setInternalPairStatus(NotPaired); +} + +void LanPairingHandler::unpair() { + NetworkPackage np(PACKAGE_TYPE_PAIR, {{"pair", false}}); + deviceLink()->sendPackage(np); + setInternalPairStatus(NotPaired); +} + +void LanPairingHandler::pairingTimeout() +{ + NetworkPackage np(PACKAGE_TYPE_PAIR, {{"pair", false}}); + deviceLink()->sendPackage(np); + setInternalPairStatus(NotPaired); //Will emit the change as well + Q_EMIT pairingError(i18n("Timed out")); +} + +void LanPairingHandler::setInternalPairStatus(LanPairingHandler::InternalPairStatus status) +{ + if (status == Requested || status == RequestedByPeer) { + m_pairingTimeout.start(); + } else { + m_pairingTimeout.stop(); + } + + if (m_status == RequestedByPeer && (status == NotPaired || status == Paired)) { + Q_EMIT deviceLink()->pairingRequestExpired(this); + } else if (status == RequestedByPeer) { + Q_EMIT deviceLink()->pairingRequest(this); + } + + m_status = status; + if (status == Paired) { + deviceLink()->setPairStatus(DeviceLink::Paired); + } else { + deviceLink()->setPairStatus(DeviceLink::NotPaired); + } +} diff --git a/core/backends/lan/lanpairinghandler.h b/core/backends/lan/lanpairinghandler.h new file mode 100644 index 00000000..1030f89c --- /dev/null +++ b/core/backends/lan/lanpairinghandler.h @@ -0,0 +1,70 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KDECONNECT_LANPAIRINGHANDLER_H +#define KDECONNECT_LANPAIRINGHANDLER_H + +#include +#include + +#include "device.h" +#include "backends/devicelink.h" +#include "backends/pairinghandler.h" + +// This class is used pairing related stuff. It has direct access to links and can directly send packages +class LanPairingHandler + : public PairingHandler +{ + Q_OBJECT + +public: + + enum InternalPairStatus { + NotPaired, + Requested, + RequestedByPeer, + Paired, + }; + + LanPairingHandler(DeviceLink* deviceLink); + ~LanPairingHandler() override { } + + void packageReceived(const NetworkPackage& np) override; + bool requestPairing() override; + bool acceptPairing() override; + void rejectPairing() override; + void unpair() override; + + bool isPairRequested() const { return m_status == Requested; } + bool isPaired() const { return m_status == Paired; } + +private Q_SLOTS: + void pairingTimeout(); + +protected: + void setInternalPairStatus(InternalPairStatus status); + + QTimer m_pairingTimeout; + + InternalPairStatus m_status; +}; + + +#endif //KDECONNECT_LANPAIRINGHANDLER_H diff --git a/core/backends/lan/server.cpp b/core/backends/lan/server.cpp new file mode 100644 index 00000000..0ad416e4 --- /dev/null +++ b/core/backends/lan/server.cpp @@ -0,0 +1,62 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "server.h" + +#include "kdeconnectconfig.h" +#include "lanlinkprovider.h" + +#include +#include +#include + +Server::Server(QObject * parent) + :QTcpServer(parent) +{ + connect(this, &QTcpServer::acceptError, this, &Server::errorFound); +} + +void Server::incomingConnection(qintptr socketDescriptor) { + QSslSocket* serverSocket = new QSslSocket(parent()); + if (serverSocket->setSocketDescriptor(socketDescriptor)) { + pendingConnections.append(serverSocket); + Q_EMIT newConnection(); + } else { + qWarning() << "setSocketDescriptor failed " + serverSocket->errorString(); + delete serverSocket; + } +} + +QSslSocket* Server::nextPendingConnection() { + if (pendingConnections.isEmpty()) { + return Q_NULLPTR; + } else { + return pendingConnections.takeFirst(); + } +} + +bool Server::hasPendingConnections() const { + return !pendingConnections.isEmpty(); +} + +void Server::errorFound(QAbstractSocket::SocketError socketError) +{ + qDebug() << "error:" << socketError; +} diff --git a/core/backends/lan/uploadjob.h b/core/backends/lan/server.h similarity index 55% copy from core/backends/lan/uploadjob.h copy to core/backends/lan/server.h index 12b6f7e6..ad1af003 100644 --- a/core/backends/lan/uploadjob.h +++ b/core/backends/lan/server.h @@ -1,53 +1,52 @@ -/* - * Copyright 2013 Albert Vaca +/** + * Copyright 2015 Vineet Garg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef UPLOADJOB_H -#define UPLOADJOB_H +#ifndef KDECONNECT_SERVER_H +#define KDECONNECT_SERVER_H -#include - -#include -#include #include -#include -#include +#include + +#include "kdeconnectcore_export.h" -class UploadJob - : public KJob +// This class overrides QTcpServer to bind QSslSocket to native socket descriptor instead of QTcpSocket +class KDECONNECTCORE_EXPORT Server + : public QTcpServer { + Q_OBJECT +private: + QList pendingConnections; + public: - UploadJob(const QSharedPointer& source); - virtual void start(); - QVariantMap getTransferInfo(); + Server(QObject* parent = 0); + ~Server() override = default; + + QSslSocket* nextPendingConnection() override; + bool hasPendingConnections() const override; + +protected: + void incomingConnection(qintptr socketDescriptor) override; private: - QSharedPointer mInput; - QTcpServer* mServer; - QTcpSocket* mSocket; - quint16 mPort; - -private Q_SLOTS: - void readyRead(); - void newConnection(); - void aboutToClose(); + void errorFound(QAbstractSocket::SocketError socketError); }; -#endif // UPLOADJOB_H +#endif //KDECONNECT_SERVER_H diff --git a/core/backends/lan/socketlinereader.cpp b/core/backends/lan/socketlinereader.cpp index a6bd85ee..3a9cc0fe 100644 --- a/core/backends/lan/socketlinereader.cpp +++ b/core/backends/lan/socketlinereader.cpp @@ -1,53 +1,53 @@ /** * 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 "socketlinereader.h" -SocketLineReader::SocketLineReader(QTcpSocket* socket, QObject* parent) +SocketLineReader::SocketLineReader(QSslSocket* socket, QObject* parent) : QObject(parent) - , mSocket(socket) + , m_socket(socket) { - connect(mSocket, SIGNAL(readyRead()), - this, SLOT(dataReceived())); + connect(m_socket, &QIODevice::readyRead, + this, &SocketLineReader::dataReceived); } void SocketLineReader::dataReceived() { - while (mSocket->canReadLine()) { - const QByteArray line = mSocket->readLine(); - if (line.length() > 1) { - mPackages.enqueue(line);//we don't want single \n + while (m_socket->canReadLine()) { + const QByteArray line = m_socket->readLine(); + if (line.length() > 1) { //we don't want a single \n + m_packages.enqueue(line); } } //If we still have things to read from the socket, 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 (mSocket->bytesAvailable() > 0) { + if (m_socket->bytesAvailable() > 0) { QMetaObject::invokeMethod(this, "dataReceived", Qt::QueuedConnection); return; } //If we have any packages, tell it to the world. - if (!mPackages.isEmpty()) { + if (!m_packages.isEmpty()) { Q_EMIT readyRead(); } } diff --git a/core/backends/lan/socketlinereader.h b/core/backends/lan/socketlinereader.h index b3be55a7..cebfb822 100644 --- a/core/backends/lan/socketlinereader.h +++ b/core/backends/lan/socketlinereader.h @@ -1,60 +1,63 @@ /** * 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 SOCKETLINEREADER_H #define SOCKETLINEREADER_H #include -#include #include -#include +#include #include +#include + /* * Encapsulates a QTcpSocket and implements the same methods of its API that are * used by LanDeviceLink, but readyRead is emitted only when a newline is found. */ -class SocketLineReader +class KDECONNECTCORE_EXPORT SocketLineReader : public QObject { Q_OBJECT public: - SocketLineReader(QTcpSocket* socket, QObject* parent = 0); + explicit SocketLineReader(QSslSocket* socket, QObject* parent = nullptr); - QByteArray readLine() { return mPackages.dequeue(); } - qint64 write(const QByteArray& data) { return mSocket->write(data); } - QHostAddress peerAddress() const { return mSocket->peerAddress(); } - qint64 bytesAvailable() const { return mPackages.size(); } + QByteArray readLine() { return m_packages.dequeue(); } + qint64 write(const QByteArray& data) { return m_socket->write(data); } + QHostAddress peerAddress() const { return m_socket->peerAddress(); } + QSslCertificate peerCertificate() const { return m_socket->peerCertificate(); } + qint64 bytesAvailable() const { return m_packages.size(); } + QSslSocket* m_socket; + Q_SIGNALS: void readyRead(); private Q_SLOTS: void dataReceived(); private: - QByteArray lastChunk; - QTcpSocket* mSocket; - QQueue mPackages; + QByteArray m_lastChunk; + QQueue m_packages; }; #endif diff --git a/core/backends/lan/uploadjob.cpp b/core/backends/lan/uploadjob.cpp index b0d35fce..573cd89c 100644 --- a/core/backends/lan/uploadjob.cpp +++ b/core/backends/lan/uploadjob.cpp @@ -1,102 +1,133 @@ /* * 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 -#include - #include "uploadjob.h" + +#include + +#include "lanlinkprovider.h" +#include "kdeconnectconfig.h" #include "core_debug.h" -UploadJob::UploadJob(const QSharedPointer& source): KJob() +UploadJob::UploadJob(const QSharedPointer& source, const QString& deviceId) + : KJob() + , m_input(source) + , m_server(new Server(this)) + , m_socket(nullptr) + , m_port(0) + , m_deviceId(deviceId) // We will use this info if link is on ssl, to send encrypted payload { - mInput = source; - mServer = new QTcpServer(this); - mSocket = 0; - mPort = 0; - - connect(mInput.data(), SIGNAL(readyRead()), this, SLOT(readyRead())); - connect(mInput.data(), SIGNAL(aboutToClose()), this, SLOT(aboutToClose())); + connect(m_input.data(), &QIODevice::readyRead, this, &UploadJob::startUploading); + connect(m_input.data(), &QIODevice::aboutToClose, this, &UploadJob::aboutToClose); } void UploadJob::start() { - mPort = 1739; - while (!mServer->listen(QHostAddress::Any, mPort)) { - mPort++; - if (mPort > 1764) { //No ports available? - qWarning(KDECONNECT_CORE) << "Error opening a port in range 1739-1764 for file transfer"; - mPort = 0; + m_port = MIN_PORT; + while (!m_server->listen(QHostAddress::Any, m_port)) { + m_port++; + if (m_port > MAX_PORT) { //No ports available? + qCWarning(KDECONNECT_CORE) << "Error opening a port in range" << MIN_PORT << "-" << MAX_PORT; + m_port = 0; + setError(1); + setErrorText(i18n("Couldn't find an available port")); + emitResult(); return; } } - connect(mServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + connect(m_server, &QTcpServer::newConnection, this, &UploadJob::newConnection); } void UploadJob::newConnection() { - - if (mSocket || !mServer->hasPendingConnections()) return; - - if (!mInput->open(QIODevice::ReadOnly)) { - qWarning() << "error when opening the input to upload"; + if (!m_input->open(QIODevice::ReadOnly)) { + qCWarning(KDECONNECT_CORE) << "error when opening the input to upload"; return; //TODO: Handle error, clean up... } - mSocket = mServer->nextPendingConnection(); - readyRead(); + Server* server = qobject_cast(sender()); + // FIXME : It is called again when payload sending is finished. Unsolved mystery :( + disconnect(m_server, &QTcpServer::newConnection, this, &UploadJob::newConnection); + + m_socket = server->nextPendingConnection(); + m_socket->setParent(this); + connect(m_socket, &QSslSocket::disconnected, this, &UploadJob::cleanup); + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketFailed(QAbstractSocket::SocketError))); + connect(m_socket, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + connect(m_socket, &QSslSocket::encrypted, this, &UploadJob::startUploading); +// connect(mSocket, &QAbstractSocket::stateChanged, [](QAbstractSocket::SocketState state){ qDebug() << "statechange" << state; }); + + LanLinkProvider::configureSslSocket(m_socket, m_deviceId, true); + + m_socket->startServerEncryption(); } -void UploadJob::readyRead() +void UploadJob::startUploading() { - //TODO: Implement payload encryption - - while ( mInput->bytesAvailable() > 0 ) + while ( m_input->bytesAvailable() > 0 ) { - qint64 bytes = qMin(mInput->bytesAvailable(), (qint64)4096); - int w = mSocket->write(mInput->read(bytes)); + qint64 bytes = qMin(m_input->bytesAvailable(), (qint64)4096); + int w = m_socket->write(m_input->read(bytes)); if (w<0) { - qWarning() << "error when writing data to upload" << bytes << mInput->bytesAvailable(); + qCWarning(KDECONNECT_CORE) << "error when writing data to upload" << bytes << m_input->bytesAvailable(); break; } else { - while ( mSocket->flush() ); + while ( m_socket->flush() ); } } - - mInput->close(); + m_input->close(); } void UploadJob::aboutToClose() { - mSocket->disconnectFromHost(); - mSocket->close(); +// qDebug() << "closing..."; + m_socket->disconnectFromHost(); +} + +void UploadJob::cleanup() +{ + m_socket->close(); +// qDebug() << "closed!"; emitResult(); } -QVariantMap UploadJob::getTransferInfo() +QVariantMap UploadJob::transferInfo() { - Q_ASSERT(mPort != 0); + Q_ASSERT(m_port != 0); + return {{"port", m_port}}; +} - QVariantMap ret; - ret["port"] = mPort; - return ret; +void UploadJob::socketFailed(QAbstractSocket::SocketError error) +{ + qWarning() << "error uploading" << error; + setError(2); + emitResult(); + m_socket->close(); } +void UploadJob::sslErrors(const QList& errors) +{ + qWarning() << "ssl errors" << errors; + setError(1); + emitResult(); + m_socket->close(); +} diff --git a/core/backends/lan/uploadjob.h b/core/backends/lan/uploadjob.h index 12b6f7e6..e449e415 100644 --- a/core/backends/lan/uploadjob.h +++ b/core/backends/lan/uploadjob.h @@ -1,53 +1,63 @@ /* * 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 UPLOADJOB_H #define UPLOADJOB_H #include #include #include -#include -#include #include +#include +#include "server.h" -class UploadJob +class KDECONNECTCORE_EXPORT UploadJob : public KJob { Q_OBJECT public: - UploadJob(const QSharedPointer& source); - virtual void start(); - QVariantMap getTransferInfo(); + explicit UploadJob(const QSharedPointer& source, const QString& deviceId); + + void start() override; + + QVariantMap transferInfo(); private: - QSharedPointer mInput; - QTcpServer* mServer; - QTcpSocket* mSocket; - quint16 mPort; + const QSharedPointer m_input; + Server * const m_server; + QSslSocket* m_socket; + quint16 m_port; + const QString m_deviceId; + + const static quint16 MIN_PORT = 1739; + const static quint16 MAX_PORT = 1764; private Q_SLOTS: - void readyRead(); + void startUploading(); void newConnection(); void aboutToClose(); + void cleanup(); + + void socketFailed(QAbstractSocket::SocketError); + void sslErrors(const QList& errors); }; #endif // UPLOADJOB_H diff --git a/core/backends/linkprovider.h b/core/backends/linkprovider.h index f4fc3097..70f35e1e 100644 --- a/core/backends/linkprovider.h +++ b/core/backends/linkprovider.h @@ -1,62 +1,61 @@ /** * 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 LINKPROVIDER_H #define LINKPROVIDER_H #include -#include -#include #include "core/networkpackage.h" +#include "pairinghandler.h" class DeviceLink; -class LinkProvider +class KDECONNECTCORE_EXPORT LinkProvider : public QObject { Q_OBJECT public: const static int PRIORITY_LOW = 0; //eg: 3g internet const static int PRIORITY_MEDIUM = 50; //eg: internet const static int PRIORITY_HIGH = 100; //eg: lan LinkProvider(); - virtual ~LinkProvider() { } + ~LinkProvider() override = default; virtual QString name() = 0; virtual int priority() = 0; public Q_SLOTS: virtual void onStart() = 0; virtual void onStop() = 0; virtual void onNetworkChange() = 0; Q_SIGNALS: //NOTE: The provider will destroy the DeviceLink when it's no longer accessible, // and every user should listen to the destroyed signal to remove its references. // That's the reason because there is no "onConnectionLost". void onConnectionReceived(const NetworkPackage& identityPackage, DeviceLink*) const; }; #endif diff --git a/core/backends/loopback/loopbackdevicelink.cpp b/core/backends/loopback/loopbackdevicelink.cpp index 718c5b8f..586f7d9f 100644 --- a/core/backends/loopback/loopbackdevicelink.cpp +++ b/core/backends/loopback/loopbackdevicelink.cpp @@ -1,71 +1,52 @@ /** * 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 "loopbackdevicelink.h" #include "loopbacklinkprovider.h" LoopbackDeviceLink::LoopbackDeviceLink(const QString& deviceId, LoopbackLinkProvider* provider) : DeviceLink(deviceId, provider) { } -bool LoopbackDeviceLink::sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& input) +QString LoopbackDeviceLink::name() { - if (mPrivateKey.isNull() || key.isNull()) { - return false; - } - - input.encrypt(key); - - QByteArray serialized = input.serialize(); - - NetworkPackage unserialized(QString::null); - NetworkPackage::unserialize(serialized, &unserialized); - - NetworkPackage output(QString::null); - unserialized.decrypt(mPrivateKey, &output); - - //LoopbackDeviceLink does not need deviceTransferInfo - if (input.hasPayload()) { - output.setPayload(input.payload(), input.payloadSize()); - } - - Q_EMIT receivedPackage(output); - - return true; + return QStringLiteral("LoopbackLink"); } bool LoopbackDeviceLink::sendPackage(NetworkPackage& input) { NetworkPackage output(QString::null); NetworkPackage::unserialize(input.serialize(), &output); //LoopbackDeviceLink does not need deviceTransferInfo if (input.hasPayload()) { + bool b = input.payload()->open(QIODevice::ReadOnly); + Q_ASSERT(b); output.setPayload(input.payload(), input.payloadSize()); } Q_EMIT receivedPackage(output); return true; } diff --git a/core/backends/loopback/loopbackdevicelink.h b/core/backends/loopback/loopbackdevicelink.h index 9d1ae14e..7cbb56a6 100644 --- a/core/backends/loopback/loopbackdevicelink.h +++ b/core/backends/loopback/loopbackdevicelink.h @@ -1,40 +1,42 @@ /** * 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 LOOPBACKDEVICELINK_H #define LOOPBACKDEVICELINK_H #include "../devicelink.h" class LoopbackLinkProvider; class LoopbackDeviceLink : public DeviceLink { Q_OBJECT public: LoopbackDeviceLink(const QString& d, LoopbackLinkProvider* a); - virtual bool sendPackage(NetworkPackage& np); - virtual bool sendPackageEncrypted(QCA::PublicKey& publicKey, NetworkPackage& np); + QString name() override; + bool sendPackage(NetworkPackage& np) override; + void userRequestsPair() override { setPairStatus(Paired); } + void userRequestsUnpair() override { setPairStatus(NotPaired); } }; #endif diff --git a/core/backends/loopback/loopbacklinkprovider.cpp b/core/backends/loopback/loopbacklinkprovider.cpp index 5442c82b..87dff862 100644 --- a/core/backends/loopback/loopbacklinkprovider.cpp +++ b/core/backends/loopback/loopbacklinkprovider.cpp @@ -1,61 +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 . */ #include "loopbacklinkprovider.h" #include "core_debug.h" LoopbackLinkProvider::LoopbackLinkProvider() : identityPackage(PACKAGE_TYPE_IDENTITY) { - loopbackDeviceLink = 0; NetworkPackage::createIdentityPackage(&identityPackage); } LoopbackLinkProvider::~LoopbackLinkProvider() { } void LoopbackLinkProvider::onNetworkChange() { - LoopbackDeviceLink* newLoopbackDeviceLink = new LoopbackDeviceLink("loopback", this); + LoopbackDeviceLink* newLoopbackDeviceLink = new LoopbackDeviceLink(QStringLiteral("loopback"), this); Q_EMIT onConnectionReceived(identityPackage, newLoopbackDeviceLink); if (loopbackDeviceLink) { delete loopbackDeviceLink; } loopbackDeviceLink = newLoopbackDeviceLink; } void LoopbackLinkProvider::onStart() { onNetworkChange(); } void LoopbackLinkProvider::onStop() { if (loopbackDeviceLink) { delete loopbackDeviceLink; - loopbackDeviceLink = 0; } } diff --git a/core/backends/loopback/loopbacklinkprovider.h b/core/backends/loopback/loopbacklinkprovider.h index 9dcb5296..e7d0f146 100644 --- a/core/backends/loopback/loopbacklinkprovider.h +++ b/core/backends/loopback/loopbacklinkprovider.h @@ -1,48 +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 LOOPBACKLINKPROVIDER_H #define LOOPBACKLINKPROVIDER_H #include "../linkprovider.h" #include "loopbackdevicelink.h" +#include class LoopbackLinkProvider : public LinkProvider { Q_OBJECT public: LoopbackLinkProvider(); - ~LoopbackLinkProvider(); + ~LoopbackLinkProvider() override; - QString name() { return "LoopbackLinkProvider"; } - int priority() { return PRIORITY_LOW; } + QString name() override { return QStringLiteral("LoopbackLinkProvider"); } + int priority() override { return PRIORITY_LOW; } - virtual void onStart(); - virtual void onStop(); - virtual void onNetworkChange(); + void onStart() override; + void onStop() override; + void onNetworkChange() override; private: - LoopbackDeviceLink* loopbackDeviceLink; + QPointer loopbackDeviceLink; NetworkPackage identityPackage; }; #endif diff --git a/core/dbushelper.cpp b/core/backends/pairinghandler.cpp similarity index 70% copy from core/dbushelper.cpp copy to core/backends/pairinghandler.cpp index e188126d..04574f93 100644 --- a/core/dbushelper.cpp +++ b/core/backends/pairinghandler.cpp @@ -1,33 +1,40 @@ /** - * Copyright 2014 Albert Vaca + * Copyright 2015 Albert Vaca Cintora * * 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 "dbushelper.h" +#include "pairinghandler.h" -#include +PairingHandler::PairingHandler(DeviceLink* parent) + : QObject(parent) + , m_deviceLink(parent) +{ + +} -namespace DbusHelper { +void PairingHandler::setDeviceLink(DeviceLink* dl) +{ + setParent(dl); + m_deviceLink = dl; +} -void filterNonExportableCharacters(QString& s) +DeviceLink* PairingHandler::deviceLink() const { - static QRegExp regexp("[^A-Za-z0-9_]", Qt::CaseSensitive, QRegExp::Wildcard); - s.replace(regexp,"_"); + return m_deviceLink; } -} \ No newline at end of file diff --git a/core/backends/pairinghandler.h b/core/backends/pairinghandler.h new file mode 100644 index 00000000..9cddd2f5 --- /dev/null +++ b/core/backends/pairinghandler.h @@ -0,0 +1,68 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KDECONNECT_PAIRINGHANDLER_H +#define KDECONNECT_PAIRINGHANDLER_H + +#include "networkpackage.h" +#include "devicelink.h" + +/* + * This class separates the pairing interface for each type of link. + * Since different links can pair via different methods, like for LanLink certificate and public key should be shared, + * for Bluetooth link they should be paired via bluetooth etc. + * Each "Device" instance maintains a hash map for these pairing handlers so that there can be single pairing handler per + * per link type per device. + * Pairing handler keeps information about device, latest link, and pair status of the link + * During first pairing process, the pairing process is nearly same as old process. + * After that if any one of the link is paired, then we can say that device is paired, so new link will pair automatically + */ + +class KDECONNECTCORE_EXPORT PairingHandler + : public QObject +{ + Q_OBJECT + +public: + PairingHandler(DeviceLink* parent); + ~PairingHandler() override = default; + + DeviceLink* deviceLink() const; + void setDeviceLink(DeviceLink* dl); + + virtual void packageReceived(const NetworkPackage& np) = 0; + virtual void unpair() = 0; + static int pairingTimeoutMsec() { return 30 * 1000; } // 30 seconds of timeout (default), subclasses that use different values should override + +public Q_SLOTS: + virtual bool requestPairing() = 0; + virtual bool acceptPairing() = 0; + virtual void rejectPairing() = 0; + +Q_SIGNALS: + void pairingError(const QString& errorMessage); + +private: + DeviceLink* m_deviceLink; + +}; + + +#endif //KDECONNECT_PAIRINGHANDLER_H diff --git a/core/backends/lan/netaddress.h b/core/core_debug.cpp similarity index 69% rename from core/backends/lan/netaddress.h rename to core/core_debug.cpp index f264ba1b..8d1fd92f 100644 --- a/core/backends/lan/netaddress.h +++ b/core/core_debug.cpp @@ -1,37 +1,40 @@ /** * 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 NETADDRESS_H -#define NETADDRESS_H +#include "core_debug.h" -#include +Q_LOGGING_CATEGORY(KDECONNECT_CORE, "kdeconnect.core") -struct NetAddress { - NetAddress() { } - NetAddress(QHostAddress _ip, quint16 _port) : ip(_ip), port(_port) { } - QHostAddress ip; - quint16 port; -}; +#ifdef Q_OS_LINUX +#include +#include +#include +#endif -inline bool operator< (const NetAddress& a, const NetAddress& b){ - return (a.ip.toString()+a.port) < (b.ip.toString()+b.port); +void logBacktrace() +{ +#ifdef Q_OS_LINUX + void* array[32]; + size_t size = backtrace (array, 32); + char** strings = backtrace_symbols (array, size); + backtrace_symbols_fd(array, size, STDERR_FILENO); + free (strings); +#endif } - -#endif // NETADDRESS_H diff --git a/core/core_debug.h b/core/core_debug.h index 33504de0..1960bcf0 100644 --- a/core/core_debug.h +++ b/core/core_debug.h @@ -1,28 +1,32 @@ /** * 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 CORE_DEBUG_H #define CORE_DEBUG_H #include -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_CORE) +#include "kdeconnectcore_export.h" -#endif //CORE_DEBUG_H \ No newline at end of file +KDECONNECTCORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_CORE) + +void logBacktrace(); + +#endif //CORE_DEBUG_H diff --git a/core/daemon.cpp b/core/daemon.cpp index af4c67b0..8fc43053 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,190 +1,287 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "daemon.h" #include #include #include #include #include "core_debug.h" #include "kdeconnectconfig.h" #include "networkpackage.h" + +#ifdef KDECONNECT_BLUETOOTH + #include "backends/bluetooth/bluetoothlinkprovider.h" +#endif + #include "backends/lan/lanlinkprovider.h" #include "backends/loopback/loopbacklinkprovider.h" #include "device.h" -#include "networkpackage.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" -Q_GLOBAL_STATIC(Daemon*, s_instance) +static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them - QSet mLinkProviders; + QSet m_linkProviders; //Every known device - QMap mDevices; + QMap m_devices; + QSet m_discoveryModeAcquisitions; }; Daemon* Daemon::instance() { - Q_ASSERT(s_instance.exists()); - return *s_instance; + Q_ASSERT(s_instance != nullptr); + return s_instance; } -Daemon::Daemon(QObject *parent) +Daemon::Daemon(QObject* parent, bool testMode) : QObject(parent) , d(new DaemonPrivate) { - Q_ASSERT(!s_instance.exists()); - *s_instance = this; + Q_ASSERT(!s_instance); + s_instance = this; qCDebug(KDECONNECT_CORE) << "KdeConnect daemon starting"; //Load backends - d->mLinkProviders.insert(new LanLinkProvider()); - //d->mLinkProviders.insert(new LoopbackLinkProvider()); + 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(); - Q_FOREACH(const QString& id, list) { - Device* device = new Device(this, id); - connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceStatusChanged())); - connect(device, SIGNAL(pairingChanged(bool)), this, SLOT(onDeviceStatusChanged())); - d->mDevices[id] = device; - Q_EMIT deviceAdded(id); + for (const QString& id : list) { + addDevice(new Device(this, id)); } //Listen to new devices - Q_FOREACH (LinkProvider* a, d->mLinkProviders) { - connect(a, SIGNAL(onConnectionReceived(NetworkPackage, DeviceLink*)), - this, SLOT(onNewDeviceLink(NetworkPackage, DeviceLink*))); + for (LinkProvider* a : qAsConst(d->m_linkProviders)) { + connect(a, &LinkProvider::onConnectionReceived, + this, &Daemon::onNewDeviceLink); + a->onStart(); } - setDiscoveryEnabled(true); //Register on DBus - QDBusConnection::sessionBus().registerService("org.kde.kdeconnect"); - QDBusConnection::sessionBus().registerObject("/modules/kdeconnect", this, QDBusConnection::ExportScriptableContents); + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); qCDebug(KDECONNECT_CORE) << "KdeConnect daemon started"; } -void Daemon::setDiscoveryEnabled(bool b) +void Daemon::acquireDiscoveryMode(const QString& key) { - Q_FOREACH (LinkProvider* a, d->mLinkProviders) { - if (b) - a->onStart(); - else - a->onStop(); + 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()); +} + +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->mLinkProviders.size() << " LinkProviders"; - Q_FOREACH (LinkProvider* a, d->mLinkProviders) { + qCDebug(KDECONNECT_CORE) << "Sending onNetworkChange to " << d->m_linkProviders.size() << " LinkProviders"; + for (LinkProvider* a : qAsConst(d->m_linkProviders)) { a->onNetworkChange(); } } -QStringList Daemon::devices(bool onlyReachable, bool onlyVisible) const +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; - Q_FOREACH(Device* device, d->mDevices) { + for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; - if (onlyVisible && !device->isPaired()) 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("deviceId"); + 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"; - Device* device = d->mDevices[id]; + if (d->m_devices.contains(id)) { + qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPackage.get(QStringLiteral("deviceName")); + Device* device = d->m_devices[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"; - + qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPackage.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPackage, dl); - connect(device, SIGNAL(reachableStatusChanged()), this, SLOT(onDeviceStatusChanged())); - connect(device, SIGNAL(pairingChanged(bool)), this, SLOT(onDeviceStatusChanged())); - d->mDevices[id] = device; - Q_EMIT deviceAdded(id); + //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(); - QString id = device->id(); - qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); + //qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); - if (!device->isReachable() && !device->isPaired()) { - qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); - d->mDevices.remove(id); - device->deleteLater(); - Q_EMIT deviceRemoved(id); + if (!device->isReachable() && !device->isTrusted()) { + //qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); + removeDevice(device); } else { - Q_EMIT deviceVisibilityChanged(id, device->isReachable()); + Q_EMIT deviceVisibilityChanged(device->id(), device->isReachable()); } } -void Daemon::setAnnouncedName(QString name) +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); +} + +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/daemon.h b/core/daemon.h index 674ca014..cda42b5f 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -1,81 +1,96 @@ /** * 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 KDECONNECT_DAEMON_H #define KDECONNECT_DAEMON_H #include #include #include #include "kdeconnectcore_export.h" +#include "device.h" class NetworkPackage; class DeviceLink; class Device; class QNetworkAccessManager; class KDECONNECTCORE_EXPORT Daemon : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") + Q_PROPERTY(bool isDiscoveringDevices READ isDiscoveringDevices) + Q_PROPERTY(QStringList pairingRequests READ pairingRequests NOTIFY pairingRequestsChanged) public: - Daemon(QObject *parent); - ~Daemon(); + explicit Daemon(QObject* parent, bool testMode = false); + ~Daemon() override; -public Q_SLOTS: - /** - * Returns the daemon. - * - * Note this can't be called before constructing the Daemon. - */ static Daemon* instance(); - //After calling this, signal deviceDiscovered will be triggered for each device - Q_SCRIPTABLE void setDiscoveryEnabled(bool b); + QList devicesList() const; + + virtual void askPairingConfirmation(Device* device) = 0; + virtual void reportError(const QString& title, const QString& description) = 0; + virtual QNetworkAccessManager* networkAccessManager(); + + Device* getDevice(const QString& deviceId); + + QStringList pairingRequests() const; + + Q_SCRIPTABLE QString selfId() const; +public Q_SLOTS: + Q_SCRIPTABLE void acquireDiscoveryMode(const QString& id); + Q_SCRIPTABLE void releaseDiscoveryMode(const QString& id); Q_SCRIPTABLE void forceOnNetworkChange(); + ///don't try to turn into Q_PROPERTY, it doesn't work Q_SCRIPTABLE QString announcedName(); - Q_SCRIPTABLE void setAnnouncedName(QString name); + Q_SCRIPTABLE void setAnnouncedName(const QString& name); //Returns a list of ids. The respective devices can be manipulated using the dbus path: "/modules/kdeconnect/Devices/"+id - Q_SCRIPTABLE QStringList devices(bool onlyReachable = false, bool onlyVisible = false) const; + Q_SCRIPTABLE QStringList devices(bool onlyReachable = false, bool onlyPaired = false) const; - virtual void requestPairing(Device *d) = 0; - virtual void reportError(const QString &title, const QString &description) = 0; - virtual QNetworkAccessManager* networkAccessManager(); + Q_SCRIPTABLE QString deviceIdByName(const QString& name) const; Q_SIGNALS: Q_SCRIPTABLE void deviceAdded(const QString& id); Q_SCRIPTABLE void deviceRemoved(const QString& id); //Note that paired devices will never be removed Q_SCRIPTABLE void deviceVisibilityChanged(const QString& id, bool isVisible); + Q_SCRIPTABLE void announcedNameChanged(const QString& announcedName); + Q_SCRIPTABLE void pairingRequestsChanged(); private Q_SLOTS: void onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl); void onDeviceStatusChanged(); private: + void addDevice(Device* device); + bool isDiscoveringDevices() const; + void removeDevice(Device* d); + void cleanDevices(); + QScopedPointer d; }; #endif diff --git a/core/dbushelper.cpp b/core/dbushelper.cpp index e188126d..127ec396 100644 --- a/core/dbushelper.cpp +++ b/core/dbushelper.cpp @@ -1,33 +1,33 @@ /** * Copyright 2014 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 "dbushelper.h" #include namespace DbusHelper { void filterNonExportableCharacters(QString& s) { static QRegExp regexp("[^A-Za-z0-9_]", Qt::CaseSensitive, QRegExp::Wildcard); - s.replace(regexp,"_"); + s.replace(regexp,QLatin1String("_")); } -} \ No newline at end of file +} diff --git a/core/dbushelper.h b/core/dbushelper.h index 43b22860..b287a9f6 100644 --- a/core/dbushelper.h +++ b/core/dbushelper.h @@ -1,29 +1,29 @@ /** * Copyright 2014 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 KDECONNECT_DBUSHELPER_H #define KDECONNECT_DBUSHELPER_H #include namespace DbusHelper { void filterNonExportableCharacters(QString& s); } -#endif \ No newline at end of file +#endif diff --git a/core/default_args.h b/core/default_args.h deleted file mode 100644 index b4b9e311..00000000 --- a/core/default_args.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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 DEFAULTARG_H -#define DEFAULTARG_H - -#include - -template -struct default_arg { - static T get(); //Not defined for any other value -}; - -//bool -> false -template<> -struct default_arg { - static bool get() { return false; } -}; - -//int -> -1 -template<> -struct default_arg { - static int get() { return -1; } -}; - -//QByteArray-> empty qbytearray -template<> -struct default_arg { - static QByteArray get() { return QByteArray(); } -}; - -//QStrings -> empty string -template<> -struct default_arg { - static QString get() { return QString(); } -}; - -//QStringList -> empty QStringList -template<> -struct default_arg -{ - static QStringList get() { return QStringList(); } -}; - -template -struct default_arg { - static T* get() { return NULL;} -}; - - -#endif // DEFAULTARG_H diff --git a/core/device.cpp b/core/device.cpp index c2dc6090..601bcb3c 100644 --- a/core/device.cpp +++ b/core/device.cpp @@ -1,524 +1,489 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "device.h" #ifdef interface // MSVC language extension, QDBusConnection uses this as a variable name #undef interface #endif #include -#include -#include +#include #include #include #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 "networkpackage.h" #include "kdeconnectconfig.h" #include "daemon.h" -Q_LOGGING_CATEGORY(KDECONNECT_CORE, "kdeconnect.core") +static void warn(const QString& info) +{ + qWarning() << "Device pairing error" << info; +} Device::Device(QObject* parent, const QString& id) : QObject(parent) , m_deviceId(id) - , m_pairStatus(Device::Paired) - , m_protocolVersion(NetworkPackage::ProtocolVersion) //We don't know it yet + , m_protocolVersion(NetworkPackage::s_protocolVersion) //We don't know it yet { KdeConnectConfig::DeviceInfo info = KdeConnectConfig::instance()->getTrustedDevice(id); m_deviceName = info.deviceName; m_deviceType = str2type(info.deviceType); - m_publicKey = QCA::RSAPublicKey::fromPEM(info.publicKey); - - m_pairingTimeut.setSingleShot(true); - m_pairingTimeut.setInterval(30 * 1000); //30 seconds of timeout - connect(&m_pairingTimeut, SIGNAL(timeout()), - this, SLOT(pairingTimeout())); //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); + + //Assume every plugin is supported until addLink is called and we can get the actual list + m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); + + connect(this, &Device::pairingError, this, &warn); } Device::Device(QObject* parent, const NetworkPackage& identityPackage, DeviceLink* dl) : QObject(parent) - , m_deviceId(identityPackage.get("deviceId")) - , m_deviceName(identityPackage.get("deviceName")) - , m_deviceType(str2type(identityPackage.get("deviceType"))) - , m_pairStatus(Device::NotPaired) - , m_protocolVersion(identityPackage.get("protocolVersion")) - , m_incomingCapabilities(identityPackage.get("SupportedIncomingInterfaces", QStringList()).toSet()) - , m_outgoingCapabilities(identityPackage.get("SupportedOutgoingInterfaces", QStringList()).toSet()) + , m_deviceId(identityPackage.get(QStringLiteral("deviceId"))) + , m_deviceName(identityPackage.get(QStringLiteral("deviceName"))) { addLink(identityPackage, dl); - + //Register in bus QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors); - //Implement deprecated signals - connect(this, &Device::pairingChanged, this, [this](bool newPairing) { - if (newPairing) - Q_EMIT pairingSuccesful(); - else - Q_EMIT unpaired(); - }); + connect(this, &Device::pairingError, this, &warn); } Device::~Device() { - + qDeleteAll(m_deviceLinks); + m_deviceLinks.clear(); } bool Device::hasPlugin(const QString& name) const { return m_plugins.contains(name); } QStringList Device::loadedPlugins() const { return m_plugins.keys(); } void Device::reloadPlugins() { - QHash newPluginMap; - QMultiMap newPluginsByIncomingInterface; - QMultiMap newPluginsByOutgoingInterface; - - if (isPaired() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices + QHash newPluginMap, oldPluginMap = m_plugins; + QMultiMap newPluginsByIncomingCapability; - KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); + if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices PluginLoader* loader = PluginLoader::instance(); - //Code borrowed from KWin - foreach (const QString& pluginName, loader->getPluginList()) { - QString enabledKey = pluginName + QString::fromLatin1("Enabled"); + for (const QString& pluginName : qAsConst(m_supportedPlugins)) { + const KPluginMetaData service = loader->getPluginInfo(pluginName); - bool isPluginEnabled = (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false) - : loader->getPluginInfo(pluginName).isEnabledByDefault()); + const bool pluginEnabled = isPluginEnabled(pluginName); + const QSet incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPackageType")).toSet(); - if (isPluginEnabled) { + if (pluginEnabled) { KdeConnectPlugin* plugin = m_plugins.take(pluginName); - QStringList incomingInterfaces, outgoingInterfaces; - if (plugin) { - incomingInterfaces = m_pluginsByIncomingInterface.keys(plugin); - outgoingInterfaces = m_pluginsByOutgoingInterface.keys(plugin); - } else { - const KPluginMetaData service = loader->getPluginInfo(pluginName); - incomingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType"); - outgoingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-OutgoingPackageType"); - } - - //If we don't find intersection with the received on one end and the sent on the other, we don't - //let the plugin stay - //Also, if no capabilities are specified on the other end, we don't apply this optimizaton, as - //we asume that the other client doesn't know about capabilities. - if (!m_incomingCapabilities.isEmpty() && !m_outgoingCapabilities.isEmpty() - && (m_incomingCapabilities & outgoingInterfaces.toSet()).isEmpty() - && (m_outgoingCapabilities & incomingInterfaces.toSet()).isEmpty() - ) { - delete plugin; - continue; - } if (!plugin) { plugin = loader->instantiatePluginForDevice(pluginName, this); } + Q_ASSERT(plugin); - foreach(const QString& interface, incomingInterfaces) { - newPluginsByIncomingInterface.insert(interface, plugin); - } - foreach(const QString& interface, outgoingInterfaces) { - newPluginsByOutgoingInterface.insert(interface, 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(m_plugins); m_plugins = newPluginMap; - m_pluginsByIncomingInterface = newPluginsByIncomingInterface; - m_pluginsByOutgoingInterface = newPluginsByOutgoingInterface; + m_pluginsByIncomingCapability = newPluginsByIncomingCapability; - Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) { + QDBusConnection bus = QDBusConnection::sessionBus(); + for (KdeConnectPlugin* plugin : qAsConst(m_plugins)) { + //TODO: see how it works in Android (only done once, when created) plugin->connected(); - } - - Q_EMIT pluginsChanged(); + 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("config"); + return KdeConnectConfig::instance()->deviceConfigDir(id()).absoluteFilePath(QStringLiteral("config")); } void Device::requestPair() { - switch(m_pairStatus) { - case Device::Paired: - Q_EMIT pairingFailed(i18n("Already paired")); - return; - case Device::Requested: - Q_EMIT pairingFailed(i18n("Pairing already requested for this device")); - return; - case Device::RequestedByPeer: - qCDebug(KDECONNECT_CORE) << "Pairing already started by the other end, accepting their request."; - acceptPairing(); - return; - case Device::NotPaired: - ; - } - - if (!isReachable()) { - Q_EMIT pairingFailed(i18n("Device not reachable")); + if (isTrusted()) { + Q_EMIT pairingError(i18n("Already paired")); return; } - m_pairStatus = Device::Requested; - - //Send our own public key - bool success = sendOwnPublicKey(); - - if (!success) { - m_pairStatus = Device::NotPaired; - Q_EMIT pairingFailed(i18n("Error contacting device")); + if (!isReachable()) { + Q_EMIT pairingError(i18n("Device not reachable")); return; } - if (m_pairStatus == Device::Paired) { - return; + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + dl->userRequestsPair(); } - - m_pairingTimeut.start(); } void Device::unpair() { - - NetworkPackage np(PACKAGE_TYPE_PAIR); - np.set("pair", false); - sendPackage(np); - - unpairInternal(); + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + dl->userRequestsUnpair(); + } + KdeConnectConfig::instance()->removeTrustedDevice(id()); + Q_EMIT trustedChanged(false); } -void Device::unpairInternal() +void Device::pairStatusChanged(DeviceLink::PairStatus status) { - bool alreadyUnpaired = (m_pairStatus != Device::Paired); - m_pairStatus = Device::NotPaired; - KdeConnectConfig::instance()->removeTrustedDevice(id()); - reloadPlugins(); //Will unload the plugins - if (!alreadyUnpaired) { - Q_EMIT pairingChanged(false); + if (status == DeviceLink::NotPaired) { + KdeConnectConfig::instance()->removeTrustedDevice(id()); + + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + if (dl != sender()) { + dl->setPairStatus(DeviceLink::NotPaired); + } + } + } else { + KdeConnectConfig::instance()->addTrustedDevice(id(), name(), type()); } -} -void Device::pairingTimeout() -{ - NetworkPackage np(PACKAGE_TYPE_PAIR); - np.set("pair", false); - sendPackage(np); - m_pairStatus = Device::NotPaired; - Q_EMIT pairingFailed(i18n("Timed out")); + reloadPlugins(); //Will load/unload plugins + + bool isTrusted = (status == DeviceLink::Paired); + Q_EMIT trustedChanged(isTrusted); + Q_ASSERT(isTrusted == this->isTrusted()); } static bool lessThan(DeviceLink* p1, DeviceLink* p2) { return p1->provider()->priority() > p2->provider()->priority(); } void Device::addLink(const NetworkPackage& identityPackage, DeviceLink* link) { //qCDebug(KDECONNECT_CORE) << "Adding link to" << id() << "via" << link->provider(); - m_protocolVersion = identityPackage.get("protocolVersion"); - if (m_protocolVersion != NetworkPackage::ProtocolVersion) { - qWarning() << m_deviceName << "- warning, device uses a different protocol version" << m_protocolVersion << "expected" << NetworkPackage::ProtocolVersion; + setName(identityPackage.get(QStringLiteral("deviceName"))); + m_deviceType = str2type(identityPackage.get(QStringLiteral("deviceType"))); + + if (m_deviceLinks.contains(link)) + return; + + m_protocolVersion = identityPackage.get(QStringLiteral("protocolVersion"), -1); + if (m_protocolVersion != NetworkPackage::s_protocolVersion) { + qCWarning(KDECONNECT_CORE) << m_deviceName << "- warning, device uses a different protocol version" << m_protocolVersion << "expected" << NetworkPackage::s_protocolVersion; } - connect(link, SIGNAL(destroyed(QObject*)), - this, SLOT(linkDestroyed(QObject*))); + connect(link, &QObject::destroyed, + this, &Device::linkDestroyed); m_deviceLinks.append(link); - //re-read the device name from the identityPackage because it could have changed - setName(identityPackage.get("deviceName")); - m_deviceType = str2type(identityPackage.get("deviceType")); - //Theoretically we will never add two links from the same provider (the provider should destroy //the old one before this is called), so we do not have to worry about destroying old links. //-- Actually, we should not destroy them or the provider will store an invalid ref! - connect(link, SIGNAL(receivedPackage(NetworkPackage)), - this, SLOT(privateReceivedPackage(NetworkPackage))); + connect(link, &DeviceLink::receivedPackage, + this, &Device::privateReceivedPackage); qSort(m_deviceLinks.begin(), m_deviceLinks.end(), lessThan); - if (m_deviceLinks.size() == 1) { - reloadPlugins(); //Will load the plugins - Q_EMIT reachableStatusChanged(); + const bool capabilitiesSupported = identityPackage.has(QStringLiteral("incomingCapabilities")) || identityPackage.has(QStringLiteral("outgoingCapabilities")); + if (capabilitiesSupported) { + const QSet outgoingCapabilities = identityPackage.get(QStringLiteral("outgoingCapabilities")).toSet() + , incomingCapabilities = identityPackage.get(QStringLiteral("incomingCapabilities")).toSet(); + + m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities); + //qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities; } else { - Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) { - plugin->connected(); - } + m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet(); } + + reloadPlugins(); + + if (m_deviceLinks.size() == 1) { + Q_EMIT reachableChanged(true); + } + + connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged); + connect(link, &DeviceLink::pairingRequest, this, &Device::addPairingRequest); + connect(link, &DeviceLink::pairingRequestExpired, this, &Device::removePairingRequest); + connect(link, &DeviceLink::pairingError, this, &Device::pairingError); +} + +void Device::addPairingRequest(PairingHandler* handler) +{ + const bool wasEmpty = m_pairRequests.isEmpty(); + m_pairRequests.insert(handler); + + if (wasEmpty != m_pairRequests.isEmpty()) + Q_EMIT hasPairingRequestsChanged(!m_pairRequests.isEmpty()); +} + +void Device::removePairingRequest(PairingHandler* handler) +{ + const bool wasEmpty = m_pairRequests.isEmpty(); + m_pairRequests.remove(handler); + + if (wasEmpty != m_pairRequests.isEmpty()) + Q_EMIT hasPairingRequestsChanged(!m_pairRequests.isEmpty()); +} + +bool Device::hasPairingRequests() const +{ + return !m_pairRequests.isEmpty(); +} + +void Device::acceptPairing() +{ + if (m_pairRequests.isEmpty()) + qWarning() << "no pair requests to accept!"; + + //copying because the pairing handler will be removed upon accept + const auto prCopy = m_pairRequests; + for (auto ph: prCopy) + ph->acceptPairing(); +} + +void Device::rejectPairing() +{ + if (m_pairRequests.isEmpty()) + qWarning() << "no pair requests to reject!"; + + //copying because the pairing handler will be removed upon reject + const auto prCopy = m_pairRequests; + for (auto ph: prCopy) + ph->rejectPairing(); } void Device::linkDestroyed(QObject* o) { removeLink(static_cast(o)); } void Device::removeLink(DeviceLink* link) { - m_deviceLinks.removeOne(link); + m_deviceLinks.removeAll(link); //qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining"; if (m_deviceLinks.isEmpty()) { reloadPlugins(); - Q_EMIT reachableStatusChanged(); + Q_EMIT reachableChanged(false); } } bool Device::sendPackage(NetworkPackage& np) { - if (np.type() != PACKAGE_TYPE_PAIR && isPaired()) { - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { - if (dl->sendPackageEncrypted(m_publicKey, np)) return true; - } - } else { - //Maybe we could block here any package that is not an identity or a pairing package to prevent sending non encrypted data - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { - if (dl->sendPackage(np)) return true; - } + Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR); + Q_ASSERT(isTrusted()); + + //Maybe we could block here any package that is not an identity or a pairing package to prevent sending non encrypted data + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + if (dl->sendPackage(np)) return true; } return false; } void Device::privateReceivedPackage(const NetworkPackage& np) { - if (np.type() == PACKAGE_TYPE_PAIR) { - - //qCDebug(KDECONNECT_CORE) << "Pair package"; - - bool wantsPair = np.get("pair"); - - if (wantsPair == isPaired()) { - qCDebug(KDECONNECT_CORE) << "Already" << (wantsPair? "paired":"unpaired"); - if (m_pairStatus == Device::Requested) { - m_pairStatus = Device::NotPaired; - m_pairingTimeut.stop(); - Q_EMIT pairingFailed(i18n("Canceled by other peer")); - } - return; - } - - if (wantsPair) { - - //Retrieve their public key - const QString& key = np.get("publicKey"); - m_publicKey = QCA::RSAPublicKey::fromPEM(key); - if (m_publicKey.isNull()) { - qCDebug(KDECONNECT_CORE) << "ERROR decoding key"; - if (m_pairStatus == Device::Requested) { - m_pairStatus = Device::NotPaired; - m_pairingTimeut.stop(); - } - Q_EMIT pairingFailed(i18n("Received incorrect key")); - return; - } - - if (m_pairStatus == Device::Requested) { //We started pairing - - qCDebug(KDECONNECT_CORE) << "Pair answer"; - setAsPaired(); - - } else { - qCDebug(KDECONNECT_CORE) << "Pair request"; - - Daemon::instance()->requestPairing(this); - - m_pairStatus = Device::RequestedByPeer; - } - - } else { - - qCDebug(KDECONNECT_CORE) << "Unpair request"; - - PairStatus prevPairStatus = m_pairStatus; - m_pairStatus = Device::NotPaired; - - if (prevPairStatus == Device::Requested) { - m_pairingTimeut.stop(); - Q_EMIT pairingFailed(i18n("Canceled by other peer")); - } else if (prevPairStatus == Device::Paired) { - unpairInternal(); - } - + Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR); + if (isTrusted()) { + const QList plugins = m_pluginsByIncomingCapability.values(np.type()); + if (plugins.isEmpty()) { + qWarning() << "discarding unsupported package" << np.type() << "for" << name(); } - - } else if (isPaired()) { - QList plugins = m_pluginsByIncomingInterface.values(np.type()); - foreach(KdeConnectPlugin* plugin, plugins) { + for (KdeConnectPlugin* plugin : plugins) { plugin->receivePackage(np); } } else { qCDebug(KDECONNECT_CORE) << "device" << name() << "not paired, ignoring package" << np.type(); - if (m_pairStatus != Device::Requested) - unpair(); + unpair(); } } -bool Device::sendOwnPublicKey() +bool Device::isTrusted() const { - NetworkPackage np(PACKAGE_TYPE_PAIR); - np.set("pair", true); - np.set("publicKey", KdeConnectConfig::instance()->publicKey().toPEM()); - bool success = sendPackage(np); - return success; + return KdeConnectConfig::instance()->trustedDevices().contains(id()); } -void Device::rejectPairing() +QStringList Device::availableLinks() const { - qCDebug(KDECONNECT_CORE) << "Rejected pairing"; - - m_pairStatus = Device::NotPaired; - - NetworkPackage np(PACKAGE_TYPE_PAIR); - np.set("pair", false); - sendPackage(np); - - Q_EMIT pairingFailed(i18n("Canceled by the user")); - + QStringList sl; + sl.reserve(m_deviceLinks.size()); + for (DeviceLink* dl : qAsConst(m_deviceLinks)) { + sl.append(dl->provider()->name()); + } + return sl; } -void Device::acceptPairing() -{ - if (m_pairStatus != Device::RequestedByPeer) return; - - qCDebug(KDECONNECT_CORE) << "Accepted pairing"; - - bool success = sendOwnPublicKey(); - - if (!success) { - m_pairStatus = Device::NotPaired; +void Device::cleanUnneededLinks() { + if (isTrusted()) { return; } - - setAsPaired(); - -} - -void Device::setAsPaired() -{ - - bool alreadyPaired = (m_pairStatus == Device::Paired); - - m_pairStatus = Device::Paired; - - m_pairingTimeut.stop(); //Just in case it was started - - //Save device info in the config - KdeConnectConfig::instance()->addTrustedDevice(id(), name(), type2str(m_deviceType), m_publicKey.toPEM()); - - reloadPlugins(); //Will actually load the plugins - - if (!alreadyPaired) { - Q_EMIT pairingChanged(true); + for(int i = 0; i < m_deviceLinks.size(); ) { + DeviceLink* dl = m_deviceLinks[i]; + if (!dl->linkShouldBeKeptAlive()) { + dl->deleteLater(); + m_deviceLinks.remove(i); + } else { + i++; + } } - } -QStringList Device::availableLinks() const +QHostAddress Device::getLocalIpAddress() const { - QStringList sl; - Q_FOREACH(DeviceLink* dl, m_deviceLinks) { - sl.append(dl->provider()->name()); + for (DeviceLink* dl : m_deviceLinks) { + LanDeviceLink* ldl = dynamic_cast(dl); + if (ldl) { + return ldl->hostAddress(); + } } - return sl; + return QHostAddress::Null; } -Device::DeviceType Device::str2type(QString deviceType) { - if (deviceType == "desktop") return Desktop; - if (deviceType == "laptop") return Laptop; - if (deviceType == "smartphone" || deviceType == "phone") return Phone; - if (deviceType == "tablet") return Tablet; +Device::DeviceType Device::str2type(const QString& deviceType) { + if (deviceType == QLatin1String("desktop")) return Desktop; + if (deviceType == QLatin1String("laptop")) return Laptop; + if (deviceType == QLatin1String("smartphone") || deviceType == QLatin1String("phone")) return Phone; + if (deviceType == QLatin1String("tablet")) return Tablet; return Unknown; } QString Device::type2str(Device::DeviceType deviceType) { - if (deviceType == Desktop) return "desktop"; - if (deviceType == Laptop) return "laptop"; - if (deviceType == Phone) return "smartphone"; - if (deviceType == Tablet) return "tablet"; - return "unknown"; + if (deviceType == Desktop) return QStringLiteral("desktop"); + if (deviceType == Laptop) return QStringLiteral("laptop"); + if (deviceType == Phone) return QStringLiteral("smartphone"); + if (deviceType == Tablet) return QStringLiteral("tablet"); + return QStringLiteral("unknown"); } QString Device::statusIconName() const { - return iconForStatus(isReachable(), isPaired()); + return iconForStatus(isReachable(), isTrusted()); } QString Device::iconName() const { return iconForStatus(true, false); } -QString Device::iconForStatus(bool reachable, bool paired) const +QString Device::iconForStatus(bool reachable, bool trusted) const { Device::DeviceType deviceType = m_deviceType; if (deviceType == Device::Unknown) { deviceType = Device::Phone; //Assume phone if we don't know the type } else if (deviceType == Device::Desktop) { deviceType = Device::Device::Laptop; // We don't have desktop icon yet } - QString status = (reachable? (paired? QStringLiteral("connected") : QStringLiteral("disconnected")) : QStringLiteral("trusted")); + QString status = (reachable? (trusted? QStringLiteral("connected") : QStringLiteral("disconnected")) : QStringLiteral("trusted")); QString type = type2str(deviceType); - return type+"-"+status; + return type+status; } -void Device::setName(const QString &name) +void Device::setName(const QString& name) { if (m_deviceName != name) { m_deviceName = name; Q_EMIT nameChanged(name); } } + +KdeConnectPlugin* Device::plugin(const QString& pluginName) const +{ + return m_plugins[pluginName]; +} + +void Device::setPluginEnabled(const QString& pluginName, bool enabled) +{ + KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); + + const QString enabledKey = pluginName + QStringLiteral("Enabled"); + pluginStates.writeEntry(enabledKey, enabled); + reloadPlugins(); +} + +bool Device::isPluginEnabled(const QString& pluginName) const +{ + const QString enabledKey = pluginName + QStringLiteral("Enabled"); + KConfigGroup pluginStates = KSharedConfig::openConfig(pluginsConfigFile())->group("Plugins"); + + return (pluginStates.hasKey(enabledKey) ? pluginStates.readEntry(enabledKey, false) + : PluginLoader::instance()->getPluginInfo(pluginName).isEnabledByDefault()); +} + +QString Device::encryptionInfo() const +{ + QString result; + QCryptographicHash::Algorithm digestAlgorithm = QCryptographicHash::Algorithm::Sha1; + + QString localSha1 = QString::fromLatin1(KdeConnectConfig::instance()->certificate().digest(digestAlgorithm).toHex()); + for (int i = 2; igetDeviceProperty(id(), QStringLiteral("certificate")).toStdString(); + QSslCertificate remoteCertificate = QSslCertificate(QByteArray(remotePem.c_str(), (int)remotePem.size())); + QString remoteSha1 = QString::fromLatin1(remoteCertificate.digest(digestAlgorithm).toHex()); + for (int i = 2; i < remoteSha1.size(); i += 3) { + remoteSha1.insert(i, ':'); // Improve readability + } + result += i18n("SHA1 fingerprint of remote device certificate is: %1\n", remoteSha1); + + return result; +} + diff --git a/core/device.h b/core/device.h index 03f528cf..dd7eac4b 100644 --- a/core/device.h +++ b/core/device.h @@ -1,160 +1,164 @@ /** * 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 DEVICE_H #define DEVICE_H #include #include #include #include -#include -#include -#include +#include #include "networkpackage.h" +#include "backends/devicelink.h" class DeviceLink; class KdeConnectPlugin; class KDECONNECTCORE_EXPORT Device : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device") Q_PROPERTY(QString type READ type CONSTANT) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString iconName READ iconName CONSTANT) Q_PROPERTY(QString statusIconName READ statusIconName) - Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableStatusChanged) - Q_PROPERTY(bool isPaired READ isPaired NOTIFY pairingChanged) - - enum PairStatus { - NotPaired, - Requested, - RequestedByPeer, - Paired, - }; + Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableChanged) + Q_PROPERTY(bool isTrusted READ isTrusted NOTIFY trustedChanged) + Q_PROPERTY(QStringList supportedPlugins READ supportedPlugins NOTIFY pluginsChanged) + Q_PROPERTY(bool hasPairingRequests READ hasPairingRequests NOTIFY hasPairingRequestsChanged) + +public: enum DeviceType { Unknown, Desktop, Laptop, Phone, Tablet, }; - static DeviceType str2type(QString deviceType); - static QString type2str(DeviceType deviceType); -public: /** * Restores the @p device from the saved configuration * * We already know it but we need to wait for an incoming DeviceLink to communicate */ Device(QObject* parent, const QString& id); /** * Device known via an incoming connection sent to us via a devicelink. * * We know everything but we don't trust it yet */ Device(QObject* parent, const NetworkPackage& np, DeviceLink* dl); - virtual ~Device(); + ~Device() override; QString id() const { return m_deviceId; } QString name() const { return m_deviceName; } QString dbusPath() const { return "/modules/kdeconnect/devices/"+id(); } - QString type() const { return type2str(m_deviceType); }; + QString type() const { return type2str(m_deviceType); } QString iconName() const; QString statusIconName() const; + Q_SCRIPTABLE QString encryptionInfo() const; //Add and remove links void addLink(const NetworkPackage& identityPackage, DeviceLink*); void removeLink(DeviceLink*); - Q_SCRIPTABLE bool isPaired() const { return m_pairStatus==Device::Paired; } - Q_SCRIPTABLE bool pairRequested() const { return m_pairStatus==Device::Requested; } + Q_SCRIPTABLE bool isTrusted() const; Q_SCRIPTABLE QStringList availableLinks() const; bool isReachable() const { return !m_deviceLinks.isEmpty(); } Q_SCRIPTABLE QStringList loadedPlugins() const; Q_SCRIPTABLE bool hasPlugin(const QString& name) const; Q_SCRIPTABLE QString pluginsConfigFile() const; + KdeConnectPlugin* plugin(const QString& pluginName) const; + Q_SCRIPTABLE void setPluginEnabled(const QString& pluginName, bool enabled); + Q_SCRIPTABLE bool isPluginEnabled(const QString& pluginName) const; + + void cleanUnneededLinks(); + + int protocolVersion() { return m_protocolVersion; } + QStringList supportedPlugins() const { return m_supportedPlugins.toList(); } + + QHostAddress getLocalIpAddress() const; + public Q_SLOTS: ///sends a @p np package to the device + ///virtual for testing purposes. virtual bool sendPackage(NetworkPackage& np); //Dbus operations public Q_SLOTS: - Q_SCRIPTABLE void requestPair(); - Q_SCRIPTABLE void unpair(); - Q_SCRIPTABLE void reloadPlugins(); //From kconf - void acceptPairing(); - void rejectPairing(); + Q_SCRIPTABLE void requestPair(); //to all links + Q_SCRIPTABLE void unpair(); //from all links + Q_SCRIPTABLE void reloadPlugins(); //from kconf + + Q_SCRIPTABLE void acceptPairing(); + Q_SCRIPTABLE void rejectPairing(); + Q_SCRIPTABLE bool hasPairingRequests() const; private Q_SLOTS: void privateReceivedPackage(const NetworkPackage& np); void linkDestroyed(QObject* o); - void pairingTimeout(); + void pairStatusChanged(DeviceLink::PairStatus current); + void addPairingRequest(PairingHandler* handler); + void removePairingRequest(PairingHandler* handler); Q_SIGNALS: Q_SCRIPTABLE void pluginsChanged(); - Q_SCRIPTABLE void reachableStatusChanged(); - Q_SCRIPTABLE void pairingChanged(bool paired); - Q_SCRIPTABLE void pairingFailed(const QString& error); + Q_SCRIPTABLE void reachableChanged(bool reachable); + Q_SCRIPTABLE void trustedChanged(bool trusted); + Q_SCRIPTABLE void pairingError(const QString& error); Q_SCRIPTABLE void nameChanged(const QString& name); - QT_DEPRECATED Q_SCRIPTABLE void pairingSuccesful(); - QT_DEPRECATED Q_SCRIPTABLE void unpaired(); + Q_SCRIPTABLE void hasPairingRequestsChanged(bool hasPairingRequests); private: //Methods - void setName(const QString &name); + static DeviceType str2type(const QString& deviceType); + static QString type2str(DeviceType deviceType); + + void setName(const QString& name); QString iconForStatus(bool reachable, bool paired) const; - void unpairInternal(); - void setAsPaired(); - bool sendOwnPublicKey(); private: //Fields (TODO: dPointer!) const QString m_deviceId; QString m_deviceName; DeviceType m_deviceType; - QCA::PublicKey m_publicKey; - PairStatus m_pairStatus; int m_protocolVersion; QVector m_deviceLinks; QHash m_plugins; - QMultiMap m_pluginsByIncomingInterface; - QMultiMap m_pluginsByOutgoingInterface; - - QTimer m_pairingTimeut; - const QSet m_incomingCapabilities; - const QSet m_outgoingCapabilities; + //Capabilities stuff + QMultiMap m_pluginsByIncomingCapability; + QSet m_supportedPlugins; + QSet m_pairRequests; }; Q_DECLARE_METATYPE(Device*) #endif // DEVICE_H diff --git a/core/filetransferjob.cpp b/core/filetransferjob.cpp index f503f6a3..583cb0af 100644 --- a/core/filetransferjob.cpp +++ b/core/filetransferjob.cpp @@ -1,116 +1,137 @@ /* * Copyright 2013 Albert Vaca * Copyright 2015 Aleix Pol i Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "filetransferjob.h" #include "daemon.h" #include #include #include #include #include FileTransferJob::FileTransferJob(const QSharedPointer& origin, qint64 size, const QUrl& destination) : KJob() - , mOrigin(origin) - , mReply(Q_NULLPTR) - , mDeviceName("KDE Connect") //TODO: Actually fetch the device name - , mDestination(destination) - , mSpeedBytes(0) - , mWritten(0) + , m_origin(origin) + , m_reply(Q_NULLPTR) + , m_from(QStringLiteral("KDE Connect")) + , m_destination(destination) + , m_speedBytes(0) + , m_written(0) + , m_size(size) { - Q_ASSERT(mOrigin); - if (mDestination.scheme().isEmpty()) { - qWarning() << "Destination QUrl" << mDestination << "lacks a scheme. Setting its scheme to 'file'."; - mDestination.setScheme("file"); + Q_ASSERT(m_origin); + Q_ASSERT(m_origin->isReadable()); + if (m_destination.scheme().isEmpty()) { + qCWarning(KDECONNECT_CORE) << "Destination QUrl" << m_destination << "lacks a scheme. Setting its scheme to 'file'."; + m_destination.setScheme(QStringLiteral("file")); } - if (size >= 0) { - setTotalAmount(Bytes, size); - } setCapabilities(Killable); - qCDebug(KDECONNECT_CORE) << "FileTransferJob Downloading payload to" << destination; + qCDebug(KDECONNECT_CORE) << "FileTransferJob Downloading payload to" << destination << "size:" << size; } void FileTransferJob::start() { QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); //qCDebug(KDECONNECT_CORE) << "FileTransferJob start"; } void FileTransferJob::doStart() { - description(this, i18n("Receiving file over KDE-Connect"), - QPair(i18nc("File transfer origin", "From"), - mDeviceName) + description(this, i18n("Receiving file over KDE Connect"), + { i18nc("File transfer origin", "From"), m_from } ); - if (mDestination.isLocalFile() && QFile::exists(mDestination.toLocalFile())) { + if (m_destination.isLocalFile() && QFile::exists(m_destination.toLocalFile())) { setError(2); setErrorText(i18n("Filename already present")); emitResult(); + return; } - startTransfer(); + if (m_origin->bytesAvailable()) + startTransfer(); + connect(m_origin.data(), &QIODevice::readyRead, this, &FileTransferJob::startTransfer); } void FileTransferJob::startTransfer() { + // Don't put each ready read + if (m_reply) + return; + setProcessedAmount(Bytes, 0); - mTime = QTime::currentTime(); - description(this, i18n("Receiving file over KDE-Connect"), - QPair(i18nc("File transfer origin", "From"), - QString(mDeviceName)), - QPair(i18nc("File transfer destination", "To"), mDestination.toLocalFile())); - - mReply = Daemon::instance()->networkAccessManager()->put(QNetworkRequest(mDestination), mOrigin.data()); - connect(mReply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64 /*bytesTotal*/) { + description(this, i18n("Receiving file over KDE Connect"), + { i18nc("File transfer origin", "From"), m_from }, + { i18nc("File transfer destination", "To"), m_destination.toLocalFile() }); + + QNetworkRequest req(m_destination); + if (m_size >= 0) { + setTotalAmount(Bytes, m_size); + req.setHeader(QNetworkRequest::ContentLengthHeader, m_size); + } + m_reply = Daemon::instance()->networkAccessManager()->put(req, m_origin.data()); + + connect(m_reply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64 /*bytesTotal*/) { + if (!m_timer.isValid()) + m_timer.start(); setProcessedAmount(Bytes, bytesSent); - emitSpeed(bytesSent/mTime.elapsed()); + + const auto elapsed = m_timer.elapsed(); + if (elapsed > 0) { + emitSpeed((1000 * bytesSent) / elapsed); + } }); - connect(mReply, &QNetworkReply::finished, this, &FileTransferJob::transferFinished); + connect(m_reply, static_cast(&QNetworkReply::error), + this, &FileTransferJob::transferFailed); + connect(m_reply, &QNetworkReply::finished, this, &FileTransferJob::transferFinished); +} + +void FileTransferJob::transferFailed(QNetworkReply::NetworkError error) +{ + qCDebug(KDECONNECT_CORE) << "Couldn't transfer the file successfully" << error << m_reply->errorString(); + setError(error); + setErrorText(i18n("Received incomplete file: %1", m_reply->errorString())); + emitResult(); + + m_reply->close(); } void FileTransferJob::transferFinished() { //TODO: MD5-check the file - if (mReply->error()) { - qCDebug(KDECONNECT_CORE) << "Couldn't transfer the file successfully" << mReply->errorString(); - setError(mReply->error()); - setErrorText(i18n("Received incomplete file: %1", mReply->errorString())); - } else { - qCDebug(KDECONNECT_CORE) << "Finished transfer" << mDestination; - } + qCDebug(KDECONNECT_CORE) << "Finished transfer" << m_destination; emitResult(); } bool FileTransferJob::doKill() { - if (mReply) { - mReply->close(); + if (m_reply) { + m_reply->close(); } - if (mOrigin) { - mOrigin->close(); + if (m_origin) { + m_origin->close(); } return true; } diff --git a/core/filetransferjob.h b/core/filetransferjob.h index 859a3032..9979577a 100644 --- a/core/filetransferjob.h +++ b/core/filetransferjob.h @@ -1,77 +1,78 @@ /* * Copyright 2013 Albert Vaca * Copyright 2015 Aleix Pol i 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 . */ #ifndef FILETRANSFERJOB_H #define FILETRANSFERJOB_H +#include + +#include #include -#include -#include #include - -#include #include -#include #include -#include + +#include "kdeconnectcore_export.h" /** * @short It will stream a device into a url destination * * Given a QIODevice, the file transfer job will use the system's QNetworkAccessManager * for putting the stream into the requested location. */ -class FileTransferJob +class KDECONNECTCORE_EXPORT FileTransferJob : public KJob { Q_OBJECT public: /** * @p origin specifies the data to read from. * @p size specifies the expected size of the stream we're reading. * @p destination specifies where these contents should be stored */ - FileTransferJob(const QSharedPointer& origin, qint64 size, const QUrl &destination); - virtual void start() Q_DECL_OVERRIDE; - QUrl destination() const { return mDestination; } - void setDeviceName(const QString &deviceName) { mDeviceName = deviceName; } + FileTransferJob(const QSharedPointer& origin, qint64 size, const QUrl& destination); + void start() override; + QUrl destination() const { return m_destination; } + void setOriginName(const QString& from) { m_from = from; } private Q_SLOTS: void doStart(); protected: - bool doKill() Q_DECL_OVERRIDE; + bool doKill() override; private: void startTransfer(); + void transferFailed(QNetworkReply::NetworkError error); void transferFinished(); - QSharedPointer mOrigin; - QNetworkReply* mReply; - QString mDeviceName; - QUrl mDestination; - QTime mTime; - qulonglong mSpeedBytes; - qint64 mWritten; + QSharedPointer m_origin; + QNetworkReply* m_reply; + QString m_from; + QUrl m_destination; + QElapsedTimer m_timer; + qulonglong m_speedBytes; + qint64 m_written; + qint64 m_size; }; #endif diff --git a/core/kdeconnectconfig.cpp b/core/kdeconnectconfig.cpp index 919505c8..e8e94581 100644 --- a/core/kdeconnectconfig.cpp +++ b/core/kdeconnectconfig.cpp @@ -1,227 +1,273 @@ /** * Copyright 2015 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 "kdeconnectconfig.h" #include -#include #include #include #include #include -#include #include #include #include #include #include +#include +#include +#include #include "core_debug.h" #include "dbushelper.h" #include "daemon.h" struct KdeConnectConfigPrivate { // The Initializer object sets things up, and also does cleanup when it goes out of scope - // Note it's not being used anywhere. That's inteneded - QCA::Initializer mQcaInitializer; + // Note it's not being used anywhere. That's intended + QCA::Initializer m_qcaInitializer; - QCA::PrivateKey privateKey; + QCA::PrivateKey m_privateKey; + QSslCertificate m_certificate; // Use QSslCertificate instead of QCA::Certificate due to compatibility with QSslSocket - QSettings* config; + QSettings* m_config; + QSettings* m_trustedDevices; }; KdeConnectConfig* KdeConnectConfig::instance() { static KdeConnectConfig* kcc = new KdeConnectConfig(); return kcc; } KdeConnectConfig::KdeConnectConfig() : d(new KdeConnectConfigPrivate) { //qCDebug(KDECONNECT_CORE) << "QCA supported capabilities:" << QCA::supportedFeatures().join(","); if(!QCA::isSupported("rsa")) { + qCritical() << "Could not find support for RSA in your QCA installation"; Daemon::instance()->reportError( i18n("KDE Connect failed to start"), i18n("Could not find support for RSA in your QCA installation. If your " "distribution provides separate packages for QCA-ossl and QCA-gnupg, " "make sure you have them installed and try again.")); return; } //Make sure base directory exists QDir().mkpath(baseConfigDir().path()); //.config/kdeconnect/config - d->config = new QSettings(baseConfigDir().absoluteFilePath("config"), QSettings::IniFormat); - - //Register my own id if not there yet - d->config->beginGroup("myself"); - if (!d->config->contains("id")) { - QString uuid = QUuid::createUuid().toString(); - DbusHelper::filterNonExportableCharacters(uuid); - d->config->setValue("id", uuid); - d->config->sync(); - qCDebug(KDECONNECT_CORE) << "My id:" << uuid; - } - d->config->endGroup(); + d->m_config = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); + d->m_trustedDevices = new QSettings(baseConfigDir().absoluteFilePath(QStringLiteral("trusted_devices")), QSettings::IniFormat); const QFile::Permissions strict = QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser; QString keyPath = privateKeyPath(); QFile privKey(keyPath); if (privKey.exists() && privKey.open(QIODevice::ReadOnly)) { - d->privateKey = QCA::PrivateKey::fromPEM(privKey.readAll()); + d->m_privateKey = QCA::PrivateKey::fromPEM(privKey.readAll()); } else { - d->privateKey = QCA::KeyGenerator().createRSA(2048); + d->m_privateKey = QCA::KeyGenerator().createRSA(2048); if (!privKey.open(QIODevice::ReadWrite | QIODevice::Truncate)) { - Daemon::instance()->reportError(QLatin1String("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); + Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store private key file: %1", keyPath)); } else { privKey.setPermissions(strict); - privKey.write(d->privateKey.toPEM().toLatin1()); + privKey.write(d->m_privateKey.toPEM().toLatin1()); + } + } + + QString certPath = certificatePath(); + QFile cert(certPath); + if (cert.exists() && cert.open(QIODevice::ReadOnly)) { + + d->m_certificate = QSslCertificate::fromPath(certPath).at(0); + + } else { + + // No certificate yet. Probably first run. Let's generate one! + + QString uuid = QUuid::createUuid().toString(); + DbusHelper::filterNonExportableCharacters(uuid); + qCDebug(KDECONNECT_CORE) << "My id:" << uuid; + + // FIXME: We only use QCA here to generate the cert and key, would be nice to get rid of it completely. + // The same thing we are doing with QCA could be done invoking openssl (altought it's potentially less portable): + // openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout privateKey.pem -days 3650 -out certificate.pem -subj "/O=KDE/OU=KDE Connect/CN=_e6e29ad4_2b31_4b6d_8f7a_9872dbaa9095_" + + QCA::CertificateOptions certificateOptions = QCA::CertificateOptions(); + QDateTime startTime = QDateTime::currentDateTime().addYears(-1); + QDateTime endTime = startTime.addYears(10); + QCA::CertificateInfo certificateInfo; + certificateInfo.insert(QCA::CommonName, uuid); + certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); + certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); + certificateOptions.setInfo(certificateInfo); + certificateOptions.setFormat(QCA::PKCS10); + certificateOptions.setSerialNumber(QCA::BigInteger(10)); + certificateOptions.setValidityPeriod(startTime, endTime); + + d->m_certificate = QSslCertificate(QCA::Certificate(certificateOptions, d->m_privateKey).toPEM().toLatin1()); + + if (!cert.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + Daemon::instance()->reportError(QStringLiteral("KDE Connect"), i18n("Could not store certificate file: %1", certPath)); + } else { + cert.setPermissions(strict); + cert.write(d->m_certificate.toPem()); } } //Extra security check if (QFile::permissions(keyPath) != strict) { - qCDebug(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath; + qCWarning(KDECONNECT_CORE) << "Warning: KDE Connect private key file has too open permissions " << keyPath; } } QString KdeConnectConfig::name() { - QString defaultName = qgetenv("USER") + "@" + QHostInfo::localHostName(); - d->config->beginGroup("myself"); - QString name = d->config->value("name", defaultName).toString(); - d->config->endGroup(); + QString defaultName = qgetenv("USER") + '@' + QHostInfo::localHostName(); + QString name = d->m_config->value(QStringLiteral("name"), defaultName).toString(); return name; } -void KdeConnectConfig::setName(QString name) +void KdeConnectConfig::setName(const QString& name) { - d->config->beginGroup("myself"); - d->config->setValue("name", name); - d->config->endGroup(); - d->config->sync(); + d->m_config->setValue(QStringLiteral("name"), name); + d->m_config->sync(); } QString KdeConnectConfig::deviceType() { - return "desktop"; // TODO + return QStringLiteral("desktop"); // TODO } QString KdeConnectConfig::deviceId() { - d->config->beginGroup("myself"); - QString id = d->config->value("id", "").toString(); - d->config->endGroup(); - return id; + return d->m_certificate.subjectInfo( QSslCertificate::CommonName ).constFirst(); } QString KdeConnectConfig::privateKeyPath() { - return baseConfigDir().absoluteFilePath("privateKey.pem"); + return baseConfigDir().absoluteFilePath(QStringLiteral("privateKey.pem")); } QCA::PrivateKey KdeConnectConfig::privateKey() { - return d->privateKey; + return d->m_privateKey; } QCA::PublicKey KdeConnectConfig::publicKey() { - return d->privateKey.toPublicKey(); + return d->m_privateKey.toPublicKey(); +} + +QString KdeConnectConfig::certificatePath() +{ + return baseConfigDir().absoluteFilePath(QStringLiteral("certificate.pem")); +} + +QSslCertificate KdeConnectConfig::certificate() +{ + return d->m_certificate; } QDir KdeConnectConfig::baseConfigDir() { QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); - QString kdeconnectConfigPath = QDir(configPath).absoluteFilePath("kdeconnect"); + QString kdeconnectConfigPath = QDir(configPath).absoluteFilePath(QStringLiteral("kdeconnect")); return QDir(kdeconnectConfigPath); } QStringList KdeConnectConfig::trustedDevices() { - d->config->beginGroup("trustedDevices"); - const QStringList& list = d->config->childGroups(); - d->config->endGroup(); + const QStringList& list = d->m_trustedDevices->childGroups(); return list; } -void KdeConnectConfig::addTrustedDevice(QString id, QString name, QString type, QString publicKey) + +void KdeConnectConfig::addTrustedDevice(const QString& id, const QString& name, const QString& type) { - d->config->beginGroup("trustedDevices"); - d->config->beginGroup(id); - d->config->setValue("name", name); - d->config->setValue("type", type); - d->config->setValue("publicKey", publicKey); - d->config->endGroup(); - d->config->endGroup(); - d->config->sync(); + d->m_trustedDevices->beginGroup(id); + d->m_trustedDevices->setValue(QStringLiteral("name"), name); + d->m_trustedDevices->setValue(QStringLiteral("type"), type); + d->m_trustedDevices->endGroup(); + d->m_trustedDevices->sync(); QDir().mkpath(deviceConfigDir(id).path()); } -KdeConnectConfig::DeviceInfo KdeConnectConfig::getTrustedDevice(QString id) +KdeConnectConfig::DeviceInfo KdeConnectConfig::getTrustedDevice(const QString& id) { - d->config->beginGroup("trustedDevices"); - d->config->beginGroup(id); + d->m_trustedDevices->beginGroup(id); KdeConnectConfig::DeviceInfo info; - info.deviceName = d->config->value("name", QLatin1String("unnamed")).toString(); - info.deviceType = d->config->value("type", QLatin1String("unknown")).toString(); - info.publicKey = d->config->value("publicKey", QString()).toString(); + info.deviceName = d->m_trustedDevices->value(QStringLiteral("name"), QLatin1String("unnamed")).toString(); + info.deviceType = d->m_trustedDevices->value(QStringLiteral("type"), QLatin1String("unknown")).toString(); - d->config->endGroup(); - d->config->endGroup(); + d->m_trustedDevices->endGroup(); return info; } -void KdeConnectConfig::removeTrustedDevice(QString deviceId) +void KdeConnectConfig::removeTrustedDevice(const QString& deviceId) { - d->config->beginGroup("trustedDevices"); - d->config->beginGroup(deviceId); - d->config->remove(QString()); - d->config->endGroup(); - d->config->endGroup(); - d->config->sync(); + d->m_trustedDevices->remove(deviceId); + d->m_trustedDevices->sync(); //We do not remove the config files. } -QDir KdeConnectConfig::deviceConfigDir(QString deviceId) +// Utility functions to set and get a value +void KdeConnectConfig::setDeviceProperty(const QString& deviceId, const QString& key, const QString& value) +{ + d->m_trustedDevices->beginGroup(deviceId); + d->m_trustedDevices->setValue(key, value); + d->m_trustedDevices->endGroup(); + d->m_trustedDevices->sync(); +} + +QString KdeConnectConfig::getDeviceProperty(const QString& deviceId, const QString& key, const QString& defaultValue) +{ + QString value; + d->m_trustedDevices->beginGroup(deviceId); + value = d->m_trustedDevices->value(key, defaultValue).toString(); + d->m_trustedDevices->endGroup(); + return value; +} + + +QDir KdeConnectConfig::deviceConfigDir(const QString& deviceId) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); return QDir(deviceConfigPath); } -QDir KdeConnectConfig::pluginConfigDir(QString deviceId, QString pluginName) +QDir KdeConnectConfig::pluginConfigDir(const QString& deviceId, const QString& pluginName) { QString deviceConfigPath = baseConfigDir().absoluteFilePath(deviceId); QString pluginConfigDir = QDir(deviceConfigPath).absoluteFilePath(pluginName); return QDir(pluginConfigDir); } + diff --git a/core/kdeconnectconfig.h b/core/kdeconnectconfig.h index af6c6dfc..35609f7d 100644 --- a/core/kdeconnectconfig.h +++ b/core/kdeconnectconfig.h @@ -1,78 +1,88 @@ /** * Copyright 2015 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 KDECONNECTCONFIG_H #define KDECONNECTCONFIG_H -#include #include #include "kdeconnectcore_export.h" +class QSslCertificate; +namespace QCA { + class PrivateKey; + class PublicKey; +} + class KDECONNECTCORE_EXPORT KdeConnectConfig { public: struct DeviceInfo { QString deviceName; QString deviceType; - QString publicKey; }; static KdeConnectConfig* instance(); /* * Our own info */ QString deviceId(); QString name(); QString deviceType(); QString privateKeyPath(); QCA::PrivateKey privateKey(); QCA::PublicKey publicKey(); - void setName(QString name); + QString certificatePath(); + QSslCertificate certificate(); + + void setName(const QString& name); /* * Trusted devices */ QStringList trustedDevices(); //list of ids - void removeTrustedDevice(QString id); - void addTrustedDevice(QString id, QString name, QString type, QString publicKey); - KdeConnectConfig::DeviceInfo getTrustedDevice(QString id); + void removeTrustedDevice(const QString& id); + void addTrustedDevice(const QString& id, const QString& name, const QString& type); + KdeConnectConfig::DeviceInfo getTrustedDevice(const QString& id); + + void setDeviceProperty(const QString& deviceId, const QString& name, const QString& value); + QString getDeviceProperty(const QString& deviceId, const QString& name, const QString& defaultValue = QString()); /* * Paths for config files, there is no guarantee the directories already exist */ QDir baseConfigDir(); - QDir deviceConfigDir(QString deviceId); - QDir pluginConfigDir(QString deviceId, QString pluginName); //Used by KdeConnectPluginConfig + QDir deviceConfigDir(const QString& deviceId); + QDir pluginConfigDir(const QString& deviceId, const QString& pluginName); //Used by KdeConnectPluginConfig private: KdeConnectConfig(); private: - QScopedPointer d; + struct KdeConnectConfigPrivate* d; }; #endif diff --git a/core/kdeconnectplugin.cpp b/core/kdeconnectplugin.cpp index ac83e65d..27adcdeb 100644 --- a/core/kdeconnectplugin.cpp +++ b/core/kdeconnectplugin.cpp @@ -1,77 +1,82 @@ /** * 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.h" -#include +#include "core_debug.h" struct KdeConnectPluginPrivate { - Device* mDevice; - QString mPluginName; - QSet mOutgoingTypes; - KdeConnectPluginConfig* mConfig; + Device* m_device; + QString m_pluginName; + QSet m_outgoingCapabilties; + KdeConnectPluginConfig* m_config; }; KdeConnectPlugin::KdeConnectPlugin(QObject* parent, const QVariantList& args) : QObject(parent) , d(new KdeConnectPluginPrivate) { - d->mDevice = qvariant_cast< Device* >(args.at(0)); - d->mPluginName = args.at(1).toString(); - d->mOutgoingTypes = args.at(2).toStringList().toSet(); - d->mConfig = 0; + d->m_device = qvariant_cast< Device* >(args.at(0)); + d->m_pluginName = args.at(1).toString(); + d->m_outgoingCapabilties = args.at(2).toStringList().toSet(); + d->m_config = nullptr; } KdeConnectPluginConfig* KdeConnectPlugin::config() const { //Create on demand, because not every plugin will use it - if (!d->mConfig) { - d->mConfig = new KdeConnectPluginConfig(d->mDevice->id(), d->mPluginName); + if (!d->m_config) { + d->m_config = new KdeConnectPluginConfig(d->m_device->id(), d->m_pluginName); } - return d->mConfig; + return d->m_config; } KdeConnectPlugin::~KdeConnectPlugin() { - if (d->mConfig) { - delete d->mConfig; + if (d->m_config) { + delete d->m_config; } } const Device* KdeConnectPlugin::device() { - return d->mDevice; + return d->m_device; } Device const* KdeConnectPlugin::device() const { - return d->mDevice; + return d->m_device; } bool KdeConnectPlugin::sendPackage(NetworkPackage& np) const { - if(!d->mOutgoingTypes.contains(np.type())) { - qWarning() << metaObject()->className() << "tried to send an unsupported package type" << np.type() << ". Supported:" << d->mOutgoingTypes; + if(!d->m_outgoingCapabilties.contains(np.type())) { + qCWarning(KDECONNECT_CORE) << metaObject()->className() << "tried to send an unsupported package type" << np.type() << ". Supported:" << d->m_outgoingCapabilties; return false; } -// qWarning() << metaObject()->className() << "sends" << np.type() << ". Supported:" << d->mOutgoingTypes; - return d->mDevice->sendPackage(np); +// qCWarning(KDECONNECT_CORE) << metaObject()->className() << "sends" << np.type() << ". Supported:" << d->mOutgoingTypes; + return d->m_device->sendPackage(np); +} + +QString KdeConnectPlugin::dbusPath() const +{ + return {}; } diff --git a/core/kdeconnectplugin.h b/core/kdeconnectplugin.h index 297bf0c8..0216db81 100644 --- a/core/kdeconnectplugin.h +++ b/core/kdeconnectplugin.h @@ -1,68 +1,70 @@ /** * 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 KDECONNECTPLUGIN_H #define KDECONNECTPLUGIN_H #include #include #include "kdeconnectcore_export.h" #include "kdeconnectpluginconfig.h" #include "networkpackage.h" #include "device.h" struct KdeConnectPluginPrivate; class KDECONNECTCORE_EXPORT KdeConnectPlugin : public QObject { Q_OBJECT public: KdeConnectPlugin(QObject* parent, const QVariantList& args); - virtual ~KdeConnectPlugin(); + ~KdeConnectPlugin() override; const Device* device(); Device const* device() const; bool sendPackage(NetworkPackage& np) const; KdeConnectPluginConfig* config() const; + virtual QString dbusPath() const; + public Q_SLOTS: /** * Returns true if it has handled the package in some way * device.sendPackage can be used to send an answer back to the device */ virtual bool receivePackage(const NetworkPackage& np) = 0; /** * This method will be called when a device is connected to this computer. * The plugin could be loaded already, but there is no guarantee we will be able to reach the device until this is called. */ virtual void connected() = 0; private: QScopedPointer d; }; #endif diff --git a/core/kdeconnectpluginconfig.cpp b/core/kdeconnectpluginconfig.cpp index cb57cb25..9e304597 100644 --- a/core/kdeconnectpluginconfig.cpp +++ b/core/kdeconnectpluginconfig.cpp @@ -1,63 +1,100 @@ /** * Copyright 2015 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 "kdeconnectpluginconfig.h" #include #include +#include +#include #include "kdeconnectconfig.h" struct KdeConnectPluginConfigPrivate { - QDir mConfigDir; - QSettings* mConfig; + QDir m_configDir; + QSettings* m_config; + QDBusMessage m_signal; }; KdeConnectPluginConfig::KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName) : d(new KdeConnectPluginConfigPrivate()) { - d->mConfigDir = KdeConnectConfig::instance()->pluginConfigDir(deviceId, pluginName); - QDir().mkpath(d->mConfigDir.path()); + d->m_configDir = KdeConnectConfig::instance()->pluginConfigDir(deviceId, pluginName); + QDir().mkpath(d->m_configDir.path()); - d->mConfig = new QSettings(d->mConfigDir.absoluteFilePath("config"), QSettings::IniFormat); + d->m_config = new QSettings(d->m_configDir.absoluteFilePath(QStringLiteral("config")), QSettings::IniFormat); + + d->m_signal = QDBusMessage::createSignal("/kdeconnect/"+deviceId+"/"+pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged")); + QDBusConnection::sessionBus().connect(QLatin1String(""), "/kdeconnect/"+deviceId+"/"+pluginName, QStringLiteral("org.kde.kdeconnect.config"), QStringLiteral("configChanged"), this, SLOT(slotConfigChanged())); } KdeConnectPluginConfig::~KdeConnectPluginConfig() { - delete d->mConfig; + delete d->m_config; } -QDir KdeConnectPluginConfig::privateDirectory() +QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& defaultValue) { - return d->mConfigDir; + d->m_config->sync(); + return d->m_config->value(key, defaultValue); } -QVariant KdeConnectPluginConfig::get(const QString& key, const QVariant& defaultValue) +QVariantList KdeConnectPluginConfig::getList(const QString& key, + const QVariantList& defaultValue) { - d->mConfig->sync(); - return d->mConfig->value(key, defaultValue); + QVariantList list; + d->m_config->sync(); // note: need sync() to get recent changes signalled from other process + int size = d->m_config->beginReadArray(key); + if (size < 1) { + d->m_config->endArray(); + return defaultValue; + } + for (int i = 0; i < size; ++i) { + d->m_config->setArrayIndex(i); + list << d->m_config->value(QStringLiteral("value")); + } + d->m_config->endArray(); + return list; } void KdeConnectPluginConfig::set(const QString& key, const QVariant& value) { - d->mConfig->setValue(key, value); - d->mConfig->sync(); + d->m_config->setValue(key, value); + d->m_config->sync(); + QDBusConnection::sessionBus().send(d->m_signal); +} + +void KdeConnectPluginConfig::setList(const QString& key, const QVariantList& list) +{ + d->m_config->beginWriteArray(key); + for (int i = 0; i < list.size(); ++i) { + d->m_config->setArrayIndex(i); + d->m_config->setValue(QStringLiteral("value"), list.at(i)); + } + d->m_config->endArray(); + d->m_config->sync(); + QDBusConnection::sessionBus().send(d->m_signal); +} + +void KdeConnectPluginConfig::slotConfigChanged() +{ + Q_EMIT configChanged(); } diff --git a/core/kdeconnectpluginconfig.h b/core/kdeconnectpluginconfig.h index 96195e4b..a13d0041 100644 --- a/core/kdeconnectpluginconfig.h +++ b/core/kdeconnectpluginconfig.h @@ -1,68 +1,77 @@ /** * Copyright 2015 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 #include #include #include #include -#include "default_args.h" #include "kdeconnectcore_export.h" struct KdeConnectPluginConfigPrivate; -class KDECONNECTCORE_EXPORT KdeConnectPluginConfig +class KDECONNECTCORE_EXPORT KdeConnectPluginConfig : public QObject { + Q_OBJECT + public: KdeConnectPluginConfig(const QString& deviceId, const QString& pluginName); - ~KdeConnectPluginConfig(); + ~KdeConnectPluginConfig() override; /** - * A directory to store stuff for this device and plugin. It's private in the sense - * of not-shared with other plugins or devices, not from a security point of view. + * Store a key-value pair in this config object */ - QDir privateDirectory(); + void set(const QString& key, const QVariant& value); /** - * Store a key-value pair in this config object + * Store a list of values in this config object under the array name + * specified in key. */ - void set(const QString& key, const QVariant& value); + void setList(const QString& key, const QVariantList& list); /** * Read a key-value pair from this config object */ QVariant get(const QString& key, const QVariant& defaultValue); /** * Convenience method that will convert the QVariant to whatever type for you */ - template T get(const QString& key, const T& defaultValue = default_arg::get()) { + template T get(const QString& key, const T& defaultValue = {}) { return get(key, QVariant(defaultValue)).template value(); //Important note: Awesome template syntax is awesome } + QVariantList getList(const QString& key, const QVariantList& defaultValue = {}); + +private Q_SLOTS: + void slotConfigChanged(); + +Q_SIGNALS: + void configChanged(); private: QScopedPointer d; }; #endif diff --git a/core/networkpackage.cpp b/core/networkpackage.cpp index 64cfab76..54ca7c77 100644 --- a/core/networkpackage.cpp +++ b/core/networkpackage.cpp @@ -1,221 +1,172 @@ /** * 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 "networkpackage.h" #include "core_debug.h" #include #include #include #include #include #include -#include #include #include "dbushelper.h" #include "filetransferjob.h" #include "pluginloader.h" #include "kdeconnectconfig.h" -const QCA::EncryptionAlgorithm NetworkPackage::EncryptionAlgorithm = QCA::EME_PKCS1v15; -const int NetworkPackage::ProtocolVersion = 5; +QDebug operator<<(QDebug s, const NetworkPackage& pkg) +{ + s.nospace() << "NetworkPackage(" << pkg.type() << ':' << pkg.body(); + if (pkg.hasPayload()) { + s.nospace() << ":withpayload"; + } + s.nospace() << ')'; + return s.space(); +} -NetworkPackage::NetworkPackage(const QString& type) +const int NetworkPackage::s_protocolVersion = 7; + +NetworkPackage::NetworkPackage(const QString& type, const QVariantMap& body) + : m_id(QString::number(QDateTime::currentMSecsSinceEpoch())) + , m_type(type) + , m_body(body) + , m_payload() + , m_payloadSize(0) { - mId = QString::number(QDateTime::currentMSecsSinceEpoch()); - mType = type; - mBody = QVariantMap(); - mPayload = QSharedPointer(); - mPayloadSize = 0; } void NetworkPackage::createIdentityPackage(NetworkPackage* np) { KdeConnectConfig* config = KdeConnectConfig::instance(); - const QString id = config->deviceId(); - np->mId = QString::number(QDateTime::currentMSecsSinceEpoch()); - np->mType = PACKAGE_TYPE_IDENTITY; - np->mPayload = QSharedPointer(); - np->mPayloadSize = 0; - np->set("deviceId", id); - np->set("deviceName", config->name()); - np->set("deviceType", config->deviceType()); - np->set("protocolVersion", NetworkPackage::ProtocolVersion); - np->set("SupportedIncomingInterfaces", PluginLoader::instance()->incomingInterfaces().join(",")); - np->set("SupportedOutgoingInterfaces", PluginLoader::instance()->outgoingInterfaces().join(",")); + np->m_id = QString::number(QDateTime::currentMSecsSinceEpoch()); + np->m_type = PACKAGE_TYPE_IDENTITY; + np->m_payload = QSharedPointer(); + np->m_payloadSize = 0; + np->set(QStringLiteral("deviceId"), config->deviceId()); + np->set(QStringLiteral("deviceName"), config->name()); + np->set(QStringLiteral("deviceType"), config->deviceType()); + np->set(QStringLiteral("protocolVersion"), NetworkPackage::s_protocolVersion); + np->set(QStringLiteral("incomingCapabilities"), PluginLoader::instance()->incomingCapabilities()); + np->set(QStringLiteral("outgoingCapabilities"), PluginLoader::instance()->outgoingCapabilities()); //qCDebug(KDECONNECT_CORE) << "createIdentityPackage" << np->serialize(); } -QVariantMap qobject2qvariant(const QObject* object) +template +QVariantMap qobject2qvariant(const T* object) { QVariantMap map; - auto metaObject = object->metaObject(); - for(int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); ++i) { - const char *name = metaObject->property(i).name(); - map.insert(QString::fromLatin1(name), object->property(name)); + auto metaObject = T::staticMetaObject; + for(int i = metaObject.propertyOffset(); i < metaObject.propertyCount(); ++i) { + QMetaProperty prop = metaObject.property(i); + map.insert(QString::fromLatin1(prop.name()), prop.readOnGadget(object)); } return map; } QByteArray NetworkPackage::serialize() const { //Object -> QVariant //QVariantMap variant; //variant["id"] = mId; //variant["type"] = mType; //variant["body"] = mBody; QVariantMap variant = qobject2qvariant(this); if (hasPayload()) { //qCDebug(KDECONNECT_CORE) << "Serializing payloadTransferInfo"; - variant["payloadSize"] = payloadSize(); - variant["payloadTransferInfo"] = mPayloadTransferInfo; + variant[QStringLiteral("payloadSize")] = payloadSize(); + variant[QStringLiteral("payloadTransferInfo")] = m_payloadTransferInfo; } //QVariant -> json auto jsonDocument = QJsonDocument::fromVariant(variant); QByteArray json = jsonDocument.toJson(QJsonDocument::Compact); if (json.isEmpty()) { qCDebug(KDECONNECT_CORE) << "Serialization error:"; } else { - if (!isEncrypted()) { + /*if (!isEncrypted()) { //qCDebug(KDECONNECT_CORE) << "Serialized package:" << json; - } + }*/ json.append('\n'); } return json; } -void qvariant2qobject(const QVariantMap& variant, QObject* object) +template +void qvariant2qobject(const QVariantMap& variant, T* object) { for ( QVariantMap::const_iterator iter = variant.begin(); iter != variant.end(); ++iter ) { - const int propertyIndex = object->metaObject()->indexOfProperty(iter.key().toLatin1()); + const int propertyIndex = T::staticMetaObject.indexOfProperty(iter.key().toLatin1()); if (propertyIndex < 0) { qCWarning(KDECONNECT_CORE) << "missing property" << object << iter.key(); continue; } - QMetaProperty property = object->metaObject()->property(propertyIndex); - bool ret = property.write(object, *iter); + QMetaProperty property = T::staticMetaObject.property(propertyIndex); + bool ret = property.writeOnGadget(object, *iter); if (!ret) { qCWarning(KDECONNECT_CORE) << "couldn't set" << object << "->" << property.name() << '=' << *iter; } } } bool NetworkPackage::unserialize(const QByteArray& a, NetworkPackage* np) { //Json -> QVariant QJsonParseError parseError; auto parser = QJsonDocument::fromJson(a, &parseError); if (parser.isNull()) { qCDebug(KDECONNECT_CORE) << "Unserialization error:" << parseError.errorString(); return false; } auto variant = parser.toVariant().toMap(); qvariant2qobject(variant, np); - if (!np->isEncrypted()) { - //qCDebug(KDECONNECT_CORE) << "Unserialized: " << a; + np->m_payloadSize = variant[QStringLiteral("payloadSize")].toInt(); //Will return 0 if was not present, which is ok + if (np->m_payloadSize == -1) { + np->m_payloadSize = np->get(QStringLiteral("size"), -1); } - - np->mPayloadSize = variant["payloadSize"].toInt(); //Will return 0 if was not present, which is ok - if (np->mPayloadSize == -1) { - np->mPayloadSize = np->get("size", -1); - } - np->mPayloadTransferInfo = variant["payloadTransferInfo"].toMap(); //Will return an empty qvariantmap if was not present, which is ok + np->m_payloadTransferInfo = variant[QStringLiteral("payloadTransferInfo")].toMap(); //Will return an empty qvariantmap if was not present, which is ok //Ids containing characters that are not allowed as dbus paths would make app crash - if (np->mBody.contains("deviceId")) + if (np->m_body.contains(QStringLiteral("deviceId"))) { - QString deviceId = np->get("deviceId"); + QString deviceId = np->get(QStringLiteral("deviceId")); DbusHelper::filterNonExportableCharacters(deviceId); - np->set("deviceId", deviceId); - } - - return true; - -} - -void NetworkPackage::encrypt(QCA::PublicKey& key) -{ - - QByteArray serialized = serialize(); - - int chunkSize = key.maximumEncryptSize(NetworkPackage::EncryptionAlgorithm); - - QStringList chunks; - while (!serialized.isEmpty()) { - const QByteArray chunk = serialized.left(chunkSize); - serialized = serialized.mid(chunkSize); - const QByteArray encryptedChunk = key.encrypt(chunk, NetworkPackage::EncryptionAlgorithm).toByteArray(); - chunks.append( encryptedChunk.toBase64() ); - } - - //qCDebug(KDECONNECT_CORE) << chunks.size() << "chunks"; - - mId = QString::number(QDateTime::currentMSecsSinceEpoch()); - mType = PACKAGE_TYPE_ENCRYPTED; - mBody = QVariantMap(); - mBody["data"] = chunks; - -} - -bool NetworkPackage::decrypt(QCA::PrivateKey& key, NetworkPackage* out) const -{ - - const QStringList chunks = mBody["data"].toStringList(); - - QByteArray decryptedJson; - Q_FOREACH(const QString& chunk, chunks) { - const QByteArray encryptedChunk = QByteArray::fromBase64(chunk.toLatin1()); - QCA::SecureArray decryptedChunk; - bool success = key.decrypt(encryptedChunk, &decryptedChunk, NetworkPackage::EncryptionAlgorithm); - if (!success) { - return false; - } - decryptedJson.append(decryptedChunk.toByteArray()); - } - - bool success = unserialize(decryptedJson, out); - - if (!success) { - return false; - } - - if (hasPayload()) { - out->mPayload = mPayload; + np->set(QStringLiteral("deviceId"), deviceId); } return true; } -FileTransferJob* NetworkPackage::createPayloadTransferJob(const QUrl &destination) const +FileTransferJob* NetworkPackage::createPayloadTransferJob(const QUrl& destination) const { return new FileTransferJob(payload(), payloadSize(), destination); } diff --git a/core/networkpackage.h b/core/networkpackage.h index 6a0bf9c9..0afb81ae 100644 --- a/core/networkpackage.h +++ b/core/networkpackage.h @@ -1,106 +1,101 @@ /** * 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 NETWORKPACKAGE_H #define NETWORKPACKAGE_H #include "networkpackagetypes.h" #include #include #include -#include #include -#include +//#include #include #include #include "kdeconnectcore_export.h" -#include "default_args.h" class FileTransferJob; -// TODO: investigate if we can turn into Q_GADGET, drop QObject -class KDECONNECTCORE_EXPORT NetworkPackage : public QObject +class KDECONNECTCORE_EXPORT NetworkPackage { - Q_OBJECT + Q_GADGET Q_PROPERTY( QString id READ id WRITE setId ) Q_PROPERTY( QString type READ type WRITE setType ) Q_PROPERTY( QVariantMap body READ body WRITE setBody ) Q_PROPERTY( QVariantMap payloadTransferInfo READ payloadTransferInfo WRITE setPayloadTransferInfo ) Q_PROPERTY( qint64 payloadSize READ payloadSize WRITE setPayloadSize ) public: - const static QCA::EncryptionAlgorithm EncryptionAlgorithm; - const static int ProtocolVersion; + //const static QCA::EncryptionAlgorithm EncryptionAlgorithm; + const static int s_protocolVersion; - NetworkPackage(const QString& type); + explicit NetworkPackage(const QString& type, const QVariantMap& body = {}); static void createIdentityPackage(NetworkPackage*); QByteArray serialize() const; static bool unserialize(const QByteArray& json, NetworkPackage* out); - void encrypt(QCA::PublicKey& key); - bool decrypt(QCA::PrivateKey& key, NetworkPackage* out) const; - bool isEncrypted() const { return mType == PACKAGE_TYPE_ENCRYPTED; } - - const QString& id() const { return mId; } - const QString& type() const { return mType; } - QVariantMap& body() { return mBody; } - const QVariantMap& body() const { return mBody; } + const QString& id() const { return m_id; } + const QString& type() const { return m_type; } + QVariantMap& body() { return m_body; } + const QVariantMap& body() const { return m_body; } //Get and set info from body. Note that id and type can not be accessed through these. - template T get(const QString& key, const T& defaultValue = default_arg::get()) const { - return mBody.value(key,defaultValue).template value(); //Important note: Awesome template syntax is awesome + template T get(const QString& key, const T& defaultValue = {}) const { + return m_body.value(key,defaultValue).template value(); //Important note: Awesome template syntax is awesome } - template void set(const QString& key, const T& value) { mBody[key] = QVariant(value); } - bool has(const QString& key) const { return mBody.contains(key); } + template void set(const QString& key, const T& value) { m_body[key] = QVariant(value); } + bool has(const QString& key) const { return m_body.contains(key); } - QSharedPointer payload() const { return mPayload; } - void setPayload(const QSharedPointer& device, qint64 payloadSize) { mPayload = device; mPayloadSize = payloadSize; Q_ASSERT(mPayloadSize >= -1); } - bool hasPayload() const { return (mPayloadSize != 0); } - qint64 payloadSize() const { return mPayloadSize; } //-1 means it is an endless stream - FileTransferJob* createPayloadTransferJob(const QUrl &destination) const; + QSharedPointer payload() const { return m_payload; } + void setPayload(const QSharedPointer& device, qint64 payloadSize) { m_payload = device; m_payloadSize = payloadSize; Q_ASSERT(m_payloadSize >= -1); } + bool hasPayload() const { return (m_payloadSize != 0); } + qint64 payloadSize() const { return m_payloadSize; } //-1 means it is an endless stream + FileTransferJob* createPayloadTransferJob(const QUrl& destination) const; //To be called by a particular DeviceLink - QVariantMap payloadTransferInfo() const { return mPayloadTransferInfo; } - void setPayloadTransferInfo(const QVariantMap& map) { mPayloadTransferInfo = map; } - bool hasPayloadTransferInfo() const { return !mPayloadTransferInfo.isEmpty(); } + QVariantMap payloadTransferInfo() const { return m_payloadTransferInfo; } + void setPayloadTransferInfo(const QVariantMap& map) { m_payloadTransferInfo = map; } + bool hasPayloadTransferInfo() const { return !m_payloadTransferInfo.isEmpty(); } private: - void setId(const QString& id) { mId = id; } - void setType(const QString& t) { mType = t; } - void setBody(const QVariantMap& b) { mBody = b; } - void setPayloadSize(qint64 s) { mPayloadSize = s; } + void setId(const QString& id) { m_id = id; } + void setType(const QString& t) { m_type = t; } + void setBody(const QVariantMap& b) { m_body = b; } + void setPayloadSize(qint64 s) { m_payloadSize = s; } - QString mId; - QString mType; - QVariantMap mBody; + QString m_id; + QString m_type; + QVariantMap m_body; - QSharedPointer mPayload; - qint64 mPayloadSize; - QVariantMap mPayloadTransferInfo; + QSharedPointer m_payload; + qint64 m_payloadSize; + QVariantMap m_payloadTransferInfo; }; +KDECONNECTCORE_EXPORT QDebug operator<<(QDebug s, const NetworkPackage& pkg); + #endif // NETWORKPACKAGE_H diff --git a/core/networkpackagetypes.h b/core/networkpackagetypes.h index 6aa24a29..5c13d4c2 100644 --- a/core/networkpackagetypes.h +++ b/core/networkpackagetypes.h @@ -1,28 +1,27 @@ /** * 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 NETWORKPACKAGETYPES_H #define NETWORKPACKAGETYPES_H -#define PACKAGE_TYPE_IDENTITY QLatin1String("kdeconnect.identity") -#define PACKAGE_TYPE_PAIR QLatin1String("kdeconnect.pair") -#define PACKAGE_TYPE_ENCRYPTED QLatin1String("kdeconnect.encrypted") +#define PACKAGE_TYPE_IDENTITY QStringLiteral("kdeconnect.identity") +#define PACKAGE_TYPE_PAIR QStringLiteral("kdeconnect.pair") #endif // NETWORKPACKAGETYPES_H diff --git a/core/pluginloader.cpp b/core/pluginloader.cpp index 40f08e52..c63b7797 100644 --- a/core/pluginloader.cpp +++ b/core/pluginloader.cpp @@ -1,103 +1,131 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "pluginloader.h" #include #include #include -#include #include "core_debug.h" #include "device.h" #include "kdeconnectplugin.h" PluginLoader* PluginLoader::instance() { static PluginLoader* instance = new PluginLoader(); return instance; } PluginLoader::PluginLoader() { - QVector data = KPluginLoader::findPlugins("kdeconnect/"); - foreach (const KPluginMetaData& metadata, data) { + const QVector data = KPluginLoader::findPlugins(QStringLiteral("kdeconnect/")); + for (const KPluginMetaData& metadata : data) { plugins[metadata.pluginId()] = metadata; } } QStringList PluginLoader::getPluginList() const { return plugins.keys(); } KPluginMetaData PluginLoader::getPluginInfo(const QString& name) const { return plugins.value(name); } KdeConnectPlugin* PluginLoader::instantiatePluginForDevice(const QString& pluginName, Device* device) const { KdeConnectPlugin* ret = Q_NULLPTR; KPluginMetaData service = plugins.value(pluginName); if (!service.isValid()) { qCDebug(KDECONNECT_CORE) << "Plugin unknown" << pluginName; return ret; } KPluginLoader loader(service.fileName()); - KPluginFactory *factory = loader.factory(); + 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(), "X-KdeConnect-OutgoingPackageType"); + const QStringList outgoingInterfaces = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPackageType")); QVariant deviceVariant = QVariant::fromValue(device); ret = factory->create(device, QVariantList() << deviceVariant << pluginName << outgoingInterfaces); if (!ret) { qCDebug(KDECONNECT_CORE) << "Error loading plugin"; return ret; } - qCDebug(KDECONNECT_CORE) << "Loaded plugin:" << service.pluginId(); + //qCDebug(KDECONNECT_CORE) << "Loaded plugin:" << service.pluginId(); return ret; } -QStringList PluginLoader::incomingInterfaces() const +QStringList PluginLoader::incomingCapabilities() const { QSet ret; - foreach(const KPluginMetaData& service, plugins) { - ret += KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType").toSet(); + for (const KPluginMetaData& service : qAsConst(plugins)) { + ret += KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPackageType")).toSet(); } return ret.toList(); } -QStringList PluginLoader::outgoingInterfaces() const +QStringList PluginLoader::outgoingCapabilities() const { QSet ret; - foreach(const KPluginMetaData& service, plugins) { - ret += KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-OutgoingPackageType").toSet(); + for (const KPluginMetaData& service : qAsConst(plugins)) { + ret += KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPackageType")).toSet(); } return ret.toList(); } + +QSet PluginLoader::pluginsForCapabilities(const QSet& incoming, const QSet& outgoing) +{ + QSet ret; + + for (const KPluginMetaData& service : qAsConst(plugins)) { + const QSet pluginIncomingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-SupportedPackageType")).toSet(); + const QSet pluginOutgoingCapabilities = KPluginMetaData::readStringList(service.rawData(), QStringLiteral("X-KdeConnect-OutgoingPackageType")).toSet(); + + bool capabilitiesEmpty = (pluginIncomingCapabilities.isEmpty() && pluginOutgoingCapabilities.isEmpty()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) + bool capabilitiesIntersect = (outgoing.intersects(pluginIncomingCapabilities) || incoming.intersects(pluginOutgoingCapabilities)); +#else + QSet commonIncoming = incoming; + commonIncoming.intersect(pluginOutgoingCapabilities); + QSet commonOutgoing = outgoing; + commonOutgoing.intersect(pluginIncomingCapabilities); + bool capabilitiesIntersect = (!commonIncoming.isEmpty() || !commonOutgoing.isEmpty()); +#endif + + if (capabilitiesIntersect || capabilitiesEmpty) { + ret += service.pluginId(); + } else { + qCDebug(KDECONNECT_CORE) << "Not loading plugin" << service.pluginId() << "because device doesn't support it"; + } + } + + return ret; +} diff --git a/core/pluginloader.h b/core/pluginloader.h index f1722ab9..306431d2 100644 --- a/core/pluginloader.h +++ b/core/pluginloader.h @@ -1,52 +1,54 @@ /** * 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 PLUGINLOADER_H #define PLUGINLOADER_H #include #include #include #include class Device; class KdeConnectPlugin; class PluginLoader { public: static PluginLoader* instance(); - QStringList incomingInterfaces() const; - QStringList outgoingInterfaces() const; QStringList getPluginList() const; KPluginMetaData getPluginInfo(const QString& name) const; KdeConnectPlugin* instantiatePluginForDevice(const QString& name, Device* device) const; + QStringList incomingCapabilities() const; + QStringList outgoingCapabilities() const; + QSet pluginsForCapabilities(const QSet& incoming, const QSet& outgoing); + private: PluginLoader(); QHash plugins; }; #endif diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 0aec9424..649fe4a4 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -1,15 +1,18 @@ project(kdeconnectd) find_package(KF5 REQUIRED COMPONENTS Notifications KIO) add_definitions(-DTRANSLATION_DOMAIN="kdeconnect-kded") add_executable(kdeconnectd kdeconnectd.cpp) target_link_libraries(kdeconnectd kdeconnectcore KF5::KIOWidgets KF5::DBusAddons KF5::Notifications KF5::I18n Qt5::Widgets) +ecm_mark_nongui_executable(kdeconnectd) + 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/daemon/kdeconnect.desktop b/daemon/kdeconnect.desktop index fd253e30..05de252a 100644 --- a/daemon/kdeconnect.desktop +++ b/daemon/kdeconnect.desktop @@ -1,65 +1,91 @@ [Desktop Entry] Type=Service Icon=preferences-system-power-management X-KDE-ServiceTypes=KDEDModule X-KDE-Library=kdeconnect X-DBUS-ServiceName=org.kde.kdeconnect X-KDE-DBus-ModuleName=kdeconnect X-KDE-Kded-autoload=true X-KDE-Kded-load-on-demand=false X-KDE-Kded-phase=1 Name=KDE Connect +Name[ar]=كدي المتّصل +Name[ast]=KDE Connect Name[bg]=KDE Connect Name[bs]=Konekcija KDE Name[ca]=KDE Connect +Name[ca@valencia]=KDE Connect Name[cs]=KDE Connect Name[da]=KDE Connect Name[de]=KDE-Connect +Name[el]=KDE Connect Name[en_GB]=KDE Connect Name[es]=KDE Connect +Name[et]=KDE Connect +Name[eu]=KDE Connect Name[fi]=KDE Connect Name[fr]=KDE Connect Name[gl]=KDE Connect +Name[he]=KDE Connect Name[hu]=KDE csatlakozás Name[it]=KDE Connect Name[ko]=KDE Connect Name[nl]=KDE Connect +Name[nn]=KDE Connect Name[pl]=KDE Connect Name[pt]=KDE Connect Name[pt_BR]=KDE Connect Name[ro]=KDE Connect Name[ru]=KDE Connect Name[sk]=KDE Connect -Name[sv]=KDE anslut -Name[tr]=KDE Bağlan +Name[sr]=КДЕ‑конекција +Name[sr@ijekavian]=КДЕ‑конекција +Name[sr@ijekavianlatin]=KDE‑konekcija +Name[sr@latin]=KDE‑konekcija +Name[sv]=KDE-anslut +Name[tr]=KDE Connect Name[uk]=З’єднання KDE Name[x-test]=xxKDE Connectxx Name[zh_CN]=KDE Connect +Name[zh_TW]=KDE 連線 Comment=Connect KDE with your smartphone +Comment[ar]=أوصل كدي بهاتفك الذّكيّ +Comment[ast]=Coneuta KDE col to teléfonu intelixente Comment[bg]=Свържете КДЕ с вашия смартфон Comment[bs]=Konektujte se na KDE sa Vašim mobitelom Comment[ca]=Connecta el KDE amb el vostre telèfon intel·ligent +Comment[ca@valencia]=Connecta el KDE amb el vostre telèfon intel·ligent Comment[cs]=Propojte KDE s vaším telefonem Comment[da]=Forbind KDE med din smartphone Comment[de]=KDE mit Ihren Smartphone verbinden +Comment[el]=Σύνδεση του KDE με το έξυπνο τηλέφωνό σας Comment[en_GB]=Connect KDE with your smartphone Comment[es]=Conecte KDE con su teléfono móvil +Comment[et]=KDE ühendamine oma nutitelefoniga +Comment[eu]=Konektatu KDE zure mugikorrarekin Comment[fi]=Yhdistä KDE älypuhelimeesi Comment[fr]=Connectez KDE avec votre smartphone Comment[gl]=Conectar KDE co seu teléfono móbil. +Comment[he]=חבר את KDE לפלאפון החכם שלך Comment[hu]=A KDE csatlakoztatása az okostelefonnal Comment[it]=Connetti KDE con il tuo smartphone Comment[ko]=KDE와 스마트폰 연결 Comment[nl]=KDE met uw smartphone verbinden +Comment[nn]=Kopla KDE til smarttelefonen din Comment[pl]=Podłącz swój smartfon do KDE Comment[pt]=Ligue o KDE ao seu telemóvel Comment[pt_BR]=Conecte o KDE ao seu celular Comment[ru]=Подключайте смартфон к KDE Comment[sk]=Prepojiť KDE s vašim smartfónom +Comment[sr]=Повежите КДЕ са својим паметним телефоном +Comment[sr@ijekavian]=Повежите КДЕ са својим паметним телефоном +Comment[sr@ijekavianlatin]=Povežite KDE sa svojim pametnim telefonom +Comment[sr@latin]=Povežite KDE sa svojim pametnim telefonom Comment[sv]=Anslut KDE till din smartphone Comment[tr]=KDE'yi akıllı telefonunuz ile bağlayın Comment[uk]=З’єднання KDE з вашим смартфоном Comment[x-test]=xxConnect KDE with your smartphonexx Comment[zh_CN]=用您的智能手机连接到 KDE +Comment[zh_TW]=將您的智慧型手機與KDE連線 diff --git a/daemon/kdeconnectd.cpp b/daemon/kdeconnectd.cpp index e71f88f3..7c856097 100644 --- a/daemon/kdeconnectd.cpp +++ b/daemon/kdeconnectd.cpp @@ -1,122 +1,91 @@ /** * Copyright 2014 Yuri Samoilenko * * 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 "core/daemon.h" #include "core/device.h" +#include "core/backends/pairinghandler.h" #include "kdeconnect-version.h" -static int sigtermfd[2]; -const static char deadbeef = 1; -struct sigaction action; - -void sighandler(int signum) -{ - if( signum == SIGTERM || signum == SIGINT) - { - ssize_t unused = ::write(sigtermfd[0], &deadbeef, sizeof(deadbeef)); - Q_UNUSED(unused); - } -} - -void initializeTermHandlers(QCoreApplication* app, Daemon* daemon) -{ - ::socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermfd); - QSocketNotifier* snTerm = new QSocketNotifier(sigtermfd[1], QSocketNotifier::Read, app); - QObject::connect(snTerm, SIGNAL(activated(int)), daemon, SLOT(deleteLater())); - - action.sa_handler = sighandler; - sigemptyset(&action.sa_mask); - action.sa_flags = 0; - - sigaction(SIGTERM, &action, NULL); - sigaction(SIGINT, &action, NULL); -} - class DesktopDaemon : public Daemon { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") public: DesktopDaemon(QObject* parent = Q_NULLPTR) : Daemon(parent) , m_nam(Q_NULLPTR) {} - void requestPairing(Device* d) Q_DECL_OVERRIDE + void askPairingConfirmation(Device* device) override { - KNotification* notification = new KNotification("pairingRequest"); + KNotification* notification = new KNotification(QStringLiteral("pairingRequest")); notification->setIconName(QStringLiteral("dialog-information")); - notification->setComponentName("kdeconnect"); - notification->setText(i18n("Pairing request from %1", d->name())); + notification->setComponentName(QStringLiteral("kdeconnect")); + notification->setText(i18n("Pairing request from %1", device->name().toHtmlEscaped())); notification->setActions(QStringList() << i18n("Accept") << i18n("Reject")); - connect(notification, &KNotification::ignored, d, &Device::rejectPairing); - connect(notification, &KNotification::action1Activated, d, &Device::acceptPairing); - connect(notification, &KNotification::action2Activated, d, &Device::rejectPairing); +// notification->setTimeout(PairingHandler::pairingTimeoutMsec()); + connect(notification, &KNotification::action1Activated, device, &Device::acceptPairing); + connect(notification, &KNotification::action2Activated, device, &Device::rejectPairing); notification->sendEvent(); } - void reportError(const QString & title, const QString & description) Q_DECL_OVERRIDE + void reportError(const QString & title, const QString & description) override { KNotification::event(KNotification::Error, title, description); } - QNetworkAccessManager* networkAccessManager() Q_DECL_OVERRIDE + QNetworkAccessManager* networkAccessManager() override { if (!m_nam) { m_nam = new KIO::AccessManager(this); } return m_nam; } private: QNetworkAccessManager* m_nam; }; int main(int argc, char* argv[]) { QApplication app(argc, argv); - app.setApplicationName("kdeconnectd"); - app.setApplicationVersion(QLatin1String(KDECONNECT_VERSION_STRING)); - app.setOrganizationDomain("kde.org"); + app.setApplicationName(QStringLiteral("kdeconnectd")); + app.setApplicationVersion(QStringLiteral(KDECONNECT_VERSION_STRING)); + app.setOrganizationDomain(QStringLiteral("kde.org")); app.setQuitOnLastWindowClosed(false); KDBusService dbusService(KDBusService::Unique); Daemon* daemon = new DesktopDaemon; QObject::connect(daemon, SIGNAL(destroyed(QObject*)), &app, SLOT(quit())); - initializeTermHandlers(&app, daemon); return app.exec(); } #include "kdeconnectd.moc" diff --git a/daemon/kdeconnectd.desktop.cmake b/daemon/kdeconnectd.desktop.cmake index c55123df..ab93439c 100644 --- a/daemon/kdeconnectd.desktop.cmake +++ b/daemon/kdeconnectd.desktop.cmake @@ -1,30 +1,47 @@ [Desktop Entry] -Type=Service -Exec=${CMAKE_INSTALL_PREFIX}/${LIBEXEC_INSTALL_DIR}/kdeconnectd +Type=Application +Exec=${KDE_INSTALL_FULL_LIBEXECDIR}/kdeconnectd X-KDE-StartupNotify=false -X-KDE-autostart-phase=0 +X-KDE-autostart-phase=1 X-GNOME-Autostart-enabled=true -OnlyShowIn=KDE;GNOME;Unity;XFCE +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/daemon/org.kde.kdeconnect.service.in b/daemon/org.kde.kdeconnect.service.in index e5f5dfdf..4f747186 100644 --- a/daemon/org.kde.kdeconnect.service.in +++ b/daemon/org.kde.kdeconnect.service.in @@ -1,3 +1,3 @@ [D-BUS Service] Name=org.kde.kdeconnect -Exec=${CMAKE_INSTALL_PREFIX}/${LIBEXEC_INSTALL_DIR}/kdeconnectd +Exec=@KDE_INSTALL_FULL_LIBEXECDIR@/kdeconnectd diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 00000000..df281f56 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1 @@ +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${HTML_INSTALL_DIR}/en SUBDIR kdeconnect) diff --git a/doc/index.docbook b/doc/index.docbook new file mode 100644 index 00000000..c75a139c --- /dev/null +++ b/doc/index.docbook @@ -0,0 +1,55 @@ + + + +]> + +
+ +KDE Connect + +&Aleix.Pol; + + + +2015-10-19 +0.9 + + +KDE +Connect +phone +remote +device +pair + + + + + +KDE Connect + +You can use this dialog to configure which devices we're paired to and what we're sharing. The UI is divided in 2 main parts: + + +Devices List + +It contains a list of devices separated between paired and unpaired. Select one to pair it and when it's paired, configure it. + + + +Device Configuration + +It offers a list of the available plugins related to the device. These plugins will correspond to the different features available for the device. This is useful mainly for knowing what plugins are available and, in case there's some information that should not be shared with a device, explicitly disable it. + + + +General interaction with the devices + +KDE Connect doesn't offer a central place to interact with your devices. It's designed to integrate properly with your operating system, making it possible to share files from the different applications or integrating with the notification system to display information. + + + + +
diff --git a/fileitemactionplugin/CMakeLists.txt b/fileitemactionplugin/CMakeLists.txt index 08e72dcd..a5fafd52 100644 --- a/fileitemactionplugin/CMakeLists.txt +++ b/fileitemactionplugin/CMakeLists.txt @@ -1,13 +1,13 @@ find_package(KF5 REQUIRED COMPONENTS KIO) add_definitions(-DTRANSLATION_DOMAIN="kdeconnect-fileitemaction") include_directories(${CMAKE_SOURCE_DIR}) -add_library(kdeconnectfiletiemaction MODULE sendfileitemaction.cpp) -target_link_libraries(kdeconnectfiletiemaction +add_library(kdeconnectfileitemaction MODULE sendfileitemaction.cpp) +target_link_libraries(kdeconnectfileitemaction KF5::KIOWidgets KF5::I18n kdeconnectinterfaces ) -install(TARGETS kdeconnectfiletiemaction DESTINATION ${PLUGIN_INSTALL_DIR}) +install(TARGETS kdeconnectfileitemaction DESTINATION ${PLUGIN_INSTALL_DIR}) install(FILES kdeconnectsendfile.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/fileitemactionplugin/kdeconnectsendfile.desktop b/fileitemactionplugin/kdeconnectsendfile.desktop index c5f7fd7e..56c5c0ab 100644 --- a/fileitemactionplugin/kdeconnectsendfile.desktop +++ b/fileitemactionplugin/kdeconnectsendfile.desktop @@ -1,57 +1,86 @@ [Desktop Entry] Type=Service Name=Send file via KDE Connect service +Name[ar]=أرسل ملفًّا عبر خدمة «كدي المتّصل» +Name[ast]=Unviar ficheru pel serviciu de KDE Connect Name[bg]=Изпращане на файл през услугата KDE Connect Name[bs]=Pošalji datoteku putem KDE Connect servisa Name[ca]=Envia un fitxer a través del servei KDE Connect +Name[ca@valencia]=Envia un fitxer a través del servei KDE Connect Name[cs]=Poslat soubor přes službu KDE Connect Name[da]=Send fil via KDE Connect-tjeneste Name[de]=Datei mit KDE-Connect -Dienst versenden +Name[el]=Αποστολή αρχείου μέσω της υπηρεσίας KDE Connect Name[en_GB]=Send file via KDE Connect service Name[es]=Enviar archivo usando el servicio KDE Connect +Name[et]=Faili saatmine KDE Connecti teenuse kaudu +Name[eu]=Bidali fitxategia KDE Connect zerbitzuaren bidez Name[fi]=Lähetä tiedosto KDE Connect -palvelulla Name[fr]=Envoyer un fichier via le service KDE Connect Name[gl]=Enviar un ficheiro mediante o servizo de KDE Connect +Name[he]=שלח קובץ דרך שירות KDE Connect Name[hu]=Fájl küldése a KDE csatlakozás szolgáltatáson keresztül Name[it]=Invia un file tramite il servizio KDE Connect Name[ko]=KDE Connect 서비스로 파일 보내기 Name[nl]=Bestand via de service KDE Connect versturen +Name[nn]=Send fil med KDE Connect-tenesta Name[pl]=Wyślij plik przez usługę KDE Connect Name[pt]=Enviar um ficheiro pelo serviço KDE Connect Name[pt_BR]=Enviar arquivo pelo serviço KDE Connect +Name[ru]=Отправить файл через KDE Connect Name[sk]=Poslať súbor cez službu KDE Connect +Name[sr]=Шаљи фајл преко КДЕ‑конекцијиног сервиса +Name[sr@ijekavian]=Шаљи фајл преко КДЕ‑конекцијиног сервиса +Name[sr@ijekavianlatin]=Šalji fajl preko KDE‑konekcijinog servisa +Name[sr@latin]=Šalji fajl preko KDE‑konekcijinog servisa Name[sv]=Skicka fil via KDE:s anslutningstjänst -Name[tr]=KDE Bağlantı servisi ile dosya gönderin +Name[tr]=KDE Connect servisi ile dosya gönderin Name[uk]=Надсилання файла за допомогою служби з’єднання KDE Name[x-test]=xxSend file via KDE Connect servicexx Name[zh_CN]=通过 KDE Connect 服务发送文件 -X-KDE-Library=kdeconnectfiletiemaction +Name[zh_TW]=以已經連結到KDE之服務或裝置來傳送檔案 +X-KDE-Library=kdeconnectfileitemaction X-KDE-Submenu=Connect +X-KDE-Submenu[ar]=اتّصل,اتصل +X-KDE-Submenu[ast]=Coneutar X-KDE-Submenu[bg]=Свързване X-KDE-Submenu[bs]=Uspostavi vezu X-KDE-Submenu[ca]=Connecta +X-KDE-Submenu[ca@valencia]=Connecta X-KDE-Submenu[cs]=Připojit X-KDE-Submenu[da]=Forbind X-KDE-Submenu[de]=Verbinden +X-KDE-Submenu[el]=Σύνδεση X-KDE-Submenu[en_GB]=Connect X-KDE-Submenu[es]=Conectar +X-KDE-Submenu[et]=Ühendus +X-KDE-Submenu[eu]=Konektatu X-KDE-Submenu[fi]=Yhdistä X-KDE-Submenu[fr]=Connecter X-KDE-Submenu[gl]=Conectar +X-KDE-Submenu[he]=חיבור X-KDE-Submenu[hu]=Csatlakozás +X-KDE-Submenu[ia]=Connecte X-KDE-Submenu[it]=Connetti X-KDE-Submenu[ko]=연결 X-KDE-Submenu[nl]=Verbinden +X-KDE-Submenu[nn]=Kopla til X-KDE-Submenu[pl]=Połącz X-KDE-Submenu[pt]=Ligar X-KDE-Submenu[pt_BR]=Conectar +X-KDE-Submenu[ru]=KDE Connect X-KDE-Submenu[sk]=Pripojiť +X-KDE-Submenu[sr]=Повежи се +X-KDE-Submenu[sr@ijekavian]=Повежи се +X-KDE-Submenu[sr@ijekavianlatin]=Poveži se +X-KDE-Submenu[sr@latin]=Poveži se X-KDE-Submenu[sv]=Anslut X-KDE-Submenu[tr]=Bağlan X-KDE-Submenu[uk]=З’єднання X-KDE-Submenu[x-test]=xxConnectxx X-KDE-Submenu[zh_CN]=连接 +X-KDE-Submenu[zh_TW]=連線 Icon=preferences-system-network ServiceTypes=KFileItemAction/Plugin MimeType=application/octet-stream diff --git a/fileitemactionplugin/sendfileitemaction.cpp b/fileitemactionplugin/sendfileitemaction.cpp index 157cbc57..2006e1c9 100644 --- a/fileitemactionplugin/sendfileitemaction.cpp +++ b/fileitemactionplugin/sendfileitemaction.cpp @@ -1,99 +1,99 @@ /* * Copyright (C) 2011 Alejandro Fiestas Olivares * Copyright (C) 2014 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sendfileitemaction.h" #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(SendFileItemActionFactory, registerPlugin();) Q_LOGGING_CATEGORY(KDECONNECT_FILEITEMACTION, "kdeconnect.fileitemaction") SendFileItemAction::SendFileItemAction(QObject* parent, const QVariantList& ): KAbstractFileItemActionPlugin(parent) { } QList SendFileItemAction::actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) { QList actions; DaemonDbusInterface iface; if (!iface.isValid()) { return actions; } QDBusPendingReply reply = iface.devices(true, true); reply.waitForFinished(); const QStringList devices = reply.value(); - foreach (const QString& id, devices) { + for (const QString& id : devices) { DeviceDbusInterface deviceIface(id); if (!deviceIface.isValid()) { continue; } - if (!deviceIface.hasPlugin("kdeconnect_share")) { + if (!deviceIface.hasPlugin(QStringLiteral("kdeconnect_share"))) { continue; } QAction* action = new QAction(QIcon::fromTheme(deviceIface.iconName()), deviceIface.name(), parentWidget); action->setProperty("id", id); action->setProperty("urls", QVariant::fromValue(fileItemInfos.urlList())); action->setProperty("parentWidget", QVariant::fromValue(parentWidget)); - connect(action, SIGNAL(triggered(bool)), this, SLOT(sendFile())); + connect(action, &QAction::triggered, this, &SendFileItemAction::sendFile); actions += action; } if (actions.count() > 1) { - QAction *menuAction = new QAction(QIcon::fromTheme("preferences-system-network"), i18n("Send via KDE Connect"), parentWidget); - QMenu *menu = new QMenu(); + QAction* menuAction = new QAction(QIcon::fromTheme(QStringLiteral("preferences-system-network")), i18n("Send via KDE Connect"), parentWidget); + QMenu* menu = new QMenu(parentWidget); menu->addActions(actions); menuAction->setMenu(menu); return QList() << menuAction; } else { if(actions.count() == 1) { actions.first()->setText(i18n("Send to '%1' via KDE Connect", actions.first()->text())); } return actions; } } void SendFileItemAction::sendFile() { - QList urls = sender()->property("urls").value>(); + const QList urls = sender()->property("urls").value>(); QString id = sender()->property("id").toString(); - foreach(const QUrl& url, urls) { - QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/"+id+"/share", "org.kde.kdeconnect.device.share", "shareUrl"); + for (const QUrl& url : urls) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+id+"/share", QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); msg.setArguments(QVariantList() << url.toString()); QDBusConnection::sessionBus().call(msg); } } #include "sendfileitemaction.moc" diff --git a/fileitemactionplugin/sendfileitemaction.h b/fileitemactionplugin/sendfileitemaction.h index 21ad1fb8..6020a23d 100644 --- a/fileitemactionplugin/sendfileitemaction.h +++ b/fileitemactionplugin/sendfileitemaction.h @@ -1,45 +1,45 @@ /* * Copyright (C) 2011 Alejandro Fiestas Olivares * Copyright (C) 2014 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SENDFILEITEMACTION_H #define SENDFILEITEMACTION_H #include #include #include class QAction; class KFileItemListProperties; class QWidget; Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_FILEITEMACTION) class SendFileItemAction : public KAbstractFileItemActionPlugin { Q_OBJECT public: - SendFileItemAction(QObject* parent, const QVariantList &args); - virtual QList< QAction* > actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget); + SendFileItemAction(QObject* parent, const QVariantList& args); + QList< QAction* > actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) override; private Q_SLOTS: void sendFile(); }; #endif // SENDFILEITEMACTION_H diff --git a/icon/32-status-laptop-connected.png b/icon/32-status-laptopconnected.png similarity index 100% rename from icon/32-status-laptop-connected.png rename to icon/32-status-laptopconnected.png diff --git a/icon/32-status-laptop-disconnected.png b/icon/32-status-laptopdisconnected.png similarity index 100% rename from icon/32-status-laptop-disconnected.png rename to icon/32-status-laptopdisconnected.png diff --git a/icon/32-status-laptop-trusted.png b/icon/32-status-laptoptrusted.png similarity index 100% rename from icon/32-status-laptop-trusted.png rename to icon/32-status-laptoptrusted.png diff --git a/icon/32-status-smartphone-connected.png b/icon/32-status-smartphoneconnected.png similarity index 100% rename from icon/32-status-smartphone-connected.png rename to icon/32-status-smartphoneconnected.png diff --git a/icon/32-status-smartphone-disconnected.png b/icon/32-status-smartphonedisconnected.png similarity index 100% rename from icon/32-status-smartphone-disconnected.png rename to icon/32-status-smartphonedisconnected.png diff --git a/icon/32-status-smartphone-trusted.png b/icon/32-status-smartphonetrusted.png similarity index 100% rename from icon/32-status-smartphone-trusted.png rename to icon/32-status-smartphonetrusted.png diff --git a/icon/32-status-tablet-connected.png b/icon/32-status-tabletconnected.png similarity index 100% rename from icon/32-status-tablet-connected.png rename to icon/32-status-tabletconnected.png diff --git a/icon/32-status-tablet-disconnected.png b/icon/32-status-tabletdisconnected.png similarity index 100% rename from icon/32-status-tablet-disconnected.png rename to icon/32-status-tabletdisconnected.png diff --git a/icon/32-status-tablet-trusted.png b/icon/32-status-tablettrusted.png similarity index 100% rename from icon/32-status-tablet-trusted.png rename to icon/32-status-tablettrusted.png diff --git a/icon/CMakeLists.txt b/icon/CMakeLists.txt index 850a669e..cf05f32f 100644 --- a/icon/CMakeLists.txt +++ b/icon/CMakeLists.txt @@ -1,39 +1,39 @@ ecm_install_icons( ICONS 256-apps-kdeconnect.png 128-apps-kdeconnect.png 32-apps-kdeconnect.png 64-apps-kdeconnect.png 48-apps-kdeconnect.png 22-apps-kdeconnect.png 16-apps-kdeconnect.png sc-apps-kdeconnect.svgz DESTINATION ${ICON_INSTALL_DIR} ) ecm_install_icons( ICONS - 32-status-laptop-connected.png - 32-status-laptop-disconnected.png - 32-status-laptop-trusted.png - 32-status-smartphone-connected.png - 32-status-smartphone-disconnected.png - 32-status-smartphone-trusted.png - 32-status-tablet-connected.png - 32-status-tablet-disconnected.png - 32-status-tablet-trusted.png - sc-status-laptop-connected.svg - sc-status-laptop-disconnected.svg - sc-status-laptop-trusted.svg - sc-status-smartphone-connected.svg - sc-status-smartphone-disconnected.svg - sc-status-smartphone-trusted.svg - sc-status-tablet-connected.svg - sc-status-tablet-disconnected.svg - sc-status-tablet-trusted.svg + 32-status-laptopconnected.png + 32-status-laptopdisconnected.png + 32-status-laptoptrusted.png + 32-status-smartphoneconnected.png + 32-status-smartphonedisconnected.png + 32-status-smartphonetrusted.png + 32-status-tabletconnected.png + 32-status-tabletdisconnected.png + 32-status-tablettrusted.png + sc-status-laptopconnected.svg + sc-status-laptopdisconnected.svg + sc-status-laptoptrusted.svg + sc-status-smartphoneconnected.svg + sc-status-smartphonedisconnected.svg + sc-status-smartphonetrusted.svg + sc-status-tabletconnected.svg + sc-status-tabletdisconnected.svg + sc-status-tablettrusted.svg DESTINATION ${ICON_INSTALL_DIR} ) diff --git a/icon/sc-status-laptop-connected.svg b/icon/sc-status-laptopconnected.svg similarity index 100% rename from icon/sc-status-laptop-connected.svg rename to icon/sc-status-laptopconnected.svg diff --git a/icon/sc-status-laptop-disconnected.svg b/icon/sc-status-laptopdisconnected.svg similarity index 100% rename from icon/sc-status-laptop-disconnected.svg rename to icon/sc-status-laptopdisconnected.svg diff --git a/icon/sc-status-laptop-trusted.svg b/icon/sc-status-laptoptrusted.svg similarity index 100% rename from icon/sc-status-laptop-trusted.svg rename to icon/sc-status-laptoptrusted.svg diff --git a/icon/sc-status-smartphone-connected.svg b/icon/sc-status-smartphoneconnected.svg similarity index 100% rename from icon/sc-status-smartphone-connected.svg rename to icon/sc-status-smartphoneconnected.svg diff --git a/icon/sc-status-smartphone-disconnected.svg b/icon/sc-status-smartphonedisconnected.svg similarity index 100% rename from icon/sc-status-smartphone-disconnected.svg rename to icon/sc-status-smartphonedisconnected.svg diff --git a/icon/sc-status-smartphone-trusted.svg b/icon/sc-status-smartphonetrusted.svg similarity index 100% rename from icon/sc-status-smartphone-trusted.svg rename to icon/sc-status-smartphonetrusted.svg diff --git a/icon/sc-status-tablet-connected.svg b/icon/sc-status-tabletconnected.svg similarity index 100% rename from icon/sc-status-tablet-connected.svg rename to icon/sc-status-tabletconnected.svg diff --git a/icon/sc-status-tablet-disconnected.svg b/icon/sc-status-tabletdisconnected.svg similarity index 100% rename from icon/sc-status-tablet-disconnected.svg rename to icon/sc-status-tabletdisconnected.svg diff --git a/icon/sc-status-tablet-trusted.svg b/icon/sc-status-tablettrusted.svg similarity index 100% rename from icon/sc-status-tablet-trusted.svg rename to icon/sc-status-tablettrusted.svg diff --git a/indicator/CMakeLists.txt b/indicator/CMakeLists.txt new file mode 100644 index 00000000..a78145b9 --- /dev/null +++ b/indicator/CMakeLists.txt @@ -0,0 +1,24 @@ +find_package(KF5Notifications REQUIRED) +find_package(KF5KCMUtils REQUIRED) + +set(indicator_SRCS + main.cpp + deviceindicator.cpp +) + +include(ECMAddAppIcon) +ecm_add_app_icon(indicator_SRCS ICONS + ../icon/16-apps-kdeconnect.png + ../icon/22-apps-kdeconnect.png + ../icon/32-apps-kdeconnect.png + ../icon/48-apps-kdeconnect.png + ../icon/64-apps-kdeconnect.png + ../icon/128-apps-kdeconnect.png + ../icon/256-apps-kdeconnect.png +) + +add_executable(kdeconnect-indicator ${indicator_SRCS}) +target_link_libraries(kdeconnect-indicator Qt5::Widgets KF5::CoreAddons KF5::I18n KF5::Notifications KF5::DBusAddons KF5::KCMUtils kdeconnectinterfaces) + +install(TARGETS kdeconnect-indicator ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(PROGRAMS org.kde.kdeconnect.nonplasma.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/indicator/deviceindicator.cpp b/indicator/deviceindicator.cpp new file mode 100644 index 00000000..856bc6b1 --- /dev/null +++ b/indicator/deviceindicator.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2016 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "deviceindicator.h" +#include +#include + +class BatteryAction : public QAction +{ +Q_OBJECT +public: + BatteryAction(DeviceDbusInterface* device) + : QAction(nullptr) + , m_batteryIface(new DeviceBatteryDbusInterface(device->id(), this)) + { + setWhenAvailable(m_batteryIface->charge(), [this](int charge) { setCharge(charge); }, this); + setWhenAvailable(m_batteryIface->isCharging(), [this](bool charging) { setCharging(charging); }, this); + + connect(m_batteryIface, SIGNAL(chargeChanged(int)), this, SLOT(setCharge(int))); + connect(m_batteryIface, SIGNAL(stateChanged(bool)), this, SLOT(setCharging(bool))); + + update(); + } + + void update() { + if (m_charge < 0) + setText(i18n("No Battery")); + else if (m_charging) + setText(i18n("Battery: %1% (Charging)", m_charge)); + else + setText(i18n("Battery: %1%", m_charge)); + } + +private Q_SLOTS: + void setCharge(int charge) { m_charge = charge; update(); } + void setCharging(bool charging) { m_charging = charging; update(); } + +private: + DeviceBatteryDbusInterface* m_batteryIface; + int m_charge = -1; + bool m_charging = false; +}; + + +DeviceIndicator::DeviceIndicator(DeviceDbusInterface* device) + : QMenu(device->name(), nullptr) + , m_device(device) +{ +#ifdef Q_OS_WIN + setIcon(QIcon(QStandardPaths::locate(QStandardPaths::AppDataLocation, "icons/hicolor/scalable/status/"+device->iconName()+".svg"))); +#else + setIcon(QIcon::fromTheme(device->iconName())); +#endif + + connect(device, SIGNAL(nameChanged(QString)), this, SLOT(setText(QString))); + + auto battery = new BatteryAction(device); + addAction(battery); + setWhenAvailable(device->hasPlugin("kdeconnect_battery"), + [battery](bool available) { battery->setVisible(available); } + , this); + + auto browse = addAction(i18n("Browse device")); + connect(browse, &QAction::triggered, device, [device](){ + SftpDbusInterface* sftpIface = new SftpDbusInterface(device->id(), device); + sftpIface->startBrowsing(); + sftpIface->deleteLater(); + }); + setWhenAvailable(device->hasPlugin("kdeconnect_sftp"), [browse](bool available) { browse->setVisible(available); }, this); + + auto findDevice = addAction(i18n("Ring device")); + connect(findDevice, &QAction::triggered, device, [device](){ + FindMyPhoneDeviceDbusInterface* iface = new FindMyPhoneDeviceDbusInterface(device->id(), device); + iface->ring(); + iface->deleteLater(); + }); + setWhenAvailable(device->hasPlugin("kdeconnect_findmyphone"), [findDevice](bool available) { findDevice->setVisible(available); }, this); + + auto sendFile = addAction(i18n("Send file")); + connect(sendFile, &QAction::triggered, device, [device, this](){ + const QUrl url = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Select file to send to '%1'", device->name()), QUrl::fromLocalFile(QDir::homePath())); + if (url.isEmpty()) + return; + + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device->id()+"/share", QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); + msg.setArguments(QVariantList() << url.toString()); + QDBusConnection::sessionBus().call(msg); + }); + setWhenAvailable(device->hasPlugin("kdeconnect_share"), [sendFile](bool available) { sendFile->setVisible(available); }, this); +} + +#include "deviceindicator.moc" diff --git a/plugins/share/autoclosingqfile.cpp b/indicator/deviceindicator.h similarity index 65% rename from plugins/share/autoclosingqfile.cpp rename to indicator/deviceindicator.h index ef2aeea3..7d197e38 100644 --- a/plugins/share/autoclosingqfile.cpp +++ b/indicator/deviceindicator.h @@ -1,27 +1,40 @@ /* - * Copyright 2013 Albert Vaca + * Copyright 2016 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "autoclosingqfile.h" +#ifndef DEVICEINDICATOR_H +#define DEVICEINDICATOR_H -AutoClosingQFile::AutoClosingQFile(const QString& name) - : QFile(name) +#include +#include "interfaces/dbusinterfaces.h" + +class DeviceIndicator : public QMenu { + Q_OBJECT + public: + DeviceIndicator(DeviceDbusInterface* device); + + public Q_SLOTS: + void setText(const QString& text) { setTitle(text); } + + private: + DeviceDbusInterface* m_device; +}; -} +#endif // DEVICEINDICATOR_H diff --git a/indicator/main.cpp b/indicator/main.cpp new file mode 100644 index 00000000..415ac581 --- /dev/null +++ b/indicator/main.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2016 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef QSYSTRAY +#include +#else +#include +#endif + +#include +#include +#include +#include + +#include "interfaces/devicesmodel.h" +#include "interfaces/notificationsmodel.h" +#include "interfaces/dbusinterfaces.h" +#include "kdeconnect-version.h" +#include "deviceindicator.h" + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + KAboutData about("kdeconnect-indicator", + i18n("KDE Connect Indicator"), + QStringLiteral(KDECONNECT_VERSION_STRING), + i18n("KDE Connect Indicator tool"), + KAboutLicense::GPL, + i18n("(C) 2016 Aleix Pol Gonzalez")); + KAboutData::setApplicationData(about); + +#ifdef Q_OS_WIN + QProcess::startDetached("kdeconnectd.exe"); +#endif + + KDBusService dbusService(KDBusService::Unique); + + DevicesModel model; + model.setDisplayFilter(DevicesModel::Reachable | DevicesModel::Paired); + QMenu* menu = new QMenu; + + DaemonDbusInterface iface; + auto refreshMenu = [&iface, &model, &menu]() { + menu->clear(); + auto configure = menu->addAction(i18n("Configure...")); + QObject::connect(configure, &QAction::triggered, configure, [](){ + KCMultiDialog* dialog = new KCMultiDialog; + dialog->addModule("kcm_kdeconnect"); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); + }); + for (int i=0, count = model.rowCount(); iaddMenu(indicator); + } + const QStringList requests = iface.pairingRequests(); + if (!requests.isEmpty()) { + menu->addSection(i18n("Pairing requests")); + + for(const auto& req: requests) { + DeviceDbusInterface* dev = new DeviceDbusInterface(req, menu); + auto pairMenu = menu->addMenu(dev->name()); + pairMenu->addAction(i18n("Pair"), dev, &DeviceDbusInterface::acceptPairing); + pairMenu->addAction(i18n("Reject"), dev, &DeviceDbusInterface::rejectPairing); + } + } + }; + + QObject::connect(&iface, &DaemonDbusInterface::pairingRequestsChangedProxy, &model, refreshMenu); + QObject::connect(&model, &DevicesModel::rowsInserted, &model, refreshMenu); + QObject::connect(&model, &DevicesModel::rowsRemoved, &model, refreshMenu); + +#ifdef QSYSTRAY + QSystemTrayIcon systray; + systray.setIcon(QIcon::fromTheme("kdeconnect")); + systray.setVisible(true); + systray.setToolTip("KDE Connect"); + QObject::connect(&model, &DevicesModel::rowsChanged, &model, [&systray, &model]() { + systray.setToolTip(i18np("%1 device connected", "%1 devices connected", model.rowCount())); + }); + + systray.setContextMenu(menu); +#else + KStatusNotifierItem systray; + systray.setIconByName(QStringLiteral("kdeconnect")); + systray.setToolTip(QStringLiteral("kdeconnect"), "KDE Connect", "KDE Connect"); + systray.setCategory(KStatusNotifierItem::Communications); + systray.setStatus(KStatusNotifierItem::Passive); + systray.setStandardActionsEnabled(false); + QObject::connect(&model, &DevicesModel::rowsChanged, &model, [&systray, &model]() { + const auto count = model.rowCount(); + systray.setStatus(count == 0 ? KStatusNotifierItem::Passive : KStatusNotifierItem::Active); + systray.setToolTip(QStringLiteral("kdeconnect"), QStringLiteral("KDE Connect"), i18np("%1 device connected", "%1 devices connected", count)); + }); + + systray.setContextMenu(menu); +#endif + + refreshMenu(); + + app.setQuitOnLastWindowClosed(false); + + return app.exec(); +} diff --git a/indicator/org.kde.kdeconnect.nonplasma.desktop b/indicator/org.kde.kdeconnect.nonplasma.desktop new file mode 100644 index 00000000..ad0fc29c --- /dev/null +++ b/indicator/org.kde.kdeconnect.nonplasma.desktop @@ -0,0 +1,72 @@ +[Desktop Entry] +Name=KDE Connect Indicator +Name[ca]=Indicador del KDE Connect +Name[ca@valencia]=Indicador del KDE Connect +Name[cs]=Indikátor KDE Connect +Name[da]=KDE Connect-indikator +Name[de]=KDE-Connect-Anzeige +Name[el]=Εφαρμογή ένδειξης KDE Connect +Name[en_GB]=KDE Connect Indicator +Name[es]=Indicador de KDE Connect +Name[eu]=KDE Connect adierazlea +Name[fr]=Indicateur de KDE Connect +Name[gl]=Indicador de KDE Connect +Name[it]=Indicatore di KDE Connect +Name[nl]=KDE-Connect indicator +Name[nn]=KDE Connect-indikator +Name[pl]=Wskaźnik KDE Connect +Name[pt]=Indicador do KDE Connect +Name[ru]=Индикатор KDE Connect +Name[sr]=КДЕ‑конекцијин показатељ +Name[sr@ijekavian]=КДЕ‑конекцијин показатељ +Name[sr@ijekavianlatin]=KDE‑konekcijin pokazatelj +Name[sr@latin]=KDE‑konekcijin pokazatelj +Name[sv]=KDE-anslut indikator +Name[tr]=KDE Connect Göstergesi +Name[uk]=Індикатор KDE Connect +Name[x-test]=xxKDE Connect Indicatorxx +Name[zh_CN]=KDE Connect 指示器 +Comment=Display information about your devices +Comment[ar]=اعرض معلومات عن أجهزتك +Comment[ast]=Amuesa información tocante a los tos preseos +Comment[ca]=Mostra la informació dels vostres dispositius +Comment[ca@valencia]=Mostra la informació dels vostres dispositius +Comment[cs]=Zobrazit informace o vašich zařízeních +Comment[da]=Vis information om dine enheder +Comment[de]=Anzeige von Informationen über Ihre Geräte +Comment[el]=Προβολή πληροφοριών σχετικά με τις συσκευές σας +Comment[en_GB]=Display information about your devices +Comment[es]=Mostrar información sobre sus dispositivos +Comment[et]=Teabe kuvamine seadmete kohta +Comment[eu]=Bistaratu zure gailuei buruzko informazioa +Comment[fi]=Näyttää tietoa laitteistasi +Comment[fr]=Afficher les informations de vos périphériques +Comment[gl]=Mostrar información sobre os dispositivos +Comment[he]=הצג מידע עך ההתקנים שלך +Comment[hu]=Információk megjelenítése az eszközeiről +Comment[it]=Visualizza informazioni sui tuoi dispositivi +Comment[ko]=장치 정보 표시 +Comment[nl]=Toon informatie over uw apparaten +Comment[nn]=Vis informasjon om einingane dine +Comment[pl]=Wyświetl informacje o twoich urządzeniach +Comment[pt]=Mostrar informações sobre os seus dispositivos +Comment[pt_BR]=Mostra informações sobre seus dispositivos +Comment[ru]=Просмотр информации о мобильных устройствах +Comment[sk]=Zobraziť informácie o vašich zariadeniach +Comment[sr]=Приказује податке о вашем телефону +Comment[sr@ijekavian]=Приказује податке о вашем телефону +Comment[sr@ijekavianlatin]=Prikazuje podatke o vašem telefonu +Comment[sr@latin]=Prikazuje podatke o vašem telefonu +Comment[sv]=Visa information om enheter +Comment[tr]=Aygıtlarınız hakkında bilgi görüntüleyin +Comment[uk]=Показати дані щодо ваших пристроїв +Comment[x-test]=xxDisplay information about your devicesxx +Comment[zh_CN]=显示您设备的信息 +Comment[zh_TW]=顯示關於您的裝置資訊 +Exec=kdeconnect-indicator +Icon=kdeconnect +Type=Application +Terminal=false +Categories=Qt;KDE;Network; +NotShowIn=KDE; + diff --git a/interfaces/CMakeLists.txt b/interfaces/CMakeLists.txt index f44b605b..806ff36e 100644 --- a/interfaces/CMakeLists.txt +++ b/interfaces/CMakeLists.txt @@ -1,78 +1,81 @@ project(KDEConnectInterfaces) function(geninterface source_h output_h) set(xml_file ${CMAKE_CURRENT_BINARY_DIR}/${output_h}.xml) qt5_generate_dbus_interface( ${source_h} ${xml_file}) list(APPEND libkdeconnect_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${output_h}) set_source_files_properties(${xml_file} PROPERTIES NO_NAMESPACE true) - get_filename_component(basename ${output_h} NAME_WE) - qt5_add_dbus_interface(libkdeconnect_SRC ${xml_file} ${basename}) + qt5_add_dbus_interface(libkdeconnect_SRC ${xml_file} ${output_h}) set(libkdeconnect_SRC ${libkdeconnect_SRC} PARENT_SCOPE) set(libkdeconnect_HEADERS ${libkdeconnect_HEADERS} PARENT_SCOPE) endfunction() set(libkdeconnect_SRC dbusinterfaces.cpp devicesmodel.cpp notificationsmodel.cpp devicessortproxymodel.cpp # modeltest.cpp ) set(libkdeconnect_public_HEADERS KDEConnect/DevicesModel KDEConnect/NotificationsModel ) set(libkdeconnect_HEADERS devicesmodel.h notificationsmodel.h dbusinterfaces.h ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectinterfaces_export.h ) geninterface(${CMAKE_SOURCE_DIR}/core/daemon.h daemoninterface) geninterface(${CMAKE_SOURCE_DIR}/core/device.h deviceinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/battery/batterydbusinterface.h devicebatteryinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/sftp/sftpplugin.h devicesftpinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/notifications/notificationsdbusinterface.h devicenotificationsinterface) +geninterface(${CMAKE_SOURCE_DIR}/plugins/findmyphone/findmyphoneplugin.h devicefindmyphoneinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/notifications/notification.h notificationinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/mprisremote/mprisremoteplugin.h mprisremoteinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/remotecontrol/remotecontrolplugin.h remotecontrolinterface) geninterface(${CMAKE_SOURCE_DIR}/plugins/lockdevice/lockdeviceplugin.h lockdeviceinterface) +geninterface(${CMAKE_SOURCE_DIR}/plugins/remotecommands/remotecommandsplugin.h remotecommandsinterface) +geninterface(${CMAKE_SOURCE_DIR}/plugins/remotekeyboard/remotekeyboardplugin.h remotekeyboardinterface) + add_library(kdeconnectinterfaces SHARED ${libkdeconnect_SRC}) set_target_properties(kdeconnectinterfaces PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) generate_export_header(kdeconnectinterfaces EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectinterfaces_export.h BASE_NAME KDEConnectInterfaces) target_link_libraries(kdeconnectinterfaces LINK_PUBLIC Qt5::Gui Qt5::DBus LINK_PRIVATE KF5::ConfigCore KF5::I18n ) configure_file(KDEConnectConfig.cmake.in ${CMAKE_BINARY_DIR}/interfaces/KDEConnectConfig.cmake @ONLY) ecm_setup_version( "${KDECONNECT_VERSION_MAJOR}.${KDECONNECT_VERSION_MINOR}.${KDECONNECT_VERSION_PATCH}" VARIABLE_PREFIX KDECONNECTINTERFACES VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdeconnectinterfaces_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDEConnectConfigVersion.cmake" SOVERSION ${KDECONNECT_VERSION_MAJOR}) install(TARGETS kdeconnectinterfaces EXPORT kdeconnectLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) ## Don't install header files until API/ABI policy is defined # # install(FILES ${libkdeconnect_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kdeconnect COMPONENT Devel) # install(FILES ${libkdeconnect_public_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/KDEConnect COMPONENT Devel) # install(FILES ${CMAKE_BINARY_DIR}/interfaces/KDEConnectConfig.cmake # ${CMAKE_BINARY_DIR}/interfaces/KDEConnectConfigVersion.cmake # DESTINATION ${LIB_INSTALL_DIR}/cmake/KDEConnect) diff --git a/plugins/notifications/notification.cpp b/interfaces/dbushelpers.h similarity index 56% copy from plugins/notifications/notification.cpp copy to interfaces/dbushelpers.h index b44b5f85..10c62443 100644 --- a/plugins/notifications/notification.cpp +++ b/interfaces/dbushelpers.h @@ -1,46 +1,48 @@ -/** - * Copyright 2013 Albert Vaca +/* + * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "notification.h" +#ifndef DBUSHELPERS_H +#define DBUSHELPERS_H -#include +#include +#include +#include -Notification::Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent) - : QObject(parent) +template +Q_REQUIRED_RESULT T blockOnReply(QDBusPendingReply reply) { - mId = np.get("id"); - mAppName = np.get("appName"); - mTicker = np.get("ticker"); - mDismissable = np.get("isClearable"); - mIconPath = iconPath; -} - -Notification::~Notification() -{ - + reply.waitForFinished(); + if (reply.isError()) { + QTextStream(stderr) << i18n("error: ") << reply.error().message() << endl; + exit(1); + } + return reply.value(); } -void Notification::dismiss() +void blockOnReply(QDBusPendingReply reply) { - if (mDismissable) { - Q_EMIT dismissRequested(this); + reply.waitForFinished(); + if (reply.isError()) { + QTextStream(stderr) << i18n("error: ") << reply.error().message() << endl; + exit(1); } } +#endif diff --git a/interfaces/dbusinterfaces.cpp b/interfaces/dbusinterfaces.cpp index db3ce5a8..eb390db6 100644 --- a/interfaces/dbusinterfaces.cpp +++ b/interfaces/dbusinterfaces.cpp @@ -1,137 +1,168 @@ /** * 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 "dbusinterfaces.h" QString DaemonDbusInterface::activatedService() { - static const QString service = "org.kde.kdeconnect"; - QDBusConnection::sessionBus().interface()->startService(service); + static const QString service = QStringLiteral("org.kde.kdeconnect"); + auto reply = QDBusConnection::sessionBus().interface()->startService(service); + if (!reply.isValid()) { + qWarning() << "error activating kdeconnectd:" << QDBusConnection::sessionBus().interface()->lastError(); + } return service; } DaemonDbusInterface::DaemonDbusInterface(QObject* parent) - : OrgKdeKdeconnectDaemonInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect", QDBusConnection::sessionBus(), parent) + : OrgKdeKdeconnectDaemonInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect"), QDBusConnection::sessionBus(), parent) { - + connect(this, &OrgKdeKdeconnectDaemonInterface::pairingRequestsChanged, this, &DaemonDbusInterface::pairingRequestsChangedProxy); } DaemonDbusInterface::~DaemonDbusInterface() { } DeviceDbusInterface::DeviceDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/"+id, QDBusConnection::sessionBus(), parent) , m_id(id) { - connect(this, &OrgKdeKdeconnectDeviceInterface::pairingChanged, this, &DeviceDbusInterface::pairingChangedProxy); + connect(this, &OrgKdeKdeconnectDeviceInterface::trustedChanged, this, &DeviceDbusInterface::trustedChangedProxy); + connect(this, &OrgKdeKdeconnectDeviceInterface::reachableChanged, this, &DeviceDbusInterface::reachableChangedProxy); + connect(this, &OrgKdeKdeconnectDeviceInterface::nameChanged, this, &DeviceDbusInterface::nameChangedProxy); + connect(this, &OrgKdeKdeconnectDeviceInterface::hasPairingRequestsChanged, this, &DeviceDbusInterface::hasPairingRequestsChangedProxy); } DeviceDbusInterface::~DeviceDbusInterface() { } QString DeviceDbusInterface::id() const { return m_id; } -void DeviceDbusInterface::pluginCall(const QString &plugin, const QString &method) +void DeviceDbusInterface::pluginCall(const QString& plugin, const QString& method) { - QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/"+id()+'/'+plugin, "org.kde.kdeconnect.device."+plugin, method); + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+id()+'/'+plugin, "org.kde.kdeconnect.device."+plugin, method); QDBusConnection::sessionBus().asyncCall(msg); } DeviceBatteryDbusInterface::DeviceBatteryDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceBatteryInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/"+id, QDBusConnection::sessionBus(), parent) { } DeviceBatteryDbusInterface::~DeviceBatteryDbusInterface() { } DeviceNotificationsDbusInterface::DeviceNotificationsDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceNotificationsInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/"+id, QDBusConnection::sessionBus(), parent) { } DeviceNotificationsDbusInterface::~DeviceNotificationsDbusInterface() { } NotificationDbusInterface::NotificationDbusInterface(const QString& deviceId, const QString& notificationId, QObject* parent) : OrgKdeKdeconnectDeviceNotificationsNotificationInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/"+deviceId+"/notifications/"+notificationId, QDBusConnection::sessionBus(), parent) + , id(notificationId) { } NotificationDbusInterface::~NotificationDbusInterface() { } SftpDbusInterface::SftpDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceSftpInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + id + "/sftp", QDBusConnection::sessionBus(), parent) { } SftpDbusInterface::~SftpDbusInterface() { } MprisDbusInterface::MprisDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceMprisremoteInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + id + "/mprisremote", QDBusConnection::sessionBus(), parent) { connect(this, &OrgKdeKdeconnectDeviceMprisremoteInterface::propertiesChanged, this, &MprisDbusInterface::propertiesChangedProxy); } MprisDbusInterface::~MprisDbusInterface() { } RemoteControlDbusInterface::RemoteControlDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceRemotecontrolInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + id + "/remotecontrol", QDBusConnection::sessionBus(), parent) { } RemoteControlDbusInterface::~RemoteControlDbusInterface() { } LockDeviceDbusInterface::LockDeviceDbusInterface(const QString& id, QObject* parent) : OrgKdeKdeconnectDeviceLockdeviceInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + id + "/lockdevice", QDBusConnection::sessionBus(), parent) { connect(this, &OrgKdeKdeconnectDeviceLockdeviceInterface::lockedChanged, this, &LockDeviceDbusInterface::lockedChangedProxy); Q_ASSERT(isValid()); } LockDeviceDbusInterface::~LockDeviceDbusInterface() { } +FindMyPhoneDeviceDbusInterface::FindMyPhoneDeviceDbusInterface(const QString& deviceId, QObject* parent): + OrgKdeKdeconnectDeviceFindmyphoneInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/findmyphone", QDBusConnection::sessionBus(), parent) +{ +} + +FindMyPhoneDeviceDbusInterface::~FindMyPhoneDeviceDbusInterface() +{ +} + +RemoteCommandsDbusInterface::RemoteCommandsDbusInterface(const QString& deviceId, QObject* parent): + OrgKdeKdeconnectDeviceRemotecommandsInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/remotecommands", QDBusConnection::sessionBus(), parent) +{ +} + +RemoteCommandsDbusInterface::~RemoteCommandsDbusInterface() = default; + +RemoteKeyboardDbusInterface::RemoteKeyboardDbusInterface(const QString& deviceId, QObject* parent): + OrgKdeKdeconnectDeviceRemotekeyboardInterface(DaemonDbusInterface::activatedService(), "/modules/kdeconnect/devices/" + deviceId + "/remotekeyboard", QDBusConnection::sessionBus(), parent) +{ + connect(this, &OrgKdeKdeconnectDeviceRemotekeyboardInterface::remoteStateChanged, this, &RemoteKeyboardDbusInterface::remoteStateChanged); +} + +RemoteKeyboardDbusInterface::~RemoteKeyboardDbusInterface() = default; + #include "dbusinterfaces.moc" diff --git a/interfaces/dbusinterfaces.h b/interfaces/dbusinterfaces.h index 2f51599f..4b2029d0 100644 --- a/interfaces/dbusinterfaces.h +++ b/interfaces/dbusinterfaces.h @@ -1,151 +1,211 @@ /** * 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 DBUSINTERFACES_H #define DBUSINTERFACES_H #include "interfaces/kdeconnectinterfaces_export.h" #include "interfaces/daemoninterface.h" #include "interfaces/deviceinterface.h" #include "interfaces/devicebatteryinterface.h" #include "interfaces/devicesftpinterface.h" +#include "interfaces/devicefindmyphoneinterface.h" #include "interfaces/devicenotificationsinterface.h" #include "interfaces/notificationinterface.h" #include "interfaces/mprisremoteinterface.h" #include "interfaces/remotecontrolinterface.h" #include "interfaces/lockdeviceinterface.h" +#include "interfaces/remotecommandsinterface.h" +#include "interfaces/remotekeyboardinterface.h" /** * Using these "proxy" classes just in case we need to rename the * interface, so we can change the class name in a single place. */ class KDECONNECTINTERFACES_EXPORT DaemonDbusInterface : public OrgKdeKdeconnectDaemonInterface { Q_OBJECT public: - DaemonDbusInterface(QObject* parent = 0); - virtual ~DaemonDbusInterface(); + explicit DaemonDbusInterface(QObject* parent = nullptr); + ~DaemonDbusInterface() override; static QString activatedService(); + +Q_SIGNALS: + void deviceAdded(const QString& id); + void pairingRequestsChangedProxy(); }; class KDECONNECTINTERFACES_EXPORT DeviceDbusInterface : public OrgKdeKdeconnectDeviceInterface { Q_OBJECT // TODO: Workaround because OrgKdeKdeconnectDeviceInterface is not generating // the signals for the properties - Q_PROPERTY(bool isPaired READ isPaired NOTIFY pairingChangedProxy) + Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableChangedProxy) + Q_PROPERTY(bool isTrusted READ isTrusted NOTIFY trustedChangedProxy) + Q_PROPERTY(QString name READ name NOTIFY nameChangedProxy) + Q_PROPERTY(bool hasPairingRequests READ hasPairingRequests NOTIFY hasPairingRequestsChangedProxy) public: - DeviceDbusInterface(const QString& deviceId, QObject* parent = 0); - virtual ~DeviceDbusInterface(); + explicit DeviceDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~DeviceDbusInterface() override; Q_SCRIPTABLE QString id() const; - Q_SCRIPTABLE void pluginCall(const QString &plugin, const QString &method); + Q_SCRIPTABLE void pluginCall(const QString& plugin, const QString& method); Q_SIGNALS: - void pairingChangedProxy(bool paired); + void nameChangedProxy(const QString& name); + void trustedChangedProxy(bool paired); + void reachableChangedProxy(bool reachable); + void hasPairingRequestsChangedProxy(bool); private: const QString m_id; }; class KDECONNECTINTERFACES_EXPORT DeviceBatteryDbusInterface : public OrgKdeKdeconnectDeviceBatteryInterface { Q_OBJECT public: - DeviceBatteryDbusInterface(const QString& deviceId, QObject* parent = 0); - virtual ~DeviceBatteryDbusInterface(); + explicit DeviceBatteryDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~DeviceBatteryDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT DeviceNotificationsDbusInterface : public OrgKdeKdeconnectDeviceNotificationsInterface { Q_OBJECT public: - DeviceNotificationsDbusInterface(const QString& deviceId, QObject* parent = 0); - virtual ~DeviceNotificationsDbusInterface(); + explicit DeviceNotificationsDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~DeviceNotificationsDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT NotificationDbusInterface : public OrgKdeKdeconnectDeviceNotificationsNotificationInterface { Q_OBJECT public: - NotificationDbusInterface(const QString& deviceId, const QString& notificationId, QObject* parent = 0); - virtual ~NotificationDbusInterface(); + NotificationDbusInterface(const QString& deviceId, const QString& notificationId, QObject* parent = nullptr); + ~NotificationDbusInterface() override; + + QString notificationId() { return id; } +private: + const QString id; }; + class KDECONNECTINTERFACES_EXPORT SftpDbusInterface : public OrgKdeKdeconnectDeviceSftpInterface { Q_OBJECT public: - SftpDbusInterface(const QString& deviceId, QObject* parent = 0); - virtual ~SftpDbusInterface(); + explicit SftpDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~SftpDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT MprisDbusInterface : public OrgKdeKdeconnectDeviceMprisremoteInterface { Q_OBJECT // TODO: Workaround because qdbusxml2cpp is not generating // the signals for the properties Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY propertiesChangedProxy) Q_PROPERTY(int length READ length NOTIFY propertiesChangedProxy) Q_PROPERTY(QString nowPlaying READ nowPlaying NOTIFY propertiesChangedProxy) Q_PROPERTY(QStringList playerList READ playerList NOTIFY propertiesChangedProxy) Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY propertiesChangedProxy) Q_PROPERTY(int position READ position WRITE setPosition NOTIFY propertiesChangedProxy) public: - MprisDbusInterface(const QString& deviceId, QObject* parent = 0); - virtual ~MprisDbusInterface(); + explicit MprisDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~MprisDbusInterface() override; Q_SIGNALS: void propertiesChangedProxy(); }; class KDECONNECTINTERFACES_EXPORT RemoteControlDbusInterface : public OrgKdeKdeconnectDeviceRemotecontrolInterface { Q_OBJECT public: - RemoteControlDbusInterface(const QString& deviceId, QObject* parent = 0); + explicit RemoteControlDbusInterface(const QString& deviceId, QObject* parent = nullptr); ~RemoteControlDbusInterface() override; }; class KDECONNECTINTERFACES_EXPORT LockDeviceDbusInterface : public OrgKdeKdeconnectDeviceLockdeviceInterface { Q_OBJECT Q_PROPERTY(bool isLocked READ isLocked WRITE setIsLocked NOTIFY lockedChangedProxy) public: - LockDeviceDbusInterface(const QString& deviceId, QObject* parent = 0); - virtual ~LockDeviceDbusInterface(); + explicit LockDeviceDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~LockDeviceDbusInterface() override; Q_SIGNALS: void lockedChangedProxy(bool isLocked); }; +class KDECONNECTINTERFACES_EXPORT FindMyPhoneDeviceDbusInterface + : public OrgKdeKdeconnectDeviceFindmyphoneInterface +{ + Q_OBJECT +public: + explicit FindMyPhoneDeviceDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~FindMyPhoneDeviceDbusInterface() override; +}; + +class KDECONNECTINTERFACES_EXPORT RemoteCommandsDbusInterface + : public OrgKdeKdeconnectDeviceRemotecommandsInterface +{ + Q_OBJECT +public: + explicit RemoteCommandsDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~RemoteCommandsDbusInterface() override; +}; + +class KDECONNECTINTERFACES_EXPORT RemoteKeyboardDbusInterface + : public OrgKdeKdeconnectDeviceRemotekeyboardInterface +{ + Q_OBJECT + Q_PROPERTY(bool remoteState READ remoteState NOTIFY remoteStateChanged) +public: + explicit RemoteKeyboardDbusInterface(const QString& deviceId, QObject* parent = nullptr); + ~RemoteKeyboardDbusInterface() override; +Q_SIGNALS: + void remoteStateChanged(bool state); +}; + +template +static void setWhenAvailable(const QDBusPendingReply& pending, W func, QObject* parent) +{ + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pending, parent); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, + parent, [func](QDBusPendingCallWatcher* watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + func(reply.value()); + }); +} + #endif diff --git a/interfaces/devicesmodel.cpp b/interfaces/devicesmodel.cpp index 11b56dae..baafd3fe 100644 --- a/interfaces/devicesmodel.cpp +++ b/interfaces/devicesmodel.cpp @@ -1,284 +1,314 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "devicesmodel.h" #include "interfaces_debug.h" #include #include #include #include #include #include #include "dbusinterfaces.h" // #include "modeltest.h" Q_LOGGING_CATEGORY(KDECONNECT_INTERFACES, "kdeconnect.interfaces"); -DevicesModel::DevicesModel(QObject *parent) +static QString createId() { return QCoreApplication::instance()->applicationName()+QString::number(QCoreApplication::applicationPid()); } + +Q_GLOBAL_STATIC_WITH_ARGS(QString, s_keyId, (createId())); + +DevicesModel::DevicesModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(new DaemonDbusInterface(this)) , m_displayFilter(StatusFilterFlag::NoFilter) { //new ModelTest(this, this); - connect(this, SIGNAL(rowsRemoved(QModelIndex, int, int)), - this, SIGNAL(rowsChanged())); - connect(this, SIGNAL(rowsInserted(QModelIndex, int, int)), - this, SIGNAL(rowsChanged())); + connect(this, &QAbstractItemModel::rowsRemoved, + this, &DevicesModel::rowsChanged); + connect(this, &QAbstractItemModel::rowsInserted, + this, &DevicesModel::rowsChanged); connect(m_dbusInterface, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); - connect(m_dbusInterface, SIGNAL(deviceVisibilityChanged(QString,bool)), - this, SLOT(deviceUpdated(QString,bool))); - connect(m_dbusInterface, SIGNAL(deviceRemoved(QString)), - this, SLOT(deviceRemoved(QString))); + connect(m_dbusInterface, &OrgKdeKdeconnectDaemonInterface::deviceVisibilityChanged, + this, &DevicesModel::deviceUpdated); + connect(m_dbusInterface, &OrgKdeKdeconnectDaemonInterface::deviceRemoved, + this, &DevicesModel::deviceRemoved); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &DevicesModel::refreshDeviceList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &DevicesModel::clearDevices); - refreshDeviceList(); + //refresh the view, acquireDiscoveryMode if necessary + setDisplayFilter(NoFilter); } QHash< int, QByteArray > DevicesModel::roleNames() const { QHash names = QAbstractItemModel::roleNames(); names.insert(IdModelRole, "deviceId"); names.insert(IconNameRole, "iconName"); names.insert(DeviceRole, "device"); names.insert(StatusModelRole, "status"); return names; } DevicesModel::~DevicesModel() { + m_dbusInterface->releaseDiscoveryMode(*s_keyId); } int DevicesModel::rowForDevice(const QString& id) const { for (int i = 0, c=m_deviceList.size(); iid() == id) { return i; } } return -1; } void DevicesModel::deviceAdded(const QString& id) { if (rowForDevice(id) >= 0) { Q_ASSERT_X(false, "deviceAdded", "Trying to add a device twice"); return; } DeviceDbusInterface* dev = new DeviceDbusInterface(id, this); Q_ASSERT(dev->isValid()); - bool onlyPaired = (m_displayFilter & StatusFilterFlag::Paired); - bool onlyReachable = (m_displayFilter & StatusFilterFlag::Reachable); - - if ((onlyReachable && !dev->isReachable()) || (onlyPaired && !dev->isPaired())) { + if (! passesFilter(dev)) { delete dev; return; } beginInsertRows(QModelIndex(), m_deviceList.size(), m_deviceList.size()); appendDevice(dev); endInsertRows(); } void DevicesModel::deviceRemoved(const QString& id) { int row = rowForDevice(id); if (row>=0) { beginRemoveRows(QModelIndex(), row, row); delete m_deviceList.takeAt(row); endRemoveRows(); } } void DevicesModel::deviceUpdated(const QString& id, bool isVisible) { + Q_UNUSED(isVisible); int row = rowForDevice(id); - if (row < 0 && isVisible) { + if (row < 0) { // FIXME: when m_dbusInterface is not valid refreshDeviceList() does // nothing and we can miss some devices. // Someone can reproduce this problem by restarting kdeconnectd while // kdeconnect's plasmoid is still running. - qCDebug(KDECONNECT_INTERFACES) << "Adding missing device" << id; + // Another reason for this branch is that we removed the device previously + // because of the filter settings. + qCDebug(KDECONNECT_INTERFACES) << "Adding missing or previously removed device" << id; deviceAdded(id); - row = rowForDevice(id); - } - - if (row >= 0) { - const QModelIndex idx = index(row); - Q_EMIT dataChanged(idx, idx); + } else { + DeviceDbusInterface* dev = getDevice(row); + if (! passesFilter(dev)) { + beginRemoveRows(QModelIndex(), row, row); + delete m_deviceList.takeAt(row); + endRemoveRows(); + qCDebug(KDECONNECT_INTERFACES) << "Removed changed device " << id; + } else { + const QModelIndex idx = index(row); + Q_EMIT dataChanged(idx, idx); + } } } int DevicesModel::displayFilter() const { return m_displayFilter; } void DevicesModel::setDisplayFilter(int flags) { m_displayFilter = (StatusFilterFlag)flags; + + const bool reachableNeeded = (m_displayFilter & StatusFilterFlag::Reachable); + if (reachableNeeded) + m_dbusInterface->acquireDiscoveryMode(*s_keyId); + else + m_dbusInterface->releaseDiscoveryMode(*s_keyId); + refreshDeviceList(); } void DevicesModel::refreshDeviceList() { if (!m_dbusInterface->isValid()) { clearDevices(); qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } bool onlyPaired = (m_displayFilter & StatusFilterFlag::Paired); bool onlyReachable = (m_displayFilter & StatusFilterFlag::Reachable); QDBusPendingReply pendingDeviceIds = m_dbusInterface->devices(onlyReachable, onlyPaired); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingDeviceIds, this); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingDeviceIds, this); - QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - this, SLOT(receivedDeviceList(QDBusPendingCallWatcher*))); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, + this, &DevicesModel::receivedDeviceList); } void DevicesModel::receivedDeviceList(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearDevices(); QDBusPendingReply pendingDeviceIds = *watcher; if (pendingDeviceIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << "error while refreshing device list" << pendingDeviceIds.error().message(); return; } Q_ASSERT(m_deviceList.isEmpty()); const QStringList deviceIds = pendingDeviceIds.value(); if (deviceIds.isEmpty()) return; beginInsertRows(QModelIndex(), 0, deviceIds.count()-1); - Q_FOREACH(const QString& id, deviceIds) { + for (const QString& id : deviceIds) { appendDevice(new DeviceDbusInterface(id, this)); } endInsertRows(); } void DevicesModel::appendDevice(DeviceDbusInterface* dev) { m_deviceList.append(dev); - connect(dev, SIGNAL(nameChanged(QString)), SLOT(nameChanged(QString))); + connect(dev, &OrgKdeKdeconnectDeviceInterface::nameChanged, this, &DevicesModel::nameChanged); } void DevicesModel::nameChanged(const QString& newName) { Q_UNUSED(newName); DeviceDbusInterface* device = static_cast(sender()); Q_ASSERT(rowForDevice(device->id()) >= 0); deviceUpdated(device->id(), true); } void DevicesModel::clearDevices() { if (!m_deviceList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_deviceList.size() - 1); qDeleteAll(m_deviceList); m_deviceList.clear(); endRemoveRows(); } } QVariant DevicesModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_deviceList.size()) { return QVariant(); } Q_ASSERT(m_dbusInterface->isValid()); DeviceDbusInterface* device = m_deviceList[index.row()]; Q_ASSERT(device->isValid()); //This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { + case Qt::SizeHintRole: + return QSize(0, 32); case IconModelRole: { QString icon = data(index, IconNameRole).toString(); return QIcon::fromTheme(icon); } case IdModelRole: return device->id(); case NameModelRole: return device->name(); case Qt::ToolTipRole: { - bool paired = device->isPaired(); + bool trusted = device->isTrusted(); bool reachable = device->isReachable(); - QString status = reachable? (paired? i18n("Device trusted and connected") : i18n("Device not trusted")) : i18n("Device disconnected"); + QString status = reachable? (trusted? i18n("Device trusted and connected") : i18n("Device not trusted")) : i18n("Device disconnected"); return status; } case StatusModelRole: { int status = StatusFilterFlag::NoFilter; if (device->isReachable()) { status |= StatusFilterFlag::Reachable; - if (device->isPaired()) status |= StatusFilterFlag::Paired; + } + if (device->isTrusted()) { + status |= StatusFilterFlag::Paired; } return status; } case IconNameRole: return device->statusIconName(); case DeviceRole: return QVariant::fromValue(device); default: return QVariant(); } } DeviceDbusInterface* DevicesModel::getDevice(int row) const { if (row < 0 || row >= m_deviceList.size()) { - return NULL; + return nullptr; } return m_deviceList[row]; } int DevicesModel::rowCount(const QModelIndex& parent) const { if(parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_deviceList.size(); } + +bool DevicesModel::passesFilter(DeviceDbusInterface* dev) const +{ + bool onlyPaired = (m_displayFilter & StatusFilterFlag::Paired); + bool onlyReachable = (m_displayFilter & StatusFilterFlag::Reachable); + + return !((onlyReachable && !dev->isReachable()) || (onlyPaired && !dev->isTrusted())); +} diff --git a/interfaces/devicesmodel.h b/interfaces/devicesmodel.h index e96af812..f208136f 100644 --- a/interfaces/devicesmodel.h +++ b/interfaces/devicesmodel.h @@ -1,95 +1,99 @@ /** * 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 DEVICESMODEL_H #define DEVICESMODEL_H #include #include #include #include "interfaces/kdeconnectinterfaces_export.h" class QDBusPendingCallWatcher; class DaemonDbusInterface; class DeviceDbusInterface; class KDECONNECTINTERFACES_EXPORT DevicesModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int displayFilter READ displayFilter WRITE setDisplayFilter) Q_PROPERTY(int count READ rowCount NOTIFY rowsChanged) public: enum ModelRoles { NameModelRole = Qt::DisplayRole, IconModelRole = Qt::DecorationRole, StatusModelRole = Qt::InitialSortOrderRole, IdModelRole = Qt::UserRole, IconNameRole, DeviceRole }; - Q_ENUMS(ModelRoles); + Q_ENUM(ModelRoles); + + // A device is always paired or reachable or both + // You can combine the Paired and Reachable flags enum StatusFilterFlag { NoFilter = 0x00, - Paired = 0x01, - Reachable = 0x02 + Paired = 0x01, // show device only if it's paired + Reachable = 0x02 // show device only if it's reachable }; Q_DECLARE_FLAGS(StatusFilterFlags, StatusFilterFlag) Q_FLAGS(StatusFilterFlags) - Q_ENUMS(StatusFilterFlag) + Q_ENUM(StatusFilterFlag) - DevicesModel(QObject *parent = 0); - virtual ~DevicesModel(); + explicit DevicesModel(QObject* parent = nullptr); + ~DevicesModel() override; void setDisplayFilter(int flags); int displayFilter() const; - virtual QVariant data(const QModelIndex& index, int role) const; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; Q_SCRIPTABLE DeviceDbusInterface* getDevice(int row) const; - virtual QHash roleNames() const; + QHash roleNames() const override; private Q_SLOTS: void deviceAdded(const QString& id); void deviceRemoved(const QString& id); void deviceUpdated(const QString& id, bool isVisible); void refreshDeviceList(); void receivedDeviceList(QDBusPendingCallWatcher* watcher); void nameChanged(const QString& newName); Q_SIGNALS: void rowsChanged(); private: int rowForDevice(const QString& id) const; void clearDevices(); void appendDevice(DeviceDbusInterface* dev); + bool passesFilter(DeviceDbusInterface* dev) const; DaemonDbusInterface* m_dbusInterface; QVector m_deviceList; StatusFilterFlag m_displayFilter; }; //Q_DECLARE_OPERATORS_FOR_FLAGS(DevicesModel::StatusFilterFlag) #endif // DEVICESMODEL_H diff --git a/interfaces/devicessortproxymodel.cpp b/interfaces/devicessortproxymodel.cpp index 4588dd68..688ae9ca 100644 --- a/interfaces/devicessortproxymodel.cpp +++ b/interfaces/devicessortproxymodel.cpp @@ -1,74 +1,74 @@ /** * 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 "devicessortproxymodel.h" #include #include DevicesSortProxyModel::DevicesSortProxyModel(DevicesModel* devicesModel) : QSortFilterProxyModel(devicesModel) { setSourceModel(devicesModel); } -void DevicesSortProxyModel::setSourceModel(QAbstractItemModel *devicesModel) +void DevicesSortProxyModel::setSourceModel(QAbstractItemModel* devicesModel) { QSortFilterProxyModel::setSourceModel(devicesModel); if (devicesModel) { setSortRole(DevicesModel::StatusModelRole); - connect(devicesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); + connect(devicesModel, &QAbstractItemModel::dataChanged, this, &DevicesSortProxyModel::sourceDataChanged); } sort(0); } -void DevicesSortProxyModel::sourceDataChanged(QModelIndex , QModelIndex ) +void DevicesSortProxyModel::sourceDataChanged() { sort(0); } bool DevicesSortProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { QAbstractItemModel* model = sourceModel(); Q_ASSERT(qobject_cast(model)); //Show connected devices first int statusLeft = model->data(left, DevicesModel::StatusModelRole).toInt(); int statusRight = model->data(right, DevicesModel::StatusModelRole).toInt(); if (statusLeft != statusRight) { return statusLeft > statusRight; } //Fallback to alphabetical order QString nameLeft = model->data(left, DevicesModel::NameModelRole).toString(); QString nameRight = model->data(right, DevicesModel::NameModelRole).toString(); return nameLeft > nameRight; } bool DevicesSortProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { Q_UNUSED(source_row); Q_UNUSED(source_parent); //Possible to-do: Implement filter return true; } diff --git a/interfaces/devicessortproxymodel.h b/interfaces/devicessortproxymodel.h index 54b7a0bf..c5ccaf55 100644 --- a/interfaces/devicessortproxymodel.h +++ b/interfaces/devicessortproxymodel.h @@ -1,42 +1,42 @@ /** * 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 DEVICESSORTPROXYMODEL_H #define DEVICESSORTPROXYMODEL_H #include #include "interfaces/kdeconnectinterfaces_export.h" class DevicesModel; class KDECONNECTINTERFACES_EXPORT DevicesSortProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - DevicesSortProxyModel(DevicesModel* devicesModel = Q_NULLPTR); - virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const; - virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; - virtual void setSourceModel(QAbstractItemModel *sourceModel); + explicit DevicesSortProxyModel(DevicesModel* devicesModel = Q_NULLPTR); + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + void setSourceModel(QAbstractItemModel* sourceModel) override; public Q_SLOTS: - void sourceDataChanged(QModelIndex,QModelIndex); + void sourceDataChanged(); }; #endif // DEVICESSORTPROXYMODEL_H diff --git a/interfaces/interfaces_debug.h b/interfaces/interfaces_debug.h index 50832397..95e05f1d 100644 --- a/interfaces/interfaces_debug.h +++ b/interfaces/interfaces_debug.h @@ -1,28 +1,28 @@ /** * 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 . */ #ifndef KDECONNECT_INTERFACE_DEBUG_H #define KDECONNECT_INTERFACE_DEBUG_H #include Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_INTERFACES) -#endif //KDECONNECT_INTERFACE_DEBUG_H \ No newline at end of file +#endif //KDECONNECT_INTERFACE_DEBUG_H diff --git a/interfaces/modeltest.cpp b/interfaces/modeltest.cpp index 9259487c..c3c86aeb 100644 --- a/interfaces/modeltest.cpp +++ b/interfaces/modeltest.cpp @@ -1,539 +1,539 @@ /**************************************************************************** ** ** Copyright (C) 2007 Trolltech ASA. All rights reserved. ** ** This file is part of the Qt Concurrent project on Trolltech Labs. ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file. Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.trolltech.com/products/qt/opensource.html ** ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http://www.trolltech.com/products/qt/licensing.html or contact the ** sales department at sales@trolltech.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ -#include - #include "modeltest.h" +#include + Q_DECLARE_METATYPE(QModelIndex) /*! Connect to all of the models signals. Whenever anything happens recheck everything. */ -ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false) +ModelTest::ModelTest(QAbstractItemModel *_model, QObject* parent) : QObject(parent), model(_model), fetchingMore(false) { Q_ASSERT(model); - connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), + connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), + connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)), + connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), + connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(runAllTests())); - connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), + connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(runAllTests())); + connect(model, SIGNAL(layoutChanged()), this, SLOT(runAllTests())); + connect(model, SIGNAL(modelReset()), this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(runAllTests())); // Special checks for inserting/removing connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); connect(model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged())); - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), - this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), - this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), - this, SLOT(rowsInserted(const QModelIndex &, int, int))); - connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), - this, SLOT(rowsRemoved(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(rowsRemoved(QModelIndex,int,int))); runAllTests(); } void ModelTest::runAllTests() { if (fetchingMore) return; nonDestructiveBasicTest(); rowCount(); columnCount(); hasIndex(); index(); parent(); data(); } /*! nonDestructiveBasicTest tries to call a number of the basic functions (not all) to make sure the model doesn't outright segfault, testing the functions that makes sense. */ void ModelTest::nonDestructiveBasicTest() { Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex()); model->canFetchMore(QModelIndex()); Q_ASSERT(model->columnCount(QModelIndex()) >= 0); Q_ASSERT(model->data(QModelIndex()) == QVariant()); fetchingMore = true; model->fetchMore(QModelIndex()); fetchingMore = false; Qt::ItemFlags flags = model->flags(QModelIndex()); Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0); model->hasChildren(QModelIndex()); model->hasIndex(0, 0); model->headerData(0, Qt::Horizontal); model->index(0, 0); Q_ASSERT(model->index(-1, -1) == QModelIndex()); model->itemData(QModelIndex()); QVariant cache; model->match(QModelIndex(), -1, cache); model->mimeTypes(); Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); Q_ASSERT(model->rowCount() >= 0); QVariant variant; model->setData(QModelIndex(), variant, -1); model->setHeaderData(-1, Qt::Horizontal, QVariant()); model->setHeaderData(0, Qt::Horizontal, QVariant()); model->setHeaderData(999999, Qt::Horizontal, QVariant()); QMap roles; model->sibling(0, 0, QModelIndex()); model->span(QModelIndex()); model->supportedDropActions(); } /*! Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() Models that are dynamically populated are not as fully tested here. */ void ModelTest::rowCount() { // check top row QModelIndex topIndex = model->index(0, 0, QModelIndex()); int rows = model->rowCount(topIndex); Q_ASSERT(rows >= 0); if (rows > 0) Q_ASSERT(model->hasChildren(topIndex) == true); QModelIndex secondLevelIndex = model->index(0, 0, topIndex); if (secondLevelIndex.isValid()) { // not the top level // check a row count where parent is valid rows = model->rowCount(secondLevelIndex); Q_ASSERT(rows >= 0); if (rows > 0) Q_ASSERT(model->hasChildren(secondLevelIndex) == true); } // The models rowCount() is tested more extensively in checkChildren(), // but this catches the big mistakes } /*! Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() */ void ModelTest::columnCount() { // check top row QModelIndex topIndex = model->index(0, 0, QModelIndex()); Q_ASSERT(model->columnCount(topIndex) >= 0); // check a column count where parent is valid QModelIndex childIndex = model->index(0, 0, topIndex); if (childIndex.isValid()) Q_ASSERT(model->columnCount(childIndex) >= 0); // columnCount() is tested more extensively in checkChildren(), // but this catches the big mistakes } /*! Tests model's implementation of QAbstractItemModel::hasIndex() */ void ModelTest::hasIndex() { // Make sure that invalid values returns an invalid index Q_ASSERT(model->hasIndex(-2, -2) == false); Q_ASSERT(model->hasIndex(-2, 0) == false); Q_ASSERT(model->hasIndex(0, -2) == false); int rows = model->rowCount(); int columns = model->columnCount(); // check out of bounds Q_ASSERT(model->hasIndex(rows, columns) == false); Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false); if (rows > 0) Q_ASSERT(model->hasIndex(0, 0) == true); // hasIndex() is tested more extensively in checkChildren(), // but this catches the big mistakes } /*! Tests model's implementation of QAbstractItemModel::index() */ void ModelTest::index() { // Make sure that invalid values returns an invalid index Q_ASSERT(model->index(-2, -2) == QModelIndex()); Q_ASSERT(model->index(-2, 0) == QModelIndex()); Q_ASSERT(model->index(0, -2) == QModelIndex()); int rows = model->rowCount(); int columns = model->columnCount(); if (rows == 0) return; // Catch off by one errors Q_ASSERT(model->index(rows, columns) == QModelIndex()); Q_ASSERT(model->index(0, 0).isValid() == true); - // Make sure that the same index is *always* returned + // Make sure that the same index is* always* returned QModelIndex a = model->index(0, 0); QModelIndex b = model->index(0, 0); Q_ASSERT(a == b); // index() is tested more extensively in checkChildren(), // but this catches the big mistakes } /*! Tests model's implementation of QAbstractItemModel::parent() */ void ModelTest::parent() { // Make sure the model wont crash and will return an invalid QModelIndex // when asked for the parent of an invalid index. Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); if (model->rowCount() == 0) return; // Column 0 | Column 1 | // QModelIndex() | | // \- topIndex | topIndex1 | // \- childIndex | childIndex1 | // Common error test #1, make sure that a top level index has a parent // that is a invalid QModelIndex. QModelIndex topIndex = model->index(0, 0, QModelIndex()); Q_ASSERT(model->parent(topIndex) == QModelIndex()); // Common error test #2, make sure that a second level index has a parent // that is the first level index. if (model->rowCount(topIndex) > 0) { QModelIndex childIndex = model->index(0, 0, topIndex); Q_ASSERT(model->parent(childIndex) == topIndex); } // Common error test #3, the second column should NOT have the same children // as the first column in a row. // Usually the second column shouldn't have children. QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); if (model->rowCount(topIndex1) > 0) { QModelIndex childIndex = model->index(0, 0, topIndex); QModelIndex childIndex1 = model->index(0, 0, topIndex1); Q_ASSERT(childIndex != childIndex1); } // Full test, walk n levels deep through the model making sure that all // parent's children correctly specify their parent. checkChildren(QModelIndex()); } /*! Called from the parent() test. A model that returns an index of parent X should also return X when asking for the parent of the index. This recursive function does pretty extensive testing on the whole model in an effort to catch edge cases. This function assumes that rowCount(), columnCount() and index() already work. If they have a bug it will point it out, but the above tests should have already found the basic bugs because it is easier to figure out the problem in those tests then this one. */ void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) { // First just try walking back up the tree. QModelIndex p = parent; while (p.isValid()) p = p.parent(); // For models that are dynamically populated if (model->canFetchMore(parent)) { fetchingMore = true; model->fetchMore(parent); fetchingMore = false; } int rows = model->rowCount(parent); int columns = model->columnCount(parent); if (rows > 0) Q_ASSERT(model->hasChildren(parent)); // Some further testing against rows(), columns(), and hasChildren() Q_ASSERT(rows >= 0); Q_ASSERT(columns >= 0); if (rows > 0) Q_ASSERT(model->hasChildren(parent) == true); //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows // << "columns:" << columns << "parent column:" << parent.column(); Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false); for (int r = 0; r < rows; ++r) { if (model->canFetchMore(parent)) { fetchingMore = true; model->fetchMore(parent); fetchingMore = false; } Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false); for (int c = 0; c < columns; ++c) { Q_ASSERT(model->hasIndex(r, c, parent) == true); QModelIndex index = model->index(r, c, parent); // rowCount() and columnCount() said that it existed... Q_ASSERT(index.isValid() == true); // index() should always return the same index when called twice in a row QModelIndex modifiedIndex = model->index(r, c, parent); Q_ASSERT(index == modifiedIndex); // Make sure we get the same index if we request it twice in a row QModelIndex a = model->index(r, c, parent); QModelIndex b = model->index(r, c, parent); Q_ASSERT(a == b); // Some basic checking on the index that is returned Q_ASSERT(index.model() == model); Q_ASSERT(index.row() == r); Q_ASSERT(index.column() == c); // While you can technically return a QVariant usually this is a sign // of an bug in data() Disable if this really is ok in your model. //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true); // If the next test fails here is some somewhat useful debug you play with. /* if (model->parent(index) != parent) { qDebug() << r << c << currentDepth << model->data(index).toString() << model->data(parent).toString(); qDebug() << index << parent << model->parent(index); // And a view that you can even use to show the model. //QTreeView view; //view.setModel(model); //view.show(); }*/ // Check that we can get back our real parent. QModelIndex p = model->parent(index); //qDebug() << "child:" << index; //qDebug() << p; //qDebug() << parent; Q_ASSERT(p == parent); // recursively go down the children if (model->hasChildren(index) && currentDepth < 10 ) { //qDebug() << r << c << "has children" << model->rowCount(index); checkChildren(index, ++currentDepth); }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ // make sure that after testing the children that the index doesn't change. QModelIndex newerIndex = model->index(r, c, parent); Q_ASSERT(index == newerIndex); } } } /*! Tests model's implementation of QAbstractItemModel::data() */ void ModelTest::data() { // Invalid index should return an invalid qvariant Q_ASSERT(!model->data(QModelIndex()).isValid()); if (model->rowCount() == 0) return; // A valid index should have a valid QVariant data Q_ASSERT(model->index(0, 0).isValid()); // shouldn't be able to set data on an invalid index Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false); // General Purpose roles that should return a QString QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); if (variant.isValid()) { Q_ASSERT(variant.canConvert()); } variant = model->data(model->index(0, 0), Qt::StatusTipRole); if (variant.isValid()) { Q_ASSERT(variant.canConvert()); } variant = model->data(model->index(0, 0), Qt::WhatsThisRole); if (variant.isValid()) { Q_ASSERT(variant.canConvert()); } // General Purpose roles that should return a QSize variant = model->data(model->index(0, 0), Qt::SizeHintRole); if (variant.isValid()) { Q_ASSERT(variant.canConvert()); } // General Purpose roles that should return a QFont QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); if (fontVariant.isValid()) { Q_ASSERT(fontVariant.canConvert()); } // Check that the alignment is one we know about QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); if (textAlignmentVariant.isValid()) { int alignment = textAlignmentVariant.toInt(); Q_ASSERT(alignment == Qt::AlignLeft || alignment == Qt::AlignRight || alignment == Qt::AlignHCenter || alignment == Qt::AlignJustify || alignment == Qt::AlignTop || alignment == Qt::AlignBottom || alignment == Qt::AlignVCenter || alignment == Qt::AlignCenter || alignment == Qt::AlignAbsolute || alignment == Qt::AlignLeading || alignment == Qt::AlignTrailing); } // General Purpose roles that should return a QColor QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole); if (colorVariant.isValid()) { Q_ASSERT(colorVariant.canConvert()); } colorVariant = model->data(model->index(0, 0), Qt::TextColorRole); if (colorVariant.isValid()) { Q_ASSERT(colorVariant.canConvert()); } // Check that the "check state" is one we know about. QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); if (checkStateVariant.isValid()) { int state = checkStateVariant.toInt(); Q_ASSERT(state == Qt::Unchecked || state == Qt::PartiallyChecked || state == Qt::Checked); } } /*! Store what is about to be inserted to make sure it actually happens \sa rowsInserted() */ void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(end); Changing c; c.parent = parent; c.oldSize = model->rowCount(parent); c.last = model->data(model->index(start - 1, 0, parent)); c.next = model->data(model->index(start, 0, parent)); insert.push(c); } /*! Confirm that what was said was going to happen actually did \sa rowsAboutToBeInserted() */ void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end) { Changing c = insert.pop(); Q_ASSERT(c.parent == parent); Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent)); Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); /* if (c.next != model->data(model->index(end + 1, 0, c.parent))) { qDebug() << start << end; for (int i=0; i < model->rowCount(); ++i) qDebug() << model->index(i, 0).data().toString(); qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); } */ Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent))); } void ModelTest::layoutAboutToBeChanged() { for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) changing.append(QPersistentModelIndex(model->index(i, 0))); } void ModelTest::layoutChanged() { for (int i = 0; i < changing.count(); ++i) { QPersistentModelIndex p = changing[i]; Q_ASSERT(p == model->index(p.row(), p.column(), p.parent())); } changing.clear(); } /*! Store what is about to be inserted to make sure it actually happens \sa rowsRemoved() */ void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Changing c; c.parent = parent; c.oldSize = model->rowCount(parent); c.last = model->data(model->index(start - 1, 0, parent)); c.next = model->data(model->index(end + 1, 0, parent)); remove.push(c); } /*! Confirm that what was said was going to happen actually did \sa rowsAboutToBeRemoved() */ void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end) { Changing c = remove.pop(); Q_ASSERT(c.parent == parent); Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent)); Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent))); } diff --git a/interfaces/modeltest.h b/interfaces/modeltest.h index 38b6b2be..befe270e 100644 --- a/interfaces/modeltest.h +++ b/interfaces/modeltest.h @@ -1,76 +1,76 @@ /**************************************************************************** ** ** Copyright (C) 2007 Trolltech ASA. All rights reserved. ** ** This file is part of the Qt Concurrent project on Trolltech Labs. ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file. Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.trolltech.com/products/qt/opensource.html ** ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http://www.trolltech.com/products/qt/licensing.html or contact the ** sales department at sales@trolltech.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ #ifndef MODELTEST_H #define MODELTEST_H #include #include #include class ModelTest : public QObject { Q_OBJECT public: - ModelTest(QAbstractItemModel *model, QObject *parent = 0); + explicit ModelTest(QAbstractItemModel* model, QObject* parent = 0); private Q_SLOTS: void nonDestructiveBasicTest(); void rowCount(); void columnCount(); void hasIndex(); void index(); void parent(); void data(); protected Q_SLOTS: void runAllTests(); void layoutAboutToBeChanged(); void layoutChanged(); - void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void rowsAboutToBeInserted(const QModelIndex& parent, int start, int end); void rowsInserted(const QModelIndex & parent, int start, int end); - void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); void rowsRemoved(const QModelIndex & parent, int start, int end); private: - void checkChildren(const QModelIndex &parent, int currentDepth = 0); + void checkChildren(const QModelIndex& parent, int currentDepth = 0); - QAbstractItemModel *model; + QAbstractItemModel* model; struct Changing { QModelIndex parent; int oldSize; QVariant last; QVariant next; }; QStack insert; QStack remove; bool fetchingMore; QList changing; }; #endif diff --git a/interfaces/notificationsmodel.cpp b/interfaces/notificationsmodel.cpp index aba45732..2c327f93 100644 --- a/interfaces/notificationsmodel.cpp +++ b/interfaces/notificationsmodel.cpp @@ -1,242 +1,271 @@ /** * 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 //#include "modeltest.h" NotificationsModel::NotificationsModel(QObject* parent) : QAbstractListModel(parent) - , m_dbusInterface(0) + , m_dbusInterface(nullptr) { //new ModelTest(this, this); - connect(this, SIGNAL(rowsInserted(QModelIndex, int, int)), - this, SIGNAL(rowsChanged())); - connect(this, SIGNAL(rowsRemoved(QModelIndex, int, int)), - this, SIGNAL(rowsChanged())); - - connect(this, SIGNAL(dataChanged(QModelIndex, QModelIndex)), - this, SIGNAL(anyDismissableChanged())); + 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, SIGNAL(notificationPosted(QString)), - this, SLOT(notificationAdded(QString))); - connect(m_dbusInterface, SIGNAL(notificationRemoved(QString)), - this, SLOT(notificationRemoved(QString))); + 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) { - //TODO: Actually add instead of refresh - Q_UNUSED(id); - refreshNotificationList(); + int currentSize = m_notificationList.size(); + beginInsertRows(QModelIndex(), currentSize, currentSize); + NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, id, this); + m_notificationList.append(dbusInterface); + endInsertRows(); } void NotificationsModel::notificationRemoved(const QString& id) { - //TODO: Actually remove instead of refresh - Q_UNUSED(id); - refreshNotificationList(); + 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); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pendingNotificationIds, this); - QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - this, SLOT(receivedNotifications(QDBusPendingCallWatcher*))); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, + this, &NotificationsModel::receivedNotifications); } void NotificationsModel::receivedNotifications(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearNotifications(); QDBusPendingReply pendingNotificationIds = *watcher; - clearNotifications(); if (pendingNotificationIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << pendingNotificationIds.error(); return; } const QStringList notificationIds = pendingNotificationIds.value(); if (notificationIds.isEmpty()) { return; } beginInsertRows(QModelIndex(), 0, notificationIds.size() - 1); - Q_FOREACH (const QString& notificationId, notificationIds) { + for (const QString& notificationId : notificationIds) { NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, notificationId, this); m_notificationList.append(dbusInterface); } endInsertRows(); } QVariant NotificationsModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_notificationList.count() || !m_notificationList[index.row()]->isValid()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } NotificationDbusInterface* notification = m_notificationList[index.row()]; //FIXME: This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { case IconModelRole: - return QIcon::fromTheme("device-notifier"); + 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 NULL; + return nullptr; } int row = index.row(); if (row < 0 || row >= m_notificationList.size()) { - return NULL; + return nullptr; } return m_notificationList[row]; } int NotificationsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_notificationList.count(); } bool NotificationsModel::isAnyDimissable() const { - Q_FOREACH (NotificationDbusInterface* notification, m_notificationList) { + for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { return true; } } return false; } void NotificationsModel::dismissAll() { - Q_FOREACH (NotificationDbusInterface* notification, m_notificationList) { + for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { notification->dismiss(); } } } void NotificationsModel::clearNotifications() { if (!m_notificationList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_notificationList.size() - 1); qDeleteAll(m_notificationList); m_notificationList.clear(); endRemoveRows(); } } + +void NotificationsModel::notificationUpdated(const QString& id) +{ + //TODO only emit the affected indices + Q_EMIT dataChanged(index(0,0), index(m_notificationList.size() - 1, 0)); +} diff --git a/interfaces/notificationsmodel.h b/interfaces/notificationsmodel.h index c54ef65f..a1bf24b6 100644 --- a/interfaces/notificationsmodel.h +++ b/interfaces/notificationsmodel.h @@ -1,86 +1,90 @@ /** * 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 NOTIFICATIONSMODEL_H #define NOTIFICATIONSMODEL_H #include #include #include #include #include "interfaces/dbusinterfaces.h" class KDECONNECTINTERFACES_EXPORT NotificationsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) Q_PROPERTY(int count READ rowCount NOTIFY rowsChanged) - Q_PROPERTY(bool isAnyDimissable READ isAnyDimissable NOTIFY anyDismissableChanged) + Q_PROPERTY(bool isAnyDimissable READ isAnyDimissable NOTIFY anyDismissableChanged STORED false) public: enum ModelRoles { IconModelRole = Qt::DecorationRole, NameModelRole = Qt::DisplayRole, ContentModelRole = Qt::UserRole, AppNameModelRole = Qt::UserRole + 1, IdModelRole, DismissableModelRole, + RepliableModelRole, + IconPathModelRole, DbusInterfaceRole, + TitleModelRole, + TextModelRole }; - NotificationsModel(QObject* parent = 0); - virtual ~NotificationsModel(); + explicit NotificationsModel(QObject* parent = nullptr); + ~NotificationsModel() override; QString deviceId() const; void setDeviceId(const QString& deviceId); - virtual QVariant data(const QModelIndex& index, int role) const; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; NotificationDbusInterface* getNotification(const QModelIndex& index) const; Q_INVOKABLE bool isAnyDimissable() const; - virtual QHash roleNames() const; + QHash roleNames() const override; public Q_SLOTS: void dismissAll(); private Q_SLOTS: void notificationAdded(const QString& id); void notificationRemoved(const QString& id); + void notificationUpdated(const QString& id); void refreshNotificationList(); void receivedNotifications(QDBusPendingCallWatcher* watcher); + void clearNotifications(); Q_SIGNALS: void deviceIdChanged(const QString& value); void anyDismissableChanged(); void rowsChanged(); private: - void clearNotifications(); - DeviceNotificationsDbusInterface* m_dbusInterface; QList m_notificationList; QString m_deviceId; }; #endif // DEVICESMODEL_H diff --git a/kcm/CMakeLists.txt b/kcm/CMakeLists.txt index b596e769..20c3510a 100644 --- a/kcm/CMakeLists.txt +++ b/kcm/CMakeLists.txt @@ -1,29 +1,29 @@ add_definitions(-DTRANSLATION_DOMAIN="kdeconnect-kcm") find_package(KF5KCMUtils 5.9 REQUIRED) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) set(kcm_SRCS kcm.cpp ) ki18n_wrap_ui(kcm_SRCS kcm.ui) add_library(kcm_kdeconnect MODULE ${kcm_SRCS}) target_link_libraries(kcm_kdeconnect Qt5::Core Qt5::Gui KF5::I18n KF5::KCMUtils kdeconnectinterfaces ) install(TARGETS kcm_kdeconnect DESTINATION ${PLUGIN_INSTALL_DIR}) +install(FILES org.kde.kdeconnect.kcm.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install(FILES kcm_kdeconnect.desktop DESTINATION ${SERVICES_INSTALL_DIR}) -install(FILES kdeconnect.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp index ac66dc02..421afacd 100644 --- a/kcm/kcm.cpp +++ b/kcm/kcm.cpp @@ -1,307 +1,373 @@ /** * 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 "kcm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_kcm.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/devicesmodel.h" #include "devicessortproxymodel.h" #include "kdeconnect-version.h" K_PLUGIN_FACTORY(KdeConnectKcmFactory, registerPlugin();) -KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&) - : KCModule(KAboutData::pluginData("kdeconnect-kcm"), parent) +static QString createId() { return QStringLiteral("kcm")+QString::number(QCoreApplication::applicationPid()); } + +KdeConnectKcm::KdeConnectKcm(QWidget* parent, const QVariantList&) + : KCModule(KAboutData::pluginData(QStringLiteral("kdeconnect-kcm")), parent) , kcmUi(new Ui::KdeConnectKcmUi()) , daemon(new DaemonDbusInterface(this)) , devicesModel(new DevicesModel(this)) - , currentDevice(0) + , currentDevice(nullptr) { - KAboutData *about = new KAboutData("kdeconnect-kcm", + KAboutData* about = new KAboutData(QStringLiteral("kdeconnect-kcm"), i18n("KDE Connect Settings"), - QLatin1String(KDECONNECT_VERSION_STRING), + QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect Settings module"), KAboutLicense::KAboutLicense::GPL_V2, i18n("(C) 2015 Albert Vaca Cintora"), QString(), - QLatin1String("https://community.kde.org/KDEConnect") + QStringLiteral("https://community.kde.org/KDEConnect") ); about->addAuthor(i18n("Albert Vaca Cintora")); setAboutData(about); kcmUi->setupUi(this); - kcmUi->deviceList->setIconSize(QSize(32,32)); - sortProxyModel = new DevicesSortProxyModel(devicesModel); kcmUi->deviceList->setModel(sortProxyModel); kcmUi->deviceInfo->setVisible(false); kcmUi->progressBar->setVisible(false); kcmUi->messages->setVisible(false); //Workaround: If we set this directly (or if we set it in the .ui file), the layout breaks kcmUi->noDeviceLinks->setWordWrap(false); QTimer::singleShot(0, [this] { kcmUi->noDeviceLinks->setWordWrap(true); }); - kcmUi->rename_label->setText(daemon->announcedName()); - kcmUi->rename_edit->setText(daemon->announcedName()); + setWhenAvailable(daemon->announcedName(), [this](const QString& announcedName) { + kcmUi->rename_label->setText(announcedName); + kcmUi->rename_edit->setText(announcedName); + }, this); + connect(daemon, SIGNAL(announcedNameChanged(QString)), + kcmUi->rename_edit, SLOT(setText(QString))); + connect(daemon, SIGNAL(announcedNameChanged(QString)), + kcmUi->rename_label, SLOT(setText(QString))); setRenameMode(false); - setButtons(KCModule::NoAdditionalButton); - - connect(devicesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), - this, SLOT(resetSelection())); - connect(kcmUi->deviceList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), - this, SLOT(deviceSelected(QModelIndex))); - connect(kcmUi->pair_button, SIGNAL(clicked()), - this, SLOT(requestPair())); - connect(kcmUi->unpair_button, SIGNAL(clicked()), - this, SLOT(unpair())); - connect(kcmUi->ping_button, SIGNAL(clicked()), - this, SLOT(sendPing())); - connect(kcmUi->refresh_button,SIGNAL(clicked()), - this, SLOT(refresh())); - connect(kcmUi->rename_edit,SIGNAL(returnPressed()), - this, SLOT(renameDone())); - connect(kcmUi->renameDone_button,SIGNAL(clicked()), - this, SLOT(renameDone())); - connect(kcmUi->renameShow_button,SIGNAL(clicked()), - this, SLOT(renameShow())); - + setButtons(KCModule::Help | KCModule::NoAdditionalButton); + + connect(devicesModel, &QAbstractItemModel::dataChanged, + this, &KdeConnectKcm::resetSelection); + connect(kcmUi->deviceList->selectionModel(), &QItemSelectionModel::currentChanged, + this, &KdeConnectKcm::deviceSelected); + connect(kcmUi->accept_button, &QAbstractButton::clicked, + this, &KdeConnectKcm::acceptPairing); + connect(kcmUi->reject_button, &QAbstractButton::clicked, + this, &KdeConnectKcm::rejectPairing); + connect(kcmUi->pair_button, &QAbstractButton::clicked, + this, &KdeConnectKcm::requestPair); + connect(kcmUi->unpair_button, &QAbstractButton::clicked, + this, &KdeConnectKcm::unpair); + connect(kcmUi->ping_button, &QAbstractButton::clicked, + this, &KdeConnectKcm::sendPing); + connect(kcmUi->refresh_button,&QAbstractButton::clicked, + this, &KdeConnectKcm::refresh); + connect(kcmUi->rename_edit,&QLineEdit::returnPressed, + this, &KdeConnectKcm::renameDone); + connect(kcmUi->renameDone_button,&QAbstractButton::clicked, + this, &KdeConnectKcm::renameDone); + connect(kcmUi->renameShow_button,&QAbstractButton::clicked, + this, &KdeConnectKcm::renameShow); + + daemon->acquireDiscoveryMode(createId()); } void KdeConnectKcm::renameShow() { setRenameMode(true); } void KdeConnectKcm::renameDone() { QString newName = kcmUi->rename_edit->text(); if (newName.isEmpty()) { //Rollback changes kcmUi->rename_edit->setText(kcmUi->rename_label->text()); } else { kcmUi->rename_label->setText(newName); daemon->setAnnouncedName(newName); } setRenameMode(false); } void KdeConnectKcm::setRenameMode(bool b) { kcmUi->renameDone_button->setVisible(b); kcmUi->rename_edit->setVisible(b); kcmUi->renameShow_button->setVisible(!b); kcmUi->rename_label->setVisible(!b); } KdeConnectKcm::~KdeConnectKcm() { + daemon->releaseDiscoveryMode(createId()); delete kcmUi; } void KdeConnectKcm::refresh() { + daemon->acquireDiscoveryMode(createId()); daemon->forceOnNetworkChange(); } void KdeConnectKcm::resetSelection() { if (!currentDevice) { return; } kcmUi->deviceList->selectionModel()->setCurrentIndex(sortProxyModel->mapFromSource(currentIndex), QItemSelectionModel::ClearAndSelect); } void KdeConnectKcm::deviceSelected(const QModelIndex& current) { - - kcmUi->noDevicePlaceholder->setVisible(false); - if (currentDevice) { - disconnect(currentDevice,SIGNAL(pairingChanged(bool)), - this, SLOT(pairingChanged(bool))); - disconnect(currentDevice,SIGNAL(pairingFailed(QString)), - this, SLOT(pairingFailed(QString))); + disconnect(currentDevice, 0, this, 0); } //Store previous device config pluginsConfigChanged(); if (!current.isValid()) { - currentDevice = NULL; + currentDevice = nullptr; kcmUi->deviceInfo->setVisible(false); return; } currentIndex = sortProxyModel->mapToSource(current); currentDevice = devicesModel->getDevice(currentIndex.row()); - bool valid = (currentDevice != NULL && currentDevice->isValid()); + kcmUi->noDevicePlaceholder->setVisible(false); + bool valid = (currentDevice != nullptr && currentDevice->isValid()); kcmUi->deviceInfo->setVisible(valid); if (!valid) { return; } kcmUi->messages->setVisible(false); - if (currentDevice->pairRequested()) { - kcmUi->progressBar->setVisible(true); - kcmUi->unpair_button->setVisible(false); - kcmUi->pair_button->setVisible(false); - kcmUi->ping_button->setVisible(false); + resetDeviceView(); + + connect(currentDevice, SIGNAL(pluginsChanged()), this, SLOT(resetCurrentDevice())); + connect(currentDevice, SIGNAL(trustedChanged(bool)), this, SLOT(trustedChanged(bool))); + connect(currentDevice, SIGNAL(pairingError(QString)), this, SLOT(pairingFailed(QString))); + connect(currentDevice, &DeviceDbusInterface::hasPairingRequestsChangedProxy, this, &KdeConnectKcm::currentDevicePairingChanged); +} + +void KdeConnectKcm::currentDevicePairingChanged(bool pairing) +{ + if (pairing) { + setCurrentDeviceTrusted(RequestedByPeer); } else { - kcmUi->progressBar->setVisible(false); - if (currentDevice->isPaired()) { - kcmUi->unpair_button->setVisible(true); - kcmUi->pair_button->setVisible(false); - kcmUi->ping_button->setVisible(true); - } else { - kcmUi->unpair_button->setVisible(false); - kcmUi->pair_button->setVisible(true); - kcmUi->ping_button->setVisible(false); - } + setWhenAvailable(currentDevice->isTrusted(), [this](bool trusted) { + setCurrentDeviceTrusted(trusted ? Trusted : NotTrusted); + }, this); + } +} + +void KdeConnectKcm::resetCurrentDevice() +{ + const QStringList supportedPluginNames = currentDevice->supportedPlugins(); + + if (m_oldSupportedPluginNames != supportedPluginNames) { + resetDeviceView(); } +} +void KdeConnectKcm::resetDeviceView() +{ //KPluginSelector has no way to remove a list of plugins and load another, so we need to destroy and recreate it each time delete kcmUi->pluginSelector; kcmUi->pluginSelector = new KPluginSelector(this); kcmUi->deviceInfo_layout->addWidget(kcmUi->pluginSelector); kcmUi->pluginSelector->setConfigurationArguments(QStringList(currentDevice->id())); kcmUi->name_label->setText(currentDevice->name()); - kcmUi->status_label->setText(currentDevice->isPaired()? i18n("(paired)") : i18n("(unpaired)")); - - connect(currentDevice,SIGNAL(pairingChanged(bool)), - this, SLOT(pairingChanged(bool))); - connect(currentDevice,SIGNAL(pairingFailed(QString)), - this, SLOT(pairingFailed(QString))); + setWhenAvailable(currentDevice->isTrusted(), [this](bool trusted) { + if (trusted) + setCurrentDeviceTrusted(Trusted); + else + setWhenAvailable(currentDevice->hasPairingRequests(), [this](bool haspr) { + setCurrentDeviceTrusted(haspr ? RequestedByPeer : NotTrusted); + }, this); + }, this); + + const QList pluginInfo = KPluginInfo::fromMetaData(KPluginLoader::findPlugins(QStringLiteral("kdeconnect/"))); + QList availablePluginInfo; + + m_oldSupportedPluginNames = currentDevice->supportedPlugins(); + for (auto it = pluginInfo.cbegin(), itEnd = pluginInfo.cend(); it!=itEnd; ++it) { + if (m_oldSupportedPluginNames.contains(it->pluginName())) { + availablePluginInfo.append(*it); + } + } - const QList pluginInfo = KPluginInfo::fromMetaData(KPluginLoader::findPlugins("kdeconnect/")); KSharedConfigPtr deviceConfig = KSharedConfig::openConfig(currentDevice->pluginsConfigFile()); - kcmUi->pluginSelector->addPlugins(pluginInfo, KPluginSelector::ReadConfigFile, i18n("Plugins"), QString(), deviceConfig); - - connect(kcmUi->pluginSelector, SIGNAL(changed(bool)), - this, SLOT(pluginsConfigChanged())); + kcmUi->pluginSelector->addPlugins(availablePluginInfo, KPluginSelector::ReadConfigFile, i18n("Available plugins"), QString(), deviceConfig); + connect(kcmUi->pluginSelector, &KPluginSelector::changed, this, &KdeConnectKcm::pluginsConfigChanged); } void KdeConnectKcm::requestPair() { if (!currentDevice) { return; } kcmUi->messages->hide(); - kcmUi->pair_button->setVisible(false); - kcmUi->progressBar->setVisible(true); + setCurrentDeviceTrusted(Requested); currentDevice->requestPair(); } void KdeConnectKcm::unpair() { if (!currentDevice) { return; } + setCurrentDeviceTrusted(NotTrusted); currentDevice->unpair(); } +void KdeConnectKcm::acceptPairing() +{ + if (!currentDevice) { + return; + } + + currentDevice->acceptPairing(); +} + +void KdeConnectKcm::rejectPairing() +{ + if (!currentDevice) { + return; + } + + currentDevice->rejectPairing(); +} + void KdeConnectKcm::pairingFailed(const QString& error) { if (sender() != currentDevice) return; - pairingChanged(false); + setCurrentDeviceTrusted(NotTrusted); kcmUi->messages->setText(i18n("Error trying to pair: %1",error)); kcmUi->messages->animatedShow(); } -void KdeConnectKcm::pairingChanged(bool paired) +void KdeConnectKcm::trustedChanged(bool trusted) { DeviceDbusInterface* senderDevice = (DeviceDbusInterface*) sender(); - if (senderDevice != currentDevice) return; + if (senderDevice == currentDevice) + setCurrentDeviceTrusted(trusted ? Trusted : NotTrusted); +} - kcmUi->pair_button->setVisible(!paired); - kcmUi->unpair_button->setVisible(paired); - kcmUi->progressBar->setVisible(senderDevice->pairRequested()); - kcmUi->ping_button->setVisible(paired); - kcmUi->status_label->setText(paired ? i18n("(paired)") : i18n("(unpaired)")); +void KdeConnectKcm::setCurrentDeviceTrusted(KdeConnectKcm::TrustStatus trusted) +{ + kcmUi->accept_button->setVisible(trusted == RequestedByPeer); + kcmUi->reject_button->setVisible(trusted == RequestedByPeer); + kcmUi->pair_button->setVisible(trusted == NotTrusted); + kcmUi->unpair_button->setVisible(trusted == Trusted); + kcmUi->progressBar->setVisible(trusted == Requested); + kcmUi->ping_button->setVisible(trusted == Trusted); + switch (trusted) { + case Trusted: + kcmUi->status_label->setText(i18n("(paired)")); + break; + case NotTrusted: + kcmUi->status_label->setText(i18n("(not paired)")); + break; + case RequestedByPeer: + kcmUi->status_label->setText(i18n("(incoming pair request)")); + break; + case Requested: + kcmUi->status_label->setText(i18n("(pairing requested)")); + break; + } } void KdeConnectKcm::pluginsConfigChanged() { //Store previous selection if (!currentDevice) return; DeviceDbusInterface* auxCurrentDevice = currentDevice; - currentDevice = 0; //HACK to avoid infinite recursion (for some reason calling save on pluginselector emits changed) + currentDevice = nullptr; //HACK to avoid infinite recursion (for some reason calling save on pluginselector emits changed) kcmUi->pluginSelector->save(); currentDevice = auxCurrentDevice; currentDevice->reloadPlugins(); } void KdeConnectKcm::save() { pluginsConfigChanged(); KCModule::save(); } void KdeConnectKcm::sendPing() { if (!currentDevice) return; - currentDevice->pluginCall("ping", "sendPing"); + currentDevice->pluginCall(QStringLiteral("ping"), QStringLiteral("sendPing")); } QSize KdeConnectKcm::sizeHint() const { return QSize(890,550); //Golden ratio :D } QSize KdeConnectKcm::minimumSizeHint() const { return QSize(500,300); } #include "kcm.moc" #include "moc_kcm.cpp" diff --git a/kcm/kcm.h b/kcm/kcm.h index 75bcd9e6..67f7cf8a 100644 --- a/kcm/kcm.h +++ b/kcm/kcm.h @@ -1,75 +1,84 @@ /** * 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 KDECONNECTKCM_H #define KDECONNECTKCM_H #include #include class QModelIndex; class DeviceDbusInterface; class DaemonDbusInterface; class DevicesModel; class DevicesSortProxyModel; namespace Ui { class KdeConnectKcmUi; } class KdeConnectKcm : public KCModule { Q_OBJECT public: - KdeConnectKcm(QWidget *parent, const QVariantList&); - virtual ~KdeConnectKcm(); + KdeConnectKcm(QWidget* parent, const QVariantList&); + ~KdeConnectKcm() override; private: - virtual void save(); - virtual QSize sizeHint() const; - virtual QSize minimumSizeHint() const; + void save() override; + QSize sizeHint() const override; + QSize minimumSizeHint() const override; private Q_SLOTS: void deviceSelected(const QModelIndex& current); void requestPair(); void pluginsConfigChanged(); void sendPing(); void resetSelection(); - void pairingChanged(bool); + void trustedChanged(bool); void pairingFailed(const QString& error); void refresh(); void renameShow(); void renameDone(); void setRenameMode(bool b); + void resetCurrentDevice(); + void currentDevicePairingChanged(bool pairing); + void acceptPairing(); + void rejectPairing(); private: + enum TrustStatus { NotTrusted, Requested, RequestedByPeer, Trusted }; + void setCurrentDeviceTrusted(TrustStatus trusted); + void resetDeviceView(); + Ui::KdeConnectKcmUi* kcmUi; DaemonDbusInterface* daemon; DevicesModel* devicesModel; DevicesSortProxyModel* sortProxyModel; DeviceDbusInterface* currentDevice; QModelIndex currentIndex; + QStringList m_oldSupportedPluginNames; public Q_SLOTS: void unpair(); }; #endif diff --git a/kcm/kcm.ui b/kcm/kcm.ui index 6f763c84..ca34b308 100644 --- a/kcm/kcm.ui +++ b/kcm/kcm.ui @@ -1,357 +1,340 @@ KdeConnectKcmUi 0 0 - 949 + 965 528 0 0 0 0 250 16777215 12 75 true KDE Connect Qt::PlainText Qt::Horizontal 40 0 - + + .. 64 - + + .. Refresh 0 0 0 0 0 0 QLayout::SetMaximumSize 10 75 true Device + + Qt::PlainText + 0 0 (status) Qt::Horizontal 40 20 0 0 0 0 -1 + + + + Accept + + + + + + + Reject + + + 0 0 Request pair 0 0 Unpair 0 0 Send ping 0 0 Qt::WheelFocus - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - No device selected. - - - Qt::AlignCenter - - - 0 0 - If you have an Android phone, make sure to install the <a href="https://play.google.com/store/apps/details?id=org.kde.kdeconnect_tp"><span style=" text-decoration: underline; color:#0000ff;">KDE Connect Android app</span></a> (also available <a href="https://f-droid.org/repository/browse/?fdid=org.kde.kdeconnect_tp"><span style=" text-decoration: underline; color:#0000ff;">from F-Droid</span></a>). + <html><head/><body><p>No device selected.<br><br>If you own an Android device, make sure to install the <a href="https://play.google.com/store/apps/details?id=org.kde.kdeconnect_tp"><span style=" text-decoration: underline; color:#4c6b8a;">KDE Connect Android app</span></a> (also available <a href="https://f-droid.org/repository/browse/?fdid=org.kde.kdeconnect_tp"><span style=" text-decoration: underline; color:#4c6b8a;">from F-Droid</span></a>) and it should appear in the list.<br><br>If you are having problems, visit the <a href="https://community.kde.org/KDEConnect"><span style=" text-decoration: underline; color:#4c6b8a;">KDE Connect Community wiki</span></a> for help.</p></body></html> Qt::RichText Qt::AlignCenter true 40 true Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - - Qt::Vertical - - - - 20 - 40 - - - - KPluginSelector QWidget
kpluginselector.h
1
KMessageWidget QWidget
kmessagewidget.h
1
diff --git a/kcm/kcm_kdeconnect.desktop b/kcm/kcm_kdeconnect.desktop index 874e3ece..985e14ff 100755 --- a/kcm/kcm_kdeconnect.desktop +++ b/kcm/kcm_kdeconnect.desktop @@ -1,86 +1,134 @@ [Desktop Entry] Exec=kcmshell5 kcm_kdeconnect Icon=kdeconnect Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kcm_kdeconnect X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=hardware Name=KDE Connect +Name[ar]=كدي المتّصل +Name[ast]=KDE Connect Name[bg]=KDE Connect Name[bs]=Konekcija KDE Name[ca]=KDE Connect +Name[ca@valencia]=KDE Connect Name[cs]=KDE Connect Name[da]=KDE Connect Name[de]=KDE-Connect +Name[el]=KDE Connect Name[en_GB]=KDE Connect Name[es]=KDE Connect +Name[et]=KDE Connect +Name[eu]=KDE Connect Name[fi]=KDE Connect Name[fr]=KDE Connect Name[gl]=KDE Connect +Name[he]=KDE Connect Name[hu]=KDE csatlakozás Name[it]=KDE Connect Name[ko]=KDE Connect Name[nl]=KDE Connect +Name[nn]=KDE Connect Name[pl]=KDE Connect Name[pt]=KDE Connect Name[pt_BR]=KDE Connect Name[ro]=KDE Connect Name[ru]=KDE Connect Name[sk]=KDE Connect -Name[sv]=KDE anslut -Name[tr]=KDE Bağlan +Name[sr]=КДЕ‑конекција +Name[sr@ijekavian]=КДЕ‑конекција +Name[sr@ijekavianlatin]=KDE‑konekcija +Name[sr@latin]=KDE‑konekcija +Name[sv]=KDE-anslut +Name[tr]=KDE Connect Name[uk]=З’єднання KDE Name[x-test]=xxKDE Connectxx Name[zh_CN]=KDE Connect +Name[zh_TW]=KDE 連線 Comment=Connect and sync your devices +Comment[ar]=اتّصل وزامن أجهزتك +Comment[ast]=Coneuta y sincroniza los tos preseos Comment[ca]=Connecta i sincronitza els vostres dispositius +Comment[ca@valencia]=Connecta i sincronitza els vostres dispositius Comment[cs]=Připojte a synchronizujte svá zařízení +Comment[da]=Forbind og synkronisér dine enheder Comment[de]=Verbinden und Abgleichen Ihrer Geräte -Comment[es]=Conectar y sincronizar sus dispositivos +Comment[el]=Σύνδεση και συγχρονισμός των συσκευών σας +Comment[en_GB]=Connect and sync your devices +Comment[es]=Conecte y sincronice sus dispositivos +Comment[et]=Oma seadmete ühendamine ja sünkroonimine +Comment[eu]=Konektatu eta sinkronizatu zure gailuak Comment[fi]=Yhdistä ja synkronoi laitteitasi +Comment[fr]=Connectez et synchronisez vos périphériques Comment[gl]=Conecte e sincronice os seus dispositivos. +Comment[he]=חבר וסנכרן את ההתקנים שלך +Comment[hu]=Csatlakoztassa és szinkronizálja eszközeit Comment[it]=Connetti e sincronizza i tuoi dispositivi +Comment[ko]=내 장치에 연결하고 동기화 Comment[nl]=Uw apparaten verbinden en synchroniseren +Comment[nn]=Kopla til og synkroniser einingar Comment[pl]=Podłącz i zsynchronizuj swoje urządzenia Comment[pt]=Ligue e sincronize os seus dispositivos Comment[pt_BR]=Conecta e sincroniza seus dispositivos +Comment[ru]=Подключение и синхронизация с мобильными устройствами Comment[sk]=Pripojiť a synchronizovať vaše zariadenia +Comment[sr]=Повежите и синхронизујте своје уређаје +Comment[sr@ijekavian]=Повежите и синхронизујте своје уређаје +Comment[sr@ijekavianlatin]=Povežite i sinhronizujte svoje uređaje +Comment[sr@latin]=Povežite i sinhronizujte svoje uređaje Comment[sv]=Anslut och synkronisera dina apparater Comment[tr]=Aygıtlarınıza bağlanın ve eşitleyin Comment[uk]=З’єднання і синхронізація ваших пристроїв Comment[x-test]=xxConnect and sync your devicesxx +Comment[zh_CN]=连接并同步您的设备 +Comment[zh_TW]=連線並且同步您的裝置 X-KDE-Keywords=Network,Android,Devices +X-KDE-Keywords[ar]=شبكة,أندرويد,اندرويد,جهاز,أجهزة +X-KDE-Keywords[ast]=Rede,Android,Preseos X-KDE-Keywords[bg]=Мрежа,Андроид,Устройства X-KDE-Keywords[bs]=Mreža,Android,Uređaji X-KDE-Keywords[ca]=Xarxa,Android,Dispositius +X-KDE-Keywords[ca@valencia]=Xarxa,Android,Dispositius X-KDE-Keywords[cs]=Síť,Android,Zařízení X-KDE-Keywords[da]=Netværk,Android,Enheder X-KDE-Keywords[de]=Netzwerk,Android,Geräte +X-KDE-Keywords[el]=Δίκτυο,Android,Συσκευές X-KDE-Keywords[en_GB]=Network,Android,Devices X-KDE-Keywords[es]=Red,Android,Dispositivos +X-KDE-Keywords[et]=Võrk,Android,Seadmed +X-KDE-Keywords[eu]=Sarea,Android,Gailuak X-KDE-Keywords[fi]=Verkko,Android,Laitteet X-KDE-Keywords[fr]=Réseau,Android,Périphériques X-KDE-Keywords[gl]=rede,Android,dispositivos +X-KDE-Keywords[he]=Network,Android,Devices,רשתות,אנדרואיד,התקנים,מכשירים X-KDE-Keywords[hu]=Hálózat,Android,Eszközök X-KDE-Keywords[it]=Rete,Android,Dispositivi X-KDE-Keywords[ko]=Network,Android,Devices,네트워크,안드로이드,장치 X-KDE-Keywords[nl]=Netwerk,Android,Apparaten +X-KDE-Keywords[nn]=nettverk,Android,einingar,smarttelefon X-KDE-Keywords[pl]=Sieć,Android,Urządzenia X-KDE-Keywords[pt]=Rede,Android,Dispositivos X-KDE-Keywords[pt_BR]=Rede,Android,Dispositivos -X-KDE-Keywords[ru]=Network,Android,Devices,сеть,Андроид,устройства +X-KDE-Keywords[ru]=Network,Android,Devices,сеть,Андроид,устройства,мобильные устройства X-KDE-Keywords[sk]=Sieť,Android,Zariadenia +X-KDE-Keywords[sr]=Network,Android,Devices,мрежа,Андроид,уређаји +X-KDE-Keywords[sr@ijekavian]=Network,Android,Devices,мрежа,Андроид,уређаји +X-KDE-Keywords[sr@ijekavianlatin]=Network,Android,Devices,mreža,Android,uređaji +X-KDE-Keywords[sr@latin]=Network,Android,Devices,mreža,Android,uređaji X-KDE-Keywords[sv]=Nätverk, Android, apparater X-KDE-Keywords[tr]=Ağ,Android,Cihazlar,Aygıtlar X-KDE-Keywords[uk]=Network,Android,Devices,мережа,андроїд,пристрої X-KDE-Keywords[x-test]=xxNetworkxx,xxAndroidxx,xxDevicesxx X-KDE-Keywords[zh_CN]=Network,Android,Devices,网络,安卓,设备 +X-KDE-Keywords[zh_TW]=網路,Android裝置,設備 + +X-DocPath=kdeconnect/index.html Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/kcm/kdeconnect.desktop b/kcm/kdeconnect.desktop deleted file mode 100755 index a35f2d40..00000000 --- a/kcm/kdeconnect.desktop +++ /dev/null @@ -1,51 +0,0 @@ -[Desktop Entry] -Type=Application -Icon=kdeconnect -Terminal=false -Exec=kcmshell5 kcm_kdeconnect -Name=KDE Connect -Name[bg]=KDE Connect -Name[bs]=Konekcija KDE -Name[ca]=KDE Connect -Name[cs]=KDE Connect -Name[da]=KDE Connect -Name[de]=KDE-Connect -Name[en_GB]=KDE Connect -Name[es]=KDE Connect -Name[fi]=KDE Connect -Name[fr]=KDE Connect -Name[gl]=KDE Connect -Name[hu]=KDE csatlakozás -Name[it]=KDE Connect -Name[ko]=KDE Connect -Name[nl]=KDE Connect -Name[pl]=KDE Connect -Name[pt]=KDE Connect -Name[pt_BR]=KDE Connect -Name[ro]=KDE Connect -Name[ru]=KDE Connect -Name[sk]=KDE Connect -Name[sv]=KDE anslut -Name[tr]=KDE Bağlan -Name[uk]=З’єднання KDE -Name[x-test]=xxKDE Connectxx -Name[zh_CN]=KDE Connect -GenericName=Connect and sync your devices -GenericName[ca]=Connecta i sincronitza els vostres dispositius -GenericName[cs]=Připojte a synchronizujte svá zařízení -GenericName[de]=Verbinden und Abgleichen Ihrer Geräte -GenericName[es]=Conectar y sincronizar sus dispositivos -GenericName[fi]=Yhdistä ja synkronoi laitteitasi -GenericName[gl]=Conecte e sincronice os seus dispositivos -GenericName[it]=Connetti e sincronizza i tuoi dispositivi -GenericName[nl]=Uw apparaten verbinden en synchroniseren -GenericName[pl]=Podłącz i zsynchronizuj swoje urządzenia -GenericName[pt]=Ligue e sincronize os seus dispositivos -GenericName[pt_BR]=Conecta e sincroniza seus dispositivos -GenericName[sk]=Pripojiť a synchronizovať vaše zariadenia -GenericName[sv]=Anslut och synkronisera dina apparater -GenericName[tr]=Aygıtlarınıza bağlanın ve eşitleyin -GenericName[uk]=З’єднання і синхронізація ваших пристроїв -GenericName[x-test]=xxConnect and sync your devicesxx -Categories=Qt;KDE;Settings;HardwareSettings; -NotShowIn=KDE; diff --git a/kcm/org.kde.kdeconnect.kcm.desktop b/kcm/org.kde.kdeconnect.kcm.desktop new file mode 100755 index 00000000..8e33f44d --- /dev/null +++ b/kcm/org.kde.kdeconnect.kcm.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Type=Application +Icon=kdeconnect +Terminal=false +Exec=kcmshell5 kcm_kdeconnect +Name=KDE Connect Settings +Name[ar]=إعدادات كدي المتّصل +Name[ast]=Axustes de KDE Connect +Name[ca]=Arranjament del KDE Connect +Name[ca@valencia]=Arranjament del KDE Connect +Name[cs]=Nastavení KDE Connect +Name[da]=Indstilling af KDE Connect +Name[de]=KDE-Connect-Einstellungen +Name[el]=Ρυθμίσεις KDE Connect +Name[en_GB]=KDE Connect Settings +Name[es]=Ajustes de KDE Connect +Name[et]=KDE Connecti seadistused +Name[eu]=KDE Connect ezarpenak +Name[fi]=KDE Connectin asetukset +Name[fr]=Paramètres de KDE Connect +Name[gl]=Configuración de KDE Connect +Name[he]=הגדרות KDE Connect +Name[hu]=A KDE Connect beállításai +Name[it]=Impostazioni di KDE Connect +Name[ko]=KDE Connect 설정 +Name[nl]=Instellingen van KDE Connect +Name[nn]=Innstillingar for KDE Connect +Name[pl]=Ustawienia KDE Connect +Name[pt]=Configuração do KDE Connect +Name[pt_BR]=Configurações do KDE Connect +Name[ru]=Настройка KDE Connect +Name[sk]=Nastavenia KDE Connect +Name[sr]=Поставке за КДЕ‑конекцију +Name[sr@ijekavian]=Поставке за КДЕ‑конекцију +Name[sr@ijekavianlatin]=Postavke za KDE‑konekciju +Name[sr@latin]=Postavke za KDE‑konekciju +Name[sv]=Inställning av KDE-anslut +Name[tr]=KDE Connect Ayarları +Name[uk]=Параметри KDE Connect +Name[x-test]=xxKDE Connect Settingsxx +Name[zh_CN]=KDE Connect 设置 +Name[zh_TW]=KDE 連線設定 +GenericName=Connect and sync your devices +GenericName[ar]=اتّصل وزامن أجهزتك +GenericName[ast]=Coneuta y sincroniza los tos preseos +GenericName[ca]=Connecta i sincronitza els vostres dispositius +GenericName[ca@valencia]=Connecta i sincronitza els vostres dispositius +GenericName[cs]=Připojte a synchronizujte svá zařízení +GenericName[da]=Forbind og synkronisér dine enheder +GenericName[de]=Verbinden und Abgleichen Ihrer Geräte +GenericName[el]=Σύνδεση και συγχρονισμός των συσκευών σας +GenericName[en_GB]=Connect and sync your devices +GenericName[es]=Conecte y sincronice sus dispositivos +GenericName[et]=Oma seadmete ühendamine ja sünkroonimine +GenericName[eu]=Konektatu eta sinkronizatu zure gailuak +GenericName[fi]=Yhdistä ja synkronoi laitteitasi +GenericName[fr]=Connectez et synchronisez vos périphériques +GenericName[gl]=Conecte e sincronice os seus dispositivos +GenericName[he]=Connect and sync your devices +GenericName[hu]=Csatlakoztassa és szinkronizálja eszközeit +GenericName[it]=Connetti e sincronizza i tuoi dispositivi +GenericName[ko]=내 장치에 연결하고 동기화 +GenericName[nl]=Uw apparaten verbinden en synchroniseren +GenericName[nn]=Kopla til og synkroniser einingar +GenericName[pl]=Podłącz i zsynchronizuj swoje urządzenia +GenericName[pt]=Ligue e sincronize os seus dispositivos +GenericName[pt_BR]=Conecta e sincroniza seus dispositivos +GenericName[ru]=Подключение и синхронизация с мобильными устройствами +GenericName[sk]=Pripojiť a synchronizovať vaše zariadenia +GenericName[sr]=Повежите и синхронизујте своје уређаје +GenericName[sr@ijekavian]=Повежите и синхронизујте своје уређаје +GenericName[sr@ijekavianlatin]=Povežite i sinhronizujte svoje uređaje +GenericName[sr@latin]=Povežite i sinhronizujte svoje uređaje +GenericName[sv]=Anslut och synkronisera dina apparater +GenericName[tr]=Aygıtlarınıza bağlanın ve eşitleyin +GenericName[uk]=З’єднання і синхронізація ваших пристроїв +GenericName[x-test]=xxConnect and sync your devicesxx +GenericName[zh_CN]=连接并同步您的设备 +GenericName[zh_TW]=連線並且同步您的裝置 +Categories=Qt;KDE;Settings;HardwareSettings; +NotShowIn=KDE; diff --git a/kcmplugin/CMakeLists.txt b/kcmplugin/CMakeLists.txt index 18b7c09b..324f16b3 100644 --- a/kcmplugin/CMakeLists.txt +++ b/kcmplugin/CMakeLists.txt @@ -1,29 +1,28 @@ project(kdeconnectpluginkcm) add_definitions(-DTRANSLATION_DOMAIN=\"kdeconnect-core\") find_package(KF5 REQUIRED COMPONENTS KCMUtils) add_library(kdeconnectpluginkcm kdeconnectpluginkcm.cpp) target_link_libraries(kdeconnectpluginkcm PUBLIC kdeconnectcore PRIVATE Qt5::DBus Qt5::Gui KF5::I18n KF5::ConfigCore KF5::KCMUtils ) set_target_properties(kdeconnectpluginkcm PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) target_include_directories(kdeconnectpluginkcm PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) -generate_export_header(kdeconnectpluginkcm EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}../core/kdeconnectcore_export.h BASE_NAME kdeconnectpluginkcm) +generate_export_header(kdeconnectpluginkcm EXPORT_FILE_NAME kdeconnectpluginkcm_export.h BASE_NAME kdeconnectpluginkcm) -# Remove NAMELINK_SKIP if/when headers are being installed and the library -# becomes public. +# Remove NAMELINK_SKIP if/when headers are being installed and the library becomes public. install(TARGETS kdeconnectpluginkcm EXPORT kdeconnectLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) diff --git a/kcmplugin/kdeconnectpluginkcm.cpp b/kcmplugin/kdeconnectpluginkcm.cpp index 15c93672..9b8cc6c0 100644 --- a/kcmplugin/kdeconnectpluginkcm.cpp +++ b/kcmplugin/kdeconnectpluginkcm.cpp @@ -1,60 +1,60 @@ /** * 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 "kdeconnectpluginkcm.h" #include #include struct KdeConnectPluginKcmPrivate { - QString mDeviceId; - QString mPluginName; - KdeConnectPluginConfig* mConfig; + QString m_deviceId; + QString m_pluginName; + KdeConnectPluginConfig* m_config; }; KdeConnectPluginKcm::KdeConnectPluginKcm(QWidget* parent, const QVariantList& args, const QString& componentName) : KCModule(KAboutData::pluginData(componentName), parent, args) , d(new KdeConnectPluginKcmPrivate()) { - d->mDeviceId = args.at(0).toString(); + d->m_deviceId = args.at(0).toString(); //The parent of the config should be the plugin itself - d->mPluginName = KService::serviceByDesktopName(componentName).constData()->property("X-KDE-ParentComponents").toString(); + d->m_pluginName = KService::serviceByDesktopName(componentName).constData()->property(QStringLiteral("X-KDE-ParentComponents")).toString(); - d->mConfig = new KdeConnectPluginConfig(d->mDeviceId, d->mPluginName); + d->m_config = new KdeConnectPluginConfig(d->m_deviceId, d->m_pluginName); } KdeConnectPluginKcm::~KdeConnectPluginKcm() { - delete d->mConfig; + delete d->m_config; } KdeConnectPluginConfig* KdeConnectPluginKcm::config() const { - return d->mConfig; + return d->m_config; } QString KdeConnectPluginKcm::deviceId() const { - return d->mDeviceId; + return d->m_deviceId; } diff --git a/kcmplugin/kdeconnectpluginkcm.h b/kcmplugin/kdeconnectpluginkcm.h index e6b604f1..2611719b 100644 --- a/kcmplugin/kdeconnectpluginkcm.h +++ b/kcmplugin/kdeconnectpluginkcm.h @@ -1,58 +1,58 @@ /** * Copyright 2015 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 KDECONNECTPLUGINKCM_H #define KDECONNECTPLUGINKCM_H #include -#include "core/kdeconnectcore_export.h" #include "core/kdeconnectpluginconfig.h" +#include "kdeconnectpluginkcm_export.h" struct KdeConnectPluginKcmPrivate; /** * Inheriting your plugin's KCM from this class gets you a easy way to share * configuration values between the KCM and the plugin. */ -class KDECONNECTCORE_EXPORT KdeConnectPluginKcm +class KDECONNECTPLUGINKCM_EXPORT KdeConnectPluginKcm : public KCModule { Q_OBJECT public: KdeConnectPluginKcm(QWidget* parent, const QVariantList& args, const QString& componentName); - virtual ~KdeConnectPluginKcm(); + ~KdeConnectPluginKcm() override; /** * The device this kcm is instantiated for */ QString deviceId() const; /** * The object where to save the config, so the plugin can access it */ KdeConnectPluginConfig* config() const; private: QScopedPointer d; }; #endif diff --git a/kdeconnect-non-plasma.desktop b/kdeconnect-non-plasma.desktop deleted file mode 100755 index 888608c2..00000000 --- a/kdeconnect-non-plasma.desktop +++ /dev/null @@ -1,57 +0,0 @@ -[Desktop Entry] -Name=KDE Connect -Name[bg]=KDE Connect -Name[bs]=Konekcija KDE -Name[ca]=KDE Connect -Name[cs]=KDE Connect -Name[da]=KDE Connect -Name[de]=KDE-Connect -Name[en_GB]=KDE Connect -Name[es]=KDE Connect -Name[fi]=KDE Connect -Name[fr]=KDE Connect -Name[gl]=KDE Connect -Name[hu]=KDE csatlakozás -Name[it]=KDE Connect -Name[ko]=KDE Connect -Name[nl]=KDE Connect -Name[pl]=KDE Connect -Name[pt]=KDE Connect -Name[pt_BR]=KDE Connect -Name[ro]=KDE Connect -Name[ru]=KDE Connect -Name[sk]=KDE Connect -Name[sv]=KDE anslut -Name[tr]=KDE Bağlan -Name[uk]=З’єднання KDE -Name[x-test]=xxKDE Connectxx -Name[zh_CN]=KDE Connect -Comment=Reach out to your devices -Comment[bg]=Достигане до устройствата ви -Comment[ca]=Connecteu amb els vostres dispositius -Comment[cs]=Dosáhněte na svá zařízení -Comment[de]=Zugriff auf Ihre Geräte -Comment[en_GB]=Reach out to your devices -Comment[es]=Contactar con sus dispositivos -Comment[fi]=Tavoitettavuutta laitteillesi -Comment[gl]=Chega aos teus dispositivos. -Comment[hu]=Érje el eszközeit -Comment[it]=Comunica con i tuoi dispositivi -Comment[nl]=maak verbinding met uw apparaten -Comment[pl]=Uzyskaj dostęp do swoich urządzeń -Comment[pt]=Aceda aos seus dispositivos -Comment[pt_BR]=Acesse seus dispositivos -Comment[sk]=Oslovenie vašich zariadení -Comment[sv]=Kontakta dina apparater -Comment[tr]=Aygıtlarınıza erişin -Comment[uk]=З’єднатися з вашими пристроями -Comment[x-test]=xxReach out to your devicesxx -Comment[zh_CN]=连接到您的设备 -Exec=plasmawindowed org.kde.kdeconnect --statusnotifier -Icon=kdeconnect -Type=Application -Terminal=false -Categories=Qt;KDE;Network; - -NotShowIn=KDE;Plasma; - diff --git a/kio/kiokdeconnect.cpp b/kio/kiokdeconnect.cpp index 0333a9b4..02243d09 100644 --- a/kio/kiokdeconnect.cpp +++ b/kio/kiokdeconnect.cpp @@ -1,223 +1,234 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kiokdeconnect.h" #include #include #include #include Q_LOGGING_CATEGORY(KDECONNECT_KIO, "kdeconnect.kio") -extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) +extern "C" int Q_DECL_EXPORT kdemain(int argc, char** argv) { if (argc != 4) { fprintf(stderr, "Usage: kio_kdeconnect protocol pool app\n"); exit(-1); } KioKdeconnect slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } //Some useful error mapping KIO::Error toKioError(const QDBusError::ErrorType type) { switch (type) { case QDBusError::NoError: return KIO::Error(KJob::NoError); case QDBusError::NoMemory: return KIO::ERR_OUT_OF_MEMORY; case QDBusError::Timeout: return KIO::ERR_SERVER_TIMEOUT; case QDBusError::TimedOut: return KIO::ERR_SERVER_TIMEOUT; default: return KIO::ERR_INTERNAL; }; }; template bool handleDBusError(QDBusReply& reply, KIO::SlaveBase* slave) { if (!reply.isValid()) { qCDebug(KDECONNECT_KIO) << "Error in DBus request:" << reply.error(); slave->error(toKioError(reply.error().type()),reply.error().message()); return true; } return false; } -KioKdeconnect::KioKdeconnect(const QByteArray &pool, const QByteArray &app) +KioKdeconnect::KioKdeconnect(const QByteArray& pool, const QByteArray& app) : SlaveBase("kdeconnect", pool, app), m_dbusInterface(new DaemonDbusInterface(this)) { } void KioKdeconnect::listAllDevices() { infoMessage(i18n("Listing devices...")); //TODO: Change to all devices and show different icons for connected and disconnected? - QStringList devices = m_dbusInterface->devices(true, true); + const QStringList devices = m_dbusInterface->devices(true, true); - totalSize(devices.length()); - - int i = 0; - Q_FOREACH(const QString& deviceId, devices) { + for (const QString& deviceId : devices) { DeviceDbusInterface interface(deviceId); - if (!interface.hasPlugin("kdeconnect_sftp")) continue; + if (!interface.hasPlugin(QStringLiteral("kdeconnect_sftp"))) continue; - const QString target = QString("kdeconnect://").append(deviceId).append("/"); + const QString path = QStringLiteral("kdeconnect://").append(deviceId).append("/"); const QString name = interface.name(); - const QString icon = "kdeconnect"; + const QString icon = QStringLiteral("kdeconnect"); KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_NAME, name); entry.insert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH); - entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, ""); - entry.insert(KIO::UDSEntry::UDS_URL, target); + entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); + entry.insert(KIO::UDSEntry::UDS_URL, path); listEntry(entry); - - processedSize(i++); - } - infoMessage(""); + // We also need a non-null and writable UDSentry for "." + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_SIZE, 0); + entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); + listEntry(entry); + + infoMessage(QLatin1String("")); finished(); } void KioKdeconnect::listDevice() { infoMessage(i18n("Accessing device...")); qCDebug(KDECONNECT_KIO) << "ListDevice" << m_currentDevice; SftpDbusInterface interface(m_currentDevice); - + QDBusReply mountreply = interface.mountAndWait(); - + if (handleDBusError(mountreply, this)) { return; } - + if (!mountreply.value()) { error(KIO::ERR_COULD_NOT_MOUNT, i18n("Could not mount device filesystem")); return; } - + QDBusReply< QVariantMap > urlreply = interface.getDirectories(); - + if (handleDBusError(urlreply, this)) { return; } - + QVariantMap urls = urlreply.value(); - - for (QVariantMap::iterator it = urls.begin(); it != urls.end(); it++) { - QString path = it.key(); - QString name = it.value().toString(); + for (QVariantMap::iterator it = urls.begin(); it != urls.end(); ++it) { + + const QString path = it.key(); + const QString name = it.value().toString(); + const QString icon = QStringLiteral("folder"); KIO::UDSEntry entry; - entry.insert(KIO::UDSEntry::UDS_NAME, "files"); + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral("files")); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, name); - entry.insert(KIO::UDSEntry::UDS_ICON_NAME, "folder"); + entry.insert(KIO::UDSEntry::UDS_ICON_NAME, icon); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH); - entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, ""); - entry.insert(KIO::UDSEntry::UDS_URL, path); + entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("")); + entry.insert(KIO::UDSEntry::UDS_URL, QUrl::fromLocalFile(path).toString()); listEntry(entry); } - infoMessage(""); + // We also need a non-null and writable UDSentry for "." + KIO::UDSEntry entry; + entry.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); + entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + entry.insert(KIO::UDSEntry::UDS_SIZE, 0); + entry.insert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); + listEntry(entry); + + infoMessage(QLatin1String("")); finished(); } -void KioKdeconnect::listDir(const QUrl &url) +void KioKdeconnect::listDir(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Listing..." << url; - /// Url is not used here becuase all we could care about the url is the host, and that's already + /// Url is not used here because all we could care about the url is the host, and that's already /// handled in @p setHost Q_UNUSED(url); if (!m_dbusInterface->isValid()) { infoMessage(i18n("Could not contact background service.")); finished(); return; } if (m_currentDevice.isEmpty()) { listAllDevices(); } else { listDevice(); } } -void KioKdeconnect::stat(const QUrl &url) +void KioKdeconnect::stat(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Stat: " << url; KIO::UDSEntry entry; entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); statEntry(entry); finished(); } -void KioKdeconnect::get(const QUrl &url) +void KioKdeconnect::get(const QUrl& url) { qCDebug(KDECONNECT_KIO) << "Get: " << url; - mimeType(""); + mimeType(QLatin1String("")); finished(); } -void KioKdeconnect::setHost(const QString &hostName, quint16 port, const QString &user, const QString &pass) +void KioKdeconnect::setHost(const QString& hostName, quint16 port, const QString& user, const QString& pass) { //This is called before everything else to set the file we want to show qCDebug(KDECONNECT_KIO) << "Setting host: " << hostName; // In this kio only the hostname is used Q_UNUSED(port) Q_UNUSED(user) Q_UNUSED(pass) m_currentDevice = hostName; } diff --git a/kio/kiokdeconnect.h b/kio/kiokdeconnect.h index f655cb22..1f5bf4dd 100644 --- a/kio/kiokdeconnect.h +++ b/kio/kiokdeconnect.h @@ -1,64 +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 KIOKDECONNECT_H #define KIOKDECONNECT_H #include #include #include #include "interfaces/dbusinterfaces.h" Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_KIO) class KioKdeconnect : public QObject, public KIO::SlaveBase { - Q_OBJECT + Q_OBJECT public: - KioKdeconnect(const QByteArray &pool, const QByteArray &app); + KioKdeconnect(const QByteArray& pool, const QByteArray& app); - void get(const QUrl &url); - void listDir(const QUrl &url); - void stat(const QUrl &url); + void get(const QUrl& url) override; + void listDir(const QUrl& url) override; + void stat(const QUrl& url) override; - void setHost(const QString &constHostname, quint16 port, const QString &user, const QString &pass); + void setHost(const QString& constHostname, quint16 port, const QString& user, const QString& pass) override; void listAllDevices(); //List all devices exported by m_dbusInterface void listDevice(); //List m_currentDevice private: /** * Contains the ID of the current device or is empty when no device is set. */ QString m_currentDevice; /** * KDED DBus interface, used to communicate to the daemon since we need some status (like connected) */ - DaemonDbusInterface *m_dbusInterface; + DaemonDbusInterface* m_dbusInterface; }; #endif diff --git a/org.kde.kdeconnect.kcm.appdata.xml b/org.kde.kdeconnect.kcm.appdata.xml new file mode 100644 index 00000000..56caf55a --- /dev/null +++ b/org.kde.kdeconnect.kcm.appdata.xml @@ -0,0 +1,127 @@ + + + org.kde.kdeconnect.kcm.desktop + CC0-1.0 + GPL-2.0+ + KDE Connect + كدي المتّصل + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE-Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + KDE Connect + КДЕ‑конекција + KDE‑konekcija + КДЕ‑конекција + KDE‑konekcija + KDE-anslut + KDE Connect + KDE Connect + xxKDE Connectxx + KDE Connect + KDE Connect + Seamless connection of your devices + اتّصال سلس بين أجهزتك + Conexón ensin torgues de los tos preseos + Connexió transparent amb els vostres dispositius + Connexió transparent amb els vostres dispositius + Snadné propojení vašich zařízení + Umærkbar forbindelse mellem dine enheder + Nahtlose Verbindung zu Ihren Geräten + Απρόσκοπτη σύνδεση των συσκευών σας + Seamless connection of your devices + Conexión sin interrupciones de sus dispositivos + Sujuv ühendus oma seadmetega + Zure gailuen etengabeko konexioa + Saumaton yhteys laitteisiisi + Connecter vos périphériques avec facilité + Conexión constante entre dispositivos + חיבור מהיר בין ההתקנים שלך + Connessione trasparente dei tuoi dispositivi + 장치와 항상 연결하기 + Naadloze verbinding met uw apparaten + Saumlaus integrering med andre einingar + Zintegrowane połączenia do twoich urządzeń + Ligação transparente aos seus dispositivos + Conexão transparente com seus dispositivos + Интеграция с мобильными устройствами + Jednoduché prepojenie vašich zariadení + Једноставно повезивање ваших уређаја + Jednostavno povezivanje vaših uređaja + Једноставно повезивање ваших уређаја + Jednostavno povezivanje vaših uređaja + Sömlös anslutning av dina apparater + Aygıtlarınızı sorunsuz bağlayın + Безшовне з’єднання для усіх ваших пристроїв + xxSeamless connection of your devicesxx + 无缝连接您的设备 + 與您的裝置無縫連線 + +

KDE Connect provides several features to integrate your phone and your computer: + + + + Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work. +

+

El KDE Connect proporciona diverses característiques per integrar el telèfon amb l'ordinador: Recordeu que haureu d'instal·lar el KDE Connect a l'ordinador perquè aquesta aplicació funcioni, i mantenir actualitzada la versió de l'escriptori amb la versió d'Android perquè funcionin les últimes característiques.

+

El KDE Connect proporciona diverses característiques per integrar el telèfon amb l'ordinador: Recordeu que haureu d'instal·lar el KDE Connect a l'ordinador perquè esta aplicació funcione, i mantindre actualitzada la versió de l'escriptori amb la versió d'Android perquè funcionen les últimes característiques.

+

KDE Connect poskytuje několik vlastností pro vzájemnou integraci vašeho telefonu a počítače: Prosím pamatujte, že pro správnou funkci této aplikace potřebujete nainstalovat KDE Connect na váš počítač a pro správnou funkci nejnovějších vlastností udržovat aplikaci na telefonu i počítači aktuální.

+

KDE Connect tilbyder adskillige funktioner, der integrerer din telefon og din computer: Bemærk at du vil skulle installere KDE Connect på din computer for at denne app virker, samt holde desktop-versionen opdateret sammen med Android-versionen, for at de nyeste funktioner vil virke.

+

KDE-Connect bietet mehrere Funktionen zur Integration Ihres Telefons und Ihres Rechners: Bitte beachten Sie, dass Sie KDE-Connect auf Ihrem Rechner installieren müssen, damit diese App funktioniert. Halten Sie die Arbeitsflächen-Version und Android-Version auf dem neusten Stand, damit Sie die neuesten Funktionen nutzen können

+

Το KDE Connect παρέχει πολλές λειτουργίες ενσωμάτωσης μεταξύ τηλεφωνικής συσκευής και υπολογιστή: Σημειώστε ότι για την εφαρμογή αυτή θα χρειαστεί να εγκαταστήσετε το KDE Connect στον υπολογιστή σας και να διατηρείτε την επιφάνεια εργασίας ενημερωμένη με την έκδοση του Android για τις πιο πρόσφατες λειτουργίες.

+

KDE Connect provides several features to integrate your phone and your computer: Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work.

+

KDE Connect proporciona varias funcionalidades para integrar su teléfono y su equipo: Por favor, tenga en cuenta que deberá instalar KDE Connect en su equipo para que esta aplicación funcione. Además, deberá mantener la versión de escritorio actualizada con su versión de Android para beneficiarse de las últimas funcionalidades.

+

KDE Connect-ek eginbide ugari ditu zure telefonoa eta ordenagailua integratzeko: KDE Connect instalatu beharko duzu zure ordenagailuan aplikazioa hau ibili dadin, eta mahaigaineko bertsioa Androiden bertsioarekin eguneratuta izan beharko duzu eginbide berrienak ibili daitezen.

+

KDE Connect fournit un ensemble de fonctionnalités pour intégrer votre téléphone et votre ordinateur : veuillez noter qu'il est nécessaire d'installer KDE Connect sur votre ordinateur pour faire fonctionner cette application, ainsi que de maintenir le logiciel à jour pour profiter des dernières fonctionnalités.

+

KDE Connect fornece varias funcionalidades para integrar teléfonos con computadores. Teña en conta que terá que instalar KDE Connect no seu computador para que este aplicativo funcione, e manter a versión de escritorio sincronizada coa de Android para que as últimas funcionalidades funcionen.

+

KDE Connect fornisce diverse funzioni per integrare il tuo telefono e il tuo computer: affinché questa applicazione funzioni, dovrai installare KDE Connect sul tuo computer e mantenere la versione desktop aggiornata alla versione Android, in modo che le funzioni più recenti siano attive.

+

KDE Connect biedt verschillende functies om uw telefoon en uw computer: merk op dat het nodig is om KDE Connect op uw computer te installeren om deze app te laten werken en de bureabladversie up-to-date te houden met de Android-versie om de laatste functies te laten werken.

+

KDE Connect har fleire funksjonar for integrering av telefonen og datamaskina di. Merk at du må installera KDE Connect på datamaskina for at denne appen skal fungera. Viss du vil bruka dei nyaste funksjonane, må du òg hugsa på å halda datamaskinversjonen oppdatert når du oppdaterer appen.

+

KDE Connect zapewnia kilka możliwości integracji twojego telefonu z twoim komputerem: Musisz mieć wgrane KDE Connect na swoim komputerze, aby ta aplikacja zadziałała, a także aktualną wersję zgodną z wersją Androida, aby wszystkie najnowsze funkcje działały.

+

O KDE Connect oferece diversas funcionalidades para integrar o seu telefone com o seu computador: Lembre-se por favor que terá de instalar o KDE Connect com o seu computador para esta aplicação funcionar e deverá manter a versão no ambiente de trabalho actualizada com a versão para Android, para que as últimas funcionalidades funcionem.

+

KDE Connect предоставляет несколько функций для интеграции телефона и компьютера. Обратите внимание, что для работы этого приложения необходимо установить KDE Connect на свой компьютер и, для работы новейших функций, держать версию обновлённой в соответствии с версией для Android.

+

КДЕ‑конекција поседује вишеструке могућности интеграције вашег телефона и рачунара. Морате да инсталирате КДЕ‑конекцију на ваш рачунар да би ова апликација радила. Такође, нека вам обе апликације буду ажурне како бисте уживали у најновијим могућностима.

+

KDE‑konekcija poseduje višestruke mogućnosti integracije vašeg telefona i računara. Morate da instalirate KDE‑konekciju na vaš računar da bi ova aplikacija radila. Takođe, neka vam obe aplikacije budu ažurne kako biste uživali u najnovijim mogućnostima.

+

КДЕ‑конекција поседује вишеструке могућности интеграције вашег телефона и рачунара. Морате да инсталирате КДЕ‑конекцију на ваш рачунар да би ова апликација радила. Такође, нека вам обе апликације буду ажурне како бисте уживали у најновијим могућностима.

+

KDE‑konekcija poseduje višestruke mogućnosti integracije vašeg telefona i računara. Morate da instalirate KDE‑konekciju na vaš računar da bi ova aplikacija radila. Takođe, neka vam obe aplikacije budu ažurne kako biste uživali u najnovijim mogućnostima.

+

KDE-anslut tillhandahåller flera funktioner för att integrera telefon och dator. Observera att KDE-anslut måste installeras på datorn för att appen ska fungera, och att skrivbordsdatorns version måste hållas uppdaterad tillsammans med Androidversionen för att de senaste funktionerna ska fungera.

+

KDE Connect, telefonunuzu ve bilgisayarınızı bütünleştirmek için çeşitli özellikler sunar: Bu uygulamanın çalışabilmesi için bilgisayarınıza KDE Connect'i yüklemeniz ve en son özelliklerin çalışabilmesi için masaüstü sürümünün Android sürümü ile güncel tutulması gereklidir.

+

У KDE Connect передбачено декілька можливостей для інтеграції вашого телефону з вашим комп’ютером. Будь ласка, зауважте, що вам доведеться встановити KDE Connect на ваш комп’ютер, щоб скористатися цією програмою, а також підтримувати актуальність версії на комп’ютері та версії для Android, щоб мати змогу скористатися найсвіжішими можливостями.

+

xxKDE Connect provides several features to integrate your phone and your computer: Please note you will need to install KDE Connect on your computer for this app to work, and keep the desktop version up-to-date with the Android version for the latest features to work.xx

+

KDE Connect 提供了很多整合手机和电脑的功能特性:请注意您需要在电脑上安装 KDE Connect 应用才能使其工作,为了使最新特性能够生效,您必须更新桌面应用和 Android 应用到最新版本。

+
+ https://community.kde.org/KDEConnect/ + https://bugs.kde.org/enter_bug.cgi?format=guided&product=kdeconnect + https://docs.kde.org/index.php?application=kdeconnect-kde&language=en + + + https://cdn.kde.org/screenshots/kdeconnect/plasmoid.png + https://cdn.kde.org/screenshots/kdeconnect/kcm.png + https://cdn.kde.org/screenshots/kdeconnect/gnome3.png + + + KDE + + kdeconnectd + kdeconnect-cli + +
diff --git a/plasmoid/declarativeplugin/CMakeLists.txt b/plasmoid/declarativeplugin/CMakeLists.txt index b483e790..077b0e0e 100644 --- a/plasmoid/declarativeplugin/CMakeLists.txt +++ b/plasmoid/declarativeplugin/CMakeLists.txt @@ -1,24 +1,23 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) set(kdeconnectdeclarativeplugin_SRC kdeconnectdeclarativeplugin.cpp responsewaiter.cpp - processrunner.cpp ) set(kdeconnectdeclarativeplugin_MOC objectfactory.h ) qt5_wrap_cpp(kdeconnectdeclarativeplugin_SRC ${kdeconnectdeclarativeplugin_MOC}) add_library(kdeconnectdeclarativeplugin SHARED ${kdeconnectdeclarativeplugin_SRC}) target_link_libraries(kdeconnectdeclarativeplugin Qt5::Qml kdeconnectinterfaces ) install(TARGETS kdeconnectdeclarativeplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect) -install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect) +install(FILES qmldir qml/PluginChecker.qml qml/DBusProperty.qml DESTINATION ${QML_INSTALL_DIR}/org/kde/kdeconnect) diff --git a/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp b/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp index 269b7ef0..f53b050b 100644 --- a/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp +++ b/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.cpp @@ -1,112 +1,133 @@ /** * 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 "kdeconnectdeclarativeplugin.h" #include #include #include #include #include #include "objectfactory.h" #include "responsewaiter.h" -#include "processrunner.h" #include "interfaces/devicessortproxymodel.h" #include "interfaces/devicesmodel.h" #include "interfaces/notificationsmodel.h" -QObject* createDeviceDbusInterface(QVariant deviceId) +QObject* createDeviceDbusInterface(const QVariant& deviceId) { return new DeviceDbusInterface(deviceId.toString()); } -QObject* createDeviceBatteryDbusInterface(QVariant deviceId) +QObject* createDeviceBatteryDbusInterface(const QVariant& deviceId) { return new DeviceBatteryDbusInterface(deviceId.toString()); } -QObject* createSftpInterface(QVariant deviceId) +QObject* createFindMyPhoneInterface(const QVariant& deviceId) +{ + return new FindMyPhoneDeviceDbusInterface(deviceId.toString()); +} + +QObject* createRemoteKeyboardInterface(const QVariant& deviceId) +{ + return new RemoteKeyboardDbusInterface(deviceId.toString()); +} + +QObject* createSftpInterface(const QVariant& deviceId) { return new SftpDbusInterface(deviceId.toString()); } -QObject* createRemoteControlInterface(QVariant deviceId) +QObject* createRemoteControlInterface(const QVariant& deviceId) { return new RemoteControlDbusInterface(deviceId.toString()); } -QObject* createMprisInterface(QVariant deviceId) +QObject* createMprisInterface(const QVariant& deviceId) { return new MprisDbusInterface(deviceId.toString()); } -QObject* createDeviceLockInterface(QVariant deviceId) +QObject* createDeviceLockInterface(const QVariant& deviceId) { Q_ASSERT(!deviceId.toString().isEmpty()); return new LockDeviceDbusInterface(deviceId.toString()); } QObject* createDBusResponse() { return new DBusAsyncResponse(); } void KdeConnectDeclarativePlugin::registerTypes(const char* uri) { qmlRegisterType(uri, 1, 0, "DevicesModel"); qmlRegisterType(uri, 1, 0, "NotificationsModel"); qmlRegisterType(uri, 1, 0, "DBusAsyncResponse"); - qmlRegisterType(uri, 1, 0, "ProcessRunner"); qmlRegisterType(uri, 1, 0, "DevicesSortProxyModel"); qmlRegisterUncreatableType(uri, 1, 0, "MprisDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterUncreatableType(uri, 1, 0, "LockDeviceDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); + qmlRegisterUncreatableType(uri, 1, 0, "FindMyPhoneDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); + qmlRegisterUncreatableType(uri, 1, 0, "RemoteKeyboardDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); qmlRegisterUncreatableType(uri, 1, 0, "DeviceDbusInterface", QStringLiteral("You're not supposed to instantiate interfacess")); + qmlRegisterSingletonType(uri, 1, 0, "DaemonDbusInterface", + [](QQmlEngine*, QJSEngine*) -> QObject* { + return new DaemonDbusInterface; + } + ); } void KdeConnectDeclarativePlugin::initializeEngine(QQmlEngine* engine, const char* uri) { QQmlExtensionPlugin::initializeEngine(engine, uri); - engine->rootContext()->setContextProperty("DeviceDbusInterfaceFactory" + engine->rootContext()->setContextProperty(QStringLiteral("DeviceDbusInterfaceFactory") , new ObjectFactory(engine, createDeviceDbusInterface)); - engine->rootContext()->setContextProperty("DeviceBatteryDbusInterfaceFactory" + engine->rootContext()->setContextProperty(QStringLiteral("DeviceBatteryDbusInterfaceFactory") , new ObjectFactory(engine, createDeviceBatteryDbusInterface)); - engine->rootContext()->setContextProperty("SftpDbusInterfaceFactory" + engine->rootContext()->setContextProperty(QStringLiteral("FindMyPhoneDbusInterfaceFactory") + , new ObjectFactory(engine, createFindMyPhoneInterface)); + + engine->rootContext()->setContextProperty(QStringLiteral("SftpDbusInterfaceFactory") , new ObjectFactory(engine, createSftpInterface)); - engine->rootContext()->setContextProperty("MprisDbusInterfaceFactory" + engine->rootContext()->setContextProperty(QStringLiteral("RemoteKeyboardDbusInterfaceFactory") + , new ObjectFactory(engine, createRemoteKeyboardInterface)); + + engine->rootContext()->setContextProperty(QStringLiteral("MprisDbusInterfaceFactory") , new ObjectFactory(engine, createMprisInterface)); - engine->rootContext()->setContextProperty("RemoteControlDbusInterfaceFactory" + engine->rootContext()->setContextProperty(QStringLiteral("RemoteControlDbusInterfaceFactory") , new ObjectFactory(engine, createRemoteControlInterface)); - engine->rootContext()->setContextProperty("LockDeviceDbusInterfaceFactory" + engine->rootContext()->setContextProperty(QStringLiteral("LockDeviceDbusInterfaceFactory") , new ObjectFactory(engine, createDeviceLockInterface)); - engine->rootContext()->setContextProperty("DBusResponseFactory" + engine->rootContext()->setContextProperty(QStringLiteral("DBusResponseFactory") , new ObjectFactory(engine, createDBusResponse)); - engine->rootContext()->setContextProperty("DBusResponseWaiter" + engine->rootContext()->setContextProperty(QStringLiteral("DBusResponseWaiter") , DBusResponseWaiter::instance()); } diff --git a/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.h b/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.h index 85d0cf04..f823418d 100644 --- a/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.h +++ b/plasmoid/declarativeplugin/kdeconnectdeclarativeplugin.h @@ -1,36 +1,36 @@ /** * 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 KDECONNECTDECLARATIVEPLUGIN_H #define KDECONNECTDECLARATIVEPLUGIN_H #include class KdeConnectDeclarativePlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") - virtual void registerTypes(const char* uri); - virtual void initializeEngine(QQmlEngine *engine, const char *uri); + void registerTypes(const char* uri) override; + void initializeEngine(QQmlEngine* engine, const char* uri) override; }; #endif // KDECONNECTDECLARATIVEPLUGIN_H diff --git a/plasmoid/declarativeplugin/objectfactory.h b/plasmoid/declarativeplugin/objectfactory.h index 0a4a8ca5..bb597441 100644 --- a/plasmoid/declarativeplugin/objectfactory.h +++ b/plasmoid/declarativeplugin/objectfactory.h @@ -1,46 +1,46 @@ #ifndef QOBJECT_FACTORY_H #define QOBJECT_FACTORY_H #include #include //Wraps a factory function with QObject class to be exposed to qml context as named factory class ObjectFactory : public QObject { Q_OBJECT typedef QObject* (*Func0)(); - typedef QObject* (*Func1)(QVariant); - typedef QObject* (*Func2)(QVariant, QVariant); + typedef QObject* (*Func1)(const QVariant&); + typedef QObject* (*Func2)(const QVariant&, const QVariant&); public: - ObjectFactory(QObject* parent, Func0 f0) : QObject(parent), m_f0(f0), m_f1(0), m_f2(0) {} - ObjectFactory(QObject* parent, Func1 f1) : QObject(parent), m_f0(0), m_f1(f1), m_f2(0) {} - ObjectFactory(QObject* parent, Func2 f2) : QObject(parent), m_f0(0), m_f1(0), m_f2(f2) {} - - virtual ~ObjectFactory() {} - + ObjectFactory(QObject* parent, Func0 f0) : QObject(parent), m_f0(f0), m_f1(nullptr), m_f2(nullptr) {} + ObjectFactory(QObject* parent, Func1 f1) : QObject(parent), m_f0(nullptr), m_f1(f1), m_f2(nullptr) {} + ObjectFactory(QObject* parent, Func2 f2) : QObject(parent), m_f0(nullptr), m_f1(nullptr), m_f2(f2) {} + ~ObjectFactory() override = default; + Q_INVOKABLE QObject* create() { - if (m_f0) return m_f0(); return 0; + if (m_f0) return m_f0(); + return nullptr; } - Q_INVOKABLE QObject* create(QVariant arg1) { + Q_INVOKABLE QObject* create(const QVariant& arg1) { if (m_f1) return m_f1(arg1); - return 0; + return nullptr; } - Q_INVOKABLE QObject* create(QVariant arg1, QVariant arg2) { + Q_INVOKABLE QObject* create(const QVariant& arg1, const QVariant& arg2) { if (m_f2) return m_f2(arg1, arg2); - return 0; + return nullptr; } private: Func0 m_f0; Func1 m_f1; Func2 m_f2; }; #endif diff --git a/plasmoid/declarativeplugin/processrunner.cpp b/plasmoid/declarativeplugin/processrunner.cpp deleted file mode 100644 index aa57aaf9..00000000 --- a/plasmoid/declarativeplugin/processrunner.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2013 by Eike Hein * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -#include "processrunner.h" - -#include - -ProcessRunner::ProcessRunner(QObject *parent) : QObject(parent) -{ -} - -ProcessRunner::~ProcessRunner() -{ -} - -void ProcessRunner::runKdeconnectKCM() -{ - QProcess::startDetached("kcmshell5", QStringList() << "kcm_kdeconnect"); -} diff --git a/plasmoid/declarativeplugin/processrunner.h b/plasmoid/declarativeplugin/processrunner.h deleted file mode 100644 index 20e86e69..00000000 --- a/plasmoid/declarativeplugin/processrunner.h +++ /dev/null @@ -1,36 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2013 by Eike Hein * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * - ***************************************************************************/ - -#ifndef PROCESSRUNNER_H -#define PROCESSRUNNER_H - -#include - -class ProcessRunner : public QObject -{ - Q_OBJECT - -public: - ProcessRunner(QObject *parent = 0); - ~ProcessRunner(); - - Q_INVOKABLE void runKdeconnectKCM(); -}; - -#endif diff --git a/plasmoid/declarativeplugin/qml/DBusProperty.qml b/plasmoid/declarativeplugin/qml/DBusProperty.qml new file mode 100644 index 00000000..30527de1 --- /dev/null +++ b/plasmoid/declarativeplugin/qml/DBusProperty.qml @@ -0,0 +1,69 @@ +/** + * Copyright 2016 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 QtQml 2.2 +import org.kde.kdeconnect 1.0 + +QtObject { + id: prop + property QtObject object: null + property string read + property string change: read+"Changed" + + Component.onCompleted: get(); + + onChangeChanged: { + if (object) { + var theSignal = object[change]; + if (theSignal) { + theSignal.connect(valueReceived); + } else { + console.warn("couldn't find signal", change, "for", object) + } + } + } + + function valueReceived(val) { + if (!val) { + get(); + } else { + _value = val; + } + } + + property var defaultValue + property var _value: defaultValue + readonly property var value: _value + + readonly property var v: DBusAsyncResponse { + id: response + autoDelete: false + onSuccess: { + prop._value = result; + } + onError: { + console.warn("failed call", object, read, write, change) + } + } + + function get() { + response.setPendingCall(object[read]()); + } +} diff --git a/plasmoid/package/contents/ui/Sftp.qml b/plasmoid/declarativeplugin/qml/PluginChecker.qml similarity index 57% copy from plasmoid/package/contents/ui/Sftp.qml copy to plasmoid/declarativeplugin/qml/PluginChecker.qml index 9ef4f417..dfd1114e 100644 --- a/plasmoid/package/contents/ui/Sftp.qml +++ b/plasmoid/declarativeplugin/qml/PluginChecker.qml @@ -1,60 +1,50 @@ /** * Copyright 2014 Samoilenko Yuri + * Copyright 2016 David Kahles * * 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.1 -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents +import QtQml 2.2 import org.kde.kdeconnect 1.0 QtObject { id: root - property string deviceId: "" - property variant device: DeviceDbusInterfaceFactory.create(deviceId) + property alias device: conn.target + property string pluginName: "" property bool available: false - property variant sftp: null - - function browse() { - if (sftp) - sftp.startBrowsing(); + readonly property Connections connection: Connections { + id: conn + onPluginsChanged: pluginsChanged() } - Component.onCompleted: { - device.pluginsChanged.connect(pluginsChanged) - pluginsChanged() - } + Component.onCompleted: pluginsChanged() - // Note: magically called by qml - onAvailableChanged: { - if (available) { - sftp = SftpDbusInterfaceFactory.create(deviceId) - } else { - sftp = null - } + readonly property var v: DBusAsyncResponse { + id: response + autoDelete: false + onSuccess: { root.available = result; } + onError: { root.available = false } } function pluginsChanged() { - var result = DBusResponseWaiter.waitForReply(device.hasPlugin("kdeconnect_sftp")) - available = (result && result != "error"); + response.setPendingCall(device.hasPlugin("kdeconnect_" + pluginName)) } - } diff --git a/plasmoid/declarativeplugin/qmldir b/plasmoid/declarativeplugin/qmldir index a98571e5..80d0bc18 100644 --- a/plasmoid/declarativeplugin/qmldir +++ b/plasmoid/declarativeplugin/qmldir @@ -1,2 +1,5 @@ module org.kde.kdeconnect plugin kdeconnectdeclarativeplugin + +PluginChecker 1.0 PluginChecker.qml +DBusProperty 1.0 DBusProperty.qml diff --git a/plasmoid/declarativeplugin/responsewaiter.cpp b/plasmoid/declarativeplugin/responsewaiter.cpp index e909842c..6288e782 100644 --- a/plasmoid/declarativeplugin/responsewaiter.cpp +++ b/plasmoid/declarativeplugin/responsewaiter.cpp @@ -1,132 +1,132 @@ +#include "responsewaiter.h" #include #include #include #include -#include "responsewaiter.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 = 0; +DBusResponseWaiter* DBusResponseWaiter::m_instance = nullptr; DBusResponseWaiter* DBusResponseWaiter::instance() { if (!m_instance) { m_instance = new DBusResponseWaiter(); } return m_instance; } DBusResponseWaiter::DBusResponseWaiter() : QObject() { m_registered << qRegisterMetaType >("QDBusPendingReply<>") << qRegisterMetaType >("QDBusPendingReply") << qRegisterMetaType >("QDBusPendingReply") << qRegisterMetaType >("QDBusPendingReply") << qRegisterMetaType >("QDBusPendingReply") ; } QVariant DBusResponseWaiter::waitForReply(QVariant variant) const { if (QDBusPendingCall* call = const_cast(extractPendingCall(variant))) { call->waitForFinished(); if (call->isError()) { + qWarning() << "error:" << call->error(); return QVariant("error"); } QDBusMessage reply = call->reply(); if (reply.arguments().count() > 0) { - return reply.arguments().first(); + 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, SIGNAL(timeout()), this, SLOT(onTimeout())); + 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, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onCallFinished(QDBusPendingCallWatcher*))); - connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), watcher, SLOT(deleteLater())); - connect(&m_timeout, SIGNAL(timeout()), watcher, SLOT(deleteLater())); + 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().first()); + Q_EMIT success(reply.arguments().at(0)); } else { Q_EMIT success(QVariant()); } } } if (m_autodelete) { deleteLater(); } } void DBusAsyncResponse::onTimeout() { - Q_EMIT error("timeout when waiting dbus response!"); + Q_EMIT error(QStringLiteral("timeout when waiting dbus response!")); } const QDBusPendingCall* DBusResponseWaiter::extractPendingCall(QVariant& variant) const { - Q_FOREACH(int type, m_registered) + for (int type : qAsConst(m_registered)) { if (variant.canConvert(QVariant::Type(type))) { return reinterpret_cast(variant.constData()); } } - return 0; + return nullptr; } diff --git a/plasmoid/declarativeplugin/responsewaiter.h b/plasmoid/declarativeplugin/responsewaiter.h index a2aec199..35c8abc8 100644 --- a/plasmoid/declarativeplugin/responsewaiter.h +++ b/plasmoid/declarativeplugin/responsewaiter.h @@ -1,61 +1,61 @@ #ifndef RESPONSE_WAITER_H #define RESPONSE_WAITER_H #include #include #include class QDBusPendingCall; class QDBusPendingCallWatcher; class DBusResponseWaiter : public QObject { Q_OBJECT public: static DBusResponseWaiter* instance(); ///extract QDbusPendingCall from \p variant and blocks untill completed Q_INVOKABLE QVariant waitForReply(QVariant variant) const; const QDBusPendingCall* extractPendingCall(QVariant& variant) const; private: DBusResponseWaiter(); static DBusResponseWaiter* m_instance; QList m_registered; }; class DBusAsyncResponse : public QObject { Q_OBJECT Q_PROPERTY(bool autoDelete READ autodelete WRITE setAutodelete) public: - DBusAsyncResponse(QObject* parent = 0); - virtual ~DBusAsyncResponse() {}; + explicit DBusAsyncResponse(QObject* parent = nullptr); + ~DBusAsyncResponse() override = default; Q_INVOKABLE void setPendingCall(QVariant e); void setAutodelete(bool b) {m_autodelete = b;}; bool autodelete() const {return m_autodelete;} Q_SIGNALS: - void success(QVariant result); - void error(QString message); + void success(const QVariant& result); + void error(const QString& message); private Q_SLOTS: void onCallFinished(QDBusPendingCallWatcher* watcher); void onTimeout(); private: QTimer m_timeout; bool m_autodelete; }; #endif diff --git a/plasmoid/package/contents/ui/Battery.qml b/plasmoid/package/contents/ui/Battery.qml index 4538437b..51e130e6 100644 --- a/plasmoid/package/contents/ui/Battery.qml +++ b/plasmoid/package/contents/ui/Battery.qml @@ -1,80 +1,68 @@ /** * Copyright 2014 Samoilenko Yuri * * 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.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kdeconnect 1.0 QtObject { id: root - property string deviceId: "" - property variant device: DeviceDbusInterfaceFactory.create(deviceId) - property bool available: false + property alias device: checker.device + readonly property alias available: checker.available + + readonly property PluginChecker pluginChecker: PluginChecker { + id: checker + pluginName: "battery" + } property bool charging: false property int charge: -1 - property string displayString: (available && charge > -1) ? ((charging) ? (i18n("Charging: %1%", charge)) : (i18n("Discharging: %1%", charge))) : i18n("No info") + property string displayString: (available && charge > -1) ? ((charging) ? (i18n("%1% charging", charge)) : (i18n("%1%", charge))) : i18n("No info") property variant battery: null property variant nested1: DBusAsyncResponse { id: startupCheck1 autoDelete: false onSuccess: root.charging = result } property variant nested2: DBusAsyncResponse { id: startupCheck2 autoDelete: false onSuccess: root.charge = result } - // Note: magically called by qml onAvailableChanged: { if (available) { - battery = DeviceBatteryDbusInterfaceFactory.create(deviceId) + battery = DeviceBatteryDbusInterfaceFactory.create(device.id()) battery.stateChanged.connect(function(c) {charging = c}) battery.chargeChanged.connect(function(c) {charge = c}) startupCheck1.setPendingCall(battery.isCharging()) startupCheck2.setPendingCall(battery.charge()) } else { battery = null } } - - function pluginsChanged() { - var result = DBusResponseWaiter.waitForReply(device.hasPlugin("kdeconnect_battery")) - - if (result && result != "error") { - available = true - } else { - available = false - } - } - - Component.onCompleted: { - device.pluginsChanged.connect(pluginsChanged) - device.pluginsChanged() - } } diff --git a/plasmoid/package/contents/ui/CompactRepresentation.qml b/plasmoid/package/contents/ui/CompactRepresentation.qml index c4edb6d4..af58dccf 100644 --- a/plasmoid/package/contents/ui/CompactRepresentation.qml +++ b/plasmoid/package/contents/ui/CompactRepresentation.qml @@ -1,32 +1,34 @@ /** * 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 . */ import QtQuick 2.1 import org.kde.plasma.core 2.0 as PlasmaCore -Item { +MouseArea { id: view + onClicked: plasmoid.expanded = !plasmoid.expanded + PlasmaCore.IconItem { id: icon source: "kdeconnect" anchors.fill: parent } } diff --git a/plasmoid/package/contents/ui/DeviceDelegate.qml b/plasmoid/package/contents/ui/DeviceDelegate.qml index d774f66b..b2eafa7a 100644 --- a/plasmoid/package/contents/ui/DeviceDelegate.qml +++ b/plasmoid/package/contents/ui/DeviceDelegate.qml @@ -1,131 +1,260 @@ /** * 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 . */ import QtQuick 2.1 +import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kdeconnect 1.0 +import QtQuick.Controls.Styles 1.4 PlasmaComponents.ListItem { id: root - property string deviceId: model.deviceId - + readonly property QtObject device: DeviceDbusInterfaceFactory.create(model.deviceId) + + RemoteKeyboard { + id: remoteKeyboard + device: root.device + + onRemoteStateChanged: { + remoteKeyboardInput.available = remoteKeyboard.remoteState; + } + + onKeyPressReceived: { +// console.log("XXX received keypress key=" + key + " special=" + specialKey + " shift=" + shift + " ctrl=" + ctrl + " text=" + remoteKeyboardInput.text + " cursorPos=" + remoteKeyboardInput.cursorPosition); + // interpret some special keys: + if (specialKey == 12 || specialKey == 14) // Return/Esc -> clear + remoteKeyboardInput.text = ""; + else if (specialKey == 4 // Left + && remoteKeyboardInput.cursorPosition > 0) + --remoteKeyboardInput.cursorPosition; + else if (specialKey == 6 // Right + && remoteKeyboardInput.cursorPosition < remoteKeyboardInput.text.length) + ++remoteKeyboardInput.cursorPosition; + else if (specialKey == 1) { // Backspace -> delete left + var pos = remoteKeyboardInput.cursorPosition; + if (pos > 0) { + remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos-1) + + remoteKeyboardInput.text.substring(pos, remoteKeyboardInput.text.length); + remoteKeyboardInput.cursorPosition = pos - 1; + } + } else if (specialKey == 13) { // Delete -> delete right + var pos = remoteKeyboardInput.cursorPosition; + if (pos < remoteKeyboardInput.text.length) { + remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos) + + remoteKeyboardInput.text.substring(pos+1, remoteKeyboardInput.text.length); + remoteKeyboardInput.cursorPosition = pos; // seems to be set to text.length automatically! + } + } else if (specialKey == 10) // Home + remoteKeyboardInput.cursorPosition = 0; + else if (specialKey == 11) // End + remoteKeyboardInput.cursorPosition = remoteKeyboardInput.text.length; + else { + // echo visible keys + var sanitized = ""; + for (var i = 0; i < key.length; i++) { + if (key.charCodeAt(i) > 31) + sanitized += key.charAt(i); + } + if (sanitized.length > 0 && !ctrl && !alt) { + // insert sanitized at current pos: + var pos = remoteKeyboardInput.cursorPosition; + remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos) + + sanitized + + remoteKeyboardInput.text.substring(pos, remoteKeyboardInput.text.length); + remoteKeyboardInput.cursorPosition = pos + 1; // seems to be set to text.length automatically! + } + } +// console.log("XXX After received keypress key=" + key + " special=" + specialKey + " shift=" + shift + " ctrl=" + ctrl + " text=" + remoteKeyboardInput.text + " cursorPos=" + remoteKeyboardInput.cursorPosition); + } + } - Column { width: parent.width - Row + RowLayout { + Item { + //spacer to make the label centre aligned in a row yet still elide and everything + implicitWidth: (ring.visible? ring.width : 0) + (browse.visible? browse.width : 0) + parent.spacing + } + + Battery { + id: battery + device: root.device + } + PlasmaComponents.Label { - width: browse.visible? parent.width - browse.width : parent.width horizontalAlignment: Text.AlignHCenter - text: display + elide: Text.ElideRight + text: (battery.available && battery.charge > -1) ? i18n("%1 (%2)", display, battery.displayString) : display + Layout.fillWidth: true + textFormat: Text.PlainText + } + + //Find my phone + PlasmaComponents.Button + { + FindMyPhone { + id: findmyphone + device: root.device + } + + id: ring + iconSource: "irc-voice" + visible: findmyphone.available + tooltip: i18n("Ring my phone") + + onClicked: { + findmyphone.ring() + } } + //SFTP PlasmaComponents.Button { Sftp { id: sftp - deviceId: root.deviceId + device: root.device } id: browse iconSource: "document-open-folder" visible: sftp.available + tooltip: i18n("Browse this device") onClicked: { sftp.browse() } } - height: browse.height width: parent.width } - //Battery + //RemoteKeyboard PlasmaComponents.ListItem { + visible: remoteKeyboardInput.available + width: parent.width - Battery { - id: battery - deviceId: root.deviceId - } + Row { + width: parent.width + spacing: 5 - sectionDelegate: true - visible: battery.available - PlasmaComponents.Label { - //font.bold: true - text: i18n("Battery") - } - PlasmaComponents.Label { - text: battery.displayString - anchors.right: parent.right + PlasmaComponents.Label { + id: remoteKeyboardLabel + text: i18n("Remote Keyboard") + } + + PlasmaComponents.TextField { + id: remoteKeyboardInput + + property bool available: remoteKeyboard.remoteState + + textColor: "black" + height: parent.height + width: parent.width - 5 - remoteKeyboardLabel.width + verticalAlignment: TextInput.AlignVCenter + readOnly: !available + enabled: available + style: TextFieldStyle { + textColor: "black" + background: Rectangle { + radius: 2 + border.color: "gray" + border.width: 1 + color: remoteKeyboardInput.available ? "white" : "lightgray" + } + } + + Keys.onPressed: { + if (remoteKeyboard.available) + remoteKeyboard.sendEvent(event); + event.accepted = true; + } + } } } //Notifications PlasmaComponents.ListItem { visible: notificationsModel.count>0 enabled: true - sectionDelegate: true PlasmaComponents.Label { - //font.bold: true - text: i18n("Notifications") + text: i18n("Notifications:") } PlasmaComponents.ToolButton { enabled: true visible: notificationsModel.isAnyDimissable; anchors.right: parent.right iconSource: "window-close" onClicked: notificationsModel.dismissAll(); } } Repeater { id: notificationsView model: NotificationsModel { id: notificationsModel - deviceId: root.deviceId + deviceId: root.device.id() } delegate: PlasmaComponents.ListItem { - PlasmaComponents.Label { - text: appName + ": " + display - anchors.right: dismissButton.left + id: listitem + enabled: true + onClicked: checked = !checked + + PlasmaCore.IconItem { + id: notificationIcon + source: appIcon + width: (valid && appIcon.length) ? dismissButton.width : 0 + height: width anchors.left: parent.left - elide: Text.ElideRight - maximumLineCount: 2 + } + PlasmaComponents.Label { + text: appName + ": " + (title.length>0 ? (appName==title?notitext:title+": "+notitext) : display) + anchors.right: replyButton.left + anchors.left: notificationIcon.right + elide: listitem.checked ? Text.ElideNone : Text.ElideRight + maximumLineCount: listitem.checked ? 0 : 1 wrapMode: Text.WordWrap } + PlasmaComponents.ToolButton { + id: replyButton + visible: repliable + enabled: repliable + anchors.right: dismissButton.left + iconSource: "mail-reply-sender" + onClicked: dbusInterface.reply(); + } PlasmaComponents.ToolButton { id: dismissButton visible: notificationsModel.isAnyDimissable; enabled: dismissable anchors.right: parent.right iconSource: "window-close" onClicked: dbusInterface.dismiss(); } } } //NOTE: More information could be displayed here } } diff --git a/plasmoid/package/contents/ui/Sftp.qml b/plasmoid/package/contents/ui/FindMyPhone.qml similarity index 64% copy from plasmoid/package/contents/ui/Sftp.qml copy to plasmoid/package/contents/ui/FindMyPhone.qml index 9ef4f417..846c1530 100644 --- a/plasmoid/package/contents/ui/Sftp.qml +++ b/plasmoid/package/contents/ui/FindMyPhone.qml @@ -1,60 +1,53 @@ /** * Copyright 2014 Samoilenko Yuri * * 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.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kdeconnect 1.0 QtObject { id: root - property string deviceId: "" - property variant device: DeviceDbusInterfaceFactory.create(deviceId) - property bool available: false + property alias device: checker.device + readonly property alias available: checker.available - property variant sftp: null - - function browse() { - if (sftp) - sftp.startBrowsing(); + readonly property PluginChecker pluginChecker: PluginChecker { + id: checker + pluginName: "findmyphone" } - Component.onCompleted: { - device.pluginsChanged.connect(pluginsChanged) - pluginsChanged() + property variant findMyPhone: null + + function ring() { + if (findMyPhone) { + findMyPhone.ring(); + } } - // Note: magically called by qml onAvailableChanged: { if (available) { - sftp = SftpDbusInterfaceFactory.create(deviceId) + findMyPhone = FindMyPhoneDbusInterfaceFactory.create(device.id()) } else { - sftp = null + findMyPhone = null } } - - function pluginsChanged() { - var result = DBusResponseWaiter.waitForReply(device.hasPlugin("kdeconnect_sftp")) - available = (result && result != "error"); - } - } diff --git a/plasmoid/package/contents/ui/FullRepresentation.qml b/plasmoid/package/contents/ui/FullRepresentation.qml index 99f25ed3..e09ae2dc 100644 --- a/plasmoid/package/contents/ui/FullRepresentation.qml +++ b/plasmoid/package/contents/ui/FullRepresentation.qml @@ -1,58 +1,58 @@ /** * 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 . */ import QtQuick 2.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kdeconnect 1.0 as KdeConnect Item { id: kdeconnect property alias devicesModel: devicesView.model PlasmaExtras.Heading { width: parent.width level: 3 opacity: 0.6 - text: i18n("No Paired Devices Available") + text: i18n("No paired devices available") visible: devicesView.count==0 } /* //Startup arguments PlasmaComponents.Label { visible: (startupArguments.length > 0) text: (""+startupArguments) anchors.fill: parent } */ PlasmaExtras.ScrollArea { id: dialogItem anchors.fill: parent ListView { id: devicesView anchors.fill: parent delegate: DeviceDelegate { } } } } diff --git a/plasmoid/package/contents/ui/RemoteKeyboard.qml b/plasmoid/package/contents/ui/RemoteKeyboard.qml new file mode 100644 index 00000000..b82f29f2 --- /dev/null +++ b/plasmoid/package/contents/ui/RemoteKeyboard.qml @@ -0,0 +1,73 @@ +/** + * Copyright 2017 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 . + */ + +import QtQuick 2.1 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.kdeconnect 1.0 + +QtObject { + + id: root + + property alias device: checker.device + readonly property alias available: checker.available + + readonly property PluginChecker pluginChecker: PluginChecker { + id: checker + pluginName: "remotekeyboard" + } + + property variant remoteKeyboard: null + + readonly property bool remoteState: available ? remoteKeyboard.remoteState : false + + signal keyPressReceived(string key, int specialKey, bool shift, bool ctrl, bool alt) + + function sendEvent(event) { + if (remoteKeyboard) { + var transEvent = JSON.parse(JSON.stringify(event)); // transform to anonymous object + if (transEvent.modifiers & Qt.ControlModifier) { + // special handling for ctrl+c/v/x/a, for which only 'key' gets + // set, but no visbile 'text', which is expected by the remoteKeyboard + // wire-format: + if (transEvent.key === Qt.Key_C) + transEvent.text = 'c'; + if (transEvent.key === Qt.Key_V) + transEvent.text = 'v'; + if (transEvent.key === Qt.Key_A) + transEvent.text = 'a'; + if (transEvent.key === Qt.Key_X) + transEvent.text = 'x'; + } + remoteKeyboard.sendQKeyEvent(transEvent); + } + } + + onAvailableChanged: { + if (available) { + remoteKeyboard = RemoteKeyboardDbusInterfaceFactory.create(device.id()); + remoteKeyboard.keyPressReceived.connect(keyPressReceived); + remoteKeyboard.remoteStateChanged.connect(remoteStateChanged); + } else { + remoteKeyboard = null + } + } +} diff --git a/plasmoid/package/contents/ui/Sftp.qml b/plasmoid/package/contents/ui/Sftp.qml index 9ef4f417..e3fc65f6 100644 --- a/plasmoid/package/contents/ui/Sftp.qml +++ b/plasmoid/package/contents/ui/Sftp.qml @@ -1,60 +1,52 @@ /** * Copyright 2014 Samoilenko Yuri * * 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.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kdeconnect 1.0 QtObject { id: root - property string deviceId: "" - property variant device: DeviceDbusInterfaceFactory.create(deviceId) - property bool available: false + property alias device: checker.device + readonly property alias available: checker.available + + readonly property PluginChecker pluginChecker: PluginChecker { + id: checker + pluginName: "sftp" + } property variant sftp: null function browse() { if (sftp) sftp.startBrowsing(); } - Component.onCompleted: { - device.pluginsChanged.connect(pluginsChanged) - pluginsChanged() - } - - // Note: magically called by qml onAvailableChanged: { if (available) { - sftp = SftpDbusInterfaceFactory.create(deviceId) + sftp = SftpDbusInterfaceFactory.create(device.id()) } else { sftp = null } } - - function pluginsChanged() { - var result = DBusResponseWaiter.waitForReply(device.hasPlugin("kdeconnect_sftp")) - available = (result && result != "error"); - } - } diff --git a/plasmoid/package/contents/ui/main.qml b/plasmoid/package/contents/ui/main.qml index fc522f41..20cc289e 100644 --- a/plasmoid/package/contents/ui/main.qml +++ b/plasmoid/package/contents/ui/main.qml @@ -1,66 +1,63 @@ /** * Copyright 2014 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) 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.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.plasmoid 2.0 +import org.kde.kquickcontrolsaddons 2.0 import org.kde.kdeconnect 1.0 Item { width: units.gridUnit * 20 height: units.gridUnit * 32 - function isConstrained() { - return (plasmoid.formFactor == PlasmaCore.Types.Vertical || plasmoid.formFactor == PlasmaCore.Types.Horizontal); - } - DevicesModel { id: connectDeviceModel displayFilter: DevicesModel.Paired | DevicesModel.Reachable } Binding { target: plasmoid property: "status" value: (connectDeviceModel.count > 0) ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus } Plasmoid.compactRepresentation: CompactRepresentation {} Plasmoid.fullRepresentation: FullRepresentation { devicesModel: connectDeviceModel } - Plasmoid.preferredRepresentation: isConstrained() ? Plasmoid.compactRepresentation : Plasmoid.fullRepresentation + readonly property bool isConstrained: (plasmoid.formFactor == PlasmaCore.Types.Vertical || plasmoid.formFactor == PlasmaCore.Types.Horizontal) - ProcessRunner { - id: processRunner - } + Plasmoid.preferredRepresentation: isConstrained ? Plasmoid.compactRepresentation : Plasmoid.fullRepresentation function action_launchkcm() { - processRunner.runKdeconnectKCM(); + KCMShell.open("kcm_kdeconnect"); } Component.onCompleted: { plasmoid.removeAction("configure"); - plasmoid.setAction("launchkcm", i18n("KDE Connect Settings..."), "configure"); + if (KCMShell.authorize("kcm_kdeconnect.desktop").length > 0) { + plasmoid.setAction("launchkcm", i18n("KDE Connect Settings..."), "configure"); + } } } diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop index 28cf1236..be02f27a 100644 --- a/plasmoid/package/metadata.desktop +++ b/plasmoid/package/metadata.desktop @@ -1,73 +1,99 @@ [Desktop Entry] Name=KDE Connect +Name[ar]=كدي المتّصل +Name[ast]=KDE Connect Name[bg]=KDE Connect Name[bs]=Konekcija KDE Name[ca]=KDE Connect +Name[ca@valencia]=KDE Connect Name[cs]=KDE Connect Name[da]=KDE Connect Name[de]=KDE-Connect +Name[el]=KDE Connect Name[en_GB]=KDE Connect Name[es]=KDE Connect +Name[et]=KDE Connect +Name[eu]=KDE Connect Name[fi]=KDE Connect Name[fr]=KDE Connect Name[gl]=KDE Connect +Name[he]=KDE Connect Name[hu]=KDE csatlakozás Name[it]=KDE Connect Name[ko]=KDE Connect Name[nl]=KDE Connect +Name[nn]=KDE Connect Name[pl]=KDE Connect Name[pt]=KDE Connect Name[pt_BR]=KDE Connect Name[ro]=KDE Connect Name[ru]=KDE Connect Name[sk]=KDE Connect -Name[sv]=KDE anslut -Name[tr]=KDE Bağlan +Name[sr]=КДЕ‑конекција +Name[sr@ijekavian]=КДЕ‑конекција +Name[sr@ijekavianlatin]=KDE‑konekcija +Name[sr@latin]=KDE‑konekcija +Name[sv]=KDE-anslut +Name[tr]=KDE Connect Name[uk]=З’єднання KDE Name[x-test]=xxKDE Connectxx Name[zh_CN]=KDE Connect +Name[zh_TW]=KDE 連線 Comment=Show notifications from your devices using KDE Connect +Comment[ar]=أظهر الإخطارات من أجهزتك باستخدام «كدي المتّصل» +Comment[ast]=Amuesa avisos de los tos preseos qu'usen KDE Connect Comment[bg]=Показване на уведомления от вашите устройства чрез KDE Connect Comment[bs]=Prikaži obavlještenja sa uređaja koji koriste KDE konekciju -Comment[ca]=Mostra les notificacions des dels vostres dispositius usant KDE Connect +Comment[ca]=Mostra les notificacions dels vostres dispositius emprant el KDE Connect +Comment[ca@valencia]=Mostra les notificacions dels vostres dispositius emprant el KDE Connect Comment[cs]=Zobrazit upozornění z vašich zařízení pomocí KDE Connect Comment[da]=Vis bekendtgørelser fra dine enheder med KDE Connect Comment[de]=Zeigt Benachrichtigungen von Ihren Geräten mit KDE-Connect +Comment[el]=Προβολή ειδοποιήσεων από τις συσκευές σας με το KDE Connect Comment[en_GB]=Show notifications from your devices using KDE Connect Comment[es]=Mostrar notificaciones de sus dispositivos usando KDE Connect +Comment[et]=Seadmete märguannete näitamine KDE Connecti vahendusel +Comment[eu]=Ikusi zure gailuetako jakinarazpenak KDE Connect erabiliz Comment[fi]=Näytä laitteidesi ilmoitukset KDE Connectilla Comment[fr]=Afficher les notifications provenant de vos périphériques à l'aide de KDE Connect Comment[gl]=Mostrar notificacións de dispositivos usando KDE Connect. +Comment[he]=הראה התראות מההתקן שלך באמצעות KDE Connect Comment[hu]=Az eszközökről származó értesítések megjelenítése a KDE csatlakozás használatával Comment[it]=Mostra le notifiche dei tuoi dispositivi tramite KDE Connect Comment[ko]=KDE Connect로 장치에 표시된 알림 보기 Comment[nl]=Meldingen van uw apparaten met KDE Connect tonen -Comment[pl]=Pokaż powiadomienia ze swoich urządzeń przy użyciu KDE Connect +Comment[nn]=Vis varslingar frå einingane dine med KDE Connect +Comment[pl]=Pokazuje powiadomienia z urządzeń z KDE Connect Comment[pt]=Mostrar notificações dos seus dispositivos usando o KDE Connect Comment[pt_BR]=Mostrar notificações dos seus dispositivos usando o KDE Connect -Comment[ru]=Показывать уведомления от устройств с помощью KDE Connect +Comment[ru]=Просмотр уведомлений с мобильных устройств с помощью KDE Connect Comment[sk]=Zobraziť oznámenia z vašich zariadení pomocou KDE Connect +Comment[sr]=Приказује обавештења са вашег уређаја помоћу КДЕ‑конекције +Comment[sr@ijekavian]=Приказује обавештења са вашег уређаја помоћу КДЕ‑конекције +Comment[sr@ijekavianlatin]=Prikazuje obaveštenja sa vašeg uređaja pomoću KDE‑konekcije +Comment[sr@latin]=Prikazuje obaveštenja sa vašeg uređaja pomoću KDE‑konekcije Comment[sv]=Visa underrättelser från apparater med KDE anslut -Comment[tr]=KDE Bağlan kullanılarak cihazınızdan bildirimleri görüntüleyin +Comment[tr]=KDE Connect kullanılarak cihazınızdan bildirimleri görüntüleyin Comment[uk]=Показ сповіщень з ваших пристроїв за допомогою програми «З’єднання KDE» Comment[x-test]=xxShow notifications from your devices using KDE Connectxx Comment[zh_CN]=通过 KDE Connect 显示来自您的设备的通知 +Comment[zh_TW]=使用KDE 連線來顯示您的裝置通知 Icon=kdeconnect Type=Service -X-KDE-ServiceTypes=Plasma/Applet,Plasma/PopupApplet +X-KDE-ServiceTypes=Plasma/Applet X-Plasma-API=declarativeappletscript X-Plasma-MainScript=ui/main.qml X-Plasma-NotificationArea=true X-Plasma-ConfigPlugins=kcm_kdeconnect X-KDE-PluginInfo-Author=Albert Vaca Cintora X-KDE-PluginInfo-Email=albertvaka@gmail.com X-KDE-PluginInfo-Name=org.kde.kdeconnect X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-Website=http://albertvaka.wordpress.com X-KDE-PluginInfo-Category=System Information X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6164b36a..24b53c53 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,25 +1,33 @@ 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(pausemusic) -add_subdirectory(mpriscontrol) add_subdirectory(clipboard) add_subdirectory(telephony) -add_subdirectory(battery) -add_subdirectory(mousepad) add_subdirectory(share) add_subdirectory(notifications) -add_subdirectory(sftp) -add_subdirectory(screensaver-inhibit) +add_subdirectory(battery) +add_subdirectory(findmyphone) +add_subdirectory(remotekeyboard) +if(WIN32) + add_subdirectory(mousepad_windows) +else() + add_subdirectory(runcommand) + add_subdirectory(sendnotifications) + add_subdirectory(pausemusic) + add_subdirectory(mpriscontrol) + add_subdirectory(mousepad) + add_subdirectory(screensaver-inhibit) + add_subdirectory(sftp) +endif() add_subdirectory(sync) - if(EXPERIMENTALAPP_ENABLED) + add_subdirectory(remotecommands) add_subdirectory(mprisremote) add_subdirectory(remotecontrol) add_subdirectory(lockdevice) 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/batterydbusinterface.cpp b/plugins/battery/batterydbusinterface.cpp index 9e06c30c..1d475281 100644 --- a/plugins/battery/batterydbusinterface.cpp +++ b/plugins/battery/batterydbusinterface.cpp @@ -1,61 +1,63 @@ /** * 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 "batterydbusinterface.h" #include "batteryplugin.h" #include #include QMap BatteryDbusInterface::s_dbusInterfaces; -BatteryDbusInterface::BatteryDbusInterface(const Device *device) +BatteryDbusInterface::BatteryDbusInterface(const Device* device) : QDBusAbstractAdaptor(const_cast(device)) - , mCharge(-1) - , mIsCharging(false) + , m_charge(-1) + , m_isCharging(false) { // FIXME: Workaround to prevent memory leak. // This makes the old BatteryDdbusInterface be deleted only after the new one is // fully operational. That seems to prevent the crash mentioned in BatteryPlugin's // destructor. QMap::iterator oldInterfaceIter = s_dbusInterfaces.find(device->id()); if (oldInterfaceIter != s_dbusInterfaces.end()) { - qCDebug(KDECONNECT_PLUGIN_BATTERY) << "Deleting stale BattteryDbusInterface for" << device->name(); - oldInterfaceIter.value()->deleteLater(); + qCDebug(KDECONNECT_PLUGIN_BATTERY) << "Deleting stale BatteryDbusInterface for" << device->name(); + //FIXME: This still crashes sometimes even after the workaround made in 38aa970, commented out by now + //oldInterfaceIter.value()->deleteLater(); + s_dbusInterfaces.erase(oldInterfaceIter); } s_dbusInterfaces[device->id()] = this; } BatteryDbusInterface::~BatteryDbusInterface() { qCDebug(KDECONNECT_PLUGIN_BATTERY) << "Destroying BatteryDbusInterface"; } void BatteryDbusInterface::updateValues(bool isCharging, int currentCharge) { - mIsCharging = isCharging; - mCharge = currentCharge; + m_isCharging = isCharging; + m_charge = currentCharge; - Q_EMIT stateChanged(mIsCharging); - Q_EMIT chargeChanged(mCharge); + Q_EMIT stateChanged(m_isCharging); + Q_EMIT chargeChanged(m_charge); } diff --git a/plugins/battery/batterydbusinterface.h b/plugins/battery/batterydbusinterface.h index 01d4ef7d..c49e5c33 100644 --- a/plugins/battery/batterydbusinterface.h +++ b/plugins/battery/batterydbusinterface.h @@ -1,55 +1,55 @@ /** * 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 BATTERYDBUSINTERFACE_H #define BATTERYDBUSINTERFACE_H #include class Device; class BatteryDbusInterface : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.battery") public: - explicit BatteryDbusInterface(const Device *device); - virtual ~BatteryDbusInterface(); + explicit BatteryDbusInterface(const Device* device); + ~BatteryDbusInterface() override; - Q_SCRIPTABLE int charge() const { return mCharge; } - Q_SCRIPTABLE bool isCharging() const { return mIsCharging; } + Q_SCRIPTABLE int charge() const { return m_charge; } + Q_SCRIPTABLE bool isCharging() const { return m_isCharging; } void updateValues(bool isCharging, int currentCharge); Q_SIGNALS: Q_SCRIPTABLE void stateChanged(bool charging); Q_SCRIPTABLE void chargeChanged(int charge); private: - int mCharge; - bool mIsCharging; + int m_charge; + bool m_isCharging; // Map to save current BatteryDbusInterface for each device. static QMap s_dbusInterfaces; }; #endif diff --git a/plugins/battery/batteryplugin.cpp b/plugins/battery/batteryplugin.cpp index 700f25d7..d79eea7f 100644 --- a/plugins/battery/batteryplugin.cpp +++ b/plugins/battery/batteryplugin.cpp @@ -1,87 +1,86 @@ /** * 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) +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() { - NetworkPackage np(PACKAGE_TYPE_BATTERY); - np.set("request",true); + NetworkPackage np(PACKAGE_TYPE_BATTERY_REQUEST, {{"request",true}}); sendPackage(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::receivePackage(const NetworkPackage& np) { - bool isCharging = np.get("isCharging"); - int currentCharge = np.get("currentCharge"); - int thresholdEvent = np.get("thresholdEvent", (int)ThresholdNone); + 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 ) { - KNotification* notification = new KNotification("batteryLow"); - notification->setIconName("battery-040"); - notification->setComponentName("kdeconnect"); + KNotification* notification = new KNotification(QStringLiteral("batteryLow")); + notification->setIconName(QStringLiteral("battery-040")); + notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(i18nc("device name: low battery", "%1: Low Battery", device()->name())); notification->setText(i18n("Battery at %1%", currentCharge)); notification->sendEvent(); } return true; } #include "batteryplugin.moc" diff --git a/plugins/battery/batteryplugin.h b/plugins/battery/batteryplugin.h index d1c2e54f..aff21146 100644 --- a/plugins/battery/batteryplugin.h +++ b/plugins/battery/batteryplugin.h @@ -1,57 +1,56 @@ /** * 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 BATTERYPLUGIN_H #define BATTERYPLUGIN_H #include #include -#define PACKAGE_TYPE_BATTERY QLatin1String("kdeconnect.battery") +#define PACKAGE_TYPE_BATTERY_REQUEST QStringLiteral("kdeconnect.battery.request") Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_BATTERY) class BatteryDbusInterface; class BatteryPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit BatteryPlugin(QObject *parent, const QVariantList &args); - virtual ~BatteryPlugin(); + explicit BatteryPlugin(QObject* parent, const QVariantList& args); + ~BatteryPlugin() override; -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override; private: // Keep these values in sync with THRESHOLD* constants in // kdeconnect-android:BatteryPlugin.java // see README for their meaning enum ThresholdBatteryEvent { ThresholdNone = 0, ThresholdBatteryLow = 1 }; BatteryDbusInterface* batteryDbusInterface; }; #endif diff --git a/plugins/battery/kdeconnect_battery.json b/plugins/battery/kdeconnect_battery.json index bb29e181..f8e6317d 100644 --- a/plugins/battery/kdeconnect_battery.json +++ b/plugins/battery/kdeconnect_battery.json @@ -1,67 +1,100 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Show your phone battery next to your computer battery", - "Description[ca]": "Mostra la bateria del telèfon al costat de la bateria de l'ordinador", - "Description[cs]": "Zobrazit baterii vedle baterie počítače", - "Description[de]": "Zeigt den Akku Ihres Telefons neben dem Akku des Rechners", - "Description[en_GB]": "Show your phone battery next to your computer battery", - "Description[es]": "Mostrar la batería del teléfono junto a la batería del equipo", - "Description[fi]": "Näytä puhelimesi akku tietokoneen akun rinnalla", - "Description[gl]": "Mostra o nivel de batería do teléfono canda o nivel de batería do computador.", - "Description[hu]": "A telefon akkumulátorának megjelenítése a számítógépé mellett", - "Description[it]": "Mostra la batteria del telefono accanto alla batteria del computer", - "Description[nl]": "Uw telefoonbatterij naast uw computerbatterij tonen", - "Description[pl]": "Pokaż baterię swojego telefonu obok baterii komputera", - "Description[pt]": "Mostrar a bateria do seu telefone ao lado da do seu computador", - "Description[pt_BR]": "Mostra a bateria do seu celular ao lado da bateria do computador", - "Description[sk]": "Zobraziť batériu vášho telefónu pri batérii počítača", - "Description[sv]": "Visa telefonens batteri intill datorbatteriet", - "Description[tr]": "Bilgisayar pilinizin yanında telefon pil durumunu göster", - "Description[uk]": "Показ даних щодо рівня заряду акумулятора на телефоні поряд з даними щодо рівня заряду акумулятора комп’ютера", - "Description[x-test]": "xxShow your phone battery next to your computer batteryxx", - "Description[zh_CN]": "在计算机电池旁显示手机电池", - "EnabledByDefault": true, - "Icon": "preferences-system-power-management", - "Id": "kdeconnect_battery", - "License": "GPL", - "Name": "Battery monitor", - "Name[ca]": "Monitor de la bateria", - "Name[cs]": "Monitor baterie", - "Name[de]": "Akkuüberwachung", - "Name[en_GB]": "Battery monitor", - "Name[es]": "Monitor de batería", - "Name[fi]": "Akkuvalvonta", - "Name[gl]": "Vixilante da batería", - "Name[hu]": "Akkumulátorfigyelő", - "Name[it]": "Monitor della batteria", - "Name[nl]": "Batterijmonitor", - "Name[pl]": "Monitor baterii", - "Name[pt]": "Monitor da bateria", - "Name[pt_BR]": "Monitor de bateria", - "Name[ru]": "Индикатор батареи", - "Name[sk]": "Monitor batérie", - "Name[sv]": "Batteriövervakare", - "Name[tr]": "Pil izleyici", - "Name[uk]": "Монітор акумулятора", - "Name[x-test]": "xxBattery monitorxx", - "Name[zh_CN]": "电池监视器", + ], + "Description": "Show your phone battery next to your computer battery", + "Description[ar]": "أظهر بطّاريّة الهاتف بجانب بطّاريّة الحاسوب", + "Description[ca@valencia]": "Mostra la bateria del telèfon al costat de la bateria de l'ordinador", + "Description[ca]": "Mostra la bateria del telèfon al costat de la bateria de l'ordinador", + "Description[cs]": "Zobrazit baterii vedle baterie počítače", + "Description[da]": "Vis din telefons batteri ved siden af dit computerbatteri", + "Description[de]": "Zeigt den Akku Ihres Telefons neben dem Akku des Rechners", + "Description[el]": "Εμφάνιση μπαταρίας συσκευής δίπλα στη μπαταρία του υπολογιστή", + "Description[es]": "Mostrar la batería del teléfono junto a la batería del equipo", + "Description[et]": "Telefoniaku näitamine otse arvutiaku kõrval", + "Description[eu]": "Erakutsi telefonoaren bateria ordenagailuaren bateriaren ondoan", + "Description[fi]": "Näytä puhelimesi akku tietokoneen akun rinnalla", + "Description[fr]": "Affichez la batterie de votre téléphone à côté de la batterie de votre ordinateur", + "Description[gl]": "Mostra o nivel de batería do teléfono canda o nivel de batería do computador.", + "Description[hu]": "A telefon akkumulátorának megjelenítése a számítógépé mellett", + "Description[it]": "Mostra la batteria del telefono accanto alla batteria del computer", + "Description[ko]": "컴퓨터 배터리와 함께 휴대폰 배터리 표시", + "Description[nl]": "Uw telefoonbatterij naast uw computerbatterij tonen", + "Description[nn]": "Vis telefonbatteriet ved sida av datamaskinbatteriet", + "Description[pl]": "Pokaż baterię swojego telefonu obok baterii komputera", + "Description[pt]": "Mostrar a bateria do seu telefone ao lado da do seu computador", + "Description[pt_BR]": "Mostra a bateria do seu celular ao lado da bateria do computador", + "Description[ru]": "Показ уровня заряда батареи телефона рядом с индикатором батареи компьютера", + "Description[sk]": "Zobraziť batériu vášho telefónu pri batérii počítača", + "Description[sr@ijekavian]": "Приказ батерије телефона у системској касети", + "Description[sr@ijekavianlatin]": "Prikaz baterije telefona u sistemskoj kaseti", + "Description[sr@latin]": "Prikaz baterije telefona u sistemskoj kaseti", + "Description[sr]": "Приказ батерије телефона у системској касети", + "Description[sv]": "Visa telefonens batteri intill datorbatteriet", + "Description[tr]": "Bilgisayar pilinizin yanında telefon pil durumunu göster", + "Description[uk]": "Показ даних щодо рівня заряду акумулятора на телефоні поряд з даними щодо рівня заряду акумулятора комп’ютера", + "Description[x-test]": "xxShow your phone battery next to your computer batteryxx", + "Description[zh_CN]": "在计算机电池旁显示手机电池", + "Description[zh_TW]": "在您的電腦電池電量旁邊顯示手機電池電量", + "EnabledByDefault": true, + "Icon": "preferences-system-power-management", + "Id": "kdeconnect_battery", + "License": "GPL", + "Name": "Battery monitor", + "Name[ar]": "مرقاب البطّاريّة", + "Name[ca@valencia]": "Monitor de la bateria", + "Name[ca]": "Monitor de la bateria", + "Name[cs]": "Monitor baterie", + "Name[da]": "Batteriovervågning", + "Name[de]": "Akkuüberwachung", + "Name[el]": "Παρακολούθηση μπαταρίας", + "Name[es]": "Monitor de batería", + "Name[et]": "Aku jälgija", + "Name[eu]": "Bateriaren monitorea", + "Name[fi]": "Akkuvalvonta", + "Name[fr]": "Moniteur de batterie", + "Name[gl]": "Vixilante da batería", + "Name[hu]": "Akkumulátorfigyelő", + "Name[ia]": "Controlator de batteria", + "Name[it]": "Monitor della batteria", + "Name[ko]": "배터리 모니터", + "Name[nl]": "Batterijmonitor", + "Name[nn]": "Batteriovervaking", + "Name[pl]": "Monitor baterii", + "Name[pt]": "Monitor da bateria", + "Name[pt_BR]": "Monitor de bateria", + "Name[ru]": "Индикатор батареи", + "Name[sk]": "Monitor batérie", + "Name[sr@ijekavian]": "Надзор батерије", + "Name[sr@ijekavianlatin]": "Nadzor baterije", + "Name[sr@latin]": "Nadzor baterije", + "Name[sr]": "Надзор батерије", + "Name[sv]": "Batteriövervakare", + "Name[tr]": "Pil izleyici", + "Name[uk]": "Монітор акумулятора", + "Name[x-test]": "xxBattery monitorxx", + "Name[zh_CN]": "电池监视器", + "Name[zh_TW]": "電池監視器", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.battery" - ], + "kdeconnect.battery.request" + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.battery" ] -} \ No newline at end of file +} diff --git a/plugins/clipboard/CMakeLists.txt b/plugins/clipboard/CMakeLists.txt index 4e6d2ab6..3481a9a1 100644 --- a/plugins/clipboard/CMakeLists.txt +++ b/plugins/clipboard/CMakeLists.txt @@ -1,7 +1,8 @@ set(kdeconnect_clipboard_SRCS clipboardplugin.cpp + clipboardlistener.cpp ) kdeconnect_add_plugin(kdeconnect_clipboard JSON kdeconnect_clipboard.json SOURCES ${kdeconnect_clipboard_SRCS}) target_link_libraries(kdeconnect_clipboard kdeconnectcore Qt5::Gui) diff --git a/plugins/notifications/notification.cpp b/plugins/clipboard/clipboardlistener.cpp similarity index 56% copy from plugins/notifications/notification.cpp copy to plugins/clipboard/clipboardlistener.cpp index b44b5f85..1c23f687 100644 --- a/plugins/notifications/notification.cpp +++ b/plugins/clipboard/clipboardlistener.cpp @@ -1,46 +1,51 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2016 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 "notification.h" +#include "clipboardlistener.h" -#include - -Notification::Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent) - : QObject(parent) +ClipboardListener::ClipboardListener() + : clipboard(QGuiApplication::clipboard()) { - mId = np.get("id"); - mAppName = np.get("appName"); - mTicker = np.get("ticker"); - mDismissable = np.get("isClearable"); - mIconPath = iconPath; + connect(clipboard, &QClipboard::changed, this, &ClipboardListener::updateClipboard); } -Notification::~Notification() +void ClipboardListener::updateClipboard(QClipboard::Mode mode) { + if (mode != QClipboard::Clipboard) { + return; + } + QString content = clipboard->text(); + + if (content == currentContent) { + return; + } + currentContent = content; + + Q_EMIT clipboardChanged(content); } -void Notification::dismiss() +void ClipboardListener::setText(const QString& content) { - if (mDismissable) { - Q_EMIT dismissRequested(this); - } + currentContent = content; + clipboard->setText(content); } + diff --git a/plugins/clipboard/clipboardplugin.h b/plugins/clipboard/clipboardlistener.h similarity index 59% copy from plugins/clipboard/clipboardplugin.h copy to plugins/clipboard/clipboardlistener.h index 12d34c22..d48c31c5 100644 --- a/plugins/clipboard/clipboardplugin.h +++ b/plugins/clipboard/clipboardlistener.h @@ -1,52 +1,59 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2016 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 CLIPBOARDPLUGIN_H -#define CLIPBOARDPLUGIN_H +#ifndef CLIPBOARDLISTENER_H +#define CLIPBOARDLISTENER_H #include #include -#include -#include +#include -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CLIPBOARD) -#define PACKAGE_TYPE_CLIPBOARD QLatin1String("kdeconnect.clipboard") - -class ClipboardPlugin - : public KdeConnectPlugin +/** + * Wrapper around QClipboard, which emits clipboardChanged only when it really changed + */ +class ClipboardListener : public QObject { Q_OBJECT +private: + ClipboardListener(); + QString currentContent; + QClipboard* clipboard; + public: - explicit ClipboardPlugin(QObject *parent, const QVariantList &args); -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } - -private Q_SLOTS: - void clipboardChanged(QClipboard::Mode mode); + static ClipboardListener* instance() + { + static ClipboardListener* me = nullptr; + if (!me) { + me = new ClipboardListener(); + } + return me; + } -private: - QString currentContent; - QClipboard *clipboard; + void updateClipboard(QClipboard::Mode mode); + + void setText(const QString& content); + +Q_SIGNALS: + void clipboardChanged(const QString& content); }; #endif diff --git a/plugins/clipboard/clipboardplugin.cpp b/plugins/clipboard/clipboardplugin.cpp index 656dc721..7149adff 100644 --- a/plugins/clipboard/clipboardplugin.cpp +++ b/plugins/clipboard/clipboardplugin.cpp @@ -1,64 +1,51 @@ /** * 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 "clipboardplugin.h" -#include -#include +#include "clipboardlistener.h" #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_clipboard.json", registerPlugin< ClipboardPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CLIPBOARD, "kdeconnect.plugin.clipboard") -ClipboardPlugin::ClipboardPlugin(QObject *parent, const QVariantList &args) +ClipboardPlugin::ClipboardPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) - , clipboard(QGuiApplication::clipboard()) { - connect(clipboard, SIGNAL(changed(QClipboard::Mode)), this, SLOT(clipboardChanged(QClipboard::Mode))); + connect(ClipboardListener::instance(), &ClipboardListener::clipboardChanged, + this, &ClipboardPlugin::propagateClipboard); } -void ClipboardPlugin::clipboardChanged(QClipboard::Mode mode) +void ClipboardPlugin::propagateClipboard(const QString& content) { - if (mode != QClipboard::Clipboard) { - return; - } - - QString content = clipboard->text(); - - if (content == currentContent) { - return; - } - - currentContent = content; - - NetworkPackage np(PACKAGE_TYPE_CLIPBOARD); - np.set("content", content); + NetworkPackage np(PACKAGE_TYPE_CLIPBOARD, {{"content", content}}); sendPackage(np); } bool ClipboardPlugin::receivePackage(const NetworkPackage& np) { - clipboard->setText(np.get("content")); + QString content = np.get(QStringLiteral("content")); + ClipboardListener::instance()->setText(content); return true; } #include "clipboardplugin.moc" diff --git a/plugins/clipboard/clipboardplugin.h b/plugins/clipboard/clipboardplugin.h index 12d34c22..873da10c 100644 --- a/plugins/clipboard/clipboardplugin.h +++ b/plugins/clipboard/clipboardplugin.h @@ -1,52 +1,48 @@ /** * 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 CLIPBOARDPLUGIN_H #define CLIPBOARDPLUGIN_H #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CLIPBOARD) -#define PACKAGE_TYPE_CLIPBOARD QLatin1String("kdeconnect.clipboard") +#define PACKAGE_TYPE_CLIPBOARD QStringLiteral("kdeconnect.clipboard") class ClipboardPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit ClipboardPlugin(QObject *parent, const QVariantList &args); + explicit ClipboardPlugin(QObject* parent, const QVariantList& args); + + bool receivePackage(const NetworkPackage& np) override; + void connected() override { } -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } - private Q_SLOTS: - void clipboardChanged(QClipboard::Mode mode); + void propagateClipboard(const QString& content); -private: - QString currentContent; - QClipboard *clipboard; }; #endif diff --git a/plugins/clipboard/kdeconnect_clipboard.json b/plugins/clipboard/kdeconnect_clipboard.json index 4bb85418..b68d796c 100644 --- a/plugins/clipboard/kdeconnect_clipboard.json +++ b/plugins/clipboard/kdeconnect_clipboard.json @@ -1,67 +1,100 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Share the clipboard between devices", - "Description[ca]": "Comparteix el porta-retalls entre dispositius", - "Description[cs]": "Sdílet obsah schránky mezi zařízeními", - "Description[de]": "Die Zwischenablage mit Geräten teilen", - "Description[en_GB]": "Share the clipboard between devices", - "Description[es]": "Compartir portapapeles entre dispositivos", - "Description[fi]": "Jaa leikepöytä laitteiden välillä", - "Description[gl]": "Comparta o portapapeis entre os dispositivos.", - "Description[hu]": "A vágólap megosztása az eszközök között", - "Description[it]": "Condividi gli appunti tra i dispositivi", - "Description[nl]": "Het klembord tussen apparaten delen", - "Description[pl]": "Współdziel schowek pomiędzy urządzeniami", - "Description[pt]": "Partilhar a área de transferência entre dispositivos", - "Description[pt_BR]": "Compartilhar a área de transferência entre dispositivos", - "Description[sk]": "Zdieľať schránku medzi zariadeniami", - "Description[sv]": "Dela klippbordet mellan apparater", - "Description[tr]": "Aygıtlar arasında panoyu paylaştır", - "Description[uk]": "Спільне використання буфера обміну даними на пристроях", - "Description[x-test]": "xxShare the clipboard between devicesxx", - "Description[zh_CN]": "在设备间共享剪贴板", - "EnabledByDefault": true, - "Icon": "klipper", - "Id": "kdeconnect_clipboard", - "License": "GPL", - "Name": "Clipboard", - "Name[ca]": "Porta-retalls", - "Name[cs]": "Schránka", - "Name[de]": "Zwischenablage", - "Name[en_GB]": "Clipboard", - "Name[es]": "Portapapeles", - "Name[fi]": "Leikepöytä", - "Name[gl]": "Portapapeis", - "Name[hu]": "Vágólap", - "Name[it]": "Appunti", - "Name[nl]": "Klembord", - "Name[pl]": "Schowek", - "Name[pt]": "Área de Transferência", - "Name[pt_BR]": "Área de transferência", - "Name[ru]": "Буфер обмена", - "Name[sk]": "Schránka", - "Name[sv]": "Klippbord", - "Name[tr]": "Geçici taşıma panosu", - "Name[uk]": "Буфер обміну", - "Name[x-test]": "xxClipboardxx", - "Name[zh_CN]": "剪贴板", + ], + "Description": "Share the clipboard between devices", + "Description[ar]": "شارك الحافظة بين الجهازين", + "Description[ca@valencia]": "Comparteix el porta-retalls entre dispositius", + "Description[ca]": "Comparteix el porta-retalls entre dispositius", + "Description[cs]": "Sdílet obsah schránky mezi zařízeními", + "Description[da]": "Del udklipsholderen mellem enheder", + "Description[de]": "Die Zwischenablage mit Geräten teilen", + "Description[el]": "Διαμοιρασμός του προχείρου μεταξύ συσκευών", + "Description[es]": "Compartir portapapeles entre dispositivos", + "Description[et]": "Lõikepuhvri jagamine seadmete vahel", + "Description[eu]": "Partekatu arbela gailuen artean", + "Description[fi]": "Jaa leikepöytä laitteiden välillä", + "Description[fr]": "Partagez le presse-papiers entre périphériques", + "Description[gl]": "Comparta o portapapeis entre os dispositivos.", + "Description[hu]": "A vágólap megosztása az eszközök között", + "Description[it]": "Condividi gli appunti tra i dispositivi", + "Description[ko]": "장치간 클립보드 공유", + "Description[nl]": "Het klembord tussen apparaten delen", + "Description[nn]": "Del utklippstavla mellom einingar", + "Description[pl]": "Współdziel schowek pomiędzy urządzeniami", + "Description[pt]": "Partilhar a área de transferência entre dispositivos", + "Description[pt_BR]": "Compartilha a área de transferência entre dispositivos", + "Description[ru]": "Общий буфер обмена между устройствами", + "Description[sk]": "Zdieľať schránku medzi zariadeniami", + "Description[sr@ijekavian]": "Дељење клипборда између уређаја", + "Description[sr@ijekavianlatin]": "Deljenje klipborda između uređaja", + "Description[sr@latin]": "Deljenje klipborda između uređaja", + "Description[sr]": "Дељење клипборда између уређаја", + "Description[sv]": "Dela klippbordet mellan apparater", + "Description[tr]": "Aygıtlar arasında panoyu paylaştır", + "Description[uk]": "Спільне використання буфера обміну даними на пристроях", + "Description[x-test]": "xxShare the clipboard between devicesxx", + "Description[zh_CN]": "在设备间共享剪贴板", + "Description[zh_TW]": "在設備之間共享剪貼板", + "EnabledByDefault": true, + "Icon": "klipper", + "Id": "kdeconnect_clipboard", + "License": "GPL", + "Name": "Clipboard", + "Name[ar]": "الحافظة", + "Name[ca@valencia]": "Porta-retalls", + "Name[ca]": "Porta-retalls", + "Name[cs]": "Schránka", + "Name[da]": "Udklipsholder", + "Name[de]": "Zwischenablage", + "Name[el]": "Πρόχειρο", + "Name[es]": "Portapapeles", + "Name[et]": "Lõikepuhver", + "Name[eu]": "Arbela", + "Name[fi]": "Leikepöytä", + "Name[fr]": "Presse-papiers", + "Name[gl]": "Portapapeis", + "Name[hu]": "Vágólap", + "Name[ia]": "Area de transferentia", + "Name[it]": "Appunti", + "Name[ko]": "클립보드", + "Name[nl]": "Klembord", + "Name[nn]": "Utklippstavle", + "Name[pl]": "Schowek", + "Name[pt]": "Área de Transferência", + "Name[pt_BR]": "Área de transferência", + "Name[ru]": "Буфер обмена", + "Name[sk]": "Schránka", + "Name[sr@ijekavian]": "Клипборд", + "Name[sr@ijekavianlatin]": "Klipbord", + "Name[sr@latin]": "Klipbord", + "Name[sr]": "Клипборд", + "Name[sv]": "Klippbord", + "Name[tr]": "Geçici taşıma panosu", + "Name[uk]": "Буфер обміну", + "Name[x-test]": "xxClipboardxx", + "Name[zh_CN]": "剪贴板", + "Name[zh_TW]": "剪貼板", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ "kdeconnect.clipboard" - ], + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.clipboard" ] -} \ No newline at end of file +} diff --git a/plugins/findmyphone/CMakeLists.txt b/plugins/findmyphone/CMakeLists.txt new file mode 100644 index 00000000..80b3f719 --- /dev/null +++ b/plugins/findmyphone/CMakeLists.txt @@ -0,0 +1,9 @@ +set(kdeconnect_findmyphone_SRCS + findmyphoneplugin.cpp +) + +kdeconnect_add_plugin(kdeconnect_findmyphone JSON kdeconnect_findmyphone.json SOURCES ${kdeconnect_findmyphone_SRCS}) + +target_link_libraries(kdeconnect_findmyphone kdeconnectcore kdeconnectcore Qt5::Core Qt5::DBus) + + diff --git a/plugins/notifications/notification.cpp b/plugins/findmyphone/findmyphoneplugin.cpp similarity index 51% copy from plugins/notifications/notification.cpp copy to plugins/findmyphone/findmyphoneplugin.cpp index b44b5f85..3f7bfd43 100644 --- a/plugins/notifications/notification.cpp +++ b/plugins/findmyphone/findmyphoneplugin.cpp @@ -1,46 +1,57 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2014 Apoorv Parle + * Copyright 2015 David Edmundson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "notification.h" +#include "findmyphoneplugin.h" #include +#include -Notification::Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent) - : QObject(parent) +#include + +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_findmyphone.json", registerPlugin< FindMyPhonePlugin >(); ) + +FindMyPhonePlugin::FindMyPhonePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) +{ +} + +FindMyPhonePlugin::~FindMyPhonePlugin() { - mId = np.get("id"); - mAppName = np.get("appName"); - mTicker = np.get("ticker"); - mDismissable = np.get("isClearable"); - mIconPath = iconPath; } -Notification::~Notification() +bool FindMyPhonePlugin::receivePackage(const NetworkPackage& np) { + Q_UNUSED(np); + return false; +} +void FindMyPhonePlugin::ring() +{ + NetworkPackage np(PACKAGE_TYPE_FINDMYPHONE_REQUEST); + sendPackage(np); } -void Notification::dismiss() +QString FindMyPhonePlugin::dbusPath() const { - if (mDismissable) { - Q_EMIT dismissRequested(this); - } + return "/modules/kdeconnect/devices/" + device()->id() + "/findmyphone"; } +#include "findmyphoneplugin.moc" + diff --git a/plugins/ping/pingplugin.h b/plugins/findmyphone/findmyphoneplugin.h similarity index 65% copy from plugins/ping/pingplugin.h copy to plugins/findmyphone/findmyphoneplugin.h index 3217dcda..95349d24 100644 --- a/plugins/ping/pingplugin.h +++ b/plugins/findmyphone/findmyphoneplugin.h @@ -1,51 +1,47 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2014 Apoorv Parle * * 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 PINGPLUGIN_H #define PINGPLUGIN_H #include #include -#define PACKAGE_TYPE_PING QLatin1String("kdeconnect.ping") +#define PACKAGE_TYPE_FINDMYPHONE_REQUEST QStringLiteral("kdeconnect.findmyphone.request") -class Q_DECL_EXPORT PingPlugin +class FindMyPhonePlugin : public KdeConnectPlugin { Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.ping") + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.findmyphone") public: - explicit PingPlugin(QObject *parent, const QVariantList &args); - virtual ~PingPlugin(); - - Q_SCRIPTABLE void sendPing(); - Q_SCRIPTABLE void sendPing(const QString& customMessage); - -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); - -private: - QString dbusPath() const; + explicit FindMyPhonePlugin(QObject* parent, const QVariantList& args); + ~FindMyPhonePlugin() override; + + Q_SCRIPTABLE void ring(); + + QString dbusPath() const override; + void connected() override {} + bool receivePackage(const NetworkPackage& np) override; }; #endif diff --git a/plugins/findmyphone/kdeconnect_findmyphone.json b/plugins/findmyphone/kdeconnect_findmyphone.json new file mode 100644 index 00000000..a64f117a --- /dev/null +++ b/plugins/findmyphone/kdeconnect_findmyphone.json @@ -0,0 +1,103 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "apparle@gmail.com", + "Name": "Apoorv Parle", + "Name[sr@ijekavian]": "Апорв Парле", + "Name[sr@ijekavianlatin]": "Aporv Parle", + "Name[sr@latin]": "Aporv Parle", + "Name[sr]": "Апорв Парле", + "Name[x-test]": "xxApoorv Parlexx" + }, + { + "Email": "davidedmundson@kde.org", + "Name": "David Edmundson", + "Name[sr@ijekavian]": "Дејвид Едмундсон", + "Name[sr@ijekavianlatin]": "Dejvid Edmundson", + "Name[sr@latin]": "Dejvid Edmundson", + "Name[sr]": "Дејвид Едмундсон", + "Name[x-test]": "xxDavid Edmundsonxx" + } + ], + "Description": "Find your lost phone by making it play an alarm sound", + "Description[ar]": "جِد هاتفك الضّائع بتشغيل صوت المنبّه", + "Description[ca@valencia]": "Troba el vostre telèfon perdut fent que reproduïsca un so d'alarma", + "Description[ca]": "Troba el vostre telèfon perdut fent que reprodueixi un so d'alarma", + "Description[cs]": "Najděte svůj telefon tím, že jej necháte přehrát zvuk upomínky", + "Description[da]": "Find din forsvundne telefon ved at få den til at afspille en alarm", + "Description[de]": "Finden Sie Ihr verlegtes Telefon, indem Sie mit ihm eine Warnton abspielen", + "Description[el]": "Βρείτε το χαμένο σας τηλέφωνο με ηχητική ειδοποίηση", + "Description[es]": "Encuentre su teléfono perdido haciéndole reproducir un sonido de alarma", + "Description[et]": "Kaotsi läinud telefoni leidmine sellel häireheli esitamisega", + "Description[eu]": "Aurkitu galdutako telefonoa alarma-soinu bat jo araziz", + "Description[fi]": "Löydä hukkaamasi puhelin laittamalla se soittamaan hälytysääntä", + "Description[fr]": "Trouver votre téléphone perdu en déclenchant une alarme", + "Description[gl]": "Reproducir un son de alarma nun teléfono móbil perdido para atopalo.", + "Description[it]": "Trova il tuo telefono smarrito facendogli suonare un allarme sonoro", + "Description[ko]": "알람 소리를 울려서 잃어버린 휴대폰 찾기", + "Description[nl]": "Zoek uw verloren telefoon door het een wekkersignaal te laten spelen", + "Description[nn]": "Finn telefonen din ved å la han spela eit lydsignal", + "Description[pl]": "Znajdź twój zgubiony telefon odgrywając na nim dźwięk", + "Description[pt]": "Descobrir o seu telefone perdido, fazendo-o tocar um som de alarme", + "Description[pt_BR]": "Encontre seu telefone fazendo ele reproduzir um som", + "Description[ru]": "Поиск утерянного телефона при помощи звукового сигнала", + "Description[sk]": "Nájsť váš stratený telefón zahraním zvuku budíka", + "Description[sr@ijekavian]": "Нађите изгубљени телефон активирањем звука аларма", + "Description[sr@ijekavianlatin]": "Nađite izgubljeni telefon aktiviranjem zvuka alarma", + "Description[sr@latin]": "Nađite izgubljeni telefon aktiviranjem zvuka alarma", + "Description[sr]": "Нађите изгубљени телефон активирањем звука аларма", + "Description[sv]": "Hitta din förlorade telefon genom att få den att spela ett alarmljud", + "Description[tr]": "Bir alarm sesi çalarak kayıp telefonunuzu bulun ", + "Description[uk]": "Знайти загублений телефон, наказавши йому відтворити дзвінок", + "Description[x-test]": "xxFind your lost phone by making it play an alarm soundxx", + "Description[zh_CN]": "通过播放闹铃声来找到您不见的手机", + "Description[zh_TW]": "讓您的手機發出警報聲,讓您找到您遺失的手機。", + "EnabledByDefault": true, + "Encoding": "UTF-8", + "Icon": "edit-find", + "Id": "kdeconnect_findmyphone", + "License": "GPL", + "Name": "Ring my phone", + "Name[ar]": "رنّ هاتفي", + "Name[ca@valencia]": "Fes sonar el meu telèfon", + "Name[ca]": "Fes sonar el meu telèfon", + "Name[cs]": "Prozvonit můj telefon", + "Name[da]": "Ring min telefon op", + "Name[de]": "Mein Telefon anklingeln", + "Name[el]": "Κουδούνισμα του τηλεφώνου μου", + "Name[es]": "Hacer sonar mi teléfono", + "Name[et]": "Helista minu telefonile", + "Name[eu]": "Jo nire telefonoaren dei-doinua", + "Name[fi]": "Soita puhelimeeni", + "Name[fr]": "Faire sonner mon téléphone", + "Name[gl]": "Facer soar o meu móbil", + "Name[it]": "Fai squillare il mio telefono", + "Name[ko]": "내 휴대폰 울리기", + "Name[nl]": "Bel mijn telefoon", + "Name[nn]": "Ring telefonen", + "Name[pl]": "Dzwoń z mojego telefonu", + "Name[pt]": "Tocar o meu telefone", + "Name[pt_BR]": "Tocar meu telefone", + "Name[ru]": "Поиск телефона", + "Name[sk]": "Prezvoniť môj telefón", + "Name[sr@ijekavian]": "Звони мој телефону", + "Name[sr@ijekavianlatin]": "Zvoni moj telefonu", + "Name[sr@latin]": "Zvoni moj telefonu", + "Name[sr]": "Звони мој телефону", + "Name[sv]": "Hitta min telefon", + "Name[tr]": "Telefonumu çaldır", + "Name[uk]": "Дзвінок на вашому телефоні", + "Name[x-test]": "xxRing my phonexx", + "Name[zh_CN]": "让我的手机响铃", + "Name[zh_TW]": "撥打我的電話", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "http://kde.org" + }, + "X-KdeConnect-OutgoingPackageType": [ + "kdeconnect.findmyphone.request" + ] +} diff --git a/plugins/kdeconnect.notifyrc b/plugins/kdeconnect.notifyrc index 7c3b38e7..63057cba 100644 --- a/plugins/kdeconnect.notifyrc +++ b/plugins/kdeconnect.notifyrc @@ -1,463 +1,730 @@ [Global] IconName=kdeconnect Name=KDE Connect +Name[ar]=كدي المتّصل +Name[ast]=KDE Connect Name[bg]=KDE Connect Name[bs]=Konekcija KDE Name[ca]=KDE Connect +Name[ca@valencia]=KDE Connect Name[cs]=KDE Connect Name[da]=KDE Connect Name[de]=KDE-Connect +Name[el]=KDE Connect Name[en_GB]=KDE Connect Name[es]=KDE Connect +Name[et]=KDE Connect +Name[eu]=KDE Connect Name[fi]=KDE Connect Name[fr]=KDE Connect Name[gl]=KDE Connect +Name[he]=KDE Connect Name[hu]=KDE csatlakozás Name[it]=KDE Connect Name[ko]=KDE Connect Name[nl]=KDE Connect +Name[nn]=KDE Connect Name[pl]=KDE Connect Name[pt]=KDE Connect Name[pt_BR]=KDE Connect Name[ro]=KDE Connect Name[ru]=KDE Connect Name[sk]=KDE Connect -Name[sv]=KDE anslut -Name[tr]=KDE Bağlan +Name[sr]=КДЕ‑конекција +Name[sr@ijekavian]=КДЕ‑конекција +Name[sr@ijekavianlatin]=KDE‑konekcija +Name[sr@latin]=KDE‑konekcija +Name[sv]=KDE-anslut +Name[tr]=KDE Connect Name[uk]=З’єднання KDE Name[x-test]=xxKDE Connectxx Name[zh_CN]=KDE Connect +Name[zh_TW]=KDE 連線 Comment=Notifications from your devices +Comment[ar]=الإخطارات من أجهزتك +Comment[ast]=Avisos de los tos preseos Comment[bg]=Уведомления от устройствата ви Comment[bs]=Notifikacija sa Vašeg uređaja -Comment[ca]=Notificacions des dels vostres dispositius +Comment[ca]=Notificacions dels vostres dispositius +Comment[ca@valencia]=Notificacions dels vostres dispositius Comment[cs]=Oznamování z vašich zařízení Comment[da]=Bekendtgørelser fra dine enheder Comment[de]=Benachrichtigungen von Ihren Geräten +Comment[el]=Ειδοποιήσεις από τις συσκευές σας Comment[en_GB]=Notifications from your devices Comment[es]=Notificaciones de sus dispositivos +Comment[et]=Seadmete märguanded +Comment[eu]=Zure gailuetako jakinarazpenak Comment[fi]=Laitteesi ilmoitukset Comment[fr]=Notifications provenant de vos périphériques Comment[gl]=Notificacións de dispositivos. +Comment[he]=התראות מההתקן שלך Comment[hu]=Az eszközökről származó értesítések Comment[it]=Notifiche dai tuoi dispositivi Comment[ko]=장치에 표시된 알림 Comment[nl]=Meldingen van uw apparaten +Comment[nn]=Varslingar frå einingane dine Comment[pl]=Powiadomienia z urządzeń Comment[pt]=Notificações dos seus dispositivos Comment[pt_BR]=Notificações dos seus dispositivos Comment[ro]=Notificări de pe dispozitivele dumneavoastră -Comment[ru]=Уведомления от устройств +Comment[ru]=Уведомления от мобильных устройств Comment[sk]=Oznámenia z vašich zariadení +Comment[sr]=Обавештење са вашег уређаја +Comment[sr@ijekavian]=Обавештење са вашег уређаја +Comment[sr@ijekavianlatin]=Obaveštenje sa vašeg uređaja +Comment[sr@latin]=Obaveštenje sa vašeg uređaja Comment[sv]=Underrättelser från dina apparater Comment[tr]=Cihazınızdan bildirimler Comment[uk]=Сповіщення з вашого пристрою Comment[x-test]=xxNotifications from your devicesxx -Comment[zh_CN]=来自你设备的通知 +Comment[zh_CN]=来自您设备的通知 +Comment[zh_TW]=從您的裝置接收到通知 [Event/pairingRequest] Name=Pairing Request +Name[ar]=طلب اقتران +Name[ast]=Solicitú d'empareyamientu Name[bg]=Заявка за сдвояване Name[bs]=Zahtjev za uparivanje Name[ca]=Sol·licitud d'aparellament +Name[ca@valencia]=Sol·licitud d'aparellament Name[cs]=Požadavek na párování Name[da]=Parringsanmodning Name[de]=Verbindungsanfrage +Name[el]=Αίτημα σύζευξης Name[en_GB]=Pairing Request Name[es]=Petición de vinculación +Name[et]=Paardumissoov +Name[eu]=Parekatzeko eskaria Name[fi]=Paripyyntö -Name[fr]=Demande d'appariement +Name[fr]=Demande d'association Name[gl]=Solicitude de emparellamento +Name[he]=בקשת התאמה Name[hu]=Párosítási kérés Name[it]=Richiesta di associazione -Name[ko]=페어링 요청 +Name[ko]=연결 요청 Name[nl]=Verzoek om een paar te maken +Name[nn]=Paringsførespurnad Name[pl]=Żądanie parowania Name[pt]=Pedido de Emparelhamento Name[pt_BR]=Solicitação de emparelhamento +Name[ru]=Запрос на сопряжение Name[sk]=Požiadavka na spárovanie +Name[sr]=Захтев за упаривање +Name[sr@ijekavian]=Захтев за упаривање +Name[sr@ijekavianlatin]=Zahtev za uparivanje +Name[sr@latin]=Zahtev za uparivanje Name[sv]=Begäran om ihopparning Name[tr]=Eşleşme İsteği Name[uk]=Запит щодо пов’язування Name[x-test]=xxPairing Requestxx Name[zh_CN]=配对请求 -Comment=Pairing request received from a devices -Comment[bg]=Получена е заявка за сдвояване от устройство -Comment[bs]=Primljen zahtjev za uparivanje od uređaja +Name[zh_TW]=配對請求 +Comment=Pairing request received from a device +Comment[ar]=استُلم طلب اقتران من جهاز +Comment[ast]=Recibióse una solicitú d'empareyamientu d'un preséu Comment[ca]=Les sol·licituds d'aparellament rebudes des d'un dispositiu +Comment[ca@valencia]=Les sol·licituds d'aparellament rebudes des d'un dispositiu Comment[cs]=Požadavek na párování přijat ze zařízení Comment[da]=Parringsanmodning modtaget fra en enhed Comment[de]=Verbindungsanfrage von einem Gerät erhalten -Comment[en_GB]=Pairing request received from a devices +Comment[el]=Λήφθηκε αίτημα σύζευξης από μια συσκευή +Comment[en_GB]=Pairing request received from a device Comment[es]=Petición de vinculación recibida desde un dispositivo +Comment[et]=Seadmelt saadi paardumissoov +Comment[eu]=Parekatzeko eskaria jaso da gailu batetik Comment[fi]=Saatiin paripyyntö laitteelta -Comment[fr]=Demande d'appariement provenant d'un périphérique +Comment[fr]=Demande d'association provenant d'un périphérique Comment[gl]=Recibiuse unha solicitude de emparellamento dun dispositivo. -Comment[hu]=Párosítási kérés érkezett egy eszköztől +Comment[he]=התקבלה בקשת התאמה מהתקן Comment[it]=Richiesta di associazione ricevuta da un dispositivo -Comment[ko]=장치에서 페어링 요청을 받음 -Comment[nl]=Verzoek om een paar te maken ontvangen van een apparaat -Comment[pl]=Otrzymano żądanie parowania z urządzeń +Comment[ko]=장치에서 연결 요청을 받음 +Comment[nl]=Verzoek om een paar te maken ontvangen van apparaten +Comment[nn]=Fekk paringsførespurnad frå eining +Comment[pl]=Otrzymano żądanie parowania z urządzenia Comment[pt]=Pedido de emparelhamento recebido de um dispositivo Comment[pt_BR]=Solicitação de emparelhamento recebida de um dispositivo +Comment[ru]=От мобильного устройства получен запрос на сопряжение Comment[sk]=Požiadavka na spárovanie prijatá zo zariadenia +Comment[sr]=Захтев за упаривање примљен од уређаја +Comment[sr@ijekavian]=Захтев за упаривање примљен од уређаја +Comment[sr@ijekavianlatin]=Zahtev za uparivanje primljen od uređaja +Comment[sr@latin]=Zahtev za uparivanje primljen od uređaja Comment[sv]=Begäran om ihopparning mottagen från en apparat -Comment[tr]=Bir aygıttan eşleşme isteği alındı +Comment[tr]=Bir cihazdan alınan eşleştirme isteği Comment[uk]=Від пристрою отримано запит щодо пов’язування -Comment[x-test]=xxPairing request received from a devicesxx +Comment[x-test]=xxPairing request received from a devicexx Comment[zh_CN]=收到来自一个设备的配对请求 +Comment[zh_TW]=從您的裝置當中收到配對請求 Action=Popup [Event/callReceived] Name=Incoming Call +Name[ar]=مكالمة واردة +Name[ast]=Llamada entrante Name[bg]=Входящо обаждане Name[ca]=Trucada entrant +Name[ca@valencia]=Trucada entrant Name[cs]=Příchozí hovor +Name[da]=Indkommende opkald Name[de]=Eingehender Anruf +Name[el]=Εισερχόμενη κλήση Name[en_GB]=Incoming Call Name[es]=Llamada entrante +Name[et]=Sisenev kõne +Name[eu]=Sarrerako deia Name[fi]=Saapuva puhelu +Name[fr]=Appel entrant Name[gl]=Chamada entrante +Name[he]=שיחה נכנסת Name[hu]=Bejövő hívás +Name[ia]=Appello in arrivata Name[it]=Chiamata in ingresso +Name[ko]=수신 전화 Name[nl]=Inkomende oproep +Name[nn]=Innkommande samtale Name[pl]=Rozmowa przychodząca Name[pt]=Chamada Recebida Name[pt_BR]=Chamada recebida +Name[ru]=Входящий звонок Name[sk]=Prichádzajúci hovor +Name[sr]=Долазни позив +Name[sr@ijekavian]=Долазни позив +Name[sr@ijekavianlatin]=Dolazni poziv +Name[sr@latin]=Dolazni poziv Name[sv]=Inkommande samtal Name[tr]=Gelen Çağrı Name[uk]=Вхідний дзвінок Name[x-test]=xxIncoming Callxx Name[zh_CN]=收到呼叫 +Name[zh_TW]=電話來電 Comment=Someone is calling you +Comment[ar]=ثمّة من يتّصل بك +Comment[ast]=Daquién ta llamándote Comment[bg]=Някой ви се обажда Comment[bs]=Neko Vas zove Comment[ca]=Algú us està trucant +Comment[ca@valencia]=Algú vos està trucant Comment[cs]=Někdo vám volá Comment[da]=Nogen ringer til dig Comment[de]=Sie werden angerufen +Comment[el]=Κάποιος σας καλεί Comment[en_GB]=Someone is calling you Comment[es]=Alguien le está llamando +Comment[et]=Keegi helistab sulle +Comment[eu]=Norbait deitzen ari zaizu Comment[fi]=Sinulle soitetaan Comment[fr]=Quelqu'un vous appelle Comment[gl]=Está a recibir unha chamada de alguén. +Comment[he]=מישהו מתקשר אילך Comment[hu]=Valaki hívja önt Comment[it]=Chiamata in arrivo Comment[ko]=누군가가 전화를 걸었음 Comment[nl]=Iemand belt u op +Comment[nn]=Nokon ringjer deg Comment[pl]=Ktoś do ciebie dzwoni Comment[pt]=Alguém está a ligar-lhe Comment[pt_BR]=Alguém está chamando você Comment[ro]=Cineva vă apelează -Comment[ru]=Вас кто-то вызывает +Comment[ru]=Вам кто-то звонит Comment[sk]=Niekto vám volá +Comment[sr]=Неко вас зове +Comment[sr@ijekavian]=Неко вас зове +Comment[sr@ijekavianlatin]=Neko vas zove +Comment[sr@latin]=Neko vas zove Comment[sv]=Någon ringer till dig Comment[tr]=Biri sizi arıyor Comment[uk]=Хтось телефонує вам Comment[x-test]=xxSomeone is calling youxx Comment[zh_CN]=某人正在呼叫您 +Comment[zh_TW]=有人打電話給您 Action=Popup [Event/missedCall] Name=Missed Call +Name[ar]=مكالمة فائتة +Name[ast]=Llamada perdida Name[bg]=Пропуснато обаждане Name[ca]=Trucada perduda +Name[ca@valencia]=Trucada perduda Name[cs]=Zmeškaný hovor +Name[da]=Ubesvaret opkald Name[de]=Verpasster Anruf +Name[el]=Αναπάντητη κλήση Name[en_GB]=Missed Call Name[es]=Llamada perdida +Name[et]=Vastamata kõne +Name[eu]=Galdutako deia Name[fi]=Vastaamaton puhelu +Name[fr]=Appel manqué Name[gl]=Chamada perdida +Name[he]=שיחה שלא נענתה Name[hu]=Nem fogadott hívás Name[it]=Chiamate perse +Name[ko]=부재 중 전화 Name[nl]=Gemiste oproep +Name[nn]=Tapt oppringing Name[pl]=Połączenie nieodebrane Name[pt]=Chamada Não Atendida Name[pt_BR]=Chamada não atendida +Name[ru]=Пропущенный звонок Name[sk]=Zmeškaný hovor +Name[sr]=Пропуштен позив +Name[sr@ijekavian]=Пропуштен позив +Name[sr@ijekavianlatin]=Propušten poziv +Name[sr@latin]=Propušten poziv Name[sv]=Missat samtal Name[tr]=Cevapsız Çağrı Name[uk]=Пропущений дзвінок Name[x-test]=xxMissed Callxx Name[zh_CN]=未接来电 +Name[zh_TW]=未接來電 Comment=You have a missed call +Comment[ar]=لقد فاتتك مكالمة +Comment[ast]=Tienes una llamada perdida Comment[bg]=Имате неприето обаждане Comment[bs]=Imate propušten poziv Comment[ca]=Teniu una trucada perduda +Comment[ca@valencia]=Teniu una trucada perduda Comment[cs]=Máte zmeškaný hovor Comment[da]=Du har et ubesvaret opkald Comment[de]=Sie haben einen Anruf verpasst +Comment[el]=Έχετε μια αναπάντητη κλήση Comment[en_GB]=You have a missed call Comment[es]=Tiene una llamada perdida +Comment[et]=Kõnele jäi vastamata +Comment[eu]=Galdutako dei bat duzu Comment[fi]=Sinulla on vastaamaton puhelu Comment[fr]=Vous avez un appel manqué Comment[gl]=Ten unha chamada perdida. +Comment[he]=יש לך שיחה שלא נענטה Comment[hu]=Nem fogadott hívása van Comment[it]=Hai una chiamata persa Comment[ko]=부재 중 전화가 있음 Comment[nl]=U hebt een gemiste oproep +Comment[nn]=Du har ei tapt oppringing Comment[pl]=Nie odebrałeś połączenia Comment[pt]=Tem uma chamada não atendida Comment[pt_BR]=Você tem uma chamada não atendida Comment[ro]=Ați pierdut un apel Comment[ru]=У вас есть пропущенный вызов Comment[sk]=Máte zmeškaný hovor +Comment[sr]=Имате пропуштен позив +Comment[sr@ijekavian]=Имате пропуштен позив +Comment[sr@ijekavianlatin]=Imate propušten poziv +Comment[sr@latin]=Imate propušten poziv Comment[sv]=Du har missat ett samtal Comment[tr]=Cevapsız çağrınız var Comment[uk]=Вами пропущено телефонний дзвінок Comment[x-test]=xxYou have a missed callxx Comment[zh_CN]=您有未接来电 +Comment[zh_TW]=您有一通未接來電 Action=Popup [Event/smsReceived] Name=SMS Received +Name[ar]=استُلمت رسالة +Name[ast]=SMS recibíu Name[bg]=Получен SMS Name[ca]=S'ha rebut un SMS +Name[ca@valencia]=S'ha rebut un SMS Name[cs]=SMS přijata +Name[da]=SMS-modtaget Name[de]=SMS empfangen +Name[el]=Λήφθηκε SMS Name[en_GB]=SMS Received Name[es]=SMS recibido +Name[et]=SMS-i saamine +Name[eu]=SMS bat jaso da Name[fi]=Saatiin tekstiviesti +Name[fr]=SMS reçu Name[gl]=Recibiuse un SMS +Name[he]=התקבלה הודעת SMS Name[hu]=SMS érkezett Name[it]=SMS ricevuto +Name[ko]=SMS 받음 Name[nl]=SMS ontvangen +Name[nn]=SMS motteken Name[pl]=Otrzymano SMSa Name[pt]=SMS Recebido Name[pt_BR]=SMS recebido +Name[ru]=Входящее SMS Name[sk]=Prijatá SMS +Name[sr]=СМС примљен +Name[sr@ijekavian]=СМС примљен +Name[sr@ijekavianlatin]=SMS primljen +Name[sr@latin]=SMS primljen Name[sv]=SMS mottaget Name[tr]=SMS Alındı Name[uk]=Отримано SMS Name[x-test]=xxSMS Receivedxx Name[zh_CN]=收到短信 +Name[zh_TW]=您有收到短訊 Comment=Someone sent you an SMS +Comment[ar]=ثمّة من أرسل رسالة إليك +Comment[ast]=Daquién unvióte un SMS Comment[bg]=Някой ви изпрати текстово съобщение Comment[bs]=Neko Vam je poslao SMS poruku Comment[ca]=Algú us ha enviat un SMS +Comment[ca@valencia]=Algú vos ha enviat un SMS Comment[cs]=Někdo vám poslal SMS Comment[da]=Nogen sendte dig en SMS Comment[de]=Jemand hat Ihnen eine SMS gesendet +Comment[el]=Κάποιος σας έστειλε SMS Comment[en_GB]=Someone sent you an SMS Comment[es]=Alguien le ha enviado un SMS +Comment[et]=Keegi saatis sulle SMS-i +Comment[eu]=Norbaitek SMS bat bidali dizu Comment[fi]=Sinulle lähetettiin tekstiviesti Comment[fr]=Quelqu'un vous a envoyé un SMS Comment[gl]=Recibiu unha mensaxe SMS de alguén. +Comment[he]=מישהו שלח לך SMS Comment[hu]=Valaki SMS-t küldött önnek Comment[it]=Hai ricevuto un SMS Comment[ko]=누군가가 문자 메시지를 보냄 Comment[nl]=Iemand heeft u een SMS gestuurd +Comment[nn]=Nokon sende deg ein SMS Comment[pl]=Ktoś do ciebie wysłał SMSa Comment[pt]=Alguém lhe enviou um SMS Comment[pt_BR]=Alguém lhe enviou um SMS Comment[ro]=Cineva v-a trimis un SMS -Comment[ru]=У вас новое SMS сообщение +Comment[ru]=Вы получили SMS-сообщение Comment[sk]=Niekto vám poslal SMS +Comment[sr]=Неко вам је послао СМС +Comment[sr@ijekavian]=Неко вам је послао СМС +Comment[sr@ijekavianlatin]=Neko vam je poslao SMS +Comment[sr@latin]=Neko vam je poslao SMS Comment[sv]=Någon skickade ett SMS till dig Comment[tr]=Biri size SMS gönderdi Comment[uk]=Хтось надіслав вам SMS Comment[x-test]=xxSomeone sent you an SMSxx Comment[zh_CN]=某人给您发了短信 +Comment[zh_TW]=有人送來一封簡訊 Action=Popup [Event/batteryLow] Name=Battery Low +Name[ar]=البطّاريّة ضعيفة +Name[ast]=Batería baxa Name[bg]=Изтощена батерия Name[ca]=Bateria baixa +Name[ca@valencia]=Bateria baixa Name[cs]=Baterie je téměř vybitá +Name[da]=Lavt batteri Name[de]=Akku-Ladestand niedrig +Name[el]=Μπαταρία χαμηλή Name[en_GB]=Battery Low Name[es]=Batería baja +Name[et]=Aku laetus on madal +Name[eu]=Bateria baxu Name[fi]=Akku vähissä +Name[fr]=Batterie faible Name[gl]=Batería baixa Name[hu]=Alacsony töltöttség +Name[ia]=Batteria basse Name[it]=Batteria a livello basso +Name[ko]=배터리 부족 Name[nl]=Batterij op laag niveau +Name[nn]=Lågt batterinivå Name[pl]=Bateria na niskim poziomie Name[pt]=Bateria Fraca Name[pt_BR]=Bateria fraca +Name[ru]=Низкий заряд батареи Name[sk]=Batéria je slabá +Name[sr]=Батерија при крају +Name[sr@ijekavian]=Батерија при крају +Name[sr@ijekavianlatin]=Baterija pri kraju +Name[sr@latin]=Baterija pri kraju Name[sv]=Låg batteriladdning Name[tr]=Pil Zayıf Name[uk]=Низький рівень заряду Name[x-test]=xxBattery Lowxx Name[zh_CN]=电池电量低 +Name[zh_TW]=電池低電量 Comment=Your battery is in low state +Comment[ar]=بطّاريّتك في الحالة الضعيفة +Comment[ast]=La to batería ta escosándose Comment[bg]=Батерията ви е с нисък заряд Comment[bs]=Vaša baterija je gotovo prazna Comment[ca]=La bateria està baixa +Comment[ca@valencia]=La bateria està baixa Comment[cs]=Máte slabou baterii Comment[da]=Dit batteri er på lavt niveau Comment[de]=Der Ladestand Ihres Akkus ist niedrig +Comment[el]=Η μπαταρία σας είναι σε χαμηλό επίπεδο Comment[en_GB]=Your battery is in low state Comment[es]=La batería está en nivel bajo +Comment[et]=Aku täituvus on madal +Comment[eu]=Zure bateria egoera baxuan dago Comment[fi]=Akkusi virta on vähissä Comment[fr]=Votre batterie est faible -Comment[gl]=A batería está esgotándose. +Comment[gl]=A batería está nas últimas. +Comment[he]=הבטריה שלך הולכת להגמר Comment[hu]=Az akkumulátora feszültsége alacsony Comment[it]=La tua batteria è al livello basso Comment[ko]=배터리가 부족함 Comment[nl]=Uw batterij is bijna leeg +Comment[nn]=Det er lite batteri att Comment[pl]=Twoja bateria ma niski poziom naładowania Comment[pt]=A sua bateria está fraca Comment[pt_BR]=Sua bateria está com carga baixa Comment[ro]=Acumulatorul are un nivel scăzut -Comment[ru]=Низкий заряд батареи +Comment[ru]=Низкий заряд батареи мобильного устройства Comment[sk]=Vaša batéria je na nízkom stave +Comment[sr]=Ваша батерија је на издисају +Comment[sr@ijekavian]=Ваша батерија је на издисају +Comment[sr@ijekavianlatin]=Vaša baterija je na izdisaju +Comment[sr@latin]=Vaša baterija je na izdisaju Comment[sv]=Batteriet är nästan slut Comment[tr]=Pilinizin seviyesi düşük Comment[uk]=Рівень заряду акумулятора є низьким Comment[x-test]=xxYour battery is in low statexx Comment[zh_CN]=您的电池电量低 +Comment[zh_TW]=您的電池處於低容量狀態 Action=Popup [Event/pingReceived] Name=Ping Received +Name[ar]=استُلمت وكزة +Name[ast]=Ping recibíu Name[bg]=Пингът е приет Name[ca]=S'ha rebut un ping +Name[ca@valencia]=S'ha rebut un ping Name[cs]=Ping přijat +Name[da]=Ping modtaget Name[de]=Ping empfangen +Name[el]=Λήφθηκε ping Name[en_GB]=Ping Received Name[es]=Ping recibido +Name[et]=Pingi saamine +Name[eu]=Ping jaso da Name[fi]=Saatiin tiedustelupaketti +Name[fr]=Ping reçu Name[gl]=Recibiuse un ping +Name[he]=התקבל פינג Name[hu]=Ping érkezett Name[it]=Ping ricevuto +Name[ko]=핑 받음 Name[nl]=Ping ontvangen +Name[nn]=Pingsignal motteke Name[pl]=Otrzymano ping Name[pt]=Contacto Recebido Name[pt_BR]=Ping recebido +Name[ru]=Пинг получен Name[sk]=Prijatý ping +Name[sr]=Примљен пинг +Name[sr@ijekavian]=Примљен пинг +Name[sr@ijekavianlatin]=Primljen ping +Name[sr@latin]=Primljen ping Name[sv]=Ping mottaget Name[tr]=Ping Alındı Name[uk]=Отримано сигнал підтримки зв’язку Name[x-test]=xxPing Receivedxx Name[zh_CN]=收到 Ping +Name[zh_TW]=收到Ping回應 Comment=Ping received +Comment[ar]=استُلمت وكزة +Comment[ast]=Recibióse un ping Comment[bg]=Пингът е приет Comment[bs]=Primili ste ping Comment[ca]=S'ha rebut un ping +Comment[ca@valencia]=S'ha rebut un ping Comment[cs]=Ping přijat Comment[da]=Ping modtaget Comment[de]=Ping empfangen +Comment[el]=Λήφθηκε ping Comment[en_GB]=Ping received Comment[es]=Ping recibido +Comment[et]=Pingi saamine +Comment[eu]=Ping jaso da Comment[fi]=Saatiin tiedustelupaketti -Comment[fr]=Commande « Ping » reçue +Comment[fr]=Ping reçu Comment[gl]=Recibiuse un “ping”. +Comment[he]=התקבל פינג Comment[hu]=Ping érkezett Comment[it]=Hai ricevuto un ping Comment[ko]=핑 받음 Comment[nl]=Ping ontvangen +Comment[nn]=Pingsignal motteke Comment[pl]=Otrzymano ping Comment[pt]=Pedido de rede recebido Comment[pt_BR]=Ping recebido Comment[ro]=Ping primit -Comment[ru]=Пинг получен +Comment[ru]=Получен тестовый сигнал Comment[sk]=Prijatý ping +Comment[sr]=Примљен пинг +Comment[sr@ijekavian]=Примљен пинг +Comment[sr@ijekavianlatin]=Primljen ping +Comment[sr@latin]=Primljen ping Comment[sv]=Ping mottaget Comment[tr]=Ping alındı Comment[uk]=Отримано сигнал підтримки зв’язку Comment[x-test]=xxPing receivedxx Comment[zh_CN]=收到 ping +Comment[zh_TW]=接收到遠端的Ping回應封包 Action=Popup [Event/notification] Name=Generic Notification +Name[ar]=إخطار عموميّ +Name[ast]=Avisu xenéricu Name[bg]=Общо уведомление Name[ca]=Notificació genèrica +Name[ca@valencia]=Notificació genèrica Name[cs]=Obecná hlášení +Name[da]=Generisk bekendtgørelse Name[de]=Allgemeine Benachrichtigung +Name[el]=Γενική ειδοποίηση Name[en_GB]=Generic Notification Name[es]=Notificación genérica +Name[et]=Üldine märguanne +Name[eu]=Jakinarazpen arrunta Name[fi]=Yleinen ilmoitus +Name[fr]=Notification Name[gl]=Notificación xenérica +Name[he]=התראה כללית Name[hu]=Általános értesítés Name[it]=Notifica generica +Name[ko]=일반 알림 Name[nl]=Algemene melding +Name[nn]=Generell varsling Name[pl]=Zwykłe powiadomienie Name[pt]=Notificação Genérica Name[pt_BR]=Notificação genérica +Name[ru]=Общее уведомление Name[sk]=Všeobecné upozornenie +Name[sr]=Опште обавештење +Name[sr@ijekavian]=Опште обавештење +Name[sr@ijekavianlatin]=Opšte obaveštenje +Name[sr@latin]=Opšte obaveštenje Name[sv]=Generell underrättelse Name[tr]=Genel Bildirim Name[uk]=Загальне сповіщення Name[x-test]=xxGeneric Notificationxx Name[zh_CN]=一般通知 +Name[zh_TW]=通用通知 Comment=Notification received +Comment[ar]=استُلم إخطار +Comment[ast]=Recibióse un avisu Comment[bg]=Уведомлението е прието Comment[bs]=Primjeno obavještenje Comment[ca]=Notificació rebuda +Comment[ca@valencia]=Notificació rebuda Comment[cs]=Bylo přijato upozornění Comment[da]=Bekendtgørelse modtaget Comment[de]=Benachrichtigung eingegangen +Comment[el]=Λήφθηκε ειδοποίηση Comment[en_GB]=Notification received Comment[es]=Notificación recibida +Comment[et]=Saadi märguanne +Comment[eu]=Jakinarazpena jaso da Comment[fi]=Saatiin ilmoitus Comment[fr]=Notification reçue Comment[gl]=Recibiuse unha notificación. +Comment[he]=התראה התקבלה Comment[hu]=Értesítés érkezett Comment[it]=Hai ricevuto una notifica Comment[ko]=알림 받음 Comment[nl]=Melding ontvangen +Comment[nn]=Varsling motteken Comment[pl]=Otrzymano powiadomienie Comment[pt]=Notificação recebida Comment[pt_BR]=Notificação recebida Comment[ro]=Notificare primită -Comment[ru]=Уведомление получено +Comment[ru]=На мобильном устройстве получено уведомление Comment[sk]=Prijaté oznámenie +Comment[sr]=Примљено је обавештење +Comment[sr@ijekavian]=Примљено је обавештење +Comment[sr@ijekavianlatin]=Primljeno je obaveštenje +Comment[sr@latin]=Primljeno je obaveštenje Comment[sv]=Underrättelse mottagen Comment[tr]=Bildirim alındı Comment[uk]=Отримано сповіщення Comment[x-test]=xxNotification receivedxx Comment[zh_CN]=收到通知 +Comment[zh_TW]=收到通知 Action=Popup [Event/transferReceived] Name=File Transfer +Name[ar]=نقل الملفّات +Name[ast]=Tresferencia de ficheros Name[bg]=Прехвърляне на файл Name[ca]=Transferència de fitxers +Name[ca@valencia]=Transferència de fitxers Name[cs]=Přenos souboru +Name[da]=Filoverførsel Name[de]=Dateiübertragung +Name[el]=Μεταφορά αρχείου Name[en_GB]=File Transfer Name[es]=Transferencia de archivo +Name[et]=Failiedastus +Name[eu]=Fitxategi-transferentzia Name[fi]=Tiedostonsiirto +Name[fr]=Transfert de fichiers Name[gl]=Transferencia dun ficheiro +Name[he]=העברת קובץ Name[hu]=Fájlátvitel +Name[ia]=Transferimento de file Name[it]=Trasferimento file +Name[ko]=파일 전송 Name[nl]=Bestandsoverdracht +Name[nn]=Filoverføring Name[pl]=Przesył plików Name[pt]=Transferência de Ficheiros Name[pt_BR]=Transferência de arquivo +Name[ru]=Передача файла Name[sk]=Prenos súboru +Name[sr]=Пренос фајлова +Name[sr@ijekavian]=Пренос фајлова +Name[sr@ijekavianlatin]=Prenos fajlova +Name[sr@latin]=Prenos fajlova Name[sv]=Filöverföring Name[tr]=Dosya Aktarımı Name[uk]=Перенесення файлів Name[x-test]=xxFile Transferxx Name[zh_CN]=文件传送 +Name[zh_TW]=檔案傳輸 Comment=Incoming file +Comment[ar]=ملفّ قادم +Comment[ast]=Ficheru entrante Comment[bg]=Входящ файл Comment[ca]=Fitxer entrant +Comment[ca@valencia]=Fitxer entrant Comment[cs]=Příchozí soubor +Comment[da]=Indkommende fil Comment[de]=Eingehende Datei +Comment[el]=Εισερχόμενο αρχείο Comment[en_GB]=Incoming file Comment[es]=Archivo entrante +Comment[et]=Sisenev fail +Comment[eu]=Sarrerako fitxategia Comment[fi]=Saapuva tiedosto +Comment[fr]=Fichier entrant Comment[gl]=Ficheiro entrante +Comment[he]=קובץ מגיע Comment[hu]=Bejövő fájl Comment[it]=File in ingresso +Comment[ko]=파일 수신 Comment[nl]=Inkomend bestand +Comment[nn]=Innkommande fil Comment[pl]=Przychodzący plik Comment[pt]=Ficheiro recebido Comment[pt_BR]=Arquivo recebido +Comment[ru]=С мобильного устройства отправлен файл Comment[sk]=Prichádzajúci súbor +Comment[sr]=Долазни фајл +Comment[sr@ijekavian]=Долазни фајл +Comment[sr@ijekavianlatin]=Dolazni fajl +Comment[sr@latin]=Dolazni fajl Comment[sv]=Inkommande fil Comment[tr]=Gelen dosya Comment[uk]=Вхідний файл Comment[x-test]=xxIncoming filexx Comment[zh_CN]=正在传入的文件 +Comment[zh_TW]=接收檔案 Action=Popup diff --git a/plugins/kdeconnect_plugin.desktop b/plugins/kdeconnect_plugin.desktop index 51b0f1ee..5379fce9 100644 --- a/plugins/kdeconnect_plugin.desktop +++ b/plugins/kdeconnect_plugin.desktop @@ -1,38 +1,51 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KdeConnect/Plugin X-KDE-Derived=KPluginInfo Name=KDEConnect Plugin +Name[ar]=ملحقة KDEConnect +Name[ast]=Complementu de KDEConnect Name[bg]=Приставка на KDEConnect Name[bs]=Priključak za KDE konekciju Name[ca]=Connector del KDE Connect +Name[ca@valencia]=Connector del KDE Connect Name[cs]=Modul KDEConnect Name[da]=KDEConnect-plugin Name[de]=KDEConnect-Modul +Name[el]=Πρόσθετο του KDEConnect Name[en_GB]=KDEConnect Plugin Name[es]=Complemento de KDEConnect +Name[et]=KDEConnecti plugin +Name[eu]=KDEConnect plugina Name[fi]=KDE Connect -liitännäinen Name[fr]=Module externe KDEConnect Name[gl]=Complemento de KDE Connect +Name[he]=תוספי KDEConnect Name[hu]=KDEConnect bővítmény Name[it]=Estensione KDEConnect Name[ko]=KDEConnect 플러그인 Name[nl]=Plug-in van KDEConnect +Name[nn]=KDEConnect-tillegg Name[pl]=Wtyczka KDEConnect Name[pt]='Plugin' do KDEConnect Name[pt_BR]=Plugin do KDEConnect Name[ro]=Extensie KDEConnect -Name[ru]=Модуль KDEConnect +Name[ru]=Модуль KDE Connect Name[sk]=Plugin KDEConnect +Name[sr]=КДЕ‑конекцијин прикључак +Name[sr@ijekavian]=КДЕ‑конекцијин прикључак +Name[sr@ijekavianlatin]=KDE‑konekcijin priključak +Name[sr@latin]=KDE‑konekcijin priključak Name[sv]=KDE anslutningsinsticksprogram Name[tr]=KDEConnect Eklentisi Name[uk]=Додаток KDEConnect Name[x-test]=xxKDEConnect Pluginxx Name[zh_CN]=KDEConnect 插件 +Name[zh_TW]=KDE 連線擴展功能插件 # mandatory, list of all the package types supported [PropertyDef::X-KdeConnect-SupportedPackageType] Type=QStringList [PropertyDef::X-KdeConnect-OutgoingPackageType] Type=QStringList diff --git a/plugins/lockdevice/kdeconnect_lockdevice.json b/plugins/lockdevice/kdeconnect_lockdevice.json index 80013cd9..520a94bf 100644 --- a/plugins/lockdevice/kdeconnect_lockdevice.json +++ b/plugins/lockdevice/kdeconnect_lockdevice.json @@ -1,50 +1,96 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "aleixpol@kde.org", - "Name": "Aleix Pol" + "Email": "aleixpol@kde.org", + "Name": "Aleix Pol", + "Name[sr@ijekavian]": "Алекс Пол Гонзалез", + "Name[sr@ijekavianlatin]": "Aleks Pol Gonzalez", + "Name[sr@latin]": "Aleks Pol Gonzalez", + "Name[sr]": "Алекс Пол Гонзалез", + "Name[x-test]": "xxAleix Polxx" } - ], - "Description": "Locks your systems", - "Description[ca]": "Bloqueja els vostres sistemes", - "Description[de]": "Sperrt Ihre Systeme", - "Description[es]": "Bloquear sus sistemas", - "Description[gl]": "Bloquea os seus sistemas.", - "Description[nl]": "Vergrendeld uw systemen", - "Description[pl]": "Zablokuj swoje systemy", - "Description[pt]": "Bloqueia os seus sistemas", - "Description[pt_BR]": "Bloqueia seus sistemas", - "Description[sv]": "Låser dina system", - "Description[uk]": "Блокує вашу систему", - "Description[x-test]": "xxLocks your systemsxx", - "EnabledByDefault": true, - "Icon": "applications-miscelaneaous", - "Id": "kdeconnect_lockdevice", - "License": "GPL", - "Name": "LockDevice", - "Name[ca]": "Bloqueja el dispositiu", - "Name[de]": "Gerätesperrung", - "Name[es]": "Bloquear dispositivo", - "Name[gl]": "Bloqueo do dispositivo", - "Name[nl]": "Apparaat vergrendelen", - "Name[pl]": "ZablokujUrządzenie", - "Name[pt]": "Bloqueio de Dispositivo", - "Name[pt_BR]": "Bloqueio de dispositivo", - "Name[sv]": "Lås enhet", - "Name[uk]": "LockDevice", - "Name[x-test]": "xxLockDevicexx", + ], + "Description": "Locks your systems", + "Description[ar]": "أوصد الأنظمة", + "Description[ca@valencia]": "Bloqueja els vostres sistemes", + "Description[ca]": "Bloqueja els vostres sistemes", + "Description[cs]": "Zamkne vaše systémy", + "Description[da]": "Låser dine systemer", + "Description[de]": "Sperrt Ihre Systeme", + "Description[el]": "Κλειδώνει τα συστήματά σας", + "Description[es]": "Bloquear sus sistemas", + "Description[et]": "Oma süsteemide lukustamine", + "Description[eu]": "Zure sistemak blokeatzen ditu", + "Description[fi]": "Lukitsee järjestelmäsi", + "Description[fr]": "Bloque votre système", + "Description[gl]": "Bloquea os seus sistemas.", + "Description[it]": "Blocca i tuoi sistemi", + "Description[ko]": "내 시스템 잠그기", + "Description[nl]": "Vergrendeld uw systemen", + "Description[nn]": "Lås systemet", + "Description[pl]": "Zablokuj swoje systemy", + "Description[pt]": "Bloqueia os seus sistemas", + "Description[pt_BR]": "Bloqueia seus sistemas", + "Description[ru]": "Блокировка ваших систем", + "Description[sk]": "Zamkne vaše systémy", + "Description[sr@ijekavian]": "Закључава ваш систем", + "Description[sr@ijekavianlatin]": "Zaključava vaš sistem", + "Description[sr@latin]": "Zaključava vaš sistem", + "Description[sr]": "Закључава ваш систем", + "Description[sv]": "Låser dina system", + "Description[tr]": "Sistemlerinizi kilitler", + "Description[uk]": "Блокує вашу систему", + "Description[x-test]": "xxLocks your systemsxx", + "Description[zh_CN]": "锁定您的系统", + "Description[zh_TW]": "鎖定您的系統", + "EnabledByDefault": true, + "Icon": "applications-miscelaneaous", + "Id": "kdeconnect_lockdevice", + "License": "GPL", + "Name": "LockDevice", + "Name[ar]": "أوصد الجهاز", + "Name[ca@valencia]": "Bloqueja el dispositiu", + "Name[ca]": "Bloqueja el dispositiu", + "Name[cs]": "Uzamknout zařízení", + "Name[da]": "LåsEnhed", + "Name[de]": "Gerätesperrung", + "Name[es]": "Bloquear dispositivo", + "Name[et]": "Seadme lukustamine", + "Name[eu]": "BlokeatuGailua", + "Name[fi]": "Lukitse laite", + "Name[gl]": "Bloqueo do dispositivo", + "Name[it]": "Blocco dispositivo", + "Name[ko]": "장치 잠금", + "Name[nl]": "Apparaat vergrendelen", + "Name[nn]": "Lås eining", + "Name[pl]": "ZablokujUrządzenie", + "Name[pt]": "Bloqueio de Dispositivo", + "Name[pt_BR]": "Bloqueio de dispositivo", + "Name[ru]": "Блокировка устройства", + "Name[sk]": "Zamknúť zariadenie", + "Name[sr@ijekavian]": "Катанац", + "Name[sr@ijekavianlatin]": "Katanac", + "Name[sr@latin]": "Katanac", + "Name[sr]": "Катанац", + "Name[sv]": "Lås enhet", + "Name[tr]": "AygıtıKilitle", + "Name[x-test]": "xxLockDevicexx", + "Name[zh_CN]": "锁定设备", + "Name[zh_TW]": "鎖定裝置", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "https://kde.org" - }, + }, "X-KdeConnect-OutgoingPackageType": [ + "kdeconnect.lock.request", "kdeconnect.lock" - ], + ], "X-KdeConnect-SupportedPackageType": [ + "kdeconnect.lock.request", "kdeconnect.lock" ] -} \ No newline at end of file +} diff --git a/plugins/lockdevice/lockdeviceplugin.cpp b/plugins/lockdevice/lockdeviceplugin.cpp index a24b66b9..27d60027 100644 --- a/plugins/lockdevice/lockdeviceplugin.cpp +++ b/plugins/lockdevice/lockdeviceplugin.cpp @@ -1,108 +1,103 @@ /** * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "lockdeviceplugin.h" #include #include #include #include #include #include "screensaverdbusinterface.h" #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectLockPluginFactory, "kdeconnect_lockdevice.json", registerPlugin(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_LOCKREMOTE, "kdeconnect.plugin.lock") LockDevicePlugin::LockDevicePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_remoteLocked(false) , m_iface(nullptr) { } LockDevicePlugin::~LockDevicePlugin() { delete m_iface; } bool LockDevicePlugin::isLocked() const { return m_remoteLocked; } void LockDevicePlugin::setLocked(bool locked) { - NetworkPackage np(PACKAGE_TYPE_LOCK); - np.set("setLocked", locked); + NetworkPackage np(PACKAGE_TYPE_LOCK_REQUEST, {{"setLocked", locked}}); sendPackage(np); } bool LockDevicePlugin::receivePackage(const NetworkPackage & np) { - if (np.has("isLocked")) { - bool locked = np.get("isLocked"); + if (np.has(QStringLiteral("isLocked"))) { + bool locked = np.get(QStringLiteral("isLocked")); if (m_remoteLocked != locked) { m_remoteLocked = locked; Q_EMIT lockedChanged(locked); } } - bool sendState = np.has("requestLocked"); - if (np.has("setLocked")) { - iface()->SetActive(np.get("setLocked")); + bool sendState = np.has(QStringLiteral("requestLocked")); + if (np.has(QStringLiteral("setLocked"))) { + iface()->SetActive(np.get(QStringLiteral("setLocked"))); sendState = true; } if (sendState) { - NetworkPackage np(PACKAGE_TYPE_LOCK); - np.set("isLocked", iface()->GetActive()); + NetworkPackage np(PACKAGE_TYPE_LOCK, QVariantMap {{"isLocked", QVariant::fromValue(iface()->GetActive())}}); sendPackage(np); } return true; } OrgFreedesktopScreenSaverInterface* LockDevicePlugin::iface() { if (!m_iface) { - m_iface = new OrgFreedesktopScreenSaverInterface("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", QDBusConnection::sessionBus()); + m_iface = new OrgFreedesktopScreenSaverInterface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/org/freedesktop/ScreenSaver"), QDBusConnection::sessionBus()); if(!m_iface->isValid()) qCWarning(KDECONNECT_PLUGIN_LOCKREMOTE) << "Couldn't connect to the ScreenSaver interface"; } return m_iface; } void LockDevicePlugin::connected() { - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); - - NetworkPackage np(PACKAGE_TYPE_LOCK); - np.set("requestLocked", QVariant()); + NetworkPackage np(PACKAGE_TYPE_LOCK_REQUEST, {{"requestLocked", QVariant()}}); sendPackage(np); } QString LockDevicePlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/lockdevice"; } #include "lockdeviceplugin.moc" diff --git a/plugins/lockdevice/lockdeviceplugin.h b/plugins/lockdevice/lockdeviceplugin.h index 834ae3a1..24bb57e5 100644 --- a/plugins/lockdevice/lockdeviceplugin.h +++ b/plugins/lockdevice/lockdeviceplugin.h @@ -1,61 +1,62 @@ /** * 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 . */ #ifndef LOCKDEVICEPLUGIN_H #define LOCKDEVICEPLUGIN_H #include #include class OrgFreedesktopScreenSaverInterface; -#define PACKAGE_TYPE_LOCK QLatin1String("kdeconnect.lock") +#define PACKAGE_TYPE_LOCK QStringLiteral("kdeconnect.lock") +#define PACKAGE_TYPE_LOCK_REQUEST QStringLiteral("kdeconnect.lock.request") class Q_DECL_EXPORT LockDevicePlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.lockdevice") Q_PROPERTY(bool isLocked READ isLocked WRITE setLocked NOTIFY lockedChanged) public: - explicit LockDevicePlugin(QObject *parent, const QVariantList &args); - virtual ~LockDevicePlugin(); + explicit LockDevicePlugin(QObject* parent, const QVariantList &args); + ~LockDevicePlugin() override; bool isLocked() const; void setLocked(bool b); + QString dbusPath() const override; void connected() override; bool receivePackage(const NetworkPackage & np) override; Q_SIGNALS: void lockedChanged(bool locked); private: - QString dbusPath() const; bool m_remoteLocked; OrgFreedesktopScreenSaverInterface* iface(); OrgFreedesktopScreenSaverInterface* m_iface; }; #endif diff --git a/plugins/mousepad/CMakeLists.txt b/plugins/mousepad/CMakeLists.txt index 85798afb..f76ce51d 100644 --- a/plugins/mousepad/CMakeLists.txt +++ b/plugins/mousepad/CMakeLists.txt @@ -1,22 +1,32 @@ -set(kdeconnect_mousepad_SRCS - mousepadplugin.cpp -) +find_package(LibFakeKey) +set_package_properties(LibFakeKey PROPERTIES DESCRIPTION "fake key events" + URL "https://www.yoctoproject.org/tools-resources/projects/matchbox" + TYPE OPTIONAL + PURPOSE "Needed for the remote mousepad plugin" + ) -find_package(XTest REQUIRED) -find_package(X11 REQUIRED) -find_package(LibFakeKey REQUIRED) -find_package(Qt5X11Extras REQUIRED) +if (LibFakeKey_FOUND) + find_package(XTest REQUIRED) + find_package(X11 REQUIRED) + find_package(Qt5X11Extras REQUIRED) +endif() find_package(KF5Wayland 5.3.90) +set(HAVE_X11 ${LibFakeKey_FOUND}) set(HAVE_WAYLAND ${KF5Wayland_FOUND}) configure_file(config-mousepad.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-mousepad.h ) -kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES ${kdeconnect_mousepad_SRCS}) +kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES mousepadplugin.cpp) -include_directories(${XTEST_INCLUDE_DIRS} ${X11_INCLUDE_DIR} ${LibFakeKey_INCLUDE_DIRS}) +if (HAVE_X11) + include_directories(${XTEST_INCLUDE_DIRS} ${X11_INCLUDE_DIR} ${LibFakeKey_INCLUDE_DIRS}) +endif() -target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui Qt5::X11Extras ${X11_LIBRARIES} ${XTEST_LIBRARIES} ${LibFakeKey_LIBRARIES} KF5::I18n) +target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui KF5::I18n) if(HAVE_WAYLAND) target_link_libraries(kdeconnect_mousepad KF5::WaylandClient) endif() +if(HAVE_X11) + target_link_libraries(kdeconnect_mousepad Qt5::X11Extras ${X11_LIBRARIES} ${XTEST_LIBRARIES} ${LibFakeKey_LIBRARIES}) +endif() diff --git a/plugins/mousepad/config-mousepad.h.cmake b/plugins/mousepad/config-mousepad.h.cmake index e6830040..e962b6ab 100644 --- a/plugins/mousepad/config-mousepad.h.cmake +++ b/plugins/mousepad/config-mousepad.h.cmake @@ -1 +1,2 @@ #cmakedefine01 HAVE_WAYLAND +#cmakedefine01 HAVE_X11 diff --git a/plugins/mousepad/kdeconnect_mousepad.json b/plugins/mousepad/kdeconnect_mousepad.json index eb4ea9fe..29ed04aa 100644 --- a/plugins/mousepad/kdeconnect_mousepad.json +++ b/plugins/mousepad/kdeconnect_mousepad.json @@ -1,63 +1,97 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "ahmedibrahimkhali@gmail.com", - "Name": "Ahmed I. Khalil" + "Email": "ahmedibrahimkhali@gmail.com", + "Name": "Ahmed I. Khalil", + "Name[ar]": "أحمد إبراهيم خليل", + "Name[sr@ijekavian]": "Ахмед И. Калил", + "Name[sr@ijekavianlatin]": "Ahmed I. Kalil", + "Name[sr@latin]": "Ahmed I. Kalil", + "Name[sr]": "Ахмед И. Калил", + "Name[x-test]": "xxAhmed I. Khalilxx" } - ], - "Description": "Use your phone as a touchpad and keyboard", - "Description[ca]": "Empra el telèfon com un ratolí tàctil i teclat", - "Description[cs]": "Používejte svůj telefon jako touchpad a klávesnici", - "Description[de]": "Verwendet Ihr Handy als Touchpad und Tastatur", - "Description[en_GB]": "Use your phone as a touchpad and keyboard", - "Description[es]": "Usar teléfono como panel táctil y teclado", - "Description[fi]": "Käytä puhelintasi kosketuslevynä ja näppäimistönä", - "Description[gl]": "Usar o teléfono móbil como touchpad e teclado.", - "Description[hu]": "A telefon használata érintőtáblaként és billentyűzetként", - "Description[it]": "Usa il telefono come touchpad e tastiera", - "Description[nl]": "Uw telefoon gebruiken als een touchpad en toetsenbord", - "Description[pl]": "Używaj swojego telefonu jako gładzika i klawiatury", - "Description[pt]": "Use o seu telefone como um rato e teclado por toque", - "Description[pt_BR]": "Use seu telefone como um touchpad e teclado", - "Description[sk]": "Použite váš telefón ako touchpad a klávesnicu", - "Description[sv]": "Använd telefonen som mus och tangentbord", - "Description[tr]": "Telefonunuzu dokunmatik yüzey ve klayve olarak kullanın", - "Description[uk]": "Скористайтеся телефоном як замінником сенсорної панелі і клавіатури", - "Description[x-test]": "xxUse your phone as a touchpad and keyboardxx", - "Description[zh_CN]": "用您的手机作为触摸板和键盘", - "EnabledByDefault": true, - "Icon": "edit-select", - "Id": "kdeconnect_mousepad", - "License": "GPL", - "Name": "Virtual input", - "Name[ca]": "Entrada virtual", - "Name[cs]": "Virtuální vstup", - "Name[de]": "Virtuelle Eingabe", - "Name[en_GB]": "Virtual input", - "Name[es]": "Entrada virtual", - "Name[fi]": "Virtuaalinen syöttö", - "Name[gl]": "Entrada virtual", - "Name[hu]": "Virtuális bevitel", - "Name[it]": "Inserimento virtuale", - "Name[nl]": "Virtuele invoer", - "Name[pl]": "Wirtualna obsługa", - "Name[pt]": "Entrada virtual", - "Name[pt_BR]": "Entrada virtual", - "Name[sk]": "Virtuálny vstup", - "Name[sv]": "Virtuell inmatning", - "Name[tr]": "Sanal içe aktarma", - "Name[uk]": "Віртуальне введення даних", - "Name[x-test]": "xxVirtual inputxx", - "Name[zh_CN]": "虚拟输入", + ], + "Description": "Use your phone as a touchpad and keyboard", + "Description[ar]": "استخدم الهاتف كفأرة ولوحة مفاتيح", + "Description[ca@valencia]": "Empra el telèfon com un ratolí tàctil i teclat", + "Description[ca]": "Empra el telèfon com un ratolí tàctil i teclat", + "Description[cs]": "Používejte svůj telefon jako touchpad a klávesnici", + "Description[da]": "Brug din telefon som touchpad og tastatur", + "Description[de]": "Verwendet Ihr Handy als Touchpad und Tastatur", + "Description[el]": "Χρήση του τηλεφώνου σας ως οθόνη αφής και πληκτρολογίου", + "Description[es]": "Usar teléfono como panel táctil y teclado", + "Description[et]": "Telefoni kasutamine puutepadja ja klaviatuurina", + "Description[eu]": "Erabili zure telefonoa touchpad eta teklatu gisa", + "Description[fi]": "Käytä puhelintasi kosketuslevynä ja näppäimistönä", + "Description[fr]": "Utilisez votre téléphone comme un pavé tactile et un clavier", + "Description[gl]": "Usar o teléfono móbil como área táctil e teclado.", + "Description[hu]": "A telefon használata érintőtáblaként és billentyűzetként", + "Description[it]": "Usa il telefono come touchpad e tastiera", + "Description[ko]": "휴대폰을 터치패드와 키보드로 사용", + "Description[nl]": "Uw telefoon gebruiken als een touchpad en toetsenbord", + "Description[nn]": "Bruk telefonen som styreplate og tastatur", + "Description[pl]": "Używaj swojego telefonu jako gładzika i klawiatury", + "Description[pt]": "Use o seu telefone como um rato e teclado por toque", + "Description[pt_BR]": "Use seu telefone como um touchpad e teclado", + "Description[ru]": "Использование телефона в качестве сенсорной панели и клавиатуры", + "Description[sk]": "Použite váš telefón ako touchpad a klávesnicu", + "Description[sr@ijekavian]": "Користите телефон као додирник и тастатуру", + "Description[sr@ijekavianlatin]": "Koristite telefon kao dodirnik i tastaturu", + "Description[sr@latin]": "Koristite telefon kao dodirnik i tastaturu", + "Description[sr]": "Користите телефон као додирник и тастатуру", + "Description[sv]": "Använd telefonen som mus och tangentbord", + "Description[tr]": "Telefonunuzu dokunmatik yüzey ve klayve olarak kullanın", + "Description[uk]": "Скористайтеся телефоном як замінником сенсорної панелі і клавіатури", + "Description[x-test]": "xxUse your phone as a touchpad and keyboardxx", + "Description[zh_CN]": "用您的手机作为触摸板和键盘", + "Description[zh_TW]": "讓您的智慧型手機當作觸碰板與鍵盤", + "EnabledByDefault": true, + "Icon": "edit-select", + "Id": "kdeconnect_mousepad", + "License": "GPL", + "Name": "Virtual input", + "Name[ar]": "دخل وهميّ", + "Name[ca@valencia]": "Entrada virtual", + "Name[ca]": "Entrada virtual", + "Name[cs]": "Virtuální vstup", + "Name[da]": "Virtuelt input", + "Name[de]": "Virtuelle Eingabe", + "Name[el]": "Εικονικά στοιχεία εισόδου", + "Name[es]": "Entrada virtual", + "Name[et]": "Virtuaalsisestus", + "Name[eu]": "Sarrera birtuala", + "Name[fi]": "Virtuaalinen syöttö", + "Name[fr]": "Entrée virtuelle", + "Name[gl]": "Entrada virtual", + "Name[hu]": "Virtuális bevitel", + "Name[it]": "Inserimento virtuale", + "Name[ko]": "가상 입력", + "Name[nl]": "Virtuele invoer", + "Name[nn]": "Virtuelt tastatur", + "Name[pl]": "Wirtualna obsługa", + "Name[pt]": "Entrada virtual", + "Name[pt_BR]": "Entrada virtual", + "Name[ru]": "Виртуальный ввод", + "Name[sk]": "Virtuálny vstup", + "Name[sr@ijekavian]": "Виртуелни унос", + "Name[sr@ijekavianlatin]": "Virtuelni unos", + "Name[sr@latin]": "Virtuelni unos", + "Name[sr]": "Виртуелни унос", + "Name[sv]": "Virtuell inmatning", + "Name[tr]": "Sanal içe aktarma", + "Name[uk]": "Віртуальне введення даних", + "Name[x-test]": "xxVirtual inputxx", + "Name[zh_CN]": "虚拟输入", + "Name[zh_TW]": "虛擬輸入", "ServiceTypes": [ "KdeConnect/Plugin" - ], + ], "Version": "0.1" - }, - "X-KdeConnect-OutgoingPackageType": [], + }, + "X-KdeConnect-OutgoingPackageType": [], "X-KdeConnect-SupportedPackageType": [ - "kdeconnect.mousepad" + "kdeconnect.mousepad.request" ] -} \ No newline at end of file +} diff --git a/plugins/mousepad/mousepadplugin.cpp b/plugins/mousepad/mousepadplugin.cpp index 89ba532f..48689eef 100644 --- a/plugins/mousepad/mousepadplugin.cpp +++ b/plugins/mousepad/mousepadplugin.cpp @@ -1,298 +1,337 @@ /** * Copyright 2014 Ahmed I. Khalil * Copyright 2015 Martin Gräßlin * * 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 "mousepadplugin.h" #include #include #include #include + +#if HAVE_X11 #include #include #include #include +#endif #if HAVE_WAYLAND #include #include #include #endif K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mousepad.json", registerPlugin< MousepadPlugin >(); ) enum MouseButtons { LeftMouseButton = 1, MiddleMouseButton = 2, RightMouseButton = 3, MouseWheelUp = 4, MouseWheelDown = 5 }; +#if HAVE_X11 //Translation table to keep in sync within all the implementations int SpecialKeysMap[] = { 0, // Invalid XK_BackSpace, // 1 XK_Tab, // 2 XK_Linefeed, // 3 XK_Left, // 4 XK_Up, // 5 XK_Right, // 6 XK_Down, // 7 XK_Page_Up, // 8 XK_Page_Down, // 9 XK_Home, // 10 XK_End, // 11 XK_Return, // 12 XK_Delete, // 13 XK_Escape, // 14 XK_Sys_Req, // 15 XK_Scroll_Lock, // 16 0, // 17 0, // 18 0, // 19 0, // 20 XK_F1, // 21 XK_F2, // 22 XK_F3, // 23 XK_F4, // 24 XK_F5, // 25 XK_F6, // 26 XK_F7, // 27 XK_F8, // 28 XK_F9, // 29 XK_F10, // 30 XK_F11, // 31 XK_F12, // 32 }; +#endif template size_t arraySize(T(&arr)[N]) { (void)arr; return N; } MousepadPlugin::MousepadPlugin(QObject* parent, const QVariantList& args) - : KdeConnectPlugin(parent, args), m_fakekey(0), m_x11(QX11Info::isPlatformX11()) + : KdeConnectPlugin(parent, args) +#if HAVE_X11 + , m_fakekey(nullptr) + , m_x11(QX11Info::isPlatformX11()) +#else + , m_x11(false) +#endif #if HAVE_WAYLAND , m_waylandInput(nullptr) , m_waylandAuthenticationRequested(false) #endif { #if HAVE_WAYLAND setupWaylandIntegration(); #endif } MousepadPlugin::~MousepadPlugin() { +#if HAVE_X11 if (m_fakekey) { free(m_fakekey); - m_fakekey = 0; + m_fakekey = nullptr; } +#endif } bool MousepadPlugin::receivePackage(const NetworkPackage& np) { +#if HAVE_X11 if (m_x11) { return handlePackageX11(np); } +#endif #if HAVE_WAYLAND if (m_waylandInput) { if (!m_waylandAuthenticationRequested) { m_waylandInput->authenticate(i18n("KDE Connect"), i18n("Use your phone as a touchpad and keyboard")); m_waylandAuthenticationRequested = true; } handPackageWayland(np); } #endif return false; } -bool MousepadPlugin::handlePackageX11(const NetworkPackage &np) +#if HAVE_X11 +bool isLeftHanded(Display * display) +{ + unsigned char map[20]; + int num_buttons = XGetPointerMapping(display, map, 20); + if( num_buttons == 1 ) { + return false; + } else if( num_buttons == 2 ) { + return ( (int)map[0] == 2 && (int)map[1] == 1 ); + } else { + return ( (int)map[0] == 3 && (int)map[2] == 1 ); + } +} +#endif + +#if HAVE_X11 +bool MousepadPlugin::handlePackageX11(const NetworkPackage& np) { //qDebug() << np.serialize(); //TODO: Split mouse/keyboard in two different plugins to avoid this big if statement - float dx = np.get("dx", 0); - float dy = np.get("dy", 0); + float dx = np.get(QStringLiteral("dx"), 0); + float dy = np.get(QStringLiteral("dy"), 0); - bool isSingleClick = np.get("singleclick", false); - bool isDoubleClick = np.get("doubleclick", false); - bool isMiddleClick = np.get("middleclick", false); - bool isRightClick = np.get("rightclick", false); - bool isSingleHold = np.get("singlehold", false); - bool isSingleRelease = np.get("singlerelease", false); - bool isScroll = np.get("scroll", false); - QString key = np.get("key", ""); - int specialKey = np.get("specialKey", 0); + bool isSingleClick = np.get(QStringLiteral("singleclick"), false); + bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); + bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); + bool isRightClick = np.get(QStringLiteral("rightclick"), false); + bool isSingleHold = np.get(QStringLiteral("singlehold"), false); + bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); + bool isScroll = np.get(QStringLiteral("scroll"), false); + QString key = np.get(QStringLiteral("key"), QLatin1String("")); + int specialKey = np.get(QStringLiteral("specialKey"), 0); if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { - Display *display = QX11Info::display(); + Display* display = QX11Info::display(); if(!display) { return false; } + bool leftHanded = isLeftHanded(display); + int mainMouseButton = leftHanded? RightMouseButton : LeftMouseButton; + int secondaryMouseButton = leftHanded? LeftMouseButton : RightMouseButton; + if (isSingleClick) { - XTestFakeButtonEvent(display, LeftMouseButton, True, 0); - XTestFakeButtonEvent(display, LeftMouseButton, False, 0); + XTestFakeButtonEvent(display, mainMouseButton, True, 0); + XTestFakeButtonEvent(display, mainMouseButton, False, 0); } else if (isDoubleClick) { - XTestFakeButtonEvent(display, LeftMouseButton, True, 0); - XTestFakeButtonEvent(display, LeftMouseButton, False, 0); - XTestFakeButtonEvent(display, LeftMouseButton, True, 0); - XTestFakeButtonEvent(display, LeftMouseButton, False, 0); + XTestFakeButtonEvent(display, mainMouseButton, True, 0); + XTestFakeButtonEvent(display, mainMouseButton, False, 0); + XTestFakeButtonEvent(display, mainMouseButton, True, 0); + XTestFakeButtonEvent(display, mainMouseButton, False, 0); } else if (isMiddleClick) { XTestFakeButtonEvent(display, MiddleMouseButton, True, 0); XTestFakeButtonEvent(display, MiddleMouseButton, False, 0); } else if (isRightClick) { - XTestFakeButtonEvent(display, RightMouseButton, True, 0); - XTestFakeButtonEvent(display, RightMouseButton, False, 0); + XTestFakeButtonEvent(display, secondaryMouseButton, True, 0); + XTestFakeButtonEvent(display, secondaryMouseButton, False, 0); } else if (isSingleHold){ //For drag'n drop - XTestFakeButtonEvent(display, LeftMouseButton, True, 0); + XTestFakeButtonEvent(display, mainMouseButton, True, 0); } else if (isSingleRelease){ //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. - XTestFakeButtonEvent(display, LeftMouseButton, False, 0); + XTestFakeButtonEvent(display, mainMouseButton, False, 0); } else if (isScroll) { if (dy < 0) { XTestFakeButtonEvent(display, MouseWheelDown, True, 0); XTestFakeButtonEvent(display, MouseWheelDown, False, 0); } else if (dy > 0) { XTestFakeButtonEvent(display, MouseWheelUp, True, 0); XTestFakeButtonEvent(display, MouseWheelUp, False, 0); } } else if (!key.isEmpty() || specialKey) { - bool ctrl = np.get("ctrl", false); - bool alt = np.get("alt", false); - bool shift = np.get("shift", false); + bool ctrl = np.get(QStringLiteral("ctrl"), false); + bool alt = np.get(QStringLiteral("alt"), false); + bool shift = np.get(QStringLiteral("shift"), false); if (ctrl) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Control_L), True, 0); if (alt) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Alt_L), True, 0); if (shift) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Shift_L), True, 0); if (specialKey) { if (specialKey >= (int)arraySize(SpecialKeysMap)) { qWarning() << "Unsupported special key identifier"; return false; } int keycode = XKeysymToKeycode(display, SpecialKeysMap[specialKey]); XTestFakeKeyEvent (display, keycode, True, 0); XTestFakeKeyEvent (display, keycode, False, 0); } else { if (!m_fakekey) { m_fakekey = fakekey_init(display); if (!m_fakekey) { qWarning() << "Failed to initialize libfakekey"; return false; } } //We use fakekey here instead of XTest (above) because it can handle utf characters instead of keycodes. - fakekey_press(m_fakekey, (const unsigned char*)key.toUtf8().constData(), -1, 0); - fakekey_release(m_fakekey); + for (int i=0;icreate(connection); connect(registry, &Registry::fakeInputAnnounced, this, [this, registry] (quint32 name, quint32 version) { m_waylandInput = registry->createFakeInput(name, version, this); } ); registry->setup(); } -bool MousepadPlugin::handPackageWayland(const NetworkPackage &np) +bool MousepadPlugin::handPackageWayland(const NetworkPackage& np) { - const float dx = np.get("dx", 0); - const float dy = np.get("dy", 0); - - const bool isSingleClick = np.get("singleclick", false); - const bool isDoubleClick = np.get("doubleclick", false); - const bool isMiddleClick = np.get("middleclick", false); - const bool isRightClick = np.get("rightclick", false); - const bool isSingleHold = np.get("singlehold", false); - const bool isSingleRelease = np.get("singlerelease", false); - const bool isScroll = np.get("scroll", false); - const QString key = np.get("key", ""); - const int specialKey = np.get("specialKey", 0); + const float dx = np.get(QStringLiteral("dx"), 0); + const float dy = np.get(QStringLiteral("dy"), 0); + + const bool isSingleClick = np.get(QStringLiteral("singleclick"), false); + const bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); + const bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); + const bool isRightClick = np.get(QStringLiteral("rightclick"), false); + const bool isSingleHold = np.get(QStringLiteral("singlehold"), false); + const bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); + const bool isScroll = np.get(QStringLiteral("scroll"), false); + const QString key = np.get(QStringLiteral("key"), QLatin1String("")); + const int specialKey = np.get(QStringLiteral("specialKey"), 0); if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { if (isSingleClick) { m_waylandInput->requestPointerButtonClick(Qt::LeftButton); } else if (isDoubleClick) { m_waylandInput->requestPointerButtonClick(Qt::LeftButton); m_waylandInput->requestPointerButtonClick(Qt::LeftButton); } else if (isMiddleClick) { m_waylandInput->requestPointerButtonClick(Qt::MiddleButton); } else if (isRightClick) { m_waylandInput->requestPointerButtonClick(Qt::RightButton); } else if (isSingleHold){ //For drag'n drop m_waylandInput->requestPointerButtonPress(Qt::LeftButton); } else if (isSingleRelease){ //For drag'n drop. NEVER USED (release is done by tapping, which actually triggers a isSingleClick). Kept here for future-proofnes. m_waylandInput->requestPointerButtonRelease(Qt::LeftButton); } else if (isScroll) { m_waylandInput->requestPointerAxis(Qt::Vertical, dy); } else if (!key.isEmpty() || specialKey) { // TODO: implement key support } } else { //Is a mouse move event m_waylandInput->requestPointerMove(QSizeF(dx, dy)); } return true; } #endif #include "mousepadplugin.moc" diff --git a/plugins/mousepad/mousepadplugin.h b/plugins/mousepad/mousepadplugin.h index 0c499884..3bb45fd2 100644 --- a/plugins/mousepad/mousepadplugin.h +++ b/plugins/mousepad/mousepadplugin.h @@ -1,69 +1,71 @@ /** * Copyright 2014 Ahmed I. Khalil * * 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 MOUSEPADPLUGIN_H #define MOUSEPADPLUGIN_H #include #include #include -#define PACKAGE_TYPE_MOUSEPAD QLatin1String("kdeconnect.mousepad") - struct FakeKey; #if HAVE_WAYLAND namespace KWayland { namespace Client { class FakeInput; } } #endif class MousepadPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit MousepadPlugin(QObject *parent, const QVariantList &args); - virtual ~MousepadPlugin(); + explicit MousepadPlugin(QObject* parent, const QVariantList& args); + ~MousepadPlugin() override; - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } + bool receivePackage(const NetworkPackage& np) override; + void connected() override { } private: +#if HAVE_X11 bool handlePackageX11(const NetworkPackage& np); +#endif #if HAVE_WAYLAND void setupWaylandIntegration(); bool handPackageWayland(const NetworkPackage& np); #endif +#if HAVE_X11 FakeKey* m_fakekey; +#endif const bool m_x11; #if HAVE_WAYLAND - KWayland::Client::FakeInput *m_waylandInput; + KWayland::Client::FakeInput* m_waylandInput; bool m_waylandAuthenticationRequested; #endif }; #endif diff --git a/plugins/mousepad_windows/CMakeLists.txt b/plugins/mousepad_windows/CMakeLists.txt new file mode 100644 index 00000000..2fbe4db2 --- /dev/null +++ b/plugins/mousepad_windows/CMakeLists.txt @@ -0,0 +1,2 @@ +kdeconnect_add_plugin(kdeconnect_mousepad JSON kdeconnect_mousepad.json SOURCES mousepadplugin_windows.cpp) +target_link_libraries(kdeconnect_mousepad kdeconnectcore Qt5::Gui KF5::I18n) diff --git a/plugins/mousepad_windows/kdeconnect_mousepad.json b/plugins/mousepad_windows/kdeconnect_mousepad.json new file mode 100644 index 00000000..29ed04aa --- /dev/null +++ b/plugins/mousepad_windows/kdeconnect_mousepad.json @@ -0,0 +1,97 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Authors": [ + { + "Email": "ahmedibrahimkhali@gmail.com", + "Name": "Ahmed I. Khalil", + "Name[ar]": "أحمد إبراهيم خليل", + "Name[sr@ijekavian]": "Ахмед И. Калил", + "Name[sr@ijekavianlatin]": "Ahmed I. Kalil", + "Name[sr@latin]": "Ahmed I. Kalil", + "Name[sr]": "Ахмед И. Калил", + "Name[x-test]": "xxAhmed I. Khalilxx" + } + ], + "Description": "Use your phone as a touchpad and keyboard", + "Description[ar]": "استخدم الهاتف كفأرة ولوحة مفاتيح", + "Description[ca@valencia]": "Empra el telèfon com un ratolí tàctil i teclat", + "Description[ca]": "Empra el telèfon com un ratolí tàctil i teclat", + "Description[cs]": "Používejte svůj telefon jako touchpad a klávesnici", + "Description[da]": "Brug din telefon som touchpad og tastatur", + "Description[de]": "Verwendet Ihr Handy als Touchpad und Tastatur", + "Description[el]": "Χρήση του τηλεφώνου σας ως οθόνη αφής και πληκτρολογίου", + "Description[es]": "Usar teléfono como panel táctil y teclado", + "Description[et]": "Telefoni kasutamine puutepadja ja klaviatuurina", + "Description[eu]": "Erabili zure telefonoa touchpad eta teklatu gisa", + "Description[fi]": "Käytä puhelintasi kosketuslevynä ja näppäimistönä", + "Description[fr]": "Utilisez votre téléphone comme un pavé tactile et un clavier", + "Description[gl]": "Usar o teléfono móbil como área táctil e teclado.", + "Description[hu]": "A telefon használata érintőtáblaként és billentyűzetként", + "Description[it]": "Usa il telefono come touchpad e tastiera", + "Description[ko]": "휴대폰을 터치패드와 키보드로 사용", + "Description[nl]": "Uw telefoon gebruiken als een touchpad en toetsenbord", + "Description[nn]": "Bruk telefonen som styreplate og tastatur", + "Description[pl]": "Używaj swojego telefonu jako gładzika i klawiatury", + "Description[pt]": "Use o seu telefone como um rato e teclado por toque", + "Description[pt_BR]": "Use seu telefone como um touchpad e teclado", + "Description[ru]": "Использование телефона в качестве сенсорной панели и клавиатуры", + "Description[sk]": "Použite váš telefón ako touchpad a klávesnicu", + "Description[sr@ijekavian]": "Користите телефон као додирник и тастатуру", + "Description[sr@ijekavianlatin]": "Koristite telefon kao dodirnik i tastaturu", + "Description[sr@latin]": "Koristite telefon kao dodirnik i tastaturu", + "Description[sr]": "Користите телефон као додирник и тастатуру", + "Description[sv]": "Använd telefonen som mus och tangentbord", + "Description[tr]": "Telefonunuzu dokunmatik yüzey ve klayve olarak kullanın", + "Description[uk]": "Скористайтеся телефоном як замінником сенсорної панелі і клавіатури", + "Description[x-test]": "xxUse your phone as a touchpad and keyboardxx", + "Description[zh_CN]": "用您的手机作为触摸板和键盘", + "Description[zh_TW]": "讓您的智慧型手機當作觸碰板與鍵盤", + "EnabledByDefault": true, + "Icon": "edit-select", + "Id": "kdeconnect_mousepad", + "License": "GPL", + "Name": "Virtual input", + "Name[ar]": "دخل وهميّ", + "Name[ca@valencia]": "Entrada virtual", + "Name[ca]": "Entrada virtual", + "Name[cs]": "Virtuální vstup", + "Name[da]": "Virtuelt input", + "Name[de]": "Virtuelle Eingabe", + "Name[el]": "Εικονικά στοιχεία εισόδου", + "Name[es]": "Entrada virtual", + "Name[et]": "Virtuaalsisestus", + "Name[eu]": "Sarrera birtuala", + "Name[fi]": "Virtuaalinen syöttö", + "Name[fr]": "Entrée virtuelle", + "Name[gl]": "Entrada virtual", + "Name[hu]": "Virtuális bevitel", + "Name[it]": "Inserimento virtuale", + "Name[ko]": "가상 입력", + "Name[nl]": "Virtuele invoer", + "Name[nn]": "Virtuelt tastatur", + "Name[pl]": "Wirtualna obsługa", + "Name[pt]": "Entrada virtual", + "Name[pt_BR]": "Entrada virtual", + "Name[ru]": "Виртуальный ввод", + "Name[sk]": "Virtuálny vstup", + "Name[sr@ijekavian]": "Виртуелни унос", + "Name[sr@ijekavianlatin]": "Virtuelni unos", + "Name[sr@latin]": "Virtuelni unos", + "Name[sr]": "Виртуелни унос", + "Name[sv]": "Virtuell inmatning", + "Name[tr]": "Sanal içe aktarma", + "Name[uk]": "Віртуальне введення даних", + "Name[x-test]": "xxVirtual inputxx", + "Name[zh_CN]": "虚拟输入", + "Name[zh_TW]": "虛擬輸入", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1" + }, + "X-KdeConnect-OutgoingPackageType": [], + "X-KdeConnect-SupportedPackageType": [ + "kdeconnect.mousepad.request" + ] +} diff --git a/plugins/mousepad_windows/mousepadplugin_windows.cpp b/plugins/mousepad_windows/mousepadplugin_windows.cpp new file mode 100644 index 00000000..31cfc927 --- /dev/null +++ b/plugins/mousepad_windows/mousepadplugin_windows.cpp @@ -0,0 +1,150 @@ +/** + * Copyright 2014 Ahmed I. Khalil + * Copyright 2015 Martin Gräßlin + * + * 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 "mousepadplugin_windows.h" +#include +#include +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mousepad.json", registerPlugin< MousepadPlugin >(); ) + +MousepadPlugin::MousepadPlugin(QObject* parent, const QVariantList& args) + : KdeConnectPlugin(parent, args) +{ +} + +MousepadPlugin::~MousepadPlugin() +{ +} + +bool MousepadPlugin::receivePackage(const NetworkPackage& np) +{ + float dx = np.get(QStringLiteral("dx"), 0); + float dy = np.get(QStringLiteral("dy"), 0); + + bool isSingleClick = np.get(QStringLiteral("singleclick"), false); + bool isDoubleClick = np.get(QStringLiteral("doubleclick"), false); + bool isMiddleClick = np.get(QStringLiteral("middleclick"), false); + bool isRightClick = np.get(QStringLiteral("rightclick"), false); + bool isSingleHold = np.get(QStringLiteral("singlehold"), false); + bool isSingleRelease = np.get(QStringLiteral("singlerelease"), false); + bool isScroll = np.get(QStringLiteral("scroll"), false); + QString key = np.get(QStringLiteral("key"), QLatin1String("")); + int specialKey = np.get(QStringLiteral("specialKey"), 0); + + if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isScroll || !key.isEmpty() || specialKey) { + + INPUT input={0}; + input.type = INPUT_MOUSE; + + if (isSingleClick) { + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + ::SendInput(1,&input,sizeof(INPUT)); + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + ::SendInput(1,&input,sizeof(INPUT)); + } else if (isDoubleClick) { + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + ::SendInput(1,&input,sizeof(INPUT)); + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + ::SendInput(1,&input,sizeof(INPUT)); + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + ::SendInput(1,&input,sizeof(INPUT)); + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + ::SendInput(1,&input,sizeof(INPUT)); + } else if (isMiddleClick) { + input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; + ::SendInput(1,&input,sizeof(INPUT)); + input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; + ::SendInput(1,&input,sizeof(INPUT)); + } else if (isRightClick) { + input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; + ::SendInput(1,&input,sizeof(INPUT)); + input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; + ::SendInput(1,&input,sizeof(INPUT)); + } else if (isSingleHold){ + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + ::SendInput(1,&input,sizeof(INPUT)); + } else if (isSingleRelease){ + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + ::SendInput(1,&input,sizeof(INPUT)); + } else if (isScroll) { + input.mi.dwFlags = MOUSEEVENTF_WHEEL; + input.mi.mouseData = dy; + ::SendInput(1,&input,sizeof(INPUT)); +/* + } else if (!key.isEmpty() || specialKey) { + + bool ctrl = np.get(QStringLiteral("ctrl"), false); + bool alt = np.get(QStringLiteral("alt"), false); + bool shift = np.get(QStringLiteral("shift"), false); + + if (ctrl) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Control_L), True, 0); + if (alt) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Alt_L), True, 0); + if (shift) XTestFakeKeyEvent (display, XKeysymToKeycode(display, XK_Shift_L), True, 0); + + if (specialKey) + { + if (specialKey >= (int)arraySize(SpecialKeysMap)) { + qWarning() << "Unsupported special key identifier"; + return false; + } + + int keycode = XKeysymToKeycode(display, SpecialKeysMap[specialKey]); + + XTestFakeKeyEvent (display, keycode, True, 0); + XTestFakeKeyEvent (display, keycode, False, 0); + + } else { + + if (!m_fakekey) { + m_fakekey = fakekey_init(display); + if (!m_fakekey) { + qWarning() << "Failed to initialize libfakekey"; + return false; + } + } + + //We use fakekey here instead of XTest (above) because it can handle utf characters instead of keycodes. + for (int i=0;i + * Copyright 2014 Ahmed I. Khalil * * 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 SCREENSAVERINHIBITPLUGIN_H -#define SCREENSAVERINHIBITPLUGIN_H +#ifndef MOUSEPADPLUGIN_H +#define MOUSEPADPLUGIN_H #include +#include #include -class Q_DECL_EXPORT ScreensaverInhibitPlugin +class MousepadPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit ScreensaverInhibitPlugin(QObject *parent, const QVariantList &args); - virtual ~ScreensaverInhibitPlugin(); + explicit MousepadPlugin(QObject* parent, const QVariantList &args); + ~MousepadPlugin() override; -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); - -private: - uint inhibitCookie; + bool receivePackage(const NetworkPackage& np) override; + void connected() override { } }; #endif diff --git a/plugins/mpriscontrol/kdeconnect_mpriscontrol.json b/plugins/mpriscontrol/kdeconnect_mpriscontrol.json index 5c1fdae6..d846f0a0 100644 --- a/plugins/mpriscontrol/kdeconnect_mpriscontrol.json +++ b/plugins/mpriscontrol/kdeconnect_mpriscontrol.json @@ -1,66 +1,99 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Remote control your music and videos", - "Description[ca]": "Comandament a distància per a la vostra música i vídeos", - "Description[cs]": "Ovládejte vzdáleně vaši hudbu a videa", - "Description[de]": "Fernbedienung für Musik und Videos", - "Description[en_GB]": "Remote control your music and videos", - "Description[es]": "Controlar remotamente vídeos y música", - "Description[fi]": "Kaukosäädin musiikkiisi ja videoihisi", - "Description[gl]": "Use o teléfono móbil como mando a distancia para música e vídeos.", - "Description[hu]": "A zene- és videolejátszás irányítása távolról", - "Description[it]": "Controlla da remoto la tua musica e i video", - "Description[nl]": "Op afstand bedienen van uw muziek en video's", - "Description[pl]": "Steruj zdalnie swoją muzyką i filmami", - "Description[pt]": "Comandar à distância a sua música e vídeos", - "Description[pt_BR]": "Controle suas músicas e vídeos remotamente", - "Description[sk]": "Vzdialene ovládať vašu hudbu a videá", - "Description[sv]": "Fjärrstyr musik och videor", - "Description[tr]": "Müzik ve videonuzu uzaktan kontrol edin", - "Description[uk]": "Віддалене керування відтворенням музики та відео", - "Description[x-test]": "xxRemote control your music and videosxx", - "Description[zh_CN]": "远程控制您的音乐和视频", - "EnabledByDefault": true, - "Icon": "media-playback-start", - "Id": "kdeconnect_mpriscontrol", - "License": "GPL", - "Name": "Multimedia control receiver", - "Name[ca]": "Receptor del comandament multimèdia", - "Name[cs]": "Dálkový ovladač multimédií", - "Name[de]": "Steuerung für Multimedia-Empfänger", - "Name[en_GB]": "Multimedia control receiver", - "Name[es]": "Receptor de control multimedia", - "Name[fi]": "Multimediakaukosäädin", - "Name[gl]": "Receptor de controis multimedia", - "Name[hu]": "Multimédiavezérlő", - "Name[it]": "Ricevitore di controllo multimediale", - "Name[nl]": "Ontvanger van bediening voor multimedia", - "Name[pl]": "Odbiornik sterowania multimediami", - "Name[pt]": "Receptor de controlo multimédia", - "Name[pt_BR]": "Receptor de controle multimídia", - "Name[sk]": "Prijímač ovládania multimédií", - "Name[sv]": "Mottagare av multimediastyrning", - "Name[tr]": "Çokluortam kontrol alıcısı", - "Name[uk]": "Отримувач команд щодо керування мультимедійними функціями", - "Name[x-test]": "xxMultimedia control receiverxx", - "Name[zh_CN]": "多媒体控制接收器", + ], + "Description": "Remote control your music and videos", + "Description[ar]": "تحكّم بالموسيقى والفيديو عن بعد", + "Description[ca@valencia]": "Comandament a distància per a la vostra música i vídeos", + "Description[ca]": "Comandament a distància per a la vostra música i vídeos", + "Description[cs]": "Ovládejte vzdáleně vaši hudbu a videa", + "Description[da]": "Fjernbetjen din musik og videoer", + "Description[de]": "Fernbedienung für Musik und Videos", + "Description[el]": "Απομακρυσμένος έλεγχος της μουσικής σας και των βίντεο", + "Description[es]": "Controlar remotamente vídeos y música", + "Description[et]": "Oma muusika ja videote kaugjuhtimine", + "Description[eu]": "Kontrolatu urrunetik zure musika eta bideoak", + "Description[fi]": "Kauko-ohjain musiikkiisi ja videoihisi", + "Description[fr]": "Contrôlez à distance votre musique et vos vidéos", + "Description[gl]": "Use o teléfono móbil como mando a distancia para música e vídeos.", + "Description[hu]": "A zene- és videolejátszás irányítása távolról", + "Description[it]": "Controlla da remoto la tua musica e i video", + "Description[ko]": "음악과 동영상 원격 제어", + "Description[nl]": "Op afstand bedienen van uw muziek en video's", + "Description[nn]": "Fjernkontroller musikk og video", + "Description[pl]": "Steruj zdalnie swoją muzyką i filmami", + "Description[pt]": "Comandar à distância a sua música e vídeos", + "Description[pt_BR]": "Controle suas músicas e vídeos remotamente", + "Description[ru]": "Удалённое управление воспроизведением музыки и видео", + "Description[sk]": "Vzdialene ovládať vašu hudbu a videá", + "Description[sr@ijekavian]": "Даљински контролишите вашу музику и видео", + "Description[sr@ijekavianlatin]": "Daljinski kontrolišite vašu muziku i video", + "Description[sr@latin]": "Daljinski kontrolišite vašu muziku i video", + "Description[sr]": "Даљински контролишите вашу музику и видео", + "Description[sv]": "Fjärrstyr musik och videor", + "Description[tr]": "Müzik ve videonuzu uzaktan kontrol edin", + "Description[uk]": "Віддалене керування відтворенням музики та відео", + "Description[x-test]": "xxRemote control your music and videosxx", + "Description[zh_CN]": "远程控制您的音乐和视频", + "Description[zh_TW]": "遠端控制您的音樂與影片", + "EnabledByDefault": true, + "Icon": "media-playback-start", + "Id": "kdeconnect_mpriscontrol", + "License": "GPL", + "Name": "Multimedia control receiver", + "Name[ar]": "مستقبل تحكّمات الوسائط المتعدّدة", + "Name[ca@valencia]": "Receptor del comandament multimèdia", + "Name[ca]": "Receptor del comandament multimèdia", + "Name[cs]": "Dálkový ovladač multimédií", + "Name[da]": "Modtager til multimediekontrol", + "Name[de]": "Steuerung für Multimedia-Empfänger", + "Name[el]": "Δέκτης ελέγχου πολυμέσων", + "Name[es]": "Receptor de control multimedia", + "Name[et]": "Multimeedia juhtimine", + "Name[eu]": "Multimedia-kontroladore hartzailea", + "Name[fi]": "Multimediakauko-ohjain", + "Name[fr]": "Receveur de contrôle multimédia", + "Name[gl]": "Receptor de controis multimedia", + "Name[hu]": "Multimédiavezérlő", + "Name[it]": "Ricevitore di controllo multimediale", + "Name[ko]": "멀티미디어 제어 수신기", + "Name[nl]": "Ontvanger van bediening voor multimedia", + "Name[nn]": "Mediefjernkontroll", + "Name[pl]": "Odbiornik sterowania multimediami", + "Name[pt]": "Receptor de controlo multimédia", + "Name[pt_BR]": "Receptor de controle multimídia", + "Name[ru]": "Управление мультимедиа", + "Name[sk]": "Prijímač ovládania multimédií", + "Name[sr@ijekavian]": "Мултимедијалне контроле", + "Name[sr@ijekavianlatin]": "Multimedijalne kontrole", + "Name[sr@latin]": "Multimedijalne kontrole", + "Name[sr]": "Мултимедијалне контроле", + "Name[sv]": "Mottagare av multimediastyrning", + "Name[tr]": "Çokluortam kontrol alıcısı", + "Name[uk]": "Отримувач команд щодо керування мультимедійними функціями", + "Name[x-test]": "xxMultimedia control receiverxx", + "Name[zh_CN]": "多媒体控制接收器", + "Name[zh_TW]": "多媒體控制接收器", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ "kdeconnect.mpris" - ], + ], "X-KdeConnect-SupportedPackageType": [ - "kdeconnect.mpris" + "kdeconnect.mpris.request" ] -} \ No newline at end of file +} diff --git a/plugins/mpriscontrol/mpriscontrolplugin.cpp b/plugins/mpriscontrol/mpriscontrolplugin.cpp index 8318217c..87a82fbf 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin.cpp +++ b/plugins/mpriscontrol/mpriscontrolplugin.cpp @@ -1,236 +1,292 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mpriscontrolplugin.h" #include #include #include #include #include #include #include #include #include #include "mprisdbusinterface.h" #include "propertiesdbusinterface.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mpriscontrol.json", registerPlugin< MprisControlPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRIS, "kdeconnect.plugin.mpris") MprisControlPlugin::MprisControlPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , prevVolume(-1) { - m_watcher = new QDBusServiceWatcher("org.mpris.MediaPlayer2", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); + m_watcher = new QDBusServiceWatcher(QString(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); - connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, &MprisControlPlugin::addPlayer); - connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &MprisControlPlugin::removePlayer); + // TODO: QDBusConnectionInterface::serviceOwnerChanged is deprecated, maybe query org.freedesktop.DBus directly? + connect(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &MprisControlPlugin::serviceOwnerChanged); //Add existing interfaces - QStringList interfaces = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); - Q_FOREACH (const QString& iface, interfaces) { - if (iface.startsWith("org.mpris.MediaPlayer2")) { - addPlayer(iface); - } + const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); + for (const QString& service : services) { + // The string doesn't matter, it just needs to be empty/non-empty + serviceOwnerChanged(service, QLatin1String(""), QStringLiteral("1")); + } +} + +// Copied from the mpris2 dataengine in the plasma-workspace repository +void MprisControlPlugin::serviceOwnerChanged(const QString& serviceName, const QString& oldOwner, const QString& newOwner) +{ + if (!serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) + return; + + if (!oldOwner.isEmpty()) { + qCDebug(KDECONNECT_PLUGIN_MPRIS) << "MPRIS service" << serviceName << "just went offline"; + removePlayer(serviceName); } + if (!newOwner.isEmpty()) { + qCDebug(KDECONNECT_PLUGIN_MPRIS) << "MPRIS service" << serviceName << "just came online"; + addPlayer(serviceName); + } } + void MprisControlPlugin::addPlayer(const QString& service) { - QDBusInterface mprisInterface(service, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2"); + QDBusInterface mprisInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2")); //FIXME: This call hangs and returns an empty string if KDED is still starting! - const QString identity = mprisInterface.property("Identity").toString(); - playerList[identity] = service; - qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris addPlayer" << service << "->" << identity; + QString identity = mprisInterface.property("Identity").toString(); + if (identity.isEmpty()) { + identity = service.mid(sizeof("org.mpris.MediaPlayer2")); + } + + QString uniqueName = identity; + for (int i = 1 ; !playerList[uniqueName].isEmpty() ; i++) { + uniqueName = identity + " [" + i + "]"; + } + + playerList[uniqueName] = service; + qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris addPlayer" << service << "->" << uniqueName; sendPlayerList(); - OrgFreedesktopDBusPropertiesInterface* freedesktopInterface = new OrgFreedesktopDBusPropertiesInterface(service, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus(), this); - connect(freedesktopInterface, SIGNAL(PropertiesChanged(QString, QVariantMap, QStringList)), this, SLOT(propertiesChanged(QString, QVariantMap))); + OrgFreedesktopDBusPropertiesInterface* freedesktopInterface = new OrgFreedesktopDBusPropertiesInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this); + connect(freedesktopInterface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &MprisControlPlugin::propertiesChanged); - OrgMprisMediaPlayer2PlayerInterface* mprisInterface0 = new OrgMprisMediaPlayer2PlayerInterface(service, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus()); - connect(mprisInterface0, SIGNAL(Seeked(qlonglong)), this, SLOT(seeked(qlonglong))); + OrgMprisMediaPlayer2PlayerInterface* mprisInterface0 = new OrgMprisMediaPlayer2PlayerInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus()); + connect(mprisInterface0, &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &MprisControlPlugin::seeked); } void MprisControlPlugin::seeked(qlonglong position){ //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeked in player"; - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("pos", position/1000); //Send milis instead of nanos OrgFreedesktopDBusPropertiesInterface* interface = (OrgFreedesktopDBusPropertiesInterface*)sender(); const QString& service = interface->service(); const QString& player = playerList.key(service); - np.set("player", player); + + NetworkPackage np(PACKAGE_TYPE_MPRIS, { + {"pos", position/1000}, //Send milis instead of nanos + {"player", player} + }); sendPackage(np); } void MprisControlPlugin::propertiesChanged(const QString& propertyInterface, const QVariantMap& properties) { Q_UNUSED(propertyInterface); NetworkPackage np(PACKAGE_TYPE_MPRIS); bool somethingToSend = false; - if (properties.contains("Volume")) { - int volume = (int) (properties["Volume"].toDouble()*100); + if (properties.contains(QStringLiteral("Volume"))) { + int volume = (int) (properties[QStringLiteral("Volume")].toDouble()*100); if (volume != prevVolume) { - np.set("volume",volume); + np.set(QStringLiteral("volume"),volume); prevVolume = volume; somethingToSend = true; } } - if (properties.contains("Metadata")) { - QDBusArgument bullshit = qvariant_cast(properties["Metadata"]); + if (properties.contains(QStringLiteral("Metadata"))) { + QDBusArgument bullshit = qvariant_cast(properties[QStringLiteral("Metadata")]); QVariantMap nowPlayingMap; bullshit >> nowPlayingMap; - if (nowPlayingMap.contains("xesam:title")) { - QString nowPlaying = nowPlayingMap["xesam:title"].toString(); - if (nowPlayingMap.contains("xesam:artist")) { - nowPlaying = nowPlayingMap["xesam:artist"].toString() + " - " + nowPlaying; - } - np.set("nowPlaying",nowPlaying); - somethingToSend = true; - } - if (nowPlayingMap.contains("mpris:length")) { - if (nowPlayingMap.contains("mpris:length")) { - long long length = nowPlayingMap["mpris:length"].toLongLong(); - np.set("length",length/1000); //milis to nanos - } - somethingToSend = true; - } + mprisPlayerMetadataToNetworkPackage(np, nowPlayingMap); + somethingToSend = true; } - if (properties.contains("PlaybackStatus")) { - bool playing = (properties["PlaybackStatus"].toString() == "Playing"); - np.set("isPlaying", playing); + if (properties.contains(QStringLiteral("PlaybackStatus"))) { + bool playing = (properties[QStringLiteral("PlaybackStatus")].toString() == QLatin1String("Playing")); + np.set(QStringLiteral("isPlaying"), playing); + somethingToSend = true; + } + if (properties.contains(QStringLiteral("CanPause"))) { + np.set(QStringLiteral("canPause"), properties[QStringLiteral("CanPause")].toBool()); + somethingToSend = true; + } + if (properties.contains(QStringLiteral("CanPlay"))) { + np.set(QStringLiteral("canPlay"), properties[QStringLiteral("CanPlay")].toBool()); + somethingToSend = true; + } + if (properties.contains(QStringLiteral("CanGoNext"))) { + np.set(QStringLiteral("canGoNext"), properties[QStringLiteral("CanGoNext")].toBool()); + somethingToSend = true; + } + if (properties.contains(QStringLiteral("CanGoPrevious"))) { + np.set(QStringLiteral("canGoPrevious"), properties[QStringLiteral("CanGoPrevious")].toBool()); + somethingToSend = true; + } + if (properties.contains(QStringLiteral("CanSeek"))) { + np.set(QStringLiteral("canSeek"), properties[QStringLiteral("CanSeek")].toBool()); somethingToSend = true; } if (somethingToSend) { OrgFreedesktopDBusPropertiesInterface* interface = (OrgFreedesktopDBusPropertiesInterface*)sender(); const QString& service = interface->service(); const QString& player = playerList.key(service); - np.set("player", player); + np.set(QStringLiteral("player"), player); // Always also update the position - OrgMprisMediaPlayer2PlayerInterface mprisInterface(service, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus()); + OrgMprisMediaPlayer2PlayerInterface mprisInterface(service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus()); if (mprisInterface.canSeek()) { long long pos = mprisInterface.position(); - np.set("pos", pos/1000); //Send milis instead of nanos + np.set(QStringLiteral("pos"), pos/1000); //Send milis instead of nanos } sendPackage(np); } } void MprisControlPlugin::removePlayer(const QString& ifaceName) { const QString identity = playerList.key(ifaceName); qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Mpris removePlayer" << ifaceName << "->" << identity; playerList.remove(identity); sendPlayerList(); } bool MprisControlPlugin::receivePackage (const NetworkPackage& np) { - if (np.has("playerList")) { + if (np.has(QStringLiteral("playerList"))) { return false; //Whoever sent this is an mpris client and not an mpris control! } //Send the player list - const QString player = np.get("player"); + const QString player = np.get(QStringLiteral("player")); bool valid_player = playerList.contains(player); - if (!valid_player || np.get("requestPlayerList")) { + if (!valid_player || np.get(QStringLiteral("requestPlayerList"))) { sendPlayerList(); if (!valid_player) { return true; } } //Do something to the mpris interface - OrgMprisMediaPlayer2PlayerInterface mprisInterface(playerList[player], "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus()); - if (np.has("action")) { - const QString& action = np.get("action"); + OrgMprisMediaPlayer2PlayerInterface mprisInterface(playerList[player], QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus()); + mprisInterface.setTimeout(500); + if (np.has(QStringLiteral("action"))) { + const QString& action = np.get(QStringLiteral("action")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Calling action" << action << "in" << playerList[player]; //TODO: Check for valid actions, currently we trust anything the other end sends us mprisInterface.call(action); } - if (np.has("setVolume")) { - double volume = np.get("setVolume")/100.f; + if (np.has(QStringLiteral("setVolume"))) { + double volume = np.get(QStringLiteral("setVolume"))/100.f; qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Setting volume" << volume << "to" << playerList[player]; mprisInterface.setVolume(volume); } - if (np.has("Seek")) { - int offset = np.get("Seek"); + if (np.has(QStringLiteral("Seek"))) { + int offset = np.get(QStringLiteral("Seek")); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Seeking" << offset << "to" << playerList[player]; mprisInterface.Seek(offset); } - if (np.has("SetPosition")){ - qlonglong position = np.get("SetPosition",0)*1000; + if (np.has(QStringLiteral("SetPosition"))){ + qlonglong position = np.get(QStringLiteral("SetPosition"),0)*1000; qlonglong seek = position - mprisInterface.position(); //qCDebug(KDECONNECT_PLUGIN_MPRIS) << "Setting position by seeking" << seek << "to" << playerList[player]; mprisInterface.Seek(seek); } //Send something read from the mpris interface NetworkPackage answer(PACKAGE_TYPE_MPRIS); bool somethingToSend = false; - if (np.get("requestNowPlaying")) { - + if (np.get(QStringLiteral("requestNowPlaying"))) { QVariantMap nowPlayingMap = mprisInterface.metadata(); - QString nowPlaying = nowPlayingMap["xesam:title"].toString(); - if (nowPlayingMap.contains("xesam:artist")) { - nowPlaying = nowPlayingMap["xesam:artist"].toString() + " - " + nowPlaying; - }if (nowPlayingMap.contains("mpris:length")) { - qlonglong length = nowPlayingMap["mpris:length"].toLongLong(); - answer.set("length",length/1000); - } - qlonglong pos = mprisInterface.position(); - answer.set("pos", pos/1000); + mprisPlayerMetadataToNetworkPackage(answer, nowPlayingMap); - answer.set("nowPlaying",nowPlaying); + qlonglong pos = mprisInterface.position(); + answer.set(QStringLiteral("pos"), pos/1000); bool playing = (mprisInterface.playbackStatus() == QLatin1String("Playing")); - answer.set("isPlaying", playing); + answer.set(QStringLiteral("isPlaying"), playing); + + answer.set(QStringLiteral("canPause"), mprisInterface.canPause()); + answer.set(QStringLiteral("canPlay"), mprisInterface.canPlay()); + answer.set(QStringLiteral("canGoNext"), mprisInterface.canGoNext()); + answer.set(QStringLiteral("canGoPrevious"), mprisInterface.canGoPrevious()); + answer.set(QStringLiteral("canSeek"), mprisInterface.canSeek()); somethingToSend = true; } - if (np.get("requestVolume")) { + if (np.get(QStringLiteral("requestVolume"))) { int volume = (int)(mprisInterface.volume() * 100); - answer.set("volume",volume); + answer.set(QStringLiteral("volume"),volume); somethingToSend = true; } if (somethingToSend) { - answer.set("player", player); + answer.set(QStringLiteral("player"), player); sendPackage(answer); } return true; } void MprisControlPlugin::sendPlayerList() { NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("playerList",playerList.keys()); + np.set(QStringLiteral("playerList"),playerList.keys()); sendPackage(np); } +void MprisControlPlugin::mprisPlayerMetadataToNetworkPackage(NetworkPackage& np, const QVariantMap& nowPlayingMap) const { + QString title = nowPlayingMap[QStringLiteral("xesam:title")].toString(); + QString artist = nowPlayingMap[QStringLiteral("xesam:artist")].toString(); + QString album = nowPlayingMap[QStringLiteral("xesam:album")].toString(); + QString albumArtUrl = nowPlayingMap[QStringLiteral("mpris:artUrl")].toString(); + QString nowPlaying = title; + if (!artist.isEmpty()) { + nowPlaying = artist + " - " + title; + } + np.set(QStringLiteral("title"), title); + np.set(QStringLiteral("artist"), artist); + np.set(QStringLiteral("album"), album); + np.set(QStringLiteral("albumArtUrl"), albumArtUrl); + np.set(QStringLiteral("nowPlaying"), nowPlaying); + + bool hasLength = false; + long long length = nowPlayingMap[QStringLiteral("mpris:length")].toLongLong(&hasLength) / 1000; //nanoseconds to milliseconds + if (!hasLength) { + length = -1; + } + np.set(QStringLiteral("length"), length); +} + #include "mpriscontrolplugin.moc" diff --git a/plugins/mpriscontrol/mpriscontrolplugin.h b/plugins/mpriscontrol/mpriscontrolplugin.h index 60bcd1a7..5036b085 100644 --- a/plugins/mpriscontrol/mpriscontrolplugin.h +++ b/plugins/mpriscontrol/mpriscontrolplugin.h @@ -1,62 +1,63 @@ /** * 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 MPRISCONTROLPLUGIN_H #define MPRISCONTROLPLUGIN_H #include #include #include #include #include -#define PACKAGE_TYPE_MPRIS QLatin1String("kdeconnect.mpris") +#define PACKAGE_TYPE_MPRIS QStringLiteral("kdeconnect.mpris") Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRIS) class MprisControlPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit MprisControlPlugin(QObject *parent, const QVariantList &args); + explicit MprisControlPlugin(QObject* parent, const QVariantList& args); -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } + bool receivePackage(const NetworkPackage& np) override; + void connected() override { } private Q_SLOTS: void propertiesChanged(const QString& propertyInterface, const QVariantMap& properties); void seeked(qlonglong); private: + void serviceOwnerChanged(const QString& serviceName, const QString& oldOwner, const QString& newOwner); void addPlayer(const QString& ifaceName); void removePlayer(const QString& ifaceName); void sendPlayerList(); + void mprisPlayerMetadataToNetworkPackage(NetworkPackage& np, const QVariantMap& nowPlayingMap) const; QHash playerList; int prevVolume; QDBusServiceWatcher* m_watcher; }; #endif diff --git a/plugins/mpriscontrol/mprisdbusinterface.xml b/plugins/mpriscontrol/mprisdbusinterface.xml index 761841be..3434febb 100644 --- a/plugins/mpriscontrol/mprisdbusinterface.xml +++ b/plugins/mpriscontrol/mprisdbusinterface.xml @@ -1,46 +1,47 @@ + diff --git a/plugins/mpriscontrol/propertiesInterface.xml b/plugins/mpriscontrol/propertiesInterface.xml index 516177f4..df41a686 100644 --- a/plugins/mpriscontrol/propertiesInterface.xml +++ b/plugins/mpriscontrol/propertiesInterface.xml @@ -1,28 +1,30 @@ - - + - - - + + + + + diff --git a/plugins/mprisremote/kdeconnect_mprisremote.json b/plugins/mprisremote/kdeconnect_mprisremote.json index 8904c147..b9bf635c 100644 --- a/plugins/mprisremote/kdeconnect_mprisremote.json +++ b/plugins/mprisremote/kdeconnect_mprisremote.json @@ -1,60 +1,79 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "aleixpol@kde.org", - "Name": "Aleix Pol" + "Email": "aleixpol@kde.org", + "Name": "Aleix Pol", + "Name[sr@ijekavian]": "Алекс Пол Гонзалез", + "Name[sr@ijekavianlatin]": "Aleks Pol Gonzalez", + "Name[sr@latin]": "Aleks Pol Gonzalez", + "Name[sr]": "Алекс Пол Гонзалез", + "Name[x-test]": "xxAleix Polxx" } - ], - "Description": "Control MPRIS services", - "Description[ca]": "Serveis de control del MPRIS", - "Description[cs]": "Ovládejte služby MPRIS", - "Description[de]": "Steuerung von MPRIS-Diensten", - "Description[es]": "Controlar los servicios MPRIS", - "Description[fi]": "Ohjaa MPRIS-palveluita", - "Description[gl]": "Controle servizos de MPRIS.", - "Description[it]": "Servizi di controllo MPRIS", - "Description[nl]": "MPRIS-services besturen", - "Description[pl]": "Sterowanie usługami MPRIS", - "Description[pt]": "Controlar os serviços MPRIS", - "Description[pt_BR]": "Controle dos serviços MPRIS", - "Description[sk]": "Ovládať MPRIS služby", - "Description[sv]": "Styr MPRIS-tjänster", - "Description[tr]": "MPRIS servislerini kontrol et", - "Description[uk]": "Керування службами MPRIS", - "Description[x-test]": "xxControl MPRIS servicesxx", - "EnabledByDefault": true, - "Icon": "applications-multimedia", - "Id": "kdeconnect_mprisremote", - "License": "GPL", - "Name": "MprisRemote", - "Name[ca]": "MprisRemote", - "Name[cs]": "MprisRemote", - "Name[de]": "MprisRemote", - "Name[es]": "MprisRemote", - "Name[fi]": "MPRIS-kaukosäädin", - "Name[gl]": "MprisRemote", - "Name[it]": "MprisRemote", - "Name[nl]": "MprisRemote", - "Name[pl]": "PilotMPRIS", - "Name[pt]": "Comando MPRIS", - "Name[pt_BR]": "MprisRemote", - "Name[sk]": "MprisRemote", - "Name[sv]": "MPRIS-fjärrkontroll", - "Name[tr]": "MprisRemote", - "Name[uk]": "MprisRemote", - "Name[x-test]": "xxMprisRemotexx", + ], + "Description": "Control MPRIS services", + "Description[ar]": "تحكّم بخدمات MPRIS", + "Description[ca@valencia]": "Serveis de control del MPRIS", + "Description[ca]": "Serveis de control del MPRIS", + "Description[cs]": "Ovládejte služby MPRIS", + "Description[da]": "Kontrollér MPRIS-tjenester", + "Description[de]": "Steuerung von MPRIS-Diensten", + "Description[el]": "Έλεγχος υπηρεσιών MPRIS", + "Description[es]": "Controlar los servicios MPRIS", + "Description[et]": "MPRIS-teenuste juhtimine", + "Description[eu]": "Kontrolatu MPRIS zerbitzuak", + "Description[fi]": "Ohjaa MPRIS-palveluita", + "Description[fr]": "Contrôlez les services MPRIS", + "Description[gl]": "Controle servizos de MPRIS.", + "Description[it]": "Servizi di controllo MPRIS", + "Description[ko]": "MPRIS 서비스 제어", + "Description[nl]": "MPRIS-services besturen", + "Description[nn]": "Kontroller MPRIS-tenester", + "Description[pl]": "Sterowanie usługami MPRIS", + "Description[pt]": "Controlar os serviços MPRIS", + "Description[pt_BR]": "Controle dos serviços MPRIS", + "Description[ru]": "Управление службами MPRIS", + "Description[sk]": "Ovládať MPRIS služby", + "Description[sr@ijekavian]": "Контрола МПРИС сервиса", + "Description[sr@ijekavianlatin]": "Kontrola MPRIS servisa", + "Description[sr@latin]": "Kontrola MPRIS servisa", + "Description[sr]": "Контрола МПРИС сервиса", + "Description[sv]": "Styr MPRIS-tjänster", + "Description[tr]": "MPRIS servislerini kontrol et", + "Description[uk]": "Керування службами MPRIS", + "Description[x-test]": "xxControl MPRIS servicesxx", + "Description[zh_CN]": "控制 MPRIS 服务", + "Description[zh_TW]": "控制 MPRIS 服務", + "EnabledByDefault": true, + "Icon": "applications-multimedia", + "Id": "kdeconnect_mprisremote", + "License": "GPL", + "Name": "MprisRemote", + "Name[ar]": "‏Mpris عن بعد", + "Name[et]": "Kaug-Mpris", + "Name[fi]": "MPRIS-kauko-ohjain", + "Name[nn]": "MPRIS-fjernkontroll", + "Name[pl]": "PilotMPRIS", + "Name[pt]": "Comando MPRIS", + "Name[ru]": "Удалённое управление MPRIS", + "Name[sr@ijekavian]": "МПРИС даљински", + "Name[sr@ijekavianlatin]": "MPRIS daljinski", + "Name[sr@latin]": "MPRIS daljinski", + "Name[sr]": "МПРИС даљински", + "Name[sv]": "MPRIS-fjärrkontroll", + "Name[x-test]": "xxMprisRemotexx", + "Name[zh_CN]": "远程 Mpris", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "https://kde.org" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.mpris" - ], + "kdeconnect.mpris.request" + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.mpris" ] -} \ No newline at end of file +} diff --git a/plugins/mprisremote/mprisremoteplugin.cpp b/plugins/mprisremote/mprisremoteplugin.cpp index 4f2e3400..a26a0c68 100644 --- a/plugins/mprisremote/mprisremoteplugin.cpp +++ b/plugins/mprisremote/mprisremoteplugin.cpp @@ -1,157 +1,155 @@ /** * 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 "mprisremoteplugin.h" #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_mprisremote.json", registerPlugin< MprisRemotePlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_MPRISREMOTE, "kdeconnect.plugin.mprisremote") MprisRemotePlugin::MprisRemotePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_player() , m_playing(false) , m_nowPlaying() , m_volume(50) , m_length(0) , m_lastPosition(0) , m_lastPositionTime() , m_playerList() { } MprisRemotePlugin::~MprisRemotePlugin() { } bool MprisRemotePlugin::receivePackage(const NetworkPackage& np) { if (np.type() != PACKAGE_TYPE_MPRIS) return false; - if (np.has("nowPlaying") || np.has("volume") || np.has("isPlaying") || np.has("length") || np.has("pos")) { - if (np.get("player") == m_player) { - m_nowPlaying = np.get("nowPlaying", m_nowPlaying); - m_volume = np.get("volume", m_volume); - m_length = np.get("length", m_length); - if(np.has("pos")){ - m_lastPosition = np.get("pos", m_lastPosition); + if (np.has(QStringLiteral("nowPlaying")) || np.has(QStringLiteral("volume")) || np.has(QStringLiteral("isPlaying")) || np.has(QStringLiteral("length")) || np.has(QStringLiteral("pos"))) { + if (np.get(QStringLiteral("player")) == m_player) { + m_nowPlaying = np.get(QStringLiteral("nowPlaying"), m_nowPlaying); + m_volume = np.get(QStringLiteral("volume"), m_volume); + m_length = np.get(QStringLiteral("length"), m_length); + if(np.has(QStringLiteral("pos"))){ + m_lastPosition = np.get(QStringLiteral("pos"), m_lastPosition); m_lastPositionTime = QDateTime::currentMSecsSinceEpoch(); } - m_playing = np.get("isPlaying", m_playing); + m_playing = np.get(QStringLiteral("isPlaying"), m_playing); } } - if (np.has("playerList")) { - m_playerList = np.get("playerList", QStringList()); + if (np.has(QStringLiteral("playerList"))) { + m_playerList = np.get(QStringLiteral("playerList"), QStringList()); } Q_EMIT propertiesChanged(); return true; } long MprisRemotePlugin::position() const { if(m_playing) { return m_lastPosition + (QDateTime::currentMSecsSinceEpoch() - m_lastPositionTime); } else { return m_lastPosition; } } -void MprisRemotePlugin::connected() -{ - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); -} - QString MprisRemotePlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/mprisremote"; } void MprisRemotePlugin::requestPlayerStatus() { - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("player",m_player); - np.set("requestNowPlaying",true); - np.set("requestVolume",true); + NetworkPackage np(PACKAGE_TYPE_MPRIS_REQUEST, { + {"player", m_player}, + {"requestNowPlaying", true}, + {"requestVolume", true}} + ); sendPackage(np); } void MprisRemotePlugin::requestPlayerList() { - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("requestPlayerList", true); + NetworkPackage np(PACKAGE_TYPE_MPRIS_REQUEST, {{"requestPlayerList", true}}); sendPackage(np); } void MprisRemotePlugin::sendAction(const QString& action) { - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("player", m_player); - np.set("action", action); + NetworkPackage np(PACKAGE_TYPE_MPRIS_REQUEST, { + {"player", m_player}, + {"action", action} + }); sendPackage(np); } void MprisRemotePlugin::seek(int offset) const { - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("player", m_player); - np.set("Seek", offset); + NetworkPackage np(PACKAGE_TYPE_MPRIS_REQUEST, { + {"player", m_player}, + {"Seek", offset}}); sendPackage(np); } void MprisRemotePlugin::setVolume(int volume) { - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("player", m_player); - np.set("setVolume",volume); + NetworkPackage np(PACKAGE_TYPE_MPRIS_REQUEST, { + {"player", m_player}, + {"setVolume",volume} + }); sendPackage(np); } void MprisRemotePlugin::setPosition(int position) { - NetworkPackage np(PACKAGE_TYPE_MPRIS); - np.set("player", m_player); - np.set("SetPosition", position); + NetworkPackage np(PACKAGE_TYPE_MPRIS_REQUEST, { + {"player", m_player}, + {"SetPosition", position} + }); sendPackage(np); m_lastPosition = position; m_lastPositionTime = QDateTime::currentMSecsSinceEpoch(); } void MprisRemotePlugin::setPlayer(const QString& player) { if (m_player != player) { m_player = player; requestPlayerStatus(); } } #include "mprisremoteplugin.moc" diff --git a/plugins/mprisremote/mprisremoteplugin.h b/plugins/mprisremote/mprisremoteplugin.h index 886e6c5a..e7e419d2 100644 --- a/plugins/mprisremote/mprisremoteplugin.h +++ b/plugins/mprisremote/mprisremoteplugin.h @@ -1,84 +1,84 @@ /** * 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 MPRISREMOTEPLUGIN_H #define MPRISREMOTEPLUGIN_H #include #include -#define PACKAGE_TYPE_MPRIS QLatin1String("kdeconnect.mpris") +#define PACKAGE_TYPE_MPRIS_REQUEST QStringLiteral("kdeconnect.mpris.request") +#define PACKAGE_TYPE_MPRIS QStringLiteral("kdeconnect.mpris") class Q_DECL_EXPORT MprisRemotePlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.mprisremote") Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY propertiesChanged) Q_PROPERTY(int length READ length NOTIFY propertiesChanged) Q_PROPERTY(bool isPlaying READ isPlaying NOTIFY propertiesChanged) Q_PROPERTY(int position READ position WRITE setPosition NOTIFY propertiesChanged) Q_PROPERTY(QStringList playerList READ playerList NOTIFY propertiesChanged) Q_PROPERTY(QString player READ player WRITE setPlayer) Q_PROPERTY(QString nowPlaying READ nowPlaying NOTIFY propertiesChanged) public: - explicit MprisRemotePlugin(QObject *parent, const QVariantList &args); - virtual ~MprisRemotePlugin(); + explicit MprisRemotePlugin(QObject* parent, const QVariantList &args); + ~MprisRemotePlugin() override; long position() const; int volume() const { return m_volume; } int length() const { return m_length; } bool isPlaying() const { return m_playing; } QStringList playerList() const { return m_playerList; } QString player() const { return m_player; } QString nowPlaying() const { return m_nowPlaying; } void setVolume(int volume); void setPosition(int position); void setPlayer(const QString& player); -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override {} + QString dbusPath() const override; - void seek(int offset) const; - void requestPlayerStatus(); - void requestPlayerList(); - void sendAction(const QString& action); + Q_SCRIPTABLE void seek(int offset) const; + Q_SCRIPTABLE void requestPlayerList(); + Q_SCRIPTABLE void sendAction(const QString& action); Q_SIGNALS: void propertiesChanged(); private: - QString dbusPath() const; + void requestPlayerStatus(); QString m_player; bool m_playing; QString m_nowPlaying; int m_volume; long m_length; long m_lastPosition; qint64 m_lastPositionTime; QStringList m_playerList; }; #endif diff --git a/plugins/notifications/CMakeLists.txt b/plugins/notifications/CMakeLists.txt index 41c5b78f..86f962c5 100644 --- a/plugins/notifications/CMakeLists.txt +++ b/plugins/notifications/CMakeLists.txt @@ -1,15 +1,20 @@ -find_package(KF5 REQUIRED COMPONENTS Notifications) +find_package(KF5 REQUIRED COMPONENTS Notifications KCMUtils I18n IconThemes Config) set(kdeconnect_notifications_SRCS notification.cpp notificationsplugin.cpp notificationsdbusinterface.cpp + sendreplydialog.cpp ) +ki18n_wrap_ui(kdeconnect_notifications_SRCS sendreplydialog.ui) + kdeconnect_add_plugin(kdeconnect_notifications JSON kdeconnect_notifications.json SOURCES ${kdeconnect_notifications_SRCS}) target_link_libraries(kdeconnect_notifications kdeconnectcore Qt5::DBus KF5::Notifications + KF5::I18n + KF5::IconThemes ) diff --git a/plugins/notifications/kdeconnect_notifications.json b/plugins/notifications/kdeconnect_notifications.json index cdf9dfea..56e79427 100644 --- a/plugins/notifications/kdeconnect_notifications.json +++ b/plugins/notifications/kdeconnect_notifications.json @@ -1,64 +1,98 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Show phone notifications on your computer and keep them in sync", - "Description[ca]": "Mostra les notificacions del telèfon a l'ordinador i les manté sincronitzades", - "Description[cs]": "Zobrazit upozornění telefonu v počítači a udržovat je synchronizovaná", - "Description[de]": "Benachrichtigungen auf Ihren Rechner anzeigen und abgleichen", - "Description[es]": "Mostrar notificaciones del teléfono en su equipo y mantenerlas en sincronía", - "Description[fi]": "Näytä puhelimen ilmoitukset tietokoneellasi ja pidä ne ajan tasalla", - "Description[gl]": "Mostre notificacións dun teléfono móbil no computador e deixe que as notificacións se sincronicen co teléfono.", - "Description[it]": "Mostra le notifiche del telefono sul tuo computer e tienile sincronizzate", - "Description[nl]": "Telefoonmeldingen op uw computer tonen en ze gesynchroniseerd houden", - "Description[pl]": "Pokaż powiadomienia telefonu na swoim komputerze i synchronizuj je", - "Description[pt]": "Mostrar as notificações do telefone no seu computador e mantê-las sincronizadas", - "Description[pt_BR]": "Mostra as notificações do celular no seu computador e as mantém sincronizadas", - "Description[sk]": "Zobraziť oznámenia telefónu na vašom počítači a udržať ich synchronizované", - "Description[sv]": "Visa telefonunderrättelser på datorn och håll dem synkroniserade", - "Description[tr]": "Telefon bildirimlerini bilgisayarınızla eşitler ve eşzamanlı tutar", - "Description[uk]": "Показ сповіщень з телефону на вашому комп’ютері та підтримання синхронізації даних сповіщень", - "Description[x-test]": "xxShow phone notifications on your computer and keep them in syncxx", - "EnabledByDefault": true, - "Icon": "preferences-desktop-notification", - "Id": "kdeconnect_notifications", - "License": "GPL", - "Name": "Notification sync", - "Name[ca]": "Sincronització de les notificacions", - "Name[cs]": "Synchronizace upozornění", - "Name[de]": "Benachrichtigungs-Abgleich", - "Name[en_GB]": "Notification sync", - "Name[es]": "Sincronización de notificaciones", - "Name[fi]": "Ilmoitusten synkronointi", - "Name[gl]": "Sincronización de notificacións", - "Name[hu]": "Értesítésszinkronizáció", - "Name[it]": "Sincronizzazione notifiche", - "Name[nl]": "Synchronisatie van meldingen", - "Name[pl]": "Powiadomienia synchronizacji", - "Name[pt]": "Sincronização de notificações", - "Name[pt_BR]": "Sincronização de notificações", - "Name[ru]": "Синхронизация уведомлений", - "Name[sk]": "Synchronizácia pripomienok", - "Name[sv]": "Underrättelsesynkronisering", - "Name[tr]": "Bildirim eşitleme", - "Name[uk]": "Синхронізація сповіщень", - "Name[x-test]": "xxNotification syncxx", - "Name[zh_CN]": "通知同步", + ], + "Description": "Show device's notifications on this computer and keep them in sync", + "Description[ar]": "أظهر إخطارات الجهاز على هذا الحاسوب وأبقها متزامنة", + "Description[ca@valencia]": "Mostra les notificacions del dispositiu a l'ordinador i les manté sincronitzades", + "Description[ca]": "Mostra les notificacions del dispositiu a l'ordinador i les manté sincronitzades", + "Description[cs]": "Zobrazit upozornění telefonu v počítači a udržovat je synchronizovaná", + "Description[da]": "Vis enhedens bekendtgørelser på denne computer og hold dem synkroniseret", + "Description[de]": "Benachrichtigungen des Geräts auf diesem Rechner anzeigen und abgleichen", + "Description[el]": "Εμφάνιση ειδοποιήσεων συσκευής στον υπολογιστή αυτόν και συγχρονισμός τους", + "Description[es]": "Mostrar notificaciones del dispositivo en este equipo y mantenerlas en sincronía", + "Description[et]": "Seadme märguannete näitamine arvutis ja nende sünkroonis hoidmine", + "Description[eu]": "Erakutsi gailuaren jakinarazpenak ordenagailu honetan eta mantendu sinkronizatuta", + "Description[fi]": "Näytä laitteen ilmoitukset tällä tietokoneella ja pidä ne ajan tasalla", + "Description[fr]": "Affichez les notifications du téléphone sur l'ordinateur et synchronisez-les", + "Description[gl]": "Mostrar as notificacións do dispositivo neste computador e mantelas sincronizadas.", + "Description[it]": "Mostra le notifiche del telefono sul tuo computer e tienile sincronizzate", + "Description[ko]": "휴대폰 알림을 컴퓨터에 표시하고 동기화하기", + "Description[nl]": "Apparaatmeldingen op deze computer tonen en ze gesynchroniseerd houden", + "Description[nn]": "Vis telefonvarslingar på datamaskina og hald dei synkronisert", + "Description[pl]": "Pokaż powiadomienia urządzenia na swoim komputerze i synchronizuj je", + "Description[pt]": "Mostrar as notificações do dispositivo neste computador e mantê-las sincronizadas", + "Description[pt_BR]": "Mostra as notificações do aparelho neste computador e as mantém sincronizadas", + "Description[ru]": "Показ уведомлений с устройства на компьютере и их синхронизация", + "Description[sk]": "Zobraziť oznámenia telefónu na vašom počítači a udržať ich synchronizované", + "Description[sr@ijekavian]": "Приказује обавештења са уређаја на рачунару и одржава их ажурним", + "Description[sr@ijekavianlatin]": "Prikazuje obaveštenja sa uređaja na računaru i održava ih ažurnim", + "Description[sr@latin]": "Prikazuje obaveštenja sa uređaja na računaru i održava ih ažurnim", + "Description[sr]": "Приказује обавештења са уређаја на рачунару и одржава их ажурним", + "Description[sv]": "Visa enhetens underrättelser på datorn och håll dem synkroniserade", + "Description[tr]": "Aygıtın bildirimlerini bilgisayarda göster ve eşzamanlı tut", + "Description[uk]": "Показ сповіщень з пристрою на цьому комп’ютері та підтримання синхронізації даних сповіщень", + "Description[x-test]": "xxShow device's notifications on this computer and keep them in syncxx", + "Description[zh_CN]": "在电脑显示手机通知并保持同步", + "Description[zh_TW]": "在電腦上顯示裝置通知並且保持同步", + "EnabledByDefault": true, + "Icon": "preferences-desktop-notification", + "Id": "kdeconnect_notifications", + "License": "GPL", + "Name": "Receive notifications", + "Name[ar]": "استقبل الإخطارات", + "Name[ca@valencia]": "Recepció de les notificacions", + "Name[ca]": "Recepció de les notificacions", + "Name[cs]": "Přijímat oznámení", + "Name[da]": "Modtag bekendtgørelser", + "Name[de]": "Benachrichtigungen empfangen", + "Name[el]": "Λήψη ειδοποιήσεων", + "Name[es]": "Recibir notificaciones", + "Name[et]": "Märguannete vastuvõtmine", + "Name[eu]": "Jaso jakinarazpenak", + "Name[fi]": "Vastaanota ilmoituksia", + "Name[fr]": "Recevoir les notifications", + "Name[gl]": "Recibir notificacións", + "Name[it]": "Ricevi notifiche", + "Name[ko]": "알림 받기", + "Name[nl]": "Meldingen ontvangen", + "Name[nn]": "Få varslingar", + "Name[pl]": "Otrzymuj powiadomienia", + "Name[pt]": "Receber as notificações", + "Name[pt_BR]": "Receber notificações", + "Name[ru]": "Получение уведомлений", + "Name[sk]": "Prijať upozornenia", + "Name[sr@ijekavian]": "Пријем обавештења", + "Name[sr@ijekavianlatin]": "Prijem obaveštenja", + "Name[sr@latin]": "Prijem obaveštenja", + "Name[sr]": "Пријем обавештења", + "Name[sv]": "Ta emot underrättelser", + "Name[tr]": "Bildirimleri al", + "Name[uk]": "Отримати сповіщення", + "Name[x-test]": "xxReceive notificationsxx", + "Name[zh_CN]": "接收通知", + "Name[zh_TW]": "收到通知", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.notification" - ], + "kdeconnect.notification.request", + "kdeconnect.notification.reply" + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.notification" ] -} \ No newline at end of file +} diff --git a/plugins/notifications/notification.cpp b/plugins/notifications/notification.cpp index b44b5f85..ba75981f 100644 --- a/plugins/notifications/notification.cpp +++ b/plugins/notifications/notification.cpp @@ -1,46 +1,187 @@ /** * 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 "notification.h" +#include "notification_debug.h" -#include +#include +#include +#include +#include +#include +#include +#include -Notification::Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent) +#include + + +Notification::Notification(const NetworkPackage& np, QObject* parent) : QObject(parent) { - mId = np.get("id"); - mAppName = np.get("appName"); - mTicker = np.get("ticker"); - mDismissable = np.get("isClearable"); - mIconPath = iconPath; + //Make a own directory for each user so noone can see each others icons + QString username; + #ifdef Q_OS_WIN + username = qgetenv("USERNAME"); + #else + username = qgetenv("USER"); + #endif + + m_imagesDir = QDir::temp().absoluteFilePath(QStringLiteral("kdeconnect_") + username); + m_imagesDir.mkpath(m_imagesDir.absolutePath()); + QFile(m_imagesDir.absolutePath()).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); + m_closed = false; + m_ready = false; + + parseNetworkPackage(np); + createKNotification(false, np); } Notification::~Notification() { - } void Notification::dismiss() { - if (mDismissable) { - Q_EMIT dismissRequested(this); + if (m_dismissable) { + Q_EMIT dismissRequested(m_internalId); + } +} + +void Notification::show() +{ + m_ready = true; + Q_EMIT ready(); + if (!m_silent) { + m_closed = false; + m_notification->sendEvent(); + } +} + +void Notification::update(const NetworkPackage& np) +{ + parseNetworkPackage(np); + createKNotification(!m_closed, np); +} + +KNotification* Notification::createKNotification(bool update, const NetworkPackage& np) +{ + if (!update) { + m_notification = new KNotification(QStringLiteral("notification"), KNotification::CloseOnTimeout, this); + m_notification->setComponentName(QStringLiteral("kdeconnect")); + } + + QString escapedTitle = m_title.toHtmlEscaped(); + QString escapedText = m_text.toHtmlEscaped(); + QString escapedTicker = m_ticker.toHtmlEscaped(); + + m_notification->setTitle(m_appName.toHtmlEscaped()); + + if (m_title.isEmpty() && m_text.isEmpty()) { + m_notification->setText(escapedTicker); + } else if (m_appName==m_title) { + m_notification->setText(escapedText); + } else if (m_title.isEmpty()){ + m_notification->setText(escapedText); + } else if (m_text.isEmpty()){ + m_notification->setText(escapedTitle); + } else { + m_notification->setText(escapedTitle+": "+escapedText); + } + + m_hasIcon = m_hasIcon && !m_payloadHash.isEmpty(); + + if (!m_hasIcon) { + applyNoIcon(); + show(); + } else { + + m_iconPath = m_imagesDir.absoluteFilePath(m_payloadHash); + + if (!QFile::exists(m_iconPath)) { + loadIcon(np); + } else { + applyIcon(); + show(); + } + } + + if (!m_requestReplyId.isEmpty()) { + m_notification->setActions(QStringList(i18n("Reply"))); + connect(m_notification, &KNotification::action1Activated, this, &Notification::reply); } + + connect(m_notification, &KNotification::closed, this, &Notification::closed); + + return m_notification; +} + +void Notification::loadIcon(const NetworkPackage& np) +{ + m_ready = false; + FileTransferJob* job = np.createPayloadTransferJob(QUrl::fromLocalFile(m_iconPath)); + job->start(); + + connect(job, &FileTransferJob::result, this, [this, job]{ + + if (job->error()) { + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Error in FileTransferJob: " << job->errorString(); + applyNoIcon(); + } else { + applyIcon(); + } + show(); + }); +} + +void Notification::applyIcon() +{ + QPixmap icon(m_iconPath, "PNG"); + m_notification->setPixmap(icon); +} + +void Notification::applyNoIcon() +{ + //HACK The only way to display no icon at all is trying to load a non-existant icon + m_notification->setIconName(QStringLiteral("not_a_real_icon")); +} + +void Notification::reply() +{ + Q_EMIT replyRequested(); +} + +void Notification::closed() +{ + m_closed = true; } +void Notification::parseNetworkPackage(const NetworkPackage& np) +{ + m_internalId = np.get(QStringLiteral("id")); + m_appName = np.get(QStringLiteral("appName")); + m_ticker = np.get(QStringLiteral("ticker")); + m_title = np.get(QStringLiteral("title")); + m_text = np.get(QStringLiteral("text")); + m_dismissable = np.get(QStringLiteral("isClearable")); + m_hasIcon = np.hasPayload(); + m_silent = np.get(QStringLiteral("silent")); + m_payloadHash = np.get(QStringLiteral("payloadHash")); + m_requestReplyId = np.get(QStringLiteral("requestReplyId"), QString()); +} diff --git a/plugins/notifications/notification.h b/plugins/notifications/notification.h index 3c14d26d..e97cb60f 100644 --- a/plugins/notifications/notification.h +++ b/plugins/notifications/notification.h @@ -1,65 +1,99 @@ /** * 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 NOTIFICATION_H #define NOTIFICATION_H #include #include +#include +#include #include class Notification : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.notifications.notification") Q_PROPERTY(QString internalId READ internalId) Q_PROPERTY(QString appName READ appName) Q_PROPERTY(QString ticker READ ticker) + Q_PROPERTY(QString title READ title) + Q_PROPERTY(QString text READ text) Q_PROPERTY(QString iconPath READ iconPath) Q_PROPERTY(bool dismissable READ dismissable) + Q_PROPERTY(bool hasIcon READ hasIcon) + Q_PROPERTY(bool silent READ silent) + Q_PROPERTY(QString replyId READ replyId) public: - Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent); - virtual ~Notification(); + Notification(const NetworkPackage& np, QObject* parent); + ~Notification() override; - QString internalId() const { return mId; } - QString appName() const { return mAppName; } - QString ticker() const { return mTicker; } - QString iconPath() const { return mIconPath; } - bool dismissable() const { return mDismissable; } + QString internalId() const { return m_internalId; } + QString appName() const { return m_appName; } + QString ticker() const { return m_ticker; } + QString title() const { return m_title; } + QString text() const { return m_text; } + QString iconPath() const { return m_iconPath; } + bool dismissable() const { return m_dismissable; } + QString replyId() const { return m_requestReplyId; } + bool hasIcon() const { return m_hasIcon; } + void show(); + bool silent() const { return m_silent; } + void update(const NetworkPackage& np); + bool isReady() const { return m_ready; } + KNotification* createKNotification(bool update, const NetworkPackage& np); public Q_SLOTS: Q_SCRIPTABLE void dismiss(); + Q_SCRIPTABLE void reply(); + void closed(); -Q_SIGNALS: - void dismissRequested(Notification* self); + Q_SIGNALS: + void dismissRequested(const QString& m_internalId); + void replyRequested(); + void ready(); private: - QString mId; - QString mAppName; - QString mTicker; - QString mIconPath; - bool mDismissable; + QString m_internalId; + QString m_appName; + QString m_ticker; + QString m_title; + QString m_text; + QString m_iconPath; + QString m_requestReplyId; + bool m_dismissable; + bool m_hasIcon; + KNotification* m_notification; + QDir m_imagesDir; + bool m_silent; + bool m_closed; + QString m_payloadHash; + bool m_ready; + void parseNetworkPackage(const NetworkPackage& np); + void loadIcon(const NetworkPackage& np); + void applyIcon(); + void applyNoIcon(); }; #endif diff --git a/plugins/notifications/notification_debug.h b/plugins/notifications/notification_debug.h index 09da52f8..febca382 100644 --- a/plugins/notifications/notification_debug.h +++ b/plugins/notifications/notification_debug.h @@ -1,28 +1,28 @@ /** * 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 . */ #ifndef NOTIFICATION_DEBUG_H #define NOTIFICATION_DEBUG_H #include Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_NOTIFICATION) -#endif //NOTIFICATION_DEBUG_H \ No newline at end of file +#endif //NOTIFICATION_DEBUG_H diff --git a/plugins/notifications/notificationsdbusinterface.cpp b/plugins/notifications/notificationsdbusinterface.cpp index 66d6790c..c0505c66 100644 --- a/plugins/notifications/notificationsdbusinterface.cpp +++ b/plugins/notifications/notificationsdbusinterface.cpp @@ -1,157 +1,197 @@ /** * 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 - #include #include -#include #include "notificationsplugin.h" +#include "sendreplydialog.h" NotificationsDbusInterface::NotificationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) - , mDevice(plugin->device()) - , mPlugin(plugin) - , mLastId(0) - , imagesDir(QDir::temp().absoluteFilePath("kdeconnect")) + , m_device(plugin->device()) + , m_plugin(plugin) + , m_lastId(0) { - imagesDir.mkpath(imagesDir.absolutePath()); } NotificationsDbusInterface::~NotificationsDbusInterface() { - clearNotifications(); + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsDbusInterface"; } void NotificationsDbusInterface::clearNotifications() { - qDeleteAll(mNotifications); + qDeleteAll(m_notifications); + m_notifications.clear(); + Q_EMIT allNotificationsRemoved(); } QStringList NotificationsDbusInterface::activeNotifications() { - return mNotifications.keys(); + return m_notifications.keys(); } void NotificationsDbusInterface::processPackage(const NetworkPackage& np) { - if (np.get("isCancel")) { - removeNotification(np.get("id")); - } else { - - - //TODO: Uncoment when we are able to display app icon on plasmoid - QString destination; - /* - if (np.hasPayload()) { - QString filename = KMD5(np.get("appName").toLatin1()).hexDigest(); //TODO: Store with extension? - destination = imagesDir.absoluteFilePath(filename); - FileTransferJob* job = np.createPayloadTransferJob(destination); - job->start(); + if (np.get(QStringLiteral("isCancel"))) { + QString id = np.get(QStringLiteral("id")); + // cut off kdeconnect-android's prefix if there: + if (id.startsWith(QLatin1String("org.kde.kdeconnect_tp::"))) + id = id.mid(id.indexOf(QLatin1String("::")) + 2); + removeNotification(id); + } else if (np.get(QStringLiteral("isRequest"))) { + for (const auto& n : qAsConst(m_notifications)) { + NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REQUEST, { + {"id", n->internalId()}, + {"appName", n->appName()}, + {"ticker", n->ticker()}, + {"isClearable", n->dismissable()}, + {"requestAnswer", true} + }); + m_plugin->sendPackage(np); } - */ - - Notification* noti = new Notification(np, destination, this); - - //Do not show updates to existent notification nor answers to a initialization request - if (!mInternalIdToPublicId.contains(noti->internalId()) && !np.get("requestAnswer", false) && !np.get("silent", false)) { - KNotification* notification = new KNotification("notification", KNotification::CloseOnTimeout, this); - notification->setIconName(QStringLiteral("preferences-desktop-notification")); - notification->setComponentName("kdeconnect"); - notification->setTitle(mDevice->name()); - notification->setText(noti->appName() + ": " + noti->ticker()); - notification->sendEvent(); + } else if(np.get(QStringLiteral("requestAnswer"), false)) { + + } 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, [this, noti]{ + addNotification(noti); + }); + } + } 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); + }); + } } - addNotification(noti); } } void NotificationsDbusInterface::addNotification(Notification* noti) { const QString& internalId = noti->internalId(); - if (mInternalIdToPublicId.contains(internalId)) { + if (m_internalIdToPublicId.contains(internalId)) { removeNotification(internalId); } - connect(noti, SIGNAL(dismissRequested(Notification*)), - this, SLOT(dismissRequested(Notification*))); + //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "addNotification" << internalId; + + connect(noti, &Notification::dismissRequested, + this, &NotificationsDbusInterface::dismissRequested); + + connect(noti, &Notification::replyRequested, this, [this,noti]{ + replyRequested(noti); + }); const QString& publicId = newId(); - mNotifications[publicId] = noti; - mInternalIdToPublicId[internalId] = publicId; + m_notifications[publicId] = noti; + m_internalIdToPublicId[internalId] = publicId; - QDBusConnection::sessionBus().registerObject(mDevice->dbusPath()+"/notifications/"+publicId, noti, QDBusConnection::ExportScriptableContents); + 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; + //qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "removeNotification" << internalId; - if (!mInternalIdToPublicId.contains(internalId)) { - qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found"; + if (!m_internalIdToPublicId.contains(internalId)) { + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found noti by internal Id: " << internalId; return; } - QString publicId = mInternalIdToPublicId.take(internalId); + QString publicId = m_internalIdToPublicId.take(internalId); - Notification* noti = mNotifications.take(publicId); + Notification* noti = m_notifications.take(publicId); if (!noti) { - qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Not found"; + 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(Notification* notification) +void NotificationsDbusInterface::dismissRequested(const QString& internalId) { - const QString& internalId = notification->internalId(); - - NetworkPackage np(PACKAGE_TYPE_NOTIFICATION); - np.set("cancel", internalId); - mPlugin->sendPackage(np); + NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REQUEST); + np.set(QStringLiteral("cancel"), internalId); + m_plugin->sendPackage(np); //Workaround: we erase notifications without waiting a repsonse from the //phone because we won't receive a response if we are out of sync and this //notification no longer exists. Ideally, each time we reach the phone //after some time disconnected we should re-sync all the notifications. removeNotification(internalId); } +void NotificationsDbusInterface::replyRequested(Notification* noti) +{ + QString replyId = noti->replyId(); + QString appName = noti->appName(); + QString originalMessage = noti->ticker(); + SendReplyDialog* dialog = new SendReplyDialog(originalMessage, replyId, appName); + connect(dialog, &SendReplyDialog::sendReply, this, &NotificationsDbusInterface::sendReply); + dialog->show(); + dialog->raise(); +} + +void NotificationsDbusInterface::sendReply(const QString& replyId, const QString& message) +{ + NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REPLY); + np.set(QStringLiteral("requestReplyId"), replyId); + np.set(QStringLiteral("message"), message); + m_plugin->sendPackage(np); +} + QString NotificationsDbusInterface::newId() { - return QString::number(++mLastId); + return QString::number(++m_lastId); } diff --git a/plugins/notifications/notificationsdbusinterface.h b/plugins/notifications/notificationsdbusinterface.h index e8d5c2a7..a6f74629 100644 --- a/plugins/notifications/notificationsdbusinterface.h +++ b/plugins/notifications/notificationsdbusinterface.h @@ -1,71 +1,74 @@ /** * 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 NOTIFICATIONSDBUSINTERFACE_H #define NOTIFICATIONSDBUSINTERFACE_H #include #include #include #include #include +#include #include "notification.h" class KdeConnectPlugin; class Device; class NotificationsDbusInterface : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.notifications") public: explicit NotificationsDbusInterface(KdeConnectPlugin* plugin); - virtual ~NotificationsDbusInterface(); + ~NotificationsDbusInterface() override; void processPackage(const NetworkPackage& np); void clearNotifications(); + void dismissRequested(const QString& notification); + void replyRequested(Notification* noti); + void addNotification(Notification* noti); public Q_SLOTS: Q_SCRIPTABLE QStringList activeNotifications(); - void dismissRequested(Notification* notification); + Q_SCRIPTABLE void sendReply(const QString& replyId, const QString& message); Q_SIGNALS: Q_SCRIPTABLE void notificationPosted(const QString& publicId); Q_SCRIPTABLE void notificationRemoved(const QString& publicId); + Q_SCRIPTABLE void notificationUpdated(const QString& publicId); + Q_SCRIPTABLE void allNotificationsRemoved(); private /*methods*/: - void addNotification(Notification* noti); void removeNotification(const QString& internalId); QString newId(); //Generates successive identifitiers to use as public ids private /*attributes*/: - const Device* mDevice; - KdeConnectPlugin* mPlugin; - QHash mNotifications; - QHash mInternalIdToPublicId; - int mLastId; - QDir imagesDir; - + const Device* m_device; + KdeConnectPlugin* m_plugin; + QHash> m_notifications; + QHash m_internalIdToPublicId; + int m_lastId; }; #endif diff --git a/plugins/notifications/notificationsplugin.cpp b/plugins/notifications/notificationsplugin.cpp index badb5d34..28af6052 100644 --- a/plugins/notifications/notificationsplugin.cpp +++ b/plugins/notifications/notificationsplugin.cpp @@ -1,64 +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 . */ #include "notificationsplugin.h" #include "notificationsdbusinterface.h" #include "notification_debug.h" #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_notifications.json", registerPlugin< NotificationsPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_NOTIFICATION, "kdeconnect.plugin.notification") NotificationsPlugin::NotificationsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { notificationsDbusInterface = new NotificationsDbusInterface(this); } void NotificationsPlugin::connected() { - NetworkPackage np(PACKAGE_TYPE_NOTIFICATION); - np.set("request", true); + NetworkPackage np(PACKAGE_TYPE_NOTIFICATION_REQUEST, {{"request", true}}); sendPackage(np); } NotificationsPlugin::~NotificationsPlugin() { + qCDebug(KDECONNECT_PLUGIN_NOTIFICATION) << "Destroying NotificationsPlugin"; //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 leaks the interface. As a mitigation we are // cleaning up the notifications inside the adaptor here. //notificationsDbusInterface->deleteLater(); notificationsDbusInterface->clearNotifications(); } bool NotificationsPlugin::receivePackage(const NetworkPackage& np) { - if (np.get("request")) return false; + if (np.get(QStringLiteral("request"))) return false; notificationsDbusInterface->processPackage(np); return true; } #include "notificationsplugin.moc" diff --git a/plugins/notifications/notificationsplugin.h b/plugins/notifications/notificationsplugin.h index a2e78280..efbac648 100644 --- a/plugins/notifications/notificationsplugin.h +++ b/plugins/notifications/notificationsplugin.h @@ -1,54 +1,57 @@ /** * 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 NOTIFICATIONSPLUGIN_H #define NOTIFICATIONSPLUGIN_H #include #include -#define PACKAGE_TYPE_NOTIFICATION QLatin1String("kdeconnect.notification") + +#define PACKAGE_TYPE_NOTIFICATION_REQUEST QStringLiteral("kdeconnect.notification.request") +#define PACKAGE_TYPE_NOTIFICATION_REPLY QStringLiteral("kdeconnect.notification.reply") /* * This class is just a proxy for NotificationsDbusInterface * because it can not inherit from QDBusAbstractAdaptor and * KdeConnectPlugin at the same time (both are QObject) */ class NotificationsDbusInterface; +class NotificationsListener; class NotificationsPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit NotificationsPlugin(QObject *parent, const QVariantList &args); - virtual ~NotificationsPlugin(); + explicit NotificationsPlugin(QObject* parent, const QVariantList& args); + ~NotificationsPlugin() override; -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override; -private: +protected: NotificationsDbusInterface* notificationsDbusInterface; + NotificationsListener* notificationsListener; }; #endif diff --git a/plugins/notifications/sendreplydialog.cpp b/plugins/notifications/sendreplydialog.cpp new file mode 100644 index 00000000..28e82ccd --- /dev/null +++ b/plugins/notifications/sendreplydialog.cpp @@ -0,0 +1,60 @@ +/** + * Copyright 2015 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 "sendreplydialog.h" + +#include +#include +#include +#include + +#include + +#include "ui_sendreplydialog.h" + +SendReplyDialog::SendReplyDialog(const QString& originalMessage, const QString& replyId, const QString& topicName, QWidget* parent) + : QDialog(parent) + , m_replyId(replyId) + , m_ui(new Ui::SendReplyDialog) +{ + m_ui->setupUi(this); + m_ui->textView->setText(topicName + ": \n" + originalMessage); + + auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok); + button->setText(i18n("Send")); + + connect(this, &QDialog::accepted, this, &SendReplyDialog::sendButtonClicked); + setWindowTitle(topicName); + setWindowIcon(QIcon::fromTheme(QStringLiteral("kdeconnect"))); + setAttribute(Qt::WA_DeleteOnClose); +} + +SendReplyDialog::~SendReplyDialog() = default; + +void SendReplyDialog::sendButtonClicked() +{ + Q_EMIT sendReply(m_replyId, m_ui->replyEdit->toPlainText()); + close(); +} + +QSize SendReplyDialog::sizeHint() const +{ + return QSize(512, 64); +} diff --git a/core/backends/lan/landevicelink.h b/plugins/notifications/sendreplydialog.h similarity index 59% copy from core/backends/lan/landevicelink.h copy to plugins/notifications/sendreplydialog.h index 7d31881f..2e0e81d3 100644 --- a/core/backends/lan/landevicelink.h +++ b/plugins/notifications/sendreplydialog.h @@ -1,51 +1,50 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2015 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 LANDEVICELINK_H -#define LANDEVICELINK_H +#ifndef SENDREPLYDIALOG_H +#define SENDREPLYDIALOG_H -#include -#include -#include +#include +#include -#include "../devicelink.h" +namespace Ui { class SendReplyDialog; } -class SocketLineReader; - -class LanDeviceLink - : public DeviceLink +class SendReplyDialog : public QDialog { Q_OBJECT public: - LanDeviceLink(const QString& deviceId, LinkProvider* parent, QTcpSocket* socket); + explicit SendReplyDialog(const QString& originalMessage, const QString& replyId, const QString& topicName, QWidget* parent = nullptr); + ~SendReplyDialog() override; - bool sendPackage(NetworkPackage& np); - bool sendPackageEncrypted(QCA::PublicKey& key, NetworkPackage& np); + QSize sizeHint() const override; private Q_SLOTS: - void dataReceived(); + void sendButtonClicked(); -private: - SocketLineReader* mSocketLineReader; +Q_SIGNALS: + void sendReply(const QString& replyId, const QString& messageBody); +private: + const QString m_replyId; + const QScopedPointer m_ui; }; #endif diff --git a/plugins/notifications/sendreplydialog.ui b/plugins/notifications/sendreplydialog.ui new file mode 100644 index 00000000..07db50b4 --- /dev/null +++ b/plugins/notifications/sendreplydialog.ui @@ -0,0 +1,74 @@ + + + SendReplyDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + Qt::NoFocus + + + true + + + + + + + + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SendReplyDialog + accept() + + + 82 + 274 + + + 2 + 279 + + + + + buttonBox + rejected() + SendReplyDialog + reject() + + + 149 + 284 + + + 143 + 298 + + + + + diff --git a/plugins/pausemusic/CMakeLists.txt b/plugins/pausemusic/CMakeLists.txt index b5f3a110..d13aa3e5 100644 --- a/plugins/pausemusic/CMakeLists.txt +++ b/plugins/pausemusic/CMakeLists.txt @@ -1,26 +1,27 @@ find_package(KF5 REQUIRED COMPONENTS KCMUtils) +find_program(PACTL pactl) kdeconnect_add_plugin(kdeconnect_pausemusic JSON kdeconnect_pausemusic.json SOURCES pausemusicplugin.cpp) target_link_libraries(kdeconnect_pausemusic kdeconnectcore Qt5::Core Qt5::DBus ) ####################################### # Config set( kdeconnect_pausemusic_config_SRCS pausemusic_config.cpp ) ki18n_wrap_ui( kdeconnect_pausemusic_config_SRCS pausemusic_config.ui ) add_library(kdeconnect_pausemusic_config MODULE ${kdeconnect_pausemusic_config_SRCS} ) target_link_libraries( kdeconnect_pausemusic_config kdeconnectcore kdeconnectpluginkcm KF5::I18n KF5::KCMUtils ) install( TARGETS kdeconnect_pausemusic_config DESTINATION ${PLUGIN_INSTALL_DIR} ) install( FILES kdeconnect_pausemusic_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/plugins/pausemusic/kdeconnect_pausemusic.json b/plugins/pausemusic/kdeconnect_pausemusic.json index 99dc49fd..338135bf 100644 --- a/plugins/pausemusic/kdeconnect_pausemusic.json +++ b/plugins/pausemusic/kdeconnect_pausemusic.json @@ -1,63 +1,96 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Pause music/videos during a phone call", - "Description[ca]": "Pausa la música/vídeos durant una trucada telefònica", - "Description[cs]": "Pozastavit hudbu/video během telefonátu", - "Description[de]": "Hält Musik oder Videos währen eines Anrufs an", - "Description[en_GB]": "Pause music/videos during a phone call", - "Description[es]": "Pausar música/video durante las llamadas telefónicas", - "Description[fi]": "Keskeytä musiikki ja videot puhelun aikana", - "Description[gl]": "Deter a reprodución de música ou vídeos durante as chamadas.", - "Description[hu]": "A médialejátszás szüneteltetése telefonhívások közben", - "Description[it]": "Sospendi musica/video durante una chiamata", - "Description[nl]": "Muziek/video's pauzeren tijdens een telefoongesprek", - "Description[pl]": "Wstrzymaj muzykę/film przy dzwonieniu", - "Description[pt]": "Pausar a música/vídeos durante uma chamada telefónica", - "Description[pt_BR]": "Pausa a música/vídeo durante uma chamada", - "Description[sk]": "Pozastaviť hudbu/videá počas telefónneho hovoru", - "Description[sv]": "Pausa musik eller videor under ett telefonsamtal", - "Description[tr]": "Bir telefon çağrısı sırasında müzik/videoları duraklat", - "Description[uk]": "Призупинка відтворення музики і відео на час телефонних дзвінків", - "Description[x-test]": "xxPause music/videos during a phone callxx", - "Description[zh_CN]": "手机来电时暂停音乐/视频", - "EnabledByDefault": true, - "Icon": "media-playback-pause", - "Id": "kdeconnect_pausemusic", - "License": "GPL", - "Name": "Pause media during calls", - "Name[ca]": "Pausa els suports durant les trucades", - "Name[cs]": "Pozastavit média během telefonátu", - "Name[de]": "Medium bei Anrufen anhalten", - "Name[en_GB]": "Pause media during calls", - "Name[es]": "Pausar medios durante las llamadas", - "Name[fi]": "Keskeytä toisto puhelujen aikana", - "Name[gl]": "Deter a reprodución durante as chamadas.", - "Name[hu]": "Médialejátszás felfüggesztése", - "Name[it]": "Sospendi i lettori multimediali", - "Name[nl]": "Media pauzeren tijdens oproepen", - "Name[pl]": "Wstrzymaj multimedia przy dzwonieniu", - "Name[pt]": "Pausar a reprodução durante as chamadas", - "Name[pt_BR]": "Pausar os conteúdos multimídia durante as chamadas", - "Name[sk]": "Pozastaviť médiá počas hovorov", - "Name[sv]": "Pausa media under samtal", - "Name[tr]": "Çağrılar sırasında ortamı duraklat", - "Name[uk]": "Призупинка відтворення під час дзвінків", - "Name[x-test]": "xxPause media during callsxx", - "Name[zh_CN]": "来电时暂停媒体", + ], + "Description": "Pause music/videos during a phone call", + "Description[ar]": "ألبث الموسيقى/الفيديو أثناء المكالمات الهاتفيّة", + "Description[ca@valencia]": "Pausa la música/vídeos durant una trucada telefònica", + "Description[ca]": "Pausa la música/vídeos durant una trucada telefònica", + "Description[cs]": "Pozastavit hudbu/video během telefonátu", + "Description[da]": "Sæt musik/videoer på pause under opkald", + "Description[de]": "Hält Musik oder Videos während eines Anrufs an", + "Description[el]": "Παύση μουσικής/βίντεο κατά τη διάρκεια κλήσης", + "Description[es]": "Pausar música/video durante las llamadas telefónicas", + "Description[et]": "Muusika või video peatamine telefonikõne ajal", + "Description[eu]": "Pausatu musika / bideoak deietan", + "Description[fi]": "Keskeytä musiikki ja videot puhelun aikana", + "Description[fr]": "Mettre en pause la musique / les vidéos pendant un appel téléphonique", + "Description[gl]": "Deter a reprodución de música ou vídeos durante as chamadas.", + "Description[hu]": "A médialejátszás szüneteltetése telefonhívások közben", + "Description[it]": "Sospendi musica/video durante una chiamata", + "Description[ko]": "휴대폰 통화 중 음악/동영상 일시 정지", + "Description[nl]": "Muziek/video's pauzeren tijdens een telefoongesprek", + "Description[nn]": "Set avspeling på pause ved oppringingar", + "Description[pl]": "Wstrzymaj muzykę/film przy dzwonieniu", + "Description[pt]": "Pausar a música/vídeos durante uma chamada telefónica", + "Description[pt_BR]": "Pausa a música/vídeo durante uma chamada", + "Description[ru]": "Приостановка воспроизведения музыки и видео во время телефонного звонка", + "Description[sk]": "Pozastaviť hudbu/videá počas telefónneho hovoru", + "Description[sr@ijekavian]": "Паузира музику или видео током телефонског позива", + "Description[sr@ijekavianlatin]": "Pauzira muziku ili video tokom telefonskog poziva", + "Description[sr@latin]": "Pauzira muziku ili video tokom telefonskog poziva", + "Description[sr]": "Паузира музику или видео током телефонског позива", + "Description[sv]": "Pausa musik eller videor under ett telefonsamtal", + "Description[tr]": "Bir telefon çağrısı sırasında müzik/videoları duraklat", + "Description[uk]": "Призупинка відтворення музики і відео на час телефонних дзвінків", + "Description[x-test]": "xxPause music/videos during a phone callxx", + "Description[zh_CN]": "手机来电时暂停音乐/视频", + "Description[zh_TW]": "在通話過程中暫停音樂與影片", + "EnabledByDefault": true, + "Icon": "media-playback-pause", + "Id": "kdeconnect_pausemusic", + "License": "GPL", + "Name": "Pause media during calls", + "Name[ar]": "ألبث الوسائط أثناء المكالمات", + "Name[ca@valencia]": "Pausa els suports durant les trucades", + "Name[ca]": "Pausa els suports durant les trucades", + "Name[cs]": "Pozastavit média během telefonátu", + "Name[da]": "Sæt medier på pause under opkald", + "Name[de]": "Medium bei Anrufen anhalten", + "Name[el]": "Παύση πολυμέσων στη διάρκεια κλήσεων", + "Name[es]": "Pausar medios durante las llamadas", + "Name[et]": "Meedia peatamine kõne ajal", + "Name[eu]": "Pausatu media deietan", + "Name[fi]": "Keskeytä toisto puhelujen aikana", + "Name[fr]": "Mettre en pause le média pendant les appels", + "Name[gl]": "Deter a reprodución durante as chamadas.", + "Name[hu]": "Médialejátszás felfüggesztése", + "Name[it]": "Sospendi i lettori multimediali", + "Name[ko]": "통화 중 미디어 일시 정지", + "Name[nl]": "Media pauzeren tijdens oproepen", + "Name[nn]": "Set avspeling på pause ved oppringingar", + "Name[pl]": "Wstrzymaj multimedia przy dzwonieniu", + "Name[pt]": "Pausar a reprodução durante as chamadas", + "Name[pt_BR]": "Pausar os conteúdos multimídia durante as chamadas", + "Name[ru]": "Приостановка воспроизведения во время звонка", + "Name[sk]": "Pozastaviť médiá počas hovorov", + "Name[sr@ijekavian]": "Паузирање медија током позива", + "Name[sr@ijekavianlatin]": "Pauziranje medija tokom poziva", + "Name[sr@latin]": "Pauziranje medija tokom poziva", + "Name[sr]": "Паузирање медија током позива", + "Name[sv]": "Pausa media under samtal", + "Name[tr]": "Çağrılar sırasında ortamı duraklat", + "Name[uk]": "Призупинка відтворення під час дзвінків", + "Name[x-test]": "xxPause media during callsxx", + "Name[zh_CN]": "来电时暂停媒体", + "Name[zh_TW]": "在通話過程中暫停媒體", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-SupportedPackageType": [ "kdeconnect.telephony" ] -} \ No newline at end of file +} diff --git a/plugins/pausemusic/kdeconnect_pausemusic_config.desktop b/plugins/pausemusic/kdeconnect_pausemusic_config.desktop index 1b513203..b8a5ab4d 100644 --- a/plugins/pausemusic/kdeconnect_pausemusic_config.desktop +++ b/plugins/pausemusic/kdeconnect_pausemusic_config.desktop @@ -1,34 +1,48 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kdeconnect_pausemusic_config X-KDE-ParentComponents=kdeconnect_pausemusic Name=Pause Music plugin settings +Name[ar]=إعدادات ملحقة إلباث الموسيقى +Name[ast]=Axustes del complementu de posar música Name[bg]=Настройки на приставката за поставяне на пауза Name[bs]=Zaustavi Muziku postavke dodatka Name[ca]=Ajustaments del connector Pausa la música +Name[ca@valencia]=Ajustaments del connector Pausa la música Name[cs]=Nastavení modulu Pozastavení hudby Name[da]=Indstilling af plugin til at sætte musik på pause Name[de]=Modul-Einstellungen für das Anhalten der Musikwiedergabe +Name[el]=Ρυθμίσεις προσθέτου παύσης μουσικής Name[en_GB]=Pause Music plugin settings Name[es]=Ajustes del complemento PauseMusic +Name[et]=Muusika peatamise plugina seadistused +Name[eu]=Pausatu-musika pluginaren ezarpenak Name[fi]=Keskeytä musiikki -liitännäisen asetukset Name[fr]=Paramètres du module de mise en pause Name[gl]=Configuración do complemento para deter a música +Name[he]=הגדרות התוספת השהיית המוזיקה Name[hu]=Zene szüneteltetése bővítmény beállításai Name[it]=Impostazioni estensione Sospendi musica Name[ko]=음악 일시 정지 플러그인 설정 Name[nl]=Plug-in-instellingen voor muziek pauzeren +Name[nn]=Innstillingar for pausing av musikk Name[pl]=Ustawienia wtyczki wstrzymywania muzyki Name[pt]=Configuração do 'plugin' de Pausa da Música Name[pt_BR]=Pausar as configurações do plugin Músicas +Name[ru]=Настройка модуля приостановки музыки Name[sk]=Nastavenia pluginu pozastavenia hudby +Name[sr]=Поставке прикључка паузирања музике +Name[sr@ijekavian]=Поставке прикључка паузирања музике +Name[sr@ijekavianlatin]=Postavke priključka pauziranja muzike +Name[sr@latin]=Postavke priključka pauziranja muzike Name[sv]=Inställningar av insticksprogram för paus i musik Name[tr]=Müziği Duraklat eklenti ayarları Name[uk]=Параметри додатка призупинення відтворення музики Name[x-test]=xxPause Music plugin settingsxx Name[zh_CN]=暂停音乐插件设置 +Name[zh_TW]=暫停音樂擴展插件設定 Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/plugins/pausemusic/pausemusic_config.cpp b/plugins/pausemusic/pausemusic_config.cpp index de5fdaa7..fde7a3ba 100644 --- a/plugins/pausemusic/pausemusic_config.cpp +++ b/plugins/pausemusic/pausemusic_config.cpp @@ -1,79 +1,79 @@ /** * 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 "pausemusic_config.h" #include "ui_pausemusic_config.h" #include K_PLUGIN_FACTORY(PauseMusicConfigFactory, registerPlugin();) -PauseMusicConfig::PauseMusicConfig(QWidget *parent, const QVariantList& args) - : KdeConnectPluginKcm(parent, args, "kdeconnect_pausemusic_config") +PauseMusicConfig::PauseMusicConfig(QWidget* parent, const QVariantList& args) + : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_pausemusic_config")) , m_ui(new Ui::PauseMusicConfigUi()) { m_ui->setupUi(this); connect(m_ui->rad_ringing, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_ui->rad_talking, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_ui->check_pause, SIGNAL(toggled(bool)), this, SLOT(changed())); connect(m_ui->check_mute, SIGNAL(toggled(bool)), this, SLOT(changed())); } PauseMusicConfig::~PauseMusicConfig() { delete m_ui; } void PauseMusicConfig::defaults() { KCModule::defaults(); m_ui->rad_talking->setChecked(false); m_ui->rad_ringing->setChecked(true); m_ui->check_pause->setChecked(true); m_ui->check_mute->setChecked(false); Q_EMIT changed(true); } void PauseMusicConfig::load() { KCModule::load(); - bool talking = config()->get("conditionTalking", false); + bool talking = config()->get(QStringLiteral("conditionTalking"), false); m_ui->rad_talking->setChecked(talking); m_ui->rad_ringing->setChecked(!talking); - bool pause = config()->get("actionPause", true); - bool mute = config()->get("actionMute", false); + bool pause = config()->get(QStringLiteral("actionPause"), true); + bool mute = config()->get(QStringLiteral("actionMute"), false); m_ui->check_pause->setChecked(pause); m_ui->check_mute->setChecked(mute); Q_EMIT changed(false); } void PauseMusicConfig::save() { - config()->set("conditionTalking", m_ui->rad_talking->isChecked()); - config()->set("actionPause", m_ui->check_pause->isChecked()); - config()->set("actionMute", m_ui->check_mute->isChecked()); + config()->set(QStringLiteral("conditionTalking"), m_ui->rad_talking->isChecked()); + config()->set(QStringLiteral("actionPause"), m_ui->check_pause->isChecked()); + config()->set(QStringLiteral("actionMute"), m_ui->check_mute->isChecked()); KCModule::save(); Q_EMIT changed(false); } #include "pausemusic_config.moc" diff --git a/plugins/pausemusic/pausemusic_config.h b/plugins/pausemusic/pausemusic_config.h index 180ebaf2..4140dd63 100644 --- a/plugins/pausemusic/pausemusic_config.h +++ b/plugins/pausemusic/pausemusic_config.h @@ -1,48 +1,48 @@ /** * 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 PAUSEMUSIC_CONFIG_H #define PAUSEMUSIC_CONFIG_H #include "kcmplugin/kdeconnectpluginkcm.h" namespace Ui { class PauseMusicConfigUi; } class PauseMusicConfig : public KdeConnectPluginKcm { Q_OBJECT public: - PauseMusicConfig(QWidget *parent, const QVariantList&); - virtual ~PauseMusicConfig(); + PauseMusicConfig(QWidget* parent, const QVariantList&); + ~PauseMusicConfig() override; public Q_SLOTS: - virtual void save(); - virtual void load(); - virtual void defaults(); + void save() override; + void load() override; + void defaults() override; private: Ui::PauseMusicConfigUi* m_ui; }; #endif diff --git a/plugins/pausemusic/pausemusicplugin.cpp b/plugins/pausemusic/pausemusicplugin.cpp index 85ef74e0..80be4195 100644 --- a/plugins/pausemusic/pausemusicplugin.cpp +++ b/plugins/pausemusic/pausemusicplugin.cpp @@ -1,141 +1,114 @@ /** * 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 K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_pausemusic.json", registerPlugin< PauseMusicPlugin >(); ) -//TODO: Port this away from KMix to use only Pulseaudio -int PauseMusicPlugin::isKMixMuted() { - QDBusInterface kmixInterface("org.kde.kmix", "/Mixers", "org.kde.KMix.MixSet"); - QString mixer = kmixInterface.property("currentMasterMixer").toString(); - QString control = kmixInterface.property("currentMasterControl").toString(); - - if (mixer.isEmpty() || control.isEmpty()) - return -1; - - mixer.replace(':','_'); - mixer.replace('.','_'); - mixer.replace('-','_'); - control.replace(':','_'); - control.replace('.','_'); - control.replace('-','_'); - - QDBusInterface mixerInterface("org.kde.kmix", "/Mixers/"+mixer+"/"+control, "org.kde.KMix.Control"); - if (mixerInterface.property("mute").toBool()) return 1; - return (mixerInterface.property("volume").toInt() == 0); -} +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PAUSEMUSIC, "kdeconnect.plugin.pausemusic") PauseMusicPlugin::PauseMusicPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , muted(false) -{ - QDBusInterface kmixInterface("org.kde.kmix", "/kmix/KMixWindow/actions/mute", "org.qtproject.Qt.QAction"); -} +{} bool PauseMusicPlugin::receivePackage(const NetworkPackage& np) { - bool pauseOnlyWhenTalking = config()->get("conditionTalking", false); + bool pauseOnlyWhenTalking = config()->get(QStringLiteral("conditionTalking"), false); if (pauseOnlyWhenTalking) { - if (np.get("event") != "talking") { + if (np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } else { //Pause as soon as it rings - if (np.get("event") != "ringing" && np.get("event") != "talking") { + if (np.get(QStringLiteral("event")) != QLatin1String("ringing") && np.get(QStringLiteral("event")) != QLatin1String("talking")) { return true; } } - bool pauseConditionFulfilled = !np.get("isCancel"); + bool pauseConditionFulfilled = !np.get(QStringLiteral("isCancel")); - bool pause = config()->get("actionPause", true); - bool mute = config()->get("actionMute", false); + bool pause = config()->get(QStringLiteral("actionPause"), true); + bool mute = config()->get(QStringLiteral("actionMute"), false); if (pauseConditionFulfilled) { if (mute) { - QDBusInterface kmixInterface("org.kde.kmix", "/kmix/KMixWindow/actions/mute", "org.qtproject.Qt.QAction"); - if (isKMixMuted() == 0) { - muted = true; - kmixInterface.call("trigger"); - } + 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 - QStringList interfaces = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); - Q_FOREACH (const QString& iface, interfaces) { - if (iface.startsWith("org.mpris.MediaPlayer2")) { - QDBusInterface mprisInterface(iface, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player"); + 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 == "Playing") { + if (status == QLatin1String("Playing")) { if (!pausedSources.contains(iface)) { pausedSources.insert(iface); if (mprisInterface.property("CanPause").toBool()) { - mprisInterface.asyncCall("Pause"); + mprisInterface.asyncCall(QStringLiteral("Pause")); } else { - mprisInterface.asyncCall("Stop"); + mprisInterface.asyncCall(QStringLiteral("Stop")); } } } } } } } else { if (mute && muted) { - QDBusInterface kmixInterface("org.kde.kmix", "/kmix/KMixWindow/actions/mute", "org.qtproject.Qt.QAction"); - if (isKMixMuted() > 0) { - kmixInterface.call("trigger"); - } - muted = false; + + qCDebug(KDECONNECT_PLUGIN_PAUSEMUSIC) << "Unmuting system volume"; + QProcess::startDetached("pactl set-sink-mute @DEFAULT_SINK@ 0"); + + muted = false; } if (pause && !pausedSources.empty()) { - Q_FOREACH (const QString& iface, pausedSources) { - QDBusInterface mprisInterface(iface, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player"); - //Calling play does not work for Spotify - //mprisInterface->call(QDBus::Block,"Play"); - //Workaround: Using playpause instead (checking first if it is already playing) - QString status = mprisInterface.property("PlaybackStatus").toString(); - mprisInterface.asyncCall("PlayPause"); - //End of workaround + 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/pausemusic/pausemusicplugin.h b/plugins/pausemusic/pausemusicplugin.h index 76c08c02..3005e32f 100644 --- a/plugins/pausemusic/pausemusicplugin.h +++ b/plugins/pausemusic/pausemusicplugin.h @@ -1,53 +1,51 @@ /** * 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 PAUSEMUSICPLUGIN_H #define PAUSEMUSICPLUGIN_H #include #include #include #include +#include + +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PAUSEMUSIC) + class PauseMusicPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit PauseMusicPlugin(QObject *parent, const QVariantList &args); - -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } + explicit PauseMusicPlugin(QObject* parent, const QVariantList& args); - /** - * @returns 0 if not muted, 1 if muted or -1 if there was an error - */ - int isKMixMuted(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override { } private: QSet pausedSources; bool muted; }; #endif diff --git a/plugins/ping/kdeconnect_ping.json b/plugins/ping/kdeconnect_ping.json index 43c83919..929d3878 100644 --- a/plugins/ping/kdeconnect_ping.json +++ b/plugins/ping/kdeconnect_ping.json @@ -1,66 +1,78 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Send and receive pings", - "Description[ca]": "Envia i rep pings", - "Description[cs]": "Posílat a přijímat ping", - "Description[de]": "Senden und Empfangen von Pings", - "Description[en_GB]": "Send and receive pings", - "Description[es]": "Enviar y recibir pings", - "Description[fi]": "Lähetä ja vastaanota tiedustelupaketteja", - "Description[gl]": "Enviar e recibir pings.", - "Description[hu]": "Pingek küldése és fogadása", - "Description[it]": "Invia e ricevi ping", - "Description[nl]": "Pings verzenden en ontvangen", - "Description[pl]": "Wysyłaj i otrzymuj pingi", - "Description[pt]": "Enviar e receber contactos", - "Description[pt_BR]": "Envia e recebe pings", - "Description[sk]": "Poslať a prijať pingy", - "Description[sv]": "Skicka och ta emot ping", - "Description[tr]": "Ping gönder ve al", - "Description[uk]": "Надсилання і отримання сигналів підтримання зв’язку", - "Description[x-test]": "xxSend and receive pingsxx", - "Description[zh_CN]": "发送和接受 ping", - "EnabledByDefault": true, - "Icon": "dialog-ok", - "Id": "kdeconnect_ping", - "License": "GPL", - "Name": "Ping", - "Name[ca]": "Ping", - "Name[cs]": "Ping", - "Name[de]": "Ping", - "Name[en_GB]": "Ping", - "Name[es]": "Ping", - "Name[fi]": "Tiedustelupaketti", - "Name[gl]": "Ping", - "Name[hu]": "Ping", - "Name[it]": "Ping", - "Name[nl]": "Ping", - "Name[pl]": "Ping", - "Name[pt]": "Contacto", - "Name[pt_BR]": "Ping", - "Name[sk]": "Ping", - "Name[sv]": "Pinga", - "Name[tr]": "Ping", - "Name[uk]": "Луна-імпульс", - "Name[x-test]": "xxPingxx", - "Name[zh_CN]": "Ping", + ], + "Description": "Send and receive pings", + "Description[ar]": "أرسل الوخزات واستقبلها", + "Description[ca@valencia]": "Envia i rep pings", + "Description[ca]": "Envia i rep pings", + "Description[cs]": "Posílat a přijímat ping", + "Description[da]": "Send og modtag ping", + "Description[de]": "Senden und Empfangen von Pings", + "Description[el]": "Αποστολή και λήψη pings", + "Description[es]": "Enviar y recibir pings", + "Description[et]": "Pingide saatmine ja vastuvõtmine", + "Description[eu]": "Bidali eta jaso pingak", + "Description[fi]": "Lähetä ja vastaanota tiedustelupaketteja", + "Description[fr]": "Envoie et reçoit des commandes « Ping »", + "Description[gl]": "Enviar e recibir pings.", + "Description[hu]": "Pingek küldése és fogadása", + "Description[it]": "Invia e ricevi ping", + "Description[ko]": "핑 보내고 받기", + "Description[nl]": "Pings verzenden en ontvangen", + "Description[nn]": "Send og ta imot pingsignal", + "Description[pl]": "Wysyłaj i otrzymuj pingi", + "Description[pt]": "Enviar e receber contactos", + "Description[pt_BR]": "Envia e recebe pings", + "Description[ru]": "Отправка и получение пингов", + "Description[sk]": "Poslať a prijať pingy", + "Description[sr@ijekavian]": "Шаље и прима пингове", + "Description[sr@ijekavianlatin]": "Šalje i prima pingove", + "Description[sr@latin]": "Šalje i prima pingove", + "Description[sr]": "Шаље и прима пингове", + "Description[sv]": "Skicka och ta emot ping", + "Description[tr]": "Ping gönder ve al", + "Description[uk]": "Надсилання і отримання сигналів підтримання зв’язку", + "Description[x-test]": "xxSend and receive pingsxx", + "Description[zh_CN]": "发送和接受 ping", + "Description[zh_TW]": "傳送與接收Ping回應封包", + "EnabledByDefault": true, + "Icon": "dialog-ok", + "Id": "kdeconnect_ping", + "License": "GPL", + "Name": "Ping", + "Name[ar]": "وخزة", + "Name[fi]": "Tiedustelupaketti", + "Name[fr]": "Commande « Ping »", + "Name[ko]": "핑", + "Name[pt]": "Contacto", + "Name[ru]": "Тестовый сигнал", + "Name[sr@ijekavian]": "Пинг", + "Name[sr]": "Пинг", + "Name[sv]": "Pinga", + "Name[uk]": "Луна-імпульс", + "Name[x-test]": "xxPingxx", + "Name[zh_TW]": "Ping回應封包", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ "kdeconnect.ping" - ], + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.ping" ] -} \ No newline at end of file +} diff --git a/plugins/ping/pingplugin.cpp b/plugins/ping/pingplugin.cpp index d090d59a..14b51271 100644 --- a/plugins/ping/pingplugin.cpp +++ b/plugins/ping/pingplugin.cpp @@ -1,88 +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 "pingplugin.h" #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_ping.json", registerPlugin< PingPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_PING, "kdeconnect.plugin.ping") PingPlugin::PingPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { // qCDebug(KDECONNECT_PLUGIN_PING) << "Ping plugin constructor for device" << device()->name(); } PingPlugin::~PingPlugin() { // qCDebug(KDECONNECT_PLUGIN_PING) << "Ping plugin destructor for device" << device()->name(); } bool PingPlugin::receivePackage(const NetworkPackage& np) { - KNotification* notification = new KNotification("pingReceived"); //KNotification::Persistent + KNotification* notification = new KNotification(QStringLiteral("pingReceived")); //KNotification::Persistent notification->setIconName(QStringLiteral("dialog-ok")); - notification->setComponentName("kdeconnect"); + notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(device()->name()); - notification->setText(np.get("message",i18n("Ping!"))); //This can be a source of spam + notification->setText(np.get(QStringLiteral("message"),i18n("Ping!"))); //This can be a source of spam notification->sendEvent(); return true; } void PingPlugin::sendPing() { NetworkPackage np(PACKAGE_TYPE_PING); bool success = sendPackage(np); qCDebug(KDECONNECT_PLUGIN_PING) << "sendPing:" << success; } void PingPlugin::sendPing(const QString& customMessage) { NetworkPackage np(PACKAGE_TYPE_PING); if (!customMessage.isEmpty()) { - np.set("message", customMessage); + np.set(QStringLiteral("message"), customMessage); } bool success = sendPackage(np); qCDebug(KDECONNECT_PLUGIN_PING) << "sendPing:" << success; } -void PingPlugin::connected() -{ - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); -} - QString PingPlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/ping"; } #include "pingplugin.moc" diff --git a/plugins/ping/pingplugin.h b/plugins/ping/pingplugin.h index 3217dcda..fb236223 100644 --- a/plugins/ping/pingplugin.h +++ b/plugins/ping/pingplugin.h @@ -1,51 +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 PINGPLUGIN_H #define PINGPLUGIN_H #include #include -#define PACKAGE_TYPE_PING QLatin1String("kdeconnect.ping") +#define PACKAGE_TYPE_PING QStringLiteral("kdeconnect.ping") class Q_DECL_EXPORT PingPlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.ping") public: - explicit PingPlugin(QObject *parent, const QVariantList &args); - virtual ~PingPlugin(); + explicit PingPlugin(QObject* parent, const QVariantList& args); + ~PingPlugin() override; Q_SCRIPTABLE void sendPing(); Q_SCRIPTABLE void sendPing(const QString& customMessage); -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override {} -private: - QString dbusPath() const; + QString dbusPath() const override; }; #endif diff --git a/plugins/remotecommands/CMakeLists.txt b/plugins/remotecommands/CMakeLists.txt new file mode 100644 index 00000000..334cf445 --- /dev/null +++ b/plugins/remotecommands/CMakeLists.txt @@ -0,0 +1,8 @@ +kdeconnect_add_plugin(kdeconnect_remotecommands JSON kdeconnect_remotecommands.json + SOURCES remotecommandsplugin.cpp +) + +target_link_libraries(kdeconnect_remotecommands + kdeconnectcore + Qt5::DBus + KF5::I18n) diff --git a/plugins/remotecommands/kdeconnect_remotecommands.json b/plugins/remotecommands/kdeconnect_remotecommands.json new file mode 100644 index 00000000..c5b9c6c6 --- /dev/null +++ b/plugins/remotecommands/kdeconnect_remotecommands.json @@ -0,0 +1,94 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Authors": [ + { + "Email": "aleixpol@kde.org", + "Name": "Aleix Pol", + "Name[sr@ijekavian]": "Алекс Пол Гонзалез", + "Name[sr@ijekavianlatin]": "Aleks Pol Gonzalez", + "Name[sr@latin]": "Aleks Pol Gonzalez", + "Name[sr]": "Алекс Пол Гонзалез", + "Name[x-test]": "xxAleix Polxx" + } + ], + "Description": "Trigger commands predefined on the remote device", + "Description[ar]": "نفّذ أوامر معرّفة مسبقًا في الجهاز البعيد", + "Description[ca@valencia]": "Activa ordes predefinides en el dispositiu remot", + "Description[ca]": "Activa ordres predefinides en el dispositiu remot", + "Description[cs]": "Spouštět příkazy předpřipravené na vzdáleném zařízení", + "Description[da]": "Udløs kommandoer som er prædefinerede på den eksterne enhed", + "Description[de]": "Vordefinierte Befehle auf dem entfernten Gerät ausführen", + "Description[el]": "Ενεργοποίηση προκαθορισμένων εντολών στην απομακρυσμένη συσκευή", + "Description[es]": "Desencadenar órdenes predefinidas en el dispositivo remoto", + "Description[et]": "Kaugseadmes määratud käskude käivitamine", + "Description[eu]": "Exekutatu urruneko gailuan aurretiaz zehaztutako komandoak", + "Description[fi]": "Suorita ennakkoon määriteltyjä komentoja etälaitteella", + "Description[fr]": "Exécuter des commandes prédéfinies sur le périphérique distant", + "Description[gl]": "Provocar ordes predefinidas no dispositivo remoto.", + "Description[it]": "Innesca comandi predefiniti sul dispositivo remoto", + "Description[ko]": "원격 장치에 미리 정의한 트리거 명령", + "Description[nl]": "Start commando's voorgedefinieerd op het apparaat op afstand", + "Description[nn]": "Løys ut ferdigdefinerte kommandoar på eininga", + "Description[pl]": "Wyzwalaj polecenia określone na zdalnym urządzeniu", + "Description[pt]": "Despoletar os comandos predefinidos para o dispositivo remoto", + "Description[pt_BR]": "Ativa comandos predefinidos no dispositivo remoto", + "Description[ru]": "Вызов команд, предопределённых на удалённом устройстве", + "Description[sk]": "Vyvolať príkazy preddefinované na vzdialenom zariadení", + "Description[sr@ijekavian]": "Окида наредбе предефинисане на удаљеном уређају", + "Description[sr@ijekavianlatin]": "Okida naredbe predefinisane na udaljenom uređaju", + "Description[sr@latin]": "Okida naredbe predefinisane na udaljenom uređaju", + "Description[sr]": "Окида наредбе предефинисане на удаљеном уређају", + "Description[sv]": "Utför kommandon fördefinierade på den andra apparaten", + "Description[tr]": "Uzak aygıta önceden tanımlanmış komutları tetikleyin", + "Description[uk]": "Запуск команд, попередньо визначених на віддаленому пристрої", + "Description[x-test]": "xxTrigger commands predefined on the remote devicexx", + "Description[zh_CN]": "出发远程设备的预设命令", + "Description[zh_TW]": "預先設定在遠端設備上的執行命令", + "EnabledByDefault": true, + "Icon": "system-run", + "Id": "kdeconnect_remotecommands", + "License": "GPL", + "Name": "Host remote commands", + "Name[ar]": "استضف الأوامر البعيدة", + "Name[ca@valencia]": "Allotja ordes remotes", + "Name[ca]": "Allotja ordres remotes", + "Name[cs]": "Přijímat vzdálené příkazy", + "Name[da]": "Vært for eksterne kommandoer", + "Name[de]": "Entfernte Rechner-Befehle", + "Name[el]": "Απομακρυσμένη εκτέλεση εντολών", + "Name[es]": "Órdenes en servidor remoto", + "Name[et]": "Kaugkäsud", + "Name[eu]": "Ostalariaren urruneko komandoak", + "Name[fi]": "Etäkomennot", + "Name[fr]": "Exécuter des commandes distantes", + "Name[gl]": "Ordes remotas do servidor", + "Name[it]": "Comandi remoti host", + "Name[ko]": "호스트 원격 명령", + "Name[nl]": "Host voor commando's op afstand", + "Name[nn]": "Fjernkommandoar", + "Name[pl]": "Wykonuj polecenia zdalne", + "Name[pt]": "Alojar os comandos remotos", + "Name[ru]": "Выполнение команд на устройстве", + "Name[sr@ijekavian]": "Даљинске наредбе домаћина", + "Name[sr@ijekavianlatin]": "Daljinske naredbe domaćina", + "Name[sr@latin]": "Daljinske naredbe domaćina", + "Name[sr]": "Даљинске наредбе домаћина", + "Name[sv]": "Fjärrkommandon för värddator", + "Name[tr]": "Sunucu uzaktan komutları", + "Name[uk]": "Віддалені команди вузлу", + "Name[x-test]": "xxHost remote commandsxx", + "Name[zh_CN]": "主机远程命令", + "Name[zh_TW]": "主機與遠端指令", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1" + }, + "X-KdeConnect-OutgoingPackageType": [ + "kdeconnect.runcommand.request" + ], + "X-KdeConnect-SupportedPackageType": [ + "kdeconnect.runcommand" + ] +} diff --git a/plugins/remotecommands/remotecommandsplugin.cpp b/plugins/remotecommands/remotecommandsplugin.cpp new file mode 100644 index 00000000..0d24a59d --- /dev/null +++ b/plugins/remotecommands/remotecommandsplugin.cpp @@ -0,0 +1,85 @@ +/** + * Copyright 2016 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "remotecommandsplugin.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PACKAGE_TYPE_RUNCOMMAND_REQUEST QLatin1String("kdeconnect.runcommand.request") + +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_remotecommands.json", registerPlugin< RemoteCommandsPlugin >(); ) + +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTECOMMANDS, "kdeconnect.plugin.remotecommands") + +RemoteCommandsPlugin::RemoteCommandsPlugin(QObject* parent, const QVariantList& args) + : KdeConnectPlugin(parent, args) + , m_commands("{}") +{ +} + +RemoteCommandsPlugin::~RemoteCommandsPlugin() = default; + +bool RemoteCommandsPlugin::receivePackage(const NetworkPackage& np) +{ + if (np.has(QStringLiteral("commandList"))) { + setCommands(np.get(QStringLiteral("commandList"))); + return true; + } + + return false; +} + +void RemoteCommandsPlugin::connected() +{ + NetworkPackage np(PACKAGE_TYPE_RUNCOMMAND_REQUEST, {{"requestCommandList", true}}); + sendPackage(np); +} + +QString RemoteCommandsPlugin::dbusPath() const +{ + return "/modules/kdeconnect/devices/" + device()->id() + "/remotecommands"; +} + +void RemoteCommandsPlugin::setCommands(const QByteArray& cmds) +{ + if (m_commands != cmds) { + m_commands = cmds; + Q_EMIT commandsChanged(m_commands); + } +} + +void RemoteCommandsPlugin::triggerCommand(const QString& key) +{ + NetworkPackage np(PACKAGE_TYPE_RUNCOMMAND_REQUEST, {{ "key", key }}); + sendPackage(np); +} + +#include "remotecommandsplugin.moc" diff --git a/plugins/remotecontrol/remotecontrolplugin.h b/plugins/remotecommands/remotecommandsplugin.h similarity index 57% copy from plugins/remotecontrol/remotecontrolplugin.h copy to plugins/remotecommands/remotecommandsplugin.h index 0c2fbc96..d592c76b 100644 --- a/plugins/remotecontrol/remotecontrolplugin.h +++ b/plugins/remotecommands/remotecommandsplugin.h @@ -1,50 +1,60 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2016 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 . */ -#ifndef MPRISREMOTEPLUGIN_H -#define MPRISREMOTEPLUGIN_H +#ifndef REMOTECOMMANDSPLUGIN_H +#define REMOTECOMMANDSPLUGIN_H #include #include +#include +#include +#include +#include +#include -#define PACKAGE_TYPE_MOUSEPAD QLatin1String("kdeconnect.mousepad") - -class Q_DECL_EXPORT RemoteControlPlugin +class Q_DECL_EXPORT RemoteCommandsPlugin : public KdeConnectPlugin { Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecontrol") + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecommands") + Q_PROPERTY(QByteArray commands READ commands NOTIFY commandsChanged) public: - explicit RemoteControlPlugin(QObject *parent, const QVariantList &args); - ~RemoteControlPlugin() override; + explicit RemoteCommandsPlugin(QObject* parent, const QVariantList& args); + ~RemoteCommandsPlugin() override; + + Q_SCRIPTABLE void triggerCommand(const QString& key); + QByteArray commands() const { return m_commands; } - bool receivePackage(const NetworkPackage& /*np*/) override { return false; } + bool receivePackage(const NetworkPackage& np) override; void connected() override; + QString dbusPath() const override; - Q_INVOKABLE void moveCursor(const QPoint &p); - Q_INVOKABLE void sendCommand(const QString &name, bool val); +Q_SIGNALS: + void commandsChanged(const QByteArray& commands); private: - QString dbusPath() const; + void setCommands(const QByteArray& commands); + + QByteArray m_commands; }; #endif diff --git a/plugins/remotecontrol/kdeconnect_remotecontrol.json b/plugins/remotecontrol/kdeconnect_remotecontrol.json index 53f3ecfc..ea021adc 100644 --- a/plugins/remotecontrol/kdeconnect_remotecontrol.json +++ b/plugins/remotecontrol/kdeconnect_remotecontrol.json @@ -1,46 +1,92 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "aleixpol@kde.org", - "Name": "Aleix Pol" + "Email": "aleixpol@kde.org", + "Name": "Aleix Pol", + "Name[sr@ijekavian]": "Алекс Пол Гонзалез", + "Name[sr@ijekavianlatin]": "Aleks Pol Gonzalez", + "Name[sr@latin]": "Aleks Pol Gonzalez", + "Name[sr]": "Алекс Пол Гонзалез", + "Name[x-test]": "xxAleix Polxx" } - ], - "Description": "Control Remote systems", - "Description[ca]": "Sistemes de control remot", - "Description[es]": "Controlar sistemas remotos", - "Description[gl]": "Controla sistemas remotos.", - "Description[nl]": "Systemen op afstand bedienen", - "Description[pl]": "Sterowanie zdalnymi systemami", - "Description[pt]": "Comandar sistemas à distância", - "Description[pt_BR]": "Controle remoto de sistemas", - "Description[sv]": "Styr fjärrsystem", - "Description[uk]": "Системи віддаленого керування", - "Description[x-test]": "xxControl Remote systemsxx", - "EnabledByDefault": true, - "Icon": "applications-multimedia", - "Id": "kdeconnect_remotecontrol", - "License": "GPL", - "Name": "RemoteControl", - "Name[ca]": "Control remot", - "Name[es]": "Control remoto", - "Name[gl]": "Mando a distancia", - "Name[nl]": "Afstandsbediening", - "Name[pl]": "ZdalneSterowanie", - "Name[pt]": "Comando à Distância", - "Name[pt_BR]": "Controle remoto", - "Name[sv]": "Fjärrkontroll", - "Name[uk]": "RemoteControl", - "Name[x-test]": "xxRemoteControlxx", + ], + "Description": "Control Remote systems", + "Description[ar]": "تحكّم بالأنظمة البعيدة", + "Description[ca@valencia]": "Sistemes de control remot", + "Description[ca]": "Sistemes de control remot", + "Description[cs]": "Ovládejte vzdálené systémy", + "Description[da]": "Kontrollér eksterne systemer", + "Description[de]": "Entfernte Systeme steuern", + "Description[el]": "Έλεγχος ανταπόκρισης απομακρυσμένων συστημάτων", + "Description[es]": "Controlar sistemas remotos", + "Description[et]": "Kaugjuhtimissüsteemid", + "Description[eu]": "Kontrolatu urruneko sistemak", + "Description[fi]": "Ohjaa järjestelmiä etänä", + "Description[fr]": "Contrôlez les systèmes distants", + "Description[gl]": "Controla sistemas remotos.", + "Description[it]": "Controlla i sistemi remoti", + "Description[ko]": "원격 시스템 제어", + "Description[nl]": "Systemen op afstand bedienen", + "Description[nn]": "Fjernkontroller system", + "Description[pl]": "Sterowanie zdalnymi systemami", + "Description[pt]": "Comandar sistemas à distância", + "Description[pt_BR]": "Controle remoto de sistemas", + "Description[ru]": "Управление удалёнными системами", + "Description[sk]": "Ovládať vzdialené systémy", + "Description[sr@ijekavian]": "Контролише удаљене системе", + "Description[sr@ijekavianlatin]": "Kontroliše udaljene sisteme", + "Description[sr@latin]": "Kontroliše udaljene sisteme", + "Description[sr]": "Контролише удаљене системе", + "Description[sv]": "Styr externt system", + "Description[tr]": "Uzak Sistemleri Kontrol Et", + "Description[uk]": "Системи віддаленого керування", + "Description[x-test]": "xxControl Remote systemsxx", + "Description[zh_CN]": "控制远程系统", + "Description[zh_TW]": "控制遠端系統", + "EnabledByDefault": true, + "Icon": "applications-multimedia", + "Id": "kdeconnect_remotecontrol", + "License": "GPL", + "Name": "RemoteControl", + "Name[ar]": "التّحكّم عن بعد", + "Name[ca@valencia]": "Control remot", + "Name[ca]": "Control remot", + "Name[cs]": "Dálkové ovládání", + "Name[da]": "FjernBetjening", + "Name[de]": "Fernsteuerung", + "Name[es]": "Control remoto", + "Name[et]": "Kaugjuhtimine", + "Name[eu]": "UrrunekoKontrola", + "Name[fi]": "Kauko-ohjain", + "Name[gl]": "Mando a distancia", + "Name[it]": "Telecomando", + "Name[ko]": "원격 제어", + "Name[nl]": "Afstandsbediening", + "Name[nn]": "Fjernkontroll", + "Name[pl]": "ZdalneSterowanie", + "Name[pt]": "Comando à Distância", + "Name[pt_BR]": "Controle remoto", + "Name[ru]": "Удалённое управление", + "Name[sk]": "Diaľkové ovládanie", + "Name[sr@ijekavian]": "Удаљене контроле", + "Name[sr@ijekavianlatin]": "Udaljene kontrole", + "Name[sr@latin]": "Udaljene kontrole", + "Name[sr]": "Удаљене контроле", + "Name[sv]": "Fjärrkontroll", + "Name[tr]": "UzaktanKumanda", + "Name[x-test]": "xxRemoteControlxx", + "Name[zh_CN]": "远程控制", + "Name[zh_TW]": "遠端控制", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "https://kde.org" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.mousepad" - ], + "kdeconnect.mousepad.request" + ], "X-KdeConnect-SupportedPackageType": [] -} \ No newline at end of file +} diff --git a/plugins/remotecontrol/remotecontrolplugin.cpp b/plugins/remotecontrol/remotecontrolplugin.cpp index 935f3b93..96c6ce79 100644 --- a/plugins/remotecontrol/remotecontrolplugin.cpp +++ b/plugins/remotecontrol/remotecontrolplugin.cpp @@ -1,70 +1,65 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "remotecontrolplugin.h" #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_remotecontrol.json", registerPlugin< RemoteControlPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTECONTROL, "kdeconnect.plugin.remotecontrol") -RemoteControlPlugin::RemoteControlPlugin(QObject *parent, const QVariantList &args) +RemoteControlPlugin::RemoteControlPlugin(QObject* parent, const QVariantList &args) : KdeConnectPlugin(parent, args) { } RemoteControlPlugin::~RemoteControlPlugin() {} void RemoteControlPlugin::moveCursor(const QPoint &p) { - NetworkPackage np(PACKAGE_TYPE_MOUSEPAD); - np.set("dx", p.x()); - np.set("dy", p.y()); + NetworkPackage np(PACKAGE_TYPE_MOUSEPAD_REQUEST, { + {"dx", p.x()}, + {"dy", p.y()} + }); sendPackage(np); } void RemoteControlPlugin::sendCommand(const QString &name, bool val) { - NetworkPackage np(PACKAGE_TYPE_MOUSEPAD); - np.set(name, val); + NetworkPackage np(PACKAGE_TYPE_MOUSEPAD_REQUEST, {{name, val}}); sendPackage(np); } -void RemoteControlPlugin::connected() -{ - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); -} - QString RemoteControlPlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/remotecontrol"; } #include "remotecontrolplugin.moc" diff --git a/plugins/remotecontrol/remotecontrolplugin.h b/plugins/remotecontrol/remotecontrolplugin.h index 0c2fbc96..40c01a87 100644 --- a/plugins/remotecontrol/remotecontrolplugin.h +++ b/plugins/remotecontrol/remotecontrolplugin.h @@ -1,50 +1,48 @@ /** - * Copyright 2013 Albert Vaca + * 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 . */ -#ifndef MPRISREMOTEPLUGIN_H -#define MPRISREMOTEPLUGIN_H +#ifndef REMOTECONTROLPLUGIN_H +#define REMOTECONTROLPLUGIN_H #include #include -#define PACKAGE_TYPE_MOUSEPAD QLatin1String("kdeconnect.mousepad") +#define PACKAGE_TYPE_MOUSEPAD_REQUEST QStringLiteral("kdeconnect.mousepad.request") class Q_DECL_EXPORT RemoteControlPlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotecontrol") public: - explicit RemoteControlPlugin(QObject *parent, const QVariantList &args); + explicit RemoteControlPlugin(QObject* parent, const QVariantList &args); ~RemoteControlPlugin() override; bool receivePackage(const NetworkPackage& /*np*/) override { return false; } - void connected() override; + void connected() override {} + QString dbusPath() const override; - Q_INVOKABLE void moveCursor(const QPoint &p); - Q_INVOKABLE void sendCommand(const QString &name, bool val); - -private: - QString dbusPath() const; + Q_SCRIPTABLE void moveCursor(const QPoint &p); + Q_SCRIPTABLE void sendCommand(const QString &name, bool val); }; #endif diff --git a/plugins/remotekeyboard/CMakeLists.txt b/plugins/remotekeyboard/CMakeLists.txt new file mode 100644 index 00000000..95ad9833 --- /dev/null +++ b/plugins/remotekeyboard/CMakeLists.txt @@ -0,0 +1,8 @@ +kdeconnect_add_plugin(kdeconnect_remotekeyboard JSON kdeconnect_remotekeyboard.json + SOURCES remotekeyboardplugin.cpp) + +target_link_libraries(kdeconnect_remotekeyboard + kdeconnectcore + KF5::I18n + Qt5::DBus +) diff --git a/plugins/remotekeyboard/README b/plugins/remotekeyboard/README new file mode 100644 index 00000000..b3016aca --- /dev/null +++ b/plugins/remotekeyboard/README @@ -0,0 +1,23 @@ +Sends key-events to remote devices. The payload structure corresponds basically +to that of remote key-presses in the mousepad-plugin (with the exception of the +"sendAck"-flag) , e.g.: + +{ + "key": "a", + "specialKey": 12, + "shift": false, + "ctrl": false, + "alt": false, + "sendAck": true +} + +If "specialKey" is a valid keycode according to the internal map (1 <= x <= 32), +the event is interpreted as a special event and the contents of "key" are not +considered. + +"key" may contain multi-char strings for performance reasons. In that case, +the peer is expected to print the whole string. + +If "sendAck" is set to true, the device expects the remote peer to echo the +event in case it could be handled. This can be used to determine whether the +remote device is ready to accept remote keypresses. diff --git a/plugins/remotekeyboard/kdeconnect_remotekeyboard.json b/plugins/remotekeyboard/kdeconnect_remotekeyboard.json new file mode 100644 index 00000000..7a6d016e --- /dev/null +++ b/plugins/remotekeyboard/kdeconnect_remotekeyboard.json @@ -0,0 +1,85 @@ +{ + "Encoding": "UTF-8", + "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": "Use your keyboard to send key-events to your paired device", + "Description[ca@valencia]": "Useu el vostre teclat per enviar esdeveniments de tecla al dispositiu enllaçat", + "Description[ca]": "Useu el vostre teclat per enviar esdeveniments de tecla al dispositiu enllaçat", + "Description[cs]": "Používejte svoji klávesnici pro odesílání událostí kláves na spárované zařízení", + "Description[da]": "Brug dit tastatur til at sende tastehændelser til din parrede enhed", + "Description[de]": "Benutzen Sie Ihre Tastatur, um Tastatureingaben an das angeschlossene Gerät zu senden", + "Description[el]": "Στέλνεις ό,τι πληκτρολογείς στη συσκευή ζεύγους", + "Description[es]": "Use su teclado para enviar eventos de teclado a su dispositivo vinculado", + "Description[eu]": "Erabili zure teklatua tekla-gertaerak zure parekatutako gailura bidaltzeko", + "Description[fr]": "Utiliser votre clavier pour envoyer des évènements de touche au périphérique associé", + "Description[gl]": "Usar o teclado para enviar eventos de tecla ao dispositivo emparellado.", + "Description[it]": "Utilizza la tua tastiera per inviare eventi di pressione dei tasti al dispositivo associato", + "Description[nl]": "Uw toetsenbord gebruiken om toetsgebeurtenissen naar uw gepaarde apparaat te verzenden", + "Description[nn]": "Bruk tastaturet til å senda tastetrykk til den para eininga", + "Description[pl]": "Użyj swojej klawiatury do wysyłania naciśnięć klawiszy do swojego sparowanego urządzenia", + "Description[pt]": "Usar o seu teclado para enviar eventos de teclas para o seu dispositivo emparelhado", + "Description[pt_BR]": "Use seu teclado para enviar eventos chace ao seu dispositivo pareado", + "Description[ru]": "Используйте клавиатуру для отправки нажатий клавиш на сопряжённое устройство", + "Description[sr@ijekavian]": "Користите тастатуру за куцање на упареном уређају", + "Description[sr@ijekavianlatin]": "Koristite tastaturu za kucanje na uparenom uređaju", + "Description[sr@latin]": "Koristite tastaturu za kucanje na uparenom uređaju", + "Description[sr]": "Користите тастатуру за куцање на упареном уређају", + "Description[sv]": "Använd tangentbordet för att skicka tangenthändelser till den parade enheten", + "Description[tr]": "Eşleşmiş cihazınıza önemli etkinlikler göndermek için klavyenizi kullanın", + "Description[uk]": "Скористайтеся вашою клавіатурою для надсилання подій натискання клавіш на пов’язаний пристрій", + "Description[x-test]": "xxUse your keyboard to send key-events to your paired devicexx", + "Description[zh_CN]": "使用您的键盘发送按键事件给配对的设备", + "EnabledByDefault": true, + "Icon": "edit-select", + "Id": "kdeconnect_remotekeyboard", + "License": "GPL", + "Name": "Remote keyboard from the desktop", + "Name[ca@valencia]": "Teclat remot des de l'escriptori", + "Name[ca]": "Teclat remot des de l'escriptori", + "Name[cs]": "Vzdálená klávesnice pro plochu", + "Name[da]": "Eksternt tastatur fra desktoppen", + "Name[de]": "Tastatureingaben von der Arbeitsfläche", + "Name[el]": "Απομακρυσμένο πληκτρολόγιο της επιφάνειας εργασίας", + "Name[es]": "Teclado remoto desde el equipo de escritorio", + "Name[eu]": "Urruneko teklatua mahaigainetik", + "Name[fr]": "Clavier distant depuis le bureau", + "Name[gl]": "Teclado remoto do escritorio", + "Name[it]": "Tastiera remota dal desktop", + "Name[nl]": "Toetsenbord op afstand vanaf het bureaublad", + "Name[nn]": "Fjerntastatur frå skrivebordet", + "Name[pl]": "Zdalna klawiatura z pulpitu", + "Name[pt]": "Teclado remoto do ambiente de trabalho", + "Name[pt_BR]": "Teclado remoto a partir da área de trabalho", + "Name[ru]": "Удалённая клавиатура с компьютера", + "Name[sr@ijekavian]": "Даљинска тастатура са радне површи", + "Name[sr@ijekavianlatin]": "Daljinska tastatura sa radne površi", + "Name[sr@latin]": "Daljinska tastatura sa radne površi", + "Name[sr]": "Даљинска тастатура са радне површи", + "Name[sv]": "Externt tangentbord från skrivbordet", + "Name[tr]": "Masaüstünden uzak klavye", + "Name[uk]": "Віддалена клавіатура з комп’ютера", + "Name[x-test]": "xxRemote keyboard from the desktopxx", + "Name[zh_CN]": "来自桌面的远程键盘", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1" + }, + "X-KdeConnect-OutgoingPackageType": [ + "kdeconnect.mousepad.request" + ], + "X-KdeConnect-SupportedPackageType": [ + "kdeconnect.mousepad.echo", + "kdeconnect.mousepad.keyboardstate" + ] +} diff --git a/plugins/remotekeyboard/remotekeyboardplugin.cpp b/plugins/remotekeyboard/remotekeyboardplugin.cpp new file mode 100644 index 00000000..a701d91a --- /dev/null +++ b/plugins/remotekeyboard/remotekeyboardplugin.cpp @@ -0,0 +1,149 @@ +/** + * Copyright 2017 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 "remotekeyboardplugin.h" +#include +#include +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_remotekeyboard.json", registerPlugin< RemoteKeyboardPlugin >(); ) + +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTEKEYBOARD, "kdeconnect.plugin.remotekeyboard"); + +// Mapping of Qt::Key to internal codes, corresponds to the mapping in mousepadplugin +QMap specialKeysMap = { + //0, // Invalid + {Qt::Key_Backspace, 1}, + {Qt::Key_Tab, 2}, + //XK_Linefeed, // 3 + {Qt::Key_Left, 4}, + {Qt::Key_Up, 5}, + {Qt::Key_Right, 6}, + {Qt::Key_Down, 7}, + {Qt::Key_PageUp, 8}, + {Qt::Key_PageDown, 9}, + {Qt::Key_Home, 10}, + {Qt::Key_End, 11}, + {Qt::Key_Return, 12}, + {Qt::Key_Enter, 12}, + {Qt::Key_Delete, 13}, + {Qt::Key_Escape, 14}, + {Qt::Key_SysReq, 15}, + {Qt::Key_ScrollLock, 16}, + //0, // 17 + //0, // 18 + //0, // 19 + //0, // 20 + {Qt::Key_F1, 21}, + {Qt::Key_F2, 22}, + {Qt::Key_F3, 23}, + {Qt::Key_F4, 24}, + {Qt::Key_F5, 25}, + {Qt::Key_F6, 26}, + {Qt::Key_F7, 27}, + {Qt::Key_F8, 28}, + {Qt::Key_F9, 29}, + {Qt::Key_F10, 30}, + {Qt::Key_F11, 31}, + {Qt::Key_F12, 32}, +}; + +RemoteKeyboardPlugin::RemoteKeyboardPlugin(QObject* parent, const QVariantList& args) + : KdeConnectPlugin(parent, args) + , m_remoteState(false) +{ +} + +RemoteKeyboardPlugin::~RemoteKeyboardPlugin() +{ +} + +bool RemoteKeyboardPlugin::receivePackage(const NetworkPackage& np) +{ + if (np.type() == PACKAGE_TYPE_MOUSEPAD_ECHO) { + if (!np.has("isAck") || !np.has("key")) { + qCWarning(KDECONNECT_PLUGIN_REMOTEKEYBOARD) << "Invalid packet of type" + << PACKAGE_TYPE_MOUSEPAD_ECHO; + return false; + } + // qCWarning(KDECONNECT_PLUGIN_REMOTEKEYBOARD) << "Received keypress" << np; + Q_EMIT keyPressReceived(np.get("key"), + np.get("specialKey", 0), + np.get("shift", false), + np.get("ctrl", false), + np.get("alt", false)); + return true; + } else if (np.type() == PACKAGE_TYPE_MOUSEPAD_KEYBOARDSTATE) { +// qCWarning(KDECONNECT_PLUGIN_REMOTEKEYBOARD) << "Received keyboardstate" << np; + if (m_remoteState != np.get("state")) { + m_remoteState = np.get("state"); + Q_EMIT remoteStateChanged(m_remoteState); + } + return true; + } + return false; +} + +void RemoteKeyboardPlugin::sendKeyPress(const QString& key, int specialKey, + bool shift, bool ctrl, + bool alt, bool sendAck) const +{ + NetworkPackage np(PACKAGE_TYPE_MOUSEPAD_REQUEST, { + {"key", key}, + {"specialKey", specialKey}, + {"shift", shift}, + {"ctrl", ctrl}, + {"alt", alt}, + {"sendAck", sendAck} + }); + sendPackage(np); +} + +void RemoteKeyboardPlugin::sendQKeyEvent(const QVariantMap& keyEvent, bool sendAck) const +{ + if (!keyEvent.contains("key")) + return; + int k = translateQtKey(keyEvent.value("key").toInt()); + int modifiers = keyEvent.value("modifiers").toInt(); + sendKeyPress(keyEvent.value("text").toString(), k, + modifiers & Qt::ShiftModifier, + modifiers & Qt::ControlModifier, + modifiers & Qt::AltModifier, + sendAck); +} + +int RemoteKeyboardPlugin::translateQtKey(int qtKey) const +{ + return specialKeysMap.value(qtKey, 0); +} + +void RemoteKeyboardPlugin::connected() +{ +} + +QString RemoteKeyboardPlugin::dbusPath() const +{ + return "/modules/kdeconnect/devices/" + device()->id() + "/remotekeyboard"; +} + + +#include "remotekeyboardplugin.moc" diff --git a/plugins/remotekeyboard/remotekeyboardplugin.h b/plugins/remotekeyboard/remotekeyboardplugin.h new file mode 100644 index 00000000..28ac1db2 --- /dev/null +++ b/plugins/remotekeyboard/remotekeyboardplugin.h @@ -0,0 +1,73 @@ +/** + * Copyright 2017 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 . + */ + +#ifndef REMOTEKEYBOARDPLUGIN_H +#define REMOTEKEYBOARDPLUGIN_H + +#include +#include +#include +#include + +struct FakeKey; + +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_REMOTEKEYBOARD); + +#define PACKAGE_TYPE_MOUSEPAD_REQUEST QLatin1String("kdeconnect.mousepad.request") +#define PACKAGE_TYPE_MOUSEPAD_ECHO QLatin1String("kdeconnect.mousepad.echo") +#define PACKAGE_TYPE_MOUSEPAD_KEYBOARDSTATE QLatin1String("kdeconnect.mousepad.keyboardstate") + +class RemoteKeyboardPlugin + : public KdeConnectPlugin +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.remotekeyboard") + Q_PROPERTY(bool remoteState READ remoteState NOTIFY remoteStateChanged) + +private: + bool m_remoteState; + +public: + explicit RemoteKeyboardPlugin(QObject* parent, const QVariantList& args); + ~RemoteKeyboardPlugin() override; + + bool receivePackage(const NetworkPackage& np) override; + QString dbusPath() const override; + void connected() override; + + bool remoteState() const { + return m_remoteState; + } + + Q_SCRIPTABLE void sendKeyPress(const QString& key, int specialKey = 0, + bool shift = false, bool ctrl = false, + bool alt = false, bool sendAck = true) const; + Q_SCRIPTABLE void sendQKeyEvent(const QVariantMap& keyEvent, + bool sendAck = true) const; + Q_SCRIPTABLE int translateQtKey(int qtKey) const; + +Q_SIGNALS: + Q_SCRIPTABLE void keyPressReceived(const QString& key, int specialKey = 0, + bool shift = false, bool ctrl = false, + bool alt = false) const; + Q_SCRIPTABLE void remoteStateChanged(bool state) const; +}; + +#endif diff --git a/plugins/runcommand/CMakeLists.txt b/plugins/runcommand/CMakeLists.txt new file mode 100644 index 00000000..4a244cc2 --- /dev/null +++ b/plugins/runcommand/CMakeLists.txt @@ -0,0 +1,26 @@ +set(kdeconnect_runcommand_SRCS + runcommandplugin.cpp +) + +kdeconnect_add_plugin(kdeconnect_runcommand JSON kdeconnect_runcommand.json SOURCES ${kdeconnect_runcommand_SRCS}) + +target_link_libraries(kdeconnect_runcommand + kdeconnectcore + Qt5::DBus + KF5::I18n) + +#---------------------- + +set( kdeconnect_runcommand_config_SRCS runcommand_config.cpp ) + +add_library(kdeconnect_runcommand_config MODULE ${kdeconnect_runcommand_config_SRCS} ) +target_link_libraries( kdeconnect_runcommand_config + kdeconnectpluginkcm + KF5::I18n + KF5::CoreAddons + KF5::ConfigWidgets + +) + +install(TARGETS kdeconnect_runcommand_config DESTINATION ${PLUGIN_INSTALL_DIR} ) +install(FILES kdeconnect_runcommand_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/plugins/runcommand/kdeconnect_runcommand.json b/plugins/runcommand/kdeconnect_runcommand.json new file mode 100644 index 00000000..aab56251 --- /dev/null +++ b/plugins/runcommand/kdeconnect_runcommand.json @@ -0,0 +1,107 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Authors": [ + { + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" + }, + { + "Email": "david@davidedmundson.co.uk", + "Name": "David Edmundson", + "Name[sr@ijekavian]": "Дејвид Едмундсон", + "Name[sr@ijekavianlatin]": "Dejvid Edmundson", + "Name[sr@latin]": "Dejvid Edmundson", + "Name[sr]": "Дејвид Едмундсон", + "Name[x-test]": "xxDavid Edmundsonxx" + } + ], + "Description": "Execute console commands remotely", + "Description[ar]": "نفّذ أوامر طرفيّة عن بعد", + "Description[ca@valencia]": "Executa ordes de la consola de forma remota", + "Description[ca]": "Executa ordres de la consola de forma remota", + "Description[cs]": "Spouštět konzolové příkazy vzdáleně", + "Description[da]": "Kør konsolkommandoer udefra", + "Description[de]": "Entfernte Konsolenbefehle ausführen", + "Description[el]": "Απομακρυσμένη εκτέλεση εντολών του τερματικού", + "Description[es]": "Ejecute órdenes de consola remotamente", + "Description[et]": "Konsoolikäskude kaugkäivitamine", + "Description[eu]": "Exekutatu kontsolako komandoak urrunetik", + "Description[fi]": "Suorita konsolikomentoja etänä", + "Description[fr]": "Exécute des commandes console à distance", + "Description[gl]": "Executar ordes de consola remotamente.", + "Description[it]": "Esegui da remoto comandi della console", + "Description[ko]": "원격으로 콘솔 명령 실행", + "Description[nl]": "Commando's in de terminal op afstand laten uitvoeren", + "Description[nn]": "Køyr konsollkommandoar på eininga", + "Description[pl]": "Wykonaj polecenie konsoli zdalnie", + "Description[pt]": "Executar de forma remota comandos da consola", + "Description[pt_BR]": "Executa comandos do console remotamente", + "Description[ru]": "Выполнение консольных команд удалённо", + "Description[sk]": "Spustiť priamo príkazy konzoly", + "Description[sr@ijekavian]": "Даљински извршава наредбе у конзоли", + "Description[sr@ijekavianlatin]": "Daljinski izvršava naredbe u konzoli", + "Description[sr@latin]": "Daljinski izvršava naredbe u konzoli", + "Description[sr]": "Даљински извршава наредбе у конзоли", + "Description[sv]": "Kör terminalkommandon på distans", + "Description[tr]": "Konsol komutlarını uzaktan çalıştır", + "Description[uk]": "Віддалене виконання консольних команд", + "Description[x-test]": "xxExecute console commands remotelyxx", + "Description[zh_CN]": "远程执行控制台命令", + "Description[zh_TW]": "遠程執行控制台命令", + "EnabledByDefault": true, + "Icon": "system-run", + "Id": "kdeconnect_runcommand", + "License": "GPL", + "Name": "Run commands", + "Name[ar]": "شغّل أوامر", + "Name[ca@valencia]": "Executa ordes", + "Name[ca]": "Executa ordres", + "Name[cs]": "Spustit příkazy", + "Name[da]": "Kør kommandoer", + "Name[de]": "Befehle ausführen", + "Name[el]": "Εκτέλεση εντολών", + "Name[es]": "Ejecutar órdenes", + "Name[et]": "Käskude käivitamine", + "Name[eu]": "Exekutatu komandoak", + "Name[fi]": "Suorita komentoja", + "Name[fr]": "Exécute des commandes", + "Name[gl]": "Executar ordes", + "Name[ia]": "Exeque commandos", + "Name[it]": "Esegui comandi", + "Name[ko]": "명령 실행", + "Name[nl]": "Commando's uitvoeren", + "Name[nn]": "Køyr kommandoar", + "Name[pl]": "Wykonywanie polecenie", + "Name[pt]": "Executar comandos", + "Name[pt_BR]": "Executar comandos", + "Name[ru]": "Выполнение команд", + "Name[sk]": "Spustiť príkazy", + "Name[sr@ijekavian]": "Изврши наредбе", + "Name[sr@ijekavianlatin]": "Izvrši naredbe", + "Name[sr@latin]": "Izvrši naredbe", + "Name[sr]": "Изврши наредбе", + "Name[sv]": "Kör kommandon", + "Name[tr]": "Komut çalıştır", + "Name[uk]": "Виконання команд", + "Name[x-test]": "xxRun commandsxx", + "Name[zh_CN]": "执行命令", + "Name[zh_TW]": "執行命令", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "http://albertvaka.wordpress.com" + }, + "X-KdeConnect-OutgoingPackageType": [ + "kdeconnect.runcommand" + ], + "X-KdeConnect-SupportedPackageType": [ + "kdeconnect.runcommand.request" + ] +} diff --git a/plugins/runcommand/kdeconnect_runcommand_config.desktop b/plugins/runcommand/kdeconnect_runcommand_config.desktop new file mode 100644 index 00000000..52d8bc49 --- /dev/null +++ b/plugins/runcommand/kdeconnect_runcommand_config.desktop @@ -0,0 +1,45 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kdeconnect_runcommand_config +X-KDE-ParentComponents=kdeconnect_runcommand + +Name=Run Command plugin settings +Name[ar]=إعدادات ملحقة تشغيل الأوامر +Name[ast]=Axustes del complementu d'execución de comandos +Name[ca]=Ajustaments del connector Executa ordres +Name[ca@valencia]=Ajustaments del connector Executa ordes +Name[cs]=Nastavení modulu Spustit příkaz +Name[da]=Indstilling af kør kommando-plugin +Name[de]=Modul-Einstellungen für Befehlsausführung +Name[el]=Ρυθμίσεις προσθέτου εκτέλεσης εντολής +Name[en_GB]=Run Command plugin settings +Name[es]=Ajustes del complemento de ejecución de órdenes +Name[et]=Käsu käivitamise plugina seadistused +Name[eu]=Exekutatu komandoa pluginaren ezarpenak +Name[fi]=Suorita komento -liitännäisen asetukset +Name[fr]=Configuration du module externe d'exécution de commande +Name[gl]=Configuración do complemento de orde de executar +Name[he]=הגדרת התוסף הרץ פקודה +Name[it]=Impostazioni estensione Esegui comando +Name[ko]=명령 실행 플러그인 설정 +Name[nl]=Plug-in-instellingen van commando Uitvoeren +Name[nn]=Innstillingar for kommandokøyring +Name[pl]=Ustawienia wtyczki wykonywania polecenia +Name[pt]=Configuração do 'plugin' de execução de comandos +Name[pt_BR]=Configurações do plugin de execução de comandos +Name[ru]=Настройка модуля выполнения команд +Name[sk]=Nastavenia pluginu spustenia príkazu +Name[sr]=Поставке прикључка извршавања наредбе +Name[sr@ijekavian]=Поставке прикључка извршавања наредбе +Name[sr@ijekavianlatin]=Postavke priključka izvršavanja naredbe +Name[sr@latin]=Postavke priključka izvršavanja naredbe +Name[sv]=Inställningar av insticksprogram för kör kommando +Name[tr]=Komut Çalıştır eklenti ayarları +Name[uk]=Параметри додатка запуску команд +Name[x-test]=xxRun Command plugin settingsxx +Name[zh_CN]=执行命令插件设置 +Name[zh_TW]=命令列擴展插件設定 + +Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/plugins/runcommand/runcommand_config.cpp b/plugins/runcommand/runcommand_config.cpp new file mode 100644 index 00000000..648b474c --- /dev/null +++ b/plugins/runcommand/runcommand_config.cpp @@ -0,0 +1,146 @@ +/** + * Copyright 2015 David Edmundson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "runcommand_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +K_PLUGIN_FACTORY(ShareConfigFactory, registerPlugin();) + +RunCommandConfig::RunCommandConfig(QWidget* parent, const QVariantList& args) + : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_runcommand_config")) +{ + QTableView* table = new QTableView(this); + table->horizontalHeader()->setStretchLastSection(true); + table->verticalHeader()->setVisible(false); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(table); + setLayout(layout); + + m_entriesModel = new QStandardItemModel(this); + table->setModel(m_entriesModel); + + m_entriesModel->setHorizontalHeaderLabels(QStringList() << i18n("Name") << i18n("Command")); + +} + +RunCommandConfig::~RunCommandConfig() +{ +} + +void RunCommandConfig::defaults() +{ + KCModule::defaults(); + m_entriesModel->clear(); + + Q_EMIT changed(true); +} + +void RunCommandConfig::load() +{ + KCModule::load(); + + QJsonDocument jsonDocument = QJsonDocument::fromJson(config()->get(QStringLiteral("commands"), "{}")); + QJsonObject jsonConfig = jsonDocument.object(); + const QStringList keys = jsonConfig.keys(); + for (const QString& key : keys) { + const QJsonObject entry = jsonConfig[key].toObject(); + const QString name = entry[QStringLiteral("name")].toString(); + const QString command = entry[QStringLiteral("command")].toString(); + + QStandardItem* newName = new QStandardItem(name); + newName->setEditable(true); + newName->setData(key); + QStandardItem* newCommand = new QStandardItem(command); + newName->setEditable(true); + + m_entriesModel->appendRow(QList() << newName << newCommand); + } + + m_entriesModel->sort(0); + + insertEmptyRow(); + connect(m_entriesModel, &QAbstractItemModel::dataChanged, this, &RunCommandConfig::onDataChanged); + + Q_EMIT changed(false); +} + +void RunCommandConfig::save() +{ + QJsonObject jsonConfig; + for (int i=0;i < m_entriesModel->rowCount(); i++) { + QString key = m_entriesModel->item(i, 0)->data().toString(); + const QString name = m_entriesModel->item(i, 0)->text(); + const QString command = m_entriesModel->item(i, 1)->text(); + + if (name.isEmpty() || command.isEmpty()) { + continue; + } + + if (key.isEmpty()) { + key = QUuid::createUuid().toString(); + } + QJsonObject entry; + entry[QStringLiteral("name")] = name; + entry[QStringLiteral("command")] = command; + jsonConfig[key] = entry; + } + QJsonDocument document; + document.setObject(jsonConfig); + config()->set(QStringLiteral("commands"), document.toJson(QJsonDocument::Compact)); + + KCModule::save(); + + Q_EMIT changed(false); +} + +void RunCommandConfig::insertEmptyRow() +{ + QStandardItem* newName = new QStandardItem; + newName->setEditable(true); + QStandardItem* newCommand = new QStandardItem; + newName->setEditable(true); + + m_entriesModel->appendRow(QList() << newName << newCommand); +} + +void RunCommandConfig::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + changed(true); + Q_UNUSED(topLeft); + if (bottomRight.row() == m_entriesModel->rowCount() - 1) { + //TODO check both entries are still empty + insertEmptyRow(); + } +} + + +#include "runcommand_config.moc" diff --git a/plugins/share/share_config.h b/plugins/runcommand/runcommand_config.h similarity index 69% copy from plugins/share/share_config.h copy to plugins/runcommand/runcommand_config.h index b3f837cb..13639cf7 100644 --- a/plugins/share/share_config.h +++ b/plugins/runcommand/runcommand_config.h @@ -1,48 +1,50 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2015 David Edmundson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SHARE_CONFIG_H #define SHARE_CONFIG_H #include "kcmplugin/kdeconnectpluginkcm.h" -namespace Ui { - class ShareConfigUi; -} +class QStandardItemModel; -class ShareConfig +class RunCommandConfig : public KdeConnectPluginKcm { Q_OBJECT public: - ShareConfig(QWidget *parent, const QVariantList&); - virtual ~ShareConfig(); + RunCommandConfig(QWidget* parent, const QVariantList&); + ~RunCommandConfig() override; public Q_SLOTS: - virtual void save(); - virtual void load(); - virtual void defaults(); + void save() override; + void load() override; + void defaults() override; +private Q_SLOTS: + void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); private: - Ui::ShareConfigUi* m_ui; + void insertEmptyRow(); + + QStandardItemModel* m_entriesModel; }; #endif diff --git a/plugins/runcommand/runcommandplugin.cpp b/plugins/runcommand/runcommandplugin.cpp new file mode 100644 index 00000000..24ac0ea3 --- /dev/null +++ b/plugins/runcommand/runcommandplugin.cpp @@ -0,0 +1,93 @@ +/** + * 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 "runcommandplugin.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PACKAGE_TYPE_RUNCOMMAND QStringLiteral("kdeconnect.runcommand") + +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_runcommand.json", registerPlugin< RunCommandPlugin >(); ) + +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_RUNCOMMAND, "kdeconnect.plugin.runcommand") + +RunCommandPlugin::RunCommandPlugin(QObject* parent, const QVariantList& args) + : KdeConnectPlugin(parent, args) +{ + connect(config(), &KdeConnectPluginConfig::configChanged, this, &RunCommandPlugin::configChanged); +} + +RunCommandPlugin::~RunCommandPlugin() +{ +} + +bool RunCommandPlugin::receivePackage(const NetworkPackage& np) +{ + if (np.get(QStringLiteral("requestCommandList"), false)) { + sendConfig(); + return true; + } + + if (np.has(QStringLiteral("key"))) { + QJsonDocument commandsDocument = QJsonDocument::fromJson(config()->get(QStringLiteral("commands"), "{}")); + QJsonObject commands = commandsDocument.object(); + QString key = np.get(QStringLiteral("key")); + QJsonValue value = commands[key]; + if (value == QJsonValue::Undefined) { + qCWarning(KDECONNECT_PLUGIN_RUNCOMMAND) << key << "is not a configured command"; + } + const QJsonObject commandJson = value.toObject(); + qCInfo(KDECONNECT_PLUGIN_RUNCOMMAND) << "Running:" << "/bin/sh" << "-c" << commandJson[QStringLiteral("command")].toString(); + QProcess::startDetached(QStringLiteral("/bin/sh"), QStringList()<< QStringLiteral("-c") << commandJson[QStringLiteral("command")].toString()); + return true; + } + + return false; +} + +void RunCommandPlugin::connected() +{ + + sendConfig(); +} + +void RunCommandPlugin::sendConfig() +{ + QString commands = config()->get(QStringLiteral("commands"),QStringLiteral("{}")); + NetworkPackage np(PACKAGE_TYPE_RUNCOMMAND, {{"commandList", commands}}); + sendPackage(np); +} + +void RunCommandPlugin::configChanged() { + sendConfig(); +} + +#include "runcommandplugin.moc" diff --git a/plugins/pausemusic/pausemusicplugin.h b/plugins/runcommand/runcommandplugin.h similarity index 67% copy from plugins/pausemusic/pausemusicplugin.h copy to plugins/runcommand/runcommandplugin.h index 76c08c02..f272548c 100644 --- a/plugins/pausemusic/pausemusicplugin.h +++ b/plugins/runcommand/runcommandplugin.h @@ -1,53 +1,53 @@ /** - * Copyright 2013 Albert Vaca + * Copyright 2015 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 PAUSEMUSICPLUGIN_H -#define PAUSEMUSICPLUGIN_H +#ifndef RUNCOMMANDPLUGIN_H +#define RUNCOMMANDPLUGIN_H #include -#include -#include #include +#include +#include +#include +#include +#include -class PauseMusicPlugin +class Q_DECL_EXPORT RunCommandPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit PauseMusicPlugin(QObject *parent, const QVariantList &args); + explicit RunCommandPlugin(QObject* parent, const QVariantList& args); + ~RunCommandPlugin() override; + + bool receivePackage(const NetworkPackage& np) override; + void connected() override; -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } +private Q_SLOTS: + void configChanged(); - /** - * @returns 0 if not muted, 1 if muted or -1 if there was an error - */ - int isKMixMuted(); - private: - QSet pausedSources; - bool muted; + void sendConfig(); }; #endif diff --git a/plugins/screensaver-inhibit/kdeconnect_screensaver_inhibit.json b/plugins/screensaver-inhibit/kdeconnect_screensaver_inhibit.json index a1fa9bfc..d013aa78 100644 --- a/plugins/screensaver-inhibit/kdeconnect_screensaver_inhibit.json +++ b/plugins/screensaver-inhibit/kdeconnect_screensaver_inhibit.json @@ -1,60 +1,91 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "pmdematagoda@mykolab.ch", - "Name": "Pramod Dematagoda" + "Email": "pmdematagoda@mykolab.ch", + "Name": "Pramod Dematagoda", + "Name[sr@ijekavian]": "Прамод Дематагода", + "Name[sr]": "Прамод Дематагода", + "Name[x-test]": "xxPramod Dematagodaxx" } - ], - "Description": "Inhibit the screensaver when the device is connected", - "Description[ca]": "Inhibeix l'estalvi de pantalla quan es connecta el dispositiu", - "Description[cs]": "Potlačit spořič pokud je zařízení připojeno", - "Description[de]": "Bildschirmschoner unterbinden wenn ein Gerät angeschlossen ist", - "Description[en_GB]": "Inhibit the screensaver when the device is connected", - "Description[es]": "Inhibir el salvapantallas cuando el dispositivo está conectado", - "Description[fi]": "Estä näytönsäästäjän käynnistyminen, kun laite on yhteydessä", - "Description[gl]": "Desactivar o salvapantallas mentres o dispositivo estea conectado.", - "Description[hu]": "A képernyővédő kikapcsolása, ha az eszköz csatlakoztatva van", - "Description[it]": "Impedisci il salvaschermo quando il dispositivo è connesso", - "Description[nl]": "Houdt de schermbeveiliging tegen wanneer het apparaat is verbonden", - "Description[pl]": "Powstrzymaj wygaszacz ekrany po podłączeniu urządzenia", - "Description[pt]": "Inibir o protector de ecrã quando o dispositivo estiver ligado", - "Description[pt_BR]": "Inibir o protetor de tela quando o dispositivo estiver conectado", - "Description[sk]": "Obmedziť šetrič obrazovky keď je pripojené zariadenie", - "Description[sv]": "Stoppa skärmsläckaren när apparaten ansluts", - "Description[tr]": "Aygıt bağlandığında ekran koruyucuyu durdur", - "Description[uk]": "Забороняє зберігач екрана, якщо з’єднано пристрій", - "Description[x-test]": "xxInhibit the screensaver when the device is connectedxx", - "Description[zh_CN]": "连接设备时禁止屏保", - "EnabledByDefault": false, - "Icon": "preferences-desktop-screensaver", - "Id": "kdeconnect_screensaver_inhibit", - "License": "GPL", - "Name": "Inhibit screensaver", - "Name[ca]": "Inhibeix l'estalvi de pantalla", - "Name[cs]": "Potlačit spořič obrazovky", - "Name[de]": "Bildschirmschoner unterbinden", - "Name[en_GB]": "Inhibit screensaver", - "Name[es]": "Inhibir salvapantallas", - "Name[fi]": "Estä näytönsäästäjän käynnistyminen", - "Name[gl]": "Desactivar o salvapantallas", - "Name[hu]": "Képernyővédő letiltása", - "Name[it]": "Impedisci il salvaschermo", - "Name[nl]": "Schermbeveiliging tegenhouden", - "Name[pl]": "Powstrzymaj wygaszacz ekranu", - "Name[pt]": "Inibir o protector de ecrã", - "Name[pt_BR]": "Inibir o protetor de tela", - "Name[sk]": "Obmedziť šetrič obrazovky", - "Name[sv]": "Stoppa skärmsläckare", - "Name[tr]": "Ekran koruyucuyu durdur", - "Name[uk]": "Заборона зберігача екрана", - "Name[x-test]": "xxInhibit screensaverxx", - "Name[zh_CN]": "禁止屏保", + ], + "Description": "Inhibit the screensaver when the device is connected", + "Description[ar]": "امنع حافظة الشّاشة إن كان الجهاز متّصلًا", + "Description[ca@valencia]": "Inhibeix l'estalvi de pantalla quan es connecta el dispositiu", + "Description[ca]": "Inhibeix l'estalvi de pantalla quan es connecta el dispositiu", + "Description[cs]": "Potlačit spořič pokud je zařízení připojeno", + "Description[da]": "Tilbagehold pauseskærmen når enheden er tilsluttet", + "Description[de]": "Bildschirmschoner unterbinden wenn ein Gerät angeschlossen ist", + "Description[el]": "Απαγόρευση προστασίας οθόνης όταν η συσκευή είναι σε σύνδεση", + "Description[es]": "Inhibir el salvapantallas cuando el dispositivo está conectado", + "Description[et]": "Ekraanisäästja keelamine, kui seade on ühendatud", + "Description[eu]": "Gelditu pantaila-babeslea gailua konektatuta dagoenean", + "Description[fi]": "Estä näytönsäästäjän käynnistyminen, kun laite on yhteydessä", + "Description[fr]": "Inhiber l'économiseur d'écran quand le périphérique est connecté", + "Description[gl]": "Desactivar o salvapantallas mentres o dispositivo estea conectado.", + "Description[hu]": "A képernyővédő kikapcsolása, ha az eszköz csatlakoztatva van", + "Description[it]": "Impedisci il salvaschermo quando il dispositivo è connesso", + "Description[ko]": "장치가 연결되었을 때 화면 보호기 실행 중지", + "Description[nl]": "Houdt de schermbeveiliging tegen wanneer het apparaat is verbonden", + "Description[nn]": "Hindra pauseskjermen i å starta når eininga er kopla til", + "Description[pl]": "Powstrzymaj wygaszacz ekrany po podłączeniu urządzenia", + "Description[pt]": "Inibir o protector de ecrã quando o dispositivo estiver ligado", + "Description[pt_BR]": "Inibe o protetor de tela quando o dispositivo estiver conectado", + "Description[ru]": "Предотвращение запуска блокировщика экрана, когда устройство подключено", + "Description[sk]": "Obmedziť šetrič obrazovky keď je pripojené zariadenie", + "Description[sr@ijekavian]": "Онемогућава чувара екрана када је уређај повезан", + "Description[sr@ijekavianlatin]": "Onemogućava čuvara ekrana kada je uređaj povezan", + "Description[sr@latin]": "Onemogućava čuvara ekrana kada je uređaj povezan", + "Description[sr]": "Онемогућава чувара екрана када је уређај повезан", + "Description[sv]": "Stoppa skärmsläckaren när apparaten ansluts", + "Description[tr]": "Aygıt bağlandığında ekran koruyucuyu durdur", + "Description[uk]": "Забороняє зберігач екрана, якщо з’єднано пристрій", + "Description[x-test]": "xxInhibit the screensaver when the device is connectedxx", + "Description[zh_CN]": "连接设备时禁止屏保", + "Description[zh_TW]": "當裝置連接中時停止螢幕保護", + "EnabledByDefault": false, + "Icon": "preferences-desktop-screensaver", + "Id": "kdeconnect_screensaver_inhibit", + "License": "GPL", + "Name": "Inhibit screensaver", + "Name[ar]": "امنع حافظة الشّاشة", + "Name[ca@valencia]": "Inhibeix l'estalvi de pantalla", + "Name[ca]": "Inhibeix l'estalvi de pantalla", + "Name[cs]": "Potlačit spořič obrazovky", + "Name[da]": "Tilbagehold pauseskærm", + "Name[de]": "Bildschirmschoner unterbinden", + "Name[el]": "Απαγόρευση προστασίας οθόνης", + "Name[es]": "Inhibir salvapantallas", + "Name[et]": "Ekraanisäästja keelamine", + "Name[eu]": "Gelditu pantaila-babeslea", + "Name[fi]": "Estä näytönsäästäjän käynnistyminen", + "Name[fr]": "Inhiber l'économiseur d'écran", + "Name[gl]": "Desactivar o salvapantallas", + "Name[hu]": "Képernyővédő letiltása", + "Name[it]": "Impedisci il salvaschermo", + "Name[ko]": "화면 보호기 중지", + "Name[nl]": "Schermbeveiliging tegenhouden", + "Name[nn]": "Hindra pauseskjerm", + "Name[pl]": "Powstrzymaj wygaszacz ekranu", + "Name[pt]": "Inibir o protector de ecrã", + "Name[pt_BR]": "Inibir o protetor de tela", + "Name[ru]": "Запрет блокировки экрана", + "Name[sk]": "Obmedziť šetrič obrazovky", + "Name[sr@ijekavian]": "Онемогући чувара екрана", + "Name[sr@ijekavianlatin]": "Onemogući čuvara ekrana", + "Name[sr@latin]": "Onemogući čuvara ekrana", + "Name[sr]": "Онемогући чувара екрана", + "Name[sv]": "Stoppa skärmsläckare", + "Name[tr]": "Ekran koruyucuyu durdur", + "Name[uk]": "Заборона зберігача екрана", + "Name[x-test]": "xxInhibit screensaverxx", + "Name[zh_CN]": "禁止屏保", + "Name[zh_TW]": "停止螢幕保護", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" } -} \ No newline at end of file +} diff --git a/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp b/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp index 49faebed..977fa787 100644 --- a/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp +++ b/plugins/screensaver-inhibit/screensaverinhibitplugin.cpp @@ -1,86 +1,86 @@ /** * Copyright 2014 Pramod Dematagoda * * 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 "screensaverinhibitplugin.h" #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_screensaver_inhibit.json", registerPlugin< ScreensaverInhibitPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SCREENSAVERINHIBIT, "kdeconnect.plugin.screensaverinhibit") -const QString INHIBIT_SERVICE = "org.freedesktop.ScreenSaver"; +const QString INHIBIT_SERVICE = QStringLiteral("org.freedesktop.ScreenSaver"); const QString INHIBIT_INTERFACE = INHIBIT_SERVICE; -const QString INHIBIT_PATH = "/ScreenSaver"; -const QString INHIBIT_METHOD = "Inhibit"; -const QString UNINHIBIT_METHOD = "UnInhibit"; -const QString SIMULATE_ACTIVITY_METHOD = "SimulateUserActivity"; +const QString INHIBIT_PATH = QStringLiteral("/ScreenSaver"); +const QString INHIBIT_METHOD = QStringLiteral("Inhibit"); +const QString UNINHIBIT_METHOD = QStringLiteral("UnInhibit"); +const QString SIMULATE_ACTIVITY_METHOD = QStringLiteral("SimulateUserActivity"); ScreensaverInhibitPlugin::ScreensaverInhibitPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { QDBusInterface inhibitInterface(INHIBIT_SERVICE, INHIBIT_PATH, INHIBIT_INTERFACE); QDBusMessage reply = inhibitInterface.call(INHIBIT_METHOD, "kdeconnect", "Phone is connected"); - if (reply.errorMessage() != NULL) { + if (reply.errorMessage() != nullptr) { qCDebug(KDECONNECT_PLUGIN_SCREENSAVERINHIBIT) << "Unable to inhibit the screensaver: " << reply.errorMessage(); inhibitCookie = 0; } else { // Store the cookie we receive, this will be sent back when sending the uninhibit call. - inhibitCookie = reply.arguments().first().toUInt(); + inhibitCookie = reply.arguments().at(0).toUInt(); } } ScreensaverInhibitPlugin::~ScreensaverInhibitPlugin() { if (inhibitCookie == 0) return; QDBusInterface inhibitInterface(INHIBIT_SERVICE, INHIBIT_PATH, INHIBIT_INTERFACE); inhibitInterface.call(UNINHIBIT_METHOD, this->inhibitCookie); /* * Simulate user activity because what ever manages the screensaver does not seem to start the timer * automatically when all inhibitions are lifted and the user does nothing which results in an * unlocked desktop which would be dangerous. Ideally we should not be doing this and the screen should * be locked anyway. */ inhibitInterface.call(SIMULATE_ACTIVITY_METHOD); } void ScreensaverInhibitPlugin::connected() { } bool ScreensaverInhibitPlugin::receivePackage(const NetworkPackage& np) { Q_UNUSED(np); return false; } #include "screensaverinhibitplugin.moc" diff --git a/plugins/screensaver-inhibit/screensaverinhibitplugin.h b/plugins/screensaver-inhibit/screensaverinhibitplugin.h index d2efeca3..462a2233 100644 --- a/plugins/screensaver-inhibit/screensaverinhibitplugin.h +++ b/plugins/screensaver-inhibit/screensaverinhibitplugin.h @@ -1,45 +1,44 @@ /** * Copyright 2014 Pramod Dematagoda * * 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 SCREENSAVERINHIBITPLUGIN_H #define SCREENSAVERINHIBITPLUGIN_H #include #include class Q_DECL_EXPORT ScreensaverInhibitPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit ScreensaverInhibitPlugin(QObject *parent, const QVariantList &args); - virtual ~ScreensaverInhibitPlugin(); + explicit ScreensaverInhibitPlugin(QObject* parent, const QVariantList& args); + ~ScreensaverInhibitPlugin() override; -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override; private: uint inhibitCookie; }; #endif diff --git a/plugins/sendnotifications/CMakeLists.txt b/plugins/sendnotifications/CMakeLists.txt new file mode 100644 index 00000000..f04d8093 --- /dev/null +++ b/plugins/sendnotifications/CMakeLists.txt @@ -0,0 +1,39 @@ +find_package(KF5 REQUIRED COMPONENTS Notifications KCMUtils I18n IconThemes) + +set(kdeconnect_sendnotifications_SRCS + sendnotificationsplugin.cpp + notificationslistener.cpp + notifyingapplication.cpp +) + +kdeconnect_add_plugin(kdeconnect_sendnotifications JSON kdeconnect_sendnotifications.json SOURCES ${kdeconnect_sendnotifications_SRCS}) + +target_link_libraries(kdeconnect_sendnotifications + kdeconnectcore + Qt5::DBus + KF5::Notifications + KF5::I18n + KF5::IconThemes + KF5::ConfigCore +) + +####################################### +# Config + +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} ) diff --git a/plugins/sendnotifications/kdeconnect_sendnotifications.json b/plugins/sendnotifications/kdeconnect_sendnotifications.json new file mode 100644 index 00000000..8f66faaa --- /dev/null +++ b/plugins/sendnotifications/kdeconnect_sendnotifications.json @@ -0,0 +1,96 @@ +{ + "Encoding": "UTF-8", + "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'este 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[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[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, + "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-OutgoingPackageType": [ + "kdeconnect.notification" + ], + "X-KdeConnect-SupportedPackageType": [ + "kdeconnect.notification.request" + ] +} diff --git a/plugins/sendnotifications/kdeconnect_sendnotifications_config.desktop b/plugins/sendnotifications/kdeconnect_sendnotifications_config.desktop new file mode 100644 index 00000000..a4f59ab8 --- /dev/null +++ b/plugins/sendnotifications/kdeconnect_sendnotifications_config.desktop @@ -0,0 +1,45 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kdeconnect_sendnotifications_config +X-KDE-ParentComponents=kdeconnect_sendnotifications + +Name=Notification synchronization plugin settings +Name[ar]=إعدادات ملحقة مزامنة الإخطارات +Name[ast]=Axustes del complementu de sincronización d'avisos +Name[ca]=Ajustament del connector Sincronitza les notificacions +Name[ca@valencia]=Ajustament del connector Sincronitza les notificacions +Name[cs]=Nastavení modulu synchronizace upozornění +Name[da]=Indstilling af plugin til bekendtgørelsessynkronisering +Name[de]=Einstellungen für Benachrichtigungsabgleich-Modul +Name[el]=Ρυθμίσεις πρόσθετου συγχρονισμού ειδοποιήσεων +Name[en_GB]=Notification synchronisation plugin settings +Name[es]=Preferencias del complemento de sincronización de notificaciones +Name[et]=Märguande sünkroonimise plugina seaditused +Name[eu]=Jakinarazpenak sinkronizatzeko pluginaren ezarpenak +Name[fi]=Ilmoitusten synkronointiliitännäisen asetukset +Name[fr]=Configuration du module externe de synchronisation des notifications +Name[gl]=Configuración do complemento de sincronización de notificacións +Name[he]=הגדרת התוסף סנכרון התראות +Name[it]=Impostazioni dell'estensione di sincronizzazione delle notifiche +Name[ko]=알림 동기화 플러그인 설정 +Name[nl]=Instellingen van plug-in voor synchronisatie van meldingen +Name[nn]=Innstillingar for synkronisering av varslingar +Name[pl]=Ustawienia wtyczki synchronizującej powiadomienia +Name[pt]=Configuração do 'plugin' de sincronização das notificações +Name[pt_BR]=Configuração do plugin de sincronização das notificações +Name[ru]=Настройка модуля синхронизации уведомлений +Name[sk]=Nastavenia pluginu notifikácií synchronizácie +Name[sr]=Поставке прикључка синхронизације обавештења +Name[sr@ijekavian]=Поставке прикључка синхронизације обавештења +Name[sr@ijekavianlatin]=Postavke priključka sinhronizacije obaveštenja +Name[sr@latin]=Postavke priključka sinhronizacije obaveštenja +Name[sv]=Inställningar av insticksprogram för underrättelsesynkronisering +Name[tr]=Bildirim eşitleme eklentisi ayarları +Name[uk]=Параметри додатка синхронізації сповіщень +Name[x-test]=xxNotification synchronization plugin settingsxx +Name[zh_CN]=通知同步插件设置 +Name[zh_TW]=同步通知擴展插件設定 + +Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/plugins/sendnotifications/notificationslistener.cpp b/plugins/sendnotifications/notificationslistener.cpp new file mode 100644 index 00000000..6209cda5 --- /dev/null +++ b/plugins/sendnotifications/notificationslistener.cpp @@ -0,0 +1,274 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "notificationslistener.h" +#include "sendnotificationsplugin.h" +#include "sendnotification_debug.h" +#include "notifyingapplication.h" + +NotificationsListener::NotificationsListener(KdeConnectPlugin* aPlugin) + : QDBusAbstractAdaptor(aPlugin), + 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->sendPackage(np); + + return (replacesId > 0 ? replacesId : id); +} diff --git a/plugins/sendnotifications/notificationslistener.h b/plugins/sendnotifications/notificationslistener.h new file mode 100644 index 00000000..adddfdc0 --- /dev/null +++ b/plugins/sendnotifications/notificationslistener.h @@ -0,0 +1,64 @@ +/** + * 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 + +class KdeConnectPlugin; +class Notification; +struct NotifyingApplication; + +class NotificationsListener : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications") + +public: + explicit NotificationsListener(KdeConnectPlugin* aPlugin); + ~NotificationsListener() override; + +protected: + KdeConnectPlugin* m_plugin; + QHash m_applications; + + // virtual helper function to make testing possible (QDBusArgument can not + // be injected without making a DBUS-call): + virtual bool parseImageDataArgument(const QVariant& argument, int& width, + int& height, int& rowStride, int& bitsPerSample, + int& channels, bool& hasAlpha, + QByteArray& imageData) const; + QSharedPointer iconForImageData(const QVariant& argument) const; + QSharedPointer iconForIconName(const QString& iconName) const; + +public Q_SLOTS: + Q_SCRIPTABLE uint Notify(const QString&, uint, const QString&, + const QString&, const QString&, + const QStringList&, const QVariantMap&, int); + +private Q_SLOTS: + void loadApplications(); + +private: + void setTranslatedAppName(); + QString m_translatedAppName; +}; diff --git a/plugins/notifications/notification.cpp b/plugins/sendnotifications/notifyingapplication.cpp similarity index 50% copy from plugins/notifications/notification.cpp copy to plugins/sendnotifications/notifyingapplication.cpp index b44b5f85..185b9d65 100644 --- a/plugins/notifications/notification.cpp +++ b/plugins/sendnotifications/notifyingapplication.cpp @@ -1,46 +1,50 @@ /** - * Copyright 2013 Albert Vaca + * 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 "notification.h" +#include "notifyingapplication.h" -#include +#include +#include -Notification::Notification(const NetworkPackage& np, const QString& iconPath, QObject* parent) - : QObject(parent) +QDataStream& operator<<(QDataStream& out, const NotifyingApplication& app) { - mId = np.get("id"); - mAppName = np.get("appName"); - mTicker = np.get("ticker"); - mDismissable = np.get("isClearable"); - mIconPath = iconPath; + out << app.name << app.icon << app.active << app.blacklistExpression.pattern(); + return out; } -Notification::~Notification() +QDataStream& operator>>(QDataStream& in, NotifyingApplication& app) { - + QString pattern; + in >> app.name; + in >> app.icon; + in >> app.active; + in >> pattern; + app.blacklistExpression.setPattern(pattern); + return in; } -void Notification::dismiss() -{ - if (mDismissable) { - Q_EMIT dismissRequested(this); - } +QDebug operator<<(QDebug dbg, const NotifyingApplication& a) { + dbg.nospace() << "{ name=" << a.name + << ", icon=" << a.icon + << ", active=" << a.active + << ", blacklistExpression =" << a.blacklistExpression + << " }"; + return dbg.space(); } - diff --git a/plugins/share/autoclosingqfile.h b/plugins/sendnotifications/notifyingapplication.h similarity index 56% rename from plugins/share/autoclosingqfile.h rename to plugins/sendnotifications/notifyingapplication.h index bea1ea19..57d38949 100644 --- a/plugins/share/autoclosingqfile.h +++ b/plugins/sendnotifications/notifyingapplication.h @@ -1,42 +1,43 @@ -/* - * Copyright 2013 Albert Vaca +/** + * 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 . */ -#ifndef AUTOCLOSINGQFILE_H -#define AUTOCLOSINGQFILE_H +#ifndef NOTIFYINGAPPLICATION_H +#define NOTIFYINGAPPLICATION_H -#include +#include -class AutoClosingQFile : public QFile -{ - Q_OBJECT -public: +struct NotifyingApplication { + QString name; + QString icon; + bool active; + QRegularExpression blacklistExpression; - AutoClosingQFile(const QString &name); - qint64 readData(char* data, qint64 maxlen) Q_DECL_OVERRIDE { - qint64 read = QFile::readData(data, maxlen); - if (read == -1 || read == bytesAvailable()) { - close(); - } - return read; + bool operator==(const NotifyingApplication& other) const { + return (name == other.name); } }; +Q_DECLARE_METATYPE(NotifyingApplication); -#endif // AUTOCLOSINGQFILE_H +QDataStream& operator<<(QDataStream& out, const NotifyingApplication& app); +QDataStream& operator>>(QDataStream& in, NotifyingApplication& app); +QDebug operator<<(QDebug dbg, const NotifyingApplication& a); + +#endif //NOTIFYINGAPPLICATION_H diff --git a/plugins/sendnotifications/notifyingapplicationmodel.cpp b/plugins/sendnotifications/notifyingapplicationmodel.cpp new file mode 100644 index 00000000..2ac3277b --- /dev/null +++ b/plugins/sendnotifications/notifyingapplicationmodel.cpp @@ -0,0 +1,238 @@ +/** + * 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 "notifyingapplicationmodel.h" + +#include + +#include +#include +#include + +#include + +//#include "modeltest.h" + +NotifyingApplicationModel::NotifyingApplicationModel(QObject* parent) + : QAbstractTableModel(parent) +{ +} + +NotifyingApplicationModel::~NotifyingApplicationModel() +{ +} + +QVector NotifyingApplicationModel::apps() +{ + return m_apps; +} + +void NotifyingApplicationModel::appendApp(const NotifyingApplication& app) +{ + if (app.name.isEmpty() || apps().contains(app)) + return; + beginInsertRows(QModelIndex(), m_apps.size(), m_apps.size()); + m_apps.append(app); + endInsertRows(); +} + +bool NotifyingApplicationModel::containsApp(const QString& name) const +{ + for (const auto& a: m_apps) + if (a.name == name) + return true; + return false; +} + +Qt::ItemFlags NotifyingApplicationModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if (index.isValid() && index.row() >= 0 && index.row() < m_apps.size() && + index.column() < 3) + { + if (index.column() == 0) + flags |= Qt::ItemIsEditable | Qt::ItemIsUserCheckable; + else if (index.column() == 2) { + if (m_apps[index.row()].active) + flags |= Qt::ItemIsEditable; + else + flags ^= Qt::ItemIsEnabled; + } + else if (index.column() == 1) { + if (!m_apps[index.row()].active) + flags ^= Qt::ItemIsEnabled; + } + } + return flags; +} + +void NotifyingApplicationModel::clearApplications() +{ + if (!m_apps.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_apps.size() - 1); + m_apps.clear(); + endRemoveRows(); + } +} + +QVariant NotifyingApplicationModel::data(const QModelIndex& index, int role) const +{ + Q_UNUSED(role); + if (!index.isValid() + || index.row() < 0 + || index.row() >= m_apps.size() + || index.column() > 3) + { + return QVariant(); + } + + switch (role) { + case Qt::TextAlignmentRole: { + if (index.column() == 0) + return int(Qt::AlignCenter | Qt::AlignVCenter ); + else + return int(Qt::AlignLeft | Qt::AlignVCenter ); + break; + } + case Qt::DisplayRole: { + if (index.column() == 1) + return m_apps[index.row()].name; + else if (index.column() == 0) + return QVariant();//m_apps[index.row()].active; + else if (index.column() == 2) + return m_apps[index.row()].blacklistExpression.pattern(); + else + return QVariant(); + break; + } + case Qt::DecorationRole: { + if (index.column() == 1) + return QIcon::fromTheme(m_apps[index.row()].icon, QIcon::fromTheme(QStringLiteral("application-x-executable"))); + else + return QVariant(); + break; + } + case Qt::EditRole: { + if (index.column() == 0) + return m_apps[index.row()].active ? Qt::Checked : Qt::Unchecked; + else if (index.column() == 2) + return m_apps[index.row()].blacklistExpression.pattern(); + else + return QVariant(); + break; + } + case Qt::CheckStateRole: { + if (index.column() == 0) + return m_apps[index.row()].active ? Qt::Checked : Qt::Unchecked; + else + return QVariant(); + break; + } + } + return QVariant(); +} + +bool NotifyingApplicationModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || + (index.column() != 0 && index.column() != 2) || + index.row() < 0 || index.row() >= m_apps.size()) + return false; + + bool res = false; + QModelIndex bottomRight = createIndex(index.row(), index.column()); + switch (role) { + case Qt::CheckStateRole: { + if (index.column() == 0) { + m_apps[index.row()].active = ((Qt::CheckState)value.toInt() == Qt::Checked); + bottomRight = createIndex(index.row(), index.column() + 1); + res = true; + } + break; + } + case Qt::EditRole: { + if (index.column() == 2) { + m_apps[index.row()].blacklistExpression.setPattern(value.toString()); + res = true; + } + } + } + if (res) { + Q_EMIT dataChanged(index, bottomRight); + Q_EMIT applicationsChanged(); // -> notify config that we need to save + } + return res; +} + +void NotifyingApplicationModel::sort(int column, Qt::SortOrder order) +{ + if (column != 1) + return; + + if (order == Qt::AscendingOrder) + std::sort(m_apps.begin(), m_apps.end(), + [](const NotifyingApplication& a, const NotifyingApplication& b) { + return (a.name.compare(b.name, Qt::CaseInsensitive) < 1); + }); + else + std::sort(m_apps.begin(), m_apps.end(), + [](const NotifyingApplication& a, const NotifyingApplication& b) { + return (b.name.compare(a.name, Qt::CaseInsensitive) < 1); + }); + Q_EMIT dataChanged(createIndex(0, 0), createIndex(m_apps.size(), 2)); +} + +QVariant NotifyingApplicationModel::headerData(int section, Qt::Orientation /*orientation*/, + int role) const +{ + switch (role) { + case Qt::DisplayRole: { + if (section == 1) + return i18n("Name"); + else if (section == 0) + return QVariant(); //i18n("Sync"); + else + return i18n("Blacklisted"); + } + case Qt::ToolTipRole: { + if (section == 1) + return i18n("Name of a notifying application."); + else if (section == 0) + return i18n("Synchronize notifications of an application?"); + else + return i18n("Regular expression defining which notifications should not be sent.\nThis pattern is applied to the summary and, if selected above, the body of notifications."); + } + } + return QVariant(); +} + +int NotifyingApplicationModel::columnCount(const QModelIndex&) const +{ + return 3; +} + +int NotifyingApplicationModel::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_apps.size(); +} diff --git a/plugins/sendnotifications/notifyingapplicationmodel.h b/plugins/sendnotifications/notifyingapplicationmodel.h new file mode 100644 index 00000000..25e2e2f2 --- /dev/null +++ b/plugins/sendnotifications/notifyingapplicationmodel.h @@ -0,0 +1,56 @@ +/** + * 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 . + */ + +#ifndef NOTIFYINGAPPLICATIONMODEL_H +#define NOTIFYINGAPPLICATIONMODEL_H + +#include + +#include "notifyingapplication.h" + +class NotifyingApplicationModel: public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit NotifyingApplicationModel(QObject* parent = nullptr); + ~NotifyingApplicationModel() override; + + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role) override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex & index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + + QVector apps(); + void clearApplications(); + void appendApp(const NotifyingApplication& app); + bool containsApp(const QString& name) const; + +Q_SIGNALS: + void applicationsChanged(); + +private: + QVector m_apps; +}; + +#endif // NOTIFYINGAPPLICATIONMODEL_H diff --git a/plugins/notifications/notification_debug.h b/plugins/sendnotifications/sendnotification_debug.h similarity index 85% copy from plugins/notifications/notification_debug.h copy to plugins/sendnotifications/sendnotification_debug.h index 09da52f8..9050139a 100644 --- a/plugins/notifications/notification_debug.h +++ b/plugins/sendnotifications/sendnotification_debug.h @@ -1,28 +1,28 @@ /** * 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 . */ -#ifndef NOTIFICATION_DEBUG_H -#define NOTIFICATION_DEBUG_H +#ifndef SENDNOTIFICATION_DEBUG_H +#define SENDNOTIFICATION_DEBUG_H #include -Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_NOTIFICATION) +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SENDNOTIFICATION) -#endif //NOTIFICATION_DEBUG_H \ No newline at end of file +#endif //NOTIFICATION_DEBUG_H diff --git a/plugins/sendnotifications/sendnotifications_config.cpp b/plugins/sendnotifications/sendnotifications_config.cpp new file mode 100644 index 00000000..55645bbe --- /dev/null +++ b/plugins/sendnotifications/sendnotifications_config.cpp @@ -0,0 +1,121 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "sendnotifications_config.h" +#include "ui_sendnotifications_config.h" +#include "notifyingapplicationmodel.h" + +#include +#include + +K_PLUGIN_FACTORY(SendNotificationsConfigFactory, registerPlugin();) + +SendNotificationsConfig::SendNotificationsConfig(QWidget* parent, const QVariantList& args) + : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_sendnotifications_config")) + , m_ui(new Ui::SendNotificationsConfigUi()) + , appModel(new NotifyingApplicationModel) +{ + qRegisterMetaTypeStreamOperators("NotifyingApplication"); + + m_ui->setupUi(this); + m_ui->appList->setIconSize(QSize(32,32)); + + m_ui->appList->setModel(appModel); + + m_ui->appList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::QHeaderView::Fixed); + m_ui->appList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::QHeaderView::Stretch); + m_ui->appList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::QHeaderView::Stretch); + for (int i = 0; i < 3; i++) + m_ui->appList->resizeColumnToContents(i); + + connect(m_ui->appList->horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + m_ui->appList, SLOT(sortByColumn(int))); + + connect(m_ui->check_persistent, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(m_ui->spin_urgency, SIGNAL(editingFinished()), this, SLOT(changed())); + connect(m_ui->check_body, SIGNAL(toggled(bool)), this, SLOT(changed())); + connect(m_ui->check_icons, SIGNAL(toggled(bool)), this, SLOT(changed())); + + connect(appModel, SIGNAL(applicationsChanged()), this, SLOT(changed())); + + connect(config(), &KdeConnectPluginConfig::configChanged, this, &SendNotificationsConfig::loadApplications); +} + +SendNotificationsConfig::~SendNotificationsConfig() +{ + delete m_ui; +} + +void SendNotificationsConfig::defaults() +{ + KCModule::defaults(); + m_ui->check_persistent->setChecked(false); + m_ui->spin_urgency->setValue(0); + m_ui->check_body->setChecked(true); + m_ui->check_icons->setChecked(true); + Q_EMIT changed(true); +} + +void SendNotificationsConfig::loadApplications() +{ + appModel->clearApplications(); + QVariantList list = config()->getList(QStringLiteral("applications")); + for (const auto& a: list) { + NotifyingApplication app = a.value(); + if (!appModel->containsApp(app.name)) { + appModel->appendApp(app); + } + } +} + +void SendNotificationsConfig::load() +{ + KCModule::load(); + bool persistent = config()->get(QStringLiteral("generalPersistent"), false); + m_ui->check_persistent->setChecked(persistent); + bool body = config()->get(QStringLiteral("generalIncludeBody"), true); + m_ui->check_body->setChecked(body); + bool icons = config()->get(QStringLiteral("generalSynchronizeIcons"), true); + m_ui->check_icons->setChecked(icons); + int urgency = config()->get(QStringLiteral("generalUrgency"), 0); + m_ui->spin_urgency->setValue(urgency); + + loadApplications(); + Q_EMIT changed(false); +} + +void SendNotificationsConfig::save() +{ + config()->set(QStringLiteral("generalPersistent"), m_ui->check_persistent->isChecked()); + config()->set(QStringLiteral("generalIncludeBody"), m_ui->check_body->isChecked()); + config()->set(QStringLiteral("generalSynchronizeIcons"), m_ui->check_icons->isChecked()); + config()->set(QStringLiteral("generalUrgency"), m_ui->spin_urgency->value()); + + QVariantList list; + list.reserve(appModel->apps().size()); + for (const auto& a: appModel->apps()) { + list.append(QVariant::fromValue(a)); + } + config()->setList(QStringLiteral("applications"), list); + KCModule::save(); + Q_EMIT changed(false); +} + +#include "sendnotifications_config.moc" diff --git a/plugins/pausemusic/pausemusic_config.h b/plugins/sendnotifications/sendnotifications_config.h similarity index 64% copy from plugins/pausemusic/pausemusic_config.h copy to plugins/sendnotifications/sendnotifications_config.h index 180ebaf2..41b562b1 100644 --- a/plugins/pausemusic/pausemusic_config.h +++ b/plugins/sendnotifications/sendnotifications_config.h @@ -1,48 +1,54 @@ /** - * Copyright 2013 Albert Vaca + * 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 . */ -#ifndef PAUSEMUSIC_CONFIG_H -#define PAUSEMUSIC_CONFIG_H +#ifndef SENDNOTIFICATIONS_CONFIG_H +#define SENDNOTIFICATIONS_CONFIG_H #include "kcmplugin/kdeconnectpluginkcm.h" namespace Ui { - class PauseMusicConfigUi; + class SendNotificationsConfigUi; } -class PauseMusicConfig +class NotifyingApplicationModel; + +class SendNotificationsConfig : public KdeConnectPluginKcm { Q_OBJECT public: - PauseMusicConfig(QWidget *parent, const QVariantList&); - virtual ~PauseMusicConfig(); + SendNotificationsConfig(QWidget* parent, const QVariantList&); + ~SendNotificationsConfig() override; public Q_SLOTS: - virtual void save(); - virtual void load(); - virtual void defaults(); + void save() override; + void load() override; + void defaults() override; + +private Q_SLOTS: + void loadApplications(); private: - Ui::PauseMusicConfigUi* m_ui; + Ui::SendNotificationsConfigUi* m_ui; + NotifyingApplicationModel* appModel; }; #endif diff --git a/plugins/sendnotifications/sendnotifications_config.ui b/plugins/sendnotifications/sendnotifications_config.ui new file mode 100644 index 00000000..cd180a20 --- /dev/null +++ b/plugins/sendnotifications/sendnotifications_config.ui @@ -0,0 +1,204 @@ + + + SendNotificationsConfigUi + + + Qt::WindowModal + + + + 0 + 0 + 350 + 328 + + + + + 0 + 0 + + + + + 350 + 0 + + + + + + + + 0 + 0 + + + + General + + + + + + Synchronize only notifications with a timeout value of 0? + + + Persistent notifications only + + + + + + + + 50 + false + + + + Append the notification body to the summary when synchronizing notifications? + + + Include body + + + + + + + + 50 + false + + + + Synchronize icons of notifying applications if possible? + + + Synchronize icons + + + + + + + + 0 + 0 + + + + + + + + 40 + 32 + + + + + 50 + false + + + + <html><head/><body><p>Minimum urgency level of the notifications</p></body></html> + + + 2 + + + + + + + + 50 + false + + + + Synchronize only notifications with the given urgency level. + + + Minimum urgency level + + + + + + + + + + + + + + 0 + 0 + + + + + + + Applications + + + + + + + 0 + 0 + + + + + 50 + false + + + + true + + + false + + + Qt::NoPen + + + true + + + true + + + 150 + + + 20 + + + true + + + true + + + false + + + + + + + + + + + diff --git a/plugins/clipboard/clipboardplugin.cpp b/plugins/sendnotifications/sendnotificationsplugin.cpp similarity index 53% copy from plugins/clipboard/clipboardplugin.cpp copy to plugins/sendnotifications/sendnotificationsplugin.cpp index 656dc721..40dcf8c3 100644 --- a/plugins/clipboard/clipboardplugin.cpp +++ b/plugins/sendnotifications/sendnotificationsplugin.cpp @@ -1,64 +1,54 @@ /** * 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 "clipboardplugin.h" +#include "sendnotificationsplugin.h" -#include -#include +#include "notificationslistener.h" +#include "sendnotification_debug.h" #include -K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_clipboard.json", registerPlugin< ClipboardPlugin >(); ) +K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_sendnotifications.json", registerPlugin< SendNotificationsPlugin >(); ) -Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_CLIPBOARD, "kdeconnect.plugin.clipboard") +Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SENDNOTIFICATION, "kdeconnect.plugin.sendnotification") -ClipboardPlugin::ClipboardPlugin(QObject *parent, const QVariantList &args) +SendNotificationsPlugin::SendNotificationsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) - , clipboard(QGuiApplication::clipboard()) { - connect(clipboard, SIGNAL(changed(QClipboard::Mode)), this, SLOT(clipboardChanged(QClipboard::Mode))); + notificationsListener = new NotificationsListener(this); } -void ClipboardPlugin::clipboardChanged(QClipboard::Mode mode) +SendNotificationsPlugin::~SendNotificationsPlugin() { - if (mode != QClipboard::Clipboard) { - return; - } - - QString content = clipboard->text(); - - if (content == currentContent) { - return; - } - - currentContent = content; - - NetworkPackage np(PACKAGE_TYPE_CLIPBOARD); - np.set("content", content); - sendPackage(np); + delete notificationsListener; } -bool ClipboardPlugin::receivePackage(const NetworkPackage& np) +bool SendNotificationsPlugin::receivePackage(const NetworkPackage& np) { - clipboard->setText(np.get("content")); + Q_UNUSED(np); return true; } -#include "clipboardplugin.moc" +void SendNotificationsPlugin::connected() +{ + +} + +#include "sendnotificationsplugin.moc" diff --git a/plugins/notifications/notificationsplugin.h b/plugins/sendnotifications/sendnotificationsplugin.h similarity index 70% copy from plugins/notifications/notificationsplugin.h copy to plugins/sendnotifications/sendnotificationsplugin.h index a2e78280..1ddf3153 100644 --- a/plugins/notifications/notificationsplugin.h +++ b/plugins/sendnotifications/sendnotificationsplugin.h @@ -1,54 +1,53 @@ /** * 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 NOTIFICATIONSPLUGIN_H -#define NOTIFICATIONSPLUGIN_H +#ifndef SENDNOTIFICATIONSPLUGIN_H +#define SENDNOTIFICATIONSPLUGIN_H -#include +#include "core/kdeconnectplugin.h" -#include -#define PACKAGE_TYPE_NOTIFICATION QLatin1String("kdeconnect.notification") +#define PACKAGE_TYPE_NOTIFICATION QStringLiteral("kdeconnect.notification") /* * This class is just a proxy for NotificationsDbusInterface * because it can not inherit from QDBusAbstractAdaptor and * KdeConnectPlugin at the same time (both are QObject) */ class NotificationsDbusInterface; +class NotificationsListener; -class NotificationsPlugin +class SendNotificationsPlugin : public KdeConnectPlugin { Q_OBJECT public: - explicit NotificationsPlugin(QObject *parent, const QVariantList &args); - virtual ~NotificationsPlugin(); + explicit SendNotificationsPlugin(QObject* parent, const QVariantList& args); + ~SendNotificationsPlugin() override; -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + bool receivePackage(const NetworkPackage& np) override; + void connected() override; -private: - NotificationsDbusInterface* notificationsDbusInterface; +protected: + NotificationsListener* notificationsListener; }; #endif diff --git a/plugins/sftp/CMakeLists.txt b/plugins/sftp/CMakeLists.txt index 4e05e199..74690486 100644 --- a/plugins/sftp/CMakeLists.txt +++ b/plugins/sftp/CMakeLists.txt @@ -1,19 +1,22 @@ +# Find fusermount -- otherwise fallback to umount +find_program(HAVE_FUSERMOUNT fusermount) +configure_file(config-sftp.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-sftp.h ) + find_package(KF5 REQUIRED COMPONENTS Notifications KIO) set(kdeconnect_sftp_SRCS mounter.cpp mountloop.cpp sftpplugin.cpp ) kdeconnect_add_plugin(kdeconnect_sftp JSON kdeconnect_sftp.json SOURCES ${kdeconnect_sftp_SRCS}) target_link_libraries(kdeconnect_sftp kdeconnectcore Qt5::DBus KF5::I18n KF5::KIOFileWidgets - KF5::IconThemes KF5::KIOWidgets KF5::Notifications ) diff --git a/plugins/sftp/config-sftp.h.cmake b/plugins/sftp/config-sftp.h.cmake new file mode 100644 index 00000000..0692c269 --- /dev/null +++ b/plugins/sftp/config-sftp.h.cmake @@ -0,0 +1 @@ +#cmakedefine01 HAVE_FUSERMOUNT diff --git a/plugins/sftp/kdeconnect_sftp.json b/plugins/sftp/kdeconnect_sftp.json index 08a89fb2..b3e20980 100644 --- a/plugins/sftp/kdeconnect_sftp.json +++ b/plugins/sftp/kdeconnect_sftp.json @@ -1,66 +1,101 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "kinnalru@gmail.com", - "Name": "Samoilenko Yuri" + "Email": "kinnalru@gmail.com", + "Name": "Samoilenko Yuri", + "Name[ru]": "Юрий Самойленко", + "Name[sr@ijekavian]": "Јуриј Самојленко", + "Name[sr@ijekavianlatin]": "Jurij Samojlenko", + "Name[sr@latin]": "Jurij Samojlenko", + "Name[sr]": "Јуриј Самојленко", + "Name[uk]": "Юрій Самойленко", + "Name[x-test]": "xxSamoilenko Yurixx" } - ], - "Description": "Browse the remote device filesystem using SFTP", - "Description[ca]": "Navega pel sistema de fitxers del dispositiu remot emprant SFTP", - "Description[cs]": "Prohlížejte souborový systém zařízení pomocí SFTP", - "Description[de]": "Browsen im Dateisystem des entfernten Geräts mit SFTP", - "Description[en_GB]": "Browse the remote device filesystem using SFTP", - "Description[es]": "Navegar por el sistema de archivos del dispositivo remoto usando SFTP", - "Description[fi]": "Selaa etälaitteiden tiedostojärjestelmiä SFTP:llä", - "Description[gl]": "Explore o sistema de ficheiros do dispositivo remoto mediante SFTP.", - "Description[hu]": "Távoli fájlrendszerek böngészése SFTP-n keresztül", - "Description[it]": "Sfoglia il filesystem del dispositivo remoto utilizzando SFTP", - "Description[nl]": "Blader door het bestandssysteem met SFTP op het apparaat op afstand", - "Description[pl]": "Przeglądaj zdalny system plików przy użyciu SFTP", - "Description[pt]": "Navegar pelo sistema de ficheiros de um dispositivo remoto por SFTP", - "Description[pt_BR]": "Navegue pelo sistema de arquivos do dispositivo usando SFTP", - "Description[sk]": "Prehliadať súborový systém vzdialeného zariadenia pomocou SFTP", - "Description[sv]": "Bläddra i fjärrenhetens filsystem med SFTP", - "Description[tr]": "Uzak aygıt dosya sistemine SFTP kullanarak göz atın", - "Description[uk]": "Перегляд файлових систем на сторонніх пристроях за допомогою SFTP", - "Description[x-test]": "xxBrowse the remote device filesystem using SFTPxx", - "Description[zh_CN]": "通过 SFTP 浏览远程设备文件系统", - "EnabledByDefault": true, - "Icon": "folder-documents", - "Id": "kdeconnect_sftp", - "License": "GPL", - "Name": "Remote filesystem browser", - "Name[ca]": "Navegador del sistema de fitxers remot", - "Name[cs]": "Vzdálený prohlížeč souborového systému", - "Name[de]": "Datei-Browser für entferne Systeme", - "Name[en_GB]": "Remote filesystem browser", - "Name[es]": "Navegador de sistema de archivos remoto", - "Name[fi]": "Etätiedostojärjestelmäselain", - "Name[gl]": "Navegador do sistema de ficheiros remoto.", - "Name[hu]": "Távoli fájlrendszer-böngésző", - "Name[it]": "Navigazione del filesystem remoto", - "Name[nl]": "Bestandssysteembrowser op afstand", - "Name[pl]": "Przeglądarka zdalnego systemu plików", - "Name[pt]": "Navegador de sistemas de ficheiros remotos", - "Name[pt_BR]": "Navegador do sistema de arquivos remoto", - "Name[sk]": "Prehliadač vzdialeného súborového systému", - "Name[sv]": "Fjärrfilsystembläddrare", - "Name[tr]": "Uzak dosya sistemi tarayıcısı", - "Name[uk]": "Перегляд віддалених файлових систем", - "Name[x-test]": "xxRemote filesystem browserxx", - "Name[zh_CN]": "远程文件系统浏览器", + ], + "Description": "Browse the remote device filesystem using SFTP", + "Description[ar]": "تصفّح نظام ملفّات الجهاز البعيد باستخدام SFTP", + "Description[ca@valencia]": "Navega pel sistema de fitxers del dispositiu remot emprant SFTP", + "Description[ca]": "Navega pel sistema de fitxers del dispositiu remot emprant SFTP", + "Description[cs]": "Prohlížejte souborový systém zařízení pomocí SFTP", + "Description[da]": "Gennemse den eksterne enheds filsystem med SFTP", + "Description[de]": "Browsen im Dateisystem des entfernten Geräts mit SFTP", + "Description[el]": "Περιήγηση του απομακρυσμένου συστήματος αρχείων με χρήση SFTP", + "Description[es]": "Navegar por el sistema de archivos del dispositivo remoto usando SFTP", + "Description[et]": "Oma seadme failisüsteemi sirvimine SFTP vahendusel", + "Description[eu]": "Arakatu urruneko gailuaren fitxategi-sistema SFTP erabiliz", + "Description[fi]": "Selaa etälaitteiden tiedostojärjestelmiä SFTP:llä", + "Description[fr]": "Naviguer dans le système de fichiers du périphérique distant en utilisant SFTP", + "Description[gl]": "Explore o sistema de ficheiros do dispositivo remoto mediante SFTP.", + "Description[hu]": "Távoli fájlrendszerek böngészése SFTP-n keresztül", + "Description[it]": "Sfoglia il filesystem del dispositivo remoto utilizzando SFTP", + "Description[ko]": "SFTP로 원격 장치 파일 시스템 탐색", + "Description[nl]": "Blader door het bestandssysteem met SFTP op het apparaat op afstand", + "Description[nn]": "Bla gjennom filsystemet på eininga med SFTP", + "Description[pl]": "Przeglądaj zdalny system plików przy użyciu SFTP", + "Description[pt]": "Navegar pelo sistema de ficheiros de um dispositivo remoto por SFTP", + "Description[pt_BR]": "Navegue pelo sistema de arquivos do dispositivo usando SFTP", + "Description[ru]": "Просмотр файловой системы удалённого устройства с помощью SFTP", + "Description[sk]": "Prehliadať súborový systém vzdialeného zariadenia pomocou SFTP", + "Description[sr@ijekavian]": "Прегледање удаљеног фајл система помоћу СФТП‑а", + "Description[sr@ijekavianlatin]": "Pregledanje udaljenog fajl sistema pomoću SFTP‑a", + "Description[sr@latin]": "Pregledanje udaljenog fajl sistema pomoću SFTP‑a", + "Description[sr]": "Прегледање удаљеног фајл система помоћу СФТП‑а", + "Description[sv]": "Bläddra i den andra apparatens filsystem med SFTP", + "Description[tr]": "Uzak aygıt dosya sistemine SFTP kullanarak göz atın", + "Description[uk]": "Перегляд файлових систем на сторонніх пристроях за допомогою SFTP", + "Description[x-test]": "xxBrowse the remote device filesystem using SFTPxx", + "Description[zh_CN]": "通过 SFTP 浏览远程设备文件系统", + "Description[zh_TW]": "使用SFTP來瀏覽遠端的檔案系統", + "EnabledByDefault": true, + "Icon": "folder-documents", + "Id": "kdeconnect_sftp", + "License": "GPL", + "Name": "Remote filesystem browser", + "Name[ar]": "متصفّح نظام الملفّات البعيد", + "Name[ca@valencia]": "Navegador del sistema de fitxers remot", + "Name[ca]": "Navegador del sistema de fitxers remot", + "Name[cs]": "Vzdálený prohlížeč souborového systému", + "Name[da]": "Browser til eksternt filsystem", + "Name[de]": "Datei-Browser für entferne Systeme", + "Name[el]": "Περιηγητής απομακρυσμένου συστήματος αρχείων", + "Name[es]": "Navegador de sistema de archivos remoto", + "Name[et]": "Kaug-failisüsteemi sirvija", + "Name[eu]": "Urruneko fitxategi-sistemaren arakatzailea", + "Name[fi]": "Etätiedostojärjestelmäselain", + "Name[fr]": "Explorateur à distance du système de fichiers", + "Name[gl]": "Navegador do sistema de ficheiros remoto.", + "Name[hu]": "Távoli fájlrendszer-böngésző", + "Name[it]": "Navigazione del filesystem remoto", + "Name[ko]": "원격 파일 시스템 탐색기", + "Name[nl]": "Bestandssysteembrowser op afstand", + "Name[nn]": "Bla gjennom eksternt filsystem", + "Name[pl]": "Przeglądarka zdalnego systemu plików", + "Name[pt]": "Navegador de sistemas de ficheiros remotos", + "Name[pt_BR]": "Navegador do sistema de arquivos remoto", + "Name[ru]": "Удалённый просмотр файловой системы", + "Name[sk]": "Prehliadač vzdialeného súborového systému", + "Name[sr@ijekavian]": "Прегледач удаљеног фајл система", + "Name[sr@ijekavianlatin]": "Pregledač udaljenog fajl sistema", + "Name[sr@latin]": "Pregledač udaljenog fajl sistema", + "Name[sr]": "Прегледач удаљеног фајл система", + "Name[sv]": "Extern filsystembläddrare", + "Name[tr]": "Uzak dosya sistemi tarayıcısı", + "Name[uk]": "Перегляд віддалених файлових систем", + "Name[x-test]": "xxRemote filesystem browserxx", + "Name[zh_CN]": "远程文件系统浏览器", + "Name[zh_TW]": "遠端檔案瀏覽器", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.sftp" - ], + "kdeconnect.sftp.request" + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.sftp" ] -} \ No newline at end of file +} diff --git a/plugins/sftp/mounter.cpp b/plugins/sftp/mounter.cpp index 916b102b..2c1c9c37 100644 --- a/plugins/sftp/mounter.cpp +++ b/plugins/sftp/mounter.cpp @@ -1,215 +1,247 @@ /** * Copyright 2014 Samoilenko Yuri * * 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 "mounter.h" +#include #include #include #include #include "mountloop.h" +#include "config-sftp.h" #include "sftp_debug.h" -#include +#include "kdeconnectconfig.h" Mounter::Mounter(SftpPlugin* sftp) : QObject(sftp) , m_sftp(sftp) - , m_proc(0) + , m_proc(nullptr) , m_mountPoint(sftp->mountPoint()) , m_started(false) { - connect(m_sftp, SIGNAL(packageReceived(NetworkPackage)), this, SLOT(onPakcageReceived(NetworkPackage))); + connect(m_sftp, &SftpPlugin::packageReceived, this, &Mounter::onPakcageReceived); - connect(&m_connectTimer, SIGNAL(timeout()), this, SLOT(onMountTimeout())); + connect(&m_connectTimer, &QTimer::timeout, this, &Mounter::onMountTimeout); - connect(this, SIGNAL(mounted()), &m_connectTimer, SLOT(stop())); - connect(this, SIGNAL(failed(QString)), &m_connectTimer, SLOT(stop())); + connect(this, &Mounter::mounted, &m_connectTimer, &QTimer::stop); + connect(this, &Mounter::failed, &m_connectTimer, &QTimer::stop); m_connectTimer.setInterval(10000); m_connectTimer.setSingleShot(true); - QTimer::singleShot(0, this, SLOT(start())); - qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created"; + QTimer::singleShot(0, this, &Mounter::start); + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created mounter"; } Mounter::~Mounter() { - unmount(); - qCDebug(KDECONNECT_PLUGIN_SFTP) << "Destroyed"; + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Destroy mounter"; + unmount(false); } bool Mounter::wait() { if (m_started) { return true; } - + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting loop to wait for mount"; - + MountLoop loop; - connect(this, SIGNAL(mounted()), &loop, SLOT(successed())); - connect(this, SIGNAL(failed(QString)), &loop, SLOT(failed())); + connect(this, &Mounter::mounted, &loop, &MountLoop::successed); + connect(this, &Mounter::failed, &loop, &MountLoop::failed); return loop.exec(); } void Mounter::onPakcageReceived(const NetworkPackage& np) { - if (np.get("stop", false)) + if (np.get(QStringLiteral("stop"), false)) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "SFTP server stopped"; - unmount(); + unmount(false); return; } //This is the previous code, to access sftp server using KIO. Now we are //using the external binary sshfs, and accessing it as a local filesystem. /* * QUrl url; * url.setScheme("sftp"); * url.setHost(np.get("ip")); * url.setPort(np.get("port").toInt()); * url.setUserName(np.get("user")); * url.setPassword(np.get("password")); * url.setPath(np.get("path")); * new KRun(url, 0); * Q_EMIT mounted(); */ - m_proc.reset(new KProcess(this)); + unmount(false); + + m_proc = new KProcess(); m_proc->setOutputChannelMode(KProcess::MergedChannels); - connect(m_proc.data(), SIGNAL(started()), SLOT(onStarted())); - connect(m_proc.data(), SIGNAL(error(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError))); - connect(m_proc.data(), SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(onFinished(int,QProcess::ExitStatus))); + connect(m_proc, &QProcess::started, this, &Mounter::onStarted); + connect(m_proc, SIGNAL(error(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError))); + connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(onFinished(int,QProcess::ExitStatus))); QDir().mkpath(m_mountPoint); - const QString program = "sshfs"; + const QString program = QStringLiteral("sshfs"); QString path; - if (np.has("multiPaths")) path = "/"; - else path = np.get("path"); + if (np.has(QStringLiteral("multiPaths"))) path = '/'; + else path = np.get(QStringLiteral("path")); + + QHostAddress addr = m_sftp->device()->getLocalIpAddress(); + if (addr == QHostAddress::Null) { + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Device doesn't have a LanDeviceLink, unable to get IP address"; + return; + } + QString ip = addr.toString(); const QStringList arguments = QStringList() - << QString("%1@%2:%3") - .arg(np.get("user")) - .arg(np.get("ip")) - .arg(path) + << QStringLiteral("%1@%2:%3").arg( + np.get(QStringLiteral("user")), + ip, + path) << m_mountPoint - << "-p" << np.get("port") - << "-f" - << "-o" << "IdentityFile=" + KdeConnectConfig::instance()->privateKeyPath() - << "-o" << "StrictHostKeyChecking=no" //Do not ask for confirmation because it is not a known host - << "-o" << "UserKnownHostsFile=/dev/null"; //Prevent storing as a known host - - m_proc->setProgram(program, arguments); + << QStringLiteral("-p") << np.get(QStringLiteral("port")) + << QStringLiteral("-s") // This fixes a bug where file chunks are sent out of order and get corrupted on reception + << QStringLiteral("-f") + << QStringLiteral("-F") << QStringLiteral("/dev/null") //Do not use ~/.ssh/config + << QStringLiteral("-o") << "IdentityFile=" + KdeConnectConfig::instance()->privateKeyPath() + << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no") //Do not ask for confirmation because it is not a known host + << QStringLiteral("-o") << QStringLiteral("UserKnownHostsFile=/dev/null") //Prevent storing as a known host + << QStringLiteral("-o") << QStringLiteral("HostKeyAlgorithms=ssh-dss") //https://bugs.kde.org/show_bug.cgi?id=351725 + << QStringLiteral("-o") << QStringLiteral("uid=") + QString::number(getuid()) + << QStringLiteral("-o") << QStringLiteral("gid=") + QString::number(getgid()) + << QStringLiteral("-o") << QStringLiteral("ServerAliveInterval=30") + << QStringLiteral("-o") << QStringLiteral("password_stdin") + ; - //To debug - //m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out"); - //m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err"); + m_proc->setProgram(program, arguments); - cleanMountPoint(); - - qCDebug(KDECONNECT_PLUGIN_SFTP) << "Staring process: " << m_proc->program().join(" "); + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting process: " << m_proc->program().join(QStringLiteral(" ")); m_proc->start(); + + //qCDebug(KDECONNECT_PLUGIN_SFTP) << "Passing password: " << np.get("password").toLatin1(); + m_proc->write(np.get(QStringLiteral("password")).toLatin1()); + m_proc->write("\n"); + } void Mounter::onStarted() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process started"; m_started = true; Q_EMIT mounted(); - - connect(m_proc.data(), &KProcess::readyReadStandardError, [this]() { - qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << m_proc->readAll(); + + //m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out"); + //m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err"); + + auto proc = m_proc; + connect(m_proc, &KProcess::readyReadStandardError, [proc]() { + qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << proc->readAll(); }); - connect(m_proc.data(), &KProcess::readyReadStandardOutput, [this]() { - qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << m_proc->readAll(); + connect(m_proc, &KProcess::readyReadStandardOutput, [proc]() { + qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << proc->readAll(); }); } void Mounter::onError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed to start"; m_started = false; Q_EMIT failed(i18n("Failed to start sshfs")); } } void Mounter::onFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit) { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process finished (exit code: " << exitCode << ")"; Q_EMIT unmounted(); } else { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed (exit code:" << exitCode << ")"; Q_EMIT failed(i18n("Error when accessing to filesystem")); } - - cleanMountPoint(); - m_proc.reset(); - m_started = false; + + unmount(true); } void Mounter::onMountTimeout() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Timeout: device not responding"; Q_EMIT failed(i18n("Failed to mount filesystem: device not responding")); } void Mounter::start() { - NetworkPackage np(PACKAGE_TYPE_SFTP); - np.set("startBrowsing", true); + NetworkPackage np(PACKAGE_TYPE_SFTP_REQUEST, {{"startBrowsing", true}}); m_sftp->sendPackage(np); - - m_connectTimer.start(); -} -void Mounter::cleanMountPoint() -{ - qCDebug(KDECONNECT_PLUGIN_SFTP()) << "cleanMountPoint"; - KProcess::execute(QStringList() << "fusermount" << "-u" << m_mountPoint, 10000); + m_connectTimer.start(); } -void Mounter::unmount() +void Mounter::unmount(bool finished) { + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Unmount" << m_proc; if (m_proc) { - cleanMountPoint(); - if (m_proc) + if (!finished) { - m_proc->terminate(); - QTimer::singleShot(5000, m_proc.data(), SLOT(kill())); - m_proc->waitForFinished(); + //Process is still running, we want to stop it + //But when the finished signal come, we might have already gone. + //Disconnect everything. + m_proc->disconnect(); + m_proc->kill(); + + auto proc = m_proc; + m_proc = nullptr; + connect(proc, static_cast(&QProcess::finished), + [proc]() { + qCDebug(KDECONNECT_PLUGIN_SFTP) << "Free" << proc; + proc->deleteLater(); + }); + Q_EMIT unmounted(); } + else + m_proc->deleteLater(); + + //Free mount point (won't always succeed if the path is in use) +#if defined(HAVE_FUSERMOUNT) + KProcess::execute(QStringList() << QStringLiteral("fusermount") << QStringLiteral("-u") << m_mountPoint, 10000); +#else + KProcess::execute(QStringList() << QStringLiteral("umount") << m_mountPoint, 10000); +#endif + m_proc = nullptr; } - - m_started = false; + m_started = false; } - diff --git a/plugins/sftp/mounter.h b/plugins/sftp/mounter.h index 76ac860f..fc94996b 100644 --- a/plugins/sftp/mounter.h +++ b/plugins/sftp/mounter.h @@ -1,67 +1,68 @@ /** * Copyright 2014 Samoilenko Yuri * * 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 SFTPPLUGIN_MOUNTJOB_H #define SFTPPLUGIN_MOUNTJOB_H #include #include +#include + #include "sftpplugin.h" class Mounter : public QObject { Q_OBJECT public: - - explicit Mounter(SftpPlugin *sftp); - virtual ~Mounter(); - + + explicit Mounter(SftpPlugin* sftp); + ~Mounter() override; + bool wait(); bool isMounted() const { return m_started; } - + Q_SIGNALS: void mounted(); void unmounted(); void failed(const QString& message); - + private Q_SLOTS: void onPakcageReceived(const NetworkPackage& np); void onStarted(); void onError(QProcess::ProcessError error); void onFinished(int exitCode, QProcess::ExitStatus exitStatus); void onMountTimeout(); void start(); -private: - void cleanMountPoint(); - void unmount(); - +private: + void unmount(bool finished); + private: SftpPlugin* m_sftp; - QScopedPointer m_proc; + KProcess* m_proc; QTimer m_connectTimer; QString m_mountPoint; bool m_started; }; #endif diff --git a/plugins/sftp/sftp_debug.h b/plugins/sftp/sftp_debug.h index 30da8f81..b5724941 100644 --- a/plugins/sftp/sftp_debug.h +++ b/plugins/sftp/sftp_debug.h @@ -1,28 +1,28 @@ /** * 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 . */ #ifndef SFTP_DEBUG_H #define SFTP_DEBUG_H #include Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SFTP) -#endif //SFTP_DEBUG_H \ No newline at end of file +#endif //SFTP_DEBUG_H diff --git a/plugins/sftp/sftpplugin.cpp b/plugins/sftp/sftpplugin.cpp index 981dd09b..dc7a18c4 100644 --- a/plugins/sftp/sftpplugin.cpp +++ b/plugins/sftp/sftpplugin.cpp @@ -1,193 +1,192 @@ /** * Copyright 2014 Samoilenko Yuri * * 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 "sftpplugin.h" #include #include #include +#include #include #include #include #include #include #include "mounter.h" #include "sftp_debug.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_sftp.json", registerPlugin< SftpPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SFTP, "kdeconnect.plugin.sftp") -static const QSet fields_c = QSet() << "ip" << "port" << "user" << "port" << "path"; +static const QSet fields_c = QSet() << QStringLiteral("ip") << QStringLiteral("port") << QStringLiteral("user") << QStringLiteral("port") << QStringLiteral("path"); struct SftpPlugin::Pimpl { - Pimpl() : mounter(0) {} + Pimpl() : m_mounter(nullptr) {} //Add KIO entry to Dolphin's Places - KFilePlacesModel placesModel; - Mounter* mounter; + KFilePlacesModel m_placesModel; + Mounter* m_mounter; }; -SftpPlugin::SftpPlugin(QObject *parent, const QVariantList &args) +SftpPlugin::SftpPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) - , m_d(new Pimpl()) + , d(new Pimpl()) { deviceId = device()->id(); addToDolphin(); qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created device:" << device()->name(); } SftpPlugin::~SftpPlugin() { QDBusConnection::sessionBus().unregisterObject(dbusPath(), QDBusConnection::UnregisterTree); removeFromDolphin(); unmount(); } void SftpPlugin::addToDolphin() { removeFromDolphin(); QUrl kioUrl("kdeconnect://"+deviceId+"/"); - m_d->placesModel.addPlace(device()->name(), kioUrl, "kdeconnect"); + d->m_placesModel.addPlace(device()->name(), kioUrl, QStringLiteral("kdeconnect")); qCDebug(KDECONNECT_PLUGIN_SFTP) << "add to dolphin"; } void SftpPlugin::removeFromDolphin() { QUrl kioUrl("kdeconnect://"+deviceId+"/"); - QModelIndex index = m_d->placesModel.closestItem(kioUrl); + QModelIndex index = d->m_placesModel.closestItem(kioUrl); while (index.row() != -1) { - m_d->placesModel.removePlace(index); - index = m_d->placesModel.closestItem(kioUrl); + d->m_placesModel.removePlace(index); + index = d->m_placesModel.closestItem(kioUrl); } } -void SftpPlugin::connected() -{ - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportScriptableContents); -} - void SftpPlugin::mount() { qCDebug(KDECONNECT_PLUGIN_SFTP) << "Mount device:" << device()->name(); - if (m_d->mounter) { + if (d->m_mounter) { return; } - m_d->mounter = new Mounter(this); - connect(m_d->mounter, SIGNAL(mounted()), this, SLOT(onMounted())); - connect(m_d->mounter, SIGNAL(unmounted()), this, SLOT(onUnmounted())); - connect(m_d->mounter, SIGNAL(failed(QString)), this, SLOT(onFailed(QString))); + d->m_mounter = new Mounter(this); + connect(d->m_mounter, &Mounter::mounted, this, &SftpPlugin::onMounted); + connect(d->m_mounter, &Mounter::unmounted, this, &SftpPlugin::onUnmounted); + connect(d->m_mounter, &Mounter::failed, this, &SftpPlugin::onFailed); } void SftpPlugin::unmount() { - if (m_d->mounter) + if (d->m_mounter) { - m_d->mounter->deleteLater(); - m_d->mounter = 0; + d->m_mounter->deleteLater(); + d->m_mounter = nullptr; } } bool SftpPlugin::mountAndWait() { mount(); - return m_d->mounter->wait(); + return d->m_mounter->wait(); } bool SftpPlugin::isMounted() const { - return m_d->mounter && m_d->mounter->isMounted(); + return d->m_mounter && d->m_mounter->isMounted(); } bool SftpPlugin::startBrowsing() { if (mountAndWait()) { //return new KRun(QUrl::fromLocalFile(mountPoint()), 0); - return new KRun(QUrl("kdeconnect://"+deviceId), 0); + return new KRun(QUrl("kdeconnect://"+deviceId), nullptr); } return false; } bool SftpPlugin::receivePackage(const NetworkPackage& np) { if (!(fields_c - np.body().keys().toSet()).isEmpty()) { // package is invalid return false; } Q_EMIT packageReceived(np); remoteDirectories.clear(); - if (np.has("multiPaths")) { - QStringList paths = np.get("multiPaths",QStringList()); - QStringList names = np.get("pathNames",QStringList()); + if (np.has(QStringLiteral("multiPaths"))) { + QStringList paths = np.get(QStringLiteral("multiPaths"),QStringList()); + QStringList names = np.get(QStringLiteral("pathNames"),QStringList()); int size = qMin(names.size(), paths.size()); for (int i = 0; i < size; i++) { remoteDirectories.insert(mountPoint() + paths.at(i), names.at(i)); } } else { remoteDirectories.insert(mountPoint(), i18n("All files")); remoteDirectories.insert(mountPoint() + "/DCIM/Camera", i18n("Camera pictures")); } return true; } QString SftpPlugin::mountPoint() { - QDir mountDir = config()->privateDirectory(); - return mountDir.absoluteFilePath(deviceId); + QString runtimePath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + if (runtimePath.isEmpty()) { + runtimePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + } + return QDir(runtimePath).absoluteFilePath(deviceId); } void SftpPlugin::onMounted() { - qCDebug(KDECONNECT_PLUGIN_SFTP) << device()->name() << QString("Remote filesystem mounted at %1").arg(mountPoint()); + qCDebug(KDECONNECT_PLUGIN_SFTP) << device()->name() << QStringLiteral("Remote filesystem mounted at %1").arg(mountPoint()); Q_EMIT mounted(); } void SftpPlugin::onUnmounted() { qCDebug(KDECONNECT_PLUGIN_SFTP) << device()->name() << "Remote filesystem unmounted"; unmount(); Q_EMIT unmounted(); } void SftpPlugin::onFailed(const QString& message) { KNotification::event(KNotification::Error, device()->name(), message); unmount(); Q_EMIT unmounted(); } QVariantMap SftpPlugin::getDirectories() { return remoteDirectories; } #include "sftpplugin.moc" diff --git a/plugins/sftp/sftpplugin.h b/plugins/sftp/sftpplugin.h index 48d89528..682e5a07 100644 --- a/plugins/sftp/sftpplugin.h +++ b/plugins/sftp/sftpplugin.h @@ -1,79 +1,79 @@ /** * Copyright 2014 Samoilenko Yuri * * 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 SFTPPLUGIN_H #define SFTPPLUGIN_H #include #include -#define PACKAGE_TYPE_SFTP QLatin1String("kdeconnect.sftp") +#define PACKAGE_TYPE_SFTP_REQUEST QStringLiteral("kdeconnect.sftp.request") class KNotification; class SftpPlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.sftp") public: - explicit SftpPlugin(QObject *parent, const QVariantList &args); - virtual ~SftpPlugin(); + explicit SftpPlugin(QObject* parent, const QVariantList& args); + ~SftpPlugin() override; + + bool receivePackage(const NetworkPackage& np) override; + void connected() override {} + QString dbusPath() const override { return "/modules/kdeconnect/devices/" + deviceId + "/sftp"; } Q_SIGNALS: void packageReceived(const NetworkPackage& np); Q_SCRIPTABLE void mounted(); Q_SCRIPTABLE void unmounted(); - -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); +public Q_SLOTS: Q_SCRIPTABLE void mount(); Q_SCRIPTABLE void unmount(); Q_SCRIPTABLE bool mountAndWait(); Q_SCRIPTABLE bool isMounted() const; Q_SCRIPTABLE bool startBrowsing(); Q_SCRIPTABLE QString mountPoint(); Q_SCRIPTABLE QVariantMap getDirectories(); //Actually a QMap, but QDBus preffers this private Q_SLOTS: void onMounted(); void onUnmounted(); void onFailed(const QString& message); private: - QString dbusPath() const { return "/modules/kdeconnect/devices/" + deviceId + "/sftp"; } void knotify(int type, const QString& text, const QPixmap& icon) const; void addToDolphin(); void removeFromDolphin(); private: struct Pimpl; - QScopedPointer m_d; + QScopedPointer d; QString deviceId; //Storing it to avoid accessing device() from the destructor which could cause a crash QVariantMap remoteDirectories; //Actually a QMap, but QDBus preffers this }; #endif diff --git a/plugins/share/CMakeLists.txt b/plugins/share/CMakeLists.txt index e7d8d938..f72ded46 100644 --- a/plugins/share/CMakeLists.txt +++ b/plugins/share/CMakeLists.txt @@ -1,35 +1,34 @@ find_package(KF5 REQUIRED COMPONENTS Notifications KIO) set(kdeconnect_share_SRCS shareplugin.cpp - autoclosingqfile.cpp ) kdeconnect_add_plugin(kdeconnect_share JSON kdeconnect_share.json SOURCES ${kdeconnect_share_SRCS}) target_link_libraries(kdeconnect_share kdeconnectcore Qt5::DBus KF5::Notifications KF5::I18n KF5::KIOWidgets ) ####################################### # Config set( kdeconnect_share_config_SRCS share_config.cpp ) ki18n_wrap_ui( kdeconnect_share_config_SRCS share_config.ui ) add_library(kdeconnect_share_config MODULE ${kdeconnect_share_config_SRCS} ) target_link_libraries( kdeconnect_share_config kdeconnectpluginkcm KF5::I18n KF5::CoreAddons KF5::ConfigWidgets KF5::KIOWidgets KF5::Notifications ) install(TARGETS kdeconnect_share_config DESTINATION ${PLUGIN_INSTALL_DIR} ) install(FILES kdeconnect_share_config.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/plugins/share/kdeconnect_share.json b/plugins/share/kdeconnect_share.json index 0171601f..68d4194f 100644 --- a/plugins/share/kdeconnect_share.json +++ b/plugins/share/kdeconnect_share.json @@ -1,66 +1,99 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Receive and send files, URLs or plain text easily", - "Description[ca]": "Rep i envia fitxers, URL o text pla amb facilitat", - "Description[cs]": "Snadno přijímejte a posílejte soubory, URL nebo čistý text", - "Description[de]": "Empfang und Senden von Dateien, URLs oder einfachem Text", - "Description[en_GB]": "Receive and send files, URLs or plain text easily", - "Description[es]": "Recibir y enviar archivos, URL o texto sin formato fácilmente", - "Description[fi]": "Vastaanota ja lähetä tiedostoja, verkko-osoitteita tai tekstiä helposti", - "Description[gl]": "Comparta e reciba con facilidade ficheiros, enderezos URL ou texto simple.", - "Description[hu]": "Fájlok, URL-ek és szöveg fogadása és küldése", - "Description[it]": "Ricevi e invia facilmente file, URL o testo semplice", - "Description[nl]": "Bestanden, URL's of platte tekst gemakkelijk ontvangen en verzenden", - "Description[pl]": "Udostępniaj i otrzymuj pliki, adresy URL lub zwykły tekst z łatwością", - "Description[pt]": "Receber e enviar ficheiros, URL's ou texto normal de forma simples", - "Description[pt_BR]": "Recebe e envia facilmente arquivos, URLs ou texto simples", - "Description[sk]": "Prijať a poslať súbory, URL alebo čisté texty jednoducho", - "Description[sv]": "Ta emot och skicka filer, webbadresser eller vanlig text enkelt", - "Description[tr]": "Dosyaları, adres ve düz metinleri kolayca alın ve gönderin", - "Description[uk]": "Спрощене отримання і надсилання файлів, адрес або текстових фрагментів", - "Description[x-test]": "xxReceive and send files, URLs or plain text easilyxx", - "Description[zh_CN]": "简单地接收和发送文件,URL 或者文本", - "EnabledByDefault": true, - "Icon": "folder-network", - "Id": "kdeconnect_share", - "License": "GPL", - "Name": "Share and receive", - "Name[ca]": "Comparteix i rep", - "Name[cs]": "Sdílet a přijímat", - "Name[de]": "Senden und Empfangen", - "Name[en_GB]": "Share and receive", - "Name[es]": "Compartir y recibir", - "Name[fi]": "Jaa ja vastaanota", - "Name[gl]": "Compartir e recibir.", - "Name[hu]": "Megosztás és fogadás", - "Name[it]": "Condividi e ricevi", - "Name[nl]": "Delen en ontvangen", - "Name[pl]": "Udostępniaj i otrzymuj", - "Name[pt]": "Partilhar e receber", - "Name[pt_BR]": "Compartilhar e receber", - "Name[sk]": "Zdieľať a prijať", - "Name[sv]": "Dela och ta emot", - "Name[tr]": "Paylaş ve al", - "Name[uk]": "Оприлюднення і отримання", - "Name[x-test]": "xxShare and receivexx", - "Name[zh_CN]": "分享和接收", + ], + "Description": "Receive and send files, URLs or plain text easily", + "Description[ar]": "استقبل الملفّات أو العناوين أو النّصوص الصّرفة وأرسلها بسهولة", + "Description[ca@valencia]": "Rep i envia fitxers, URL o text pla amb facilitat", + "Description[ca]": "Rep i envia fitxers, URL o text pla amb facilitat", + "Description[cs]": "Snadno přijímejte a posílejte soubory, URL nebo čistý text", + "Description[da]": "Modtag og send nemt filer, URL'er eller klartekst", + "Description[de]": "Empfang und Senden von Dateien, URLs oder einfachem Text", + "Description[el]": "Εύκολη λήψη και αποστολή αρχείων, URL ή απλού κειμένου", + "Description[es]": "Recibir y enviar archivos, URL o texto sin formato fácilmente", + "Description[et]": "Failide, URL-ide või lihtteksti hõlpus vastuvõtmine ja saatmine", + "Description[eu]": "Jaso eta bidali fitxategiak, URL-ak, edo testu soila erraz", + "Description[fi]": "Vastaanota ja lähetä tiedostoja, verkko-osoitteita tai tekstiä helposti", + "Description[fr]": "Recevoir et envoyer des fichiers, des URLs ou du texte brut facilement", + "Description[gl]": "Comparta e reciba con facilidade ficheiros, enderezos URL ou texto simple.", + "Description[hu]": "Fájlok, URL-ek és szöveg fogadása és küldése", + "Description[it]": "Ricevi e invia facilmente file, URL o testo semplice", + "Description[ko]": "파일, URL, 일반 텍스트 공유하고 받기", + "Description[nl]": "Bestanden, URL's of platte tekst gemakkelijk ontvangen en verzenden", + "Description[nn]": "Ta imot og send filer, nettadresser og tekst på ein enkel måte", + "Description[pl]": "Udostępniaj i otrzymuj pliki, adresy URL lub zwykły tekst z łatwością", + "Description[pt]": "Receber e enviar ficheiros, URL's ou texto normal de forma simples", + "Description[pt_BR]": "Recebe e envia facilmente arquivos, URLs ou texto simples", + "Description[ru]": "Получение и отправка файлов, URL адресов или простого текста", + "Description[sk]": "Prijať a poslať súbory, URL alebo čisté texty jednoducho", + "Description[sr@ijekavian]": "Примај и шаљи фајлове, УРЛ‑ове или обичан текст, са лакоћом", + "Description[sr@ijekavianlatin]": "Primaj i šalji fajlove, URL‑ove ili običan tekst, sa lakoćom", + "Description[sr@latin]": "Primaj i šalji fajlove, URL‑ove ili običan tekst, sa lakoćom", + "Description[sr]": "Примај и шаљи фајлове, УРЛ‑ове или обичан текст, са лакоћом", + "Description[sv]": "Ta emot och skicka filer, webbadresser eller vanlig text enkelt", + "Description[tr]": "Dosyaları, adres ve düz metinleri kolayca alın ve gönderin", + "Description[uk]": "Спрощене отримання і надсилання файлів, адрес або текстових фрагментів", + "Description[x-test]": "xxReceive and send files, URLs or plain text easilyxx", + "Description[zh_CN]": "简单地接收和发送文件,URL 或者文本", + "Description[zh_TW]": "接收與傳送檔案,URL網址或純文字檔", + "EnabledByDefault": true, + "Icon": "folder-network", + "Id": "kdeconnect_share", + "License": "GPL", + "Name": "Share and receive", + "Name[ar]": "شارك واستقبل", + "Name[ca@valencia]": "Comparteix i rep", + "Name[ca]": "Comparteix i rep", + "Name[cs]": "Sdílet a přijímat", + "Name[da]": "Del og modtag", + "Name[de]": "Senden und Empfangen", + "Name[el]": "Διαμοιρασμός και λήψη", + "Name[es]": "Compartir y recibir", + "Name[et]": "Jagamine ja vastuvõtmine", + "Name[eu]": "Partekatu eta jaso", + "Name[fi]": "Jaa ja vastaanota", + "Name[fr]": "Partager et recevoir", + "Name[gl]": "Compartir e recibir.", + "Name[hu]": "Megosztás és fogadás", + "Name[it]": "Condividi e ricevi", + "Name[ko]": "공유하고 받기", + "Name[nl]": "Delen en ontvangen", + "Name[nn]": "Del og ta imot", + "Name[pl]": "Udostępniaj i otrzymuj", + "Name[pt]": "Partilhar e receber", + "Name[pt_BR]": "Compartilhar e receber", + "Name[ru]": "Общий доступ и получение", + "Name[sk]": "Zdieľať a prijať", + "Name[sr@ijekavian]": "Дели и примај", + "Name[sr@ijekavianlatin]": "Deli i primaj", + "Name[sr@latin]": "Deli i primaj", + "Name[sr]": "Дели и примај", + "Name[sv]": "Dela och ta emot", + "Name[tr]": "Paylaş ve al", + "Name[uk]": "Оприлюднення і отримання", + "Name[x-test]": "xxShare and receivexx", + "Name[zh_CN]": "分享和接收", + "Name[zh_TW]": "共享與接收", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.share" - ], + "kdeconnect.share.request" + ], "X-KdeConnect-SupportedPackageType": [ - "kdeconnect.share" + "kdeconnect.share.request" ] -} \ No newline at end of file +} diff --git a/plugins/share/kdeconnect_share_config.desktop b/plugins/share/kdeconnect_share_config.desktop index c7df489d..16a34bbd 100644 --- a/plugins/share/kdeconnect_share_config.desktop +++ b/plugins/share/kdeconnect_share_config.desktop @@ -1,34 +1,48 @@ [Desktop Entry] Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kdeconnect_share_config X-KDE-ParentComponents=kdeconnect_share Name=Share plugin settings +Name[ar]=إعدادات ملحقة المشاركة +Name[ast]=Axustes del complementu de compartición Name[bg]=Настройки на приставката за споделяне Name[bs]=Podijeli postavke dodatka Name[ca]=Ajustaments del connector Compartició +Name[ca@valencia]=Ajustaments del connector Compartició Name[cs]=Nastavení modulu sdílení Name[da]=Indstilling af deling-plugin Name[de]=Modul-Einstellungen für Veröffentlichung +Name[el]=Ρυθμίσεις προσθέτου κοινής χρήσης Name[en_GB]=Share plugin settings Name[es]=Ajustes del complemento para compartir +Name[et]=Jagamisplugina seadistused +Name[eu]=Partekatu pluginaren ezarpenak Name[fi]=Jakoliitännäisen asetukset Name[fr]=Paramètres du module de partage Name[gl]=Configuración do complemento de compartir +Name[he]=שתף את הגדרות התוספים Name[hu]=Megosztás bővítmény beállításai Name[it]=Impostazioni estensione Condivisione Name[ko]=공유 플러그인 설정 Name[nl]=Plug-in-instellingen van delen +Name[nn]=Innstillingar for deling Name[pl]=Ustawienia wtyczki udostępniania Name[pt]=Configuração do 'plugin' de partilha Name[pt_BR]=Compartilhar as configurações do plugin +Name[ru]=Настройка модуля обмена файлами Name[sk]=Nastavenia pluginu zdieľania +Name[sr]=Поставке прикључка дељења +Name[sr@ijekavian]=Поставке прикључка дељења +Name[sr@ijekavianlatin]=Postavke priključka deljenja +Name[sr@latin]=Postavke priključka deljenja Name[sv]=Inställningar av delningsinsticksprogram Name[tr]=Paylaşma eklentisi ayarları Name[uk]=Параметри додатка оприлюднення даних Name[x-test]=xxShare plugin settingsxx Name[zh_CN]=分享插件设置 +Name[zh_TW]=共享擴展插件設定 Categories=Qt;KDE;X-KDE-settings-kdeconnect; diff --git a/plugins/share/share_config.cpp b/plugins/share/share_config.cpp index 84a9b173..a469c80c 100644 --- a/plugins/share/share_config.cpp +++ b/plugins/share/share_config.cpp @@ -1,74 +1,76 @@ /** * 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 "share_config.h" #include "ui_share_config.h" #include #include #include K_PLUGIN_FACTORY(ShareConfigFactory, registerPlugin();) -ShareConfig::ShareConfig(QWidget *parent, const QVariantList& args) - : KdeConnectPluginKcm(parent, args, "kdeconnect_share_config") +ShareConfig::ShareConfig(QWidget* parent, const QVariantList& args) + : KdeConnectPluginKcm(parent, args, QStringLiteral("kdeconnect_share_config")) , m_ui(new Ui::ShareConfigUi()) { m_ui->setupUi(this); // xgettext:no-c-format - m_ui->commentLabel->setText(i18n("%1 in the path will be replaced with the specific device name.")); + m_ui->commentLabel->setTextFormat(Qt::RichText); + m_ui->commentLabel->setText(i18n("%1 in the path will be replaced with the specific device name.")); connect(m_ui->kurlrequester, SIGNAL(textChanged(QString)), this, SLOT(changed())); } ShareConfig::~ShareConfig() { delete m_ui; } void ShareConfig::defaults() { KCModule::defaults(); - m_ui->kurlrequester->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation))); + m_ui->kurlrequester->setText(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); Q_EMIT changed(true); } void ShareConfig::load() { KCModule::load(); - m_ui->kurlrequester->setUrl(QUrl::fromLocalFile(config()->get("incoming_path", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)))); + const auto standardPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + m_ui->kurlrequester->setText(config()->get(QStringLiteral("incoming_path"), standardPath)); Q_EMIT changed(false); } void ShareConfig::save() { - config()->set("incoming_path", m_ui->kurlrequester->text()); + config()->set(QStringLiteral("incoming_path"), m_ui->kurlrequester->text()); KCModule::save(); Q_EMIT changed(false); } #include "share_config.moc" diff --git a/plugins/share/share_config.h b/plugins/share/share_config.h index b3f837cb..a0bb0a6a 100644 --- a/plugins/share/share_config.h +++ b/plugins/share/share_config.h @@ -1,48 +1,48 @@ /** * 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 SHARE_CONFIG_H #define SHARE_CONFIG_H #include "kcmplugin/kdeconnectpluginkcm.h" namespace Ui { class ShareConfigUi; } class ShareConfig : public KdeConnectPluginKcm { Q_OBJECT public: - ShareConfig(QWidget *parent, const QVariantList&); - virtual ~ShareConfig(); + ShareConfig(QWidget* parent, const QVariantList&); + ~ShareConfig() override; public Q_SLOTS: - virtual void save(); - virtual void load(); - virtual void defaults(); + void save() override; + void load() override; + void defaults() override; private: Ui::ShareConfigUi* m_ui; }; #endif diff --git a/plugins/share/share_debug.h b/plugins/share/share_debug.h index d6cf4161..92e7b152 100644 --- a/plugins/share/share_debug.h +++ b/plugins/share/share_debug.h @@ -1,28 +1,28 @@ /** * 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 . */ #ifndef SHARE_DEBUG_H #define SHARE_DEBUG_H #include Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SHARE) -#endif //SHARE_DEBUG_H \ No newline at end of file +#endif //SHARE_DEBUG_H diff --git a/plugins/share/shareplugin.cpp b/plugins/share/shareplugin.cpp index b4331b32..c2a1e053 100644 --- a/plugins/share/shareplugin.cpp +++ b/plugins/share/shareplugin.cpp @@ -1,163 +1,174 @@ /** * 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 "shareplugin.h" #include "share_debug.h" #include #include #include #include #include #include +#include #include #include #include #include -#include -#include "autoclosingqfile.h" +#include "core/filetransferjob.h" K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_share.json", registerPlugin< SharePlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SHARE, "kdeconnect.plugin.share") SharePlugin::SharePlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) { } QUrl SharePlugin::destinationDir() const { const QString defaultDownloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - QUrl dir = QUrl::fromLocalFile(config()->get("incoming_path", defaultDownloadPath)); + QUrl dir = QUrl::fromLocalFile(config()->get(QStringLiteral("incoming_path"), defaultDownloadPath)); - if (dir.path().contains("%1")) { + if (dir.path().contains(QLatin1String("%1"))) { dir.setPath(dir.path().arg(device()->name())); } KJob* job = KIO::mkpath(dir); bool ret = job->exec(); if (!ret) { qWarning() << "couldn't create" << dir; } return dir; } +static QString cleanFilename(const QString &filename) +{ + int idx = filename.lastIndexOf(QLatin1Char('/')); + return idx>=0 ? filename.mid(idx + 1) : filename; +} + bool SharePlugin::receivePackage(const NetworkPackage& np) { /* //TODO: Write a test like this if (np.type() == PACKAGE_TYPE_PING) { qCDebug(KDECONNECT_PLUGIN_SHARE) << "sending file" << (QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.bashrc"); - NetworkPackage out(PACKAGE_TYPE_SHARE); + NetworkPackage out(PACKAGE_TYPE_SHARE_REQUEST); out.set("filename", mDestinationDir + "itworks.txt"); AutoClosingQFile* file = new AutoClosingQFile(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.bashrc"); //Test file to transfer out.setPayload(file, file->size()); device()->sendPackage(out); return true; } */ qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer"; if (np.hasPayload()) { - //qCDebug(KDECONNECT_PLUGIN_SHARE) << "receiving file"; - const QString filename = np.get("filename", QString::number(QDateTime::currentMSecsSinceEpoch())); + const QString filename = cleanFilename(np.get(QStringLiteral("filename"), QString::number(QDateTime::currentMSecsSinceEpoch()))); const QUrl dir = destinationDir().adjusted(QUrl::StripTrailingSlash); - QUrl destination(dir.toString() + '/' + filename); + QUrl destination(dir); + destination.setPath(dir.path() + '/' + filename, QUrl::DecodedMode); if (destination.isLocalFile() && QFile::exists(destination.toLocalFile())) { - destination = QUrl(dir.toString() + '/' + KIO::suggestName(dir, filename)); + destination.setPath(dir.path() + '/' + KIO::suggestName(dir, filename), QUrl::DecodedMode); } +// qCDebug(KDECONNECT_PLUGIN_SHARE) << "receiving file" << filename << "in" << dir << "into" << destination; FileTransferJob* job = np.createPayloadTransferJob(destination); - job->setDeviceName(device()->name()); - connect(job, SIGNAL(result(KJob*)), this, SLOT(finished(KJob*))); + job->setOriginName(device()->name() + ": " + filename); + connect(job, &KJob::result, this, &SharePlugin::finished); KIO::getJobTracker()->registerJob(job); job->start(); - } else if (np.has("text")) { - QString text = np.get("text"); - if (!QStandardPaths::findExecutable("kate").isEmpty()) { + } else if (np.has(QStringLiteral("text"))) { + QString text = np.get(QStringLiteral("text")); + if (!QStandardPaths::findExecutable(QStringLiteral("kate")).isEmpty()) { QProcess* proc = new QProcess(); connect(proc, SIGNAL(finished(int)), proc, SLOT(deleteLater())); - proc->start("kate", QStringList("--stdin")); + proc->start(QStringLiteral("kate"), QStringList(QStringLiteral("--stdin"))); proc->write(text.toUtf8()); proc->closeWriteChannel(); } else { QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); tmpFile.open(); tmpFile.write(text.toUtf8()); tmpFile.close(); - QDesktopServices::openUrl(QUrl::fromLocalFile(tmpFile.fileName())); + + const QString fileName = tmpFile.fileName(); + Q_EMIT shareReceived(fileName); + QDesktopServices::openUrl(QUrl::fromLocalFile(fileName)); } - } else if (np.has("url")) { - QUrl url = QUrl::fromEncoded(np.get("url")); + } else if (np.has(QStringLiteral("url"))) { + QUrl url = QUrl::fromEncoded(np.get(QStringLiteral("url"))); QDesktopServices::openUrl(url); + Q_EMIT shareReceived(url.toString()); } else { qCDebug(KDECONNECT_PLUGIN_SHARE) << "Error: Nothing attached!"; } return true; - } void SharePlugin::finished(KJob* job) { - qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer finished. Success:" << (!job->error()); + FileTransferJob* ftjob = qobject_cast(job); + if (ftjob && !job->error()) { + Q_EMIT shareReceived(ftjob->destination().toString()); + qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer finished." << ftjob->destination(); + } else { + qCDebug(KDECONNECT_PLUGIN_SHARE) << "File transfer failed." << (ftjob ? ftjob->destination() : QUrl()); + } } void SharePlugin::openDestinationFolder() { QDesktopServices::openUrl(destinationDir()); } void SharePlugin::shareUrl(const QUrl& url) { - NetworkPackage package(PACKAGE_TYPE_SHARE); + NetworkPackage package(PACKAGE_TYPE_SHARE_REQUEST); if(url.isLocalFile()) { QSharedPointer ioFile(new QFile(url.toLocalFile())); package.setPayload(ioFile, ioFile->size()); - package.set("filename", QUrl(url).fileName()); + package.set(QStringLiteral("filename"), QUrl(url).fileName()); } else { - package.set("url", url.toString()); + package.set(QStringLiteral("url"), url.toString()); } sendPackage(package); } -void SharePlugin::connected() -{ - QDBusConnection::sessionBus().registerObject(dbusPath(), this, QDBusConnection::ExportAllContents); -} - QString SharePlugin::dbusPath() const { return "/modules/kdeconnect/devices/" + device()->id() + "/share"; } #include "shareplugin.moc" diff --git a/plugins/share/shareplugin.h b/plugins/share/shareplugin.h index de00693d..e3ca64b0 100644 --- a/plugins/share/shareplugin.h +++ b/plugins/share/shareplugin.h @@ -1,57 +1,60 @@ /** * 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 SHAREPLUGIN_H #define SHAREPLUGIN_H #include #include #include -#define PACKAGE_TYPE_SHARE QLatin1String("kdeconnect.share") +#define PACKAGE_TYPE_SHARE_REQUEST QStringLiteral("kdeconnect.share.request") class SharePlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.share") public: - explicit SharePlugin(QObject *parent, const QVariantList &args); + explicit SharePlugin(QObject* parent, const QVariantList& args); ///Helper method, QDBus won't recognize QUrl Q_SCRIPTABLE void shareUrl(const QString& url) { shareUrl(QUrl(url)); } -public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected(); + + bool receivePackage(const NetworkPackage& np) override; + void connected() override {} + QString dbusPath() const override; private Q_SLOTS: void finished(KJob*); void openDestinationFolder(); +Q_SIGNALS: + void shareReceived(const QString& url); + private: void shareUrl(const QUrl& url); - QString dbusPath() const; QUrl destinationDir() const; }; #endif diff --git a/plugins/telephony/CMakeLists.txt b/plugins/telephony/CMakeLists.txt index 7c8958c1..f9d4614f 100644 --- a/plugins/telephony/CMakeLists.txt +++ b/plugins/telephony/CMakeLists.txt @@ -1,13 +1,15 @@ find_package(KF5 REQUIRED COMPONENTS Notifications) -set(kdeconnect_telephony_SRCS - telephonyplugin.cpp -) +include_directories(${CMAKE_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../notifications/) # needed for the sendreplydialog -kdeconnect_add_plugin(kdeconnect_telephony JSON kdeconnect_telephony.json SOURCES ${kdeconnect_telephony_SRCS}) +ki18n_wrap_ui(kdeconnect_telephony_SRCS ../notifications/sendreplydialog.ui) +kdeconnect_add_plugin(kdeconnect_telephony JSON kdeconnect_telephony.json SOURCES telephonyplugin.cpp ../notifications/sendreplydialog.cpp ${kdeconnect_telephony_SRCS}) target_link_libraries(kdeconnect_telephony kdeconnectcore KF5::I18n KF5::Notifications + Qt5::DBus + Qt5::Widgets ) diff --git a/plugins/telephony/kdeconnect_telephony.json b/plugins/telephony/kdeconnect_telephony.json index 448cfa31..832af0ae 100644 --- a/plugins/telephony/kdeconnect_telephony.json +++ b/plugins/telephony/kdeconnect_telephony.json @@ -1,66 +1,98 @@ { - "Encoding": "UTF-8", + "Encoding": "UTF-8", "KPlugin": { "Authors": [ { - "Email": "albertvaka@gmail.com", - "Name": "Albert Vaca" + "Email": "albertvaka@gmail.com", + "Name": "Albert Vaca", + "Name[sr@ijekavian]": "Алберт Вака Синтора", + "Name[sr@ijekavianlatin]": "Albert Vaka Sintora", + "Name[sr@latin]": "Albert Vaka Sintora", + "Name[sr]": "Алберт Вака Синтора", + "Name[x-test]": "xxAlbert Vacaxx" } - ], - "Description": "Show notifications for calls and SMS (answering coming soon)", - "Description[ca]": "Mostra les notificacions de les trucades i SMS (properament es respondrà)", - "Description[cs]": "Zobrazit upozornění pro telefonáty a SMS (na odpovídání se pracuje)", - "Description[de]": "Zeigt Benachrichtigungen für Anrufe und SMS", - "Description[en_GB]": "Show notifications for calls and SMS (answering coming soon)", - "Description[es]": "Mostrar notificaciones para llamadas y SMS (el soporte para las contestaciones es inminente)", - "Description[fi]": "Näytä puhelujen ja tekstiviestien ilmoitukset (vastaaminen tulossa pian)", - "Description[gl]": "Mostrar notificacións de chamadas e mensaxes SMS (no futuro tamén poderá contestar chamadas).", - "Description[hu]": "Értesítések megjelenítése hívásokról és SMS-ekről", - "Description[it]": "Mostra notifiche per le chiamate e per gli SMS (la risposta arriverà)", - "Description[nl]": "Meldingen tonen van oproepen en SMSjes (beantwoorden komt spoedig)", - "Description[pl]": "Pokaż powiadomienia dla dzwonienia i SMS (odpowiadanie wkrótce)", - "Description[pt]": "Mostrar notificações para as chamadas e SMS (atendimento em breve)", - "Description[pt_BR]": "Mostra notificações para chamadas e SMS (resposta em breve)", - "Description[sk]": "Zobraziť oznámenia pre hovory a SMS (čoskoro aj odpovede)", - "Description[sv]": "Visa underrättelser om samtal och SMS (att svara kommer snart)", - "Description[tr]": "Çağrı ve SMS'ler için bildirim göster (yanıtlama yakında geliyor)", - "Description[uk]": "Показ сповіщень щодо дзвінків і SMS (скоро буде реалізовано і автовідповідач)", - "Description[x-test]": "xxShow notifications for calls and SMS (answering coming soon)xx", - "Description[zh_CN]": "显示来电和短信通知 (应答稍后实现)", - "EnabledByDefault": true, - "Icon": "call-start", - "Id": "kdeconnect_telephony", - "License": "GPL", - "Name": "Telephony integration", - "Name[ca]": "Integració amb la telefonia", - "Name[cs]": "Integrace telefonu", - "Name[de]": "Telefon-Integration", - "Name[en_GB]": "Telephony integration", - "Name[es]": "Integración con el teléfono", - "Name[fi]": "Puhelinintegrointi", - "Name[gl]": "Integración telefónica", - "Name[hu]": "Telefonintegráció", - "Name[it]": "Integrazione telefonica", - "Name[nl]": "Telefoonintegratie", - "Name[pl]": "Integracja z telefonem", - "Name[pt]": "Integração telefónica", - "Name[pt_BR]": "Integração telefônica", - "Name[sk]": "Integrácia telefónie", - "Name[sv]": "Integrering av telefoni", - "Name[tr]": "Telefon tümleştirmesi", - "Name[uk]": "Інтеграція з системою телефонії", - "Name[x-test]": "xxTelephony integrationxx", - "Name[zh_CN]": "Telephony 集成", + ], + "Description": "Show notifications for calls and SMS", + "Description[ar]": "أظهر إخطارات المكالمات والرّسائل", + "Description[ca@valencia]": "Mostra les notificacions de les trucades i els SMS", + "Description[ca]": "Mostra les notificacions de les trucades i els SMS", + "Description[cs]": "Zobrazit upozornění pro telefonáty a SMS", + "Description[da]": "Vis bekendtgørelser for opkald og sms", + "Description[de]": "Benachrichtigungen für Anrufe und SMS anzeigen", + "Description[el]": "Εμφάνιση ειδοποιήσεων για κλήσεις και SMS", + "Description[es]": "Mostrar notificaciones de llamadas y SMS", + "Description[et]": "Kõnede ja SMS-ide märguannete näitamine", + "Description[eu]": "Erakutsi deien eta SMS mezuen jakinarazpenak", + "Description[fi]": "Näytä puhelujen ja tekstiviestien ilmoitukset", + "Description[fr]": "Affichez les notifications pour les appels et les SMS", + "Description[gl]": "Mostrar notificacións de chamadas e mensaxes SMS.", + "Description[it]": "Mostra notifiche per le chiamate e per gli SMS", + "Description[ko]": "통화와 SMS 알림 표시", + "Description[nl]": "Meldingen tonen van oproepen en SMSjes", + "Description[nn]": "Vis varslingar for oppringingar og SMS", + "Description[pl]": "Powiadamiaj o dzwonieniu i esemesach", + "Description[pt]": "Mostrar notificações para as chamadas e SMS", + "Description[pt_BR]": "Mostra notificações para chamadas e SMS", + "Description[ru]": "Показ уведомлений для звонков и SMS", + "Description[sr@ijekavian]": "Приказује обавештења за позиве и СМС", + "Description[sr@ijekavianlatin]": "Prikazuje obaveštenja za pozive i SMS", + "Description[sr@latin]": "Prikazuje obaveštenja za pozive i SMS", + "Description[sr]": "Приказује обавештења за позиве и СМС", + "Description[sv]": "Visa underrättelser om samtal och SMS", + "Description[tr]": "Çağrı ve SMS'ler için bildirim göster", + "Description[uk]": "Показ сповіщень щодо дзвінків і SMS", + "Description[x-test]": "xxShow notifications for calls and SMSxx", + "Description[zh_CN]": "显示来电和短信通知", + "Description[zh_TW]": "顯示來電與短訊通知", + "EnabledByDefault": true, + "Icon": "call-start", + "Id": "kdeconnect_telephony", + "License": "GPL", + "Name": "Telephony integration", + "Name[ar]": "تكامل الهاتف", + "Name[ca@valencia]": "Integració amb la telefonia", + "Name[ca]": "Integració amb la telefonia", + "Name[cs]": "Integrace telefonu", + "Name[da]": "Integration af telefoni", + "Name[de]": "Telefon-Integration", + "Name[el]": "Ενσωμάτωση τηλεφωνίας", + "Name[es]": "Integración con el teléfono", + "Name[et]": "Telefoniga lõimimine", + "Name[eu]": "Telefoniarekin integrazioa", + "Name[fi]": "Puhelinintegrointi", + "Name[fr]": "Intégration de la téléphonie", + "Name[gl]": "Integración telefónica", + "Name[hu]": "Telefonintegráció", + "Name[it]": "Integrazione telefonica", + "Name[ko]": "전화 통합", + "Name[nl]": "Telefoonintegratie", + "Name[nn]": "Telefonintegrering", + "Name[pl]": "Integracja z telefonem", + "Name[pt]": "Integração telefónica", + "Name[pt_BR]": "Integração telefônica", + "Name[ru]": "Интеграция телефонии", + "Name[sk]": "Integrácia telefónie", + "Name[sr@ijekavian]": "Интегрисана телефонија", + "Name[sr@ijekavianlatin]": "Integrisana telefonija", + "Name[sr@latin]": "Integrisana telefonija", + "Name[sr]": "Интегрисана телефонија", + "Name[sv]": "Integrering av telefoni", + "Name[tr]": "Telefon tümleştirmesi", + "Name[uk]": "Інтеграція з системою телефонії", + "Name[x-test]": "xxTelephony integrationxx", + "Name[zh_CN]": "Telephony 集成", + "Name[zh_TW]": "集成電話", "ServiceTypes": [ "KdeConnect/Plugin" - ], - "Version": "0.1", + ], + "Version": "0.1", "Website": "http://albertvaka.wordpress.com" - }, + }, "X-KdeConnect-OutgoingPackageType": [ - "kdeconnect.telephony" - ], + "kdeconnect.telephony.request", + "kdeconnect.sms.request" + ], "X-KdeConnect-SupportedPackageType": [ "kdeconnect.telephony" ] -} \ No newline at end of file +} diff --git a/plugins/telephony/telephonyplugin.cpp b/plugins/telephony/telephonyplugin.cpp index c81498fe..02aee86d 100644 --- a/plugins/telephony/telephonyplugin.cpp +++ b/plugins/telephony/telephonyplugin.cpp @@ -1,121 +1,170 @@ /** * 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 "telephonyplugin.h" +#include "sendreplydialog.h" + #include #include #include +#include #include - K_PLUGIN_FACTORY_WITH_JSON( KdeConnectPluginFactory, "kdeconnect_telephony.json", registerPlugin< TelephonyPlugin >(); ) Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_TELEPHONY, "kdeconnect.plugin.telephony") -TelephonyPlugin::TelephonyPlugin(QObject *parent, const QVariantList &args) +TelephonyPlugin::TelephonyPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) + , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect")) { - } KNotification* TelephonyPlugin::createNotification(const NetworkPackage& np) { - - const QString event = np.get("event"); - const QString phoneNumber = np.get("phoneNumber", i18n("unknown number")); + const QString event = np.get(QStringLiteral("event")); + const QString phoneNumber = np.get(QStringLiteral("phoneNumber"), i18n("unknown number")); + const QString contactName = np.get(QStringLiteral("contactName"), phoneNumber); + const QByteArray phoneThumbnail = QByteArray::fromBase64(np.get(QStringLiteral("phoneThumbnail"), "")); + + // In case telepathy can handle the message, don't do anything else + if (event == QLatin1String("sms") && m_telepathyInterface.isValid()) { + qCDebug(KDECONNECT_PLUGIN_TELEPHONY) << "Passing a text message to the telepathy interface"; + connect(&m_telepathyInterface, SIGNAL(messageReceived(QString,QString)), SLOT(sendSms(QString,QString)), Qt::UniqueConnection); + const QString messageBody = np.get(QStringLiteral("messageBody"),QLatin1String("")); + QDBusReply reply = m_telepathyInterface.call(QStringLiteral("sendMessage"), phoneNumber, contactName, messageBody); + if (reply) { + return nullptr; + } else { + qCDebug(KDECONNECT_PLUGIN_TELEPHONY) << "Telepathy failed, falling back to the default handling"; + } + } QString content, type, icon; KNotification::NotificationFlags flags = KNotification::CloseOnTimeout; const QString title = device()->name(); - if (event == "ringing") { + if (event == QLatin1String("ringing")) { type = QStringLiteral("callReceived"); icon = QStringLiteral("call-start"); - content = i18n("Incoming call from %1", phoneNumber); - } else if (event == "missedCall") { + content = i18n("Incoming call from %1", contactName); + } else if (event == QLatin1String("missedCall")) { type = QStringLiteral("missedCall"); icon = QStringLiteral("call-start"); - content = i18n("Missed call from %1", phoneNumber); - flags = KNotification::Persistent; - } else if (event == "sms") { + content = i18n("Missed call from %1", contactName); + flags |= KNotification::Persistent; //Note that in Unity this generates a message box! + } else if (event == QLatin1String("sms")) { type = QStringLiteral("smsReceived"); icon = QStringLiteral("mail-receive"); - QString messageBody = np.get("messageBody",""); - content = i18n("SMS from %1
%2", phoneNumber, messageBody); - flags = KNotification::Persistent; - } else if (event == "talking") { - return NULL; + QString messageBody = np.get(QStringLiteral("messageBody"), QLatin1String("")); + content = i18n("SMS from %1
%2", contactName, messageBody); + flags |= KNotification::Persistent; //Note that in Unity this generates a message box! + } else if (event == QLatin1String("talking")) { + return nullptr; } else { #ifndef NDEBUG - return NULL; + return nullptr; #else type = QStringLiteral("callReceived"); icon = QStringLiteral("phone"); content = i18n("Unknown telephony event: %1", event); #endif } qCDebug(KDECONNECT_PLUGIN_TELEPHONY) << "Creating notification with type:" << type; KNotification* notification = new KNotification(type, flags, this); - notification->setIconName(icon); - notification->setComponentName("kdeconnect"); + if (!phoneThumbnail.isEmpty()) { + QPixmap photo; + photo.loadFromData(phoneThumbnail, "JPEG"); + notification->setPixmap(photo); + } else { + notification->setIconName(icon); + } + notification->setComponentName(QStringLiteral("kdeconnect")); notification->setTitle(title); notification->setText(content); if (event == QLatin1String("ringing")) { notification->setActions( QStringList(i18n("Mute Call")) ); connect(notification, &KNotification::action1Activated, this, &TelephonyPlugin::sendMutePackage); + } else if (event == QLatin1String("sms")) { + const QString messageBody = np.get(QStringLiteral("messageBody"),QLatin1String("")); + notification->setActions( QStringList(i18n("Reply")) ); + notification->setProperty("phoneNumber", phoneNumber); + notification->setProperty("contactName", contactName); + notification->setProperty("originalMessage", messageBody); + connect(notification, &KNotification::action1Activated, this, &TelephonyPlugin::showSendSmsDialog); } return notification; } bool TelephonyPlugin::receivePackage(const NetworkPackage& np) { - if (np.get("isCancel")) { - - //It would be awesome to remove the old notification from the system tray here, but there is no way to do it :( - //Now I realize why at the end of the day I have hundreds of notifications from facebook messages that I HAVE ALREADY READ, - //...it's just because the telepathy client has no way to remove them! even when it knows that I have read those messages! - - } else { - - KNotification* n = createNotification(np); - if (n != NULL) n->sendEvent(); + if (np.get(QStringLiteral("isCancel"))) { + //TODO: Clear the old notification + return true; } - return true; + KNotification* n = createNotification(np); + if (n != nullptr) n->sendEvent(); + return true; } void TelephonyPlugin::sendMutePackage() { - NetworkPackage package(PACKAGE_TYPE_TELEPHONY); - package.set( "action", "mute" ); + NetworkPackage package(PACKAGE_TYPE_TELEPHONY_REQUEST, {{"action", "mute"}}); sendPackage(package); } +void TelephonyPlugin::sendSms(const QString& phoneNumber, const QString& messageBody) +{ + NetworkPackage np(PACKAGE_TYPE_SMS_REQUEST, { + {"sendSms", true}, + {"phoneNumber", phoneNumber}, + {"messageBody", messageBody} + }); + sendPackage(np); +} + +void TelephonyPlugin::showSendSmsDialog() +{ + QString phoneNumber = sender()->property("phoneNumber").toString(); + QString contactName = sender()->property("contactName").toString(); + QString originalMessage = sender()->property("originalMessage").toString(); + SendReplyDialog* dialog = new SendReplyDialog(originalMessage, phoneNumber, contactName); + connect(dialog, &SendReplyDialog::sendReply, this, &TelephonyPlugin::sendSms); + dialog->show(); + dialog->raise(); +} + +QString TelephonyPlugin::dbusPath() const +{ + return "/modules/kdeconnect/devices/" + device()->id() + "/telephony"; +} + #include "telephonyplugin.moc" diff --git a/plugins/telephony/telephonyplugin.h b/plugins/telephony/telephonyplugin.h index ce74e9e2..a1f3c7bb 100644 --- a/plugins/telephony/telephonyplugin.h +++ b/plugins/telephony/telephonyplugin.h @@ -1,52 +1,62 @@ /** * 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 TELEPHONYPLUGIN_H #define TELEPHONYPLUGIN_H #include +#include #include #include -#define PACKAGE_TYPE_TELEPHONY QLatin1String("kdeconnect.telephony") +#define PACKAGE_TYPE_TELEPHONY_REQUEST QStringLiteral("kdeconnect.telephony.request") +#define PACKAGE_TYPE_SMS_REQUEST QStringLiteral("kdeconnect.sms.request") Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_TELEPHONY) class TelephonyPlugin : public KdeConnectPlugin { Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.telephony") public: - explicit TelephonyPlugin(QObject *parent, const QVariantList &args); + explicit TelephonyPlugin(QObject* parent, const QVariantList& args); + + bool receivePackage(const NetworkPackage& np) override; + void connected() override {} + QString dbusPath() const override; public Q_SLOTS: - virtual bool receivePackage(const NetworkPackage& np); - virtual void connected() { } + Q_SCRIPTABLE void sendSms(const QString& phoneNumber, const QString& messageBody); + +private Q_SLOTS: void sendMutePackage(); + void showSendSmsDialog(); private: KNotification* createNotification(const NetworkPackage& np); + QDBusInterface m_telepathyInterface; }; #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b00a574d..78322832 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,17 +1,33 @@ -find_package(KF5 REQUIRED COMPONENTS KIO) +find_package(Qt5 REQUIRED COMPONENTS Test) +find_package(KF5 REQUIRED COMPONENTS KIO Notifications IconThemes) include_directories( ${KDEConnectCore_BINARY_DIR} ${CMAKE_SOURCE_DIR} ) set(kdeconnect_libraries - kdeconnectcore + kdeconnectcore + KF5::I18n KF5::KIOWidgets + Qt5::DBus Qt5::Network Qt5::Test qca-qt5 ) +ecm_add_test(pluginloadtest.cpp LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(sendfiletest.cpp LINK_LIBRARIES ${kdeconnect_libraries}) ecm_add_test(networkpackagetests.cpp LINK_LIBRARIES ${kdeconnect_libraries}) -ecm_add_test(testsocketlinereader.cpp ../core/backends/lan/socketlinereader.cpp TEST_NAME testsocketlinereader LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(testsocketlinereader.cpp TEST_NAME testsocketlinereader LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(testsslsocketlinereader.cpp TEST_NAME testsslsocketlinereader LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(kdeconnectconfigtest.cpp TEST_NAME kdeconnectconfigtest LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(lanlinkprovidertest.cpp TEST_NAME lanlinkprovidertest LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(devicetest.cpp TEST_NAME devicetest LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(downloadjobtest.cpp TEST_NAME downloadjobtest LINK_LIBRARIES ${kdeconnect_libraries}) +ecm_add_test(testnotificationlistener.cpp + ../plugins/sendnotifications/sendnotificationsplugin.cpp + ../plugins/sendnotifications/notificationslistener.cpp + ../plugins/sendnotifications/notifyingapplication.cpp + TEST_NAME testnotificationlistener + LINK_LIBRARIES ${kdeconnect_libraries} Qt5::DBus KF5::Notifications KF5::IconThemes) diff --git a/tests/devicetest.cpp b/tests/devicetest.cpp new file mode 100644 index 00000000..77228900 --- /dev/null +++ b/tests/devicetest.cpp @@ -0,0 +1,126 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../core/device.h" +#include "../core/backends/lan/lanlinkprovider.h" +#include "../core/kdeconnectconfig.h" + +#include + +/** + * This class tests the working of device class + */ +class DeviceTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + + void initTestCase(); + void testUnpairedDevice(); + void testPairedDevice(); + void cleanupTestCase(); + +private: + QString deviceId; + QString deviceName; + QString deviceType; + NetworkPackage* identityPackage; + +}; + +void DeviceTest::initTestCase() +{ + deviceId = QStringLiteral("testdevice"); + deviceName = QStringLiteral("Test Device"); + deviceType = QStringLiteral("smartphone"); + QString stringPackage = QStringLiteral("{\"id\":1439365924847,\"type\":\"kdeconnect.identity\",\"body\":{\"deviceId\":\"testdevice\",\"deviceName\":\"Test Device\",\"protocolVersion\":6,\"deviceType\":\"phone\"}}"); + identityPackage = new NetworkPackage(QStringLiteral("kdeconnect.identity")); + NetworkPackage::unserialize(stringPackage.toLatin1(), identityPackage); +} + +void DeviceTest::testPairedDevice() +{ + KdeConnectConfig* kcc = KdeConnectConfig::instance(); + kcc->addTrustedDevice(deviceId, deviceName, deviceType); + kcc->setDeviceProperty(deviceId, QStringLiteral("certificate"), QString::fromLatin1(kcc->certificate().toPem())); // Using same certificate from kcc, instead of generating one + + Device device(this, deviceId); + + QCOMPARE(device.id(), deviceId); + QCOMPARE(device.name(), deviceName); + QCOMPARE(device.type(), deviceType); + + QCOMPARE(device.isTrusted(), true); + + QCOMPARE(device.isReachable(), false); + + // Add link + LanLinkProvider linkProvider; + QSslSocket socket; + LanDeviceLink* link = new LanDeviceLink(deviceId, &linkProvider, &socket, LanDeviceLink::Locally); + device.addLink(*identityPackage, link); + + QCOMPARE(device.isReachable(), true); + QCOMPARE(device.availableLinks().contains(linkProvider.name()), true); + + // Remove link + device.removeLink(link); + + QCOMPARE(device.isReachable(), false); + QCOMPARE(device.availableLinks().contains(linkProvider.name()), false); + + device.unpair(); + QCOMPARE(device.isTrusted(), false); + +} + +void DeviceTest::testUnpairedDevice() +{ + LanLinkProvider linkProvider; + QSslSocket socket; + LanDeviceLink* link = new LanDeviceLink(deviceId, &linkProvider, &socket, LanDeviceLink::Locally); + + Device device(this, *identityPackage, link); + + QCOMPARE(device.id(), deviceId); + QCOMPARE(device.name(), deviceName); + QCOMPARE(device.type(), deviceType); + + QCOMPARE(device.isTrusted(), false); + + QCOMPARE(device.isReachable(), true); + QCOMPARE(device.availableLinks().contains(linkProvider.name()), true); + + // Remove link + device.removeLink(link); + + QCOMPARE(device.isReachable(), false); + QCOMPARE(device.availableLinks().contains(linkProvider.name()), false); +} + +void DeviceTest::cleanupTestCase() +{ + delete identityPackage; +} + +QTEST_GUILESS_MAIN(DeviceTest) + +#include "devicetest.moc" diff --git a/tests/downloadjobtest.cpp b/tests/downloadjobtest.cpp new file mode 100644 index 00000000..d59e3f96 --- /dev/null +++ b/tests/downloadjobtest.cpp @@ -0,0 +1,83 @@ +/************************************************************************************* + * Copyright (C) 2014 by Albert Vaca Cintora * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + *************************************************************************************/ + +#include "../core/backends/lan/downloadjob.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DownloadJobTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void failToConnectShouldDestroyTheJob(); + void closingTheConnectionShouldDestroyTheJob(); + +private: + void initServer(); + void initDownloadJob(); + void awaitToBeDestroyedOrTimeOut(); + void stopServer(); + + QPointer m_test; + QPointer m_server; +}; + +void DownloadJobTest::initServer() +{ + delete m_server; + m_server = new QTcpServer(this); + QVERIFY2(m_server->listen(QHostAddress::LocalHost, 8694), "Failed to create local tcp server"); +} + +void DownloadJobTest::failToConnectShouldDestroyTheJob() +{ + // no initServer + m_test = new DownloadJob(QHostAddress::LocalHost, {{"port", 8694}}); + + QSignalSpy spy(m_test.data(), &KJob::finished); + m_test->start(); + + QVERIFY(spy.count() || spy.wait()); + + QCOMPARE(m_test->error(), 1); +} + +void DownloadJobTest::closingTheConnectionShouldDestroyTheJob() +{ + initServer(); + m_test = new DownloadJob(QHostAddress::LocalHost, {{"port", 8694}}); + QSignalSpy spy(m_test.data(), &KJob::finished); + m_test->start(); + m_server->close(); + + QVERIFY(spy.count() || spy.wait(2000)); +} + +QTEST_GUILESS_MAIN(DownloadJobTest) + +#include "downloadjobtest.moc" diff --git a/tests/kdeconnectconfigtest.cpp b/tests/kdeconnectconfigtest.cpp new file mode 100644 index 00000000..d43ae95d --- /dev/null +++ b/tests/kdeconnectconfigtest.cpp @@ -0,0 +1,94 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + + +#include "../core/kdeconnectconfig.h" + +#include + +/* + * This class tests the working of kdeconnect config that certificate and key is generated and saved properly + */ +class KdeConnectConfigTest : public QObject +{ +Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void addTrustedDevice(); +/* + void remoteCertificateTest(); +*/ + void removeTrustedDevice(); + +private: + KdeConnectConfig* kcc; +}; + +void KdeConnectConfigTest::initTestCase() +{ + kcc = KdeConnectConfig::instance(); +} + +void KdeConnectConfigTest::addTrustedDevice() +{ + kcc->addTrustedDevice(QStringLiteral("testdevice"), QStringLiteral("Test Device"), QStringLiteral("phone")); + KdeConnectConfig::DeviceInfo devInfo = kcc->getTrustedDevice(QStringLiteral("testdevice")); + QCOMPARE(devInfo.deviceName, QString("Test Device")); + QCOMPARE(devInfo.deviceType, QString("phone")); +} + +/* +// This checks whether certificate is generated correctly and stored correctly or not +void KdeConnectConfigTest::remoteCertificateTest() +{ + QSslCertificate certificate = kcc->certificate(); // Using same certificate as of device + + QCOMPARE(certificate.serialNumber().toInt(0,16), 10); + QCOMPARE(certificate.subjectInfo(QSslCertificate::SubjectInfo::CommonName).first(), kcc->deviceId()); + QCOMPARE(certificate.subjectInfo(QSslCertificate::SubjectInfo::Organization).first(), QString("KDE")); + QCOMPARE(certificate.subjectInfo(QSslCertificate::OrganizationalUnitName).first(), QString("Kde connect")); + + kcc->setDeviceProperty("testdevice","certificate", QString::fromLatin1(certificate.toPem())); + + KdeConnectConfig::DeviceInfo devInfo = kcc->getTrustedDevice("testdevice"); + QSslCertificate devCertificate = QSslCertificate::fromData(devInfo.certificate.toLatin1()).first(); + + QCOMPARE(devCertificate.serialNumber().toInt(0,16), 10); + QCOMPARE(devCertificate.subjectInfo(QSslCertificate::SubjectInfo::CommonName).first(), kcc->deviceId()); + QCOMPARE(devCertificate.subjectInfo(QSslCertificate::SubjectInfo::Organization).first(), QString("KDE")); + QCOMPARE(devCertificate.subjectInfo(QSslCertificate::OrganizationalUnitName).first(), QString("Kde connect")); + +} +*/ + + +void KdeConnectConfigTest::removeTrustedDevice() +{ + kcc->removeTrustedDevice(QStringLiteral("testdevice")); + KdeConnectConfig::DeviceInfo devInfo = kcc->getTrustedDevice(QStringLiteral("testdevice")); + QCOMPARE(devInfo.deviceName, QString("unnamed")); + QCOMPARE(devInfo.deviceType, QString("unknown")); +} + +QTEST_GUILESS_MAIN(KdeConnectConfigTest) + +#include "kdeconnectconfigtest.moc" diff --git a/tests/lanlinkprovidertest.cpp b/tests/lanlinkprovidertest.cpp new file mode 100644 index 00000000..926b8848 --- /dev/null +++ b/tests/lanlinkprovidertest.cpp @@ -0,0 +1,356 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// This class tests the behaviour of the class LanLinkProvider, be sure to kill process kdeconnectd to avoid any port binding issues + + +#include "../core/backends/lan/lanlinkprovider.h" +#include "../core/backends/lan/server.h" +#include "../core/backends/lan/socketlinereader.h" +#include "../core/kdeconnectconfig.h" + +#include +#include +#include +#include +#include + +/* + * This class tests the working of LanLinkProvider under different conditions that when identity package is received over TCP, over UDP and same when the device is paired. + * It depends on KdeConnectConfig since LanLinkProvider internally uses it. + */ +class LanLinkProviderTest : public QObject +{ + Q_OBJECT +public: + explicit LanLinkProviderTest() + : m_lanLinkProvider(true) { + QStandardPaths::setTestModeEnabled(true); + } + +public Q_SLOTS: + void initTestCase(); + +private Q_SLOTS: + + void pairedDeviceTcpPackageReceived(); + void pairedDeviceUdpPackageReceived(); + + void unpairedDeviceTcpPackageReceived(); + void unpairedDeviceUdpPackageReceived(); + + +private: + const int TEST_PORT = 8520; + // Add some private fields here + LanLinkProvider m_lanLinkProvider; + Server* m_server; + SocketLineReader* m_reader; + QUdpSocket* m_udpSocket; + QString m_identityPackage; + + // Attributes for test device + QString m_deviceId; + QString m_name; + QCA::PrivateKey m_privateKey; + QSslCertificate m_certificate; + + QSslCertificate generateCertificate(QString&, QCA::PrivateKey&); + void addTrustedDevice(); + void removeTrustedDevice(); + void setSocketAttributes(QSslSocket* socket); + void testIdentityPackage(QByteArray& identityPackage); + +}; + +void LanLinkProviderTest::initTestCase() +{ + removeTrustedDevice(); // Remove trusted device if left by chance by any test + + m_deviceId = QStringLiteral("testdevice"); + m_name = QStringLiteral("Test Device"); + m_privateKey = QCA::KeyGenerator().createRSA(2048); + m_certificate = generateCertificate(m_deviceId, m_privateKey); + + m_lanLinkProvider.onStart(); + + m_identityPackage = QStringLiteral("{\"id\":1439365924847,\"type\":\"kdeconnect.identity\",\"body\":{\"deviceId\":\"testdevice\",\"deviceName\":\"Test Device\",\"protocolVersion\":6,\"deviceType\":\"phone\",\"tcpPort\":") + QString::number(TEST_PORT) + QStringLiteral("}}"); +} + +void LanLinkProviderTest::pairedDeviceTcpPackageReceived() +{ + KdeConnectConfig* kcc = KdeConnectConfig::instance(); + addTrustedDevice(); + + QUdpSocket* mUdpServer = new QUdpSocket; + bool b = mUdpServer->bind(QHostAddress::LocalHost, LanLinkProvider::UDP_PORT, QUdpSocket::ShareAddress); + QVERIFY(b); + + QSignalSpy spy(mUdpServer, SIGNAL(readyRead())); + m_lanLinkProvider.onNetworkChange(); + QVERIFY(!spy.isEmpty() || spy.wait()); + + QByteArray datagram; + datagram.resize(mUdpServer->pendingDatagramSize()); + QHostAddress sender; + + mUdpServer->readDatagram(datagram.data(), datagram.size(), &sender); + + testIdentityPackage(datagram); + + QJsonDocument jsonDocument = QJsonDocument::fromJson(datagram); + QJsonObject body = jsonDocument.object().value(QStringLiteral("body")).toObject(); + int tcpPort = body.value(QStringLiteral("tcpPort")).toInt(); + + QSslSocket socket; + QSignalSpy spy2(&socket, SIGNAL(connected())); + + socket.connectToHost(sender, tcpPort); + QVERIFY(spy2.wait()); + + QVERIFY2(socket.isOpen(), "Socket disconnected immediately"); + + socket.write(m_identityPackage.toLatin1()); + socket.waitForBytesWritten(2000); + + QSignalSpy spy3(&socket, SIGNAL(encrypted())); + + setSocketAttributes(&socket); + socket.addCaCertificate(kcc->certificate()); + socket.setPeerVerifyMode(QSslSocket::VerifyPeer); + socket.setPeerVerifyName(kcc->name()); + + socket.startServerEncryption(); + QVERIFY(spy3.wait()); + + QCOMPARE(socket.sslErrors().size(), 0); + QVERIFY2(socket.isValid(), "Server socket disconnected"); + QVERIFY2(socket.isEncrypted(), "Server socket not yet encrypted"); + QVERIFY2(!socket.peerCertificate().isNull(), "Peer certificate is null"); + + removeTrustedDevice(); + delete mUdpServer; +} + +void LanLinkProviderTest::pairedDeviceUdpPackageReceived() +{ + KdeConnectConfig* kcc = KdeConnectConfig::instance(); + addTrustedDevice(); + + m_server = new Server(this); + m_udpSocket = new QUdpSocket(this); + + m_server->listen(QHostAddress::LocalHost, TEST_PORT); + + QSignalSpy spy(m_server, SIGNAL(newConnection())); + + qint64 bytesWritten = m_udpSocket->writeDatagram(m_identityPackage.toLatin1(), QHostAddress::LocalHost, LanLinkProvider::UDP_PORT); // write an identity package to udp socket here, we do not broadcast it here + QCOMPARE(bytesWritten, m_identityPackage.size()); + + // We should have an incoming connection now, wait for incoming connection + QVERIFY(!spy.isEmpty() || spy.wait()); + + QSslSocket* serverSocket = m_server->nextPendingConnection(); + + QVERIFY2(serverSocket != 0, "Server socket is null"); + QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); + + m_reader = new SocketLineReader(serverSocket, this); + QSignalSpy spy2(m_reader, SIGNAL(readyRead())); + QVERIFY(spy2.wait()); + + QByteArray receivedPackage = m_reader->readLine(); + testIdentityPackage(receivedPackage); + // Received identiy package from LanLinkProvider now start ssl + + QSignalSpy spy3(serverSocket, SIGNAL(encrypted())); + QVERIFY(connect(serverSocket, static_cast(&QSslSocket::error), + this, [](QAbstractSocket::SocketError error){ qDebug() << "error:" << error; })); + + setSocketAttributes(serverSocket); + serverSocket->addCaCertificate(kcc->certificate()); + serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); + serverSocket->setPeerVerifyName(kcc->deviceId()); + + serverSocket->startClientEncryption(); // Its TCP server. but SSL client + QVERIFY(!serverSocket->isEncrypted()); + spy3.wait(2000); + qDebug() << "xxxxxxxxx" << serverSocket->sslErrors(); + + QCOMPARE(serverSocket->sslErrors().size(), 0); + QVERIFY2(serverSocket->isValid(), "Server socket disconnected"); + QVERIFY2(serverSocket->isEncrypted(), "Server socket not yet encrypted"); + QVERIFY2(!serverSocket->peerCertificate().isNull(), "Peer certificate is null"); + + removeTrustedDevice(); + + delete m_server; + delete m_udpSocket; +} + +void LanLinkProviderTest::unpairedDeviceTcpPackageReceived() +{ + QUdpSocket* mUdpServer = new QUdpSocket; + bool b = mUdpServer->bind(QHostAddress::LocalHost, LanLinkProvider::UDP_PORT, QUdpSocket::ShareAddress); + QVERIFY(b); + + QSignalSpy spy(mUdpServer, SIGNAL(readyRead())); + m_lanLinkProvider.onNetworkChange(); + QVERIFY(!spy.isEmpty() || spy.wait()); + + QByteArray datagram; + datagram.resize(mUdpServer->pendingDatagramSize()); + QHostAddress sender; + + mUdpServer->readDatagram(datagram.data(), datagram.size(), &sender); + + testIdentityPackage(datagram); + + QJsonDocument jsonDocument = QJsonDocument::fromJson(datagram); + QJsonObject body = jsonDocument.object().value(QStringLiteral("body")).toObject(); + int tcpPort = body.value(QStringLiteral("tcpPort")).toInt(); + + QSslSocket socket; + QSignalSpy spy2(&socket, SIGNAL(connected())); + + socket.connectToHost(sender, tcpPort); + QVERIFY(spy2.wait()); + + QVERIFY2(socket.isOpen(), "Socket disconnected immediately"); + + socket.write(m_identityPackage.toLatin1()); + socket.waitForBytesWritten(2000); + + QSignalSpy spy3(&socket, SIGNAL(encrypted())); + // We don't take care for sslErrors signal here, but signal will emit still we will get successful connection + + setSocketAttributes(&socket); + socket.setPeerVerifyMode(QSslSocket::QueryPeer); + + socket.startServerEncryption(); + + QVERIFY(spy3.wait()); + + QVERIFY2(socket.isValid(), "Server socket disconnected"); + QVERIFY2(socket.isEncrypted(), "Server socket not yet encrypted"); + QVERIFY2(!socket.peerCertificate().isNull(), "Peer certificate is null"); + + delete mUdpServer; +} + +void LanLinkProviderTest::unpairedDeviceUdpPackageReceived() +{ + m_server = new Server(this); + m_udpSocket = new QUdpSocket(this); + + m_server->listen(QHostAddress::LocalHost, TEST_PORT); + + QSignalSpy spy(m_server, &Server::newConnection); + qint64 bytesWritten = m_udpSocket->writeDatagram(m_identityPackage.toLatin1(), QHostAddress::LocalHost, LanLinkProvider::UDP_PORT); // write an identity package to udp socket here, we do not broadcast it here + QCOMPARE(bytesWritten, m_identityPackage.size()); + + QVERIFY(!spy.isEmpty() || spy.wait()); + + QSslSocket* serverSocket = m_server->nextPendingConnection(); + + QVERIFY2(serverSocket != 0, "Server socket is null"); + QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); + + m_reader = new SocketLineReader(serverSocket, this); + QSignalSpy spy2(m_reader, &SocketLineReader::readyRead); + QVERIFY(spy2.wait()); + + QByteArray receivedPackage = m_reader->readLine(); + QVERIFY2(!receivedPackage.isEmpty(), "Empty package received"); + + testIdentityPackage(receivedPackage); + + // Received identity package from LanLinkProvider now start ssl + + QSignalSpy spy3(serverSocket, SIGNAL(encrypted())); + + setSocketAttributes(serverSocket); + serverSocket->setPeerVerifyMode(QSslSocket::QueryPeer); + serverSocket->startClientEncryption(); // Its TCP server. but SSL client + QVERIFY(spy3.wait()); + + QVERIFY2(serverSocket->isValid(), "Server socket disconnected"); + QVERIFY2(serverSocket->isEncrypted(), "Server socket not yet encrypted"); + QVERIFY2(!serverSocket->peerCertificate().isNull(), "Peer certificate is null"); + + delete m_server; + delete m_udpSocket; +} + +void LanLinkProviderTest::testIdentityPackage(QByteArray& identityPackage) +{ + QJsonDocument jsonDocument = QJsonDocument::fromJson(identityPackage); + QJsonObject jsonObject = jsonDocument.object(); + QJsonObject body = jsonObject.value(QStringLiteral("body")).toObject(); + + QCOMPARE(jsonObject.value("type").toString(), QString("kdeconnect.identity")); + QVERIFY2(body.contains("deviceName"), "Device name not found in identity package"); + QVERIFY2(body.contains("deviceId"), "Device id not found in identity package"); + QVERIFY2(body.contains("protocolVersion"), "Protocol version not found in identity package"); + QVERIFY2(body.contains("deviceType"), "Device type not found in identity package"); +} + +QSslCertificate LanLinkProviderTest::generateCertificate(QString& commonName, QCA::PrivateKey& privateKey) +{ + QDateTime startTime = QDateTime::currentDateTime(); + QDateTime endTime = startTime.addYears(10); + QCA::CertificateInfo certificateInfo; + certificateInfo.insert(QCA::CommonName,commonName); + certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); + certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); + + QCA::CertificateOptions certificateOptions(QCA::PKCS10); + certificateOptions.setSerialNumber(10); + certificateOptions.setInfo(certificateInfo); + certificateOptions.setValidityPeriod(startTime, endTime); + certificateOptions.setFormat(QCA::PKCS10); + + QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privateKey).toPEM().toLatin1()); + return certificate; +} + +void LanLinkProviderTest::setSocketAttributes(QSslSocket* socket) +{ + socket->setPrivateKey(QSslKey(m_privateKey.toPEM().toLatin1(), QSsl::Rsa)); + socket->setLocalCertificate(m_certificate); +} + +void LanLinkProviderTest::addTrustedDevice() +{ + KdeConnectConfig* kcc = KdeConnectConfig::instance(); + kcc->addTrustedDevice(m_deviceId, m_name, QStringLiteral("phone")); + kcc->setDeviceProperty(m_deviceId, QStringLiteral("certificate"), QString::fromLatin1(m_certificate.toPem())); +} + +void LanLinkProviderTest::removeTrustedDevice() +{ + KdeConnectConfig* kcc = KdeConnectConfig::instance(); + kcc->removeTrustedDevice(m_deviceId); +} + + +QTEST_GUILESS_MAIN(LanLinkProviderTest) + +#include "lanlinkprovidertest.moc" diff --git a/tests/networkpackagetests.cpp b/tests/networkpackagetests.cpp index d7079b4b..d0d11a69 100644 --- a/tests/networkpackagetests.cpp +++ b/tests/networkpackagetests.cpp @@ -1,159 +1,96 @@ /** * 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 "networkpackagetests.h" #include "core/networkpackage.h" #include +#include QTEST_GUILESS_MAIN(NetworkPackageTests); void NetworkPackageTests::initTestCase() { // Called before the first testfunction is executed } -void NetworkPackageTests::dummyTest() -{ - QDate date; - date.setDate( 1967, 3, 11 ); - QVERIFY( date.isValid() ); - QCOMPARE( date.month(), 3 ); - QCOMPARE( QDate::longMonthName(date.month()), QString("March") ); - - QCOMPARE(QString("hello").toUpper(), QString("HELLO")); -} - void NetworkPackageTests::networkPackageTest() { - NetworkPackage np("com.test"); + NetworkPackage np(QStringLiteral("com.test")); - np.set("hello","hola"); + np.set(QStringLiteral("hello"),"hola"); QCOMPARE( (np.get("hello","bye")) , QString("hola") ); - np.set("hello",""); + np.set(QStringLiteral("hello"),""); QCOMPARE( (np.get("hello","bye")) , QString("") ); - np.body().remove("hello"); + np.body().remove(QStringLiteral("hello")); QCOMPARE( (np.get("hello","bye")) , QString("bye") ); - np.set("foo", "bar"); + np.set(QStringLiteral("foo"), "bar"); QByteArray ba = np.serialize(); //qDebug() << "Serialized package:" << ba; - NetworkPackage np2(""); + NetworkPackage np2(QLatin1String("")); NetworkPackage::unserialize(ba,&np2); QCOMPARE( np.id(), np2.id() ); QCOMPARE( np.type(), np2.type() ); QCOMPARE( np.body(), np2.body() ); QByteArray json("{\"id\":\"123\",\"type\":\"test\",\"body\":{\"testing\":true}}"); //qDebug() << json; NetworkPackage::unserialize(json,&np2); QCOMPARE( np2.id(), QString("123") ); QCOMPARE( (np2.get("testing")), true ); QCOMPARE( (np2.get("not_testing")), false ); QCOMPARE( (np2.get("not_testing",true)), true ); //NetworkPackage::unserialize("this is not json",&np2); //QtTest::ignoreMessage(QtSystemMsg, "json_parser - syntax error found, forcing abort, Line 1 Column 0"); //QtTest::ignoreMessage(QtDebugMsg, "Unserialization error: 1 \"syntax error, unexpected string\""); } void NetworkPackageTests::networkPackageIdentityTest() { - NetworkPackage np(""); + NetworkPackage np(QLatin1String("")); NetworkPackage::createIdentityPackage(&np); - QCOMPARE( np.get("protocolVersion") , NetworkPackage::ProtocolVersion ); + QCOMPARE( np.get("protocolVersion", -1) , NetworkPackage::s_protocolVersion ); QCOMPARE( np.type() , PACKAGE_TYPE_IDENTITY ); } -void NetworkPackageTests::networkPackageEncryptionTest() -{ - QCA::Initializer init; - if(!QCA::isSupported("rsa")) { - QFAIL("RSA isn't supported by your QCA. "); - return; - } - - - NetworkPackage original("com.test"); - original.set("hello","hola"); - - NetworkPackage copy(""); - NetworkPackage::unserialize(original.serialize(), ©); - - NetworkPackage decrypted(""); - - QCA::PrivateKey privateKey = QCA::KeyGenerator().createRSA(2048); - QVERIFY(!privateKey.isNull()); - QCA::PublicKey publicKey = privateKey.toPublicKey(); - - - //Encrypt and decrypt np - QCOMPARE( original.type(), QString("com.test") ); - original.encrypt(publicKey); - QCOMPARE( original.type(), PACKAGE_TYPE_ENCRYPTED ); - original.decrypt(privateKey, &decrypted); - QCOMPARE( original.type(), PACKAGE_TYPE_ENCRYPTED ); - QCOMPARE( decrypted.type(), QString("com.test") ); - - //np should be equal top np2 - QCOMPARE( decrypted.id(), copy.id() ); - QCOMPARE( decrypted.type(), copy.type() ); - QCOMPARE( decrypted.body(), copy.body() ); - - - //Test for long package encryption that need multi-chunk encryption - - QByteArray json = "{\"body\":{\"nowPlaying\":\"A really long song name - A really long artist name\",\"player\":\"A really long player name\",\"the_meaning_of_life_the_universe_and_everything\":\"42\"},\"id\":\"A really long package id\",\"payloadSize\":0,\"payloadTransferInfo\":{},\"type\":\"kdeconnect.a_really_really_long_package_type\"}\n"; - qDebug() << "EME_PKCS1_OAEP maximumEncryptSize" << publicKey.maximumEncryptSize(QCA::EME_PKCS1_OAEP); - qDebug() << "EME_PKCS1v15 maximumEncryptSize" << publicKey.maximumEncryptSize(QCA::EME_PKCS1v15); - QCOMPARE( json.size() > publicKey.maximumEncryptSize(NetworkPackage::EncryptionAlgorithm), true ); - - NetworkPackage::unserialize(json, &original); - original.encrypt(publicKey); - original.decrypt(privateKey, &decrypted); - QByteArray decryptedJson = decrypted.serialize(); - - QCOMPARE(QString(decryptedJson), QString(json)); - -} - - void NetworkPackageTests::cleanupTestCase() { // Called after the last testfunction was executed } void NetworkPackageTests::init() { // Called before each testfunction is executed } void NetworkPackageTests::cleanup() { // Called after every testfunction } diff --git a/tests/networkpackagetests.h b/tests/networkpackagetests.h index bb2b7437..973e9168 100644 --- a/tests/networkpackagetests.h +++ b/tests/networkpackagetests.h @@ -1,45 +1,44 @@ /** * 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 NETWORKPACKAGETESTS_H #define NETWORKPACKAGETESTS_H #include class NetworkPackageTests : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); - void dummyTest(); void networkPackageTest(); void networkPackageIdentityTest(); - void networkPackageEncryptionTest(); + //void networkPackageEncryptionTest(); void cleanupTestCase(); void init(); void cleanup(); }; #endif diff --git a/tests/pluginloadtest.cpp b/tests/pluginloadtest.cpp new file mode 100644 index 00000000..4b735181 --- /dev/null +++ b/tests/pluginloadtest.cpp @@ -0,0 +1,85 @@ +/** + * Copyright 2015 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/daemon.h" +#include "core/device.h" +#include "core/kdeconnectplugin.h" +#include +#include "kdeconnect-version.h" +#include "testdaemon.h" + +class PluginLoadTest : public QObject +{ + Q_OBJECT + public: + PluginLoadTest() { + QStandardPaths::setTestModeEnabled(true); + m_daemon = new TestDaemon; + } + + private Q_SLOTS: + void testPlugins() { + Device* d = nullptr; + m_daemon->acquireDiscoveryMode(QStringLiteral("plugintest")); + const QList devicesList = m_daemon->devicesList(); + for (Device* id : devicesList) { + if (id->isReachable()) { + if (!id->isTrusted()) + id->requestPair(); + d = id; + break; + } + } + m_daemon->releaseDiscoveryMode(QStringLiteral("plugintest")); + + if (!d->loadedPlugins().contains(QStringLiteral("kdeconnect_remotecontrol"))) { + QSKIP("kdeconnect_remotecontrol is required for this test"); + } + + QVERIFY(d); + QVERIFY(d->isTrusted()); + QVERIFY(d->isReachable()); + + d->setPluginEnabled(QStringLiteral("kdeconnect_mousepad"), false); + QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), false); + QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol")); + + d->setPluginEnabled(QStringLiteral("kdeconnect_mousepad"), true); + QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), true); + QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol")); + } + + private: + TestDaemon* m_daemon; +}; + +QTEST_MAIN(PluginLoadTest); + +#include "pluginloadtest.moc" diff --git a/tests/sendfiletest.cpp b/tests/sendfiletest.cpp new file mode 100644 index 00000000..b5d2af24 --- /dev/null +++ b/tests/sendfiletest.cpp @@ -0,0 +1,149 @@ +/** + * Copyright 2015 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/daemon.h" +#include "core/device.h" +#include "core/kdeconnectplugin.h" +#include +#include "kdeconnect-version.h" +#include "testdaemon.h" + +class TestSendFile : public QObject +{ + Q_OBJECT + public: + TestSendFile() { + QStandardPaths::setTestModeEnabled(true); + m_daemon = new TestDaemon; + } + + private Q_SLOTS: + void testSend() { + m_daemon->acquireDiscoveryMode(QStringLiteral("test")); + Device* d = nullptr; + const QList devicesList = m_daemon->devicesList(); + for (Device* id : devicesList) { + if (id->isReachable()) { + if (!id->isTrusted()) + id->requestPair(); + d = id; + } + } + m_daemon->releaseDiscoveryMode(QStringLiteral("test")); + QVERIFY(d); + QCOMPARE(d->isReachable(), true); + QCOMPARE(d->isTrusted(), true); + + QByteArray content("12312312312313213123213123"); + + QTemporaryFile temp; + temp.open(); + temp.write(content); + temp.close(); + + KdeConnectPlugin* plugin = d->plugin(QStringLiteral("kdeconnect_share")); + QVERIFY(plugin); + plugin->metaObject()->invokeMethod(plugin, "shareUrl", Q_ARG(QString, QUrl::fromLocalFile(temp.fileName()).toString())); + + QSignalSpy spy(plugin, SIGNAL(shareReceived(QString))); + QVERIFY(spy.wait(2000)); + + QVariantList args = spy.takeFirst(); + QUrl sentFile(args.first().toUrl()); + + QFile file(sentFile.toLocalFile()); + QCOMPARE(file.size(), content.size()); + QVERIFY(file.open(QIODevice::ReadOnly)); + QCOMPARE(file.readAll(), content); + } + + void testSslJobs() + { + const QString aFile = QFINDTESTDATA("sendfiletest.cpp"); + const QString destFile = QDir::tempPath() + "/kdeconnect-test-sentfile"; + QFile(destFile).remove(); + + const QString deviceId = KdeConnectConfig::instance()->deviceId() + , deviceName = QStringLiteral("testdevice") + , deviceType = KdeConnectConfig::instance()->deviceType(); + + KdeConnectConfig* kcc = KdeConnectConfig::instance(); + kcc->addTrustedDevice(deviceId, deviceName, deviceType); + kcc->setDeviceProperty(deviceId, QStringLiteral("certificate"), QString::fromLatin1(kcc->certificate().toPem())); // Using same certificate from kcc, instead of generating + + QSharedPointer f(new QFile(aFile)); + UploadJob* uj = new UploadJob(f, deviceId); + QSignalSpy spyUpload(uj, &KJob::result); + uj->start(); + + auto info = uj->transferInfo(); + info.insert(QStringLiteral("deviceId"), deviceId); + info.insert(QStringLiteral("size"), aFile.size()); + + DownloadJob* dj = new DownloadJob(QHostAddress::LocalHost, info); + + QVERIFY(dj->getPayload()->open(QIODevice::ReadOnly)); + + FileTransferJob* ft = new FileTransferJob(dj->getPayload(), uj->transferInfo()[QStringLiteral("size")].toInt(), QUrl::fromLocalFile(destFile)); + + QSignalSpy spyDownload(dj, &KJob::result); + QSignalSpy spyTransfer(ft, &KJob::result); + + ft->start(); + dj->start(); + + QVERIFY(spyTransfer.count() || spyTransfer.wait(1000000000)); + + if (ft->error()) qWarning() << "fterror" << ft->errorString(); + + QCOMPARE(ft->error(), 0); + QCOMPARE(spyDownload.count(), 1); + QCOMPARE(spyUpload.count(), 1); + + QFile resultFile(destFile), originFile(aFile); + QVERIFY(resultFile.open(QIODevice::ReadOnly)); + QVERIFY(originFile.open(QIODevice::ReadOnly)); + + const QByteArray resultContents = resultFile.readAll(), originContents = originFile.readAll(); + QCOMPARE(resultContents.size(), originContents.size()); + QCOMPARE(resultFile.readAll(), originFile.readAll()); + } + + private: + TestDaemon* m_daemon; +}; + +QTEST_MAIN(TestSendFile); + +#include "sendfiletest.moc" diff --git a/app/qml/DeviceDelegate.qml b/tests/testdaemon.h similarity index 54% rename from app/qml/DeviceDelegate.qml rename to tests/testdaemon.h index 2b528ca0..9d45aada 100644 --- a/app/qml/DeviceDelegate.qml +++ b/tests/testdaemon.h @@ -1,56 +1,57 @@ -/* +/** * 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.2 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.1 -import org.kde.kquickcontrolsaddons 2.0 -import org.kde.kdeconnect 1.0 +#ifndef TESTDAEMON_H +#define TESTDAEMON_H -Item +#include +#include + +class TestDaemon : public Daemon { - height: info.height - signal clicked - QIconItem { - id: icon - width: 40 - height: parent.height - icon: iconName - anchors.verticalCenter: parent.verticalCenter +public: + TestDaemon(QObject* parent = Q_NULLPTR) + : Daemon(parent, true) + , m_nam(Q_NULLPTR) + { } - MouseArea { - anchors.fill: parent - onClicked: parent.clicked() + + void reportError(const QString & title, const QString & description) override + { + qWarning() << "error:" << title << description; } - ColumnLayout { - id: info - anchors { - left: icon.right - top: parent.top - right: parent.right - } - property bool expand: false - Label { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: display + "\n" + toolTip + + void askPairingConfirmation(Device * d) override { + d->acceptPairing(); + } + + QNetworkAccessManager* networkAccessManager() override + { + if (!m_nam) { + m_nam = new KIO::AccessManager(this); } + return m_nam; } -} + +private: + QNetworkAccessManager* m_nam; +}; + +#endif diff --git a/tests/testnotificationlistener.cpp b/tests/testnotificationlistener.cpp new file mode 100644 index 00000000..ad7443b3 --- /dev/null +++ b/tests/testnotificationlistener.cpp @@ -0,0 +1,502 @@ +/** + * Copyright 2015 Holger Kaelberer + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/daemon.h" +#include "core/device.h" +#include "core/kdeconnectplugin.h" +#include "kdeconnect-version.h" +#include "plugins/sendnotifications/sendnotificationsplugin.h" +#include "plugins/sendnotifications/notificationslistener.h" +#include "plugins/sendnotifications/notifyingapplication.h" + +// Tweaked NotificationsPlugin for testing +class TestNotificationsPlugin : public SendNotificationsPlugin +{ + Q_OBJECT +public: + explicit TestNotificationsPlugin(QObject* parent, const QVariantList& args) + : SendNotificationsPlugin(parent, args) + { + } + + ~TestNotificationsPlugin() override = default; + + // allow to access notificationsListener for testing: + NotificationsListener* getNotificationsListener() const + { + return notificationsListener; + } + + void setNotificationsListener(NotificationsListener* value) + { + notificationsListener = value; + } + +}; + +// Tweaked Device for testing: +class TestDevice: public Device +{ + Q_OBJECT +private: + int sentPackages; + NetworkPackage* lastPackage; + +public: + explicit TestDevice(QObject* parent, const QString& id) + : Device (parent, id) + , sentPackages(0) + , lastPackage(nullptr) + {} + + ~TestDevice() override + { + delete lastPackage; + } + + int getSentPackages() const + { + return sentPackages; + } + + NetworkPackage* getLastPackage() + { + return lastPackage; + } + + void deleteLastPackage() + { + delete lastPackage; + lastPackage = nullptr; + } + +public Q_SLOTS: + bool sendPackage(NetworkPackage& np) override + { + ++sentPackages; + // copy package manually to allow for inspection (can't use copy-constructor) + deleteLastPackage(); + lastPackage = new NetworkPackage(np.type()); + Q_ASSERT(lastPackage); + for (QVariantMap::ConstIterator iter = np.body().constBegin(); + iter != np.body().constEnd(); iter++) + lastPackage->set(iter.key(), iter.value()); + lastPackage->setPayload(np.payload(), np.payloadSize()); + return true; + } +}; + +// Tweaked NotificationsListener for testing: +class TestedNotificationsListener: public NotificationsListener +{ + Q_OBJECT +public: + explicit TestedNotificationsListener(KdeConnectPlugin* aPlugin) + : NotificationsListener(aPlugin) + {} + + ~TestedNotificationsListener() override + {} + + QHash& getApplications() + { + return m_applications; + } + + void setApplications(const QHash& value) + { + m_applications = value; + } + +protected: + bool parseImageDataArgument(const QVariant& argument, int& width, + int& height, int& rowStride, int& bitsPerSample, + int& channels, bool& hasAlpha, + QByteArray& imageData) const override + { + width = argument.toMap().value(QStringLiteral("width")).toInt(); + height = argument.toMap().value(QStringLiteral("height")).toInt(); + rowStride = argument.toMap().value(QStringLiteral("rowStride")).toInt(); + bitsPerSample = argument.toMap().value(QStringLiteral("bitsPerSample")).toInt(); + channels = argument.toMap().value(QStringLiteral("channels")).toInt(); + hasAlpha = argument.toMap().value(QStringLiteral("hasAlpha")).toBool(); + imageData = argument.toMap().value(QStringLiteral("imageData")).toByteArray(); + return true; + } + +}; + +class TestNotificationListener : public QObject +{ + Q_OBJECT + public: + TestNotificationListener() + : plugin(nullptr) + { + QStandardPaths::setTestModeEnabled(true); + } + + private Q_SLOTS: + void testNotify(); + + private: + TestNotificationsPlugin* plugin; +}; + +void TestNotificationListener::testNotify() +{ + // + // set things up: + // + + QString dId(QStringLiteral("testid")); + TestDevice* d = new TestDevice(nullptr, dId); + + int proxiedNotifications = 0; + QCOMPARE(proxiedNotifications, d->getSentPackages()); + plugin = new TestNotificationsPlugin(this, + QVariantList({ QVariant::fromValue(d), + "notifications_plugin", + {"kdeconnect.notification"}})); + QVERIFY(plugin->getNotificationsListener()); + delete plugin->getNotificationsListener(); + + // inject our tweaked NotificationsListener: + TestedNotificationsListener* listener = new TestedNotificationsListener(plugin); + QVERIFY(listener); + plugin->setNotificationsListener(listener); + QCOMPARE(listener, plugin->getNotificationsListener()); + + // make sure config is default: + plugin->config()->set(QStringLiteral("generalPersistent"), false); + plugin->config()->set(QStringLiteral("generalIncludeBody"), true); + plugin->config()->set(QStringLiteral("generalUrgency"), 0); + QCOMPARE(plugin->config()->get("generalPersistent"), false); + QCOMPARE(plugin->config()->get("generalIncludeBody"), true); + QCOMPARE(plugin->config()->get("generalUrgency"), false); + + // applications are modified directly: + listener->getApplications().clear(); + QCOMPARE(listener->getApplications().count(), 0); + + // + // Go !!! + // + + uint replacesId = 99; + uint retId; + QString appName(QStringLiteral("some-appName")); + QString body(QStringLiteral("some-body")); + QString icon(QStringLiteral("some-icon")); + QString summary(QStringLiteral("some-summary")); + + // regular Notify call that is synchronized ... + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); + // ... should return replacesId, + QCOMPARE(retId, replacesId); + // ... have triggered sending a package + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + // ... with our properties, + QCOMPARE(d->getLastPackage()->get("id"), replacesId); + QCOMPARE(d->getLastPackage()->get("appName"), appName); + QCOMPARE(d->getLastPackage()->get("ticker"), summary + ": " + body); + QCOMPARE(d->getLastPackage()->get("isClearable"), true); + QCOMPARE(d->getLastPackage()->hasPayload(), false); + + // ... and create a new application internally that is initialized correctly: + QCOMPARE(listener->getApplications().count(), 1); + QVERIFY(listener->getApplications().contains(appName)); + QVERIFY(listener->getApplications()[appName].active); + QCOMPARE(listener->getApplications()[appName].name, appName); + QVERIFY(listener->getApplications()[appName].blacklistExpression.pattern().isEmpty()); + QCOMPARE(listener->getApplications()[appName].name, appName); + QCOMPARE(listener->getApplications()[appName].icon, icon); + + // another one, with other timeout and urgency values: + QString appName2(QStringLiteral("some-appName2")); + QString body2(QStringLiteral("some-body2")); + QString icon2(QStringLiteral("some-icon2")); + QString summary2(QStringLiteral("some-summary2")); + + retId = listener->Notify(appName2, replacesId+1, icon2, summary2, body2, {}, {{"urgency", 2}}, 10); + QCOMPARE(retId, replacesId+1); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QCOMPARE(d->getLastPackage()->get("id"), replacesId+1); + QCOMPARE(d->getLastPackage()->get("appName"), appName2); + QCOMPARE(d->getLastPackage()->get("ticker"), summary2 + ": " + body2); + QCOMPARE(d->getLastPackage()->get("isClearable"), false); // timeout != 0 + QCOMPARE(d->getLastPackage()->hasPayload(), false); + QCOMPARE(listener->getApplications().count(), 2); + QVERIFY(listener->getApplications().contains(appName2)); + QVERIFY(listener->getApplications().contains(appName)); + + // if persistent-only is set, timeouts > 0 are not synced: + plugin->config()->set(QStringLiteral("generalPersistent"), true); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 1); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + retId = listener->Notify(appName2, replacesId, icon2, summary2, body2, {}, {{}}, 3); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + // but timeout == 0 is + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + plugin->config()->set(QStringLiteral("generalPersistent"), false); + + // if min-urgency is set, lower urgency levels are not synced: + plugin->config()->set(QStringLiteral("generalUrgency"), 1); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 0}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + // equal urgency is + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 1}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + // higher urgency as well + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 2}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + plugin->config()->set(QStringLiteral("generalUrgency"), 0); + + // notifications for a deactivated application are not synced: + QVERIFY(listener->getApplications().contains(appName)); + listener->getApplications()[appName].active = false; + QVERIFY(!listener->getApplications()[appName].active); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{"urgency", 0}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + // others are still: + retId = listener->Notify(appName2, replacesId+1, icon2, summary2, body2, {}, {{}}, 0); + QCOMPARE(retId, replacesId+1); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + // back to normal: + listener->getApplications()[appName].active = true; + QVERIFY(listener->getApplications()[appName].active); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + + // notifications with blacklisted subjects are not synced: + QVERIFY(listener->getApplications().contains(appName)); + listener->getApplications()[appName].blacklistExpression.setPattern(QStringLiteral("black[12]|foo(bar|baz)")); + retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary black1"), body, {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary foobar"), body, {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + // other subjects are synced: + retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary foo"), body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + retId = listener->Notify(appName, replacesId, icon, QStringLiteral("summary black3"), body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + // also body is checked by blacklist if requested: + plugin->config()->set(QStringLiteral("generalIncludeBody"), true); + retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body black1"), {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body foobaz"), {}, {{}}, 0); + QCOMPARE(retId, 0U); + QCOMPARE(proxiedNotifications, d->getSentPackages()); + // body does not matter if inclusion was not requested: + plugin->config()->set(QStringLiteral("generalIncludeBody"), false); + retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body black1"), {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + // without body, also ticker value is different: + QCOMPARE(d->getLastPackage()->get("ticker"), summary); + retId = listener->Notify(appName, replacesId, icon, summary, QStringLiteral("body foobaz"), {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + + // back to normal: + listener->getApplications()[appName].blacklistExpression.setPattern(QLatin1String("")); + plugin->config()->set(QStringLiteral("generalIncludeBody"), true); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + retId = listener->Notify(appName2, replacesId, icon2, summary2, body2, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + + // icon synchronization: + QStringList iconPaths; + // appIcon + int count = 0; + const QStringList icons = KIconLoader::global()->queryIcons(-KIconLoader::SizeEnormous, KIconLoader::Application); + for (const auto& iconName : icons) { + if (!iconName.endsWith(QLatin1String(".png"))) + continue; + if (count++ > 3) // max 3 iterations + break; + iconPaths.append(iconName); // memorize some paths for later + + // existing icons are sync-ed if requested + plugin->config()->set(QStringLiteral("generalSynchronizeIcons"), true); + QFileInfo fi(iconName); + retId = listener->Notify(appName, replacesId, fi.baseName(), summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()->hasPayload()); + QCOMPARE(d->getLastPackage()->payloadSize(), fi.size()); + // works also with abolute paths + retId = listener->Notify(appName, replacesId, iconName, summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()->hasPayload()); + QCOMPARE(d->getLastPackage()->payloadSize(), fi.size()); + // extensions other than png are not accepted: + retId = listener->Notify(appName, replacesId, iconName + ".svg", summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(!d->getLastPackage()->hasPayload()); + + // if sync not requested no payload: + plugin->config()->set(QStringLiteral("generalSynchronizeIcons"), false); + retId = listener->Notify(appName, replacesId, fi.baseName(), summary, body, {}, {{}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(!d->getLastPackage()->hasPayload()); + QCOMPARE(d->getLastPackage()->payloadSize(), 0); + } + plugin->config()->set(QStringLiteral("generalSynchronizeIcons"), true); + + // image-path in hints + if (iconPaths.size() > 0) { + retId = listener->Notify(appName, replacesId, iconPaths.size() > 1 ? iconPaths[1] : icon, summary, body, {}, {{"image-path", iconPaths[0]}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()->hasPayload()); + QFileInfo hintsFi(iconPaths[0]); + // image-path has priority over appIcon parameter: + QCOMPARE(d->getLastPackage()->payloadSize(), hintsFi.size()); + } + + // image_path in hints + if (iconPaths.size() > 0) { + retId = listener->Notify(appName, replacesId, iconPaths.size() > 1 ? iconPaths[1] : icon, summary, body, {}, {{"image_path", iconPaths[0]}}, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()->hasPayload()); + QFileInfo hintsFi(iconPaths[0]); + // image_path has priority over appIcon parameter: + QCOMPARE(d->getLastPackage()->payloadSize(), hintsFi.size()); + } + + // image-data in hints + // set up: + QBuffer* buffer; + QImage image; + int width = 2, height = 2, rowStride = 4*width, bitsPerSample = 8, + channels = 4; + bool hasAlpha = 1; + char rawData[] = { 0x01, 0x02, 0x03, 0x04, // raw rgba data + 0x11, 0x12, 0x13, 0x14, + 0x21, 0x22, 0x23, 0x24, + 0x31, 0x32, 0x33, 0x34 }; + QVariantMap imageData = {{"width", width}, {"height", height}, {"rowStride", rowStride}, + {"bitsPerSample", bitsPerSample}, {"channels", channels}, + {"hasAlpha", hasAlpha}, {"imageData", QByteArray(rawData, sizeof(rawData))}}; + QVariantMap hints; +#define COMPARE_PIXEL(x, y) \ + QCOMPARE(qRed(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 0]); \ + QCOMPARE(qGreen(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 1]); \ + QCOMPARE(qBlue(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 2]); \ + QCOMPARE(qAlpha(image.pixel(x,y)), (int)rawData[x*4 + y*rowStride + 3]); + + hints.insert(QStringLiteral("image-data"), imageData); + if (iconPaths.size() > 0) + hints.insert(QStringLiteral("image-path"), iconPaths[0]); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, hints, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()->hasPayload()); + buffer = dynamic_cast(d->getLastPackage()->payload().data()); + QCOMPARE(d->getLastPackage()->payloadSize(), buffer->size()); + // image-data is attached as png data + QVERIFY(image.loadFromData(reinterpret_cast(buffer->data().constData()), buffer->size(), "PNG")); + // image-data has priority over image-path: + QCOMPARE(image.byteCount(), rowStride*height); + // rgba -> argb conversion was done correctly: + COMPARE_PIXEL(0,0); + COMPARE_PIXEL(1,0); + COMPARE_PIXEL(0,1); + COMPARE_PIXEL(1,1); + + // same for image_data in hints + hints.clear(); + hints.insert(QStringLiteral("image-data"), imageData); + if (iconPaths.size() > 0) + hints.insert(QStringLiteral("image_path"), iconPaths[0]); + retId = listener->Notify(appName, replacesId, icon, summary, body, {}, hints, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()->hasPayload()); + buffer = dynamic_cast(d->getLastPackage()->payload().data()); + QCOMPARE(d->getLastPackage()->payloadSize(), buffer->size()); + // image-data is attached as png data + QVERIFY(image.loadFromData(reinterpret_cast(buffer->data().constData()), buffer->size(), "PNG")); + // image_data has priority over image_path/image-path: + QCOMPARE(image.byteCount(), rowStride*height); + // rgba -> argb conversion was done correctly: + COMPARE_PIXEL(0,0); + COMPARE_PIXEL(1,0); + COMPARE_PIXEL(0,1); + COMPARE_PIXEL(1,1); + + // same for icon_data, which has lowest priority + hints.clear(); + hints.insert(QStringLiteral("icon_data"), imageData); + retId = listener->Notify(appName, replacesId, QLatin1String(""), summary, body, {}, hints, 0); + QCOMPARE(retId, replacesId); + QCOMPARE(++proxiedNotifications, d->getSentPackages()); + QVERIFY(d->getLastPackage()); + QVERIFY(d->getLastPackage()->hasPayload()); + buffer = dynamic_cast(d->getLastPackage()->payload().data()); + // image-data is attached as png data + QVERIFY(image.loadFromData(reinterpret_cast(buffer->data().constData()), buffer->size(), "PNG")); + QCOMPARE(image.byteCount(), rowStride*height); + // rgba -> argb conversion was done correctly: + COMPARE_PIXEL(0,0); + COMPARE_PIXEL(1,0); + COMPARE_PIXEL(0,1); + COMPARE_PIXEL(1,1); + +#undef COMPARE_PIXEL +} + + +QTEST_GUILESS_MAIN(TestNotificationListener); + +#include "testnotificationlistener.moc" diff --git a/tests/testsocketlinereader.cpp b/tests/testsocketlinereader.cpp index 77a9b4bf..f027dd03 100644 --- a/tests/testsocketlinereader.cpp +++ b/tests/testsocketlinereader.cpp @@ -1,122 +1,122 @@ /************************************************************************************* * Copyright (C) 2014 by Alejandro Fiestas Olivares * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************************/ #include "../core/backends/lan/socketlinereader.h" +#include "../core/backends/lan/server.h" #include -#include -#include +#include #include #include #include -#include class TestSocketLineReader : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void newPackage(); private Q_SLOTS: void socketLineReader(); private: - QTimer mTimer; - QEventLoop mLoop; - QList mPackages; - QTcpServer *mServer; - QTcpSocket *mConn; - SocketLineReader *mReader; + QTimer m_timer; + QEventLoop m_loop; + QList m_packages; + Server* m_server; + QSslSocket* m_conn; + SocketLineReader* m_reader; }; void TestSocketLineReader::initTestCase() { - mServer = new QTcpServer(this); - QVERIFY2(mServer->listen(QHostAddress::LocalHost, 8694), "Failed to create local tcp server"); + m_server = new Server(this); - mTimer.setInterval(4000);//For second is more enough to send some data via local socket - mTimer.setSingleShot(true); - connect(&mTimer, SIGNAL(timeout()), &mLoop, SLOT(quit())); + QVERIFY2(m_server->listen(QHostAddress::LocalHost, 8694), "Failed to create local tcp server"); - mConn = new QTcpSocket(this); - mConn->connectToHost(QHostAddress::LocalHost, 8694); - connect(mConn, SIGNAL(connected()), &mLoop, SLOT(quit())); - mTimer.start(); - mLoop.exec(); + m_timer.setInterval(4000);//For second is more enough to send some data via local socket + m_timer.setSingleShot(true); + connect(&m_timer, &QTimer::timeout, &m_loop, &QEventLoop::quit); - QVERIFY2(mConn->isOpen(), "Could not connect to local tcp server"); + m_conn = new QSslSocket(this); + m_conn->connectToHost(QHostAddress::LocalHost, 8694); + connect(m_conn, &QAbstractSocket::connected, &m_loop, &QEventLoop::quit); + m_timer.start(); + m_loop.exec(); + + QVERIFY2(m_conn->isOpen(), "Could not connect to local tcp server"); } void TestSocketLineReader::socketLineReader() { QList dataToSend; dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n"; - Q_FOREACH(const QByteArray &line, dataToSend) { - mConn->write(line); + for (const QByteArray& line : dataToSend) { + m_conn->write(line); } - mConn->flush(); + m_conn->flush(); int maxAttemps = 5; - while(!mServer->hasPendingConnections() && maxAttemps > 0) { + while(!m_server->hasPendingConnections() && maxAttemps > 0) { --maxAttemps; QTest::qSleep(1000); } - QTcpSocket *sock = mServer->nextPendingConnection(); + QSslSocket* sock = m_server->nextPendingConnection(); - QVERIFY2(sock != 0, "Could not open a connection to the client"); + QVERIFY2(sock != nullptr, "Could not open a connection to the client"); - mReader = new SocketLineReader(sock, this); - connect(mReader, SIGNAL(readyRead()), SLOT(newPackage())); - mTimer.start(); - mLoop.exec(); + m_reader = new SocketLineReader(sock, this); + connect(m_reader, &SocketLineReader::readyRead, this, &TestSocketLineReader::newPackage); + m_timer.start(); + m_loop.exec(); /* remove the empty line before compare */ dataToSend.removeOne("\n"); - QCOMPARE(mPackages.count(), 5);//We expect 5 Packages + QCOMPARE(m_packages.count(), 5);//We expect 5 Packages for(int x = 0;x < 5; ++x) { - QCOMPARE(mPackages[x], dataToSend[x]); + QCOMPARE(m_packages[x], dataToSend[x]); } } void TestSocketLineReader::newPackage() { - if (!mReader->bytesAvailable()) { + if (!m_reader->bytesAvailable()) { return; } int maxLoops = 5; - while(mReader->bytesAvailable() > 0 && maxLoops > 0) { + while(m_reader->bytesAvailable() > 0 && maxLoops > 0) { --maxLoops; - const QByteArray package = mReader->readLine(); + const QByteArray package = m_reader->readLine(); if (!package.isEmpty()) { - mPackages.append(package); + m_packages.append(package); } - if (mPackages.count() == 5) { - mLoop.exit(); + if (m_packages.count() == 5) { + m_loop.exit(); } } } QTEST_GUILESS_MAIN(TestSocketLineReader) #include "testsocketlinereader.moc" diff --git a/tests/testsslsocketlinereader.cpp b/tests/testsslsocketlinereader.cpp new file mode 100644 index 00000000..485cbd29 --- /dev/null +++ b/tests/testsslsocketlinereader.cpp @@ -0,0 +1,310 @@ +/** + * Copyright 2015 Vineet Garg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../core/backends/lan/server.h" +#include "../core/backends/lan/socketlinereader.h" + +#include +#include +#include +#include + +/* + * This class tests the behaviour of socket line reader when the connection if over ssl. Since SSL takes part below application layer, + * working of SocketLineReader should be same. + */ +class TestSslSocketLineReader : public QObject +{ + Q_OBJECT +public Q_SLOTS: + void newPackage(); + +private Q_SLOTS: + + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void testTrustedDevice(); + void testUntrustedDevice(); + void testTrustedDeviceWithWrongCertificate(); + + +private: + const int PORT = 7894; + QTimer m_timer; + QCA::Initializer m_qcaInitializer; + QEventLoop m_loop; + QList m_packages; + Server* m_server; + QSslSocket* m_clientSocket; + SocketLineReader* m_reader; + +private: + void setSocketAttributes(QSslSocket* socket, QString deviceName); +}; + +void TestSslSocketLineReader::initTestCase() +{ + m_server = new Server(this); + + QVERIFY2(m_server->listen(QHostAddress::LocalHost, PORT), "Failed to create local tcp server"); + + m_timer.setInterval(10 * 1000);//Ten second is more enough for this test, just used this so that to break mLoop if stuck + m_timer.setSingleShot(true); + connect(&m_timer, &QTimer::timeout, &m_loop, &QEventLoop::quit); + + m_timer.start(); +} + +void TestSslSocketLineReader::init() +{ + m_clientSocket = new QSslSocket(this); + m_clientSocket->connectToHost(QHostAddress::LocalHost, PORT); + connect(m_clientSocket, &QAbstractSocket::connected, &m_loop, &QEventLoop::quit); + + m_loop.exec(); + + QVERIFY2(m_clientSocket->isOpen(), "Could not connect to local tcp server"); +} + +void TestSslSocketLineReader::cleanup() +{ + m_clientSocket->disconnectFromHost(); + delete m_clientSocket; +} + +void TestSslSocketLineReader::cleanupTestCase() +{ + delete m_server; +} + +void TestSslSocketLineReader::testTrustedDevice() +{ + + int maxAttemps = 5; + QCOMPARE(true, m_server->hasPendingConnections()); + while(!m_server->hasPendingConnections() && maxAttemps > 0) { + --maxAttemps; + QTest::qSleep(1000); + } + + QSslSocket* serverSocket = m_server->nextPendingConnection(); + + QVERIFY2(serverSocket != 0, "Null socket returned by server"); + QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); + + setSocketAttributes(serverSocket, QStringLiteral("Test Server")); + setSocketAttributes(m_clientSocket, QStringLiteral("Test Client")); + + serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); + serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); + serverSocket->addCaCertificate(m_clientSocket->localCertificate()); + + m_clientSocket->setPeerVerifyName(QStringLiteral("Test Server")); + m_clientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); + m_clientSocket->addCaCertificate(serverSocket->localCertificate()); + + connect(m_clientSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); + serverSocket->startServerEncryption(); + m_clientSocket->startClientEncryption(); + m_loop.exec(); + + // Both client and server socket should be encrypted here and should have remote certificate because VerifyPeer is used + QVERIFY2(m_clientSocket->isOpen(), "Client socket already closed"); + QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); + QVERIFY2(m_clientSocket->isEncrypted(), "Client is not encrypted"); + QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted"); + QVERIFY2(!m_clientSocket->peerCertificate().isNull(), "Server certificate not received"); + QVERIFY2(!serverSocket->peerCertificate().isNull(), "Client certificate not received"); + + QList dataToSend; + dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n"; + for (const QByteArray& line : dataToSend) { + m_clientSocket->write(line); + } + m_clientSocket->flush(); + + m_packages.clear(); + + m_reader = new SocketLineReader(serverSocket, this); + connect(m_reader, &SocketLineReader::readyRead, this,&TestSslSocketLineReader::newPackage); + m_loop.exec(); + + /* remove the empty line before compare */ + dataToSend.removeOne("\n"); + + QCOMPARE(m_packages.count(), 5);//We expect 5 Packages + for(int x = 0;x < 5; ++x) { + QCOMPARE(m_packages[x], dataToSend[x]); + } + + delete m_reader; +} + +void TestSslSocketLineReader::testUntrustedDevice() +{ + int maxAttemps = 5; + QCOMPARE(true, m_server->hasPendingConnections()); + while(!m_server->hasPendingConnections() && maxAttemps > 0) { + --maxAttemps; + QTest::qSleep(1000); + } + + QSslSocket* serverSocket = m_server->nextPendingConnection(); + + QVERIFY2(serverSocket != 0, "Null socket returned by server"); + QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); + + setSocketAttributes(serverSocket, QStringLiteral("Test Server")); + setSocketAttributes(m_clientSocket, QStringLiteral("Test Client")); + + serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); + serverSocket->setPeerVerifyMode(QSslSocket::QueryPeer); + + m_clientSocket->setPeerVerifyName(QStringLiteral("Test Server")); + m_clientSocket->setPeerVerifyMode(QSslSocket::QueryPeer); + + connect(m_clientSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); + serverSocket->startServerEncryption(); + m_clientSocket->startClientEncryption(); + m_loop.exec(); + + QVERIFY2(m_clientSocket->isOpen(), "Client socket already closed"); + QVERIFY2(serverSocket->isOpen(), "Server socket already closed"); + QVERIFY2(m_clientSocket->isEncrypted(), "Client is not encrypted"); + QVERIFY2(serverSocket->isEncrypted(), "Server is not encrypted"); + QVERIFY2(!m_clientSocket->peerCertificate().isNull(), "Server certificate not received"); + QVERIFY2(!serverSocket->peerCertificate().isNull(), "Client certificate not received"); + + QList dataToSend; + dataToSend << "foobar\n" << "barfoo\n" << "foobar?\n" << "\n" << "barfoo!\n" << "panda\n"; + for (const QByteArray& line : dataToSend) { + m_clientSocket->write(line); + } + m_clientSocket->flush(); + + m_packages.clear(); + + m_reader = new SocketLineReader(serverSocket, this); + connect(m_reader, &SocketLineReader::readyRead, this, &TestSslSocketLineReader::newPackage); + m_loop.exec(); + + /* remove the empty line before compare */ + dataToSend.removeOne("\n"); + + QCOMPARE(m_packages.count(), 5);//We expect 5 Packages + for(int x = 0;x < 5; ++x) { + QCOMPARE(m_packages[x], dataToSend[x]); + } + + delete m_reader; +} + +void TestSslSocketLineReader::testTrustedDeviceWithWrongCertificate() +{ + int maxAttemps = 5; + while(!m_server->hasPendingConnections() && maxAttemps > 0) { + --maxAttemps; + QTest::qSleep(1000); + } + + QSslSocket* serverSocket = m_server->nextPendingConnection(); + + QVERIFY2(serverSocket != 0, "Could not open a connection to the client"); + + setSocketAttributes(serverSocket, QStringLiteral("Test Server")); + setSocketAttributes(m_clientSocket, QStringLiteral("Test Client")); + + // Not adding other device certificate to list of CA certificate, and using VerifyPeer. This should lead to handshake failure + serverSocket->setPeerVerifyName(QStringLiteral("Test Client")); + serverSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); + + m_clientSocket->setPeerVerifyName(QStringLiteral("Test Server")); + m_clientSocket->setPeerVerifyMode(QSslSocket::VerifyPeer); + + connect(serverSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); // Encrypted signal should never be emitted + connect(m_clientSocket, &QSslSocket::encrypted, &m_loop, &QEventLoop::quit); // Encrypted signal should never be emitted + connect(serverSocket, &QAbstractSocket::disconnected, &m_loop, &QEventLoop::quit); + connect(m_clientSocket, &QAbstractSocket::disconnected, &m_loop, &QEventLoop::quit); + + serverSocket->startServerEncryption(); + m_clientSocket->startClientEncryption(); + m_loop.exec(); + + QVERIFY2(!serverSocket->isEncrypted(), "Server is encrypted, it should not"); + QVERIFY2(!m_clientSocket->isEncrypted(), "lient is encrypted, it should now"); + + if (serverSocket->state() != QAbstractSocket::UnconnectedState) m_loop.exec(); // Wait until serverSocket is disconnected, It should be in disconnected state + if (m_clientSocket->state() != QAbstractSocket::UnconnectedState) m_loop.exec(); // Wait until mClientSocket is disconnected, It should be in disconnected state + + QCOMPARE((int)m_clientSocket->state(), 0); + QCOMPARE((int)serverSocket->state(), 0); + +} + +void TestSslSocketLineReader::newPackage() +{ + if (!m_reader->bytesAvailable()) { + return; + } + + int maxLoops = 5; + while(m_reader->bytesAvailable() > 0 && maxLoops > 0) { + --maxLoops; + const QByteArray package = m_reader->readLine(); + if (!package.isEmpty()) { + m_packages.append(package); + } + + if (m_packages.count() == 5) { + m_loop.exit(); + } + } +} + +void TestSslSocketLineReader::setSocketAttributes(QSslSocket* socket, QString deviceName) { + + QDateTime startTime = QDateTime::currentDateTime(); + QDateTime endTime = startTime.addYears(10); + QCA::CertificateInfo certificateInfo; + certificateInfo.insert(QCA::CommonName,deviceName); + certificateInfo.insert(QCA::Organization,QStringLiteral("KDE")); + certificateInfo.insert(QCA::OrganizationalUnit,QStringLiteral("Kde connect")); + + QCA::CertificateOptions certificateOptions(QCA::PKCS10); + certificateOptions.setSerialNumber(10); + certificateOptions.setInfo(certificateInfo); + certificateOptions.setValidityPeriod(startTime, endTime); + certificateOptions.setFormat(QCA::PKCS10); + + QCA::PrivateKey privKey = QCA::KeyGenerator().createRSA(2048); + QSslCertificate certificate = QSslCertificate(QCA::Certificate(certificateOptions, privKey).toPEM().toLatin1()); + + socket->setPrivateKey(QSslKey(privKey.toPEM().toLatin1(), QSsl::Rsa)); + socket->setLocalCertificate(certificate); + +} + +QTEST_GUILESS_MAIN(TestSslSocketLineReader) + +#include "testsslsocketlinereader.moc" + diff --git a/urlhandler/CMakeLists.txt b/urlhandler/CMakeLists.txt new file mode 100644 index 00000000..d381b8a5 --- /dev/null +++ b/urlhandler/CMakeLists.txt @@ -0,0 +1,13 @@ +ki18n_wrap_ui(telhandler_SRCS dialog.ui) + +add_executable(kdeconnect-handler kdeconnect-handler.cpp ${telhandler_SRCS}) + +target_link_libraries(kdeconnect-handler + kdeconnectinterfaces + Qt5::Widgets + KF5::CoreAddons + KF5::I18n +) + +install(TARGETS kdeconnect-handler ${INSTALL_TARGETS_DEFAULT_ARGS}) +install(PROGRAMS org.kde.kdeconnect.telhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) diff --git a/urlhandler/Messages.sh b/urlhandler/Messages.sh new file mode 100644 index 00000000..1fb14e0e --- /dev/null +++ b/urlhandler/Messages.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$XGETTEXT `find . -name '*.cpp'` -o $podir/kdeconnect-urlhandler.pot diff --git a/urlhandler/dialog.ui b/urlhandler/dialog.ui new file mode 100644 index 00000000..e7708566 --- /dev/null +++ b/urlhandler/dialog.ui @@ -0,0 +1,82 @@ + + + Dialog + + + + 0 + 0 + 420 + 104 + + + + + + + + + Device to open the url with: + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 224 + 80 + + + 157 + 103 + + + + + buttonBox + rejected() + Dialog + reject() + + + 292 + 86 + + + 286 + 103 + + + + + diff --git a/urlhandler/kdeconnect-handler.cpp b/urlhandler/kdeconnect-handler.cpp new file mode 100644 index 00000000..216c0fea --- /dev/null +++ b/urlhandler/kdeconnect-handler.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include "kdeconnect-version.h" +#include "ui_dialog.h" + +/** + * Show only devices that can be shared to + */ +class ShareDevicesProxyModel : public DevicesSortProxyModel +{ +public: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override { + const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); + auto device = qobject_cast(idx.data(DevicesModel::DeviceRole).value()); + return device->supportedPlugins().contains(QStringLiteral("kdeconnect_share")); + } +}; + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + const QString description = i18n("KDE Connect URL handler"); + KAboutData about(QStringLiteral("kdeconnect-urlhandler"), + description, + QStringLiteral(KDECONNECT_VERSION_STRING), + description, + KAboutLicense::GPL, + i18n("(C) 2017 Aleix Pol Gonzalez")); + about.addAuthor( QStringLiteral("Aleix Pol Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org") ); + KAboutData::setApplicationData(about); + + QUrl urlToShare; + { + QCommandLineParser parser; + parser.addPositionalArgument(QStringLiteral("url"), i18n("URL to share")); + parser.addHelpOption(); + about.setupCommandLine(&parser); + parser.process(app); + about.processCommandLine(&parser); + if (parser.positionalArguments().count() != 1) { + parser.showHelp(1); + return 1; + } + + urlToShare = QUrl::fromUserInput(parser.positionalArguments().constFirst()); + } + + DevicesModel model; + model.setDisplayFilter(DevicesModel::Paired | DevicesModel::Reachable); + ShareDevicesProxyModel proxyModel; + proxyModel.setSourceModel(&model); + + QDialog dialog; + dialog.setWindowTitle(urlToShare.toDisplayString()); + Ui::Dialog uidialog; + uidialog.setupUi(&dialog); + uidialog.devicePicker->setModel(&proxyModel); + + if (urlToShare.scheme() == QLatin1String("tel")) { + uidialog.label->setText(i18n("Device to call this phone number with:")); + uidialog.urlLabel->setText(urlToShare.toDisplayString(QUrl::RemoveScheme)); + } else { + uidialog.urlLabel->setText(urlToShare.toDisplayString()); + } + + if (dialog.exec() == QDialog::Accepted) { + QUrl url = urlToShare; + const int currentDeviceIndex = uidialog.devicePicker->currentIndex(); + if(!url.isEmpty() && currentDeviceIndex >= 0) { + const QString device = proxyModel.index(currentDeviceIndex, 0).data(DevicesModel::IdModelRole).toString(); + + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kdeconnect"), "/modules/kdeconnect/devices/"+device+"/share", QStringLiteral("org.kde.kdeconnect.device.share"), QStringLiteral("shareUrl")); + msg.setArguments({ url.toString() }); + blockOnReply(QDBusConnection::sessionBus().asyncCall(msg)); + return 0; + } else { + QTextStream(stderr) << (i18n("Couldn't share %1", url.toString())) << endl; + return 1; + } + } else { + return 1; + } +} diff --git a/urlhandler/org.kde.kdeconnect.telhandler.desktop b/urlhandler/org.kde.kdeconnect.telhandler.desktop new file mode 100644 index 00000000..0ac9296c --- /dev/null +++ b/urlhandler/org.kde.kdeconnect.telhandler.desktop @@ -0,0 +1,34 @@ +[Desktop Entry] +Name=KDE Connect Phone URL Handler +Name[ca]=Gestor d'URL de telèfons del KDE Connect +Name[ca@valencia]=Gestor d'URL de telèfons del KDE Connect +Name[cs]=Nástroj pro práci s URL telefonu v KDE Connect +Name[da]=Telefon-URL-håndtering til KDE Connect +Name[de]=KDE-Connect-Telefon-Dienstprogramm für URLs (Adressen) +Name[el]=KDE Connect χειριστής URL για σύνδεση τηλεφώνου +Name[en_GB]=KDE Connect Phone URL Handler +Name[es]=Controlador de URL de teléfonos de KDE Connect +Name[eu]=KDE Connect-en URL-kudeatzailea +Name[fr]=Gestionnaire d'URL téléphoniques de KDE Connect +Name[gl]=Manexador de URL de teléfono de KDE Connect +Name[it]=Gestore URL del telefono di KDE Connect +Name[nl]=URL behandelaar van KDE-Connect-telefoon +Name[nn]=Telefon-URL-handsamar for KDE Connect +Name[pl]=KDE Connect - obsługa URL telefonu +Name[pt]=Tratamento de URL's Telefónicos do KDE Connect +Name[ru]=Обработчик телефонных ссылок KDE Connect +Name[sr]=КДЕ‑конекцијин руковалац телефонским УРЛ‑овима +Name[sr@ijekavian]=КДЕ‑конекцијин руковалац телефонским УРЛ‑овима +Name[sr@ijekavianlatin]=KDE‑konekcijin rukovalac telefonskim URL‑ovima +Name[sr@latin]=KDE‑konekcijin rukovalac telefonskim URL‑ovima +Name[sv]=KDE-anslut hanterare av telefonwebbadress +Name[tr]=KDE Connect Telefon URL İşleyici +Name[uk]=Обробник телефонних адрес KDE Connect +Name[x-test]=xxKDE Connect Phone URL Handlerxx +Name[zh_CN]=KDE Connect 电话 URL 处理程序 +Exec=kdeconnect-handler %U +Icon=kdeconnect +Type=Application +NoDisplay=true + +MimeType=x-scheme-handler/tel